Compare commits
6 Commits
migrate-se
...
@bigcapita
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b16a7d2f | ||
|
|
fd27136274 | ||
|
|
951e360405 | ||
|
|
d2242b7744 | ||
|
|
4e9b0ae24f | ||
|
|
e5ffb5661a |
@@ -60,114 +60,6 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "kochie",
|
|
||||||
"name": "Robert Koch",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/10809884?v=4",
|
|
||||||
"profile": "https://me.kochie.io",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cschuijt",
|
|
||||||
"name": "Casper Schuijt",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/5460015?v=4",
|
|
||||||
"profile": "http://cschuijt.nl",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "ANasouf",
|
|
||||||
"name": "ANasouf",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/19536487?v=4",
|
|
||||||
"profile": "https://github.com/ANasouf",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "xprnio",
|
|
||||||
"name": "Ragnar Laud",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3042904?v=4",
|
|
||||||
"profile": "https://ragnarlaud.dev",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "asenawritescode",
|
|
||||||
"name": "Asena",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/67445192?v=4",
|
|
||||||
"profile": "https://github.com/asenawritescode",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "benpsnyder",
|
|
||||||
"name": "Ben Snyder",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/707567?v=4",
|
|
||||||
"profile": "https://snyder.tech",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cloudsbird",
|
|
||||||
"name": "Vederis Leunardus",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/13505006?v=4",
|
|
||||||
"profile": "http://vederis.id",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "ccantrell72",
|
|
||||||
"name": "Chris Cantrell",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/104120598?v=4",
|
|
||||||
"profile": "http://www.pivoten.com",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "oleynikd",
|
|
||||||
"name": "Denis",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
|
|
||||||
"profile": "https://github.com/oleynikd",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mittalsam98",
|
|
||||||
"name": "Sachin Mittal",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/42431274?v=4",
|
|
||||||
"profile": "https://myself.vercel.app/",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Champetaman",
|
|
||||||
"name": "Camilo Oviedo",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
|
|
||||||
"profile": "https://www.camilooviedo.com/",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "nklmantey",
|
|
||||||
"name": "Mantey",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/90279429?v=4",
|
|
||||||
"profile": "https://nklmantey.com/",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
54
.env.example
54
.env.example
@@ -8,7 +8,7 @@ MAIL_FROM_NAME=
|
|||||||
MAIL_FROM_ADDRESS=
|
MAIL_FROM_ADDRESS=
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
DB_HOST=localhost
|
DB_HOST=mysql
|
||||||
DB_USER=bigcapital
|
DB_USER=bigcapital
|
||||||
DB_PASSWORD=bigcapital
|
DB_PASSWORD=bigcapital
|
||||||
DB_ROOT_PASSWORD=root
|
DB_ROOT_PASSWORD=root
|
||||||
@@ -48,55 +48,5 @@ SIGNUP_DISABLED=false
|
|||||||
SIGNUP_ALLOWED_DOMAINS=
|
SIGNUP_ALLOWED_DOMAINS=
|
||||||
SIGNUP_ALLOWED_EMAILS=
|
SIGNUP_ALLOWED_EMAILS=
|
||||||
|
|
||||||
# Sign-up Email Confirmation
|
|
||||||
SIGNUP_EMAIL_CONFIRMATION=false
|
|
||||||
|
|
||||||
# API rate limit (points,duration,block duration).
|
# API rate limit (points,duration,block duration).
|
||||||
API_RATE_LIMIT=120,60,600
|
API_RATE_LIMIT=120,60,600
|
||||||
|
|
||||||
# Gotenberg API for PDF printing - (production).
|
|
||||||
GOTENBERG_URL=http://gotenberg:3000
|
|
||||||
GOTENBERG_DOCS_URL=http://server:3000/public/
|
|
||||||
|
|
||||||
# Gotenberg API - (development)
|
|
||||||
# GOTENBERG_URL=http://localhost:9000
|
|
||||||
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
|
|
||||||
|
|
||||||
# Exchange Rate Service
|
|
||||||
EXCHANGE_RATE_SERVICE=open-exchange-rate
|
|
||||||
|
|
||||||
# Open Exchange Rate
|
|
||||||
OPEN_EXCHANGE_RATE_APP_ID=
|
|
||||||
|
|
||||||
# The Plaid environment to use ('sandbox' or 'development').
|
|
||||||
# https://plaid.com/docs/#api-host
|
|
||||||
PLAID_ENV=sandbox
|
|
||||||
|
|
||||||
# Your Plaid keys, which can be found in the Plaid Dashboard.
|
|
||||||
# https://dashboard.plaid.com/account/keys
|
|
||||||
PLAID_CLIENT_ID=
|
|
||||||
PLAID_SECRET=
|
|
||||||
PLAID_LINK_WEBHOOK=
|
|
||||||
|
|
||||||
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
|
||||||
LEMONSQUEEZY_API_KEY=
|
|
||||||
LEMONSQUEEZY_STORE_ID=
|
|
||||||
LEMONSQUEEZY_WEBHOOK_SECRET=
|
|
||||||
|
|
||||||
# S3 documents and attachments
|
|
||||||
S3_REGION=US
|
|
||||||
S3_ACCESS_KEY_ID=
|
|
||||||
S3_SECRET_ACCESS_KEY=
|
|
||||||
S3_ENDPOINT=
|
|
||||||
S3_BUCKET=
|
|
||||||
|
|
||||||
# PostHog
|
|
||||||
POSTHOG_API_KEY=
|
|
||||||
POSTHOG_HOST=
|
|
||||||
|
|
||||||
# Stripe Payment
|
|
||||||
STRIPE_PAYMENT_SECRET_KEY=
|
|
||||||
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
|
||||||
STRIPE_PAYMENT_CLIENT_ID=
|
|
||||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
|
||||||
STRIPE_PAYMENT_REDIRECT_URL=
|
|
||||||
94
.github/workflows/build-deploy-container.yml
vendored
94
.github/workflows/build-deploy-container.yml
vendored
@@ -6,66 +6,43 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
|
REGISTRY: ghcr.io
|
||||||
SERVER_IMAGE_NAME: bigcapitalhq/server
|
WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp
|
||||||
|
SERVER_IMAGE_NAME: bigcapital/bigcapital-server
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-publish-webapp:
|
build-publish-webapp:
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
name: Build and deploy webapp container
|
name: Build and deploy webapp container
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: production
|
environment: production
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
# Login to Container registry.
|
# Login to Container registry.
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||||
with:
|
with:
|
||||||
images: ${{ env.WEBAPP_IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }}
|
||||||
|
|
||||||
# Builds and push the Docker image.
|
# Builds and push the Docker image.
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v2
|
||||||
id: build
|
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: .
|
||||||
file: ./packages/webapp/Dockerfile
|
file: ./packages/webapp/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
push: true
|
||||||
push: true
|
tags: ghcr.io/bigcapitalhq/webapp:latest
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-webapp
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
# Send notification to Slack channel.
|
# Send notification to Slack channel.
|
||||||
- name: Slack Notification built and published webapp container successfully.
|
- name: Slack Notification built and published webapp container successfully.
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@v2
|
||||||
@@ -76,52 +53,29 @@ jobs:
|
|||||||
name: Build and deploy server container
|
name: Build and deploy server container
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- name: Checkout code
|
||||||
run: |
|
uses: actions/checkout@v2
|
||||||
platform=${{ matrix.platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
# Login to Container registry.
|
# Login to Container registry.
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
# Builds and push the Docker image.
|
# Builds and push the Docker image.
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v2
|
||||||
id: build
|
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./packages/server/Dockerfile
|
file: ./packages/server/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
push: true
|
||||||
tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}}
|
tags: ghcr.io/bigcapitalhq/server:latest
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-server
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
# Send notification to Slack channel.
|
# Send notification to Slack channel.
|
||||||
- name: Slack Notification built and published server container successfully.
|
- name: Slack Notification built and published server container successfully.
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@v2
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# This workflow will build a docker container, publish it to Github Registry.
|
|
||||||
name: Build and Deploy Develop Docker Container
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
|
|
||||||
env:
|
|
||||||
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
|
|
||||||
SERVER_IMAGE_NAME: bigcapitalhq/server
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-publish-webapp:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
name: Build and deploy webapp container
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: production
|
|
||||||
steps:
|
|
||||||
- name: Prepare
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
# Login to Container registry.
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
|
||||||
with:
|
|
||||||
images: ${{ env.WEBAPP_IMAGE_NAME }}
|
|
||||||
|
|
||||||
# Builds and push the Docker image.
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
context: ./
|
|
||||||
file: ./packages/webapp/Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
tags: bigcapitalhq/webapp:develop
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-webapp
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
# Send notification to Slack channel.
|
|
||||||
- name: Slack Notification built and published webapp container successfully.
|
|
||||||
uses: rtCamp/action-slack-notify@v2
|
|
||||||
env:
|
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
|
||||||
|
|
||||||
build-publish-server:
|
|
||||||
name: Build and deploy server container
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Prepare
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
# Login to Container registry.
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
# Builds and push the Docker image.
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
context: ./
|
|
||||||
file: ./packages/server/Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: bigcapitalhq/server:develop
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-server
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
# Send notification to Slack channel.
|
|
||||||
- name: Slack Notification built and published server container successfully.
|
|
||||||
uses: rtCamp/action-slack-notify@v2
|
|
||||||
env:
|
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
|
||||||
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
@@ -8,14 +8,14 @@ on:
|
|||||||
- '**.ts'
|
- '**.ts'
|
||||||
- '**.tsx'
|
- '**.tsx'
|
||||||
- '**/tsconfig.json'
|
- '**/tsconfig.json'
|
||||||
- 'pnpm-lock.yaml'
|
- 'yarn.lock'
|
||||||
- '.github/workflows/e2e.yml'
|
- '.github/workflows/e2e.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '**.ts'
|
- '**.ts'
|
||||||
- '**.tsx'
|
- '**.tsx'
|
||||||
- '**/tsconfig.json'
|
- '**/tsconfig.json'
|
||||||
- 'pnpm-lock.yaml'
|
- 'yarn.lock'
|
||||||
- '.github/workflows/e2e.yml'
|
- '.github/workflows/e2e.yml'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
22
.gitpod.yml
22
.gitpod.yml
@@ -1,22 +0,0 @@
|
|||||||
tasks:
|
|
||||||
- name: Init
|
|
||||||
init: |
|
|
||||||
pnpm install &&
|
|
||||||
cp .env.example .env &&
|
|
||||||
docker-compose up -d &&
|
|
||||||
pnpm run build:server &&
|
|
||||||
node packages/server/build/commands.js system:migrate:latest
|
|
||||||
command: |
|
|
||||||
docker-compose up -d &&
|
|
||||||
pnpm run dev
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- port: 4000
|
|
||||||
visibility: public
|
|
||||||
onOpen: open-preview
|
|
||||||
- port: 3000
|
|
||||||
visibility: public
|
|
||||||
onOpen: ignore
|
|
||||||
- port: 3306
|
|
||||||
visibility: public
|
|
||||||
onOpen: ignore
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
pnpx commitlint --edit
|
yarn commitlint --edit
|
||||||
|
|||||||
415
CHANGELOG.md
415
CHANGELOG.md
@@ -2,412 +2,11 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
# [0.22.0]
|
# [0.10.1] - 25-09-2023
|
||||||
|
|
||||||
* feat: estimate, receipt, credit note mail preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/757
|
|
||||||
* feat: Add discount to transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/758
|
|
||||||
* fix: update financial forms to use new formatted amount utilities and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/760
|
|
||||||
* fix: total lines style by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/761
|
|
||||||
* fix: discount & adjustment sale transactions bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/762
|
|
||||||
* fix: discount transactions GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/763
|
|
||||||
|
|
||||||
# [0.21.2]
|
|
||||||
|
|
||||||
* hotbug: upload attachments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/755
|
|
||||||
|
|
||||||
# [0.21.1]
|
|
||||||
|
|
||||||
* fix: download invoice document on payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/750
|
|
||||||
* fix: attach branding template attrs to payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/751
|
|
||||||
* fix: make manual entries adjust decimal credit/debit amounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/754
|
|
||||||
* feat: allow quantity of entries accept decimal value by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/753
|
|
||||||
|
|
||||||
# [0.21.0]
|
|
||||||
|
|
||||||
* fix: Credit and debit totals not balancing when decimal values are used by @nklmantey in https://github.com/bigcapitalhq/bigcapital/pull/722
|
|
||||||
* docs: add nklmantey as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/725
|
|
||||||
* feat: track more services events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/721
|
|
||||||
* feat: Invoice mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/723
|
|
||||||
* fix: change the send mail button on invoice drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/730
|
|
||||||
* refactor: notification mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/731
|
|
||||||
* fix: attach payment link in sending invoice mail receipt by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/732
|
|
||||||
* fix: send invoice drawer layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/733
|
|
||||||
* fix: hook up cc and bcc fields to mail sender by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/734
|
|
||||||
* fix: company logo does not show up in mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/736
|
|
||||||
* fix: change default invoice mail message by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/737
|
|
||||||
* fix: typing invoice send mail fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/738
|
|
||||||
* fix: clean up ivnoice mail receipt preview component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/739
|
|
||||||
* feat: add shared package to pdf templates to render in the server and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/735
|
|
||||||
* feat: getting invoice preview on send mail view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/740
|
|
||||||
* fix: style SSR invoice paper template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/741
|
|
||||||
* fix: send invoice receipt addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/742
|
|
||||||
* fix: due invoice server invoice by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/744
|
|
||||||
* fix: `BIG-265` forgot password text by @ibutiti in https://github.com/bigcapitalhq/bigcapital/pull/745
|
|
||||||
* Crims on sv translation by @Crims-on in https://github.com/bigcapitalhq/bigcapital/pull/671
|
|
||||||
* feat: Added Spanish language to the App 🇪🇸 by @angelosorno in https://github.com/bigcapitalhq/bigcapital/pull/530
|
|
||||||
* fix: mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/746
|
|
||||||
* fix: company logo of the template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/747
|
|
||||||
* fix: monorepo dependencies scope by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/748
|
|
||||||
|
|
||||||
# [0.20.6]
|
|
||||||
|
|
||||||
* fix: Import category column of item resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/710
|
|
||||||
* fix: Parse the uppercase values in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/711
|
|
||||||
* chore: Move i18nApply localization to the account transformer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/713
|
|
||||||
* fix: Sync Plaid credit card account type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/714
|
|
||||||
* fix: Sync account normal of cashflow GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/715
|
|
||||||
* feat: Add quantity column to pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/716
|
|
||||||
* feat: Pre-line invoice statements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/717
|
|
||||||
* feat: Invoice number in downloaded pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/718
|
|
||||||
* feat: Track events of pdf documents views by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/719
|
|
||||||
* fix: Customer note does not appear in pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/720
|
|
||||||
|
|
||||||
# [0.20.5]
|
|
||||||
|
|
||||||
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
|
|
||||||
* fix: Invoice form layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/705
|
|
||||||
* refactor: Invoice, estimate, receipt, credit note and payment received date input fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/707
|
|
||||||
* feat: Add customize templates button to edit forms by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/708
|
|
||||||
* feat: Track account, invoice and item viewed events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/709
|
|
||||||
|
|
||||||
# [0.20.4]
|
|
||||||
|
|
||||||
* fix: Delete company logo from the PDF template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/699
|
|
||||||
* fix: Set max width/height to company logo of pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/700
|
|
||||||
|
|
||||||
# [0.20.3]
|
|
||||||
|
|
||||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
|
||||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
|
||||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
|
||||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
|
||||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
|
||||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
|
||||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
|
||||||
|
|
||||||
# [0.20.2]
|
|
||||||
|
|
||||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
|
||||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
|
||||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
|
||||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
|
||||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
|
||||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
|
||||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
|
||||||
|
|
||||||
# [0.20.1]
|
|
||||||
|
|
||||||
* fix: Getting uploaded object uri by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/684
|
|
||||||
|
|
||||||
# [0.20.0]
|
|
||||||
|
|
||||||
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
|
||||||
* feat: Onboard accounts to Stripe Connect by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/668
|
|
||||||
* feat: Upload company logo to invoice templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/670
|
|
||||||
* fix: Invoice pdf customize by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/672
|
|
||||||
* fix: Invoice customize bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/673
|
|
||||||
* feat: Clean up payment links endpoints by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/674
|
|
||||||
* feat: Hook up company logo to server-side pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/675
|
|
||||||
* feat: Company branding preferences by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/677
|
|
||||||
* feat: Pdf templates customer/company addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/678
|
|
||||||
* fix: Listen to Stripe session completed event by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/679
|
|
||||||
* feat: Track pdf templates Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/680
|
|
||||||
* fix: Branding customize content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/681
|
|
||||||
* feat: Listen to Stripe integration events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/682
|
|
||||||
* feat: Hook up customer/company address to invoice preview of payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/683
|
|
||||||
|
|
||||||
# [0.19.17]
|
|
||||||
|
|
||||||
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
|
|
||||||
|
|
||||||
# [0.19.16]
|
|
||||||
|
|
||||||
* feat: Tracking more Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/653
|
|
||||||
* fix: Expense cannot accept credit card as payment account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/654
|
|
||||||
* fix: Suspense the lazy loaded components in banking pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/657
|
|
||||||
* feat: Add help dropdown menu by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/656
|
|
||||||
* feat: Bank pages layout breaking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/658
|
|
||||||
* feat: Datatable UI improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/655
|
|
||||||
* fix: Array cast of recognize function rule ids by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/660
|
|
||||||
* fix: Payment made filling the form full amount field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/661
|
|
||||||
* feat: Tabular number of all money columns by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/659
|
|
||||||
* refactor: The expense G/L writer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/662
|
|
||||||
|
|
||||||
## [0.19.15] -
|
|
||||||
|
|
||||||
* fix: Bank transactions infinity scrolling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/648
|
|
||||||
* feat: Integrate multiple branches and warehouses to resource importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/645
|
|
||||||
* fix: Integrate multiple branches with expense resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/649
|
|
||||||
* feat: Cover more tracking events. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/650
|
|
||||||
* feat: Track banking service events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/651
|
|
||||||
|
|
||||||
## [0.19.14]
|
|
||||||
|
|
||||||
* fix: Import bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/643
|
|
||||||
* fix: Set default index to transaction entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/644
|
|
||||||
* feat(server): Events tracking using Posthog by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/646
|
|
||||||
|
|
||||||
## [0.19.13]
|
|
||||||
|
|
||||||
* fix: Subscription middleware by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/624
|
|
||||||
* fix: Getting the sheet columns in import sheet by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/641
|
|
||||||
|
|
||||||
## [0.19.12]
|
|
||||||
|
|
||||||
* fix: Typo one-click demo page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/640
|
|
||||||
|
|
||||||
## [0.19.11]
|
|
||||||
|
|
||||||
* fix: Avoid running the cost job in import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/635
|
|
||||||
* fix: Debounce scheduling calculating items cost by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/634
|
|
||||||
* fix: Expand the resources export page size limitation by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/636
|
|
||||||
* feat: Optimize loading perf. by splitting big chunks and lazy loading them by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/632
|
|
||||||
* fix: Use standard ISO 8601 format for exported data by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/638
|
|
||||||
* fix: Add customer type to customers resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/639
|
|
||||||
|
|
||||||
## [0.19.10]
|
|
||||||
|
|
||||||
* fix: Add subscription plans offer text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/629
|
|
||||||
|
|
||||||
## [0.19.9]
|
|
||||||
|
|
||||||
* fix: Make webapp package env variables dynamic by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/628
|
|
||||||
|
|
||||||
## [v0.19.8]
|
|
||||||
|
|
||||||
* fix: Cannot import items income and cost accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/617
|
|
||||||
* fix: Some bank account details hidden by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/618
|
|
||||||
* feat(ee): One-click demo account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/616
|
|
||||||
* feat: change banking service language by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/619
|
|
||||||
* feat(banking): Filter uncategorized bank transactions by date by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/590
|
|
||||||
* Fix: Syntax error caused error by @wolone in https://github.com/bigcapitalhq/bigcapital/pull/622
|
|
||||||
* fix: Listen to payment webhooks by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/623
|
|
||||||
* fix: Add prefix J-00001 to manual journals increments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/625
|
|
||||||
* fix: Disable sms service until Twilo integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/626
|
|
||||||
* fix: Style tweaks in onboarding page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/627
|
|
||||||
|
|
||||||
## [0.19.5] - 18-08-2024
|
|
||||||
|
|
||||||
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
|
||||||
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
|
||||||
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
|
|
||||||
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
|
|
||||||
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
|
|
||||||
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
|
|
||||||
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
|
|
||||||
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
|
|
||||||
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
|
|
||||||
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
|
|
||||||
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
|
|
||||||
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
|
|
||||||
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
|
|
||||||
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
|
|
||||||
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
|
|
||||||
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
|
|
||||||
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
|
||||||
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
|
||||||
|
|
||||||
## [v0.19.4]
|
|
||||||
|
|
||||||
* feat: Import and export tax rates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/591
|
|
||||||
* feat: Un-categorize bank transactions in bulk by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/587
|
|
||||||
* feat: Pending bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/589
|
|
||||||
* fix: Update `dev` Script in `package.json` to Use `cross-env` by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/588
|
|
||||||
* fix: Should not load branches on reconcile matching form if the branches not enabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/592
|
|
||||||
* fix: Rounding the total amount the pending and matched transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/593
|
|
||||||
|
|
||||||
## [v0.18.0] - 10-08-2024
|
|
||||||
|
|
||||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
|
||||||
* feat: Categorize & match bank transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
|
||||||
* feat: Reconcile match transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/522
|
|
||||||
* fix: Issues in matching transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/523
|
|
||||||
* fix: Cashflow transactions types by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/524
|
|
||||||
|
|
||||||
## [v0.17.5] - 17-06-2024
|
|
||||||
|
|
||||||
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
|
||||||
* fix: add space between buttons on floating actions bar by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/508
|
|
||||||
* feat: Migrating to Envoy proxy instead of Nginx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/509
|
|
||||||
* fix: Disable email confirmation does not work with invited users by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/497
|
|
||||||
* feat: Setting up the date format in the whole system dates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/506
|
|
||||||
|
|
||||||
## [0.17.0] - 04-06-2024
|
|
||||||
|
|
||||||
### New
|
|
||||||
|
|
||||||
* feat: Upload and attach documents by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/461
|
|
||||||
* feat: Export resource tables to pdf by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/460
|
|
||||||
* feat: Build and deploy develop Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/476
|
|
||||||
* feat: Internal docker virtual network by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/478
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* fix: Skip send confirmation email if disabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/459
|
|
||||||
* fix: Lemon Squeezy redirect to base url by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/479
|
|
||||||
* fix: Organize Plaid env variables for development and sandbox envs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/480
|
|
||||||
* fix: Plaid syncs deposit imports as withdrawals by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/481
|
|
||||||
* fix: Validate the s3 configures exist by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/482
|
|
||||||
* fix: Run migrations only for initialized tenants by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/484
|
|
||||||
|
|
||||||
## [0.16.16] -
|
|
||||||
|
|
||||||
* feat: handle http exceptions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/456
|
|
||||||
* feat: add the missing Newrelic env vars to docker-compose.prod file by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/457
|
|
||||||
* fix: add the signup email confirmation env var by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/458
|
|
||||||
|
|
||||||
## [0.16.14] -
|
|
||||||
|
|
||||||
* fix: Typo in setup wizard by @ccantrell72 in https://github.com/bigcapitalhq/bigcapital/pull/440
|
|
||||||
* fix: Showing the real mail address on email confirmation view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/445
|
|
||||||
* fix: Auto-increment setting parsing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/453
|
|
||||||
|
|
||||||
## [0.16.12] -
|
|
||||||
|
|
||||||
* feat: Create a manifest list for `webapp` Docker image and push it to DockerHub. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/436
|
|
||||||
* feat: Combine arm64 and amd64 in one Github action runner by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/437
|
|
||||||
|
|
||||||
## [0.16.11] - 06-05-2024
|
|
||||||
|
|
||||||
### improvements
|
|
||||||
|
|
||||||
* feat: Export resource data to csv, xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/430
|
|
||||||
* feat: User email verification after signing-up. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/426
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
* feat(repo): upgrade to latest lerna v8 and pnpm v9 by @benpsnyder in https://github.com/bigcapitalhq/bigcapital/pull/414
|
|
||||||
* feat: Update Docker Build-Push Action and Add ARM64 Support by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/412
|
|
||||||
* feat: Pushing docker containers by version tag by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/421
|
|
||||||
|
|
||||||
## [0.16.10]
|
|
||||||
|
|
||||||
* fix: Running migration Docker container on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/432
|
|
||||||
|
|
||||||
## [0.16.9]
|
|
||||||
|
|
||||||
* feat: New Relic for tracking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/429
|
|
||||||
|
|
||||||
## [0.16.8]
|
|
||||||
|
|
||||||
* feat: Ability to enable/disable the bank connect feature by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/423
|
|
||||||
|
|
||||||
## [0.16.6]
|
|
||||||
|
|
||||||
* hotfix: fix the subscription plan when subscribe on cloud by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/422
|
|
||||||
|
|
||||||
## [0.16.5]
|
|
||||||
|
|
||||||
IMPORTANT: If you upgraded to the v0.16 recently you should upgrade to v0.16.4 as soon as possible, because there're some breaking changes affected the sign-in and some users reported couldn't sign-in.
|
|
||||||
|
|
||||||
* feat: Seed free subscription to tenants that have no subscription. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/410
|
|
||||||
|
|
||||||
## [0.16.3]
|
|
||||||
|
|
||||||
* feat: Integrate Lemon Squeezy payment by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/402
|
|
||||||
* feat: optimize the onboarding subscription experience. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/404
|
|
||||||
* feat: subscription page content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/405
|
|
||||||
* feat: auto subscribe to free plan once signup on community version. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/406
|
|
||||||
* chore: add default value to env variable by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/407
|
|
||||||
* fix: absolute storage imports path. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/408
|
|
||||||
|
|
||||||
## [0.16.0]
|
|
||||||
|
|
||||||
* feat: add convert to invoice button on estimate drawer toolbar by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/361
|
|
||||||
* feat(webapp): add mark as delivered to action bar of invoice details … by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/360
|
|
||||||
* feat(webapp): Dialog to choose the bank service provider by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/378
|
|
||||||
* feat: Categorize the bank synced transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/377
|
|
||||||
* feat: uncategorize the cashflow transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/381
|
|
||||||
* Import resources from csv/xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/382
|
|
||||||
* feat(webapp): import resource UI by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/386
|
|
||||||
* fix: import resources improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/388
|
|
||||||
* feat: add sample sheet to accounts and bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/389
|
|
||||||
* fix: show the unique row value in the import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/392
|
|
||||||
* feat: advanced parser for numeric and boolean import values by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/394
|
|
||||||
* feat: validate the given imported sheet whether is empty by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/395
|
|
||||||
* feat: linking relation with id in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/393
|
|
||||||
* feat: Aggregate rows import by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/396
|
|
||||||
* feat: clean up the imported temp files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/400
|
|
||||||
* feat: add hints to import fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/401
|
|
||||||
|
|
||||||
## [0.15.0]
|
|
||||||
|
|
||||||
* feat: Printing financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/363
|
|
||||||
* feat: Convert invoice status after sending mail notification by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/332
|
|
||||||
* feat: Bigcapital <> Plaid Integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/346
|
|
||||||
* fix: Broken transactions by vendor report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/369
|
|
||||||
* fix: Optimize the print style some financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/370
|
|
||||||
|
|
||||||
## [0.14.0] - 30-01-2024
|
|
||||||
|
|
||||||
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
|
|
||||||
* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339
|
|
||||||
* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340
|
|
||||||
* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335
|
|
||||||
* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342
|
|
||||||
|
|
||||||
## [0.13.3] - 22-01-2024
|
|
||||||
|
|
||||||
* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329
|
|
||||||
|
|
||||||
## [0.13.2] - 21-01-2024
|
|
||||||
|
|
||||||
* feat: show customer / vendor balance. by @asenawritescode in https://github.com/bigcapitalhq/bigcapital/pull/311
|
|
||||||
* feat: inventory valuation csv and xlsx export by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/308
|
|
||||||
* feat: sales by items export csv & xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/310
|
|
||||||
* fix(server): the invoice and payment receipt printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/315
|
|
||||||
* fix: get cashflow transaction broken cause transaction type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/318
|
|
||||||
* fix: `AccountActivateAlert` import by @xprnio in https://github.com/bigcapitalhq/bigcapital/pull/322
|
|
||||||
|
|
||||||
## [0.13.1] - 15-01-2024
|
|
||||||
|
|
||||||
* feat(webapp): add approve/reject to action bar of estimate details dr… by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/304
|
|
||||||
* docs: add ANasouf as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/305
|
|
||||||
* feat: Export general ledger & Journal to CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/303
|
|
||||||
* feat: Auto re-calculate the items rate once changing the invoice exchange rate. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/270
|
|
||||||
|
|
||||||
## [0.13.0] - 31-12-2023
|
|
||||||
|
|
||||||
* feat: Send an invoice mail the customer email by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/292
|
|
||||||
* fix: Allow non-numeric postal codes by @cschuijt in https://github.com/bigcapitalhq/bigcapital/pull/294
|
|
||||||
* docs: add cschuijt as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/295
|
|
||||||
|
|
||||||
## [0.12.1] - 17-11-2023
|
|
||||||
|
|
||||||
* feat: Add default customer message and terms conditions to the transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/291
|
|
||||||
* fix: The currency code of transaction tax rate entry by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/293
|
|
||||||
|
|
||||||
## [0.12.0] - 04-11-2023
|
|
||||||
|
|
||||||
* feat: Export reports via CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/286
|
|
||||||
* fix: Axios upgrade by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/288
|
|
||||||
* fix(server): Allow decimal amount in sale/purchase transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/289
|
|
||||||
* feat: Optimize invoice documents printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/280
|
|
||||||
* chore(deps): bump axios from 0.20.0 to 1.6.0 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/284
|
|
||||||
* chore(deps): bump axios from 0.20.0 to 1.6.0 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/283
|
|
||||||
|
|
||||||
## [0.11.0] - 28-10-2023
|
|
||||||
|
|
||||||
* feat: Migrate to pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/253
|
|
||||||
* feat: Integrate tax rates to bills by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/260
|
|
||||||
* feat: Assign default sell/purchase tax rates to items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/261
|
|
||||||
* chore(deps-dev): bump @babel/traverse from 7.23.0 to 7.23.2 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/272
|
|
||||||
* feat: Improve financial statements rows color by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/276
|
|
||||||
* fix: Trial balance sheet adjusted balance by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/273
|
|
||||||
* feat: Adds tax numbers to organization and customers by @kochie in https://github.com/bigcapitalhq/bigcapital/pull/269
|
|
||||||
* docs: Add kochie as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/277
|
|
||||||
* feat: Computed Net Income under Equity in Balance Sheet report. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/271
|
|
||||||
* fix: Change Dockerfile files with new pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/278
|
|
||||||
|
|
||||||
## [0.10.2] - 02-10-2023
|
|
||||||
|
|
||||||
fix(webapp): Disable tax rates from item entries editor table services do not support tax rates (https://github.com/bigcapitalhq/bigcapital/commit/69afa07e3ba45495a4cab3490c15f2b0c40c4790) by @abouolia
|
|
||||||
fix(server): Add missing method in ItemEntry model (https://github.com/bigcapitalhq/bigcapital/commit/07628ddc37f46c98959ced0323f28752e0a98944) by @abouolia
|
|
||||||
|
|
||||||
## [0.10.1] - 25-09-2023
|
|
||||||
|
|
||||||
* Fix: Running tenants migration on Docker migration container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/242
|
* Fix: Running tenants migration on Docker migration container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/242
|
||||||
|
|
||||||
## [0.10.0] - 24-09-2023
|
# [0.10.0] - 24-09-2023
|
||||||
|
|
||||||
* Added: Tax rates service by @abouolia @elforjani13 in https://github.com/bigcapitalhq/bigcapital/pull/204
|
* Added: Tax rates service by @abouolia @elforjani13 in https://github.com/bigcapitalhq/bigcapital/pull/204
|
||||||
* Added: Sales Tax Liability Summary report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/204
|
* Added: Sales Tax Liability Summary report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/204
|
||||||
@@ -417,7 +16,7 @@ fix(server): Add missing method in ItemEntry model (https://github.com/bigcapita
|
|||||||
* chore(deps): bump word-wrap from 1.2.3 to 1.2.4 in /packages/webapp by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/199
|
* chore(deps): bump word-wrap from 1.2.3 to 1.2.4 in /packages/webapp by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/199
|
||||||
* chore(deps): bump mongoose from 5.13.15 to 5.13.20 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/197
|
* chore(deps): bump mongoose from 5.13.15 to 5.13.20 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/197
|
||||||
|
|
||||||
## [0.9.12] - 29-08-2023
|
# [0.9.12] - 29-08-2023
|
||||||
|
|
||||||
* Refactor: split the services to multiple service classes. (by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/202)
|
* Refactor: split the services to multiple service classes. (by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/202)
|
||||||
* Fix: create quick customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/206
|
* Fix: create quick customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/206
|
||||||
@@ -431,18 +30,18 @@ fix(server): Add missing method in ItemEntry model (https://github.com/bigcapita
|
|||||||
* Fix: filter by customers, vendors and items in reports do not work by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/224
|
* Fix: filter by customers, vendors and items in reports do not work by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/224
|
||||||
https://github.com/bigcapitalhq/bigcapital/pull/225
|
https://github.com/bigcapitalhq/bigcapital/pull/225
|
||||||
|
|
||||||
## [0.9.11] - 23-07-2023
|
# [0.9.11] - 23-07-2023
|
||||||
|
|
||||||
* added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198
|
* added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198
|
||||||
* fix: Expose and expand the rate limit to the env variables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/195
|
* fix: Expose and expand the rate limit to the env variables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/195
|
||||||
|
|
||||||
## [0.9.10] - 18-07-2023
|
# [0.9.10] - 18-07-2023
|
||||||
|
|
||||||
* feat(e2e): E2E onboarding process by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/176
|
* feat(e2e): E2E onboarding process by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/176
|
||||||
* fix(webapp): Show loading message of cost computing job on financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/196
|
* fix(webapp): Show loading message of cost computing job on financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/196
|
||||||
* fix(webapp): Change the currency code of sales and purchases transactions with foreign contacts.
|
* fix(webapp): Change the currency code of sales and purchases transactions with foreign contacts.
|
||||||
|
|
||||||
## [0.9.9] - 28-06-2023
|
# [0.9.9] - 28-06-2023
|
||||||
|
|
||||||
* refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171
|
* refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171
|
||||||
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
|
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
|
||||||
@@ -454,7 +53,7 @@ https://github.com/bigcapitalhq/bigcapital/pull/225
|
|||||||
* fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179
|
* fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179
|
||||||
* fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182
|
* fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182
|
||||||
|
|
||||||
## [0.9.8] - 19-06-2023
|
# [0.9.8] - 19-06-2023
|
||||||
|
|
||||||
`bigcapitalhq/webapp`
|
`bigcapitalhq/webapp`
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ Contributions via pull requests are much appreciated. Once the approach is agree
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Local Setup Prerequisites
|
## Local Setup Prerequisites
|
||||||
- The application currently supports **Node.js v18.x**.
|
- The application currently supports **Node.js v14.x**. Please ensure that you are using this version of Node.js when developing. (use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) to switch between node versions)
|
||||||
- `pnpm` packages manager, (from pnpm [guide](https://pnpm.io/installation) pick any installation method).
|
|
||||||
|
|
||||||
## Contribute to Backend
|
## Contribute to Backend
|
||||||
|
|
||||||
@@ -45,10 +44,11 @@ Contributions via pull requests are much appreciated. Once the approach is agree
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
- Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit the command on root directory and it will install dependencies of all packages.
|
- Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit these command on root directory and it will install dependencies of all packages.
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm install
|
npm install
|
||||||
|
npm run bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all required docker containers in the development, we already configured all containers under `docker-compose.yml`.
|
- Run all required docker containers in the development, we already configured all containers under `docker-compose.yml`.
|
||||||
@@ -69,7 +69,7 @@ cefa73fe2881 bigcapital-redis "docker-entrypoint.s…" 7 seconds ago Up
|
|||||||
- There're some CLI commands we should run before running the server like databaase migration, so we need to build the `server` app first.
|
- There're some CLI commands we should run before running the server like databaase migration, so we need to build the `server` app first.
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm run build:server
|
npm run build:server
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run the database migration for system database.
|
- Run the database migration for system database.
|
||||||
@@ -87,7 +87,7 @@ Batch 1 run: 6 migrations
|
|||||||
- Next, start the webapp application.
|
- Next, start the webapp application.
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm run dev:server
|
npm run dev:server
|
||||||
```
|
```
|
||||||
|
|
||||||
**[`^top^`](#)**
|
**[`^top^`](#)**
|
||||||
@@ -105,13 +105,14 @@ git clone https://github.com/bigcapital/bigcapital.git && cd bigcaptial
|
|||||||
- Install all npm dependencies of the monorepo, you don't have to change directory to the `frontend` package. just hit that command and will install all packages across all application.
|
- Install all npm dependencies of the monorepo, you don't have to change directory to the `frontend` package. just hit that command and will install all packages across all application.
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm install
|
npm install
|
||||||
|
npm run bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
- Next, start the webapp application.
|
- Next, start the webapp application.
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm run dev:webapp
|
npm run dev:webapp
|
||||||
```
|
```
|
||||||
|
|
||||||
**[`^top^`](#)**
|
**[`^top^`](#)**
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://bigcapital.app" target="_blank">
|
<a href="https://bigcapital.ly" target="_blank">
|
||||||
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/bigcapital.svg" alt="Bigcapital" width="280" height="75">
|
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/bigcapital.svg" alt="Bigcapital" width="280" height="75">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -12,9 +12,6 @@
|
|||||||
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
||||||
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/u/bigcapitalhq">
|
|
||||||
<img src="https://img.shields.io/docker/pulls/bigcapitalhq/webapp" />
|
|
||||||
</a>
|
|
||||||
<a href="https://discord.com/invite/c8nPBJafeb">
|
<a href="https://discord.com/invite/c8nPBJafeb">
|
||||||
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
||||||
</a>
|
</a>
|
||||||
@@ -28,10 +25,6 @@
|
|||||||
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
|
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://my.bigcapital.app">Bigcapital Cloud</a>
|
|
||||||
</p>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# What's Bigcapital?
|
# What's Bigcapital?
|
||||||
@@ -44,40 +37,9 @@ Bigcapital is a smart and open-source accounting and inventory software, Bigcapi
|
|||||||
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/screenshot-3.png" width="270">
|
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/screenshot-3.png" width="270">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
We've got serveral options on dev and prod depending on your need to get started quickly with Bigcapital.
|
|
||||||
|
|
||||||
## Self-hosted
|
|
||||||
|
|
||||||
Bigcapital is available open-source under AGPL license. You can host it on your own servers using Docker.
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.app/deployment/docker).
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Local Setup
|
|
||||||
|
|
||||||
To get started locally, we have a [guide to help you](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md).
|
|
||||||
|
|
||||||
### Gitpod
|
|
||||||
|
|
||||||
- Click the Gitpod button below to open this project in development mode.
|
|
||||||
- This will open and configure the workspace in your browser with all the necessary dependencies.
|
|
||||||
|
|
||||||
[](https://gitpod.io/new/#https://github.com/bigcapitalhq/bigcapital)
|
|
||||||
|
|
||||||
## Headless Accounting
|
|
||||||
|
|
||||||
You can integrate Bigcapital API with your system to organize your transactions in double-entry system to get the best financial reports.
|
|
||||||
|
|
||||||
[](https://www.postman.com/bigcapital/workspace/bigcapital-api)
|
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
- [Documentation](https://docs.bigcapital.app/) - Learn how to use.
|
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
|
||||||
- [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions.
|
- [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions.
|
||||||
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
|
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
|
||||||
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
|
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
|
||||||
@@ -118,22 +80,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/suhaibaffan"><img src="https://avatars.githubusercontent.com/u/18115937?v=4?s=100" width="100px;" alt="Suhaib Affan"/><br /><sub><b>Suhaib Affan</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=suhaibaffan" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/suhaibaffan"><img src="https://avatars.githubusercontent.com/u/18115937?v=4?s=100" width="100px;" alt="Suhaib Affan"/><br /><sub><b>Suhaib Affan</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=suhaibaffan" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KalliopiPliogka"><img src="https://avatars.githubusercontent.com/u/81677549?v=4?s=100" width="100px;" alt="Kalliopi Pliogka"/><br /><sub><b>Kalliopi Pliogka</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3AKalliopiPliogka" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KalliopiPliogka"><img src="https://avatars.githubusercontent.com/u/81677549?v=4?s=100" width="100px;" alt="Kalliopi Pliogka"/><br /><sub><b>Kalliopi Pliogka</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3AKalliopiPliogka" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://me.kochie.io"><img src="https://avatars.githubusercontent.com/u/10809884?v=4?s=100" width="100px;" alt="Robert Koch"/><br /><sub><b>Robert Koch</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=kochie" title="Code">💻</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="http://cschuijt.nl"><img src="https://avatars.githubusercontent.com/u/5460015?v=4?s=100" width="100px;" alt="Casper Schuijt"/><br /><sub><b>Casper Schuijt</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Acschuijt" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ANasouf"><img src="https://avatars.githubusercontent.com/u/19536487?v=4?s=100" width="100px;" alt="ANasouf"/><br /><sub><b>ANasouf</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=ANasouf" title="Code">💻</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://ragnarlaud.dev"><img src="https://avatars.githubusercontent.com/u/3042904?v=4?s=100" width="100px;" alt="Ragnar Laud"/><br /><sub><b>Ragnar Laud</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Axprnio" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://snyder.tech"><img src="https://avatars.githubusercontent.com/u/707567?v=4?s=100" width="100px;" alt="Ben Snyder"/><br /><sub><b>Ben Snyder</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=benpsnyder" title="Code">💻</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -3,33 +3,38 @@
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
proxy:
|
nginx:
|
||||||
image: envoyproxy/envoy:v1.30-latest
|
container_name: bigcapital-nginx-gateway
|
||||||
depends_on:
|
build:
|
||||||
- server
|
context: ./docker/nginx
|
||||||
- webapp
|
args:
|
||||||
|
- SERVER_PROXY_PORT=3000
|
||||||
|
- WEB_SSL=false
|
||||||
|
- SELF_SIGNED=false
|
||||||
|
volumes:
|
||||||
|
- ./data/logs/nginx/:/var/log/nginx
|
||||||
|
- ./docker/certbot/certs/:/var/certs
|
||||||
ports:
|
ports:
|
||||||
- '${PUBLIC_PROXY_PORT:-80}:80'
|
- '${PUBLIC_PROXY_PORT:-80}:80'
|
||||||
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
depends_on:
|
||||||
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
|
- server
|
||||||
restart: on-failure
|
- webapp
|
||||||
networks:
|
deploy:
|
||||||
- bigcapital_network
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
webapp:
|
webapp:
|
||||||
container_name: bigcapital-webapp
|
container_name: bigcapital-webapp
|
||||||
image: bigcapitalhq/webapp:latest
|
image: ghcr.io/bigcapitalhq/webapp:latest
|
||||||
restart: on-failure
|
deploy:
|
||||||
networks:
|
restart_policy:
|
||||||
- bigcapital_network
|
condition: unless-stopped
|
||||||
|
|
||||||
server:
|
server:
|
||||||
container_name: bigcapital-server
|
container_name: bigcapital-server
|
||||||
image: bigcapitalhq/server:latest
|
image: ghcr.io/bigcapitalhq/server:latest
|
||||||
expose:
|
|
||||||
- '3000'
|
|
||||||
links:
|
links:
|
||||||
- mysql
|
- mysql
|
||||||
- mongo
|
- mongo
|
||||||
@@ -38,9 +43,9 @@ services:
|
|||||||
- mysql
|
- mysql
|
||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
restart: on-failure
|
deploy:
|
||||||
networks:
|
restart_policy:
|
||||||
- bigcapital_network
|
condition: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
# Mail
|
# Mail
|
||||||
- MAIL_HOST=${MAIL_HOST}
|
- MAIL_HOST=${MAIL_HOST}
|
||||||
@@ -81,48 +86,6 @@ services:
|
|||||||
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
||||||
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
|
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
|
||||||
|
|
||||||
# Sign-up email confirmation
|
|
||||||
- SIGNUP_EMAIL_CONFIRMATION=${SIGNUP_EMAIL_CONFIRMATION}
|
|
||||||
|
|
||||||
# Gotenberg (Pdf generator)
|
|
||||||
- GOTENBERG_URL=${GOTENBERG_URL}
|
|
||||||
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
|
|
||||||
|
|
||||||
# Exchange Rate
|
|
||||||
- EXCHANGE_RATE_SERVICE=${EXCHANGE_RATE_SERVICE}
|
|
||||||
- OPEN_EXCHANGE_RATE_APP_ID-${OPEN_EXCHANGE_RATE_APP_ID}
|
|
||||||
|
|
||||||
# Bank Sync
|
|
||||||
- BANKING_CONNECT=${BANKING_CONNECT}
|
|
||||||
|
|
||||||
# Plaid
|
|
||||||
- PLAID_ENV=${PLAID_ENV}
|
|
||||||
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
|
|
||||||
- PLAID_SECRET=${PLAID_SECRET}
|
|
||||||
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
|
|
||||||
|
|
||||||
# Lemon Squeez
|
|
||||||
- LEMONSQUEEZY_API_KEY=${LEMONSQUEEZY_API_KEY}
|
|
||||||
- LEMONSQUEEZY_STORE_ID=${LEMONSQUEEZY_STORE_ID}
|
|
||||||
- LEMONSQUEEZY_WEBHOOK_SECRET=${LEMONSQUEEZY_WEBHOOK_SECRET}
|
|
||||||
- HOSTED_ON_BIGCAPITAL_CLOUD=${HOSTED_ON_BIGCAPITAL_CLOUD}
|
|
||||||
|
|
||||||
# New Relic matrics tracking.
|
|
||||||
- NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=${NEW_RELIC_DISTRIBUTED_TRACING_ENABLED}
|
|
||||||
- NEW_RELIC_LOG=${NEW_RELIC_LOG}
|
|
||||||
- NEW_RELIC_AI_MONITORING_ENABLED=${NEW_RELIC_AI_MONITORING_ENABLED}
|
|
||||||
- NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED}
|
|
||||||
- NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED}
|
|
||||||
- NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}
|
|
||||||
- NEW_RELIC_APP_NAME=${NEW_RELIC_APP_NAME}
|
|
||||||
|
|
||||||
# S3
|
|
||||||
- S3_REGION=${S3_REGION}
|
|
||||||
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
|
|
||||||
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
|
||||||
- S3_ENDPOINT=${S3_ENDPOINT}
|
|
||||||
- S3_BUCKET=${S3_BUCKET}
|
|
||||||
|
|
||||||
database_migration:
|
database_migration:
|
||||||
container_name: bigcapital-database-migration
|
container_name: bigcapital-database-migration
|
||||||
build:
|
build:
|
||||||
@@ -139,12 +102,12 @@ services:
|
|||||||
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
|
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
container_name: bigcapital-mysql
|
container_name: bigcapital-mysql
|
||||||
restart: on-failure
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build:
|
build:
|
||||||
context: ./docker/mariadb
|
context: ./docker/mariadb
|
||||||
environment:
|
environment:
|
||||||
@@ -156,38 +119,29 @@ services:
|
|||||||
- mysql:/var/lib/mysql
|
- mysql:/var/lib/mysql
|
||||||
expose:
|
expose:
|
||||||
- '3306'
|
- '3306'
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
container_name: bigcapital-mongo
|
container_name: bigcapital-mongo
|
||||||
restart: on-failure
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build: ./docker/mongo
|
build: ./docker/mongo
|
||||||
expose:
|
expose:
|
||||||
- '27017'
|
- '27017'
|
||||||
volumes:
|
volumes:
|
||||||
- mongo:/var/lib/mongodb
|
- mongo:/var/lib/mongodb
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: bigcapital-redis
|
container_name: bigcapital-redis
|
||||||
restart: on-failure
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build:
|
build:
|
||||||
context: ./docker/redis
|
context: ./docker/redis
|
||||||
expose:
|
expose:
|
||||||
- '6379'
|
- '6379'
|
||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
gotenberg:
|
|
||||||
image: gotenberg/gotenberg:7
|
|
||||||
expose:
|
|
||||||
- '9000'
|
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
# Volumes
|
# Volumes
|
||||||
volumes:
|
volumes:
|
||||||
@@ -202,8 +156,3 @@ volumes:
|
|||||||
redis:
|
redis:
|
||||||
name: bigcapital_prod_redis
|
name: bigcapital_prod_redis
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
# Networks
|
|
||||||
networks:
|
|
||||||
bigcapital_network:
|
|
||||||
driver: bridge
|
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ services:
|
|||||||
restart_policy:
|
restart_policy:
|
||||||
condition: unless-stopped
|
condition: unless-stopped
|
||||||
|
|
||||||
gotenberg:
|
|
||||||
image: gotenberg/gotenberg:7
|
|
||||||
ports:
|
|
||||||
- "9000:3000"
|
|
||||||
|
|
||||||
# Volumes
|
# Volumes
|
||||||
volumes:
|
volumes:
|
||||||
mysql:
|
mysql:
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
static_resources:
|
|
||||||
listeners:
|
|
||||||
- name: listener_0
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_value: 80
|
|
||||||
filter_chains:
|
|
||||||
- filters:
|
|
||||||
- name: envoy.filters.network.http_connection_manager
|
|
||||||
typed_config:
|
|
||||||
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
|
||||||
stat_prefix: ingress_http
|
|
||||||
route_config:
|
|
||||||
name: local_route
|
|
||||||
virtual_hosts:
|
|
||||||
- name: backend
|
|
||||||
domains: ['*']
|
|
||||||
routes:
|
|
||||||
- match:
|
|
||||||
prefix: '/api'
|
|
||||||
route:
|
|
||||||
cluster: dynamic_server
|
|
||||||
- match:
|
|
||||||
prefix: '/'
|
|
||||||
route:
|
|
||||||
cluster: webapp
|
|
||||||
http_filters:
|
|
||||||
- name: envoy.filters.http.router
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
|
||||||
|
|
||||||
clusters:
|
|
||||||
- name: dynamic_server
|
|
||||||
connect_timeout: 0.25s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: dynamic_server
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: server
|
|
||||||
port_value: 3000
|
|
||||||
|
|
||||||
- name: webapp
|
|
||||||
connect_timeout: 0.25s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: webapp
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: webapp
|
|
||||||
port_value: 80
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM bigcapitalhq/server:latest as build
|
FROM ghcr.io/bigcapitalhq/server:latest as build
|
||||||
|
|
||||||
ARG DB_HOST= \
|
ARG DB_HOST= \
|
||||||
DB_USER= \
|
DB_USER= \
|
||||||
@@ -34,5 +34,7 @@ WORKDIR /app/packages/server
|
|||||||
|
|
||||||
RUN git clone https://github.com/vishnubob/wait-for-it.git
|
RUN git clone https://github.com/vishnubob/wait-for-it.git
|
||||||
|
|
||||||
# Once we listen the mysql port run the migration task.
|
ADD docker/migration/start.sh /
|
||||||
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"
|
RUN chmod +x /start.sh
|
||||||
|
|
||||||
|
CMD ["/start.sh"]
|
||||||
5
docker/migration/start.sh
Normal file
5
docker/migration/start.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Migrate the master system database.
|
||||||
|
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js system:migrate:latest
|
||||||
|
|
||||||
|
# Migrate all tenants.
|
||||||
|
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js tenants:migrate:latest
|
||||||
21
docker/nginx/Dockerfile
Normal file
21
docker/nginx/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM nginx:1.11
|
||||||
|
|
||||||
|
RUN mkdir /etc/nginx/sites-available && rm /etc/nginx/conf.d/default.conf
|
||||||
|
ADD nginx.conf /etc/nginx/
|
||||||
|
|
||||||
|
COPY scripts /root/scripts/
|
||||||
|
COPY certs /etc/ssl/
|
||||||
|
|
||||||
|
COPY sites /etc/nginx/templates
|
||||||
|
|
||||||
|
ARG SERVER_PROXY_PORT=3000
|
||||||
|
ARG WEB_SSL=false
|
||||||
|
ARG SELF_SIGNED=false
|
||||||
|
|
||||||
|
ENV SERVER_PROXY_PORT=$SERVER_PROXY_PORT
|
||||||
|
ENV WEB_SSL=$WEB_SSL
|
||||||
|
ENV SELF_SIGNED=$SELF_SIGNED
|
||||||
|
|
||||||
|
RUN /bin/bash /root/scripts/build-nginx.sh
|
||||||
|
|
||||||
|
CMD nginx
|
||||||
33
docker/nginx/nginx.conf
Normal file
33
docker/nginx/nginx.conf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
user www-data;
|
||||||
|
worker_processes auto;
|
||||||
|
pid /run/nginx.pid;
|
||||||
|
daemon off;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 2048;
|
||||||
|
use epoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server_tokens off;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 15;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
open_file_cache max=100;
|
||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
include /etc/nginx/sites-available/*;
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
}
|
||||||
9
docker/nginx/scripts/build-nginx.sh
Normal file
9
docker/nginx/scripts/build-nginx.sh
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for conf in /etc/nginx/templates/*.conf; do
|
||||||
|
mv $conf "/etc/nginx/sites-available/"$(basename $conf) > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
for template in /etc/nginx/templates/*.template; do
|
||||||
|
envsubst < $template > "/etc/nginx/sites-available/"$(basename $template)".conf"
|
||||||
|
done
|
||||||
16
docker/nginx/sites/server.template
Normal file
16
docker/nginx/sites/server.template
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://server:${SERVER_PROXY_PORT};
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://webapp;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/letsencrypt/;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,5 @@
|
|||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "independent",
|
"version": "independent",
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"packages": [
|
"packages": ["packages/*"]
|
||||||
"packages/*",
|
|
||||||
"shared/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
package.json
23
package.json
@@ -2,16 +2,15 @@
|
|||||||
"name": "bigcapital-monorepo",
|
"name": "bigcapital-monorepo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"bootstrap": "lerna exec pnpm install",
|
||||||
"dev": "lerna run dev",
|
"dev": "lerna run dev",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
|
||||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
|
||||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
|
||||||
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
"build:server": "lerna run build --scope \"@bigcapital/server\"",
|
||||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
|
||||||
"server2:start": "lerna run start:dev --scope \"@bigcapital/server2\"",
|
"test:e2e": "playwright test",
|
||||||
"test:watch": "lerna run test:watch",
|
|
||||||
"test:e2e": "lerna run test:e2e",
|
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -21,18 +20,14 @@
|
|||||||
"@faker-js/faker": "^8.0.2",
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@playwright/test": "^1.32.3",
|
"@playwright/test": "^1.32.3",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lerna": "^8.1.2",
|
"lerna": "^6.4.1"
|
||||||
"pnpm": "^9.0.5"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x || 17.x || 18.x"
|
"node": "14.x"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"tsup": "^8.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
# Mail
|
|
||||||
MAIL_HOST=
|
|
||||||
MAIL_USERNAME=
|
|
||||||
MAIL_PASSWORD=
|
|
||||||
MAIL_PORT=
|
|
||||||
MAIL_SECURE=
|
|
||||||
MAIL_FROM_NAME=
|
|
||||||
MAIL_FROM_ADDRESS=
|
|
||||||
|
|
||||||
# Database
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_USER=bigcapital
|
|
||||||
DB_PASSWORD=bigcapital
|
|
||||||
DB_ROOT_PASSWORD=root
|
|
||||||
DB_CHARSET=utf8
|
|
||||||
|
|
||||||
# System database
|
|
||||||
SYSTEM_DB_NAME=bigcapital_system
|
|
||||||
# SYSTEM_DB_USER=
|
|
||||||
# SYSTEM_DB_PASSWORD=
|
|
||||||
# SYSTEM_DB_NAME=
|
|
||||||
# SYSTEM_DB_CHARSET=
|
|
||||||
|
|
||||||
# Tenant databases
|
|
||||||
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
|
||||||
# TENANT_DB_HOST=
|
|
||||||
# TENANT_DB_USER=
|
|
||||||
# TENANT_DB_PASSWORD=
|
|
||||||
# TENANT_DB_CHARSET=
|
|
||||||
|
|
||||||
# Application
|
|
||||||
BASE_URL=http://example.com
|
|
||||||
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
|
||||||
|
|
||||||
# Jobs MongoDB
|
|
||||||
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
|
||||||
|
|
||||||
# App proxy
|
|
||||||
PUBLIC_PROXY_PORT=80
|
|
||||||
PUBLIC_PROXY_SSL_PORT=443
|
|
||||||
|
|
||||||
# Agendash
|
|
||||||
AGENDASH_AUTH_USER=agendash
|
|
||||||
AGENDASH_AUTH_PASSWORD=123123
|
|
||||||
|
|
||||||
# Sign-up restrictions
|
|
||||||
SIGNUP_DISABLED=false
|
|
||||||
SIGNUP_ALLOWED_DOMAINS=
|
|
||||||
SIGNUP_ALLOWED_EMAILS=
|
|
||||||
|
|
||||||
# Sign-up Email Confirmation
|
|
||||||
SIGNUP_EMAIL_CONFIRMATION=false
|
|
||||||
|
|
||||||
# API rate limit (points,duration,block duration).
|
|
||||||
API_RATE_LIMIT=120,60,600
|
|
||||||
|
|
||||||
# Gotenberg API for PDF printing - (production).
|
|
||||||
GOTENBERG_URL=http://gotenberg:3000
|
|
||||||
GOTENBERG_DOCS_URL=http://server:3000/public/
|
|
||||||
|
|
||||||
# Gotenberg API - (development)
|
|
||||||
# GOTENBERG_URL=http://localhost:9000
|
|
||||||
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
|
|
||||||
|
|
||||||
# Exchange Rate Service
|
|
||||||
EXCHANGE_RATE_SERVICE=open-exchange-rate
|
|
||||||
|
|
||||||
# Open Exchange Rate
|
|
||||||
OPEN_EXCHANGE_RATE_APP_ID=
|
|
||||||
|
|
||||||
# The Plaid environment to use ('sandbox' or 'development').
|
|
||||||
# https://plaid.com/docs/#api-host
|
|
||||||
PLAID_ENV=sandbox
|
|
||||||
|
|
||||||
# Your Plaid keys, which can be found in the Plaid Dashboard.
|
|
||||||
# https://dashboard.plaid.com/account/keys
|
|
||||||
PLAID_CLIENT_ID=
|
|
||||||
PLAID_SECRET=
|
|
||||||
PLAID_LINK_WEBHOOK=
|
|
||||||
|
|
||||||
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
|
||||||
LEMONSQUEEZY_API_KEY=
|
|
||||||
LEMONSQUEEZY_STORE_ID=
|
|
||||||
LEMONSQUEEZY_WEBHOOK_SECRET=
|
|
||||||
|
|
||||||
# S3 documents and attachments
|
|
||||||
S3_REGION=US
|
|
||||||
S3_ACCESS_KEY_ID=
|
|
||||||
S3_SECRET_ACCESS_KEY=
|
|
||||||
S3_ENDPOINT=
|
|
||||||
S3_BUCKET=
|
|
||||||
|
|
||||||
# PostHog
|
|
||||||
POSTHOG_API_KEY=
|
|
||||||
POSTHOG_HOST=
|
|
||||||
|
|
||||||
# Stripe Payment
|
|
||||||
STRIPE_PAYMENT_SECRET_KEY=
|
|
||||||
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
|
||||||
STRIPE_PAYMENT_CLIENT_ID=
|
|
||||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
|
||||||
STRIPE_PAYMENT_REDIRECT_URL=
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['.eslintrc.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
56
packages/server-nest/.gitignore
vendored
56
packages/server-nest/.gitignore
vendored
@@ -1,56 +0,0 @@
|
|||||||
# compiled output
|
|
||||||
/dist
|
|
||||||
/node_modules
|
|
||||||
/build
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
/coverage
|
|
||||||
/.nyc_output
|
|
||||||
|
|
||||||
# IDEs and editors
|
|
||||||
/.idea
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.c9/
|
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# IDE - VSCode
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# temp directory
|
|
||||||
.temp
|
|
||||||
.tmp
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "all"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
- Build authentication services.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
## @bigcapitalhq/server
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/nest-cli",
|
|
||||||
"collection": "@nestjs/schematics",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"compilerOptions": {
|
|
||||||
"deleteOutDir": true,
|
|
||||||
"assets": [
|
|
||||||
{ "include": "i18n/**/*", "watchAssets": true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@bigcapital/server2",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "",
|
|
||||||
"author": "",
|
|
||||||
"private": true,
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"scripts": {
|
|
||||||
"build": "nest build",
|
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
||||||
"start": "nest start",
|
|
||||||
"start:dev": "nest start --watch",
|
|
||||||
"start:debug": "nest start --debug --watch",
|
|
||||||
"start:prod": "node dist/main",
|
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
||||||
"test": "jest",
|
|
||||||
"test:watch": "jest --watch",
|
|
||||||
"test:cov": "jest --coverage",
|
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json --watchAll"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@bigcapital/email-components": "*",
|
|
||||||
"@bigcapital/pdf-templates": "*",
|
|
||||||
"@bigcapital/utils": "*",
|
|
||||||
"@nestjs/bull": "^10.2.1",
|
|
||||||
"@nestjs/bullmq": "^10.2.1",
|
|
||||||
"@nestjs/cache-manager": "^2.2.2",
|
|
||||||
"@nestjs/common": "^10.0.0",
|
|
||||||
"@nestjs/config": "^3.2.3",
|
|
||||||
"@nestjs/core": "^10.0.0",
|
|
||||||
"@nestjs/event-emitter": "^2.0.4",
|
|
||||||
"@nestjs/jwt": "^10.2.0",
|
|
||||||
"@nestjs/passport": "^10.0.3",
|
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
|
||||||
"@nestjs/swagger": "^7.4.2",
|
|
||||||
"@nestjs/throttler": "^6.2.1",
|
|
||||||
"@supercharge/promise-pool": "^3.2.0",
|
|
||||||
"@types/nodemailer": "^6.4.17",
|
|
||||||
"@types/passport-local": "^1.0.38",
|
|
||||||
"@types/ramda": "^0.30.2",
|
|
||||||
"accounting": "^0.4.1",
|
|
||||||
"async": "^3.2.0",
|
|
||||||
"axios": "^1.6.0",
|
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"bull": "^4.16.3",
|
|
||||||
"bullmq": "^5.21.1",
|
|
||||||
"cache-manager": "^6.1.1",
|
|
||||||
"cache-manager-redis-store": "^3.0.1",
|
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.14.1",
|
|
||||||
"express-validator": "^7.2.0",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"fp-ts": "^2.16.9",
|
|
||||||
"is-my-json-valid": "^2.20.5",
|
|
||||||
"js-money": "^0.6.3",
|
|
||||||
"knex": "^3.1.0",
|
|
||||||
"lamda": "^0.4.1",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"moment": "^2.30.1",
|
|
||||||
"mysql": "^2.18.1",
|
|
||||||
"mysql2": "^3.11.3",
|
|
||||||
"nestjs-cls": "^4.4.1",
|
|
||||||
"nestjs-i18n": "^10.4.9",
|
|
||||||
"nodemailer": "^6.3.0",
|
|
||||||
"object-hash": "^2.0.3",
|
|
||||||
"objection": "^3.1.5",
|
|
||||||
"passport": "^0.7.0",
|
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"plaid": "^10.3.0",
|
|
||||||
"pluralize": "^8.0.0",
|
|
||||||
"posthog-node": "^4.3.2",
|
|
||||||
"pug": "^3.0.2",
|
|
||||||
"ramda": "^0.30.1",
|
|
||||||
"redis": "^4.7.0",
|
|
||||||
"reflect-metadata": "^0.2.0",
|
|
||||||
"rxjs": "^7.8.1",
|
|
||||||
"serialize-interceptor": "^1.1.7",
|
|
||||||
"strategy": "^1.1.1",
|
|
||||||
"uniqid": "^5.2.0",
|
|
||||||
"uuid": "^10.0.0",
|
|
||||||
"yup": "^0.28.1",
|
|
||||||
"zod": "^3.23.8"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nestjs/cli": "^10.0.0",
|
|
||||||
"@nestjs/schematics": "^10.0.0",
|
|
||||||
"@nestjs/testing": "^10.0.0",
|
|
||||||
"@types/express": "^5.0.0",
|
|
||||||
"@types/jest": "^29.5.2",
|
|
||||||
"@types/node": "^20.3.1",
|
|
||||||
"@types/supertest": "^6.0.0",
|
|
||||||
"@types/yup": "^0.29.13",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
|
||||||
"eslint": "^9.0.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
|
||||||
"jest": "^29.5.0",
|
|
||||||
"prettier": "^3.0.0",
|
|
||||||
"source-map-support": "^0.5.21",
|
|
||||||
"supertest": "^7.0.0",
|
|
||||||
"ts-jest": "^29.1.0",
|
|
||||||
"ts-loader": "^9.4.3",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsconfig-paths": "^4.2.0",
|
|
||||||
"typescript": "^5.1.3"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"rootDir": "src",
|
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
|
||||||
},
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"**/*.(t|j)s"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "../coverage",
|
|
||||||
"testEnvironment": "node"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('gotenberg', () => ({
|
|
||||||
url: process.env.GOTENBERG_URL,
|
|
||||||
docsUrl: process.env.GOTENBERG_DOCS_URL,
|
|
||||||
}));
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import systemDatabase from './system-database';
|
|
||||||
import tenantDatabase from './tenant-database';
|
|
||||||
import signup from './signup';
|
|
||||||
import gotenberg from './gotenberg';
|
|
||||||
import plaid from './plaid';
|
|
||||||
import lemonsqueezy from './lemonsqueezy';
|
|
||||||
import s3 from './s3';
|
|
||||||
import openExchange from './open-exchange';
|
|
||||||
import posthog from './posthog';
|
|
||||||
|
|
||||||
export const config = [
|
|
||||||
systemDatabase,
|
|
||||||
tenantDatabase,
|
|
||||||
signup,
|
|
||||||
gotenberg,
|
|
||||||
plaid,
|
|
||||||
lemonsqueezy,
|
|
||||||
s3,
|
|
||||||
openExchange,
|
|
||||||
posthog,
|
|
||||||
];
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('lemonsqueezy', () => ({
|
|
||||||
apiKey: process.env.LEMONSQUEEZY_API_KEY,
|
|
||||||
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
|
||||||
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
|
|
||||||
}));
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('openExchange', () => ({
|
|
||||||
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
|
|
||||||
}));
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('plaid', () => ({
|
|
||||||
env: process.env.PLAID_ENV || 'sandbox',
|
|
||||||
clientId: process.env.PLAID_CLIENT_ID,
|
|
||||||
secret: process.env.PLAID_SECRET,
|
|
||||||
linkWebhook: process.env.PLAID_LINK_WEBHOOK,
|
|
||||||
}));
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('posthog', () => ({
|
|
||||||
apiKey: process.env.POSTHOG_API_KEY,
|
|
||||||
host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
|
|
||||||
}));
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('s3', () => ({
|
|
||||||
region: process.env.S3_REGION || 'US',
|
|
||||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
|
||||||
endpoint: process.env.S3_ENDPOINT,
|
|
||||||
bucket: process.env.S3_BUCKET,
|
|
||||||
}));
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('signup', () => ({
|
|
||||||
disabled: process.env.SIGNUP_DISABLED === 'true',
|
|
||||||
allowedDomains: process.env.SIGNUP_ALLOWED_DOMAINS
|
|
||||||
? process.env.SIGNUP_ALLOWED_DOMAINS.split(',')
|
|
||||||
: [],
|
|
||||||
allowedEmails: process.env.SIGNUP_ALLOWED_EMAILS
|
|
||||||
? process.env.SIGNUP_ALLOWED_EMAILS.split(',')
|
|
||||||
: [],
|
|
||||||
emailConfirmation: process.env.SIGNUP_EMAIL_CONFIRMATION === 'true',
|
|
||||||
}));
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('stripePayment', () => ({
|
|
||||||
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY,
|
|
||||||
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY,
|
|
||||||
clientId: process.env.STRIPE_PAYMENT_CLIENT_ID,
|
|
||||||
webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET,
|
|
||||||
redirectUrl: process.env.STRIPE_PAYMENT_REDIRECT_URL,
|
|
||||||
}));
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('systemDatabase', () => ({
|
|
||||||
client: 'mysql',
|
|
||||||
host: process.env.SYSTEM_DB_HOST || process.env.DB_HOST,
|
|
||||||
port: process.env.SYSTEM_DB_PORT || process.env.DB_PORT || 5432,
|
|
||||||
user: process.env.SYSTEM_DB_USER || process.env.DB_USER,
|
|
||||||
password: process.env.SYSTEM_DB_PASSWORD || process.env.DB_PASSWORD,
|
|
||||||
databaseName: process.env.SYSTEM_DB_NAME || process.env.DB_NAME,
|
|
||||||
}));
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('tenantDatabase', () => ({
|
|
||||||
client: 'mysql',
|
|
||||||
host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
|
||||||
port: process.env.TENANT_DB_PORT || process.env.DB_PORT || 5432,
|
|
||||||
user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
|
||||||
password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
|
||||||
}));
|
|
||||||
@@ -1,756 +0,0 @@
|
|||||||
export const events = {
|
|
||||||
/**
|
|
||||||
* Authentication service.
|
|
||||||
*/
|
|
||||||
auth: {
|
|
||||||
signIn: 'onSignIn',
|
|
||||||
signingIn: 'onSigningIn',
|
|
||||||
|
|
||||||
signUp: 'onSignUp',
|
|
||||||
signingUp: 'onSigningUp',
|
|
||||||
|
|
||||||
signUpConfirming: 'signUpConfirming',
|
|
||||||
signUpConfirmed: 'signUpConfirmed',
|
|
||||||
|
|
||||||
sendingResetPassword: 'onSendingResetPassword',
|
|
||||||
sendResetPassword: 'onSendResetPassword',
|
|
||||||
|
|
||||||
resetPassword: 'onResetPassword',
|
|
||||||
resetingPassword: 'onResetingPassword',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invite users service.
|
|
||||||
*/
|
|
||||||
inviteUser: {
|
|
||||||
acceptInvite: 'onUserAcceptInvite',
|
|
||||||
sendInvite: 'onUserSendInvite',
|
|
||||||
resendInvite: 'onUserInviteResend',
|
|
||||||
checkInvite: 'onUserCheckInvite',
|
|
||||||
sendInviteTenantSynced: 'onUserSendInviteTenantSynced',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organization managment service.
|
|
||||||
*/
|
|
||||||
organization: {
|
|
||||||
build: 'onOrganizationBuild',
|
|
||||||
built: 'onOrganizationBuilt',
|
|
||||||
|
|
||||||
seeded: 'onOrganizationSeeded',
|
|
||||||
|
|
||||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organization subscription.
|
|
||||||
*/
|
|
||||||
subscription: {
|
|
||||||
onSubscriptionCancel: 'onSubscriptionCancel',
|
|
||||||
onSubscriptionCancelled: 'onSubscriptionCancelled',
|
|
||||||
|
|
||||||
onSubscriptionResume: 'onSubscriptionResume',
|
|
||||||
onSubscriptionResumed: 'onSubscriptionResumed',
|
|
||||||
|
|
||||||
onSubscriptionPlanChange: 'onSubscriptionPlanChange',
|
|
||||||
onSubscriptionPlanChanged: 'onSubscriptionPlanChanged',
|
|
||||||
|
|
||||||
onSubscriptionSubscribed: 'onSubscriptionSubscribed',
|
|
||||||
|
|
||||||
onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed',
|
|
||||||
onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tenants managment service.
|
|
||||||
*/
|
|
||||||
tenantManager: {
|
|
||||||
databaseCreated: 'onDatabaseCreated',
|
|
||||||
tenantMigrated: 'onTenantMigrated',
|
|
||||||
tenantSeeded: 'onTenantSeeded',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accounts service.
|
|
||||||
*/
|
|
||||||
accounts: {
|
|
||||||
onViewed: 'onAccountViewed',
|
|
||||||
onListViewed: 'onAccountsListViewed',
|
|
||||||
|
|
||||||
onCreating: 'onAccountCreating',
|
|
||||||
onCreated: 'onAccountCreated',
|
|
||||||
|
|
||||||
onEditing: 'onAccountEditing',
|
|
||||||
onEdited: 'onAccountEdited',
|
|
||||||
|
|
||||||
onDelete: 'onAccountDelete',
|
|
||||||
onDeleted: 'onAccountDeleted',
|
|
||||||
|
|
||||||
onBulkDeleted: 'onBulkDeleted',
|
|
||||||
onBulkActivated: 'onAccountBulkActivated',
|
|
||||||
|
|
||||||
onActivated: 'onAccountActivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manual journals service.
|
|
||||||
*/
|
|
||||||
manualJournals: {
|
|
||||||
onCreating: 'onManualJournalCreating',
|
|
||||||
onCreated: 'onManualJournalCreated',
|
|
||||||
|
|
||||||
onEditing: 'onManualJournalEditing',
|
|
||||||
onEdited: 'onManualJournalEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onManualJournalDeleting',
|
|
||||||
onDeleted: 'onManualJournalDeleted',
|
|
||||||
|
|
||||||
onPublished: 'onManualJournalPublished',
|
|
||||||
onPublishing: 'onManualJournalPublishing',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expenses service.
|
|
||||||
*/
|
|
||||||
expenses: {
|
|
||||||
onCreating: 'onExpenseCreating',
|
|
||||||
onCreated: 'onExpenseCreated',
|
|
||||||
|
|
||||||
onEditing: 'onExpenseEditing',
|
|
||||||
onEdited: 'onExpenseEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onExpenseDeleting',
|
|
||||||
onDeleted: 'onExpenseDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onExpensePublishing',
|
|
||||||
onPublished: 'onExpensePublished',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sales invoices service.
|
|
||||||
*/
|
|
||||||
saleInvoice: {
|
|
||||||
onViewed: 'onSaleInvoiceItemViewed',
|
|
||||||
onListViewed: 'onSaleInvoiceListViewed',
|
|
||||||
|
|
||||||
onPdfViewed: 'onSaleInvoicePdfViewed',
|
|
||||||
|
|
||||||
onCreate: 'onSaleInvoiceCreate',
|
|
||||||
onCreating: 'onSaleInvoiceCreating',
|
|
||||||
onCreated: 'onSaleInvoiceCreated',
|
|
||||||
|
|
||||||
onEdit: 'onSaleInvoiceEdit',
|
|
||||||
onEditing: 'onSaleInvoiceEditing',
|
|
||||||
onEdited: 'onSaleInvoiceEdited',
|
|
||||||
|
|
||||||
onDelete: 'onSaleInvoiceDelete',
|
|
||||||
onDeleting: 'onSaleInvoiceDeleting',
|
|
||||||
onDeleted: 'onSaleInvoiceDeleted',
|
|
||||||
|
|
||||||
onDelivering: 'onSaleInvoiceDelivering',
|
|
||||||
onDeliver: 'onSaleInvoiceDeliver',
|
|
||||||
onDelivered: 'onSaleInvoiceDelivered',
|
|
||||||
|
|
||||||
onPublish: 'onSaleInvoicePublish',
|
|
||||||
onPublished: 'onSaleInvoicePublished',
|
|
||||||
|
|
||||||
onWriteoff: 'onSaleInvoiceWriteoff',
|
|
||||||
onWrittenoff: 'onSaleInvoiceWrittenoff',
|
|
||||||
onWrittenoffCancel: 'onSaleInvoiceWrittenoffCancel',
|
|
||||||
onWrittenoffCanceled: 'onSaleInvoiceWrittenoffCanceled',
|
|
||||||
|
|
||||||
onNotifySms: 'onSaleInvoiceNotifySms',
|
|
||||||
onNotifiedSms: 'onSaleInvoiceNotifiedSms',
|
|
||||||
|
|
||||||
onNotifyMail: 'onSaleInvoiceNotifyMail',
|
|
||||||
onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail',
|
|
||||||
|
|
||||||
onPreMailSend: 'onSaleInvoicePreMailSend',
|
|
||||||
onMailSend: 'onSaleInvoiceMailSend',
|
|
||||||
onMailSent: 'onSaleInvoiceMailSent',
|
|
||||||
|
|
||||||
onMailReminderSend: 'onSaleInvoiceMailReminderSend',
|
|
||||||
onMailReminderSent: 'onSaleInvoiceMailReminderSent',
|
|
||||||
|
|
||||||
onPublicLinkGenerating: 'onPublicSharableLinkGenerating',
|
|
||||||
onPublicLinkGenerated: 'onPublicSharableLinkGenerated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sales estimates service.
|
|
||||||
*/
|
|
||||||
saleEstimate: {
|
|
||||||
onPdfViewed: 'onSaleEstimatePdfViewed',
|
|
||||||
|
|
||||||
onCreating: 'onSaleEstimateCreating',
|
|
||||||
onCreated: 'onSaleEstimateCreated',
|
|
||||||
|
|
||||||
onEditing: 'onSaleEstimateEditing',
|
|
||||||
onEdited: 'onSaleEstimateEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onSaleEstimatedDeleting',
|
|
||||||
onDeleted: 'onSaleEstimatedDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onSaleEstimatedPublishing',
|
|
||||||
onPublished: 'onSaleEstimatedPublished',
|
|
||||||
|
|
||||||
onNotifySms: 'onSaleEstimateNotifySms',
|
|
||||||
onNotifiedSms: 'onSaleEstimateNotifiedSms',
|
|
||||||
|
|
||||||
onDelivering: 'onSaleEstimateDelivering',
|
|
||||||
onDelivered: 'onSaleEstimateDelivered',
|
|
||||||
|
|
||||||
onConvertedToInvoice: 'onSaleEstimateConvertedToInvoice',
|
|
||||||
|
|
||||||
onApproving: 'onSaleEstimateApproving',
|
|
||||||
onApproved: 'onSaleEstimateApproved',
|
|
||||||
|
|
||||||
onRejecting: 'onSaleEstimateRejecting',
|
|
||||||
onRejected: 'onSaleEstimateRejected',
|
|
||||||
|
|
||||||
onNotifyMail: 'onSaleEstimateNotifyMail',
|
|
||||||
|
|
||||||
onPreMailSend: 'onSaleEstimatePreMailSend',
|
|
||||||
onMailSend: 'onSaleEstimateMailSend',
|
|
||||||
onMailSent: 'onSaleEstimateMailSend',
|
|
||||||
|
|
||||||
onViewed: 'onSaleEstimateViewed',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sales receipts service.
|
|
||||||
*/
|
|
||||||
saleReceipt: {
|
|
||||||
onPdfViewed: 'onSaleReceiptPdfViewed',
|
|
||||||
|
|
||||||
onCreating: 'onSaleReceiptsCreating',
|
|
||||||
onCreated: 'onSaleReceiptsCreated',
|
|
||||||
|
|
||||||
onEditing: 'onSaleReceiptsEditing',
|
|
||||||
onEdited: 'onSaleReceiptsEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onSaleReceiptsDeleting',
|
|
||||||
onDeleted: 'onSaleReceiptsDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onSaleReceiptPublishing',
|
|
||||||
onPublished: 'onSaleReceiptPublished',
|
|
||||||
|
|
||||||
onClosed: 'onSaleReceiptClosed',
|
|
||||||
onClosing: 'onSaleReceiptClosing',
|
|
||||||
|
|
||||||
onNotifySms: 'onSaleReceiptNotifySms',
|
|
||||||
onNotifiedSms: 'onSaleReceiptNotifiedSms',
|
|
||||||
|
|
||||||
onPreMailSend: 'onSaleReceiptPreMailSend',
|
|
||||||
onMailSend: 'onSaleReceiptMailSend',
|
|
||||||
onMailSent: 'onSaleReceiptMailSent',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payment receipts service.
|
|
||||||
*/
|
|
||||||
paymentReceive: {
|
|
||||||
onPdfViewed: 'onPaymentReceivedPdfViewed',
|
|
||||||
|
|
||||||
onCreated: 'onPaymentReceiveCreated',
|
|
||||||
onCreating: 'onPaymentReceiveCreating',
|
|
||||||
|
|
||||||
onEditing: 'onPaymentReceiveEditing',
|
|
||||||
onEdited: 'onPaymentReceiveEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onPaymentReceiveDeleting',
|
|
||||||
onDeleted: 'onPaymentReceiveDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onPaymentReceivePublishing',
|
|
||||||
onPublished: 'onPaymentReceivePublished',
|
|
||||||
|
|
||||||
onNotifySms: 'onPaymentReceiveNotifySms',
|
|
||||||
onNotifiedSms: 'onPaymentReceiveNotifiedSms',
|
|
||||||
|
|
||||||
onPreMailSend: 'onPaymentReceivePreMailSend',
|
|
||||||
onMailSend: 'onPaymentReceiveMailSend',
|
|
||||||
onMailSent: 'onPaymentReceiveMailSent',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bills service.
|
|
||||||
*/
|
|
||||||
bill: {
|
|
||||||
onCreating: 'onBillCreating',
|
|
||||||
onCreated: 'onBillCreated',
|
|
||||||
|
|
||||||
onEditing: 'onBillEditing',
|
|
||||||
onEdited: 'onBillEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onBillDeleting',
|
|
||||||
onDeleted: 'onBillDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onBillPublishing',
|
|
||||||
onPublished: 'onBillPublished',
|
|
||||||
|
|
||||||
onOpening: 'onBillOpening',
|
|
||||||
onOpened: 'onBillOpened',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bill payments service.
|
|
||||||
*/
|
|
||||||
billPayment: {
|
|
||||||
onCreating: 'onBillPaymentCreating',
|
|
||||||
onCreated: 'onBillPaymentCreated',
|
|
||||||
|
|
||||||
onEditing: 'onBillPaymentEditing',
|
|
||||||
onEdited: 'onBillPaymentEdited',
|
|
||||||
|
|
||||||
onDeleted: 'onBillPaymentDeleted',
|
|
||||||
onDeleting: 'onBillPaymentDeleting',
|
|
||||||
|
|
||||||
onPublishing: 'onBillPaymentPublishing',
|
|
||||||
onPublished: 'onBillPaymentPublished',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customers services.
|
|
||||||
*/
|
|
||||||
customers: {
|
|
||||||
onCreating: 'onCustomerCreating',
|
|
||||||
onCreated: 'onCustomerCreated',
|
|
||||||
|
|
||||||
onEdited: 'onCustomerEdited',
|
|
||||||
onEditing: 'onCustomerEditing',
|
|
||||||
|
|
||||||
onDeleted: 'onCustomerDeleted',
|
|
||||||
onDeleting: 'onCustomerDeleting',
|
|
||||||
onBulkDeleted: 'onBulkDeleted',
|
|
||||||
|
|
||||||
onOpeningBalanceChanging: 'onCustomerOpeningBalanceChanging',
|
|
||||||
onOpeningBalanceChanged: 'onCustomerOpeingBalanceChanged',
|
|
||||||
|
|
||||||
onActivating: 'onCustomerActivating',
|
|
||||||
onActivated: 'onCustomerActivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vendors services.
|
|
||||||
*/
|
|
||||||
vendors: {
|
|
||||||
onCreated: 'onVendorCreated',
|
|
||||||
onCreating: 'onVendorCreating',
|
|
||||||
|
|
||||||
onEdited: 'onVendorEdited',
|
|
||||||
onEditing: 'onVendorEditing',
|
|
||||||
|
|
||||||
onDeleted: 'onVendorDeleted',
|
|
||||||
onDeleting: 'onVendorDeleting',
|
|
||||||
|
|
||||||
onOpeningBalanceChanging: 'onVendorOpeingBalanceChanging',
|
|
||||||
onOpeningBalanceChanged: 'onVendorOpeingBalanceChanged',
|
|
||||||
|
|
||||||
onActivating: 'onVendorActivating',
|
|
||||||
onActivated: 'onVendorActivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Items service.
|
|
||||||
*/
|
|
||||||
item: {
|
|
||||||
onViewed: 'onItemViewed',
|
|
||||||
|
|
||||||
onCreated: 'onItemCreated',
|
|
||||||
onCreating: 'onItemCreating',
|
|
||||||
|
|
||||||
onEditing: 'onItemEditing',
|
|
||||||
onEdited: 'onItemEdited',
|
|
||||||
|
|
||||||
onDeleted: 'onItemDeleted',
|
|
||||||
onDeleting: 'onItemDeleting',
|
|
||||||
|
|
||||||
onActivating: 'onItemActivating',
|
|
||||||
onActivated: 'onItemActivated',
|
|
||||||
|
|
||||||
onInactivating: 'onInactivating',
|
|
||||||
onInactivated: 'onItemInactivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item category service.
|
|
||||||
*/
|
|
||||||
itemCategory: {
|
|
||||||
onCreated: 'onItemCategoryCreated',
|
|
||||||
onEdited: 'onItemCategoryEdited',
|
|
||||||
onDeleted: 'onItemCategoryDeleted',
|
|
||||||
onBulkDeleted: 'onItemCategoryBulkDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inventory service.
|
|
||||||
*/
|
|
||||||
inventory: {
|
|
||||||
onInventoryTransactionsCreated: 'onInventoryTransactionsCreated',
|
|
||||||
onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted',
|
|
||||||
|
|
||||||
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
|
|
||||||
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
|
|
||||||
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted',
|
|
||||||
|
|
||||||
onInventoryCostEntriesWritten: 'onInventoryCostEntriesWritten',
|
|
||||||
|
|
||||||
onCostLotsGLEntriesBeforeWrite: 'onInventoryCostLotsGLEntriesBeforeWrite',
|
|
||||||
onCostLotsGLEntriesWrite: 'onInventoryCostLotsGLEntriesWrite',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inventory adjustment service.
|
|
||||||
*/
|
|
||||||
inventoryAdjustment: {
|
|
||||||
onQuickCreating: 'onInventoryAdjustmentCreating',
|
|
||||||
onQuickCreated: 'onInventoryAdjustmentQuickCreated',
|
|
||||||
|
|
||||||
onCreated: 'onInventoryAdjustmentCreated',
|
|
||||||
|
|
||||||
onDeleting: 'onInventoryAdjustmentDeleting',
|
|
||||||
onDeleted: 'onInventoryAdjustmentDeleted',
|
|
||||||
|
|
||||||
onPublishing: 'onInventoryAdjustmentPublishing',
|
|
||||||
onPublished: 'onInventoryAdjustmentPublished',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bill landed cost.
|
|
||||||
*/
|
|
||||||
billLandedCost: {
|
|
||||||
onCreate: 'onBillLandedCostCreate',
|
|
||||||
onCreated: 'onBillLandedCostCreated',
|
|
||||||
onDelete: 'onBillLandedCostDelete',
|
|
||||||
onDeleted: 'onBillLandedCostDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
cashflow: {
|
|
||||||
onOwnerContributionCreate: 'onCashflowOwnerContributionCreate',
|
|
||||||
onOwnerContributionCreated: 'onCashflowOwnerContributionCreated',
|
|
||||||
|
|
||||||
onOtherIncomeCreate: 'onCashflowOtherIncomeCreate',
|
|
||||||
onOtherIncomeCreated: 'onCashflowOtherIncomeCreated',
|
|
||||||
|
|
||||||
onTransactionCreating: 'onCashflowTransactionCreating',
|
|
||||||
onTransactionCreated: 'onCashflowTransactionCreated',
|
|
||||||
|
|
||||||
onTransactionDeleting: 'onCashflowTransactionDeleting',
|
|
||||||
onTransactionDeleted: 'onCashflowTransactionDeleted',
|
|
||||||
|
|
||||||
onTransactionCategorizing: 'onTransactionCategorizing',
|
|
||||||
onTransactionCategorized: 'onCashflowTransactionCategorized',
|
|
||||||
|
|
||||||
onTransactionUncategorizedCreating: 'onTransactionUncategorizedCreating',
|
|
||||||
onTransactionUncategorizedCreated: 'onTransactionUncategorizedCreated',
|
|
||||||
|
|
||||||
onTransactionUncategorizing: 'onTransactionUncategorizing',
|
|
||||||
onTransactionUncategorized: 'onTransactionUncategorized',
|
|
||||||
|
|
||||||
onTransactionCategorizingAsExpense: 'onTransactionCategorizingAsExpense',
|
|
||||||
onTransactionCategorizedAsExpense: 'onTransactionCategorizedAsExpense',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Roles service events.
|
|
||||||
*/
|
|
||||||
roles: {
|
|
||||||
onCreate: 'onRoleCreate',
|
|
||||||
onCreated: 'onRoleCreated',
|
|
||||||
onEdit: 'onRoleEdit',
|
|
||||||
onEdited: 'onRoleEdited',
|
|
||||||
onDelete: 'onRoleDelete',
|
|
||||||
onDeleted: 'onRoleDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
tenantUser: {
|
|
||||||
onEdited: 'onTenantUserEdited',
|
|
||||||
onDeleted: 'onTenantUserDeleted',
|
|
||||||
onActivated: 'onTenantUserActivated',
|
|
||||||
onInactivated: 'onTenantUserInactivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Credit note service.
|
|
||||||
*/
|
|
||||||
creditNote: {
|
|
||||||
onPdfViewed: 'onCreditNotePdfViewed',
|
|
||||||
|
|
||||||
onCreate: 'onCreditNoteCreate',
|
|
||||||
onCreating: 'onCreditNoteCreating',
|
|
||||||
onCreated: 'onCreditNoteCreated',
|
|
||||||
|
|
||||||
onEditing: 'onCreditNoteEditing',
|
|
||||||
onEdit: 'onCreditNoteEdit',
|
|
||||||
onEdited: 'onCreditNoteEdited',
|
|
||||||
|
|
||||||
onDelete: 'onCreditNoteDelete',
|
|
||||||
onDeleting: 'onCreditNoteDeleting',
|
|
||||||
onDeleted: 'onCreditNoteDeleted',
|
|
||||||
|
|
||||||
onOpen: 'onCreditNoteOpen',
|
|
||||||
onOpening: 'onCreditNoteOpening',
|
|
||||||
onOpened: 'onCreditNoteOpened',
|
|
||||||
|
|
||||||
onRefundCreate: 'onCreditNoteRefundCreate',
|
|
||||||
onRefundCreating: 'onCreditNoteRefundCreating',
|
|
||||||
onRefundCreated: 'onCreditNoteRefundCreated',
|
|
||||||
|
|
||||||
onRefundDelete: 'onCreditNoteRefundDelete',
|
|
||||||
onRefundDeleting: 'onCreditNoteRefundDeleting',
|
|
||||||
onRefundDeleted: 'onCreditNoteRefundDeleted',
|
|
||||||
|
|
||||||
onApplyToInvoicesCreated: 'onCreditNoteApplyToInvoiceCreated',
|
|
||||||
onApplyToInvoicesCreate: 'onCreditNoteApplyToInvoiceCreate',
|
|
||||||
onApplyToInvoicesDeleted: 'onCreditNoteApplyToInvoiceDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vendor credit service.
|
|
||||||
*/
|
|
||||||
vendorCredit: {
|
|
||||||
onCreate: 'onVendorCreditCreate',
|
|
||||||
onCreating: 'onVendorCreditCreating',
|
|
||||||
onCreated: 'onVendorCreditCreated',
|
|
||||||
|
|
||||||
onEdit: 'onVendorCreditEdit',
|
|
||||||
onEditing: 'onVendorCreditEditing',
|
|
||||||
onEdited: 'onVendorCreditEdited',
|
|
||||||
|
|
||||||
onDelete: 'onVendorCreditDelete',
|
|
||||||
onDeleting: 'onVendorCreditDeleting',
|
|
||||||
onDeleted: 'onVendorCreditDeleted',
|
|
||||||
|
|
||||||
onOpen: 'onVendorCreditOpen',
|
|
||||||
onOpened: 'onVendorCreditOpened',
|
|
||||||
|
|
||||||
onRefundCreating: 'onVendorCreditRefundCreating',
|
|
||||||
onRefundCreate: 'onVendorCreditRefundCreate',
|
|
||||||
onRefundCreated: 'onVendorCreditRefundCreated',
|
|
||||||
|
|
||||||
onRefundDelete: 'onVendorCreditRefundDelete',
|
|
||||||
onRefundDeleting: 'onVendorCreditRefundDeleting',
|
|
||||||
onRefundDeleted: 'onVendorCreditRefundDeleted',
|
|
||||||
|
|
||||||
onApplyToInvoicesCreated: 'onVendorCreditApplyToInvoiceCreated',
|
|
||||||
onApplyToInvoicesCreate: 'onVendorCreditApplyToInvoiceCreate',
|
|
||||||
onApplyToInvoicesDeleted: 'onVendorCreditApplyToInvoiceDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
transactionsLocking: {
|
|
||||||
locked: 'onTransactionLockingLocked',
|
|
||||||
lockCanceled: 'onTransactionLockingLockCanceled',
|
|
||||||
partialUnlocked: 'onTransactionLockingPartialUnlocked',
|
|
||||||
partialUnlockCanceled: 'onTransactionLockingPartialUnlockCanceled',
|
|
||||||
},
|
|
||||||
|
|
||||||
warehouse: {
|
|
||||||
onCreate: 'onWarehouseCreate',
|
|
||||||
onCreated: 'onWarehouseCreated',
|
|
||||||
|
|
||||||
onEdit: 'onWarehouseEdit',
|
|
||||||
onEdited: 'onWarehouseEdited',
|
|
||||||
|
|
||||||
onDelete: 'onWarehouseDelete',
|
|
||||||
onDeleted: 'onWarehouseDeleted',
|
|
||||||
|
|
||||||
onActivate: 'onWarehouseActivate',
|
|
||||||
onActivated: 'onWarehouseActivated',
|
|
||||||
|
|
||||||
onMarkPrimary: 'onWarehouseMarkPrimary',
|
|
||||||
onMarkedPrimary: 'onWarehouseMarkedPrimary',
|
|
||||||
},
|
|
||||||
|
|
||||||
warehouseTransfer: {
|
|
||||||
onCreate: 'onWarehouseTransferCreate',
|
|
||||||
onCreated: 'onWarehouseTransferCreated',
|
|
||||||
|
|
||||||
onEdit: 'onWarehouseTransferEdit',
|
|
||||||
onEdited: 'onWarehouseTransferEdited',
|
|
||||||
|
|
||||||
onDelete: 'onWarehouseTransferDelete',
|
|
||||||
onDeleted: 'onWarehouseTransferDeleted',
|
|
||||||
|
|
||||||
onInitiate: 'onWarehouseTransferInitiate',
|
|
||||||
onInitiated: 'onWarehouseTransferInitated',
|
|
||||||
|
|
||||||
onTransfer: 'onWarehouseTransferInitiate',
|
|
||||||
onTransferred: 'onWarehouseTransferTransferred',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Branches.
|
|
||||||
*/
|
|
||||||
branch: {
|
|
||||||
onActivate: 'onBranchActivate',
|
|
||||||
onActivated: 'onBranchActivated',
|
|
||||||
|
|
||||||
onMarkPrimary: 'onBranchMarkPrimary',
|
|
||||||
onMarkedPrimary: 'onBranchMarkedPrimary',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Projects.
|
|
||||||
*/
|
|
||||||
project: {
|
|
||||||
onCreate: 'onProjectCreate',
|
|
||||||
onCreating: 'onProjectCreating',
|
|
||||||
onCreated: 'onProjectCreated',
|
|
||||||
|
|
||||||
onEdit: 'onEditProject',
|
|
||||||
onEditing: 'onEditingProject',
|
|
||||||
onEdited: 'onEditedProject',
|
|
||||||
|
|
||||||
onEditStatus: 'onEditStatusProject',
|
|
||||||
onEditingStatus: 'onEditingStatusProject',
|
|
||||||
onEditedStatus: 'onEditedStatusProject',
|
|
||||||
|
|
||||||
onDelete: 'onDeleteProject',
|
|
||||||
onDeleting: 'onDeletingProject',
|
|
||||||
onDeleted: 'onDeletedProject',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project Tasks.
|
|
||||||
*/
|
|
||||||
projectTask: {
|
|
||||||
onCreate: 'onProjectTaskCreate',
|
|
||||||
onCreating: 'onProjectTaskCreating',
|
|
||||||
onCreated: 'onProjectTaskCreated',
|
|
||||||
|
|
||||||
onEdit: 'onProjectTaskEdit',
|
|
||||||
onEditing: 'onProjectTaskEditing',
|
|
||||||
onEdited: 'onProjectTaskEdited',
|
|
||||||
|
|
||||||
onDelete: 'onProjectTaskDelete',
|
|
||||||
onDeleting: 'onProjectTaskDeleting',
|
|
||||||
onDeleted: 'onProjectTaskDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project Times.
|
|
||||||
*/
|
|
||||||
projectTime: {
|
|
||||||
onCreate: 'onProjectTimeCreate',
|
|
||||||
onCreating: 'onProjectTimeCreating',
|
|
||||||
onCreated: 'onProjectTimeCreated',
|
|
||||||
|
|
||||||
onEdit: 'onProjectTimeEdit',
|
|
||||||
onEditing: 'onProjectTimeEditing',
|
|
||||||
onEdited: 'onProjectTimeEdited',
|
|
||||||
|
|
||||||
onDelete: 'onProjectTimeDelete',
|
|
||||||
onDeleting: 'onProjectTimeDeleting',
|
|
||||||
onDeleted: 'onProjectTimeDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
taxRates: {
|
|
||||||
onCreating: 'onTaxRateCreating',
|
|
||||||
onCreated: 'onTaxRateCreated',
|
|
||||||
|
|
||||||
onEditing: 'onTaxRateEditing',
|
|
||||||
onEdited: 'onTaxRateEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onTaxRateDeleting',
|
|
||||||
onDeleted: 'onTaxRateDeleted',
|
|
||||||
|
|
||||||
onActivating: 'onTaxRateActivating',
|
|
||||||
onActivated: 'onTaxRateActivated',
|
|
||||||
|
|
||||||
onInactivating: 'onTaxRateInactivating',
|
|
||||||
onInactivated: 'onTaxRateInactivated',
|
|
||||||
},
|
|
||||||
|
|
||||||
plaid: {
|
|
||||||
onItemCreated: 'onPlaidItemCreated',
|
|
||||||
onTransactionsSynced: 'onPlaidTransactionsSynced',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Bank rules.
|
|
||||||
bankRules: {
|
|
||||||
onCreating: 'onBankRuleCreating',
|
|
||||||
onCreated: 'onBankRuleCreated',
|
|
||||||
|
|
||||||
onEditing: 'onBankRuleEditing',
|
|
||||||
onEdited: 'onBankRuleEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onBankRuleDeleting',
|
|
||||||
onDeleted: 'onBankRuleDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Bank matching.
|
|
||||||
bankMatch: {
|
|
||||||
onMatching: 'onBankTransactionMatching',
|
|
||||||
onMatched: 'onBankTransactionMatched',
|
|
||||||
|
|
||||||
onUnmatching: 'onBankTransactionUnmathcing',
|
|
||||||
onUnmatched: 'onBankTransactionUnmathced',
|
|
||||||
},
|
|
||||||
|
|
||||||
bankTransactions: {
|
|
||||||
onExcluding: 'onBankTransactionExclude',
|
|
||||||
onExcluded: 'onBankTransactionExcluded',
|
|
||||||
|
|
||||||
onUnexcluding: 'onBankTransactionUnexcluding',
|
|
||||||
onUnexcluded: 'onBankTransactionUnexcluded',
|
|
||||||
|
|
||||||
onPendingRemoving: 'onBankTransactionPendingRemoving',
|
|
||||||
onPendingRemoved: 'onBankTransactionPendingRemoved',
|
|
||||||
},
|
|
||||||
|
|
||||||
bankAccount: {
|
|
||||||
onDisconnecting: 'onBankAccountDisconnecting',
|
|
||||||
onDisconnected: 'onBankAccountDisconnected',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Import files.
|
|
||||||
import: {
|
|
||||||
onImportCommitted: 'onImportFileCommitted',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Branding templates
|
|
||||||
pdfTemplate: {
|
|
||||||
onCreating: 'onPdfTemplateCreating',
|
|
||||||
onCreated: 'onPdfTemplateCreated',
|
|
||||||
|
|
||||||
onEditing: 'onPdfTemplateEditing',
|
|
||||||
onEdited: 'onPdfTemplatedEdited',
|
|
||||||
|
|
||||||
onDeleting: 'onPdfTemplateDeleting',
|
|
||||||
onDeleted: 'onPdfTemplateDeleted',
|
|
||||||
|
|
||||||
onAssignedDefault: 'onPdfTemplateAssignedDefault',
|
|
||||||
onAssigningDefault: 'onPdfTemplateAssigningDefault',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Payment method.
|
|
||||||
paymentMethod: {
|
|
||||||
onEditing: 'onPaymentMethodEditing',
|
|
||||||
onEdited: 'onPaymentMethodEdited',
|
|
||||||
|
|
||||||
onDeleted: 'onPaymentMethodDeleted',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Payment methods integrations
|
|
||||||
paymentIntegrationLink: {
|
|
||||||
onPaymentIntegrationLink: 'onPaymentIntegrationLink',
|
|
||||||
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Stripe Payment Integration
|
|
||||||
stripeIntegration: {
|
|
||||||
onAccountCreated: 'onStripeIntegrationAccountCreated',
|
|
||||||
onAccountDeleted: 'onStripeIntegrationAccountDeleted',
|
|
||||||
|
|
||||||
onPaymentLinkCreated: 'onStripePaymentLinkCreated',
|
|
||||||
onPaymentLinkInactivated: 'onStripePaymentLinkInactivated',
|
|
||||||
|
|
||||||
onOAuthCodeGranted: 'onStripeOAuthCodeGranted',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Stripe Payment Webhooks
|
|
||||||
stripeWebhooks: {
|
|
||||||
onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted',
|
|
||||||
onAccountUpdated: 'onStripeAccountUpdated',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export class ModelEntityNotFound extends Error {
|
|
||||||
constructor(entityId, message?) {
|
|
||||||
message = message || `Entity with id ${entityId} does not exist`;
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
ExceptionFilter,
|
|
||||||
Catch,
|
|
||||||
ArgumentsHost,
|
|
||||||
HttpStatus,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Response } from 'express';
|
|
||||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
|
||||||
|
|
||||||
@Catch(ServiceError)
|
|
||||||
export class ServiceErrorFilter implements ExceptionFilter {
|
|
||||||
catch(exception: ServiceError, host: ArgumentsHost) {
|
|
||||||
const ctx = host.switchToHttp();
|
|
||||||
const response = ctx.getResponse<Response>();
|
|
||||||
const status = exception.getStatus();
|
|
||||||
|
|
||||||
response.status(status).json({
|
|
||||||
statusCode: status,
|
|
||||||
errorType: exception.errorType,
|
|
||||||
message: exception.message,
|
|
||||||
payload: exception.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import {
|
|
||||||
type ExecutionContext,
|
|
||||||
Injectable,
|
|
||||||
type NestInterceptor,
|
|
||||||
type CallHandler,
|
|
||||||
Optional,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { type Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
export function camelToSnake<T = any>(value: T) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.map(camelToSnake);
|
|
||||||
}
|
|
||||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(value).map(([key, value]) => [
|
|
||||||
key
|
|
||||||
.split(/(?=[A-Z])/)
|
|
||||||
.join('_')
|
|
||||||
.toLowerCase(),
|
|
||||||
camelToSnake(value),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function snakeToCamel<T = any>(value: T) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.map(snakeToCamel);
|
|
||||||
}
|
|
||||||
|
|
||||||
const impl = (str: string) => {
|
|
||||||
const converted = str.replace(/([-_]\w)/g, (group) =>
|
|
||||||
group[1].toUpperCase(),
|
|
||||||
);
|
|
||||||
return converted[0].toLowerCase() + converted.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(value).map(([key, value]) => [
|
|
||||||
impl(key),
|
|
||||||
snakeToCamel(value),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_STRATEGY = {
|
|
||||||
in: snakeToCamel,
|
|
||||||
out: camelToSnake,
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SerializeInterceptor implements NestInterceptor<any, any> {
|
|
||||||
constructor(@Optional() readonly strategy = DEFAULT_STRATEGY) {}
|
|
||||||
|
|
||||||
intercept(
|
|
||||||
context: ExecutionContext,
|
|
||||||
next: CallHandler<any>,
|
|
||||||
): Observable<any> {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
request.body = this.strategy.in(request.body);
|
|
||||||
|
|
||||||
// handle returns stream..
|
|
||||||
return next.handle().pipe(map(this.strategy.out));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import {
|
|
||||||
PipeTransform,
|
|
||||||
ArgumentMetadata,
|
|
||||||
BadRequestException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ZodSchema } from 'zod';
|
|
||||||
|
|
||||||
export class ZodValidationPipe implements PipeTransform {
|
|
||||||
constructor(private schema: ZodSchema) {}
|
|
||||||
|
|
||||||
transform(value: unknown, metadata: ArgumentMetadata) {
|
|
||||||
try {
|
|
||||||
const parsedValue = this.schema.parse(value);
|
|
||||||
return parsedValue;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
throw new BadRequestException(error.errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
// import hashObject from 'object-hash';
|
|
||||||
// import { EntityRepository } from './EntityRepository';
|
|
||||||
|
|
||||||
// export class CachableRepository extends EntityRepository {
|
|
||||||
// repositoryName: string;
|
|
||||||
// cache: any;
|
|
||||||
// i18n: any;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Constructor method.
|
|
||||||
// * @param {Knex} knex
|
|
||||||
// * @param {Cache} cache
|
|
||||||
// */
|
|
||||||
// constructor(knex, cache, i18n) {
|
|
||||||
// super(knex);
|
|
||||||
|
|
||||||
// this.cache = cache;
|
|
||||||
// this.i18n = i18n;
|
|
||||||
// this.repositoryName = this.constructor.name;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getByCache(key, callback) {
|
|
||||||
// return callback();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieve the cache key of the method name and arguments.
|
|
||||||
// * @param {string} method
|
|
||||||
// * @param {...any} args
|
|
||||||
// * @return {string}
|
|
||||||
// */
|
|
||||||
// getCacheKey(method, ...args) {
|
|
||||||
// const hashArgs = hashObject({ ...args });
|
|
||||||
// const repositoryName = this.repositoryName;
|
|
||||||
|
|
||||||
// return `${repositoryName}-${method}-${hashArgs}`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieve all entries with specified relations.
|
|
||||||
// * @param withRelations
|
|
||||||
// */
|
|
||||||
// all(withRelations?, trx?) {
|
|
||||||
// const cacheKey = this.getCacheKey('all', withRelations);
|
|
||||||
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.all(withRelations, trx);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Finds list of entities with specified attributes
|
|
||||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
|
||||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
// */
|
|
||||||
// find(attributeValues = {}, withRelations?) {
|
|
||||||
// const cacheKey = this.getCacheKey('find', attributeValues, withRelations);
|
|
||||||
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.find(attributeValues, withRelations);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Finds list of entities with attribute values that are different from specified ones
|
|
||||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
// */
|
|
||||||
// findWhereNot(attributeValues = {}, withRelations?) {
|
|
||||||
// const cacheKey = this.getCacheKey(
|
|
||||||
// 'findWhereNot',
|
|
||||||
// attributeValues,
|
|
||||||
// withRelations
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.findWhereNot(attributeValues, withRelations);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Finds list of entities with specified attributes (any of multiple specified values)
|
|
||||||
// * Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
|
||||||
// *
|
|
||||||
// * @param {string|Object} searchParam - attribute name or search criteria object
|
|
||||||
// * @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
|
||||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
// * @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
// */
|
|
||||||
// findWhereIn(searchParam, attributeValues, withRelations?) {
|
|
||||||
// const cacheKey = this.getCacheKey(
|
|
||||||
// 'findWhereIn',
|
|
||||||
// attributeValues,
|
|
||||||
// withRelations
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.findWhereIn(searchParam, attributeValues, withRelations);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Finds first entity by given parameters
|
|
||||||
// *
|
|
||||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
// * @returns {Promise<Object>}
|
|
||||||
// */
|
|
||||||
// findOne(attributeValues = {}, withRelations?) {
|
|
||||||
// const cacheKey = this.getCacheKey(
|
|
||||||
// 'findOne',
|
|
||||||
// attributeValues,
|
|
||||||
// withRelations
|
|
||||||
// );
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.findOne(attributeValues, withRelations);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Finds first entity by given parameters
|
|
||||||
// *
|
|
||||||
// * @param {string || number} id - value of id column of the entity
|
|
||||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
// * @returns {Promise<Object>}
|
|
||||||
// */
|
|
||||||
// findOneById(id, withRelations?) {
|
|
||||||
// const cacheKey = this.getCacheKey('findOneById', id, withRelations);
|
|
||||||
|
|
||||||
// return this.getByCache(cacheKey, () => {
|
|
||||||
// return super.findOneById(id, withRelations);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Persists new entity or an array of entities.
|
|
||||||
// * This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
|
||||||
// * Batch insert only works on PostgreSQL
|
|
||||||
// * @param {Object} entity - model instance or parameters for a new entity
|
|
||||||
// * @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
// */
|
|
||||||
// async create(entity, trx?) {
|
|
||||||
// const result = await super.create(entity, trx);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after insert operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
|
||||||
// *
|
|
||||||
// * @param {Object} entity - single entity instance
|
|
||||||
// * @param {Object} [trx] - knex transaction instance. If not specified, new implicit transaction will be used.
|
|
||||||
// * @returns {Promise<integer>} number of affected rows
|
|
||||||
// */
|
|
||||||
// async update(entity, whereAttributes?, trx?) {
|
|
||||||
// const result = await super.update(entity, whereAttributes, trx);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after update operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @param {Object} attributeValues - values to filter deleted entities by
|
|
||||||
// * @param {Object} [trx]
|
|
||||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
|
||||||
// */
|
|
||||||
// async deleteBy(attributeValues, trx?) {
|
|
||||||
// const result = await super.deleteBy(attributeValues, trx);
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @param {string || number} id - value of id column of the entity
|
|
||||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
|
||||||
// */
|
|
||||||
// deleteById(id: number | string, trx?) {
|
|
||||||
// const result = super.deleteById(id, trx);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after insert operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// *
|
|
||||||
// * @param {string|number[]} values -
|
|
||||||
// */
|
|
||||||
// async deleteWhereIn(field: string, values: string | number[]) {
|
|
||||||
// const result = await super.deleteWhereIn(field, values);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after delete operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// *
|
|
||||||
// * @param {string|number[]} values
|
|
||||||
// */
|
|
||||||
// async deleteWhereIdIn(values: string | number[], trx?) {
|
|
||||||
// const result = await super.deleteWhereIdIn(values, trx);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after delete operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// *
|
|
||||||
// * @param graph
|
|
||||||
// * @param options
|
|
||||||
// */
|
|
||||||
// async upsertGraph(graph, options) {
|
|
||||||
// const result = await super.upsertGraph(graph, options);
|
|
||||||
|
|
||||||
// // Flushes the repository cache after insert operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// *
|
|
||||||
// * @param {} whereAttributes
|
|
||||||
// * @param {string} field
|
|
||||||
// * @param {number} amount
|
|
||||||
// */
|
|
||||||
// async changeNumber(whereAttributes, field: string, amount: number, trx?) {
|
|
||||||
// const result = await super.changeNumber(
|
|
||||||
// whereAttributes,
|
|
||||||
// field,
|
|
||||||
// amount,
|
|
||||||
// trx
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // Flushes the repository cache after update operation.
|
|
||||||
// this.flushCache();
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Flush repository cache.
|
|
||||||
// */
|
|
||||||
// flushCache(): void {
|
|
||||||
// this.cache.delStartWith(this.repositoryName);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
import { cloneDeep, forOwn, isString } from 'lodash';
|
|
||||||
import { ModelEntityNotFound } from '../exceptions/ModelEntityNotFound';
|
|
||||||
import { Model } from 'objection';
|
|
||||||
|
|
||||||
function applyGraphFetched(withRelations, builder) {
|
|
||||||
const relations = Array.isArray(withRelations)
|
|
||||||
? withRelations
|
|
||||||
: typeof withRelations === 'string'
|
|
||||||
? withRelations.split(',').map((relation) => relation.trim())
|
|
||||||
: [];
|
|
||||||
|
|
||||||
relations.forEach((relation) => {
|
|
||||||
builder.withGraphFetched(relation);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EntityRepository {
|
|
||||||
idColumn: string = 'id';
|
|
||||||
knex: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the repository model binded it to knex instance.
|
|
||||||
*/
|
|
||||||
get model(): typeof Model {
|
|
||||||
throw new Error("The repository's model is not defined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all entries with specified relations.
|
|
||||||
* @param withRelations
|
|
||||||
*/
|
|
||||||
all(withRelations?, trx?) {
|
|
||||||
const builder = this.model.query(trx);
|
|
||||||
applyGraphFetched(withRelations, builder);
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds list of entities with specified attributes
|
|
||||||
*
|
|
||||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
|
||||||
* @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
*/
|
|
||||||
find(attributeValues = {}, withRelations?) {
|
|
||||||
const builder = this.model.query().where(attributeValues);
|
|
||||||
|
|
||||||
applyGraphFetched(withRelations, builder);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds list of entities with attribute values that are different from specified ones
|
|
||||||
*
|
|
||||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
*/
|
|
||||||
findWhereNot(attributeValues = {}, withRelations?) {
|
|
||||||
const builder = this.model.query().whereNot(attributeValues);
|
|
||||||
|
|
||||||
applyGraphFetched(withRelations, builder);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds list of entities with specified attributes (any of multiple specified values)
|
|
||||||
* Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
|
||||||
*
|
|
||||||
* @param {string|Object} searchParam - attribute name or search criteria object
|
|
||||||
* @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
|
||||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
*/
|
|
||||||
findWhereIn(searchParam, attributeValues, withRelations?) {
|
|
||||||
const commonBuilder = (builder) => {
|
|
||||||
applyGraphFetched(withRelations, builder);
|
|
||||||
};
|
|
||||||
if (isString(searchParam)) {
|
|
||||||
return this.model
|
|
||||||
.query()
|
|
||||||
.whereIn(searchParam, attributeValues)
|
|
||||||
.onBuild(commonBuilder);
|
|
||||||
} else {
|
|
||||||
const builder = this.model.query(this.knex).onBuild(commonBuilder);
|
|
||||||
|
|
||||||
forOwn(searchParam, (value, key) => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
builder.whereIn(key, value);
|
|
||||||
} else {
|
|
||||||
builder.where(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds first entity by given parameters
|
|
||||||
*
|
|
||||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
|
||||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
* @returns {Promise<Object>}
|
|
||||||
*/
|
|
||||||
async findOne(attributeValues = {}, withRelations?) {
|
|
||||||
const results = await this.find(attributeValues, withRelations);
|
|
||||||
return results[0] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds first entity by given parameters
|
|
||||||
*
|
|
||||||
* @param {string || number} id - value of id column of the entity
|
|
||||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
|
||||||
* @returns {Promise<Object>}
|
|
||||||
*/
|
|
||||||
findOneById(id, withRelations?) {
|
|
||||||
return this.findOne({ [this.idColumn]: id }, withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists new entity or an array of entities.
|
|
||||||
* This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
|
||||||
* Batch insert only works on PostgreSQL
|
|
||||||
*
|
|
||||||
* @param {Object} entity - model instance or parameters for a new entity
|
|
||||||
* @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
|
||||||
*/
|
|
||||||
create(entity, trx?) {
|
|
||||||
// Keep the input parameter immutable
|
|
||||||
const instanceDTO = cloneDeep(entity);
|
|
||||||
|
|
||||||
return this.model.query(trx).insert(instanceDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
|
||||||
*
|
|
||||||
* @param {Object} entity - single entity instance
|
|
||||||
* @returns {Promise<integer>} number of affected rows
|
|
||||||
*/
|
|
||||||
async update(entity, whereAttributes?, trx?) {
|
|
||||||
const entityDto = cloneDeep(entity);
|
|
||||||
const identityClause = {};
|
|
||||||
|
|
||||||
if (Array.isArray(this.idColumn)) {
|
|
||||||
this.idColumn.forEach(
|
|
||||||
(idColumn) => (identityClause[idColumn] = entityDto[idColumn]),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
identityClause[this.idColumn] = entityDto[this.idColumn];
|
|
||||||
}
|
|
||||||
const whereConditions = whereAttributes || identityClause;
|
|
||||||
const modifiedEntitiesCount = await this.model
|
|
||||||
.query(trx)
|
|
||||||
.where(whereConditions)
|
|
||||||
.update(entityDto);
|
|
||||||
|
|
||||||
if (modifiedEntitiesCount === 0) {
|
|
||||||
throw new ModelEntityNotFound(entityDto[this.idColumn]);
|
|
||||||
}
|
|
||||||
return modifiedEntitiesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} attributeValues - values to filter deleted entities by
|
|
||||||
* @param {Object} [trx]
|
|
||||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
|
||||||
*/
|
|
||||||
deleteBy(attributeValues, trx?) {
|
|
||||||
return this.model.query(trx).delete().where(attributeValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string || number} id - value of id column of the entity
|
|
||||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
|
||||||
*/
|
|
||||||
deleteById(id: number | string, trx?) {
|
|
||||||
return this.deleteBy(
|
|
||||||
{
|
|
||||||
[this.idColumn]: id,
|
|
||||||
},
|
|
||||||
trx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given entries in the array on the specific field.
|
|
||||||
* @param {string} field -
|
|
||||||
* @param {number|string} values -
|
|
||||||
*/
|
|
||||||
deleteWhereIn(field: string, values: (string | number)[], trx) {
|
|
||||||
return this.model.query(trx).whereIn(field, values).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string|number[]} values
|
|
||||||
*/
|
|
||||||
deleteWhereIdIn(values: (string | number)[], trx?) {
|
|
||||||
return this.deleteWhereIn(this.idColumn, values, trx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrary relation graphs can be upserted (insert + update + delete)
|
|
||||||
* using the upsertGraph method.
|
|
||||||
* @param graph
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
upsertGraph(graph, options) {
|
|
||||||
// Keep the input grpah immutable
|
|
||||||
const graphCloned = cloneDeep(graph);
|
|
||||||
return this.model.query().upsertGraph(graphCloned, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {object} whereAttributes
|
|
||||||
* @param {string} field
|
|
||||||
* @param amount
|
|
||||||
*/
|
|
||||||
changeNumber(whereAttributes, field: string, amount: number, trx) {
|
|
||||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
|
||||||
|
|
||||||
return this.model
|
|
||||||
.query(trx)
|
|
||||||
.where(whereAttributes)
|
|
||||||
[changeMethod](field, Math.abs(amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// import { CachableRepository } from './CachableRepository';
|
|
||||||
import { EntityRepository } from './EntityRepository';
|
|
||||||
|
|
||||||
export class TenantRepository extends EntityRepository {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum DiscountType {
|
|
||||||
Percentage = 'percentage',
|
|
||||||
Amount = 'amount',
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
export enum Features {
|
|
||||||
WAREHOUSES = 'warehouses',
|
|
||||||
BRANCHES = 'branches',
|
|
||||||
BankSyncing = 'BankSyncing',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFeatureAllItem {
|
|
||||||
name: string;
|
|
||||||
isAccessible: boolean;
|
|
||||||
defaultAccessible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFeatureConfiugration {
|
|
||||||
name: string;
|
|
||||||
defaultValue?: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
export const ACCOUNT_TYPE = {
|
|
||||||
CASH: 'cash',
|
|
||||||
BANK: 'bank',
|
|
||||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
|
||||||
INVENTORY: 'inventory',
|
|
||||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
|
||||||
FIXED_ASSET: 'fixed-asset',
|
|
||||||
NON_CURRENT_ASSET: 'none-current-asset',
|
|
||||||
|
|
||||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
|
||||||
CREDIT_CARD: 'credit-card',
|
|
||||||
TAX_PAYABLE: 'tax-payable',
|
|
||||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
|
||||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
|
||||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
|
||||||
|
|
||||||
EQUITY: 'equity',
|
|
||||||
INCOME: 'income',
|
|
||||||
OTHER_INCOME: 'other-income',
|
|
||||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
OTHER_EXPENSE: 'other-expense',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_PARENT_TYPE = {
|
|
||||||
CURRENT_ASSET: 'current-asset',
|
|
||||||
FIXED_ASSET: 'fixed-asset',
|
|
||||||
NON_CURRENT_ASSET: 'non-current-asset',
|
|
||||||
|
|
||||||
CURRENT_LIABILITY: 'current-liability',
|
|
||||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
|
||||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
|
||||||
|
|
||||||
EQUITY: 'equity',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
INCOME: 'income',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_ROOT_TYPE = {
|
|
||||||
ASSET: 'asset',
|
|
||||||
LIABILITY: 'liability',
|
|
||||||
EQUITY: 'equity',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
INCOME: 'income',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_NORMAL = {
|
|
||||||
CREDIT: 'credit',
|
|
||||||
DEBIT: 'debit',
|
|
||||||
};
|
|
||||||
export const ACCOUNT_TYPES = [
|
|
||||||
{
|
|
||||||
label: 'Cash',
|
|
||||||
key: ACCOUNT_TYPE.CASH,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Bank',
|
|
||||||
key: ACCOUNT_TYPE.BANK,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Accounts Receivable',
|
|
||||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Inventory',
|
|
||||||
key: ACCOUNT_TYPE.INVENTORY,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Current Asset',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Fixed Asset',
|
|
||||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Non-Current Asset',
|
|
||||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Accounts Payable',
|
|
||||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Credit Card',
|
|
||||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Tax Payable',
|
|
||||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Current Liability',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Long Term Liability',
|
|
||||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Non-Current Liability',
|
|
||||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Equity',
|
|
||||||
key: ACCOUNT_TYPE.EQUITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Income',
|
|
||||||
key: ACCOUNT_TYPE.INCOME,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Income',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cost of Goods Sold',
|
|
||||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Expense',
|
|
||||||
key: ACCOUNT_TYPE.EXPENSE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Expense',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getAccountsSupportsMultiCurrency = () => {
|
|
||||||
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const DATATYPES_LENGTH = {
|
|
||||||
STRING: 255,
|
|
||||||
TEXT: 65535,
|
|
||||||
INT_10: 4294967295,
|
|
||||||
DECIMAL_13_3: 9999999999.999,
|
|
||||||
DECIMAL_15_5: 999999999999.999,
|
|
||||||
};
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
// import { getTransactionsLockingSettingsSchema } from '@/api/controllers/TransactionsLocking/utils';
|
|
||||||
|
|
||||||
export const SettingsOptions = {
|
|
||||||
organization: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
base_currency: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
industry: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
location: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
fiscal_year: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
financial_date_start: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
language: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
time_zone: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
date_format: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
accounting_basis: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
manual_journals: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bill_payments: {
|
|
||||||
withdrawal_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sales_estimates: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
customer_notes: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sales_receipts: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
preferred_deposit_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
receipt_message: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sales_invoices: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
customer_notes: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
payment_receives: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
preferred_deposit_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
preferred_advance_deposit: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
preferred_sell_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
preferred_cost_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
preferred_inventory_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expenses: {
|
|
||||||
preferred_payment_account: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inventory: {
|
|
||||||
cost_compute_running: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accounts: {
|
|
||||||
account_code_required: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
account_code_unique: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cashflow: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
credit_note: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
customer_notes: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
vendor_credit: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
warehouse_transfers: {
|
|
||||||
next_number: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
number_prefix: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
auto_increment: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'sms-notification': {
|
|
||||||
'sms-notification-enable.sale-invoice-details': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'sms-notification-enable.sale-invoice-reminder': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'sms-notification-enable.sale-estimate-details': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'sms-notification-enable.sale-receipt-details': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'sms-notification-enable.payment-receive-details': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'sms-notification-enable.customer-balance': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'transactions-locking': {
|
|
||||||
'locking-type': {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
// ...getTransactionsLockingSettingsSchema([
|
|
||||||
// 'all',
|
|
||||||
// 'sales',
|
|
||||||
// 'purchases',
|
|
||||||
// 'financial',
|
|
||||||
// ]),
|
|
||||||
},
|
|
||||||
features: {
|
|
||||||
'multi-warehouses': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
'multi-branches': {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"HELLO": "Hello",
|
|
||||||
"PRODUCT": {
|
|
||||||
"NEW": "New Product: {name}"
|
|
||||||
},
|
|
||||||
"ENGLISH": "English",
|
|
||||||
"ARRAY": ["ONE", "TWO", "THREE"],
|
|
||||||
"cat": "Cat",
|
|
||||||
"cat_name": "Cat: {name}",
|
|
||||||
"set-up-password": {
|
|
||||||
"heading": "Hello, {username}",
|
|
||||||
"title": "Forgot password",
|
|
||||||
"followLink": "Please follow the link to set up your password"
|
|
||||||
},
|
|
||||||
"day_interval": {
|
|
||||||
"one": "Every day",
|
|
||||||
"other": "Every {count} days",
|
|
||||||
"zero": "Never"
|
|
||||||
},
|
|
||||||
"nested": "We go shopping: $t(test.day_interval, {{\"count\": {count} }})"
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import {
|
|
||||||
Injectable,
|
|
||||||
NestInterceptor,
|
|
||||||
ExecutionContext,
|
|
||||||
CallHandler,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ExcludeNullInterceptor implements NestInterceptor {
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
||||||
return next.handle().pipe(map((value) => (value === null ? '' : value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import {
|
|
||||||
Injectable,
|
|
||||||
NestInterceptor,
|
|
||||||
ExecutionContext,
|
|
||||||
CallHandler,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GlobalPrefixInterceptor implements NestInterceptor {
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
request.url = `/api${request.url}`;
|
|
||||||
return next.handle().pipe(
|
|
||||||
map((data) => {
|
|
||||||
return data;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import {
|
|
||||||
CallHandler,
|
|
||||||
ExecutionContext,
|
|
||||||
Injectable,
|
|
||||||
NestInterceptor,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ClsService } from 'nestjs-cls';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UserIpInterceptor implements NestInterceptor {
|
|
||||||
constructor(private readonly cls: ClsService) {}
|
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
const userIp = request.connection.remoteAddress;
|
|
||||||
this.cls.set('ip', userIp);
|
|
||||||
|
|
||||||
return next.handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
export interface IAccountDTO {
|
|
||||||
name: string;
|
|
||||||
code: string;
|
|
||||||
description: string;
|
|
||||||
accountType: string;
|
|
||||||
parentAccountId?: number;
|
|
||||||
active: boolean;
|
|
||||||
bankBalance?: number;
|
|
||||||
accountMask?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountCreateDTO extends IAccountDTO {
|
|
||||||
currencyCode?: string;
|
|
||||||
plaidAccountId?: string;
|
|
||||||
plaidItemId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEditDTO extends IAccountDTO {}
|
|
||||||
|
|
||||||
export interface IAccount {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
code: string;
|
|
||||||
index: number;
|
|
||||||
description: string;
|
|
||||||
accountType: string;
|
|
||||||
parentAccountId: number;
|
|
||||||
active: boolean;
|
|
||||||
predefined: boolean;
|
|
||||||
amount: number;
|
|
||||||
currencyCode: string;
|
|
||||||
transactions?: any[];
|
|
||||||
type?: any[];
|
|
||||||
accountNormal: string;
|
|
||||||
accountParentType: string;
|
|
||||||
bankBalance: string;
|
|
||||||
plaidItemId: number | null;
|
|
||||||
lastFeedsUpdatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AccountNormal {
|
|
||||||
DEBIT = 'debit',
|
|
||||||
CREDIT = 'credit',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountsTransactionsFilter {
|
|
||||||
accountId?: number;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountTransaction {
|
|
||||||
id?: number;
|
|
||||||
|
|
||||||
credit: number;
|
|
||||||
debit: number;
|
|
||||||
currencyCode: string;
|
|
||||||
exchangeRate: number;
|
|
||||||
|
|
||||||
accountId: number;
|
|
||||||
contactId?: number | null;
|
|
||||||
date: string | Date;
|
|
||||||
|
|
||||||
referenceType: string;
|
|
||||||
referenceTypeFormatted: string;
|
|
||||||
referenceId: number;
|
|
||||||
|
|
||||||
referenceNumber?: string;
|
|
||||||
|
|
||||||
transactionNumber?: string;
|
|
||||||
transactionType?: string;
|
|
||||||
|
|
||||||
note?: string;
|
|
||||||
|
|
||||||
index: number;
|
|
||||||
indexGroup?: number;
|
|
||||||
|
|
||||||
costable?: boolean;
|
|
||||||
|
|
||||||
userId?: number;
|
|
||||||
itemId?: number;
|
|
||||||
branchId?: number;
|
|
||||||
projectId?: number;
|
|
||||||
|
|
||||||
account?: IAccount;
|
|
||||||
|
|
||||||
taxRateId?: number;
|
|
||||||
taxRate?: number;
|
|
||||||
}
|
|
||||||
export interface IAccountResponse extends IAccount {}
|
|
||||||
|
|
||||||
export enum IAccountsStructureType {
|
|
||||||
Tree = 'tree',
|
|
||||||
Flat = 'flat',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountsFilter {
|
|
||||||
stringifiedFilterRoles?: string;
|
|
||||||
onlyInactive: boolean;
|
|
||||||
structure?: IAccountsStructureType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountType {
|
|
||||||
label: string;
|
|
||||||
key: string;
|
|
||||||
normal: string;
|
|
||||||
rootType: string;
|
|
||||||
childType: string;
|
|
||||||
balanceSheet: boolean;
|
|
||||||
incomeSheet: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountsTypesService {
|
|
||||||
getAccountsTypes(tenantId: number): Promise<IAccountType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventCreatingPayload {
|
|
||||||
tenantId: number;
|
|
||||||
accountDTO: any;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
export interface IAccountEventCreatedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
account: IAccount;
|
|
||||||
accountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventEditedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
account: IAccount;
|
|
||||||
oldAccount: IAccount;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventDeletedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
accountId: number;
|
|
||||||
oldAccount: IAccount;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventDeletePayload {
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
oldAccount: IAccount;
|
|
||||||
tenantId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventActivatedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
accountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AccountAction {
|
|
||||||
CREATE = 'Create',
|
|
||||||
EDIT = 'Edit',
|
|
||||||
DELETE = 'Delete',
|
|
||||||
VIEW = 'View',
|
|
||||||
TransactionsLocking = 'TransactionsLocking',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TaxRateAction {
|
|
||||||
CREATE = 'Create',
|
|
||||||
EDIT = 'Edit',
|
|
||||||
DELETE = 'Delete',
|
|
||||||
VIEW = 'View',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateAccountParams {
|
|
||||||
ignoreUniqueName: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { Item } from '@/modules/Items/models/Item';
|
|
||||||
// import { AbilitySubject } from '@/interfaces';
|
|
||||||
// import { IFilterRole } from '@/interfaces/DynamicFilter';
|
|
||||||
|
|
||||||
export interface IItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
code: string;
|
|
||||||
|
|
||||||
sellable: boolean;
|
|
||||||
purchasable: boolean;
|
|
||||||
|
|
||||||
costPrice: number;
|
|
||||||
sellPrice: number;
|
|
||||||
currencyCode: string;
|
|
||||||
|
|
||||||
costAccountId: number;
|
|
||||||
sellAccountId: number;
|
|
||||||
inventoryAccountId: number;
|
|
||||||
|
|
||||||
sellDescription: string;
|
|
||||||
purchaseDescription: string;
|
|
||||||
|
|
||||||
sellTaxRateId: number;
|
|
||||||
purchaseTaxRateId: number;
|
|
||||||
|
|
||||||
quantityOnHand: number;
|
|
||||||
|
|
||||||
note: string;
|
|
||||||
active: boolean;
|
|
||||||
|
|
||||||
categoryId: number;
|
|
||||||
userId: number;
|
|
||||||
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IItemDTO {
|
|
||||||
@ApiProperty()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
code: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
sellable: boolean;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
purchasable: boolean;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
costPrice: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
sellPrice: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
currencyCode: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
costAccountId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
sellAccountId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
inventoryAccountId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
sellDescription: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
purchaseDescription: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
sellTaxRateId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
purchaseTaxRateId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
quantityOnHand: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
note: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
active: boolean;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
categoryId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IItemCreateDTO extends IItemDTO {}
|
|
||||||
export interface IItemEditDTO extends IItemDTO {}
|
|
||||||
|
|
||||||
// export interface IItemsService {
|
|
||||||
// getItem(tenantId: number, itemId: number): Promise<IItem>;
|
|
||||||
// deleteItem(tenantId: number, itemId: number): Promise<void>;
|
|
||||||
// editItem(tenantId: number, itemId: number, itemDTO: IItemDTO): Promise<IItem>;
|
|
||||||
// newItem(tenantId: number, itemDTO: IItemDTO): Promise<IItem>;
|
|
||||||
// itemsList(
|
|
||||||
// tenantId: number,
|
|
||||||
// itemsFilter: IItemsFilter,
|
|
||||||
// ): Promise<{ items: IItem[] }>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export interface IItemsFilter extends IDynamicListFilterDTO {
|
|
||||||
// stringifiedFilterRoles?: string;
|
|
||||||
// page: number;
|
|
||||||
// pageSize: number;
|
|
||||||
// inactiveMode: boolean;
|
|
||||||
// viewSlug?: string;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export interface IItemsAutoCompleteFilter {
|
|
||||||
// limit: number;
|
|
||||||
// keyword: string;
|
|
||||||
// filterRoles?: IFilterRole[];
|
|
||||||
// columnSortBy: string;
|
|
||||||
// sortOrder: string;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export interface IItemEventCreatedPayload {
|
|
||||||
// tenantId: number;
|
|
||||||
item: Item;
|
|
||||||
itemId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IItemEventEditedPayload {
|
|
||||||
item: Item;
|
|
||||||
oldItem: Item;
|
|
||||||
itemId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IItemEventDeletingPayload {
|
|
||||||
// tenantId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
oldItem: Item;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IItemEventDeletedPayload {
|
|
||||||
// tenantId: number;
|
|
||||||
itemId: number;
|
|
||||||
oldItem: Item;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ItemAction {
|
|
||||||
CREATE = 'Create',
|
|
||||||
EDIT = 'Edit',
|
|
||||||
DELETE = 'Delete',
|
|
||||||
VIEW = 'View',
|
|
||||||
}
|
|
||||||
|
|
||||||
// export type ItemAbility = [ItemAction, AbilitySubject.Item];
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
export interface IModel {
|
|
||||||
name: string;
|
|
||||||
tableName: string;
|
|
||||||
fields: { [key: string]: any };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFilterMeta {
|
|
||||||
sortOrder: string;
|
|
||||||
sortBy: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPaginationMeta {
|
|
||||||
pageSize: number;
|
|
||||||
page: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaDefaultSort {
|
|
||||||
sortOrder: ISortOrder;
|
|
||||||
sortField: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IModelColumnType =
|
|
||||||
| 'text'
|
|
||||||
| 'number'
|
|
||||||
| 'enumeration'
|
|
||||||
| 'boolean'
|
|
||||||
| 'relation';
|
|
||||||
|
|
||||||
export type ISortOrder = 'DESC' | 'ASC';
|
|
||||||
|
|
||||||
export interface IModelMetaFieldCommon {
|
|
||||||
name: string;
|
|
||||||
column: string;
|
|
||||||
columnable?: boolean;
|
|
||||||
customQuery?: Function;
|
|
||||||
required?: boolean;
|
|
||||||
importHint?: string;
|
|
||||||
importableRelationLabel?: string;
|
|
||||||
order?: number;
|
|
||||||
unique?: number;
|
|
||||||
dataTransferObjectKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaFieldText {
|
|
||||||
fieldType: 'text';
|
|
||||||
minLength?: number;
|
|
||||||
maxLength?: number;
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldBoolean {
|
|
||||||
fieldType: 'boolean';
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldNumber {
|
|
||||||
fieldType: 'number';
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldDate {
|
|
||||||
fieldType: 'date';
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldUrl {
|
|
||||||
fieldType: 'url';
|
|
||||||
}
|
|
||||||
export type IModelMetaField = IModelMetaFieldCommon &
|
|
||||||
(
|
|
||||||
| IModelMetaFieldText
|
|
||||||
| IModelMetaFieldNumber
|
|
||||||
| IModelMetaFieldBoolean
|
|
||||||
| IModelMetaFieldDate
|
|
||||||
| IModelMetaFieldUrl
|
|
||||||
| IModelMetaEnumerationField
|
|
||||||
| IModelMetaRelationField
|
|
||||||
| IModelMetaCollectionField
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface IModelMetaEnumerationOption {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaEnumerationField {
|
|
||||||
fieldType: 'enumeration';
|
|
||||||
options: IModelMetaEnumerationOption[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaRelationFieldCommon {
|
|
||||||
fieldType: 'relation';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaRelationEnumerationField {
|
|
||||||
relationType: 'enumeration';
|
|
||||||
relationKey: string;
|
|
||||||
relationEntityLabel: string;
|
|
||||||
relationEntityKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaFieldWithFields {
|
|
||||||
fields: IModelMetaFieldCommon2 &
|
|
||||||
(
|
|
||||||
| IModelMetaFieldText
|
|
||||||
| IModelMetaFieldNumber
|
|
||||||
| IModelMetaFieldBoolean
|
|
||||||
| IModelMetaFieldDate
|
|
||||||
| IModelMetaFieldUrl
|
|
||||||
| IModelMetaEnumerationField
|
|
||||||
| IModelMetaRelationField
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IModelMetaCollectionObjectField extends IModelMetaFieldWithFields {
|
|
||||||
collectionOf: 'object';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaCollectionFieldCommon {
|
|
||||||
fieldType: 'collection';
|
|
||||||
collectionMinLength?: number;
|
|
||||||
collectionMaxLength?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IModelMetaCollectionField = IModelMetaCollectionFieldCommon &
|
|
||||||
IModelMetaCollectionObjectField;
|
|
||||||
|
|
||||||
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
|
||||||
IModelMetaRelationEnumerationField;
|
|
||||||
|
|
||||||
interface IModelPrintMeta {
|
|
||||||
pageTitle: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMeta {
|
|
||||||
defaultFilterField: string;
|
|
||||||
defaultSort: IModelMetaDefaultSort;
|
|
||||||
|
|
||||||
exportable?: boolean;
|
|
||||||
exportFlattenOn?: string;
|
|
||||||
|
|
||||||
importable?: boolean;
|
|
||||||
importAggregator?: string;
|
|
||||||
importAggregateOn?: string;
|
|
||||||
importAggregateBy?: string;
|
|
||||||
|
|
||||||
print?: IModelPrintMeta;
|
|
||||||
|
|
||||||
fields: Record<string, IModelMetaField>;
|
|
||||||
fields2: Record<string, IModelMetaField2>;
|
|
||||||
columns: Record<string, IModelMetaColumn>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----
|
|
||||||
export interface IModelMetaFieldCommon2 {
|
|
||||||
name: string;
|
|
||||||
required?: boolean;
|
|
||||||
importHint?: string;
|
|
||||||
order?: number;
|
|
||||||
unique?: number;
|
|
||||||
features?: Array<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IModelMetaRelationField2 {
|
|
||||||
fieldType: 'relation';
|
|
||||||
relationModel: string;
|
|
||||||
importableRelationLabel: string | string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IModelMetaField2 = IModelMetaFieldCommon2 &
|
|
||||||
(
|
|
||||||
| IModelMetaFieldText
|
|
||||||
| IModelMetaFieldNumber
|
|
||||||
| IModelMetaFieldBoolean
|
|
||||||
| IModelMetaFieldDate
|
|
||||||
| IModelMetaFieldUrl
|
|
||||||
| IModelMetaEnumerationField
|
|
||||||
| IModelMetaRelationField2
|
|
||||||
| IModelMetaCollectionField
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface ImodelMetaColumnMeta {
|
|
||||||
name: string;
|
|
||||||
accessor?: string;
|
|
||||||
exportable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IModelMetaColumnText {
|
|
||||||
type: 'text;';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IModelMetaColumnCollection {
|
|
||||||
type: 'collection';
|
|
||||||
collectionOf: 'object';
|
|
||||||
columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText };
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IModelMetaColumn = ImodelMetaColumnMeta &
|
|
||||||
(IModelMetaColumnText | IModelMetaColumnCollection);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface SubscriptionPayload {
|
|
||||||
lemonSqueezyId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SubscriptionPaymentStatus {
|
|
||||||
Succeed = 'succeed',
|
|
||||||
Failed = 'failed',
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { get } from 'lodash';
|
|
||||||
import { ACCOUNT_TYPES } from '@/constants/accounts';
|
|
||||||
|
|
||||||
export class AccountTypesUtils {
|
|
||||||
/**
|
|
||||||
* Retrieve account types list.
|
|
||||||
*/
|
|
||||||
static getList() {
|
|
||||||
return ACCOUNT_TYPES;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts types by the given root type.
|
|
||||||
* @param {string} rootType -
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
static getTypesByRootType(rootType: string) {
|
|
||||||
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve account type by the given account type key.
|
|
||||||
* @param {string} key
|
|
||||||
* @param {string} accessor
|
|
||||||
*/
|
|
||||||
static getType(key: string, accessor?: string) {
|
|
||||||
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
|
||||||
|
|
||||||
if (accessor) {
|
|
||||||
return get(type, accessor);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts types by the parent account type.
|
|
||||||
* @param {string} parentType
|
|
||||||
*/
|
|
||||||
static getTypesByParentType(parentType: string) {
|
|
||||||
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts types by the given account normal.
|
|
||||||
* @param {string} normal
|
|
||||||
*/
|
|
||||||
static getTypesByNormal(normal: string) {
|
|
||||||
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the root type equals the account type.
|
|
||||||
* @param {string} key
|
|
||||||
* @param {string} rootType
|
|
||||||
*/
|
|
||||||
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
|
||||||
return ACCOUNT_TYPES.some((type) => {
|
|
||||||
const isType = type.key === key;
|
|
||||||
const isRootType = type.rootType === rootType;
|
|
||||||
|
|
||||||
return isType && isRootType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the parent account type equals the account type key.
|
|
||||||
* @param {string} key - Account type key.
|
|
||||||
* @param {string} parentType - Account parent type.
|
|
||||||
*/
|
|
||||||
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
|
||||||
return ACCOUNT_TYPES.some((type) => {
|
|
||||||
const isType = type.key === key;
|
|
||||||
const isParentType = type.parentType === parentType;
|
|
||||||
|
|
||||||
return isType && isParentType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether account type has balance sheet.
|
|
||||||
* @param {string} key - Account type key.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static isTypeBalanceSheet(key: string): boolean {
|
|
||||||
return ACCOUNT_TYPES.some((type) => {
|
|
||||||
const isType = type.key === key;
|
|
||||||
return isType && type.balanceSheet;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether account type has profit/loss sheet.
|
|
||||||
* @param {string} key - Account type key.
|
|
||||||
*/
|
|
||||||
static isTypePLSheet(key: string): boolean {
|
|
||||||
return ACCOUNT_TYPES.some((type) => {
|
|
||||||
const isType = type.key === key;
|
|
||||||
return isType && type.incomeSheet;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ChromiumRoute, LibreOfficeRoute, PdfEngineRoute } from './_types';
|
|
||||||
|
|
||||||
export class Chromiumly {
|
|
||||||
public static readonly GOTENBERG_ENDPOINT = process.env.GOTENBERG_URL || '';
|
|
||||||
|
|
||||||
public static readonly CHROMIUM_PATH = 'forms/chromium/convert';
|
|
||||||
public static readonly PDF_ENGINES_PATH = 'forms/pdfengines';
|
|
||||||
public static readonly LIBRE_OFFICE_PATH = 'forms/libreoffice';
|
|
||||||
|
|
||||||
public static readonly GOTENBERG_DOCS_ENDPOINT =
|
|
||||||
process.env.GOTENBERG_DOCS_URL || '';
|
|
||||||
|
|
||||||
public static readonly CHROMIUM_ROUTES = {
|
|
||||||
url: ChromiumRoute.URL,
|
|
||||||
html: ChromiumRoute.HTML,
|
|
||||||
markdown: ChromiumRoute.MARKDOWN,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly PDF_ENGINE_ROUTES = {
|
|
||||||
merge: PdfEngineRoute.MERGE,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly LIBRE_OFFICE_ROUTES = {
|
|
||||||
convert: LibreOfficeRoute.CONVERT,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import FormData from 'form-data';
|
|
||||||
import { GotenbergUtils } from './GotenbergUtils';
|
|
||||||
import { PageProperties } from './_types';
|
|
||||||
|
|
||||||
export class ConverterUtils {
|
|
||||||
public static injectPageProperties(
|
|
||||||
data: FormData,
|
|
||||||
pageProperties: PageProperties
|
|
||||||
): void {
|
|
||||||
if (pageProperties.size) {
|
|
||||||
GotenbergUtils.assert(
|
|
||||||
pageProperties.size.width >= 1.0 && pageProperties.size.height >= 1.5,
|
|
||||||
'size is smaller than the minimum printing requirements (i.e. 1.0 x 1.5 in)'
|
|
||||||
);
|
|
||||||
|
|
||||||
data.append('paperWidth', pageProperties.size.width);
|
|
||||||
data.append('paperHeight', pageProperties.size.height);
|
|
||||||
}
|
|
||||||
if (pageProperties.margins) {
|
|
||||||
GotenbergUtils.assert(
|
|
||||||
pageProperties.margins.top >= 0 &&
|
|
||||||
pageProperties.margins.bottom >= 0 &&
|
|
||||||
pageProperties.margins.left >= 0 &&
|
|
||||||
pageProperties.margins.left >= 0,
|
|
||||||
'negative margins are not allowed'
|
|
||||||
);
|
|
||||||
data.append('marginTop', pageProperties.margins.top);
|
|
||||||
data.append('marginBottom', pageProperties.margins.bottom);
|
|
||||||
data.append('marginLeft', pageProperties.margins.left);
|
|
||||||
data.append('marginRight', pageProperties.margins.right);
|
|
||||||
}
|
|
||||||
if (pageProperties.preferCssPageSize) {
|
|
||||||
data.append(
|
|
||||||
'preferCssPageSize',
|
|
||||||
String(pageProperties.preferCssPageSize)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (pageProperties.printBackground) {
|
|
||||||
data.append('printBackground', String(pageProperties.printBackground));
|
|
||||||
}
|
|
||||||
if (pageProperties.landscape) {
|
|
||||||
data.append('landscape', String(pageProperties.landscape));
|
|
||||||
}
|
|
||||||
if (pageProperties.scale) {
|
|
||||||
GotenbergUtils.assert(
|
|
||||||
pageProperties.scale >= 0.1 && pageProperties.scale <= 2.0,
|
|
||||||
'scale is outside of [0.1 - 2] range'
|
|
||||||
);
|
|
||||||
data.append('scale', pageProperties.scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageProperties.nativePageRanges) {
|
|
||||||
GotenbergUtils.assert(
|
|
||||||
pageProperties.nativePageRanges.from > 0 &&
|
|
||||||
pageProperties.nativePageRanges.to > 0 &&
|
|
||||||
pageProperties.nativePageRanges.to >=
|
|
||||||
pageProperties.nativePageRanges.from,
|
|
||||||
'page ranges syntax error'
|
|
||||||
);
|
|
||||||
data.append(
|
|
||||||
'nativePageRanges',
|
|
||||||
`${pageProperties.nativePageRanges.from}-${pageProperties.nativePageRanges.to}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { Chromiumly } from './Chromiumly';
|
|
||||||
import { ChromiumRoute } from './_types';
|
|
||||||
|
|
||||||
export abstract class Converter {
|
|
||||||
readonly endpoint: string;
|
|
||||||
|
|
||||||
constructor(route: ChromiumRoute) {
|
|
||||||
this.endpoint = `${Chromiumly.GOTENBERG_ENDPOINT}/${Chromiumly.CHROMIUM_PATH}/${Chromiumly.CHROMIUM_ROUTES[route]}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import FormData from 'form-data';
|
|
||||||
import Axios from 'axios';
|
|
||||||
|
|
||||||
export class GotenbergUtils {
|
|
||||||
public static assert(condition: boolean, message: string): asserts condition {
|
|
||||||
if (!condition) {
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
|
|
||||||
try {
|
|
||||||
const response = await Axios.post(endpoint, data, {
|
|
||||||
headers: {
|
|
||||||
...data.getHeaders(),
|
|
||||||
},
|
|
||||||
responseType: 'arraybuffer', // This ensures you get a Buffer bac
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { constants, createReadStream, PathLike, promises } from 'fs';
|
|
||||||
import FormData from 'form-data';
|
|
||||||
import { GotenbergUtils } from './GotenbergUtils';
|
|
||||||
import { IConverter, PageProperties } from './_types';
|
|
||||||
import { PdfFormat, ChromiumRoute } from './_types';
|
|
||||||
import { ConverterUtils } from './ConvertUtils';
|
|
||||||
import { Converter } from './Converter';
|
|
||||||
|
|
||||||
export class HtmlConverter extends Converter implements IConverter {
|
|
||||||
constructor() {
|
|
||||||
super(ChromiumRoute.HTML);
|
|
||||||
}
|
|
||||||
|
|
||||||
async convert({
|
|
||||||
html,
|
|
||||||
properties,
|
|
||||||
pdfFormat,
|
|
||||||
}: {
|
|
||||||
html: PathLike;
|
|
||||||
properties?: PageProperties;
|
|
||||||
pdfFormat?: PdfFormat;
|
|
||||||
}): Promise<Buffer> {
|
|
||||||
try {
|
|
||||||
await promises.access(html, constants.R_OK);
|
|
||||||
const data = new FormData();
|
|
||||||
if (pdfFormat) {
|
|
||||||
data.append('pdfFormat', pdfFormat);
|
|
||||||
}
|
|
||||||
data.append('index.html', createReadStream(html));
|
|
||||||
if (properties) {
|
|
||||||
ConverterUtils.injectPageProperties(data, properties);
|
|
||||||
}
|
|
||||||
return GotenbergUtils.fetch(this.endpoint, data);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import FormData from 'form-data';
|
|
||||||
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
|
|
||||||
import { ConverterUtils } from './ConvertUtils';
|
|
||||||
import { Converter } from './Converter';
|
|
||||||
import { GotenbergUtils } from './GotenbergUtils';
|
|
||||||
|
|
||||||
export class UrlConverter extends Converter implements IConverter {
|
|
||||||
constructor() {
|
|
||||||
super(ChromiumRoute.URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
async convert({
|
|
||||||
url,
|
|
||||||
properties,
|
|
||||||
pdfFormat,
|
|
||||||
}: {
|
|
||||||
url: string;
|
|
||||||
properties?: PageProperties;
|
|
||||||
pdfFormat?: PdfFormat;
|
|
||||||
}): Promise<Buffer> {
|
|
||||||
try {
|
|
||||||
const _url = new URL(url);
|
|
||||||
const data = new FormData();
|
|
||||||
|
|
||||||
if (pdfFormat) {
|
|
||||||
data.append('pdfFormat', pdfFormat);
|
|
||||||
}
|
|
||||||
data.append('url', _url.href);
|
|
||||||
|
|
||||||
if (properties) {
|
|
||||||
ConverterUtils.injectPageProperties(data, properties);
|
|
||||||
}
|
|
||||||
return GotenbergUtils.fetch(this.endpoint, data);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { PathLike } from 'fs';
|
|
||||||
|
|
||||||
export type PageSize = {
|
|
||||||
width: number; // Paper width, in inches (default 8.5)
|
|
||||||
height: number; //Paper height, in inches (default 11)
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageMargins = {
|
|
||||||
top: number; // Top margin, in inches (default 0.39)
|
|
||||||
bottom: number; // Bottom margin, in inches (default 0.39)
|
|
||||||
left: number; // Left margin, in inches (default 0.39)
|
|
||||||
right: number; // Right margin, in inches (default 0.39)
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PageProperties = {
|
|
||||||
size?: PageSize;
|
|
||||||
margins?: PageMargins;
|
|
||||||
preferCssPageSize?: boolean; // Define whether to prefer page size as defined by CSS (default false)
|
|
||||||
printBackground?: boolean; // Print the background graphics (default false)
|
|
||||||
landscape?: boolean; // Set the paper orientation to landscape (default false)
|
|
||||||
scale?: number; // The scale of the page rendering (default 1.0)
|
|
||||||
nativePageRanges?: { from: number; to: number }; // Page ranges to print
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IConverter {
|
|
||||||
convert({
|
|
||||||
...args
|
|
||||||
}: {
|
|
||||||
[x: string]: string | PathLike | PageProperties | PdfFormat;
|
|
||||||
}): Promise<Buffer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PdfFormat {
|
|
||||||
A_1a = 'PDF/A-1a',
|
|
||||||
A_2b = 'PDF/A-2b',
|
|
||||||
A_3b = 'PDF/A-3b',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ChromiumRoute {
|
|
||||||
URL = 'url',
|
|
||||||
HTML = 'html',
|
|
||||||
MARKDOWN = 'markdown',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PdfEngineRoute {
|
|
||||||
MERGE = 'merge',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum LibreOfficeRoute {
|
|
||||||
CONVERT = 'convert',
|
|
||||||
}
|
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
/**
|
|
||||||
* A simple dependency graph
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
|
||||||
*
|
|
||||||
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
|
||||||
* parameter is "true" in which case it ignores them).
|
|
||||||
*
|
|
||||||
* @param edges The set of edges to DFS through
|
|
||||||
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
|
|
||||||
* @param result An array in which the results will be populated
|
|
||||||
* @param circular A boolean to allow circular dependencies
|
|
||||||
*/
|
|
||||||
function createDFS(edges, leavesOnly, result, circular) {
|
|
||||||
var visited = {};
|
|
||||||
return function (start) {
|
|
||||||
if (visited[start]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var inCurrentPath = {};
|
|
||||||
var currentPath = [];
|
|
||||||
var todo = []; // used as a stack
|
|
||||||
todo.push({ node: start, processed: false });
|
|
||||||
while (todo.length > 0) {
|
|
||||||
var current = todo[todo.length - 1]; // peek at the todo stack
|
|
||||||
var processed = current.processed;
|
|
||||||
var node = current.node;
|
|
||||||
if (!processed) {
|
|
||||||
// Haven't visited edges yet (visiting phase)
|
|
||||||
if (visited[node]) {
|
|
||||||
todo.pop();
|
|
||||||
continue;
|
|
||||||
} else if (inCurrentPath[node]) {
|
|
||||||
// It's not a DAG
|
|
||||||
if (circular) {
|
|
||||||
todo.pop();
|
|
||||||
// If we're tolerating cycles, don't revisit the node
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
currentPath.push(node);
|
|
||||||
throw new DepGraphCycleError(currentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
inCurrentPath[node] = true;
|
|
||||||
currentPath.push(node);
|
|
||||||
var nodeEdges = edges[node];
|
|
||||||
// (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
|
|
||||||
for (var i = nodeEdges.length - 1; i >= 0; i--) {
|
|
||||||
todo.push({ node: nodeEdges[i], processed: false });
|
|
||||||
}
|
|
||||||
current.processed = true;
|
|
||||||
} else {
|
|
||||||
// Have visited edges (stack unrolling phase)
|
|
||||||
todo.pop();
|
|
||||||
currentPath.pop();
|
|
||||||
inCurrentPath[node] = false;
|
|
||||||
visited[node] = true;
|
|
||||||
if (!leavesOnly || edges[node].length === 0) {
|
|
||||||
result.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple Dependency Graph
|
|
||||||
*/
|
|
||||||
var DepGraph = (DepGraph = function DepGraph(opts) {
|
|
||||||
this.nodes = {}; // Node -> Node/Data (treated like a Set)
|
|
||||||
this.outgoingEdges = {}; // Node -> [Dependency Node]
|
|
||||||
this.incomingEdges = {}; // Node -> [Dependant Node]
|
|
||||||
this.circular = opts && !!opts.circular; // Allows circular deps
|
|
||||||
});
|
|
||||||
|
|
||||||
DepGraph.fromArray = (
|
|
||||||
items,
|
|
||||||
options = { itemId: 'id', parentItemId: 'parent_id' }
|
|
||||||
) => {
|
|
||||||
const depGraph = new DepGraph();
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
|
||||||
depGraph.addNode(item[options.itemId], item);
|
|
||||||
});
|
|
||||||
items.forEach((item) => {
|
|
||||||
if (item[options.parentItemId]) {
|
|
||||||
depGraph.addDependency(item[options.parentItemId], item[options.itemId]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return depGraph;
|
|
||||||
};
|
|
||||||
|
|
||||||
DepGraph.prototype = {
|
|
||||||
/**
|
|
||||||
* The number of nodes in the graph.
|
|
||||||
*/
|
|
||||||
size: function () {
|
|
||||||
return Object.keys(this.nodes).length;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Add a node to the dependency graph. If a node already exists, this method will do nothing.
|
|
||||||
*/
|
|
||||||
addNode: function (node, data) {
|
|
||||||
if (!this.hasNode(node)) {
|
|
||||||
// Checking the arguments length allows the user to add a node with undefined data
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
this.nodes[node] = data;
|
|
||||||
} else {
|
|
||||||
this.nodes[node] = node;
|
|
||||||
}
|
|
||||||
this.outgoingEdges[node] = [];
|
|
||||||
this.incomingEdges[node] = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
|
|
||||||
*/
|
|
||||||
removeNode: function (node) {
|
|
||||||
if (this.hasNode(node)) {
|
|
||||||
delete this.nodes[node];
|
|
||||||
delete this.outgoingEdges[node];
|
|
||||||
delete this.incomingEdges[node];
|
|
||||||
[this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) {
|
|
||||||
Object.keys(edgeList).forEach(function (key) {
|
|
||||||
var idx = edgeList[key].indexOf(node);
|
|
||||||
if (idx >= 0) {
|
|
||||||
edgeList[key].splice(idx, 1);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Check if a node exists in the graph
|
|
||||||
*/
|
|
||||||
hasNode: function (node) {
|
|
||||||
return this.nodes.hasOwnProperty(node);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the data associated with a node name
|
|
||||||
*/
|
|
||||||
getNodeData: function (node) {
|
|
||||||
if (this.hasNode(node)) {
|
|
||||||
return this.nodes[node];
|
|
||||||
} else {
|
|
||||||
throw new Error('Node does not exist: ' + node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
|
|
||||||
*/
|
|
||||||
setNodeData: function (node, data) {
|
|
||||||
if (this.hasNode(node)) {
|
|
||||||
this.nodes[node] = data;
|
|
||||||
} else {
|
|
||||||
throw new Error('Node does not exist: ' + node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Add a dependency between two nodes. If either of the nodes does not exist,
|
|
||||||
* an Error will be thrown.
|
|
||||||
*/
|
|
||||||
addDependency: function (from, to) {
|
|
||||||
if (!this.hasNode(from)) {
|
|
||||||
throw new Error('Node does not exist: ' + from);
|
|
||||||
}
|
|
||||||
if (!this.hasNode(to)) {
|
|
||||||
throw new Error('Node does not exist: ' + to);
|
|
||||||
}
|
|
||||||
if (this.outgoingEdges[from].indexOf(to) === -1) {
|
|
||||||
this.outgoingEdges[from].push(to);
|
|
||||||
}
|
|
||||||
if (this.incomingEdges[to].indexOf(from) === -1) {
|
|
||||||
this.incomingEdges[to].push(from);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Remove a dependency between two nodes.
|
|
||||||
*/
|
|
||||||
removeDependency: function (from, to) {
|
|
||||||
var idx;
|
|
||||||
if (this.hasNode(from)) {
|
|
||||||
idx = this.outgoingEdges[from].indexOf(to);
|
|
||||||
if (idx >= 0) {
|
|
||||||
this.outgoingEdges[from].splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasNode(to)) {
|
|
||||||
idx = this.incomingEdges[to].indexOf(from);
|
|
||||||
if (idx >= 0) {
|
|
||||||
this.incomingEdges[to].splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Return a clone of the dependency graph. If any custom data is attached
|
|
||||||
* to the nodes, it will only be shallow copied.
|
|
||||||
*/
|
|
||||||
clone: function () {
|
|
||||||
var source = this;
|
|
||||||
var result = new DepGraph();
|
|
||||||
var keys = Object.keys(source.nodes);
|
|
||||||
keys.forEach(function (n) {
|
|
||||||
result.nodes[n] = source.nodes[n];
|
|
||||||
result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
|
|
||||||
result.incomingEdges[n] = source.incomingEdges[n].slice(0);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get an array containing the nodes that the specified node depends on (transitively).
|
|
||||||
*
|
|
||||||
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
||||||
*
|
|
||||||
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
|
|
||||||
* in the array.
|
|
||||||
*/
|
|
||||||
dependenciesOf: function (node, leavesOnly) {
|
|
||||||
if (this.hasNode(node)) {
|
|
||||||
var result = [];
|
|
||||||
var DFS = createDFS(
|
|
||||||
this.outgoingEdges,
|
|
||||||
leavesOnly,
|
|
||||||
result,
|
|
||||||
this.circular
|
|
||||||
);
|
|
||||||
DFS(node);
|
|
||||||
var idx = result.indexOf(node);
|
|
||||||
if (idx >= 0) {
|
|
||||||
result.splice(idx, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error('Node does not exist: ' + node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* get an array containing the nodes that depend on the specified node (transitively).
|
|
||||||
*
|
|
||||||
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
||||||
*
|
|
||||||
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
|
||||||
*/
|
|
||||||
dependantsOf: function (node, leavesOnly) {
|
|
||||||
if (this.hasNode(node)) {
|
|
||||||
var result = [];
|
|
||||||
var DFS = createDFS(
|
|
||||||
this.incomingEdges,
|
|
||||||
leavesOnly,
|
|
||||||
result,
|
|
||||||
this.circular
|
|
||||||
);
|
|
||||||
DFS(node);
|
|
||||||
var idx = result.indexOf(node);
|
|
||||||
if (idx >= 0) {
|
|
||||||
result.splice(idx, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error('Node does not exist: ' + node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Construct the overall processing order for the dependency graph.
|
|
||||||
*
|
|
||||||
* Throws an Error if the graph has a cycle.
|
|
||||||
*
|
|
||||||
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
|
|
||||||
*/
|
|
||||||
overallOrder: function (leavesOnly) {
|
|
||||||
var self = this;
|
|
||||||
var result = [];
|
|
||||||
var keys = Object.keys(this.nodes);
|
|
||||||
if (keys.length === 0) {
|
|
||||||
return result; // Empty graph
|
|
||||||
} else {
|
|
||||||
if (!this.circular) {
|
|
||||||
// Look for cycles - we run the DFS starting at all the nodes in case there
|
|
||||||
// are several disconnected subgraphs inside this dependency graph.
|
|
||||||
var CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular);
|
|
||||||
keys.forEach(function (n) {
|
|
||||||
CycleDFS(n);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var DFS = createDFS(
|
|
||||||
this.outgoingEdges,
|
|
||||||
leavesOnly,
|
|
||||||
result,
|
|
||||||
this.circular
|
|
||||||
);
|
|
||||||
// Find all potential starting points (nodes with nothing depending on them) an
|
|
||||||
// run a DFS starting at these points to get the order
|
|
||||||
keys
|
|
||||||
.filter(function (node) {
|
|
||||||
return self.incomingEdges[node].length === 0;
|
|
||||||
})
|
|
||||||
.forEach(function (n) {
|
|
||||||
DFS(n);
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we're allowing cycles - we need to run the DFS against any remaining
|
|
||||||
// nodes that did not end up in the initial result (as they are part of a
|
|
||||||
// subgraph that does not have a clear starting point)
|
|
||||||
if (this.circular) {
|
|
||||||
keys
|
|
||||||
.filter(function (node) {
|
|
||||||
return result.indexOf(node) === -1;
|
|
||||||
})
|
|
||||||
.forEach(function (n) {
|
|
||||||
DFS(n);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mapNodes(mapper) {},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycle error, including the path of the cycle.
|
|
||||||
*/
|
|
||||||
var DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
|
|
||||||
var message = 'Dependency Cycle Found: ' + cyclePath.join(' -> ');
|
|
||||||
var instance = new Error(message);
|
|
||||||
instance.cyclePath = cyclePath;
|
|
||||||
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
|
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
Error.captureStackTrace(instance, DepGraphCycleError);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
});
|
|
||||||
DepGraphCycleError.prototype = Object.create(Error.prototype, {
|
|
||||||
constructor: {
|
|
||||||
value: Error,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Object.setPrototypeOf(DepGraphCycleError, Error);
|
|
||||||
|
|
||||||
export default DepGraph;
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
|
|
||||||
const OperationType = {
|
|
||||||
LOGIC: 'LOGIC',
|
|
||||||
STRING: 'STRING',
|
|
||||||
COMPARISON: 'COMPARISON',
|
|
||||||
MATH: 'MATH',
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Lexer {
|
|
||||||
// operation table
|
|
||||||
static get optable() {
|
|
||||||
return {
|
|
||||||
'=': OperationType.LOGIC,
|
|
||||||
'&': OperationType.LOGIC,
|
|
||||||
'|': OperationType.LOGIC,
|
|
||||||
'?': OperationType.LOGIC,
|
|
||||||
':': OperationType.LOGIC,
|
|
||||||
|
|
||||||
'\'': OperationType.STRING,
|
|
||||||
'"': OperationType.STRING,
|
|
||||||
|
|
||||||
'!': OperationType.COMPARISON,
|
|
||||||
'>': OperationType.COMPARISON,
|
|
||||||
'<': OperationType.COMPARISON,
|
|
||||||
|
|
||||||
'(': OperationType.MATH,
|
|
||||||
')': OperationType.MATH,
|
|
||||||
'+': OperationType.MATH,
|
|
||||||
'-': OperationType.MATH,
|
|
||||||
'*': OperationType.MATH,
|
|
||||||
'/': OperationType.MATH,
|
|
||||||
'%': OperationType.MATH,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {*} expression -
|
|
||||||
*/
|
|
||||||
constructor(expression) {
|
|
||||||
this.currentIndex = 0;
|
|
||||||
this.input = expression;
|
|
||||||
this.tokenList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getTokens() {
|
|
||||||
let tok;
|
|
||||||
do {
|
|
||||||
// read current token, so step should be -1
|
|
||||||
tok = this.pickNext(-1);
|
|
||||||
const pos = this.currentIndex;
|
|
||||||
switch (Lexer.optable[tok]) {
|
|
||||||
case OperationType.LOGIC:
|
|
||||||
// == && || ===
|
|
||||||
this.readLogicOpt(tok);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OperationType.STRING:
|
|
||||||
this.readString(tok);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OperationType.COMPARISON:
|
|
||||||
this.readCompare(tok);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OperationType.MATH:
|
|
||||||
this.receiveToken();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
this.readValue(tok);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the pos not changed, this loop will go into a infinite loop, every step of while loop,
|
|
||||||
// we must move the pos forward
|
|
||||||
// so here we should throw error, for example `1 & 2`
|
|
||||||
if (pos === this.currentIndex && tok !== undefined) {
|
|
||||||
const err = new Error(`unkonw token ${tok} from input string ${this.input}`);
|
|
||||||
err.name = 'UnknowToken';
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
} while (tok !== undefined)
|
|
||||||
|
|
||||||
return this.tokenList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* read next token, the index param can set next step, default go foward 1 step
|
|
||||||
*
|
|
||||||
* @param index next postion
|
|
||||||
*/
|
|
||||||
pickNext(index = 0) {
|
|
||||||
return this.input[index + this.currentIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store token into result tokenList, and move the pos index
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
*/
|
|
||||||
receiveToken(index = 1) {
|
|
||||||
const tok = this.input.slice(this.currentIndex, this.currentIndex + index).trim();
|
|
||||||
// skip empty string
|
|
||||||
if (tok) {
|
|
||||||
this.tokenList.push(tok);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentIndex += index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ' or "
|
|
||||||
readString(tok) {
|
|
||||||
let next;
|
|
||||||
let index = 0;
|
|
||||||
do {
|
|
||||||
next = this.pickNext(index);
|
|
||||||
index += 1;
|
|
||||||
} while (next !== tok && next !== undefined);
|
|
||||||
this.receiveToken(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// > or < or >= or <= or !==
|
|
||||||
// tok in (>, <, !)
|
|
||||||
readCompare(tok) {
|
|
||||||
if (this.pickNext() !== '=') {
|
|
||||||
this.receiveToken(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// !==
|
|
||||||
if (tok === '!' && this.pickNext(1) === '=') {
|
|
||||||
this.receiveToken(3);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.receiveToken(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === or ==
|
|
||||||
// && ||
|
|
||||||
readLogicOpt(tok) {
|
|
||||||
if (this.pickNext() === tok) {
|
|
||||||
// ===
|
|
||||||
if (tok === '=' && this.pickNext(1) === tok) {
|
|
||||||
return this.receiveToken(3);
|
|
||||||
}
|
|
||||||
// == && ||
|
|
||||||
return this.receiveToken(2);
|
|
||||||
}
|
|
||||||
// handle as &&
|
|
||||||
// a ? b : c is equal to a && b || c
|
|
||||||
if (tok === '?' || tok === ':') {
|
|
||||||
return this.receiveToken(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readValue(tok) {
|
|
||||||
if (!tok) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
while (!Lexer.optable[tok] && tok !== undefined) {
|
|
||||||
tok = this.pickNext(index);
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
this.receiveToken(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function token(expression) {
|
|
||||||
const lexer = new Lexer(expression);
|
|
||||||
return lexer.getTokens();
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
export const OPERATION = {
|
|
||||||
'!': 5,
|
|
||||||
'*': 4,
|
|
||||||
'/': 4,
|
|
||||||
'%': 4,
|
|
||||||
'+': 3,
|
|
||||||
'-': 3,
|
|
||||||
'>': 2,
|
|
||||||
'<': 2,
|
|
||||||
'>=': 2,
|
|
||||||
'<=': 2,
|
|
||||||
'===': 2,
|
|
||||||
'!==': 2,
|
|
||||||
'==': 2,
|
|
||||||
'!=': 2,
|
|
||||||
'&&': 1,
|
|
||||||
'||': 1,
|
|
||||||
'?': 1,
|
|
||||||
':': 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// export interface Node {
|
|
||||||
// left: Node | string | null;
|
|
||||||
// right: Node | string | null;
|
|
||||||
// operation: string;
|
|
||||||
// grouped?: boolean;
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default class Parser {
|
|
||||||
|
|
||||||
constructor(token) {
|
|
||||||
this.index = -1;
|
|
||||||
this.blockLevel = 0;
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return {Node | string} =-
|
|
||||||
*/
|
|
||||||
parse() {
|
|
||||||
let tok;
|
|
||||||
let root = {
|
|
||||||
left: null,
|
|
||||||
right: null,
|
|
||||||
operation: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
tok = this.parseStatement();
|
|
||||||
|
|
||||||
if (tok === null || tok === undefined) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.left === null) {
|
|
||||||
root.left = tok;
|
|
||||||
root.operation = this.nextToken();
|
|
||||||
|
|
||||||
if (!root.operation) {
|
|
||||||
return tok;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.right = this.parseStatement();
|
|
||||||
} else {
|
|
||||||
if (typeof tok !== 'string') {
|
|
||||||
throw new Error('operation must be string, but get ' + JSON.stringify(tok));
|
|
||||||
}
|
|
||||||
root = this.addNode(tok, this.parseStatement(), root);
|
|
||||||
}
|
|
||||||
} while (tok);
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextToken() {
|
|
||||||
this.index += 1;
|
|
||||||
return this.token[this.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
prevToken() {
|
|
||||||
return this.token[this.index - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} operation
|
|
||||||
* @param {Node|String|null} right
|
|
||||||
* @param {Node} root
|
|
||||||
*/
|
|
||||||
addNode(operation, right, root) {
|
|
||||||
let pre = root;
|
|
||||||
|
|
||||||
if (this.compare(pre.operation, operation) < 0 && !pre.grouped) {
|
|
||||||
|
|
||||||
while (pre.right !== null &&
|
|
||||||
typeof pre.right !== 'string' &&
|
|
||||||
this.compare(pre.right.operation, operation) < 0 && !pre.right.grouped) {
|
|
||||||
pre = pre.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.right = {
|
|
||||||
operation,
|
|
||||||
left: pre.right,
|
|
||||||
right,
|
|
||||||
};
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
left: pre,
|
|
||||||
right,
|
|
||||||
operation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} a
|
|
||||||
* @param {String} b
|
|
||||||
*/
|
|
||||||
compare(a, b) {
|
|
||||||
if (!OPERATION.hasOwnProperty(a) || !OPERATION.hasOwnProperty(b)) {
|
|
||||||
throw new Error(`unknow operation ${a} or ${b}`);
|
|
||||||
}
|
|
||||||
return OPERATION[a] - OPERATION[b];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string | Node | null
|
|
||||||
*/
|
|
||||||
parseStatement() {
|
|
||||||
const token = this.nextToken();
|
|
||||||
if (token === '(') {
|
|
||||||
this.blockLevel += 1;
|
|
||||||
const node = this.parse();
|
|
||||||
this.blockLevel -= 1;
|
|
||||||
|
|
||||||
if (typeof node !== 'string') {
|
|
||||||
node.grouped = true;
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token === ')') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token === '!') {
|
|
||||||
return { left: null, operation: token, right: this.parseStatement() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 > -12 or -12 + 10
|
|
||||||
if (token === '-' && (OPERATION[this.prevToken()] > 0 || this.prevToken() === undefined)) {
|
|
||||||
return { left: '0', operation: token, right: this.parseStatement(), grouped: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { OPERATION } from './Parser';
|
|
||||||
|
|
||||||
export default class QueryParser {
|
|
||||||
|
|
||||||
constructor(tree, queries) {
|
|
||||||
this.tree = tree;
|
|
||||||
this.queries = queries;
|
|
||||||
this.query = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setQuery(query) {
|
|
||||||
this.query = query.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
parse() {
|
|
||||||
return this.parseNode(this.tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseNode(node) {
|
|
||||||
if (typeof node === 'string') {
|
|
||||||
const nodeQuery = this.getQuery(node);
|
|
||||||
return (query) => { nodeQuery(query); };
|
|
||||||
}
|
|
||||||
if (OPERATION[node.operation] === undefined) {
|
|
||||||
throw new Error(`unknow expression ${node.operation}`);
|
|
||||||
}
|
|
||||||
const leftQuery = this.getQuery(node.left);
|
|
||||||
const rightQuery = this.getQuery(node.right);
|
|
||||||
|
|
||||||
switch (node.operation) {
|
|
||||||
case '&&':
|
|
||||||
case 'AND':
|
|
||||||
default:
|
|
||||||
return (nodeQuery) => nodeQuery.where((query) => {
|
|
||||||
query.where((q) => { leftQuery(q); });
|
|
||||||
query.andWhere((q) => { rightQuery(q); });
|
|
||||||
});
|
|
||||||
case '||':
|
|
||||||
case 'OR':
|
|
||||||
return (nodeQuery) => nodeQuery.where((query) => {
|
|
||||||
query.where((q) => { leftQuery(q); });
|
|
||||||
query.orWhere((q) => { rightQuery(q); });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getQuery(node) {
|
|
||||||
if (typeof node !== 'string' && node !== null) {
|
|
||||||
return this.parseNode(node);
|
|
||||||
}
|
|
||||||
const value = parseFloat(node);
|
|
||||||
|
|
||||||
if (!isNaN(value)) {
|
|
||||||
if (typeof this.queries[node] === 'undefined') {
|
|
||||||
throw new Error(`unknow query under index ${node}`);
|
|
||||||
}
|
|
||||||
return this.queries[node];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
||||||
import { ClsMiddleware } from 'nestjs-cls';
|
|
||||||
import './utils/moment-mysql';
|
|
||||||
import { AppModule } from './modules/App/App.module';
|
|
||||||
import { ServiceErrorFilter } from './common/filters/service-error.filter';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
|
||||||
const app = await NestFactory.create(AppModule);
|
|
||||||
app.setGlobalPrefix('/api');
|
|
||||||
|
|
||||||
// create and mount the middleware manually here
|
|
||||||
app.use(new ClsMiddleware({}).use);
|
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
|
||||||
.setTitle('Bigcapital')
|
|
||||||
.setDescription('Financial accounting software')
|
|
||||||
.setVersion('1.0')
|
|
||||||
.addTag('cats')
|
|
||||||
.build();
|
|
||||||
|
|
||||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup('swagger', app, documentFactory);
|
|
||||||
|
|
||||||
app.useGlobalFilters(new ServiceErrorFilter());
|
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
|
||||||
}
|
|
||||||
bootstrap();
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class LoggerMiddleware implements NestMiddleware {
|
|
||||||
use(req: Request, res: Response, next: NextFunction) {
|
|
||||||
// @ts-expect-error
|
|
||||||
req.test = 'test';
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { QueryBuilder, Model } from 'objection';
|
|
||||||
|
|
||||||
interface PaginationResult<M extends Model> {
|
|
||||||
results: M[];
|
|
||||||
pagination: {
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PaginationQueryBuilderType<M extends Model> = QueryBuilder<
|
|
||||||
M,
|
|
||||||
PaginationResult<M>
|
|
||||||
>;
|
|
||||||
|
|
||||||
class PaginationQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
|
|
||||||
M,
|
|
||||||
R
|
|
||||||
> {
|
|
||||||
pagination(page: number, pageSize: number): PaginationQueryBuilderType<M> {
|
|
||||||
const query = super.page(page, pageSize);
|
|
||||||
|
|
||||||
return query.runAfter(({ results, total }) => {
|
|
||||||
return {
|
|
||||||
results,
|
|
||||||
pagination: {
|
|
||||||
total,
|
|
||||||
page: page + 1,
|
|
||||||
pageSize,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}) as unknown as PaginationQueryBuilderType<M>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BaseModel extends Model {
|
|
||||||
public readonly id: number;
|
|
||||||
public readonly tableName: string;
|
|
||||||
|
|
||||||
QueryBuilderType!: PaginationQueryBuilder<this>;
|
|
||||||
static QueryBuilder = PaginationQueryBuilder;
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import { Transformer } from '../Transformer/Transformer';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
|
|
||||||
import { assocDepthLevelToObjectTree } from '@/utils/assoc-depth-level-to-object-tree';
|
|
||||||
import { nestedArrayToFlatten } from '@/utils/nested-array-to-flatten';
|
|
||||||
import { IAccountsStructureType } from './Accounts.types';
|
|
||||||
|
|
||||||
export class AccountTransformer extends Transformer {
|
|
||||||
/**
|
|
||||||
* Include these attributes to sale invoice object.
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
public includeAttributes = (): string[] => {
|
|
||||||
return [
|
|
||||||
'accountTypeLabel',
|
|
||||||
'accountNormalFormatted',
|
|
||||||
'formattedAmount',
|
|
||||||
'flattenName',
|
|
||||||
'bankBalanceFormatted',
|
|
||||||
'lastFeedsUpdatedAtFormatted',
|
|
||||||
'isFeedsPaused',
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude attributes.
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
public excludeAttributes = (): string[] => {
|
|
||||||
return ['plaidItem'];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the flatten name with all dependants accounts names.
|
|
||||||
* @param {IAccount} account -
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public flattenName = (account: Account): string => {
|
|
||||||
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
|
|
||||||
account.id,
|
|
||||||
);
|
|
||||||
const prefixAccounts = parentDependantsIds.map((dependId) => {
|
|
||||||
const node = this.options.accountsGraph.getNodeData(dependId);
|
|
||||||
return `${node.name}: `;
|
|
||||||
});
|
|
||||||
return `${prefixAccounts}${account.name}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted account amount.
|
|
||||||
* @param {IAccount} invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedAmount = (account: Account): string => {
|
|
||||||
return this.formatNumber(account.amount, {
|
|
||||||
currencyCode: account.currencyCode,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted bank balance.
|
|
||||||
* @param {Account} account
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected bankBalanceFormatted = (account: Account): string => {
|
|
||||||
return this.formatNumber(account.bankBalance, {
|
|
||||||
currencyCode: account.currencyCode,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted last feeds update at.
|
|
||||||
* @param {IAccount} account
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected lastFeedsUpdatedAtFormatted = (account: Account): string => {
|
|
||||||
return account.lastFeedsUpdatedAt
|
|
||||||
? this.formatDate(account.lastFeedsUpdatedAt)
|
|
||||||
: '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the bank account connection is paused.
|
|
||||||
* @param account
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
protected isFeedsPaused = (account: Account): boolean => {
|
|
||||||
// return account.plaidItem?.isPaused || false;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves formatted account type label.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected accountTypeLabel = (account: Account): string => {
|
|
||||||
return this.context.i18n.t(account.accountTypeLabel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves formatted account normal.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected accountNormalFormatted = (account: Account): string => {
|
|
||||||
return this.context.i18n.t(account.accountNormalFormatted);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformes the accounts collection to flat or nested array.
|
|
||||||
* @param {IAccount[]}
|
|
||||||
* @returns {IAccount[]}
|
|
||||||
*/
|
|
||||||
protected postCollectionTransform = (accounts: Account[]) => {
|
|
||||||
// Transfom the flatten to accounts tree.
|
|
||||||
const transformed = flatToNestedArray(accounts, {
|
|
||||||
id: 'id',
|
|
||||||
parentId: 'parentAccountId',
|
|
||||||
});
|
|
||||||
// Associate `accountLevel` attr to indicate object depth.
|
|
||||||
const transformed2 = assocDepthLevelToObjectTree(
|
|
||||||
transformed,
|
|
||||||
1,
|
|
||||||
'accountLevel',
|
|
||||||
);
|
|
||||||
return this.options.structure === IAccountsStructureType.Flat
|
|
||||||
? nestedArrayToFlatten(transformed2)
|
|
||||||
: transformed2;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import { Transformer } from '../Transformer/Transformer';
|
|
||||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
|
||||||
|
|
||||||
export class AccountTransactionTransformer extends Transformer {
|
|
||||||
/**
|
|
||||||
* Include these attributes to sale invoice object.
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
public includeAttributes = (): string[] => {
|
|
||||||
return [
|
|
||||||
'date',
|
|
||||||
'formattedDate',
|
|
||||||
'transactionType',
|
|
||||||
'transactionId',
|
|
||||||
'transactionTypeFormatted',
|
|
||||||
'credit',
|
|
||||||
'debit',
|
|
||||||
'formattedCredit',
|
|
||||||
'formattedDebit',
|
|
||||||
'fcCredit',
|
|
||||||
'fcDebit',
|
|
||||||
'formattedFcCredit',
|
|
||||||
'formattedFcDebit',
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude all attributes of the model.
|
|
||||||
* @returns {Array<string>}
|
|
||||||
*/
|
|
||||||
public excludeAttributes = (): string[] => {
|
|
||||||
return ['*'];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted date.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public formattedDate(transaction: AccountTransaction) {
|
|
||||||
return this.formatDate(transaction.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted transaction type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public transactionTypeFormatted(transaction: AccountTransaction) {
|
|
||||||
return this.context.i18n.t(transaction.referenceTypeFormatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the tranasction type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public transactionType(transaction: AccountTransaction) {
|
|
||||||
return transaction.referenceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the transaction id.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
public transactionId(transaction: AccountTransaction) {
|
|
||||||
return transaction.referenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the credit amount.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCredit(transaction: AccountTransaction) {
|
|
||||||
return this.formatMoney(transaction.credit, {
|
|
||||||
excerptZero: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the credit amount.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedDebit(transaction: AccountTransaction) {
|
|
||||||
return this.formatMoney(transaction.debit, {
|
|
||||||
excerptZero: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the foreign credit amount.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected fcCredit(transaction: AccountTransaction) {
|
|
||||||
return transaction.credit * transaction.exchangeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the foreign debit amount.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected fcDebit(transaction: AccountTransaction) {
|
|
||||||
return transaction.debit * transaction.exchangeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted foreign credit amount.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedFcCredit(transaction: AccountTransaction) {
|
|
||||||
return this.formatMoney(this.fcCredit(transaction), {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
excerptZero: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted foreign debit amount.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedFcDebit(transaction: AccountTransaction) {
|
|
||||||
return this.formatMoney(this.fcDebit(transaction), {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
excerptZero: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,637 +0,0 @@
|
|||||||
export const OtherExpensesAccount = {
|
|
||||||
name: 'Other Expenses',
|
|
||||||
slug: 'other-expenses',
|
|
||||||
account_type: 'other-expense',
|
|
||||||
code: '40011',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TaxPayableAccount = {
|
|
||||||
name: 'Tax Payable',
|
|
||||||
slug: 'tax-payable',
|
|
||||||
account_type: 'tax-payable',
|
|
||||||
code: '20006',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UnearnedRevenueAccount = {
|
|
||||||
name: 'Unearned Revenue',
|
|
||||||
slug: 'unearned-revenue',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50005',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PrepardExpenses = {
|
|
||||||
name: 'Prepaid Expenses',
|
|
||||||
slug: 'prepaid-expenses',
|
|
||||||
account_type: 'other-current-asset',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '100010',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StripeClearingAccount = {
|
|
||||||
name: 'Stripe Clearing',
|
|
||||||
slug: 'stripe-clearing',
|
|
||||||
account_type: 'other-current-asset',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '100020',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DiscountExpenseAccount = {
|
|
||||||
name: 'Discount',
|
|
||||||
slug: 'discount',
|
|
||||||
account_type: 'other-income',
|
|
||||||
code: '40008',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PurchaseDiscountAccount = {
|
|
||||||
name: 'Purchase Discount',
|
|
||||||
slug: 'purchase-discount',
|
|
||||||
account_type: 'other-expense',
|
|
||||||
code: '40009',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OtherChargesAccount = {
|
|
||||||
name: 'Other Charges',
|
|
||||||
slug: 'other-charges',
|
|
||||||
account_type: 'other-income',
|
|
||||||
code: '40010',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SeedAccounts = [
|
|
||||||
{
|
|
||||||
name: 'Bank Account',
|
|
||||||
slug: 'bank-account',
|
|
||||||
account_type: 'bank',
|
|
||||||
code: '10001',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Saving Bank Account',
|
|
||||||
slug: 'saving-bank-account',
|
|
||||||
account_type: 'bank',
|
|
||||||
code: '10002',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Undeposited Funds',
|
|
||||||
slug: 'undeposited-funds',
|
|
||||||
account_type: 'cash',
|
|
||||||
code: '10003',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Petty Cash',
|
|
||||||
slug: 'petty-cash',
|
|
||||||
account_type: 'cash',
|
|
||||||
code: '10004',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Computer Equipment',
|
|
||||||
slug: 'computer-equipment',
|
|
||||||
code: '10005',
|
|
||||||
account_type: 'fixed-asset',
|
|
||||||
predefined: 0,
|
|
||||||
parent_account_id: null,
|
|
||||||
index: 1,
|
|
||||||
active: 1,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Office Equipment',
|
|
||||||
slug: 'office-equipment',
|
|
||||||
code: '10006',
|
|
||||||
account_type: 'fixed-asset',
|
|
||||||
predefined: 0,
|
|
||||||
parent_account_id: null,
|
|
||||||
index: 1,
|
|
||||||
active: 1,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Accounts Receivable (A/R)',
|
|
||||||
slug: 'accounts-receivable',
|
|
||||||
account_type: 'accounts-receivable',
|
|
||||||
code: '10007',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Inventory Asset',
|
|
||||||
slug: 'inventory-asset',
|
|
||||||
code: '10008',
|
|
||||||
account_type: 'inventory',
|
|
||||||
predefined: 1,
|
|
||||||
parent_account_id: null,
|
|
||||||
index: 1,
|
|
||||||
active: 1,
|
|
||||||
description:
|
|
||||||
'An account that holds valuation of products or goods that available for sale.',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Libilities
|
|
||||||
{
|
|
||||||
name: 'Accounts Payable (A/P)',
|
|
||||||
slug: 'accounts-payable',
|
|
||||||
account_type: 'accounts-payable',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '20001',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Owner A Drawings',
|
|
||||||
slug: 'owner-drawings',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '20002',
|
|
||||||
description: 'Withdrawals by the owners.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Loan',
|
|
||||||
slug: 'owner-drawings',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
code: '20003',
|
|
||||||
description: 'Money that has been borrowed from a creditor.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Opening Balance Liabilities',
|
|
||||||
slug: 'opening-balance-liabilities',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
code: '20004',
|
|
||||||
description:
|
|
||||||
'This account will hold the difference in the debits and credits entered during the opening balance..',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Revenue Received in Advance',
|
|
||||||
slug: 'revenue-received-in-advance',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '20005',
|
|
||||||
description: 'When customers pay in advance for products/services.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
TaxPayableAccount,
|
|
||||||
|
|
||||||
// Equity
|
|
||||||
{
|
|
||||||
name: 'Retained Earnings',
|
|
||||||
slug: 'retained-earnings',
|
|
||||||
account_type: 'equity',
|
|
||||||
code: '30001',
|
|
||||||
description:
|
|
||||||
'Retained earnings tracks net income from previous fiscal years.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Opening Balance Equity',
|
|
||||||
slug: 'opening-balance-equity',
|
|
||||||
account_type: 'equity',
|
|
||||||
code: '30002',
|
|
||||||
description:
|
|
||||||
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Owner's Equity",
|
|
||||||
slug: 'owner-equity',
|
|
||||||
account_type: 'equity',
|
|
||||||
code: '30003',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Drawings`,
|
|
||||||
slug: 'drawings',
|
|
||||||
account_type: 'equity',
|
|
||||||
code: '30003',
|
|
||||||
description:
|
|
||||||
'Goods purchased with the intention of selling these to customers',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Expenses
|
|
||||||
OtherExpensesAccount,
|
|
||||||
{
|
|
||||||
name: 'Other Expenses',
|
|
||||||
slug: 'other-expenses',
|
|
||||||
account_type: 'other-expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40001',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Cost of Goods Sold',
|
|
||||||
slug: 'cost-of-goods-sold',
|
|
||||||
account_type: 'cost-of-goods-sold',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40002',
|
|
||||||
description: 'Tracks the direct cost of the goods sold.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Office expenses',
|
|
||||||
slug: 'office-expenses',
|
|
||||||
account_type: 'expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40003',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Rent',
|
|
||||||
slug: 'rent',
|
|
||||||
account_type: 'expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40004',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Exchange Gain or Loss',
|
|
||||||
slug: 'exchange-grain-loss',
|
|
||||||
account_type: 'other-expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40005',
|
|
||||||
description: 'Tracks the gain and losses of the exchange differences.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Bank Fees and Charges',
|
|
||||||
slug: 'bank-fees-and-charges',
|
|
||||||
account_type: 'expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40006',
|
|
||||||
description:
|
|
||||||
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Depreciation Expense',
|
|
||||||
slug: 'depreciation-expense',
|
|
||||||
account_type: 'expense',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '40007',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Income
|
|
||||||
{
|
|
||||||
name: 'Sales of Product Income',
|
|
||||||
slug: 'sales-of-product-income',
|
|
||||||
account_type: 'income',
|
|
||||||
predefined: 1,
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50001',
|
|
||||||
index: 1,
|
|
||||||
active: 1,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Sales of Service Income',
|
|
||||||
slug: 'sales-of-service-income',
|
|
||||||
account_type: 'income',
|
|
||||||
predefined: 0,
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50002',
|
|
||||||
index: 1,
|
|
||||||
active: 1,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Uncategorized Income',
|
|
||||||
slug: 'uncategorized-income',
|
|
||||||
account_type: 'income',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50003',
|
|
||||||
description: '',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Other Income',
|
|
||||||
slug: 'other-income',
|
|
||||||
account_type: 'other-income',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50004',
|
|
||||||
description:
|
|
||||||
'The income activities are not associated to the core business.',
|
|
||||||
active: 1,
|
|
||||||
index: 1,
|
|
||||||
predefined: 0,
|
|
||||||
},
|
|
||||||
UnearnedRevenueAccount,
|
|
||||||
PrepardExpenses,
|
|
||||||
DiscountExpenseAccount,
|
|
||||||
PurchaseDiscountAccount,
|
|
||||||
OtherChargesAccount,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ACCOUNT_TYPE = {
|
|
||||||
CASH: 'cash',
|
|
||||||
BANK: 'bank',
|
|
||||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
|
||||||
INVENTORY: 'inventory',
|
|
||||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
|
||||||
FIXED_ASSET: 'fixed-asset',
|
|
||||||
NON_CURRENT_ASSET: 'none-current-asset',
|
|
||||||
|
|
||||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
|
||||||
CREDIT_CARD: 'credit-card',
|
|
||||||
TAX_PAYABLE: 'tax-payable',
|
|
||||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
|
||||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
|
||||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
|
||||||
|
|
||||||
EQUITY: 'equity',
|
|
||||||
INCOME: 'income',
|
|
||||||
OTHER_INCOME: 'other-income',
|
|
||||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
OTHER_EXPENSE: 'other-expense',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_PARENT_TYPE = {
|
|
||||||
CURRENT_ASSET: 'current-asset',
|
|
||||||
FIXED_ASSET: 'fixed-asset',
|
|
||||||
NON_CURRENT_ASSET: 'non-current-asset',
|
|
||||||
|
|
||||||
CURRENT_LIABILITY: 'current-liability',
|
|
||||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
|
||||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
|
||||||
|
|
||||||
EQUITY: 'equity',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
INCOME: 'income',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_ROOT_TYPE = {
|
|
||||||
ASSET: 'asset',
|
|
||||||
LIABILITY: 'liability',
|
|
||||||
EQUITY: 'equity',
|
|
||||||
EXPENSE: 'expense',
|
|
||||||
INCOME: 'income',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACCOUNT_NORMAL = {
|
|
||||||
CREDIT: 'credit',
|
|
||||||
DEBIT: 'debit',
|
|
||||||
};
|
|
||||||
export const ACCOUNT_TYPES = [
|
|
||||||
{
|
|
||||||
label: 'Cash',
|
|
||||||
key: ACCOUNT_TYPE.CASH,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Bank',
|
|
||||||
key: ACCOUNT_TYPE.BANK,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Accounts Receivable',
|
|
||||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Inventory',
|
|
||||||
key: ACCOUNT_TYPE.INVENTORY,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Current Asset',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Fixed Asset',
|
|
||||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Non-Current Asset',
|
|
||||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Accounts Payable',
|
|
||||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Credit Card',
|
|
||||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
multiCurrency: true,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Tax Payable',
|
|
||||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Current Liability',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Long Term Liability',
|
|
||||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Non-Current Liability',
|
|
||||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Equity',
|
|
||||||
key: ACCOUNT_TYPE.EQUITY,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
|
||||||
balanceSheet: true,
|
|
||||||
incomeSheet: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Income',
|
|
||||||
key: ACCOUNT_TYPE.INCOME,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Income',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
|
||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cost of Goods Sold',
|
|
||||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Expense',
|
|
||||||
key: ACCOUNT_TYPE.EXPENSE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Expense',
|
|
||||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
|
||||||
normal: ACCOUNT_NORMAL.DEBIT,
|
|
||||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
|
||||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
|
||||||
balanceSheet: false,
|
|
||||||
incomeSheet: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getAccountsSupportsMultiCurrency = () => {
|
|
||||||
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
|
||||||
};
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Param,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
Query,
|
|
||||||
ParseIntPipe,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AccountsApplication } from './AccountsApplication.service';
|
|
||||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
|
||||||
import { EditAccountDTO } from './EditAccount.dto';
|
|
||||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
|
||||||
import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
|
||||||
// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
|
||||||
// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
|
||||||
|
|
||||||
@Controller('accounts')
|
|
||||||
@PublicRoute()
|
|
||||||
export class AccountsController {
|
|
||||||
constructor(private readonly accountsApplication: AccountsApplication) {}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
async createAccount(@Body() accountDTO: CreateAccountDTO) {
|
|
||||||
return this.accountsApplication.createAccount(accountDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post(':id')
|
|
||||||
async editAccount(
|
|
||||||
@Param('id', ParseIntPipe) id: number,
|
|
||||||
@Body() accountDTO: EditAccountDTO,
|
|
||||||
) {
|
|
||||||
return this.accountsApplication.editAccount(id, accountDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(':id')
|
|
||||||
async deleteAccount(@Param('id', ParseIntPipe) id: number) {
|
|
||||||
return this.accountsApplication.deleteAccount(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post(':id/activate')
|
|
||||||
async activateAccount(@Param('id', ParseIntPipe) id: number) {
|
|
||||||
return this.accountsApplication.activateAccount(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post(':id/inactivate')
|
|
||||||
async inactivateAccount(@Param('id', ParseIntPipe) id: number) {
|
|
||||||
return this.accountsApplication.inactivateAccount(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('types')
|
|
||||||
async getAccountTypes() {
|
|
||||||
return this.accountsApplication.getAccountTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('transactions')
|
|
||||||
async getAccountTransactions(@Query() filter: IAccountsTransactionsFilter) {
|
|
||||||
return this.accountsApplication.getAccountsTransactions(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
|
||||||
async getAccount(@Param('id', ParseIntPipe) id: number) {
|
|
||||||
return this.accountsApplication.getAccount(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
async getAccounts(@Query() filter: IAccountsFilter) {
|
|
||||||
return this.accountsApplication.getAccounts(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
|
||||||
import { AccountsController } from './Accounts.controller';
|
|
||||||
import { AccountsApplication } from './AccountsApplication.service';
|
|
||||||
import { CreateAccountService } from './CreateAccount.service';
|
|
||||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
|
||||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
|
||||||
import { AccountRepository } from './repositories/Account.repository';
|
|
||||||
import { EditAccount } from './EditAccount.service';
|
|
||||||
import { DeleteAccount } from './DeleteAccount.service';
|
|
||||||
import { GetAccount } from './GetAccount.service';
|
|
||||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
|
||||||
import { ActivateAccount } from './ActivateAccount.service';
|
|
||||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
|
||||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
|
||||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
|
||||||
import { BankAccount } from '../BankingTransactions/models/BankAccount';
|
|
||||||
// import { GetAccountsService } from './GetAccounts.service';
|
|
||||||
|
|
||||||
const models = [RegisterTenancyModel(BankAccount)];
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [TenancyDatabaseModule],
|
|
||||||
controllers: [AccountsController],
|
|
||||||
providers: [
|
|
||||||
AccountsApplication,
|
|
||||||
CreateAccountService,
|
|
||||||
TenancyContext,
|
|
||||||
CommandAccountValidators,
|
|
||||||
AccountRepository,
|
|
||||||
EditAccount,
|
|
||||||
DeleteAccount,
|
|
||||||
GetAccount,
|
|
||||||
TransformerInjectable,
|
|
||||||
ActivateAccount,
|
|
||||||
GetAccountTypesService,
|
|
||||||
GetAccountTransactionsService,
|
|
||||||
...models,
|
|
||||||
],
|
|
||||||
exports: [AccountRepository, CreateAccountService, ...models],
|
|
||||||
})
|
|
||||||
export class AccountsModule {}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
// import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
|
||||||
|
|
||||||
export enum AccountNormal {
|
|
||||||
DEBIT = 'debit',
|
|
||||||
CREDIT = 'credit',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountsTransactionsFilter {
|
|
||||||
accountId?: number;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum IAccountsStructureType {
|
|
||||||
Tree = 'tree',
|
|
||||||
Flat = 'flat',
|
|
||||||
}
|
|
||||||
|
|
||||||
// export interface IAccountsFilter extends IDynamicListFilterDTO {
|
|
||||||
// }
|
|
||||||
export interface IAccountsFilter {
|
|
||||||
onlyInactive: boolean;
|
|
||||||
structure?: IAccountsStructureType;
|
|
||||||
}
|
|
||||||
export interface IAccountType {
|
|
||||||
label: string;
|
|
||||||
key: string;
|
|
||||||
normal: string;
|
|
||||||
rootType: string;
|
|
||||||
childType: string;
|
|
||||||
balanceSheet: boolean;
|
|
||||||
incomeSheet: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountsTypesService {
|
|
||||||
getAccountsTypes(): Promise<IAccountType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventCreatingPayload {
|
|
||||||
accountDTO: any;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
export interface IAccountEventCreatedPayload {
|
|
||||||
account: Account;
|
|
||||||
accountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventEditedPayload {
|
|
||||||
account: Account;
|
|
||||||
oldAccount: Account;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventDeletedPayload {
|
|
||||||
accountId: number;
|
|
||||||
oldAccount: Account;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventDeletePayload {
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
oldAccount: Account;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAccountEventActivatedPayload {
|
|
||||||
accountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AccountAction {
|
|
||||||
CREATE = 'Create',
|
|
||||||
EDIT = 'Edit',
|
|
||||||
DELETE = 'Delete',
|
|
||||||
VIEW = 'View',
|
|
||||||
TransactionsLocking = 'TransactionsLocking',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TaxRateAction {
|
|
||||||
CREATE = 'Create',
|
|
||||||
EDIT = 'Edit',
|
|
||||||
DELETE = 'Delete',
|
|
||||||
VIEW = 'View',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateAccountParams {
|
|
||||||
ignoreUniqueName: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetAccountTransactionPOJO {}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { CreateAccountService } from './CreateAccount.service';
|
|
||||||
import { DeleteAccount } from './DeleteAccount.service';
|
|
||||||
import { EditAccount } from './EditAccount.service';
|
|
||||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { EditAccountDTO } from './EditAccount.dto';
|
|
||||||
import { GetAccount } from './GetAccount.service';
|
|
||||||
import { ActivateAccount } from './ActivateAccount.service';
|
|
||||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
|
||||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
|
||||||
import {
|
|
||||||
IAccountsFilter,
|
|
||||||
IAccountsTransactionsFilter,
|
|
||||||
IGetAccountTransactionPOJO,
|
|
||||||
} from './Accounts.types';
|
|
||||||
import { GetAccountsService } from './GetAccounts.service';
|
|
||||||
import { IFilterMeta } from '@/interfaces/Model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AccountsApplication {
|
|
||||||
constructor(
|
|
||||||
private readonly createAccountService: CreateAccountService,
|
|
||||||
private readonly editAccountService: EditAccount,
|
|
||||||
private readonly deleteAccountService: DeleteAccount,
|
|
||||||
private readonly activateAccountService: ActivateAccount,
|
|
||||||
private readonly getAccountTypesService: GetAccountTypesService,
|
|
||||||
private readonly getAccountService: GetAccount,
|
|
||||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
|
||||||
private readonly getAccountsService: GetAccountsService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
|
||||||
* @returns {Promise<IAccount>}
|
|
||||||
*/
|
|
||||||
public createAccount = (
|
|
||||||
accountDTO: CreateAccountDTO,
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
): Promise<Account> => {
|
|
||||||
return this.createAccountService.createAccount(accountDTO, trx);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} accountId
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public deleteAccount = (accountId: number) => {
|
|
||||||
return this.deleteAccountService.deleteAccount(accountId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edits the given account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} accountId
|
|
||||||
* @param {IAccountEditDTO} accountDTO
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public editAccount = (accountId: number, accountDTO: EditAccountDTO) => {
|
|
||||||
return this.editAccountService.editAccount(accountId, accountDTO);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate the given account.
|
|
||||||
* @param {number} accountId - Account id.
|
|
||||||
*/
|
|
||||||
public activateAccount = (accountId: number) => {
|
|
||||||
return this.activateAccountService.activateAccount(accountId, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inactivate the given account.
|
|
||||||
* @param {number} accountId - Account id.
|
|
||||||
*/
|
|
||||||
public inactivateAccount = (accountId: number) => {
|
|
||||||
return this.activateAccountService.activateAccount(accountId, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the account details.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {number} accountId - Account id.
|
|
||||||
* @returns {Promise<IAccount>}
|
|
||||||
*/
|
|
||||||
public getAccount = (accountId: number) => {
|
|
||||||
return this.getAccountService.getAccount(accountId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all account types.
|
|
||||||
* @returns {Promise<IAccountType[]>}
|
|
||||||
*/
|
|
||||||
public getAccountTypes = () => {
|
|
||||||
return this.getAccountTypesService.getAccountsTypes();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the accounts list.
|
|
||||||
* @param {IAccountsFilter} filterDTO - Filter DTO.
|
|
||||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
|
||||||
*/
|
|
||||||
public getAccounts = (
|
|
||||||
filterDTO: IAccountsFilter,
|
|
||||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> => {
|
|
||||||
return this.getAccountsService.getAccountsList(filterDTO);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the given account transactions.
|
|
||||||
* @param {IAccountsTransactionsFilter} filter
|
|
||||||
* @returns {Promise<IGetAccountTransactionPOJO[]>}
|
|
||||||
*/
|
|
||||||
public getAccountsTransactions = (
|
|
||||||
filter: IAccountsTransactionsFilter,
|
|
||||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
|
||||||
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// import { Inject, Service } from 'typedi';
|
|
||||||
// import { AccountsApplication } from './AccountsApplication.service';
|
|
||||||
// import { Exportable } from '../Export/Exportable';
|
|
||||||
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
|
||||||
// import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
|
||||||
|
|
||||||
// @Service()
|
|
||||||
// export class AccountsExportable extends Exportable {
|
|
||||||
// @Inject()
|
|
||||||
// private accountsApplication: AccountsApplication;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieves the accounts data to exportable sheet.
|
|
||||||
// * @param {number} tenantId
|
|
||||||
// * @returns
|
|
||||||
// */
|
|
||||||
// public exportable(tenantId: number, query: IAccountsFilter) {
|
|
||||||
// const parsedQuery = {
|
|
||||||
// sortOrder: 'desc',
|
|
||||||
// columnSortBy: 'created_at',
|
|
||||||
// inactiveMode: false,
|
|
||||||
// ...query,
|
|
||||||
// structure: IAccountsStructureType.Flat,
|
|
||||||
// pageSize: EXPORT_SIZE_LIMIT,
|
|
||||||
// page: 1,
|
|
||||||
// } as IAccountsFilter;
|
|
||||||
|
|
||||||
// return this.accountsApplication
|
|
||||||
// .getAccounts(tenantId, parsedQuery)
|
|
||||||
// .then((output) => output.accounts);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
export const AccountsSampleData = [
|
|
||||||
{
|
|
||||||
'Account Name': 'Utilities Expense',
|
|
||||||
'Account Code': 9000,
|
|
||||||
Type: 'Expense',
|
|
||||||
Description: 'Omnis voluptatum consequatur.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Unearned Revenue',
|
|
||||||
'Account Code': 9010,
|
|
||||||
Type: 'Long Term Liability',
|
|
||||||
Description: 'Autem odit voluptas nihil unde.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Long-Term Debt',
|
|
||||||
'Account Code': 9020,
|
|
||||||
Type: 'Long Term Liability',
|
|
||||||
Description: 'In voluptas cumque exercitationem.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Salaries and Wages Expense',
|
|
||||||
'Account Code': 9030,
|
|
||||||
Type: 'Expense',
|
|
||||||
Description: 'Assumenda aspernatur soluta aliquid perspiciatis quasi.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Rental Income',
|
|
||||||
'Account Code': 9040,
|
|
||||||
Type: 'Income',
|
|
||||||
Description: 'Omnis possimus amet occaecati inventore.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Paypal',
|
|
||||||
'Account Code': 9050,
|
|
||||||
Type: 'Bank',
|
|
||||||
Description: 'In voluptas cumque exercitationem.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// import { Inject, Service } from 'typedi';
|
|
||||||
// import { Knex } from 'knex';
|
|
||||||
// import { IAccountCreateDTO } from '@/interfaces';
|
|
||||||
// import { CreateAccount } from './CreateAccount.service';
|
|
||||||
// import { Importable } from '../Import/Importable';
|
|
||||||
// import { AccountsSampleData } from './AccountsImportable.SampleData';
|
|
||||||
|
|
||||||
// @Service()
|
|
||||||
// export class AccountsImportable extends Importable {
|
|
||||||
// @Inject()
|
|
||||||
// private createAccountService: CreateAccount;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Importing to account service.
|
|
||||||
// * @param {number} tenantId
|
|
||||||
// * @param {IAccountCreateDTO} createAccountDTO
|
|
||||||
// * @returns
|
|
||||||
// */
|
|
||||||
// public importable(
|
|
||||||
// tenantId: number,
|
|
||||||
// createAccountDTO: IAccountCreateDTO,
|
|
||||||
// trx?: Knex.Transaction
|
|
||||||
// ) {
|
|
||||||
// return this.createAccountService.createAccount(
|
|
||||||
// tenantId,
|
|
||||||
// createAccountDTO,
|
|
||||||
// trx
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Concurrrency controlling of the importing process.
|
|
||||||
// * @returns {number}
|
|
||||||
// */
|
|
||||||
// public get concurrency() {
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieves the sample data that used to download accounts sample sheet.
|
|
||||||
// */
|
|
||||||
// public sampleData(): any[] {
|
|
||||||
// return AccountsSampleData;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { IAccountEventActivatedPayload } from './Accounts.types';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { AccountRepository } from './repositories/Account.repository';
|
|
||||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ActivateAccount {
|
|
||||||
constructor(
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
private readonly uow: UnitOfWork,
|
|
||||||
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
private readonly accountRepository: AccountRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activates/Inactivates the given account.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @param {boolean} activate
|
|
||||||
*/
|
|
||||||
public activateAccount = async (accountId: number, activate?: boolean) => {
|
|
||||||
// Retrieve the given account or throw not found error.
|
|
||||||
const oldAccount = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.findById(accountId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Get all children accounts.
|
|
||||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
|
||||||
const dependenciesAccounts = accountsGraph.dependenciesOf(accountId);
|
|
||||||
|
|
||||||
const patchAccountsIds = [...dependenciesAccounts, accountId];
|
|
||||||
|
|
||||||
// Activate account and associated transactions under unit-of-work environment.
|
|
||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
|
||||||
// Activate and inactivate the given accounts ids.
|
|
||||||
activate
|
|
||||||
? await this.accountRepository.activateByIds(patchAccountsIds, trx)
|
|
||||||
: await this.accountRepository.inactivateByIds(patchAccountsIds, trx);
|
|
||||||
|
|
||||||
// Triggers `onAccountActivated` event.
|
|
||||||
this.eventEmitter.emitAsync(events.accounts.onActivated, {
|
|
||||||
accountId,
|
|
||||||
trx,
|
|
||||||
} as IAccountEventActivatedPayload);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
||||||
// import { IAccountDTO, IAccount, IAccountCreateDTO } from './Accounts.types';
|
|
||||||
// import AccountTypesUtils from '@/lib/AccountTypes';
|
|
||||||
import { ServiceError } from '../Items/ServiceError';
|
|
||||||
import { ERRORS, MAX_ACCOUNTS_CHART_DEPTH } from './constants';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { AccountRepository } from './repositories/Account.repository';
|
|
||||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
|
||||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
|
||||||
import { EditAccountDTO } from './EditAccount.dto';
|
|
||||||
|
|
||||||
@Injectable({ scope: Scope.REQUEST })
|
|
||||||
export class CommandAccountValidators {
|
|
||||||
constructor(
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
private readonly accountRepository: AccountRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws error if the account was prefined.
|
|
||||||
* @param {Account} account
|
|
||||||
*/
|
|
||||||
public throwErrorIfAccountPredefined(account: Account) {
|
|
||||||
if (account.predefined) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_PREDEFINED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Diff account type between new and old account, throw service error
|
|
||||||
* if they have different account type.
|
|
||||||
* @param {Account|CreateAccountDTO|EditAccountDTO} oldAccount
|
|
||||||
* @param {Account|CreateAccountDTO|EditAccountDTO} newAccount
|
|
||||||
*/
|
|
||||||
public async isAccountTypeChangedOrThrowError(
|
|
||||||
oldAccount: Account | CreateAccountDTO | EditAccountDTO,
|
|
||||||
newAccount: Account | CreateAccountDTO | EditAccountDTO,
|
|
||||||
) {
|
|
||||||
if (oldAccount.accountType !== newAccount.accountType) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve account type or throws service error.
|
|
||||||
* @param {string} accountTypeKey -
|
|
||||||
* @return {IAccountType}
|
|
||||||
*/
|
|
||||||
public getAccountTypeOrThrowError(accountTypeKey: string) {
|
|
||||||
const accountType = AccountTypesUtils.getType(accountTypeKey);
|
|
||||||
|
|
||||||
if (!accountType) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return accountType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve parent account or throw service error.
|
|
||||||
* @param {number} accountId - Account id.
|
|
||||||
* @param {number} notAccountId - Ignore the account id.
|
|
||||||
*/
|
|
||||||
public async getParentAccountOrThrowError(
|
|
||||||
accountId: number,
|
|
||||||
notAccountId?: number,
|
|
||||||
) {
|
|
||||||
const parentAccount = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.findById(accountId)
|
|
||||||
.onBuild((query) => {
|
|
||||||
if (notAccountId) {
|
|
||||||
query.whereNot('id', notAccountId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!parentAccount) {
|
|
||||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return parentAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws error if the account type was not unique on the storage.
|
|
||||||
* @param {string} accountCode - Account code.
|
|
||||||
* @param {number} notAccountId - Ignore the account id.
|
|
||||||
*/
|
|
||||||
public async isAccountCodeUniqueOrThrowError(
|
|
||||||
accountCode: string,
|
|
||||||
notAccountId?: number,
|
|
||||||
) {
|
|
||||||
const account = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.where('code', accountCode)
|
|
||||||
.onBuild((query) => {
|
|
||||||
if (notAccountId) {
|
|
||||||
query.whereNot('id', notAccountId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (account.length > 0) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
|
||||||
'Account code is not unique.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the account name uniquiness.
|
|
||||||
* @param {string} accountName - Account name.
|
|
||||||
* @param {number} notAccountId - Ignore the account id.
|
|
||||||
*/
|
|
||||||
public async validateAccountNameUniquiness(
|
|
||||||
accountName: string,
|
|
||||||
notAccountId?: number,
|
|
||||||
) {
|
|
||||||
const foundAccount = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.findOne('name', accountName)
|
|
||||||
.onBuild((query) => {
|
|
||||||
if (notAccountId) {
|
|
||||||
query.whereNot('id', notAccountId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (foundAccount) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
|
||||||
'Account name is not unique.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given account type supports multi-currency.
|
|
||||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO -
|
|
||||||
*/
|
|
||||||
public validateAccountTypeSupportCurrency = (
|
|
||||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
|
||||||
baseCurrency: string,
|
|
||||||
) => {
|
|
||||||
// Can't continue to validate the type has multi-currency feature
|
|
||||||
// if the given currency equals the base currency or not assigned.
|
|
||||||
if (accountDTO.currencyCode === baseCurrency || !accountDTO.currencyCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const meta = AccountTypesUtils.getType(accountDTO.accountType);
|
|
||||||
|
|
||||||
// Throw error if the account type does not support multi-currency.
|
|
||||||
if (!meta?.multiCurrency) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the account DTO currency code whether equals the currency code of
|
|
||||||
* parent account.
|
|
||||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO
|
|
||||||
* @param {Account} parentAccount
|
|
||||||
* @param {string} baseCurrency -
|
|
||||||
* @throws {ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT)}
|
|
||||||
*/
|
|
||||||
public validateCurrentSameParentAccount = (
|
|
||||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
|
||||||
parentAccount: Account,
|
|
||||||
baseCurrency: string,
|
|
||||||
) => {
|
|
||||||
// If the account DTO currency not assigned and the parent account has no base currency.
|
|
||||||
if (
|
|
||||||
!accountDTO.currencyCode &&
|
|
||||||
parentAccount.currencyCode !== baseCurrency
|
|
||||||
) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
|
||||||
}
|
|
||||||
// If the account DTO is assigned and not equals the currency code of parent account.
|
|
||||||
if (
|
|
||||||
accountDTO.currencyCode &&
|
|
||||||
parentAccount.currencyCode !== accountDTO.currencyCode
|
|
||||||
) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws service error if parent account has different type.
|
|
||||||
* @param {IAccountDTO} accountDTO
|
|
||||||
* @param {IAccount} parentAccount
|
|
||||||
*/
|
|
||||||
public throwErrorIfParentHasDiffType(
|
|
||||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
|
||||||
parentAccount: Account,
|
|
||||||
) {
|
|
||||||
if (accountDTO.accountType !== parentAccount.accountType) {
|
|
||||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_HAS_DIFFERENT_TYPE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve account of throw service error in case account not found.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @return {IAccount}
|
|
||||||
*/
|
|
||||||
public async getAccountOrThrowError(accountId: number) {
|
|
||||||
const account = await this.accountRepository.findOneById(accountId);
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the max depth level of accounts chart.
|
|
||||||
* @param {number} parentAccountId - Parent account id.
|
|
||||||
*/
|
|
||||||
public async validateMaxParentAccountDepthLevels(parentAccountId: number) {
|
|
||||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
|
||||||
const parentDependantsIds = accountsGraph.dependantsOf(parentAccountId);
|
|
||||||
|
|
||||||
if (parentDependantsIds.length >= MAX_ACCOUNTS_CHART_DEPTH) {
|
|
||||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import {
|
|
||||||
IsString,
|
|
||||||
IsOptional,
|
|
||||||
IsInt,
|
|
||||||
MinLength,
|
|
||||||
MaxLength,
|
|
||||||
IsBoolean,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateAccountDTO {
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(6)
|
|
||||||
code?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
currencyCode?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
|
||||||
accountType: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
parentAccountId?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
active?: boolean;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
plaidAccountId?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
plaidItemId?: string;
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { kebabCase } from 'lodash';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import {
|
|
||||||
// IAccount,
|
|
||||||
// IAccountEventCreatedPayload,
|
|
||||||
// IAccountCreateDTO,
|
|
||||||
IAccountEventCreatingPayload,
|
|
||||||
CreateAccountParams,
|
|
||||||
} from './Accounts.types';
|
|
||||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
|
||||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreateAccountService {
|
|
||||||
constructor(
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
private readonly uow: UnitOfWork,
|
|
||||||
private readonly validator: CommandAccountValidators,
|
|
||||||
private readonly tenancyContext: TenancyContext,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorize the account creation.
|
|
||||||
* @param {CreateAccountDTO} accountDTO
|
|
||||||
*/
|
|
||||||
private authorize = async (
|
|
||||||
accountDTO: CreateAccountDTO,
|
|
||||||
baseCurrency: string,
|
|
||||||
params?: CreateAccountParams,
|
|
||||||
) => {
|
|
||||||
// Validate account name uniquiness.
|
|
||||||
if (!params.ignoreUniqueName) {
|
|
||||||
await this.validator.validateAccountNameUniquiness(accountDTO.name);
|
|
||||||
}
|
|
||||||
// Validate the account code uniquiness.
|
|
||||||
if (accountDTO.code) {
|
|
||||||
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
|
|
||||||
}
|
|
||||||
// Retrieve the account type meta or throw service error if not found.
|
|
||||||
this.validator.getAccountTypeOrThrowError(accountDTO.accountType);
|
|
||||||
|
|
||||||
// Ingore the parent account validation if not presented.
|
|
||||||
if (accountDTO.parentAccountId) {
|
|
||||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
|
||||||
accountDTO.parentAccountId,
|
|
||||||
);
|
|
||||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
|
||||||
|
|
||||||
// Inherit active status from parent account.
|
|
||||||
accountDTO.active = parentAccount.active;
|
|
||||||
|
|
||||||
// Validate should currency code be the same currency of parent account.
|
|
||||||
this.validator.validateCurrentSameParentAccount(
|
|
||||||
accountDTO,
|
|
||||||
parentAccount,
|
|
||||||
baseCurrency,
|
|
||||||
);
|
|
||||||
// Validates the max depth level of accounts chart.
|
|
||||||
await this.validator.validateMaxParentAccountDepthLevels(
|
|
||||||
accountDTO.parentAccountId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Validates the given account type supports the multi-currency.
|
|
||||||
this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformes the create account DTO to input model.
|
|
||||||
* @param {IAccountCreateDTO} createAccountDTO
|
|
||||||
*/
|
|
||||||
private transformDTOToModel = (
|
|
||||||
createAccountDTO: CreateAccountDTO,
|
|
||||||
baseCurrency: string,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
...createAccountDTO,
|
|
||||||
slug: kebabCase(createAccountDTO.name),
|
|
||||||
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
|
||||||
|
|
||||||
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
|
||||||
isSyncingOwner: Boolean(
|
|
||||||
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new account on the storage.
|
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
|
||||||
* @returns {Promise<IAccount>}
|
|
||||||
*/
|
|
||||||
public createAccount = async (
|
|
||||||
accountDTO: CreateAccountDTO,
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
params: CreateAccountParams = { ignoreUniqueName: false },
|
|
||||||
): Promise<Account> => {
|
|
||||||
// Retrieves the given tenant metadata.
|
|
||||||
const tenant = await this.tenancyContext.getTenant(true);
|
|
||||||
|
|
||||||
// Authorize the account creation.
|
|
||||||
await this.authorize(accountDTO, tenant.metadata.baseCurrency, params);
|
|
||||||
|
|
||||||
// Transformes the DTO to model.
|
|
||||||
const accountInputModel = this.transformDTOToModel(
|
|
||||||
accountDTO,
|
|
||||||
tenant.metadata.baseCurrency,
|
|
||||||
);
|
|
||||||
// Creates a new account with associated transactions under unit-of-work envirement.
|
|
||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onAccountCreating` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.accounts.onCreating, {
|
|
||||||
accountDTO,
|
|
||||||
trx,
|
|
||||||
} as IAccountEventCreatingPayload);
|
|
||||||
|
|
||||||
// Inserts account to the storage.
|
|
||||||
const account = await this.accountModel.query().insert({
|
|
||||||
...accountInputModel,
|
|
||||||
});
|
|
||||||
// Triggers `onAccountCreated` event.
|
|
||||||
// await this.eventEmitter.emitAsync(events.accounts.onCreated, {
|
|
||||||
// account,
|
|
||||||
// accountId: account.id,
|
|
||||||
// trx,
|
|
||||||
// } as IAccountEventCreatedPayload);
|
|
||||||
|
|
||||||
return account;
|
|
||||||
}, trx);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
// import { IAccountEventDeletedPayload } from '@/interfaces';
|
|
||||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
import { IAccountEventDeletedPayload } from './Accounts.types';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DeleteAccount {
|
|
||||||
constructor(
|
|
||||||
@Inject(Account.name) private accountModel: typeof Account,
|
|
||||||
private eventEmitter: EventEmitter2,
|
|
||||||
private uow: UnitOfWork,
|
|
||||||
private validator: CommandAccountValidators,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorize account delete.
|
|
||||||
* @param {number} accountId - Account id.
|
|
||||||
*/
|
|
||||||
private authorize = async (accountId: number, oldAccount: Account) => {
|
|
||||||
// Throw error if the account was predefined.
|
|
||||||
this.validator.throwErrorIfAccountPredefined(oldAccount);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlink the given parent account with children accounts.
|
|
||||||
* @param {number|number[]} parentAccountId -
|
|
||||||
*/
|
|
||||||
private async unassociateChildrenAccountsFromParent(
|
|
||||||
parentAccountId: number | number[],
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
) {
|
|
||||||
const accountsIds = Array.isArray(parentAccountId)
|
|
||||||
? parentAccountId
|
|
||||||
: [parentAccountId];
|
|
||||||
|
|
||||||
await this.accountModel
|
|
||||||
.query(trx)
|
|
||||||
.whereIn('parent_account_id', accountsIds)
|
|
||||||
.patch({ parent_account_id: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the account from the storage.
|
|
||||||
* @param {number} accountId
|
|
||||||
*/
|
|
||||||
public deleteAccount = async (accountId: number): Promise<void> => {
|
|
||||||
// Retrieve account or not found service error.
|
|
||||||
const oldAccount = await this.accountModel.query().findById(accountId);
|
|
||||||
|
|
||||||
// Authorize before delete account.
|
|
||||||
await this.authorize(accountId, oldAccount);
|
|
||||||
|
|
||||||
// Deletes the account and associated transactions under UOW environment.
|
|
||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onAccountDelete` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.accounts.onDelete, {
|
|
||||||
trx,
|
|
||||||
oldAccount,
|
|
||||||
} as IAccountEventDeletedPayload);
|
|
||||||
|
|
||||||
// Unlink the parent account from children accounts.
|
|
||||||
await this.unassociateChildrenAccountsFromParent(accountId, trx);
|
|
||||||
|
|
||||||
// Deletes account by the given id.
|
|
||||||
await this.accountModel.query(trx).deleteById(accountId);
|
|
||||||
|
|
||||||
// Triggers `onAccountDeleted` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.accounts.onDeleted, {
|
|
||||||
accountId,
|
|
||||||
oldAccount,
|
|
||||||
trx,
|
|
||||||
} as IAccountEventDeletedPayload);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import {
|
|
||||||
IsString,
|
|
||||||
IsOptional,
|
|
||||||
IsInt,
|
|
||||||
MinLength,
|
|
||||||
MaxLength,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class EditAccountDTO {
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(6)
|
|
||||||
code?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(3)
|
|
||||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
|
||||||
accountType: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
parentAccountId?: number;
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
import { EditAccountDTO } from './EditAccount.dto';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class EditAccount {
|
|
||||||
constructor(
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
private readonly uow: UnitOfWork,
|
|
||||||
private readonly validator: CommandAccountValidators,
|
|
||||||
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorize the account editing.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @param {IAccountEditDTO} accountDTO
|
|
||||||
* @param {IAccount} oldAccount -
|
|
||||||
*/
|
|
||||||
private authorize = async (
|
|
||||||
accountId: number,
|
|
||||||
accountDTO: EditAccountDTO,
|
|
||||||
oldAccount: Account,
|
|
||||||
) => {
|
|
||||||
// Validate account name uniquiness.
|
|
||||||
await this.validator.validateAccountNameUniquiness(
|
|
||||||
accountDTO.name,
|
|
||||||
accountId,
|
|
||||||
);
|
|
||||||
// Validate the account type should be not mutated.
|
|
||||||
await this.validator.isAccountTypeChangedOrThrowError(
|
|
||||||
oldAccount,
|
|
||||||
accountDTO,
|
|
||||||
);
|
|
||||||
// Validate the account code not exists on the storage.
|
|
||||||
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
|
|
||||||
await this.validator.isAccountCodeUniqueOrThrowError(
|
|
||||||
accountDTO.code,
|
|
||||||
oldAccount.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Retrieve the parent account of throw not found service error.
|
|
||||||
if (accountDTO.parentAccountId) {
|
|
||||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
|
||||||
accountDTO.parentAccountId,
|
|
||||||
oldAccount.id,
|
|
||||||
);
|
|
||||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edits details of the given account.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @param {IAccountDTO} accountDTO
|
|
||||||
*/
|
|
||||||
public async editAccount(
|
|
||||||
accountId: number,
|
|
||||||
accountDTO: EditAccountDTO,
|
|
||||||
): Promise<Account> {
|
|
||||||
// Retrieve the old account or throw not found service error.
|
|
||||||
const oldAccount = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.findById(accountId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Authorize the account editing.
|
|
||||||
await this.authorize(accountId, accountDTO, oldAccount);
|
|
||||||
|
|
||||||
// Edits account and associated transactions under unit-of-work environment.
|
|
||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onAccountEditing` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.accounts.onEditing, {
|
|
||||||
oldAccount,
|
|
||||||
accountDTO,
|
|
||||||
});
|
|
||||||
// Update the account on the storage.
|
|
||||||
const account = await this.accountModel
|
|
||||||
.query(trx)
|
|
||||||
.findById(accountId)
|
|
||||||
.updateAndFetch({ ...accountDTO });
|
|
||||||
|
|
||||||
// Triggers `onAccountEdited` event.
|
|
||||||
// await this.eventEmitter.emitAsync(events.accounts.onEdited, {
|
|
||||||
// account,
|
|
||||||
// oldAccount,
|
|
||||||
// trx,
|
|
||||||
// } as IAccountEventEditedPayload);
|
|
||||||
|
|
||||||
return account;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { AccountTransformer } from './Account.transformer';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { AccountRepository } from './repositories/Account.repository';
|
|
||||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { events } from '@/common/events/events';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetAccount {
|
|
||||||
constructor(
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
private readonly accountRepository: AccountRepository,
|
|
||||||
private readonly transformer: TransformerInjectable,
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the given account details.
|
|
||||||
* @param {number} accountId
|
|
||||||
*/
|
|
||||||
public getAccount = async (accountId: number) => {
|
|
||||||
// Find the given account or throw not found error.
|
|
||||||
const account = await this.accountModel
|
|
||||||
.query()
|
|
||||||
.findById(accountId)
|
|
||||||
.withGraphFetched('plaidItem')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
|
||||||
|
|
||||||
// Transforms the account model to POJO.
|
|
||||||
const transformed = await this.transformer.transform(
|
|
||||||
account,
|
|
||||||
new AccountTransformer(),
|
|
||||||
{ accountsGraph },
|
|
||||||
);
|
|
||||||
const eventPayload = { accountId };
|
|
||||||
|
|
||||||
// Triggers `onAccountViewed` event.
|
|
||||||
await this.eventEmitter.emitAsync(events.accounts.onViewed, eventPayload);
|
|
||||||
|
|
||||||
return transformed;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import {
|
|
||||||
IAccountsTransactionsFilter,
|
|
||||||
IGetAccountTransactionPOJO,
|
|
||||||
} from './Accounts.types';
|
|
||||||
import { AccountTransactionTransformer } from './AccountTransaction.transformer';
|
|
||||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetAccountTransactionsService {
|
|
||||||
constructor(
|
|
||||||
private readonly transformer: TransformerInjectable,
|
|
||||||
|
|
||||||
@Inject(AccountTransaction.name)
|
|
||||||
private readonly accountTransaction: typeof AccountTransaction,
|
|
||||||
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly account: typeof Account,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts transactions.
|
|
||||||
* @param {IAccountsTransactionsFilter} filter -
|
|
||||||
*/
|
|
||||||
public getAccountsTransactions = async (
|
|
||||||
filter: IAccountsTransactionsFilter,
|
|
||||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
|
||||||
// Retrieve the given account or throw not found error.
|
|
||||||
if (filter.accountId) {
|
|
||||||
await this.account.query().findById(filter.accountId).throwIfNotFound();
|
|
||||||
}
|
|
||||||
const transactions = await this.accountTransaction
|
|
||||||
.query()
|
|
||||||
.onBuild((query) => {
|
|
||||||
query.orderBy('date', 'DESC');
|
|
||||||
|
|
||||||
if (filter.accountId) {
|
|
||||||
query.where('account_id', filter.accountId);
|
|
||||||
}
|
|
||||||
query.withGraphFetched('account');
|
|
||||||
query.withGraphFetched('contact');
|
|
||||||
query.limit(filter.limit || 50);
|
|
||||||
});
|
|
||||||
// Transform the account transaction.
|
|
||||||
return this.transformer.transform(
|
|
||||||
transactions,
|
|
||||||
new AccountTransactionTransformer(),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// import { IAccountType } from './Accounts.types';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetAccountTypesService {
|
|
||||||
/**
|
|
||||||
* Retrieve all accounts types.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @return {IAccountType}
|
|
||||||
*/
|
|
||||||
public getAccountsTypes() {
|
|
||||||
const accountTypes = AccountTypesUtils.getList();
|
|
||||||
|
|
||||||
return accountTypes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { IAccountsFilter } from './Accounts.types';
|
|
||||||
import { DynamicListService } from '../DynamicListing/DynamicList.service';
|
|
||||||
import { AccountTransformer } from './Account.transformer';
|
|
||||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
|
||||||
import { Account } from './models/Account.model';
|
|
||||||
import { AccountRepository } from './repositories/Account.repository';
|
|
||||||
import { IFilterMeta } from '@/interfaces/Model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetAccountsService {
|
|
||||||
constructor(
|
|
||||||
private readonly dynamicListService: DynamicListService,
|
|
||||||
private readonly transformerService: TransformerInjectable,
|
|
||||||
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
private readonly accountRepository: AccountRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts datatable list.
|
|
||||||
* @param {IAccountsFilter} accountsFilter
|
|
||||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
|
||||||
*/
|
|
||||||
public async getAccountsList(
|
|
||||||
filterDTO: IAccountsFilter,
|
|
||||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> {
|
|
||||||
// Parses the stringified filter roles.
|
|
||||||
const filter = this.parseListFilterDTO(filterDTO);
|
|
||||||
|
|
||||||
// Dynamic list service.
|
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(
|
|
||||||
this.accountModel,
|
|
||||||
filter,
|
|
||||||
);
|
|
||||||
// Retrieve accounts model based on the given query.
|
|
||||||
const accounts = await this.accountModel.query().onBuild((builder) => {
|
|
||||||
dynamicList.buildQuery()(builder);
|
|
||||||
builder.modify('inactiveMode', filter.inactiveMode);
|
|
||||||
});
|
|
||||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
|
||||||
|
|
||||||
// Retrieves the transformed accounts collection.
|
|
||||||
const transformedAccounts = await this.transformerService.transform(
|
|
||||||
accounts,
|
|
||||||
new AccountTransformer(),
|
|
||||||
{ accountsGraph, structure: filterDTO.structure },
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
accounts: transformedAccounts,
|
|
||||||
filterMeta: dynamicList.getResponseMeta(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsees accounts list filter DTO.
|
|
||||||
* @param filterDTO
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private parseListFilterDTO(filterDTO) {
|
|
||||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// import { Inject, Service } from 'typedi';
|
|
||||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
// @Service()
|
|
||||||
// export class MutateBaseCurrencyAccounts {
|
|
||||||
// @Inject()
|
|
||||||
// tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Mutates the all accounts or the organziation.
|
|
||||||
// * @param {number} tenantId
|
|
||||||
// * @param {string} currencyCode
|
|
||||||
// */
|
|
||||||
// public mutateAllAccountsCurrency = async (
|
|
||||||
// tenantId: number,
|
|
||||||
// currencyCode: string
|
|
||||||
// ) => {
|
|
||||||
// const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// await Account.query().update({ currencyCode });
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
export const ERRORS = {
|
|
||||||
ACCOUNT_NOT_FOUND: 'account_not_found',
|
|
||||||
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
|
||||||
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
|
||||||
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
|
||||||
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
|
||||||
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
|
||||||
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
|
||||||
ACCOUNT_PREDEFINED: 'account_predefined',
|
|
||||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
|
||||||
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
|
||||||
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
|
||||||
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
|
|
||||||
'close_account_and_to_account_not_same_type',
|
|
||||||
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
|
||||||
ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY:
|
|
||||||
'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY',
|
|
||||||
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT:
|
|
||||||
'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT',
|
|
||||||
PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL:
|
|
||||||
'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default views columns.
|
|
||||||
export const DEFAULT_VIEW_COLUMNS = [
|
|
||||||
{ key: 'name', label: 'Account name' },
|
|
||||||
{ key: 'code', label: 'Account code' },
|
|
||||||
{ key: 'account_type_label', label: 'Account type' },
|
|
||||||
{ key: 'account_normal', label: 'Account normal' },
|
|
||||||
{ key: 'amount', label: 'Balance' },
|
|
||||||
{ key: 'currencyCode', label: 'Currency' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const MAX_ACCOUNTS_CHART_DEPTH = 5;
|
|
||||||
|
|
||||||
// Accounts default views.
|
|
||||||
export const DEFAULT_VIEWS = [
|
|
||||||
{
|
|
||||||
name: 'Assets',
|
|
||||||
slug: 'assets',
|
|
||||||
rolesLogicExpression: '1',
|
|
||||||
roles: [
|
|
||||||
{ index: 1, fieldKey: 'root_type', comparator: 'equals', value: 'asset' },
|
|
||||||
],
|
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Liabilities',
|
|
||||||
slug: 'liabilities',
|
|
||||||
rolesLogicExpression: '1',
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
fieldKey: 'root_type',
|
|
||||||
index: 1,
|
|
||||||
comparator: 'equals',
|
|
||||||
value: 'liability',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Equity',
|
|
||||||
slug: 'equity',
|
|
||||||
rolesLogicExpression: '1',
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
fieldKey: 'root_type',
|
|
||||||
index: 1,
|
|
||||||
comparator: 'equals',
|
|
||||||
value: 'equity',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Income',
|
|
||||||
slug: 'income',
|
|
||||||
rolesLogicExpression: '1',
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
fieldKey: 'root_type',
|
|
||||||
index: 1,
|
|
||||||
comparator: 'equals',
|
|
||||||
value: 'income',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Expenses',
|
|
||||||
slug: 'expenses',
|
|
||||||
rolesLogicExpression: '1',
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
fieldKey: 'root_type',
|
|
||||||
index: 1,
|
|
||||||
comparator: 'equals',
|
|
||||||
value: 'expense',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user