mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-24 08:39:49 +00:00
Compare commits
104 Commits
accounts-b
...
v0.16.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aefa7709c | ||
|
|
3020295841 | ||
|
|
7f31a48755 | ||
|
|
8c0ef61038 | ||
|
|
76bb82f2b4 | ||
|
|
4c0dc276dd | ||
|
|
a69c4b4067 | ||
|
|
d81e544e82 | ||
|
|
6eeda23559 | ||
|
|
cd046cbe27 | ||
|
|
9b7bc1e5b9 | ||
|
|
987341ed29 | ||
|
|
d7cad17f1b | ||
|
|
dd02ae471e | ||
|
|
a5bfb0b02b | ||
|
|
f4440c9a03 | ||
|
|
cb88c234d1 | ||
|
|
f6a0476fb4 | ||
|
|
63cdc45295 | ||
|
|
83a5010dc5 | ||
|
|
55aab76c9b | ||
|
|
495941f43a | ||
|
|
00a1e070c6 | ||
|
|
fab71d2b65 | ||
|
|
9504bb5ccd | ||
|
|
7e89966f20 | ||
|
|
8a96c41258 | ||
|
|
4a713980bf | ||
|
|
2a1cbf6ced | ||
|
|
9103b60653 | ||
|
|
b9fc0cdd9e | ||
|
|
4368c18479 | ||
|
|
7e5c6b6487 | ||
|
|
b7214044bb | ||
|
|
93cb3615c3 | ||
|
|
7abfa6a162 | ||
|
|
1372a1f0a8 | ||
|
|
484024ec28 | ||
|
|
46639c7b86 | ||
|
|
d10d1654c1 | ||
|
|
2f06070ecb | ||
|
|
deefdb9bfd | ||
|
|
3cc62d80de | ||
|
|
4962c5d4d3 | ||
|
|
571a332658 | ||
|
|
b44c318a5d | ||
|
|
bd9717f4dc | ||
|
|
f48aea8e5a | ||
|
|
0ac3a5dea9 | ||
|
|
56b40ad4cb | ||
|
|
9b6f934990 | ||
|
|
80e3522f8a | ||
|
|
7975643765 | ||
|
|
2ac7f86bdb | ||
|
|
956b9b6812 | ||
|
|
60248ec3f6 | ||
|
|
9d3f1541eb | ||
|
|
9b5f1a36ab | ||
|
|
8ee691e1ed | ||
|
|
f9cb14da9e | ||
|
|
5e87581f4e | ||
|
|
8ca9cf39da | ||
|
|
9001fea524 | ||
|
|
dea0d71732 | ||
|
|
c191c4bd26 | ||
|
|
47d82ce591 | ||
|
|
9321db2a3a | ||
|
|
e486333c96 | ||
|
|
a9748b23c0 | ||
|
|
693ae61141 | ||
|
|
9807ac04b0 | ||
|
|
bddfde4138 | ||
|
|
a39dcd00d5 | ||
|
|
4d616e9287 | ||
|
|
dc52fb1de5 | ||
|
|
21a1777424 | ||
|
|
16b721db91 | ||
|
|
079491823d | ||
|
|
f7a87a6e9c | ||
|
|
e0cdf42980 | ||
|
|
ee56653f4b | ||
|
|
2310b09778 | ||
|
|
0684e50ebd | ||
|
|
aaa8f39e50 | ||
|
|
af981ce630 | ||
|
|
a1f8417b5d | ||
|
|
086b060351 | ||
|
|
bbafdcd8bd | ||
|
|
dd9098bdc1 | ||
|
|
3851d34ba4 | ||
|
|
b9651f30d5 | ||
|
|
45b5fb4088 | ||
|
|
aa64bcf69b | ||
|
|
cbd867b334 | ||
|
|
1a8ca83786 | ||
|
|
80c14ba1a0 | ||
|
|
785045dbad | ||
|
|
291301c1e3 | ||
|
|
824e4e13d1 | ||
|
|
74da28b464 | ||
|
|
22a016b56e | ||
|
|
040f016273 | ||
|
|
8ab809fc71 | ||
|
|
2baa667c5d |
@@ -105,6 +105,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ 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
|
||||||
|
|
||||||
@@ -95,3 +98,8 @@ PLAID_LINK_WEBHOOK=
|
|||||||
|
|
||||||
PLAID_SANDBOX_REDIRECT_URI=
|
PLAID_SANDBOX_REDIRECT_URI=
|
||||||
PLAID_DEVELOPMENT_REDIRECT_URI=
|
PLAID_DEVELOPMENT_REDIRECT_URI=
|
||||||
|
|
||||||
|
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
||||||
|
LEMONSQUEEZY_API_KEY=
|
||||||
|
LEMONSQUEEZY_STORE_ID=
|
||||||
|
LEMONSQUEEZY_WEBHOOK_SECRET=
|
||||||
|
|||||||
94
.github/workflows/build-deploy-container.yml
vendored
94
.github/workflows/build-deploy-container.yml
vendored
@@ -6,43 +6,66 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
|
||||||
WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp
|
SERVER_IMAGE_NAME: bigcapitalhq/server
|
||||||
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@v2
|
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@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
username: ${{ github.actor }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
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.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }}
|
images: ${{ 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@v2
|
uses: docker/build-push-action@v5
|
||||||
|
id: build
|
||||||
with:
|
with:
|
||||||
context: .
|
context: ./
|
||||||
file: ./packages/webapp/Dockerfile
|
file: ./packages/webapp/Dockerfile
|
||||||
push: true
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: ghcr.io/bigcapitalhq/webapp:latest
|
push: true
|
||||||
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
|
||||||
@@ -53,29 +76,52 @@ jobs:
|
|||||||
name: Build and deploy server container
|
name: Build and deploy server container
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Prepare
|
||||||
uses: actions/checkout@v2
|
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.
|
# Login to Container registry.
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
username: ${{ github.actor }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
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@v2
|
uses: docker/build-push-action@v5
|
||||||
|
id: build
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./packages/server/Dockerfile
|
file: ./packages/server/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ghcr.io/bigcapitalhq/server:latest
|
tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}}
|
||||||
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 }}
|
||||||
|
|||||||
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'
|
||||||
- 'yarn.lock'
|
- 'pnpm-lock.yaml'
|
||||||
- '.github/workflows/e2e.yml'
|
- '.github/workflows/e2e.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '**.ts'
|
- '**.ts'
|
||||||
- '**.tsx'
|
- '**.tsx'
|
||||||
- '**/tsconfig.json'
|
- '**/tsconfig.json'
|
||||||
- 'yarn.lock'
|
- 'pnpm-lock.yaml'
|
||||||
- '.github/workflows/e2e.yml'
|
- '.github/workflows/e2e.yml'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
yarn commitlint --edit
|
pnpx commitlint --edit
|
||||||
|
|||||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -2,6 +2,76 @@
|
|||||||
|
|
||||||
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.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
|
## [0.14.0] - 30-01-2024
|
||||||
|
|
||||||
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
|
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://bigcapital.ly" target="_blank">
|
<a href="https://bigcapital.app" 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>
|
||||||
@@ -25,6 +25,10 @@
|
|||||||
<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?
|
||||||
@@ -47,7 +51,7 @@ Bigcapital is available open-source under AGPL license. You can host it on your
|
|||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.ly/deployment/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
|
## Development
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@ You can integrate Bigcapital API with your system to organize your transactions
|
|||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
|
- [Documentation](https://docs.bigcapital.app/) - 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,6 +122,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<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://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://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://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>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -21,20 +21,16 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
- webapp
|
- webapp
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
|
|
||||||
webapp:
|
webapp:
|
||||||
container_name: bigcapital-webapp
|
container_name: bigcapital-webapp
|
||||||
image: ghcr.io/bigcapitalhq/webapp:latest
|
image: bigcapitalhq/webapp:latest
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
container_name: bigcapital-server
|
container_name: bigcapital-server
|
||||||
image: ghcr.io/bigcapitalhq/server:latest
|
image: bigcapitalhq/server:latest
|
||||||
expose:
|
expose:
|
||||||
- '3000'
|
- '3000'
|
||||||
links:
|
links:
|
||||||
@@ -45,9 +41,7 @@ services:
|
|||||||
- mysql
|
- mysql
|
||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
environment:
|
environment:
|
||||||
# Mail
|
# Mail
|
||||||
- MAIL_HOST=${MAIL_HOST}
|
- MAIL_HOST=${MAIL_HOST}
|
||||||
@@ -92,6 +86,30 @@ services:
|
|||||||
- GOTENBERG_URL=${GOTENBERG_URL}
|
- GOTENBERG_URL=${GOTENBERG_URL}
|
||||||
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
|
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
|
||||||
|
|
||||||
|
# Bank Sync
|
||||||
|
- BANKING_CONNECT=${BANKING_CONNECT}
|
||||||
|
|
||||||
|
# Plaid
|
||||||
|
- PLAID_ENV=${PLAID_ENV}
|
||||||
|
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
|
||||||
|
- PLAID_SECRET_DEVELOPMENT=${PLAID_SECRET_DEVELOPMENT}
|
||||||
|
- PLAID_SECRET_SANDBOX=${b8cf42b441e110451e2f69ad7e1e9f}
|
||||||
|
- 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}
|
||||||
|
|
||||||
|
|
||||||
database_migration:
|
database_migration:
|
||||||
container_name: bigcapital-database-migration
|
container_name: bigcapital-database-migration
|
||||||
build:
|
build:
|
||||||
@@ -111,9 +129,7 @@ services:
|
|||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
container_name: bigcapital-mysql
|
container_name: bigcapital-mysql
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
build:
|
build:
|
||||||
context: ./docker/mariadb
|
context: ./docker/mariadb
|
||||||
environment:
|
environment:
|
||||||
@@ -128,9 +144,7 @@ services:
|
|||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
container_name: bigcapital-mongo
|
container_name: bigcapital-mongo
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
build: ./docker/mongo
|
build: ./docker/mongo
|
||||||
expose:
|
expose:
|
||||||
- '27017'
|
- '27017'
|
||||||
@@ -139,9 +153,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: bigcapital-redis
|
container_name: bigcapital-redis
|
||||||
deploy:
|
restart: on-failure
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
build:
|
build:
|
||||||
context: ./docker/redis
|
context: ./docker/redis
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
@@ -37,4 +37,5 @@ RUN git clone https://github.com/vishnubob/wait-for-it.git
|
|||||||
ADD docker/migration/start.sh /
|
ADD docker/migration/start.sh /
|
||||||
RUN chmod +x /start.sh
|
RUN chmod +x /start.sh
|
||||||
|
|
||||||
CMD ["/start.sh"]
|
# Once we listen the mysql port run the migration task.
|
||||||
|
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest"
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "independent",
|
"version": "independent",
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"useWorkspaces": true,
|
"packages": [
|
||||||
"packages": ["packages/*"]
|
"packages/*"
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
"@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": "^6.4.1"
|
"lerna": "^8.1.2",
|
||||||
|
"pnpm": "^9.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x || 17.x || 18.x"
|
"node": "16.x || 17.x || 18.x"
|
||||||
|
|||||||
3
packages/server/.gitignore
vendored
3
packages/server/.gitignore
vendored
@@ -3,3 +3,6 @@
|
|||||||
stdout.log
|
stdout.log
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
|
/public/imports
|
||||||
|
|
||||||
|
dist
|
||||||
@@ -78,6 +78,9 @@ ENV MAIL_HOST=$MAIL_HOST \
|
|||||||
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||||
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||||
|
|
||||||
|
# New Relic config file.
|
||||||
|
ENV NEW_RELIC_NO_CONFIG_FILE=true
|
||||||
|
|
||||||
# Create app directory.
|
# Create app directory.
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -89,8 +92,8 @@ RUN npm install -g pnpm
|
|||||||
# Copy application dependency manifests to the container image.
|
# Copy application dependency manifests to the container image.
|
||||||
COPY ./package*.json ./
|
COPY ./package*.json ./
|
||||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
||||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
||||||
COPY ./lerna.json ./lerna.json
|
COPY ./lerna.json ./lerna.json
|
||||||
|
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||||
COPY ./packages/server/package*.json ./packages/server/
|
COPY ./packages/server/package*.json ./packages/server/
|
||||||
|
|
||||||
# Install application dependencies
|
# Install application dependencies
|
||||||
|
|||||||
17747
packages/server/package-lock.json
generated
17747
packages/server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
"@types/i18n": "^0.8.7",
|
"@types/i18n": "^0.8.7",
|
||||||
"@types/knex": "^0.16.1",
|
"@types/knex": "^0.16.1",
|
||||||
"@types/mathjs": "^6.0.12",
|
"@types/mathjs": "^6.0.12",
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
"mustache": "^3.0.3",
|
"mustache": "^3.0.3",
|
||||||
"mysql": "^2.17.1",
|
"mysql": "^2.17.1",
|
||||||
"mysql2": "^1.6.5",
|
"mysql2": "^1.6.5",
|
||||||
|
"newrelic": "^11.15.0",
|
||||||
"node-cache": "^4.2.1",
|
"node-cache": "^4.2.1",
|
||||||
"nodemailer": "^6.3.0",
|
"nodemailer": "^6.3.0",
|
||||||
"nodemon": "^1.19.1",
|
"nodemon": "^1.19.1",
|
||||||
@@ -89,17 +91,17 @@
|
|||||||
"objection-filter": "^4.0.1",
|
"objection-filter": "^4.0.1",
|
||||||
"objection-soft-delete": "^1.0.7",
|
"objection-soft-delete": "^1.0.7",
|
||||||
"objection-unique": "^1.2.2",
|
"objection-unique": "^1.2.2",
|
||||||
|
"plaid": "^10.3.0",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"puppeteer": "^10.2.0",
|
"puppeteer": "^10.2.0",
|
||||||
"plaid": "^10.3.0",
|
|
||||||
"qim": "0.0.52",
|
"qim": "0.0.52",
|
||||||
"ramda": "^0.27.1",
|
"ramda": "^0.27.1",
|
||||||
"rate-limiter-flexible": "^2.1.14",
|
"rate-limiter-flexible": "^2.1.14",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rtl-detect": "^1.0.4",
|
"rtl-detect": "^1.0.4",
|
||||||
"source-map-loader": "^4.0.1",
|
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
|
"source-map-loader": "^4.0.1",
|
||||||
"tmp-promise": "^3.0.3",
|
"tmp-promise": "^3.0.3",
|
||||||
"ts-transformer-keys": "^0.4.2",
|
"ts-transformer-keys": "^0.4.2",
|
||||||
"tsyringe": "^4.3.0",
|
"tsyringe": "^4.3.0",
|
||||||
|
|||||||
BIN
packages/server/public/.DS_Store
vendored
Normal file
BIN
packages/server/public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
packages/server/public/imports/.DS_Store
vendored
Normal file
BIN
packages/server/public/imports/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -244,26 +244,29 @@
|
|||||||
"account.field.active": "Active",
|
"account.field.active": "Active",
|
||||||
"account.field.currency": "Currency",
|
"account.field.currency": "Currency",
|
||||||
"account.field.balance": "Balance",
|
"account.field.balance": "Balance",
|
||||||
|
"account.field.bank_balance": "Bank Balance",
|
||||||
|
"account.field.parent_account": "Parent Account",
|
||||||
"account.field.created_at": "Created at",
|
"account.field.created_at": "Created at",
|
||||||
"item.field.type": "Item type",
|
"item.field.type": "Item Type",
|
||||||
"item.field.type.inventory": "Inventory",
|
"item.field.type.inventory": "Inventory",
|
||||||
"item.field.type.service": "Service",
|
"item.field.type.service": "Service",
|
||||||
"item.field.type.non-inventory": "Non inventory",
|
"item.field.type.non-inventory": "Non Inventory",
|
||||||
"item.field.name": "Name",
|
"item.field.name": "Item Name",
|
||||||
"item.field.code": "Code",
|
"item.field.code": "Item Code",
|
||||||
"item.field.sellable": "Sellable",
|
"item.field.sellable": "Sellable",
|
||||||
"item.field.purchasable": "Purchasable",
|
"item.field.purchasable": "Purchasable",
|
||||||
"item.field.cost_price": "Cost price",
|
"item.field.cost_price": "Cost Price",
|
||||||
"item.field.cost_account": "Cost account",
|
"item.field.sell_price": "Sell Price",
|
||||||
"item.field.sell_account": "Sell account",
|
"item.field.cost_account": "Cost Account",
|
||||||
"item.field.sell_description": "Sell description",
|
"item.field.sell_account": "Sell Account",
|
||||||
"item.field.inventory_account": "Inventory account",
|
"item.field.sell_description": "Sell Description",
|
||||||
"item.field.purchase_description": "Purchase description",
|
"item.field.inventory_account": "Inventory Account",
|
||||||
"item.field.quantity_on_hand": "Quantity on hand",
|
"item.field.purchase_description": "Purchase Description",
|
||||||
|
"item.field.quantity_on_hand": "Quantity on Hand",
|
||||||
"item.field.note": "Note",
|
"item.field.note": "Note",
|
||||||
"item.field.category": "Category",
|
"item.field.category": "Category",
|
||||||
"item.field.active": "Active",
|
"item.field.active": "Active",
|
||||||
"item.field.created_at": "Created at",
|
"item.field.created_at": "Created At",
|
||||||
"item_category.field.name": "Name",
|
"item_category.field.name": "Name",
|
||||||
"item_category.field.description": "Description",
|
"item_category.field.description": "Description",
|
||||||
"item_category.field.count": "Count",
|
"item_category.field.count": "Count",
|
||||||
@@ -276,8 +279,14 @@
|
|||||||
"invoice.field.invoice_message": "Invoice message",
|
"invoice.field.invoice_message": "Invoice message",
|
||||||
"invoice.field.terms_conditions": "Terms & conditions",
|
"invoice.field.terms_conditions": "Terms & conditions",
|
||||||
"invoice.field.amount": "Amount",
|
"invoice.field.amount": "Amount",
|
||||||
|
"invoice.field.exchange_rate": "Exchange Rate",
|
||||||
"invoice.field.payment_amount": "Payment amount",
|
"invoice.field.payment_amount": "Payment amount",
|
||||||
"invoice.field.due_amount": "Due amount",
|
"invoice.field.due_amount": "Due amount",
|
||||||
|
"invoice.field.delivered": "Delivered",
|
||||||
|
"invoice.field.item_name": "Item Name",
|
||||||
|
"invoice.field.rate": "Rate",
|
||||||
|
"invoice.field.quantity": "Quantity",
|
||||||
|
"invoice.field.description": "Description",
|
||||||
"invoice.field.status": "Status",
|
"invoice.field.status": "Status",
|
||||||
"invoice.field.status.paid": "Paid",
|
"invoice.field.status.paid": "Paid",
|
||||||
"invoice.field.status.partially-paid": "Partially paid",
|
"invoice.field.status.partially-paid": "Partially paid",
|
||||||
@@ -286,6 +295,8 @@
|
|||||||
"invoice.field.status.delivered": "Delivered",
|
"invoice.field.status.delivered": "Delivered",
|
||||||
"invoice.field.status.draft": "Draft",
|
"invoice.field.status.draft": "Draft",
|
||||||
"invoice.field.created_at": "Created at",
|
"invoice.field.created_at": "Created at",
|
||||||
|
"invoice.field.currency": "Currency",
|
||||||
|
"invoice.field.entries": "Entries",
|
||||||
"estimate.field.amount": "Amount",
|
"estimate.field.amount": "Amount",
|
||||||
"estimate.field.estimate_number": "Estimate number",
|
"estimate.field.estimate_number": "Estimate number",
|
||||||
"estimate.field.customer": "Customer",
|
"estimate.field.customer": "Customer",
|
||||||
@@ -300,22 +311,31 @@
|
|||||||
"estimate.field.status.approved": "Approved",
|
"estimate.field.status.approved": "Approved",
|
||||||
"estimate.field.status.draft": "Draft",
|
"estimate.field.status.draft": "Draft",
|
||||||
"estimate.field.created_at": "Created at",
|
"estimate.field.created_at": "Created at",
|
||||||
"payment_receive.field.customer": "Customer",
|
|
||||||
"payment_receive.field.payment_date": "Payment date",
|
|
||||||
"payment_receive.field.amount": "Amount",
|
"payment_receive.field.amount": "Amount",
|
||||||
"payment_receive.field.reference_no": "Reference No.",
|
|
||||||
"payment_receive.field.deposit_account": "Deposit account",
|
|
||||||
"payment_receive.field.payment_receive_no": "Payment receive No.",
|
"payment_receive.field.payment_receive_no": "Payment receive No.",
|
||||||
"payment_receive.field.statement": "Statement",
|
"payment_receive.field.statement": "Statement",
|
||||||
"payment_receive.field.created_at": "Created at",
|
"payment_receive.field.created_at": "Created at",
|
||||||
|
"payment_receive.field.customer": "Customer",
|
||||||
|
"payment_receive.field.exchange_rate": "Exchange Rate",
|
||||||
|
"payment_receive.field.payment_date": "Payment Date",
|
||||||
|
"payment_receive.field.reference_no": "Reference No.",
|
||||||
|
"payment_receive.field.deposit_account": "Deposit Account",
|
||||||
|
"payment_receive.field.entries": "Entries",
|
||||||
|
"payment_receive.field.invoice": "Invoice",
|
||||||
|
"payment_receive.field.entries.payment_amount": "Payment Amount",
|
||||||
"bill_payment.field.vendor": "Vendor",
|
"bill_payment.field.vendor": "Vendor",
|
||||||
"bill_payment.field.amount": "Amount",
|
"bill_payment.field.amount": "Amount",
|
||||||
"bill_payment.field.due_amount": "Due amount",
|
"bill_payment.field.due_amount": "Due Amount",
|
||||||
"bill_payment.field.payment_account": "Payment account",
|
"bill_payment.field.payment_account": "Payment Account",
|
||||||
"bill_payment.field.payment_number": "Payment number",
|
"bill_payment.field.payment_number": "Payment No.",
|
||||||
"bill_payment.field.payment_date": "Payment date",
|
"bill_payment.field.payment_date": "Payment Date",
|
||||||
"bill_payment.field.reference_no": "Reference No.",
|
"bill_payment.field.reference_no": "Reference No.",
|
||||||
"bill_payment.field.description": "Description",
|
"bill_payment.field.description": "Description",
|
||||||
|
"bill_payment.field.exchange_rate": "Exchange Rate",
|
||||||
|
"bill_payment.field.note": "Note",
|
||||||
|
"bill_payment.field.entries.bill": "Bill No.",
|
||||||
|
"bill_payment.field.entries.payment_amount": "Payment Amount",
|
||||||
|
"bill_payment.field.reference": "Reference No.",
|
||||||
"bill_payment.field.created_at": "Created at",
|
"bill_payment.field.created_at": "Created at",
|
||||||
"bill.field.vendor": "Vendor",
|
"bill.field.vendor": "Vendor",
|
||||||
"bill.field.bill_number": "Bill number",
|
"bill.field.bill_number": "Bill number",
|
||||||
@@ -343,22 +363,30 @@
|
|||||||
"inventory_adjustment.field.description": "Description",
|
"inventory_adjustment.field.description": "Description",
|
||||||
"inventory_adjustment.field.published_at": "Published at",
|
"inventory_adjustment.field.published_at": "Published at",
|
||||||
"inventory_adjustment.field.created_at": "Created at",
|
"inventory_adjustment.field.created_at": "Created at",
|
||||||
"expense.field.payment_date": "Payment date",
|
"expense.field.payment_date": "Payment Date",
|
||||||
"expense.field.payment_account": "Payment account",
|
"expense.field.payment_account": "Payment Account",
|
||||||
"expense.field.amount": "Amount",
|
"expense.field.amount": "Amount",
|
||||||
|
"expense.field.currency_code": "Currency",
|
||||||
|
"expense.field.exchange_rate": "Exchange Rate",
|
||||||
"expense.field.reference_no": "Reference No.",
|
"expense.field.reference_no": "Reference No.",
|
||||||
"expense.field.description": "Description",
|
"expense.field.description": "Description",
|
||||||
|
"expense.field.line_description": "Line Description",
|
||||||
"expense.field.published": "Published",
|
"expense.field.published": "Published",
|
||||||
|
"expense.field.categories": "Categories",
|
||||||
|
"expense.field.expense_account": "Expense Account",
|
||||||
|
"expense.field.publish": "Publish",
|
||||||
"expense.field.status": "Status",
|
"expense.field.status": "Status",
|
||||||
"expense.field.status.draft": "Draft",
|
"expense.field.status.draft": "Draft",
|
||||||
"expense.field.status.published": "Published",
|
"expense.field.status.published": "Published",
|
||||||
"expense.field.created_at": "Created at",
|
"expense.field.created_at": "Created at",
|
||||||
"manual_journal.field.date": "Date",
|
"manual_journal.field.date": "Date",
|
||||||
"manual_journal.field.journal_number": "Journal number",
|
"manual_journal.field.journal_number": "Journal No.",
|
||||||
"manual_journal.field.reference": "Reference No.",
|
"manual_journal.field.reference": "Reference No.",
|
||||||
"manual_journal.field.journal_type": "Journal type",
|
"manual_journal.field.journal_type": "Journal Type",
|
||||||
"manual_journal.field.amount": "Amount",
|
"manual_journal.field.amount": "Amount",
|
||||||
"manual_journal.field.description": "Description",
|
"manual_journal.field.description": "Description",
|
||||||
|
"manual_journal.field.currency": "Currency",
|
||||||
|
"manual_journal.field.exchange_rate": "Exchange Rate",
|
||||||
"manual_journal.field.status": "Status",
|
"manual_journal.field.status": "Status",
|
||||||
"manual_journal.field.created_at": "Created at",
|
"manual_journal.field.created_at": "Created at",
|
||||||
"receipt.field.amount": "Amount",
|
"receipt.field.amount": "Amount",
|
||||||
@@ -404,6 +432,7 @@
|
|||||||
"vendor.field.created_at": "Created at",
|
"vendor.field.created_at": "Created at",
|
||||||
"vendor.field.balance": "Balance",
|
"vendor.field.balance": "Balance",
|
||||||
"vendor.field.status": "Status",
|
"vendor.field.status": "Status",
|
||||||
|
"vendor.field.note": "Note",
|
||||||
"vendor.field.currency": "Currency",
|
"vendor.field.currency": "Currency",
|
||||||
"vendor.field.status.active": "Active",
|
"vendor.field.status.active": "Active",
|
||||||
"vendor.field.status.inactive": "Inactive",
|
"vendor.field.status.inactive": "Inactive",
|
||||||
@@ -411,6 +440,8 @@
|
|||||||
"vendor.field.status.unpaid": "Unpaid",
|
"vendor.field.status.unpaid": "Unpaid",
|
||||||
"Invoice write-off": "Invoice write-off",
|
"Invoice write-off": "Invoice write-off",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"transaction_type.credit_note": "Credit note",
|
"transaction_type.credit_note": "Credit note",
|
||||||
"transaction_type.refund_credit_note": "Refund credit note",
|
"transaction_type.refund_credit_note": "Refund credit note",
|
||||||
"transaction_type.vendor_credit": "Vendor credit",
|
"transaction_type.vendor_credit": "Vendor credit",
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export default class AccountsController extends BaseController {
|
|||||||
// Filter query.
|
// Filter query.
|
||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
structure: IAccountsStructureType.Tree,
|
structure: IAccountsStructureType.Tree,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { DATATYPES_LENGTH } from '@/data/DataTypes';
|
|||||||
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
|
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
|
||||||
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
|
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
|
||||||
|
|
||||||
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
@Service()
|
@Service()
|
||||||
export default class AuthenticationController extends BaseController {
|
export default class AuthenticationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -28,6 +30,20 @@ export default class AuthenticationController extends BaseController {
|
|||||||
asyncMiddleware(this.login.bind(this)),
|
asyncMiddleware(this.login.bind(this)),
|
||||||
this.handlerErrors
|
this.handlerErrors
|
||||||
);
|
);
|
||||||
|
router.use('/register/verify/resend', JWTAuth);
|
||||||
|
router.use('/register/verify/resend', AttachCurrentTenantUser);
|
||||||
|
router.post(
|
||||||
|
'/register/verify/resend',
|
||||||
|
asyncMiddleware(this.registerVerifyResendMail.bind(this)),
|
||||||
|
this.handlerErrors
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/register/verify',
|
||||||
|
this.signupVerifySchema,
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.registerVerify.bind(this)),
|
||||||
|
this.handlerErrors
|
||||||
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/register',
|
'/register',
|
||||||
this.registerSchema,
|
this.registerSchema,
|
||||||
@@ -99,6 +115,17 @@ export default class AuthenticationController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get signupVerifySchema(): ValidationChain[] {
|
||||||
|
return [
|
||||||
|
check('email')
|
||||||
|
.exists()
|
||||||
|
.isString()
|
||||||
|
.isEmail()
|
||||||
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
|
check('token').exists().isString(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset password schema.
|
* Reset password schema.
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
@@ -166,6 +193,58 @@ export default class AuthenticationController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the provider user's email after signin-up.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response}| res
|
||||||
|
* @param {Function} next
|
||||||
|
* @returns {Response|void}
|
||||||
|
*/
|
||||||
|
private async registerVerify(req: Request, res: Response, next: Function) {
|
||||||
|
const signUpVerifyDTO: { email: string; token: string } =
|
||||||
|
this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await this.authApplication.signUpConfirm(
|
||||||
|
signUpVerifyDTO.email,
|
||||||
|
signUpVerifyDTO.token
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
type: 'success',
|
||||||
|
message: 'The given user has verified successfully',
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the confirmation email to the user.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response}| res
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
private async registerVerifyResendMail(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: Function
|
||||||
|
) {
|
||||||
|
const { user } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.authApplication.signUpConfirmResend(user.id);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
type: 'success',
|
||||||
|
message: 'The given user has verified successfully',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send reset password handler
|
* Send reset password handler
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ export default class CustomersController extends ContactsController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -144,10 +144,8 @@ export default class VendorsController extends ContactsController {
|
|||||||
try {
|
try {
|
||||||
const vendor = await this.vendorsApplication.createVendor(
|
const vendor = await this.vendorsApplication.createVendor(
|
||||||
tenantId,
|
tenantId,
|
||||||
contactDTO,
|
contactDTO
|
||||||
user
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: vendor.id,
|
id: vendor.id,
|
||||||
message: 'The vendor has been created successfully.',
|
message: 'The vendor has been created successfully.',
|
||||||
@@ -272,7 +270,7 @@ export default class VendorsController extends ContactsController {
|
|||||||
const vendorsFilter: IVendorsFilter = {
|
const vendorsFilter: IVendorsFilter = {
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ export default class DashboardMetaController {
|
|||||||
dashboardService: DashboardService;
|
dashboardService: DashboardService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Constructor router.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/boot', this.getDashboardBoot);
|
router.get('/boot', this.getDashboardBoot);
|
||||||
@@ -25,7 +25,7 @@ export default class DashboardMetaController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @param {NextFunction} next -
|
* @param {NextFunction} next -
|
||||||
*/
|
*/
|
||||||
getDashboardBoot = async (
|
private getDashboardBoot = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { query } from 'express-validator';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ExportApplication } from '@/services/Export/ExportApplication';
|
||||||
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExportController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private exportResourceApp: ExportApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor method.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
[
|
||||||
|
query('resource').exists(),
|
||||||
|
query('format').isIn(['csv', 'xlsx']).optional(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
this.export.bind(this),
|
||||||
|
this.catchServiceErrors
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports xlsx/csv to the given resource type.
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
* @param {NextFunction} next -
|
||||||
|
*/
|
||||||
|
private async export(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const query = this.matchedQueryData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const accept = this.accepts(req);
|
||||||
|
|
||||||
|
const acceptType = accept.types([
|
||||||
|
ACCEPT_TYPE.APPLICATION_XLSX,
|
||||||
|
ACCEPT_TYPE.APPLICATION_CSV,
|
||||||
|
ACCEPT_TYPE.APPLICATION_PDF,
|
||||||
|
]);
|
||||||
|
const data = await this.exportResourceApp.export(
|
||||||
|
tenantId,
|
||||||
|
query.resource,
|
||||||
|
acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv'
|
||||||
|
);
|
||||||
|
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
|
||||||
|
return res.send(data);
|
||||||
|
// Retrieves the xlsx format.
|
||||||
|
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Disposition',
|
||||||
|
'attachment; filename=output.xlsx'
|
||||||
|
);
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Type',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
);
|
||||||
|
return res.send(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms service errors to response.
|
||||||
|
* @param {Error}
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {ServiceError} error
|
||||||
|
*/
|
||||||
|
private catchServiceErrors(
|
||||||
|
error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
if (error instanceof ServiceError) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: error.errorType }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ export class ImportController extends BaseController {
|
|||||||
[
|
[
|
||||||
param('import_id').exists().isString(),
|
param('import_id').exists().isString(),
|
||||||
body('mapping').exists().isArray({ min: 1 }),
|
body('mapping').exists().isArray({ min: 1 }),
|
||||||
|
body('mapping.*.group').optional(),
|
||||||
body('mapping.*.from').exists(),
|
body('mapping.*.from').exists(),
|
||||||
body('mapping.*.to').exists(),
|
body('mapping.*.to').exists(),
|
||||||
],
|
],
|
||||||
@@ -47,6 +48,7 @@ export class ImportController extends BaseController {
|
|||||||
router.get(
|
router.get(
|
||||||
'/sample',
|
'/sample',
|
||||||
[query('resource').exists(), query('format').optional()],
|
[query('resource').exists(), query('format').optional()],
|
||||||
|
this.validationResult,
|
||||||
this.downloadImportSample.bind(this),
|
this.downloadImportSample.bind(this),
|
||||||
this.catchServiceErrors
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
import Multer from 'multer';
|
import Multer from 'multer';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { getImportsStoragePath } from '@/services/Import/_utils';
|
||||||
|
|
||||||
export function allowSheetExtensions(req, file, cb) {
|
export function allowSheetExtensions(req, file, cb) {
|
||||||
if (
|
if (
|
||||||
file.mimetype !== 'text/csv' &&
|
file.mimetype !== 'text/csv' &&
|
||||||
file.mimetype !== 'application/vnd.ms-excel' &&
|
file.mimetype !== 'application/vnd.ms-excel' &&
|
||||||
file.mimetype !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
file.mimetype !==
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
) {
|
) {
|
||||||
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
|
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cb(null, true);
|
cb(null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storage = Multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
const path = getImportsStoragePath();
|
||||||
|
cb(null, path);
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
// Add the creation timestamp to clean up temp files later.
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
cb(null, uniqueSuffix);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const uploadImportFile = Multer({
|
export const uploadImportFile = Multer({
|
||||||
dest: './public/imports',
|
storage,
|
||||||
limits: { fileSize: 5 * 1024 * 1024 },
|
limits: { fileSize: 5 * 1024 * 1024 },
|
||||||
fileFilter: allowSheetExtensions,
|
fileFilter: allowSheetExtensions,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ItemTransactionsController from './ItemsTransactions';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ItemsBaseController {
|
export default class ItemsBaseController {
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/', Container.get(ItemsController).router());
|
router.use('/', Container.get(ItemsController).router());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { check, ValidationChain } from 'express-validator';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import JWTAuth from '@/api/middleware/jwtAuth';
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
|
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
|
||||||
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
import OrganizationService from '@/services/Organization/OrganizationService';
|
import OrganizationService from '@/services/Organization/OrganizationService';
|
||||||
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
|
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
|
||||||
@@ -17,7 +18,7 @@ import BaseController from '@/api/controllers/BaseController';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class OrganizationController extends BaseController {
|
export default class OrganizationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private organizationService: OrganizationService;
|
organizationService: OrganizationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -25,10 +26,13 @@ export default class OrganizationController extends BaseController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
// Should before build tenant database the user be authorized and
|
||||||
|
// most important than that, should be subscribed to any plan.
|
||||||
router.use(JWTAuth);
|
router.use(JWTAuth);
|
||||||
router.use(AttachCurrentTenantUser);
|
router.use(AttachCurrentTenantUser);
|
||||||
router.use(TenancyMiddleware);
|
router.use(TenancyMiddleware);
|
||||||
|
|
||||||
|
router.use('/build', SubscriptionMiddleware('main'));
|
||||||
router.post(
|
router.post(
|
||||||
'/build',
|
'/build',
|
||||||
this.buildOrganizationValidationSchema,
|
this.buildOrganizationValidationSchema,
|
||||||
|
|||||||
@@ -297,8 +297,7 @@ export default class VendorCreditController extends BaseController {
|
|||||||
try {
|
try {
|
||||||
const vendorCredit = await this.createVendorCreditService.newVendorCredit(
|
const vendorCredit = await this.createVendorCreditService.newVendorCredit(
|
||||||
tenantId,
|
tenantId,
|
||||||
vendorCreditCreateDTO,
|
vendorCreditCreateDTO
|
||||||
user
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
|
|||||||
@@ -338,8 +338,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
try {
|
try {
|
||||||
const creditNote = await this.createCreditNoteService.newCreditNote(
|
const creditNote = await this.createCreditNoteService.newCreditNote(
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNoteDTO,
|
creditNoteDTO
|
||||||
user
|
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: creditNote.id,
|
id: creditNote.id,
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
|
import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import BaseController from '../BaseController';
|
||||||
|
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SubscriptionController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private subscriptionService: SubscriptionService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private lemonSqueezyService: LemonSqueezyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(JWTAuth);
|
||||||
|
router.use(AttachCurrentTenantUser);
|
||||||
|
router.use(TenancyMiddleware);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/lemon/checkout_url',
|
||||||
|
[body('variantId').exists().trim()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getCheckoutUrl.bind(this)
|
||||||
|
);
|
||||||
|
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all subscriptions of the authenticated user's tenant.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private async getSubscriptions(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subscriptions = await this.subscriptionService.getSubscriptions(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ subscriptions });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the LemonSqueezy checkout url.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private async getCheckoutUrl(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { variantId } = this.matchedBodyData(req);
|
||||||
|
const { user } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const checkout = await this.lemonSqueezyService.getCheckout(
|
||||||
|
variantId,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
return res.status(200).send(checkout);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './SubscriptionController';
|
||||||
@@ -3,6 +3,7 @@ import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
|
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
||||||
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -10,18 +11,39 @@ export class Webhooks extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private plaidApp: PlaidApplication;
|
private plaidApp: PlaidApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private lemonWebhooksService: LemonSqueezyWebhooks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use(PlaidWebhookTenantBootMiddleware);
|
router.use('/plaid', PlaidWebhookTenantBootMiddleware);
|
||||||
router.post('/plaid', this.plaidWebhooks.bind(this));
|
router.post('/plaid', this.plaidWebhooks.bind(this));
|
||||||
|
|
||||||
|
router.post('/lemon', this.lemonWebhooks.bind(this));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to Lemon Squeezy webhooks events.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
public async lemonWebhooks(req: Request, res: Response) {
|
||||||
|
const data = req.body;
|
||||||
|
const signature = req.headers['x-signature'] ?? '';
|
||||||
|
const rawBody = req.rawBody;
|
||||||
|
|
||||||
|
await this.lemonWebhooksService.handlePostWebhook(rawBody, data, signature);
|
||||||
|
|
||||||
|
return res.status(200).send();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to Plaid webhooks.
|
* Listens to Plaid webhooks.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Container } from 'typedi';
|
|||||||
// Middlewares
|
// Middlewares
|
||||||
import JWTAuth from '@/api/middleware/jwtAuth';
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
|
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
|
||||||
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized';
|
import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized';
|
||||||
import SettingsMiddleware from '@/api/middleware/SettingsMiddleware';
|
import SettingsMiddleware from '@/api/middleware/SettingsMiddleware';
|
||||||
@@ -36,6 +37,7 @@ import Resources from './controllers/Resources';
|
|||||||
import ExchangeRates from '@/api/controllers/ExchangeRates';
|
import ExchangeRates from '@/api/controllers/ExchangeRates';
|
||||||
import Media from '@/api/controllers/Media';
|
import Media from '@/api/controllers/Media';
|
||||||
import Ping from '@/api/controllers/Ping';
|
import Ping from '@/api/controllers/Ping';
|
||||||
|
import { SubscriptionController } from '@/api/controllers/Subscription';
|
||||||
import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments';
|
import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments';
|
||||||
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
|
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
|
||||||
import Jobs from './controllers/Jobs';
|
import Jobs from './controllers/Jobs';
|
||||||
@@ -59,6 +61,7 @@ import { TaxRatesController } from './controllers/TaxRates/TaxRates';
|
|||||||
import { ImportController } from './controllers/Import/ImportController';
|
import { ImportController } from './controllers/Import/ImportController';
|
||||||
import { BankingController } from './controllers/Banking/BankingController';
|
import { BankingController } from './controllers/Banking/BankingController';
|
||||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||||
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -70,6 +73,7 @@ export default () => {
|
|||||||
|
|
||||||
app.use('/auth', Container.get(Authentication).router());
|
app.use('/auth', Container.get(Authentication).router());
|
||||||
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
|
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
|
||||||
|
app.use('/subscription', Container.get(SubscriptionController).router());
|
||||||
app.use('/organization', Container.get(Organization).router());
|
app.use('/organization', Container.get(Organization).router());
|
||||||
app.use('/ping', Container.get(Ping).router());
|
app.use('/ping', Container.get(Ping).router());
|
||||||
app.use('/jobs', Container.get(Jobs).router());
|
app.use('/jobs', Container.get(Jobs).router());
|
||||||
@@ -83,6 +87,7 @@ export default () => {
|
|||||||
dashboard.use(JWTAuth);
|
dashboard.use(JWTAuth);
|
||||||
dashboard.use(AttachCurrentTenantUser);
|
dashboard.use(AttachCurrentTenantUser);
|
||||||
dashboard.use(TenancyMiddleware);
|
dashboard.use(TenancyMiddleware);
|
||||||
|
dashboard.use(SubscriptionMiddleware('main'));
|
||||||
dashboard.use(EnsureTenantIsInitialized);
|
dashboard.use(EnsureTenantIsInitialized);
|
||||||
dashboard.use(SettingsMiddleware);
|
dashboard.use(SettingsMiddleware);
|
||||||
dashboard.use(I18nAuthenticatedMiddlware);
|
dashboard.use(I18nAuthenticatedMiddlware);
|
||||||
@@ -136,12 +141,11 @@ export default () => {
|
|||||||
dashboard.use('/warehouses', Container.get(WarehousesController).router());
|
dashboard.use('/warehouses', Container.get(WarehousesController).router());
|
||||||
dashboard.use('/projects', Container.get(ProjectsController).router());
|
dashboard.use('/projects', Container.get(ProjectsController).router());
|
||||||
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
|
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
|
||||||
|
|
||||||
dashboard.use('/import', Container.get(ImportController).router());
|
dashboard.use('/import', Container.get(ImportController).router());
|
||||||
|
dashboard.use('/export', Container.get(ExportController).router())
|
||||||
|
|
||||||
dashboard.use('/', Container.get(ProjectTasksController).router());
|
dashboard.use('/', Container.get(ProjectTasksController).router());
|
||||||
dashboard.use('/', Container.get(ProjectTimesController).router());
|
dashboard.use('/', Container.get(ProjectTimesController).router());
|
||||||
|
|
||||||
dashboard.use('/', Container.get(WarehousesItemController).router());
|
dashboard.use('/', Container.get(WarehousesItemController).router());
|
||||||
|
|
||||||
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
||||||
|
|||||||
29
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
29
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Container } from 'typedi';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
export default (subscriptionSlug = 'main') =>
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const { tenant, tenantId } = req;
|
||||||
|
const { subscriptionRepository } = Container.get('repositories');
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
throw new Error('Should load `TenancyMiddlware` before this middleware.');
|
||||||
|
}
|
||||||
|
const subscription = await subscriptionRepository.getBySlugInTenant(
|
||||||
|
subscriptionSlug,
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
// Validate in case there is no any already subscription.
|
||||||
|
if (!subscription) {
|
||||||
|
return res.boom.badRequest('Tenant has no subscription.', {
|
||||||
|
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Validate in case the subscription is inactive.
|
||||||
|
else if (subscription.inactive()) {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { toInteger } from 'lodash';
|
import { defaultTo, toInteger } from 'lodash';
|
||||||
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -55,7 +55,7 @@ module.exports = {
|
|||||||
mail: {
|
mail: {
|
||||||
host: process.env.MAIL_HOST,
|
host: process.env.MAIL_HOST,
|
||||||
port: process.env.MAIL_PORT,
|
port: process.env.MAIL_PORT,
|
||||||
secure: !!parseInt(process.env.MAIL_SECURE, 10),
|
secure: parseBoolean(defaultTo(process.env.MAIL_SECURE, false), false),
|
||||||
username: process.env.MAIL_USERNAME,
|
username: process.env.MAIL_USERNAME,
|
||||||
password: process.env.MAIL_PASSWORD,
|
password: process.env.MAIL_PASSWORD,
|
||||||
from: process.env.MAIL_FROM_ADDRESS,
|
from: process.env.MAIL_FROM_ADDRESS,
|
||||||
@@ -153,6 +153,13 @@ module.exports = {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign-up email confirmation
|
||||||
|
*/
|
||||||
|
signupConfirmation: {
|
||||||
|
enabled: parseBoolean<boolean>(process.env.SIGNUP_EMAIL_CONFIRMATION, false),
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puppeteer remote browserless connection.
|
* Puppeteer remote browserless connection.
|
||||||
*/
|
*/
|
||||||
@@ -180,6 +187,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bank Synchronization.
|
||||||
|
*/
|
||||||
|
bankSync: {
|
||||||
|
enabled: parseBoolean(defaultTo(process.env.BANKING_CONNECT, false), false),
|
||||||
|
provider: 'plaid',
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plaid.
|
* Plaid.
|
||||||
*/
|
*/
|
||||||
@@ -190,6 +205,24 @@ module.exports = {
|
|||||||
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
|
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
|
||||||
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
|
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
|
||||||
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
|
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
|
||||||
linkWebhook: process.env.PLAID_LINK_WEBHOOK
|
linkWebhook: process.env.PLAID_LINK_WEBHOOK,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lemon Squeezy.
|
||||||
|
*/
|
||||||
|
lemonSqueezy: {
|
||||||
|
key: process.env.LEMONSQUEEZY_API_KEY,
|
||||||
|
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
||||||
|
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bigcapital (Cloud).
|
||||||
|
* NOTE: DO NOT CHANGE THIS OPTION OR ADD THIS ENV VAR.
|
||||||
|
*/
|
||||||
|
hostedOnBigcapitalCloud: parseBoolean(
|
||||||
|
defaultTo(process.env.HOSTED_ON_BIGCAPITAL_CLOUD, false),
|
||||||
|
false
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default class NotAllowedChangeSubscriptionPlan {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.name = "NotAllowedChangeSubscriptionPlan";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import NotAllowedChangeSubscriptionPlan from './NotAllowedChangeSubscriptionPlan';
|
||||||
import ServiceError from './ServiceError';
|
import ServiceError from './ServiceError';
|
||||||
import ServiceErrors from './ServiceErrors';
|
import ServiceErrors from './ServiceErrors';
|
||||||
import TenantAlreadyInitialized from './TenantAlreadyInitialized';
|
import TenantAlreadyInitialized from './TenantAlreadyInitialized';
|
||||||
@@ -6,6 +7,7 @@ import TenantDBAlreadyExists from './TenantDBAlreadyExists';
|
|||||||
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
|
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
NotAllowedChangeSubscriptionPlan,
|
||||||
ServiceError,
|
ServiceError,
|
||||||
ServiceErrors,
|
ServiceErrors,
|
||||||
TenantAlreadyInitialized,
|
TenantAlreadyInitialized,
|
||||||
|
|||||||
@@ -66,16 +66,27 @@ export interface IAuthResetedPasswordEventPayload {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IAuthSendingResetPassword {
|
export interface IAuthSendingResetPassword {
|
||||||
user: ISystemUser,
|
user: ISystemUser;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
export interface IAuthSendedResetPassword {
|
export interface IAuthSendedResetPassword {
|
||||||
user: ISystemUser,
|
user: ISystemUser;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthGetMetaPOJO {
|
export interface IAuthGetMetaPOJO {
|
||||||
signupDisabled: boolean;
|
signupDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAuthSignUpVerifingEventPayload {
|
||||||
|
email: string;
|
||||||
|
verifyToken: string;
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSignUpVerifiedEventPayload {
|
||||||
|
email: string;
|
||||||
|
verifyToken: string;
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum Features {
|
export enum Features {
|
||||||
WAREHOUSES = 'warehouses',
|
WAREHOUSES = 'warehouses',
|
||||||
BRANCHES = 'branches',
|
BRANCHES = 'branches',
|
||||||
|
BankSyncing = 'BankSyncing'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFeatureAllItem {
|
export interface IFeatureAllItem {
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ export interface IModelMetaFieldCommon {
|
|||||||
name: string;
|
name: string;
|
||||||
column: string;
|
column: string;
|
||||||
columnable?: boolean;
|
columnable?: boolean;
|
||||||
fieldType: IModelColumnType;
|
|
||||||
customQuery?: Function;
|
customQuery?: Function;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
importHint?: string;
|
importHint?: string;
|
||||||
|
importableRelationLabel?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
dataTransferObjectKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelMetaFieldText {
|
export interface IModelMetaFieldText {
|
||||||
@@ -67,6 +69,7 @@ export type IModelMetaField = IModelMetaFieldCommon &
|
|||||||
| IModelMetaFieldUrl
|
| IModelMetaFieldUrl
|
||||||
| IModelMetaEnumerationField
|
| IModelMetaEnumerationField
|
||||||
| IModelMetaRelationField
|
| IModelMetaRelationField
|
||||||
|
| IModelMetaCollectionField
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface IModelMetaEnumerationOption {
|
export interface IModelMetaEnumerationOption {
|
||||||
@@ -90,12 +93,93 @@ export interface IModelMetaRelationEnumerationField {
|
|||||||
relationEntityKey: 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 &
|
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
||||||
IModelMetaRelationEnumerationField;
|
IModelMetaRelationEnumerationField;
|
||||||
|
|
||||||
export interface IModelMeta {
|
export interface IModelMeta {
|
||||||
defaultFilterField: string;
|
defaultFilterField: string;
|
||||||
defaultSort: IModelMetaDefaultSort;
|
defaultSort: IModelMetaDefaultSort;
|
||||||
|
|
||||||
|
exportable?: boolean;
|
||||||
|
exportFlattenOn?: string;
|
||||||
|
|
||||||
importable?: boolean;
|
importable?: boolean;
|
||||||
|
importAggregator?: string;
|
||||||
|
importAggregateOn?: string;
|
||||||
|
importAggregateBy?: string;
|
||||||
|
|
||||||
fields: { [key: string]: IModelMetaField };
|
fields: { [key: string]: IModelMetaField };
|
||||||
|
columns: { [key: string]: IModelMetaColumn };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
export interface IModelMetaFieldCommon2 {
|
||||||
|
name: string;
|
||||||
|
required?: boolean;
|
||||||
|
importHint?: string;
|
||||||
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ export default class MetableStore implements IMetableStore {
|
|||||||
* @param {String} key -
|
* @param {String} key -
|
||||||
* @param {Mixied} defaultValue -
|
* @param {Mixied} defaultValue -
|
||||||
*/
|
*/
|
||||||
get(query: string | IMetaQuery, defaultValue: any): any | false {
|
get(query: string | IMetaQuery, defaultValue: any): any | null {
|
||||||
const metadata = this.find(query);
|
const metadata = this.find(query);
|
||||||
return metadata
|
return metadata
|
||||||
? metadata.value
|
? metadata.value
|
||||||
: typeof defaultValue !== 'undefined'
|
: typeof defaultValue !== 'undefined'
|
||||||
? defaultValue
|
? defaultValue
|
||||||
: false;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -89,7 +89,10 @@ import { InvoiceChangeStatusOnMailSentSubscriber } from '@/services/Sales/Invoic
|
|||||||
import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber';
|
import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber';
|
||||||
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
||||||
import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize';
|
import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize';
|
||||||
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; }
|
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete';
|
||||||
|
import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity';
|
||||||
|
import { SendVerfiyMailOnSignUp } from '@/services/Authentication/events/SendVerfiyMailOnSignUp';
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -218,6 +221,9 @@ export const susbcribers = () => {
|
|||||||
|
|
||||||
// Cashflow
|
// Cashflow
|
||||||
DeleteCashflowTransactionOnUncategorize,
|
DeleteCashflowTransactionOnUncategorize,
|
||||||
PreventDeleteTransactionOnDelete
|
PreventDeleteTransactionOnDelete,
|
||||||
|
|
||||||
|
SubscribeFreeOnSignupCommunity,
|
||||||
|
SendVerfiyMailOnSignUp
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ export default ({ app }) => {
|
|||||||
// Boom response objects.
|
// Boom response objects.
|
||||||
app.use(boom());
|
app.use(boom());
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(
|
||||||
|
bodyParser.json({
|
||||||
|
verify: (req, res, buf) => {
|
||||||
|
req.rawBody = buf;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Parses both json and urlencoded.
|
// Parses both json and urlencoded.
|
||||||
app.use(json());
|
app.use(json());
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEsti
|
|||||||
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
||||||
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
||||||
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
||||||
|
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
|
||||||
|
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
|
||||||
|
|
||||||
export default ({ agenda }: { agenda: Agenda }) => {
|
export default ({ agenda }: { agenda: Agenda }) => {
|
||||||
new ResetPasswordMailJob(agenda);
|
new ResetPasswordMailJob(agenda);
|
||||||
@@ -25,6 +27,10 @@ export default ({ agenda }: { agenda: Agenda }) => {
|
|||||||
new SaleReceiptMailNotificationJob(agenda);
|
new SaleReceiptMailNotificationJob(agenda);
|
||||||
new PaymentReceiveMailNotificationJob(agenda);
|
new PaymentReceiveMailNotificationJob(agenda);
|
||||||
new PlaidFetchTransactionsJob(agenda);
|
new PlaidFetchTransactionsJob(agenda);
|
||||||
|
new ImportDeleteExpiredFilesJobs(agenda);
|
||||||
|
new SendVerifyMailJob(agenda);
|
||||||
|
|
||||||
agenda.start();
|
agenda.start().then(() => {
|
||||||
|
agenda.every('1 hours', 'delete-expired-imported-files', {});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import {
|
import {
|
||||||
SystemUserRepository,
|
SystemUserRepository,
|
||||||
|
SubscriptionRepository,
|
||||||
TenantRepository,
|
TenantRepository,
|
||||||
} from '@/system/repositories';
|
} from '@/system/repositories';
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ export default () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
systemUserRepository: new SystemUserRepository(knex, cache),
|
systemUserRepository: new SystemUserRepository(knex, cache),
|
||||||
|
subscriptionRepository: new SubscriptionRepository(knex, cache),
|
||||||
tenantRepository: new TenantRepository(knex, cache),
|
tenantRepository: new TenantRepository(knex, cache),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,6 @@ import Task from 'models/Task';
|
|||||||
import TaxRate from 'models/TaxRate';
|
import TaxRate from 'models/TaxRate';
|
||||||
import TaxRateTransaction from 'models/TaxRateTransaction';
|
import TaxRateTransaction from 'models/TaxRateTransaction';
|
||||||
import Attachment from 'models/Attachment';
|
import Attachment from 'models/Attachment';
|
||||||
import Import from 'models/Import';
|
|
||||||
import PlaidItem from 'models/PlaidItem';
|
import PlaidItem from 'models/PlaidItem';
|
||||||
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
|
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
|
||||||
|
|
||||||
@@ -128,7 +127,6 @@ export default (knex) => {
|
|||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRateTransaction,
|
TaxRateTransaction,
|
||||||
Attachment,
|
Attachment,
|
||||||
Import,
|
|
||||||
PlaidItem,
|
PlaidItem,
|
||||||
UncategorizedCashflowTransaction
|
UncategorizedCashflowTransaction
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,23 +7,17 @@ export default {
|
|||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
importable: true,
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
name: 'account.field.name',
|
name: 'account.field.name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
unique: true,
|
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
order: 1,
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'account.field.description',
|
name: 'account.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
},
|
},
|
||||||
slug: {
|
slug: {
|
||||||
name: 'account.field.slug',
|
name: 'account.field.slug',
|
||||||
@@ -31,19 +25,13 @@ export default {
|
|||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
columnable: false,
|
columnable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
name: 'account.field.code',
|
name: 'account.field.code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
exportable: true,
|
|
||||||
importable: true,
|
|
||||||
minLength: 3,
|
|
||||||
maxLength: 6,
|
|
||||||
importHint: 'Unique number to identify the account.',
|
|
||||||
},
|
},
|
||||||
rootType: {
|
root_type: {
|
||||||
name: 'account.field.root_type',
|
name: 'account.field.root_type',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -55,7 +43,6 @@ export default {
|
|||||||
],
|
],
|
||||||
filterCustomQuery: RootTypeFieldFilterQuery,
|
filterCustomQuery: RootTypeFieldFilterQuery,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
name: 'account.field.normal',
|
name: 'account.field.normal',
|
||||||
@@ -66,9 +53,8 @@ export default {
|
|||||||
],
|
],
|
||||||
filterCustomQuery: NormalTypeFieldFilterQuery,
|
filterCustomQuery: NormalTypeFieldFilterQuery,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
accountType: {
|
type: {
|
||||||
name: 'account.field.type',
|
name: 'account.field.type',
|
||||||
column: 'account_type',
|
column: 'account_type',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
@@ -76,47 +62,121 @@ export default {
|
|||||||
label: accountType.label,
|
label: accountType.label,
|
||||||
key: accountType.key,
|
key: accountType.key,
|
||||||
})),
|
})),
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
order: 2,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'account.field.active',
|
name: 'account.field.active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
exportable: true,
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
name: 'account.field.balance',
|
name: 'account.field.balance',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currency: {
|
||||||
name: 'account.field.currency',
|
name: 'account.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
},
|
},
|
||||||
parentAccount: {
|
created_at: {
|
||||||
name: 'account.field.parent_account',
|
|
||||||
column: 'parent_account_id',
|
|
||||||
fieldType: 'relation',
|
|
||||||
to: { model: 'Account', to: 'id' },
|
|
||||||
importable: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
name: 'account.field.created_at',
|
name: 'account.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: false,
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
name: {
|
||||||
|
name: 'account.field.name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'account.field.code',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
rootType: {
|
||||||
|
name: 'account.field.root_type',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'accountRootType',
|
||||||
|
},
|
||||||
|
accountType: {
|
||||||
|
name: 'account.field.type',
|
||||||
|
accessor: 'accountTypeLabel',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
accountNormal: {
|
||||||
|
name: 'account.field.normal',
|
||||||
|
accessor: 'accountNormalFormatted',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'account.field.currency',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
bankBalance: {
|
||||||
|
name: 'account.field.bank_balance',
|
||||||
|
accessor: 'bankBalanceFormatted',
|
||||||
|
type: 'text',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
},
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'account.field.balance',
|
||||||
|
accessor: 'amount',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'account.field.description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'account.field.active',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'account.field.created_at',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'account.field.name',
|
||||||
|
fieldType: 'text',
|
||||||
|
unique: true,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'account.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'account.field.code',
|
||||||
|
fieldType: 'text',
|
||||||
|
minLength: 3,
|
||||||
|
maxLength: 6,
|
||||||
|
unique: true,
|
||||||
|
importHint: 'Unique number to identify the account.',
|
||||||
|
},
|
||||||
|
accountType: {
|
||||||
|
name: 'account.field.type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||||
|
label: accountType.label,
|
||||||
|
key: accountType.key,
|
||||||
|
})),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'account.field.active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'account.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
parentAccountId: {
|
||||||
|
name: 'account.field.parent_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'vendor',
|
defaultFilterField: 'vendor',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'bill_date',
|
sortField: 'bill_date',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
exportable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'billNumber',
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'bill.field.vendor',
|
name: 'bill.field.vendor',
|
||||||
@@ -77,6 +82,155 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
billNumber: {
|
||||||
|
name: 'Bill No.',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billDate: {
|
||||||
|
name: 'Date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'Due Date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
accessor: 'vendor.displayName',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
accessor: 'formattedAmount',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'Currency Code',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
dueAmount: {
|
||||||
|
name: 'Due Amount',
|
||||||
|
accessor: 'formattedDueAmount',
|
||||||
|
},
|
||||||
|
paidAmount: {
|
||||||
|
name: 'Paid Amount',
|
||||||
|
accessor: 'formattedPaymentAmount',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
billNumber: {
|
||||||
|
name: 'Bill No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
billDate: {
|
||||||
|
name: 'Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'Due Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Line Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'bill_date',
|
sortField: 'bill_date',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'paymentNumber',
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'bill_payment.field.vendor',
|
name: 'bill_payment.field.vendor',
|
||||||
@@ -33,7 +38,7 @@ export default {
|
|||||||
|
|
||||||
relationType: 'enumeration',
|
relationType: 'enumeration',
|
||||||
relationKey: 'paymentAccount',
|
relationKey: 'paymentAccount',
|
||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
@@ -63,4 +68,107 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
vendor: {
|
||||||
|
name: 'bill_payment.field.vendor',
|
||||||
|
type: 'relation',
|
||||||
|
accessor: 'vendor.displayName',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'bill_payment.field.payment_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
paymentNumber: {
|
||||||
|
name: 'bill_payment.field.payment_number',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
paymentAccount: {
|
||||||
|
name: 'bill_payment.field.payment_account',
|
||||||
|
accessor: 'paymentAccount.name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
accessor: 'formattedAmount',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'Currency Code',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'bill_payment.field.exchange_rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'bill_payment.field.note',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'bill_payment.field.reference',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
vendorId: {
|
||||||
|
name: 'bill_payment.field.vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
payment_date: {
|
||||||
|
name: 'bill_payment.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
paymentNumber: {
|
||||||
|
name: 'bill_payment.field.payment_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
unique: true,
|
||||||
|
importHint: 'The payment number should be unique.',
|
||||||
|
},
|
||||||
|
paymentAccountId: {
|
||||||
|
name: 'bill_payment.field.payment_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'bill_payment.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'bill_payment.field.note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'bill_payment.field.reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'bill_payment.field.entries',
|
||||||
|
column: 'entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
billId: {
|
||||||
|
name: 'bill_payment.field.entries.bill',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Bill',
|
||||||
|
relationImportMatch: 'billNumber',
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the bill number.',
|
||||||
|
},
|
||||||
|
paymentAmount: {
|
||||||
|
name: 'bill_payment.field.entries.payment_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -187,18 +187,4 @@ export default class Contact extends TenantModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
|
||||||
return {
|
|
||||||
contact_service: {
|
|
||||||
column: 'contact_service',
|
|
||||||
},
|
|
||||||
display_name: {
|
|
||||||
column: 'display_name',
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
column: 'created_at',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'creditNoteNumber',
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'credit_note.field.customer',
|
name: 'credit_note.field.customer',
|
||||||
@@ -77,4 +85,133 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
customer: {
|
||||||
|
name: 'Customer',
|
||||||
|
type: 'relation',
|
||||||
|
accessor: 'customer.displayName',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
creditNoteDate: {
|
||||||
|
name: 'Credit Note Date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
creditNoteNumber: {
|
||||||
|
name: 'Credit Note Number',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
creditNoteDate: {
|
||||||
|
name: 'Credit Note Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
creditNoteNumber: {
|
||||||
|
name: 'Credit Note Number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,215 +1,381 @@
|
|||||||
export default {
|
export default {
|
||||||
importable: true,
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
defaultFilterField: 'displayName',
|
defaultFilterField: 'displayName',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
|
first_name: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
column: 'first_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
last_name: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
column: 'last_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
display_name: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
work_phone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
personal_phone: {
|
||||||
|
name: 'vendor.field.personal_pone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
company_name: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'vendor.field.balance',
|
||||||
|
column: 'balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
opening_balance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
opening_balance_at: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
currency_code: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'vendor.field.status',
|
||||||
|
type: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'overdue', label: 'vendor.field.status.overdue' },
|
||||||
|
{ key: 'unpaid', label: 'vendor.field.status.unpaid' },
|
||||||
|
],
|
||||||
|
filterCustomQuery: (query, role) => {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'overdue':
|
||||||
|
query.modify('overdue');
|
||||||
|
break;
|
||||||
|
case 'unpaid':
|
||||||
|
query.modify('unpaid');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
firstName: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
workPhone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
personalPhone: {
|
||||||
|
name: 'vendor.field.personal_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
companyName: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'vendor.field.balance',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
openingBalance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
openingBalanceAt: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'vendor.field.status',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'vendor.field.note',
|
||||||
|
},
|
||||||
|
// Billing Address
|
||||||
|
billingAddress1: {
|
||||||
|
name: 'Billing Address 1',
|
||||||
|
column: 'billing_address1',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddress2: {
|
||||||
|
name: 'Billing Address 2',
|
||||||
|
column: 'billing_address2',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddressCity: {
|
||||||
|
name: 'Billing Address City',
|
||||||
|
column: 'billing_address_city',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddressCountry: {
|
||||||
|
name: 'Billing Address Country',
|
||||||
|
column: 'billing_address_country',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddressPostcode: {
|
||||||
|
name: 'Billing Address Postcode',
|
||||||
|
column: 'billing_address_postcode',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddressState: {
|
||||||
|
name: 'Billing Address State',
|
||||||
|
column: 'billing_address_state',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
billingAddressPhone: {
|
||||||
|
name: 'Billing Address Phone',
|
||||||
|
column: 'billing_address_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
// Shipping Address
|
||||||
|
shippingAddress1: {
|
||||||
|
name: 'Shipping Address 1',
|
||||||
|
column: 'shipping_address1',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddress2: {
|
||||||
|
name: 'Shipping Address 2',
|
||||||
|
column: 'shipping_address2',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddressCity: {
|
||||||
|
name: 'Shipping Address City',
|
||||||
|
column: 'shipping_address_city',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddressCountry: {
|
||||||
|
name: 'Shipping Address Country',
|
||||||
|
column: 'shipping_address_country',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddressPostcode: {
|
||||||
|
name: 'Shipping Address Postcode',
|
||||||
|
column: 'shipping_address_postcode',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddressPhone: {
|
||||||
|
name: 'Shipping Address Phone',
|
||||||
|
column: 'shipping_address_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
shippingAddressState: {
|
||||||
|
name: 'Shipping Address State',
|
||||||
|
column: 'shipping_address_state',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
customerType: {
|
customerType: {
|
||||||
name: 'Customer Type',
|
name: 'Customer Type',
|
||||||
column: 'contact_type',
|
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'business', label: 'Business' },
|
{ key: 'business', label: 'Business' },
|
||||||
{ key: 'individual', label: 'Individual' },
|
{ key: 'individual', label: 'Individual' },
|
||||||
],
|
],
|
||||||
importable: true,
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
firstName: {
|
firstName: {
|
||||||
name: 'customer.field.first_name',
|
name: 'customer.field.first_name',
|
||||||
column: 'first_name',
|
column: 'first_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
lastName: {
|
lastName: {
|
||||||
name: 'customer.field.last_name',
|
name: 'customer.field.last_name',
|
||||||
column: 'last_name',
|
column: 'last_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
name: 'customer.field.display_name',
|
name: 'customer.field.display_name',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
name: 'customer.field.email',
|
name: 'customer.field.email',
|
||||||
column: 'email',
|
column: 'email',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
workPhone: {
|
workPhone: {
|
||||||
name: 'customer.field.work_phone',
|
name: 'customer.field.work_phone',
|
||||||
column: 'work_phone',
|
column: 'work_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
personalPhone: {
|
personalPhone: {
|
||||||
name: 'customer.field.personal_phone',
|
name: 'customer.field.personal_phone',
|
||||||
column: 'personal_phone',
|
column: 'personal_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
companyName: {
|
companyName: {
|
||||||
name: 'customer.field.company_name',
|
name: 'customer.field.company_name',
|
||||||
column: 'company_name',
|
column: 'company_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
name: 'customer.field.website',
|
name: 'customer.field.website',
|
||||||
column: 'website',
|
column: 'website',
|
||||||
fieldType: 'url',
|
fieldType: 'url',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
name: 'customer.field.balance',
|
|
||||||
column: 'balance',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
},
|
||||||
openingBalance: {
|
openingBalance: {
|
||||||
name: 'customer.field.opening_balance',
|
name: 'customer.field.opening_balance',
|
||||||
column: 'opening_balance',
|
column: 'opening_balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
openingBalanceAt: {
|
||||||
name: 'customer.field.opening_balance_at',
|
name: 'customer.field.opening_balance_at',
|
||||||
column: 'opening_balance_at',
|
column: 'opening_balance_at',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceExchangeRate: {
|
openingBalanceExchangeRate: {
|
||||||
name: 'Opening Balance Ex. Rate',
|
name: 'Opening Balance Ex. Rate',
|
||||||
column: 'opening_balance_exchange_rate',
|
column: 'opening_balance_exchange_rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currencyCode: {
|
||||||
name: 'customer.field.currency',
|
name: 'customer.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'Note',
|
name: 'Note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'Active',
|
name: 'Active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
name: 'customer.field.status',
|
|
||||||
fieldType: 'enumeration',
|
|
||||||
options: [
|
|
||||||
{ key: 'active', label: 'customer.field.status.active' },
|
|
||||||
{ key: 'inactive', label: 'customer.field.status.inactive' },
|
|
||||||
{ key: 'overdue', label: 'customer.field.status.overdue' },
|
|
||||||
{ key: 'unpaid', label: 'customer.field.status.unpaid' },
|
|
||||||
],
|
|
||||||
filterCustomQuery: statusFieldFilterQuery,
|
|
||||||
},
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
//
|
|
||||||
createdAt: {
|
|
||||||
name: 'customer.field.created_at',
|
|
||||||
column: 'created_at',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
exportFlattenOn: 'categories',
|
||||||
|
exportable: true,
|
||||||
fields: {
|
fields: {
|
||||||
'payment_date': {
|
payment_date: {
|
||||||
name: 'expense.field.payment_date',
|
name: 'expense.field.payment_date',
|
||||||
column: 'payment_date',
|
column: 'payment_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'payment_account': {
|
payment_account: {
|
||||||
name: 'expense.field.payment_account',
|
name: 'expense.field.payment_account',
|
||||||
column: 'payment_account_id',
|
column: 'payment_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -24,27 +27,27 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'expense.field.amount',
|
name: 'expense.field.amount',
|
||||||
column: 'total_amount',
|
column: 'total_amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'expense.field.reference_no',
|
name: 'expense.field.reference_no',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'description': {
|
description: {
|
||||||
name: 'expense.field.description',
|
name: 'expense.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'published': {
|
published: {
|
||||||
name: 'expense.field.published',
|
name: 'expense.field.published',
|
||||||
column: 'published_at',
|
column: 'published_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'expense.field.status',
|
name: 'expense.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -54,12 +57,121 @@ export default {
|
|||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'expense.field.created_at',
|
name: 'expense.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
paymentReceive: {
|
||||||
|
name: 'expense.field.payment_account',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'paymentAccount.name'
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'expense.field.reference_no',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'expense.field.payment_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'expense.field.currency_code',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'expense.field.exchange_rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
name: 'expense.field.categories',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
expenseAccount: {
|
||||||
|
name: 'expense.field.expense_account',
|
||||||
|
accessor: 'expenseAccount.name',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'expense.field.amount',
|
||||||
|
accessor: 'amountFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.line_description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'expense.field.publish',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
paymentAccountId: {
|
||||||
|
name: 'expense.field.payment_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'expense.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'expense.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'expense.field.currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'expense.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
name: 'expense.field.categories',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
fields: {
|
||||||
|
expenseAccountId: {
|
||||||
|
name: 'expense.field.expense_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'expense.field.amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.line_description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'expense.field.publish',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
|
|||||||
@@ -4,6 +4,54 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'date',
|
sortField: 'date',
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
date: {
|
||||||
|
name: 'inventory_adjustment.field.date',
|
||||||
|
column: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
name: 'inventory_adjustment.field.type',
|
||||||
|
column: 'type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'increment', name: 'inventory_adjustment.field.type.increment' },
|
||||||
|
{ key: 'decrement', name: 'inventory_adjustment.field.type.decrement' },
|
||||||
|
],
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
adjustmentAccount: {
|
||||||
|
name: 'inventory_adjustment.field.adjustment_account',
|
||||||
|
type: 'adjustment_account_id',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
reason: {
|
||||||
|
name: 'inventory_adjustment.field.reason',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'inventory_adjustment.field.reference_no',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'inventory_adjustment.field.description',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
publishedAt: {
|
||||||
|
name: 'inventory_adjustment.field.published_at',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'inventory_adjustment.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
fields: {
|
fields: {
|
||||||
date: {
|
date: {
|
||||||
name: 'inventory_adjustment.field.date',
|
name: 'inventory_adjustment.field.date',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
importable: true,
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
defaultFilterField: 'name',
|
defaultFilterField: 'name',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
@@ -15,45 +16,38 @@ export default {
|
|||||||
{ key: 'service', label: 'item.field.type.service' },
|
{ key: 'service', label: 'item.field.type.service' },
|
||||||
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||||
],
|
],
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
name: 'item.field.name',
|
name: 'item.field.name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
name: 'item.field.code',
|
name: 'item.field.code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellable: {
|
sellable: {
|
||||||
name: 'item.field.sellable',
|
name: 'item.field.sellable',
|
||||||
column: 'sellable',
|
column: 'sellable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
purchasable: {
|
purchasable: {
|
||||||
name: 'item.field.purchasable',
|
name: 'item.field.purchasable',
|
||||||
column: 'purchasable',
|
column: 'purchasable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellPrice: {
|
sell_price: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
column: 'sell_price',
|
column: 'sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
costPrice: {
|
cost_price: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
column: 'cost_price',
|
column: 'cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
costAccount: {
|
cost_account: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -63,10 +57,8 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellAccount: {
|
sell_account: {
|
||||||
name: 'item.field.sell_description',
|
name: 'item.field.sell_description',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -76,10 +68,8 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
inventoryAccount: {
|
inventory_account: {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
column: 'inventory_account_id',
|
column: 'inventory_account_id',
|
||||||
|
|
||||||
@@ -88,32 +78,26 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellDescription: {
|
sell_description: {
|
||||||
name: 'Sell description',
|
name: 'Sell description',
|
||||||
column: 'sell_description',
|
column: 'sell_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
purchaseDescription: {
|
purchase_description: {
|
||||||
name: 'Purchase description',
|
name: 'Purchase description',
|
||||||
column: 'purchase_description',
|
column: 'purchase_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
quantityOnHand: {
|
quantity_on_hand: {
|
||||||
name: 'item.field.quantity_on_hand',
|
name: 'item.field.quantity_on_hand',
|
||||||
column: 'quantity_on_hand',
|
column: 'quantity_on_hand',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'item.field.note',
|
name: 'item.field.note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
name: 'item.field.category',
|
name: 'item.field.category',
|
||||||
@@ -124,19 +108,190 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'item.field.active',
|
name: 'item.field.active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
filterable: false,
|
||||||
},
|
},
|
||||||
createdAt: {
|
created_at: {
|
||||||
name: 'item.field.created_at',
|
name: 'item.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
type: {
|
||||||
|
name: 'item.field.type',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
name: 'item.field.name',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'item.field.code',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
sellable: {
|
||||||
|
name: 'item.field.sellable',
|
||||||
|
type: 'boolean',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
purchasable: {
|
||||||
|
name: 'item.field.purchasable',
|
||||||
|
type: 'boolean',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
sellPrice: {
|
||||||
|
name: 'item.field.cost_price',
|
||||||
|
type: 'number',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
costPrice: {
|
||||||
|
name: 'item.field.cost_account',
|
||||||
|
type: 'number',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
costAccount: {
|
||||||
|
name: 'item.field.sell_account',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'costAccount.name',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
sellAccount: {
|
||||||
|
name: 'item.field.sell_description',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'sellAccount.name',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
inventoryAccount: {
|
||||||
|
name: 'item.field.inventory_account',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'inventoryAccount.name',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
sellDescription: {
|
||||||
|
name: 'Sell description',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
purchaseDescription: {
|
||||||
|
name: 'Purchase description',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
quantityOnHand: {
|
||||||
|
name: 'item.field.quantity_on_hand',
|
||||||
|
type: 'number',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'item.field.note',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
name: 'item.field.category',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'category.name',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'item.field.active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'item.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
type: {
|
||||||
|
name: 'item.field.type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'inventory', label: 'item.field.type.inventory' },
|
||||||
|
{ key: 'service', label: 'item.field.type.service' },
|
||||||
|
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
name: 'item.field.name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'item.field.code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
sellable: {
|
||||||
|
name: 'item.field.sellable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
purchasable: {
|
||||||
|
name: 'item.field.purchasable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
sellPrice: {
|
||||||
|
name: 'item.field.sell_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
cost_price: {
|
||||||
|
name: 'item.field.cost_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
costAccount: {
|
||||||
|
name: 'item.field.cost_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
sellAccount: {
|
||||||
|
name: 'item.field.sell_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
inventoryAccount: {
|
||||||
|
name: 'item.field.inventory_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
sellDescription: {
|
||||||
|
name: 'Sell Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
purchaseDescription: {
|
||||||
|
name: 'Purchase Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'item.field.note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
name: 'item.field.category',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'ItemCategory',
|
||||||
|
relationImportMatch: ['name'],
|
||||||
|
importHint: 'Matches the category name.',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'item.field.active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export default {
|
|||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
name: 'item_category.field.name',
|
name: 'item_category.field.name',
|
||||||
@@ -27,4 +29,34 @@ export default {
|
|||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
name: {
|
||||||
|
name: 'item_category.field.name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'item_category.field.description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
name: 'item_category.field.count',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'item_category.field.created_at',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'item_category.field.name',
|
||||||
|
column: 'name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'item_category.field.description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,54 +4,203 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
exportable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'journalNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'date': {
|
date: {
|
||||||
name: 'manual_journal.field.date',
|
name: 'manual_journal.field.date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'journal_number': {
|
journal_number: {
|
||||||
name: 'manual_journal.field.journal_number',
|
name: 'manual_journal.field.journal_number',
|
||||||
column: 'journal_number',
|
column: 'journal_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'reference': {
|
reference: {
|
||||||
name: 'manual_journal.field.reference',
|
name: 'manual_journal.field.reference',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'journal_type': {
|
journal_type: {
|
||||||
name: 'manual_journal.field.journal_type',
|
name: 'manual_journal.field.journal_type',
|
||||||
column: 'journal_type',
|
column: 'journal_type',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'manual_journal.field.amount',
|
name: 'manual_journal.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'description': {
|
description: {
|
||||||
name: 'manual_journal.field.description',
|
name: 'manual_journal.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'manual_journal.field.status',
|
name: 'manual_journal.field.status',
|
||||||
column: 'status',
|
column: 'status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'draft', label: 'Draft' },
|
{ key: 'draft', label: 'Draft' },
|
||||||
{ key: 'published', label: 'published' }
|
{ key: 'published', label: 'published' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'manual_journal.field.created_at',
|
name: 'manual_journal.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
date: {
|
||||||
|
name: 'manual_journal.field.date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
journalNumber: {
|
||||||
|
name: 'manual_journal.field.journal_number',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'manual_journal.field.reference',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
journalType: {
|
||||||
|
name: 'manual_journal.field.journal_type',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
accessor: 'formattedAmount',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'manual_journal.field.currency',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'manual_journal.field.exchange_rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'manual_journal.field.description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
credit: {
|
||||||
|
name: 'Credit',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
debit: {
|
||||||
|
name: 'Debit',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
name: 'Account',
|
||||||
|
accessor: 'account.name',
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'Contact',
|
||||||
|
accessor: 'contact.displayName',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'Publish',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
publishedAt: {
|
||||||
|
name: 'Published At',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'Created At',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
date: {
|
||||||
|
name: 'manual_journal.field.date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
journalNumber: {
|
||||||
|
name: 'manual_journal.field.journal_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'manual_journal.field.reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
journalType: {
|
||||||
|
name: 'manual_journal.field.journal_type',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'manual_journal.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchange_rate: {
|
||||||
|
name: 'manual_journal.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'manual_journal.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 2,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
credit: {
|
||||||
|
name: 'Credit',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
debit: {
|
||||||
|
name: 'Debit',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
accountId: {
|
||||||
|
name: 'Account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'Contact',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'Publish',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +213,6 @@ function StatusFieldSortQuery(query, role) {
|
|||||||
/**
|
/**
|
||||||
* Status field filter custom query.
|
* Status field filter custom query.
|
||||||
*/
|
*/
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
query.modify('filterByStatus', role.value);
|
query.modify('filterByStatus', role.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,54 @@
|
|||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from '@/interfaces';
|
import {
|
||||||
|
IModelMeta,
|
||||||
|
IModelMetaField,
|
||||||
|
IModelMetaDefaultSort,
|
||||||
|
} from '@/interfaces';
|
||||||
|
|
||||||
|
const defaultModelMeta = {
|
||||||
|
fields: {},
|
||||||
|
fields2: {},
|
||||||
|
};
|
||||||
|
|
||||||
export default (Model) =>
|
export default (Model) =>
|
||||||
class ModelSettings extends Model {
|
class ModelSettings extends Model {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
* @returns {IModelMeta}
|
||||||
*/
|
*/
|
||||||
static get meta(): IModelMeta {
|
static get meta(): IModelMeta {
|
||||||
throw new Error('');
|
throw new Error('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsed meta merged with default emta.
|
||||||
|
* @returns {IModelMeta}
|
||||||
|
*/
|
||||||
|
static get parsedMeta(): IModelMeta {
|
||||||
|
return {
|
||||||
|
...defaultModelMeta,
|
||||||
|
...this.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve specific model field meta of the given field key.
|
* Retrieve specific model field meta of the given field key.
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {IModelMetaField}
|
* @returns {IModelMetaField}
|
||||||
*/
|
*/
|
||||||
public static getField(key: string, attribute?:string): IModelMetaField {
|
public static getField(key: string, attribute?: string): IModelMetaField {
|
||||||
const field = get(this.meta.fields, key);
|
const field = get(this.meta.fields, key);
|
||||||
|
|
||||||
return attribute ? get(field, attribute) : field;
|
return attribute ? get(field, attribute) : field;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the specific model meta.
|
* Retrieves the specific model meta.
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public static getMeta(key?: string) {
|
public static getMeta(key?: string) {
|
||||||
return key ? get(this.meta, key): this.meta;
|
return key ? get(this.parsedMeta, key) : this.parsedMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'paymentReceiveNo',
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'payment_receive.field.customer',
|
name: 'payment_receive.field.customer',
|
||||||
@@ -54,4 +58,101 @@ export default {
|
|||||||
fieldDate: 'date',
|
fieldDate: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
customer: {
|
||||||
|
name: 'payment_receive.field.customer',
|
||||||
|
accessor: 'customer.displayName',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'payment_receive.field.payment_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'payment_receive.field.amount',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'payment_receive.field.reference_no',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
depositAccount: {
|
||||||
|
name: 'payment_receive.field.deposit_account',
|
||||||
|
accessor: 'depositAccount.name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
paymentReceiveNo: {
|
||||||
|
name: 'payment_receive.field.payment_receive_no',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'payment_receive.field.statement',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'payment_receive.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'payment_receive.field.customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'payment_receive.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'payment_receive.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'payment_receive.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
depositAccountId: {
|
||||||
|
name: 'payment_receive.field.deposit_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
paymentReceiveNo: {
|
||||||
|
name: 'payment_receive.field.payment_receive_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
importHint: 'The payment number should be unique.',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'payment_receive.field.statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'payment_receive.field.entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
invoiceId: {
|
||||||
|
name: 'payment_receive.field.invoice',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'SaleInvoice',
|
||||||
|
relationImportMatch: 'invoiceNo',
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the invoice number.',
|
||||||
|
},
|
||||||
|
paymentAmount: {
|
||||||
|
name: 'payment_receive.field.entries.payment_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,18 +4,25 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'estimate_date',
|
sortField: 'estimate_date',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'estimateNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'estimate.field.amount',
|
name: 'estimate.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'estimate_number': {
|
estimate_number: {
|
||||||
name: 'estimate.field.estimate_number',
|
name: 'estimate.field.estimate_number',
|
||||||
column: 'estimate_number',
|
column: 'estimate_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'customer': {
|
customer: {
|
||||||
name: 'estimate.field.customer',
|
name: 'estimate.field.customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -26,32 +33,32 @@ export default {
|
|||||||
relationEntityLabel: 'display_name',
|
relationEntityLabel: 'display_name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
},
|
},
|
||||||
'estimate_date': {
|
estimate_date: {
|
||||||
name: 'estimate.field.estimate_date',
|
name: 'estimate.field.estimate_date',
|
||||||
column: 'estimate_date',
|
column: 'estimate_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'expiration_date': {
|
expiration_date: {
|
||||||
name: 'estimate.field.expiration_date',
|
name: 'estimate.field.expiration_date',
|
||||||
column: 'expiration_date',
|
column: 'expiration_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'estimate.field.reference_no',
|
name: 'estimate.field.reference_no',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'note': {
|
note: {
|
||||||
name: 'estimate.field.note',
|
name: 'estimate.field.note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'terms_conditions': {
|
terms_conditions: {
|
||||||
name: 'estimate.field.terms_conditions',
|
name: 'estimate.field.terms_conditions',
|
||||||
column: 'terms_conditions',
|
column: 'terms_conditions',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'estimate.field.status',
|
name: 'estimate.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -63,12 +70,175 @@ export default {
|
|||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'estimate.field.created_at',
|
name: 'estimate.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
customer: {
|
||||||
|
name: 'Customer',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'customer.displayName',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
estimateDate: {
|
||||||
|
name: 'Estimate Date',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
expirationDate: {
|
||||||
|
name: 'Expiration Date',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
estimateNumber: {
|
||||||
|
name: 'Estimate No.',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
accessor: 'formattedAmount',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
type: 'number',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'Currency',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'Delivered',
|
||||||
|
type: 'boolean',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
estimateDate: {
|
||||||
|
name: 'Estimate Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
expirationDate: {
|
||||||
|
name: 'Expiration Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
estimateNumber: {
|
||||||
|
name: 'Estimate No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'Currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'Delivered',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Line Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldSortQuery(query, role) {
|
function StatusFieldSortQuery(query, role) {
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'invoiceNo',
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'invoice.field.customer',
|
name: 'invoice.field.customer',
|
||||||
@@ -83,6 +90,167 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
invoiceDate: {
|
||||||
|
name: 'invoice.field.invoice_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'invoice.field.due_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'invoice.field.reference_no',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
invoiceNo: {
|
||||||
|
name: 'invoice.field.invoice_no',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'invoice.field.customer',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'customer.displayName',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'invoice.field.amount',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'balanceAmountFormatted',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'invoice.field.exchange_rate',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'invoice.field.currency',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
paidAmount: {
|
||||||
|
name: 'Paid Amount',
|
||||||
|
accessor: 'paymentAmountFormatted',
|
||||||
|
},
|
||||||
|
dueAmount: {
|
||||||
|
name: 'Due Amount',
|
||||||
|
accessor: 'dueAmountFormatted',
|
||||||
|
},
|
||||||
|
invoiceMessage: {
|
||||||
|
name: 'invoice.field.invoice_message',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'invoice.field.terms_conditions',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'invoice.field.delivered',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
invoiceDate: {
|
||||||
|
name: 'invoice.field.invoice_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'invoice.field.due_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'invoice.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
invoiceNo: {
|
||||||
|
name: 'invoice.field.invoice_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
customerId: {
|
||||||
|
name: 'invoice.field.customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'invoice.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'invoice.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
invoiceMessage: {
|
||||||
|
name: 'invoice.field.invoice_message',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'invoice.field.terms_conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'invoice.field.entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'invoice.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'invoice.field.delivered',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'receiptNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'receipt.field.amount',
|
name: 'receipt.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'deposit_account': {
|
deposit_account: {
|
||||||
column: 'deposit_account_id',
|
column: 'deposit_account_id',
|
||||||
name: 'receipt.field.deposit_account',
|
name: 'receipt.field.deposit_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -21,7 +28,7 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
'customer': {
|
customer: {
|
||||||
name: 'receipt.field.customer',
|
name: 'receipt.field.customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -32,38 +39,37 @@ export default {
|
|||||||
relationEntityLabel: 'display_name',
|
relationEntityLabel: 'display_name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
},
|
},
|
||||||
'receipt_date': {
|
receipt_date: {
|
||||||
name: 'receipt.field.receipt_date',
|
name: 'receipt.field.receipt_date',
|
||||||
column: 'receipt_date',
|
column: 'receipt_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
|
|
||||||
},
|
},
|
||||||
'receipt_number': {
|
receipt_number: {
|
||||||
name: 'receipt.field.receipt_number',
|
name: 'receipt.field.receipt_number',
|
||||||
column: 'receipt_number',
|
column: 'receipt_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'receipt.field.reference_no',
|
name: 'receipt.field.reference_no',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'receipt_message': {
|
receipt_message: {
|
||||||
name: 'receipt.field.receipt_message',
|
name: 'receipt.field.receipt_message',
|
||||||
column: 'receipt_message',
|
column: 'receipt_message',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'statement': {
|
statement: {
|
||||||
name: 'receipt.field.statement',
|
name: 'receipt.field.statement',
|
||||||
column: 'statement',
|
column: 'statement',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'receipt.field.created_at',
|
name: 'receipt.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'receipt.field.status',
|
name: 'receipt.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -74,6 +80,162 @@ export default {
|
|||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
amount: {
|
||||||
|
name: 'receipt.field.amount',
|
||||||
|
column: 'amount',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
depositAccount: {
|
||||||
|
name: 'receipt.field.deposit_account',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'depositAccount.name',
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'receipt.field.customer',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'customer.displayName',
|
||||||
|
},
|
||||||
|
receiptDate: {
|
||||||
|
name: 'receipt.field.receipt_date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
receiptNumber: {
|
||||||
|
name: 'receipt.field.receipt_number',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'receipt.field.reference_no',
|
||||||
|
column: 'reference_no',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
receiptMessage: {
|
||||||
|
name: 'receipt.field.receipt_message',
|
||||||
|
column: 'receipt_message',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'receipt.field.statement',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'receipt.field.status',
|
||||||
|
type: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'draft', label: 'receipt.field.status.draft' },
|
||||||
|
{ key: 'closed', label: 'receipt.field.status.closed' },
|
||||||
|
],
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'receipt.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
receiptDate: {
|
||||||
|
name: 'Receipt Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
depositAccountId: {
|
||||||
|
name: 'Deposit Account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
receiptNumber: {
|
||||||
|
name: 'Receipt Number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
name: 'Closed',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'invoice.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'Statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
receiptMessage: {
|
||||||
|
name: 'Receipt Message',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export default {
|
|||||||
defaultFilterField: 'createdAt',
|
defaultFilterField: 'createdAt',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
importable: true,
|
importable: true,
|
||||||
fields: {
|
fields: {
|
||||||
@@ -10,33 +10,27 @@ export default {
|
|||||||
name: 'Date',
|
name: 'Date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
payee: {
|
payee: {
|
||||||
name: 'Payee',
|
name: 'Payee',
|
||||||
column: 'payee',
|
column: 'payee',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'Description',
|
name: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'Reference No.',
|
name: 'Reference No.',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
name: 'Amount',
|
name: 'Amount',
|
||||||
column: 'Amount',
|
column: 'Amount',
|
||||||
fieldType: 'numeric',
|
fieldType: 'numeric',
|
||||||
required: true,
|
required: true,
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
name: 'Account',
|
name: 'Account',
|
||||||
@@ -51,4 +45,28 @@ export default {
|
|||||||
importable: false,
|
importable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
date: {
|
||||||
|
name: 'Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
payee: {
|
||||||
|
name: 'Payee',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,99 +2,75 @@ export default {
|
|||||||
defaultFilterField: 'displayName',
|
defaultFilterField: 'displayName',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
importable: true,
|
importable: true,
|
||||||
|
exportable: true,
|
||||||
fields: {
|
fields: {
|
||||||
firstName: {
|
first_name: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
column: 'first_name',
|
column: 'first_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
lastName: {
|
last_name: {
|
||||||
name: 'vendor.field.last_name',
|
name: 'vendor.field.last_name',
|
||||||
column: 'last_name',
|
column: 'last_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
displayName: {
|
display_name: {
|
||||||
name: 'vendor.field.display_name',
|
name: 'vendor.field.display_name',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
name: 'vendor.field.email',
|
name: 'vendor.field.email',
|
||||||
column: 'email',
|
column: 'email',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
workPhone: {
|
work_phone: {
|
||||||
name: 'vendor.field.work_phone',
|
name: 'vendor.field.work_phone',
|
||||||
column: 'work_phone',
|
column: 'work_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
personalPhone: {
|
personal_phone: {
|
||||||
name: 'vendor.field.personal_phone',
|
name: 'vendor.field.personal_phone',
|
||||||
column: 'personal_phone',
|
column: 'personal_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
companyName: {
|
company_name: {
|
||||||
name: 'vendor.field.company_name',
|
name: 'vendor.field.company_name',
|
||||||
column: 'company_name',
|
column: 'company_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
name: 'vendor.field.website',
|
name: 'vendor.field.website',
|
||||||
column: 'website',
|
column: 'website',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
name: 'vendor.field.balance',
|
name: 'vendor.field.balance',
|
||||||
column: 'balance',
|
column: 'balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
openingBalance: {
|
opening_balance: {
|
||||||
name: 'vendor.field.opening_balance',
|
name: 'vendor.field.opening_balance',
|
||||||
column: 'opening_balance',
|
column: 'opening_balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
opening_balance_at: {
|
||||||
name: 'vendor.field.opening_balance_at',
|
name: 'vendor.field.opening_balance_at',
|
||||||
column: 'opening_balance_at',
|
column: 'opening_balance_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceExchangeRate: {
|
currency_code: {
|
||||||
name: 'Opening Balance Ex. Rate',
|
|
||||||
column: 'opening_balance_exchange_rate',
|
|
||||||
fieldType: 'number',
|
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
currencyCode: {
|
|
||||||
name: 'vendor.field.currency',
|
name: 'vendor.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
name: 'Note',
|
|
||||||
column: 'note',
|
|
||||||
fieldType: 'text',
|
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
name: 'Active',
|
|
||||||
column: 'active',
|
|
||||||
fieldType: 'boolean',
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'vendor.field.status',
|
name: 'vendor.field.status',
|
||||||
@@ -114,96 +90,298 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
firstName: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
workPhone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
personalPhone: {
|
||||||
|
name: 'vendor.field.personal_phone',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
companyName: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'vendor.field.balance',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
openingBalance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
openingBalanceAt: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'vendor.field.status',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'vendor.field.note',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
// Billing Address
|
||||||
|
billingAddress1: {
|
||||||
|
name: 'Billing Address 1',
|
||||||
|
column: 'billing_address1',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddress2: {
|
||||||
|
name: 'Billing Address 2',
|
||||||
|
column: 'billing_address2',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddressCity: {
|
||||||
|
name: 'Billing Address City',
|
||||||
|
column: 'billing_address_city',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddressCountry: {
|
||||||
|
name: 'Billing Address Country',
|
||||||
|
column: 'billing_address_country',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddressPostcode: {
|
||||||
|
name: 'Billing Address Postcode',
|
||||||
|
column: 'billing_address_postcode',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddressState: {
|
||||||
|
name: 'Billing Address State',
|
||||||
|
column: 'billing_address_state',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
billingAddressPhone: {
|
||||||
|
name: 'Billing Address Phone',
|
||||||
|
column: 'billing_address_phone',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
// Shipping Address
|
||||||
|
shippingAddress1: {
|
||||||
|
name: 'Shipping Address 1',
|
||||||
|
column: 'shipping_address1',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddress2: {
|
||||||
|
name: 'Shipping Address 2',
|
||||||
|
column: 'shipping_address2',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddressCity: {
|
||||||
|
name: 'Shipping Address City',
|
||||||
|
column: 'shipping_address_city',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddressCountry: {
|
||||||
|
name: 'Shipping Address Country',
|
||||||
|
column: 'shipping_address_country',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddressPostcode: {
|
||||||
|
name: 'Shipping Address Postcode',
|
||||||
|
column: 'shipping_address_postcode',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddressState: {
|
||||||
|
name: 'Shipping Address State',
|
||||||
|
column: 'shipping_address_state',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
shippingAddressPhone: {
|
||||||
|
name: 'Shipping Address Phone',
|
||||||
|
column: 'shipping_address_phone',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
firstName: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
column: 'first_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
column: 'last_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
workPhone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
personalPhone: {
|
||||||
|
name: 'vendor.field.personal_phone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
companyName: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
openingBalance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
openingBalanceAt: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
openingBalanceExchangeRate: {
|
||||||
|
name: 'Opening Balance Ex. Rate',
|
||||||
|
column: 'opening_balance_exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
column: 'note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
column: 'active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
name: 'vendor.field.created_at',
|
|
||||||
column: 'created_at',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'vendorCreditNumber',
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'vendor_credit.field.vendor',
|
name: 'vendor_credit.field.vendor',
|
||||||
@@ -72,4 +80,142 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
type: 'relation',
|
||||||
|
accessor: 'vendor.displayName',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Echange Rate',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditNumber: {
|
||||||
|
name: 'Vendor Credit No.',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Refernece No.',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditDate: {
|
||||||
|
name: 'Vendor Credit Date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
accessor: 'formattedAmount',
|
||||||
|
},
|
||||||
|
creditRemaining: {
|
||||||
|
name: 'Credits Remaining',
|
||||||
|
accessor: 'formattedCreditsRemaining',
|
||||||
|
},
|
||||||
|
refundedAmount: {
|
||||||
|
name: 'Refunded Amount',
|
||||||
|
accessor: 'refundedAmount',
|
||||||
|
},
|
||||||
|
invoicedAmount: {
|
||||||
|
name: 'Invoiced Amount',
|
||||||
|
accessor: 'formattedInvoicedAmount',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
itemName: {
|
||||||
|
name: 'Item Name',
|
||||||
|
accessor: 'item.name',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Item Rate',
|
||||||
|
accessor: 'rateFormatted',
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Item Quantity',
|
||||||
|
accessor: 'quantityFormatted',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Item Description',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Item Amount',
|
||||||
|
accessor: 'totalFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Echange Rate',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditNumber: {
|
||||||
|
name: 'Vendor Credit No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Refernece No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditDate: {
|
||||||
|
name: 'Vendor Credit Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item Name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,34 @@ export default {
|
|||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
},
|
},
|
||||||
|
columns: {
|
||||||
|
date: {
|
||||||
|
name: 'warehouse_transfer.field.date',
|
||||||
|
type: 'date',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
transaction_number: {
|
||||||
|
name: 'warehouse_transfer.field.transaction_number',
|
||||||
|
type: 'text',
|
||||||
|
exportable: true,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'warehouse_transfer.field.status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'draft', label: 'Draft' },
|
||||||
|
{ key: 'in-transit', label: 'In Transit' },
|
||||||
|
{ key: 'transferred', label: 'Transferred' },
|
||||||
|
],
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'warehouse_transfer.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
fields: {
|
fields: {
|
||||||
date: {
|
date: {
|
||||||
name: 'warehouse_transfer.field.date',
|
name: 'warehouse_transfer.field.date',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'reflect-metadata'; // We need this in order to use @Decorators
|
import 'reflect-metadata'; // We need this in order to use @Decorators
|
||||||
|
import 'newrelic';
|
||||||
import './before';
|
import './before';
|
||||||
import '@/config';
|
import '@/config';
|
||||||
|
|
||||||
|
|||||||
31
packages/server/src/services/Accounts/AccountsExportable.ts
Normal file
31
packages/server/src/services/Accounts/AccountsExportable.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { AccountsApplication } from './AccountsApplication';
|
||||||
|
import { Exportable } from '../Export/Exportable';
|
||||||
|
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||||
|
|
||||||
|
@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: 12000,
|
||||||
|
page: 1,
|
||||||
|
} as IAccountsFilter;
|
||||||
|
|
||||||
|
return this.accountsApplication
|
||||||
|
.getAccounts(tenantId, parsedQuery)
|
||||||
|
.then((output) => output.accounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,9 +97,11 @@ export class CommandAccountValidators {
|
|||||||
query.whereNot('id', notAccountId);
|
query.whereNot('id', notAccountId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account.length > 0) {
|
if (account.length > 0) {
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_CODE_NOT_UNIQUE);
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
||||||
|
'Account code is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +126,10 @@ export class CommandAccountValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundAccount) {
|
if (foundAccount) {
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_NAME_NOT_UNIQUE);
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
||||||
|
'Account name is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Service, Inject, Container } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import {
|
import {
|
||||||
IRegisterDTO,
|
IRegisterDTO,
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
@@ -9,6 +9,9 @@ import { AuthSigninService } from './AuthSignin';
|
|||||||
import { AuthSignupService } from './AuthSignup';
|
import { AuthSignupService } from './AuthSignup';
|
||||||
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
||||||
import { GetAuthMeta } from './GetAuthMeta';
|
import { GetAuthMeta } from './GetAuthMeta';
|
||||||
|
import { AuthSignupConfirmService } from './AuthSignupConfirm';
|
||||||
|
import { SystemUser } from '@/system/models';
|
||||||
|
import { AuthSignupConfirmResend } from './AuthSignupResend';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AuthenticationApplication {
|
export default class AuthenticationApplication {
|
||||||
@@ -18,6 +21,12 @@ export default class AuthenticationApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private authSignupService: AuthSignupService;
|
private authSignupService: AuthSignupService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private authSignupConfirmService: AuthSignupConfirmService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private authSignUpConfirmResendService: AuthSignupConfirmResend;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private authResetPasswordService: AuthSendResetPassword;
|
private authResetPasswordService: AuthSendResetPassword;
|
||||||
|
|
||||||
@@ -44,6 +53,28 @@ export default class AuthenticationApplication {
|
|||||||
return this.authSignupService.signUp(signupDTO);
|
return this.authSignupService.signUp(signupDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verfying the provided user's email after signin-up.
|
||||||
|
* @param {string} email
|
||||||
|
* @param {string} token
|
||||||
|
* @returns {Promise<SystemUser>}
|
||||||
|
*/
|
||||||
|
public async signUpConfirm(
|
||||||
|
email: string,
|
||||||
|
token: string
|
||||||
|
): Promise<SystemUser> {
|
||||||
|
return this.authSignupConfirmService.signUpConfirm(email, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the confirmation email of the given system user.
|
||||||
|
* @param {number} userId - System user id.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async signUpConfirmResend(userId: number) {
|
||||||
|
return this.authSignUpConfirmResendService.signUpConfirmResend(userId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates and retrieve password reset token for the given user email.
|
* Generates and retrieve password reset token for the given user email.
|
||||||
* @param {string} email
|
* @param {string} email
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { isEmpty, omit } from 'lodash';
|
import { defaultTo, isEmpty, omit } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import crypto from 'crypto';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import {
|
||||||
IAuthSignedUpEventPayload,
|
IAuthSignedUpEventPayload,
|
||||||
@@ -42,6 +43,13 @@ export class AuthSignupService {
|
|||||||
|
|
||||||
const hashedPassword = await hashPassword(signupDTO.password);
|
const hashedPassword = await hashPassword(signupDTO.password);
|
||||||
|
|
||||||
|
const verifyTokenCrypto = crypto.randomBytes(64).toString('hex');
|
||||||
|
const verifiedEnabed = defaultTo(config.signupConfirmation.enabled, false);
|
||||||
|
const verifyToken = verifiedEnabed ? verifyTokenCrypto : '';
|
||||||
|
const verified = !verifiedEnabed;
|
||||||
|
|
||||||
|
const inviteAcceptedAt = moment().format('YYYY-MM-DD');
|
||||||
|
|
||||||
// Triggers signin up event.
|
// Triggers signin up event.
|
||||||
await this.eventPublisher.emitAsync(events.auth.signingUp, {
|
await this.eventPublisher.emitAsync(events.auth.signingUp, {
|
||||||
signupDTO,
|
signupDTO,
|
||||||
@@ -50,10 +58,12 @@ export class AuthSignupService {
|
|||||||
const tenant = await this.tenantsManager.createTenant();
|
const tenant = await this.tenantsManager.createTenant();
|
||||||
const registeredUser = await systemUserRepository.create({
|
const registeredUser = await systemUserRepository.create({
|
||||||
...omit(signupDTO, 'country'),
|
...omit(signupDTO, 'country'),
|
||||||
|
verifyToken,
|
||||||
|
verified,
|
||||||
active: true,
|
active: true,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
tenantId: tenant.id,
|
tenantId: tenant.id,
|
||||||
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
|
inviteAcceptedAt,
|
||||||
});
|
});
|
||||||
// Triggers signed up event.
|
// Triggers signed up event.
|
||||||
await this.eventPublisher.emitAsync(events.auth.signUp, {
|
await this.eventPublisher.emitAsync(events.auth.signUp, {
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { SystemUser } from '@/system/models';
|
||||||
|
import { ERRORS } from './_constants';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
IAuthSignUpVerifiedEventPayload,
|
||||||
|
IAuthSignUpVerifingEventPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AuthSignupConfirmService {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the provided user's email after signing-up.
|
||||||
|
* @throws {ServiceErrors}
|
||||||
|
* @param {IRegisterDTO} signupDTO
|
||||||
|
* @returns {Promise<ISystemUser>}
|
||||||
|
*/
|
||||||
|
public async signUpConfirm(
|
||||||
|
email: string,
|
||||||
|
verifyToken: string
|
||||||
|
): Promise<SystemUser> {
|
||||||
|
const foundUser = await SystemUser.query().findOne({ email, verifyToken });
|
||||||
|
|
||||||
|
if (!foundUser) {
|
||||||
|
throw new ServiceError(ERRORS.SIGNUP_CONFIRM_TOKEN_INVALID);
|
||||||
|
}
|
||||||
|
const userId = foundUser.id;
|
||||||
|
|
||||||
|
// Triggers `signUpConfirming` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.signUpConfirming, {
|
||||||
|
email,
|
||||||
|
verifyToken,
|
||||||
|
userId,
|
||||||
|
} as IAuthSignUpVerifingEventPayload);
|
||||||
|
|
||||||
|
const updatedUser = await SystemUser.query().patchAndFetchById(
|
||||||
|
foundUser.id,
|
||||||
|
{
|
||||||
|
verified: true,
|
||||||
|
verifyToken: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Triggers `signUpConfirmed` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.signUpConfirmed, {
|
||||||
|
email,
|
||||||
|
verifyToken,
|
||||||
|
userId,
|
||||||
|
} as IAuthSignUpVerifiedEventPayload);
|
||||||
|
|
||||||
|
return updatedUser as SystemUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { SystemUser } from '@/system/models';
|
||||||
|
import { ERRORS } from './_constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AuthSignupConfirmResend {
|
||||||
|
@Inject('agenda')
|
||||||
|
private agenda: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the email confirmation of the given user.
|
||||||
|
* @param {number} userId - User ID.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async signUpConfirmResend(userId: number) {
|
||||||
|
const user = await SystemUser.query().findById(userId).throwIfNotFound();
|
||||||
|
|
||||||
|
// Throw error if the user is already verified.
|
||||||
|
if (user.verified) {
|
||||||
|
throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED);
|
||||||
|
}
|
||||||
|
// Throw error if the verification token is not exist.
|
||||||
|
if (!user.verifyToken) {
|
||||||
|
throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED);
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
email: user.email,
|
||||||
|
token: user.verifyToken,
|
||||||
|
fullName: user.firstName,
|
||||||
|
};
|
||||||
|
await this.agenda.now('send-signup-verify-mail', payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,33 @@ export default class AuthenticationMailMesssages {
|
|||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends signup verification mail.
|
||||||
|
* @param {string} email - Email address
|
||||||
|
* @param {string} fullName - User name.
|
||||||
|
* @param {string} token - Verification token.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async sendSignupVerificationMail(
|
||||||
|
email: string,
|
||||||
|
fullName: string,
|
||||||
|
token: string
|
||||||
|
) {
|
||||||
|
const verifyUrl = `${config.baseURL}/auth/email_confirmation?token=${token}&email=${email}`;
|
||||||
|
|
||||||
|
await new Mail()
|
||||||
|
.setSubject('Bigcapital - Verify your email')
|
||||||
|
.setView('mail/SignupVerifyEmail.html')
|
||||||
|
.setTo(email)
|
||||||
|
.setAttachments([
|
||||||
|
{
|
||||||
|
filename: 'bigcapital.png',
|
||||||
|
path: `${global.__views_dir}/images/bigcapital.png`,
|
||||||
|
cid: 'bigcapital_logo',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.setData({ verifyUrl, fullName })
|
||||||
|
.send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ export const ERRORS = {
|
|||||||
EMAIL_EXISTS: 'EMAIL_EXISTS',
|
EMAIL_EXISTS: 'EMAIL_EXISTS',
|
||||||
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
||||||
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
|
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
|
||||||
|
SIGNUP_CONFIRM_TOKEN_INVALID: 'SIGNUP_CONFIRM_TOKEN_INVALID',
|
||||||
|
USER_ALREADY_VERIFIED: 'USER_ALREADY_VERIFIED',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { IAuthSignedUpEventPayload } from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { Inject } from 'typedi';
|
||||||
|
|
||||||
|
export class SendVerfiyMailOnSignUp {
|
||||||
|
@Inject('agenda')
|
||||||
|
private agenda: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches events with handles.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(events.auth.signUp, this.handleSendVerifyMailOnSignup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ITaxRateEditedPayload} payload -
|
||||||
|
*/
|
||||||
|
private handleSendVerifyMailOnSignup = async ({
|
||||||
|
user,
|
||||||
|
}: IAuthSignedUpEventPayload) => {
|
||||||
|
const payload = {
|
||||||
|
email: user.email,
|
||||||
|
token: user.verifyToken,
|
||||||
|
fullName: user.firstName,
|
||||||
|
};
|
||||||
|
await this.agenda.now('send-signup-verify-mail', payload);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Container } from 'typedi';
|
||||||
|
import AuthenticationMailMesssages from '@/services/Authentication/AuthenticationMailMessages';
|
||||||
|
|
||||||
|
export class SendVerifyMailJob {
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {Agenda} agenda
|
||||||
|
*/
|
||||||
|
constructor(agenda) {
|
||||||
|
agenda.define(
|
||||||
|
'send-signup-verify-mail',
|
||||||
|
{ priority: 'high' },
|
||||||
|
this.handler.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle send welcome mail job.
|
||||||
|
* @param {Job} job
|
||||||
|
* @param {Function} done
|
||||||
|
*/
|
||||||
|
public async handler(job, done: Function): Promise<void> {
|
||||||
|
const { data } = job.attrs;
|
||||||
|
const { email, fullName, token } = data;
|
||||||
|
const authService = Container.get(AuthenticationMailMesssages);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authService.sendSignupVerificationMail(email, fullName, token);
|
||||||
|
done();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
done(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { ERRORS } from './constants';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class CashflowDeleteAccount {
|
export default class CashflowDeleteAccount {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the account has no associated cashflow transactions.
|
* Validate the account has no associated cashflow transactions.
|
||||||
|
|||||||
@@ -79,27 +79,25 @@ export interface ICashflowTransactionTypeMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BankTransactionsSampleData = [
|
export const BankTransactionsSampleData = [
|
||||||
[
|
{
|
||||||
{
|
Amount: '6,410.19',
|
||||||
Amount: '6,410.19',
|
Date: '2024-03-26',
|
||||||
Date: '2024-03-26',
|
Payee: 'MacGyver and Sons',
|
||||||
Payee: 'MacGyver and Sons',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Commodi quo labore.',
|
||||||
Description: 'Commodi quo labore.',
|
},
|
||||||
},
|
{
|
||||||
{
|
Amount: '8,914.17',
|
||||||
Amount: '8,914.17',
|
Date: '2024-01-05',
|
||||||
Date: '2024-01-05',
|
Payee: 'Eichmann - Bergnaum',
|
||||||
Payee: 'Eichmann - Bergnaum',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Quia enim et.',
|
||||||
Description: 'Quia enim et.',
|
},
|
||||||
},
|
{
|
||||||
{
|
Amount: '6,200.88',
|
||||||
Amount: '6,200.88',
|
Date: '2024-02-17',
|
||||||
Date: '2024-02-17',
|
Payee: 'Luettgen, Mraz and Legros',
|
||||||
Payee: 'Luettgen, Mraz and Legros',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Occaecati consequuntur cum impedit illo.',
|
||||||
Description: 'Occaecati consequuntur cum impedit illo.',
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -50,10 +50,7 @@ export class CustomersApplication {
|
|||||||
* @param {ISystemUser} authorizedUser
|
* @param {ISystemUser} authorizedUser
|
||||||
* @returns {Promise<ICustomer>}
|
* @returns {Promise<ICustomer>}
|
||||||
*/
|
*/
|
||||||
public createCustomer = (
|
public createCustomer = (tenantId: number, customerDTO: ICustomerNewDTO) => {
|
||||||
tenantId: number,
|
|
||||||
customerDTO: ICustomerNewDTO,
|
|
||||||
) => {
|
|
||||||
return this.createCustomerService.createCustomer(tenantId, customerDTO);
|
return this.createCustomerService.createCustomer(tenantId, customerDTO);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { IItemsFilter } from '@/interfaces';
|
||||||
|
import { CustomersApplication } from './CustomersApplication';
|
||||||
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CustomersExportable extends Exportable {
|
||||||
|
@Inject()
|
||||||
|
private customersApplication: CustomersApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts data to exportable sheet.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public exportable(tenantId: number, query: IItemsFilter) {
|
||||||
|
const parsedQuery = {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
page: 1,
|
||||||
|
...query,
|
||||||
|
pageSize: 12,
|
||||||
|
} as IItemsFilter;
|
||||||
|
|
||||||
|
return this.customersApplication
|
||||||
|
.getCustomers(tenantId, parsedQuery)
|
||||||
|
.then((output) => output.customers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
IVendorEditDTO,
|
IVendorEditDTO,
|
||||||
@@ -42,13 +43,9 @@ export class VendorsApplication {
|
|||||||
public createVendor = (
|
public createVendor = (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
vendorDTO: IVendorNewDTO,
|
vendorDTO: IVendorNewDTO,
|
||||||
authorizedUser: ISystemUser
|
trx?: Knex.Transaction
|
||||||
) => {
|
) => {
|
||||||
return this.createVendorService.createVendor(
|
return this.createVendorService.createVendor(tenantId, vendorDTO, trx);
|
||||||
tenantId,
|
|
||||||
vendorDTO,
|
|
||||||
authorizedUser
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { IItemsFilter } from '@/interfaces';
|
||||||
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
|
import { VendorsApplication } from './VendorsApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class VendorsExportable extends Exportable {
|
||||||
|
@Inject()
|
||||||
|
private vendorsApplication: VendorsApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts data to exportable sheet.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public exportable(tenantId: number, query: IItemsFilter) {
|
||||||
|
const parsedQuery = {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
page: 1,
|
||||||
|
...query,
|
||||||
|
pageSize: 12,
|
||||||
|
} as IItemsFilter;
|
||||||
|
|
||||||
|
return this.vendorsApplication
|
||||||
|
.getVendors(tenantId, parsedQuery)
|
||||||
|
.then((output) => output.vendors);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
||||||
"Billing Address 1": "862 Jessika Well",
|
"Billing Address 1": "862 Jessika Well",
|
||||||
"Billing Address 2": "1091 Dorthy Mount",
|
"Billing Address 2": "1091 Dorthy Mount",
|
||||||
@@ -42,7 +42,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
||||||
"Billing Address 1": "532 Simonis Spring",
|
"Billing Address 1": "532 Simonis Spring",
|
||||||
"Billing Address 2": "3122 Nicolas Inlet",
|
"Billing Address 2": "3122 Nicolas Inlet",
|
||||||
@@ -72,7 +72,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
||||||
"Billing Address 1": "214 Sauer Villages",
|
"Billing Address 1": "214 Sauer Villages",
|
||||||
"Billing Address 2": "30687 Kacey Square",
|
"Billing Address 2": "30687 Kacey Square",
|
||||||
@@ -102,7 +102,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Quis cumque molestias rerum.",
|
"Note": "Quis cumque molestias rerum.",
|
||||||
"Billing Address 1": "22590 Cathy Harbor",
|
"Billing Address 1": "22590 Cathy Harbor",
|
||||||
"Billing Address 2": "24493 Brycen Brooks",
|
"Billing Address 2": "24493 Brycen Brooks",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
ICreditNoteCreatedPayload,
|
ICreditNoteCreatedPayload,
|
||||||
ICreditNoteCreatingPayload,
|
ICreditNoteCreatingPayload,
|
||||||
ICreditNoteNewDTO,
|
ICreditNoteNewDTO,
|
||||||
ISystemUser,
|
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
@@ -34,7 +33,7 @@ export default class CreateCreditNote extends BaseCreditNotes {
|
|||||||
public newCreditNote = async (
|
public newCreditNote = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
creditNoteDTO: ICreditNoteNewDTO,
|
creditNoteDTO: ICreditNoteNewDTO,
|
||||||
authorizedUser: ISystemUser
|
trx?: Knex.Transaction
|
||||||
) => {
|
) => {
|
||||||
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -66,28 +65,32 @@ export default class CreateCreditNote extends BaseCreditNotes {
|
|||||||
customer.currencyCode
|
customer.currencyCode
|
||||||
);
|
);
|
||||||
// Creates a new credit card transactions under unit-of-work envirement.
|
// Creates a new credit card transactions under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onCreditNoteCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onCreditNoteCreating` event.
|
||||||
creditNoteDTO,
|
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
||||||
trx,
|
tenantId,
|
||||||
} as ICreditNoteCreatingPayload);
|
creditNoteDTO,
|
||||||
|
trx,
|
||||||
|
} as ICreditNoteCreatingPayload);
|
||||||
|
|
||||||
// Upsert the credit note graph.
|
// Upsert the credit note graph.
|
||||||
const creditNote = await CreditNote.query(trx).upsertGraph({
|
const creditNote = await CreditNote.query(trx).upsertGraph({
|
||||||
...creditNoteModel,
|
...creditNoteModel,
|
||||||
});
|
});
|
||||||
// Triggers `onCreditNoteCreated` event.
|
// Triggers `onCreditNoteCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
|
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNoteDTO,
|
creditNoteDTO,
|
||||||
creditNote,
|
creditNote,
|
||||||
creditNoteId: creditNote.id,
|
creditNoteId: creditNote.id,
|
||||||
trx,
|
trx,
|
||||||
} as ICreditNoteCreatedPayload);
|
} as ICreditNoteCreatedPayload);
|
||||||
|
|
||||||
return creditNote;
|
return creditNote;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ICreditNotesQueryDTO } from '@/interfaces';
|
||||||
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
|
import ListCreditNotes from './ListCreditNotes';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreditNotesExportable extends Exportable {
|
||||||
|
@Inject()
|
||||||
|
private getCreditNotes: ListCreditNotes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts data to exportable sheet.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IVendorCreditsQueryDTO} query -
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
public exportable(tenantId: number, query: ICreditNotesQueryDTO) {
|
||||||
|
const parsedQuery = {
|
||||||
|
sortOrder: 'desc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
...query,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12000,
|
||||||
|
} as ICreditNotesQueryDTO;
|
||||||
|
|
||||||
|
return this.getCreditNotes
|
||||||
|
.getCreditNotesList(tenantId, parsedQuery)
|
||||||
|
.then((output) => output.creditNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { ICreditNoteNewDTO } from '@/interfaces';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import CreateCreditNote from './CreateCreditNote';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreditNotesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createCreditNoteImportable: CreateCreditNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: ICreditNoteNewDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createCreditNoteImportable.newCreditNote(
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ export default class ListCreditNotes extends BaseCreditNotes {
|
|||||||
);
|
);
|
||||||
const { results, pagination } = await CreditNote.query()
|
const { results, pagination } = await CreditNote.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('entries');
|
builder.withGraphFetched('entries.item');
|
||||||
builder.withGraphFetched('customer');
|
builder.withGraphFetched('customer');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ export class CreateExpense {
|
|||||||
public newExpense = async (
|
public newExpense = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
expenseDTO: IExpenseCreateDTO,
|
expenseDTO: IExpenseCreateDTO,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<IExpense> => {
|
): Promise<IExpense> => {
|
||||||
const { Expense } = await this.tenancy.models(tenantId);
|
const { Expense } = await this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -103,28 +104,32 @@ export class CreateExpense {
|
|||||||
);
|
);
|
||||||
// Writes the expense transaction with associated transactions under
|
// Writes the expense transaction with associated transactions under
|
||||||
// unit-of-work envirement.
|
// unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onExpenseCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
trx,
|
// Triggers `onExpenseCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
||||||
expenseDTO,
|
trx,
|
||||||
} as IExpenseCreatingPayload);
|
tenantId,
|
||||||
|
expenseDTO,
|
||||||
|
} as IExpenseCreatingPayload);
|
||||||
|
|
||||||
// Creates a new expense transaction graph.
|
// Creates a new expense transaction graph.
|
||||||
const expense: IExpense = await Expense.query(trx).upsertGraph(
|
const expense: IExpense = await Expense.query(trx).upsertGraph(
|
||||||
expenseObj
|
expenseObj
|
||||||
);
|
);
|
||||||
// Triggers `onExpenseCreated` event.
|
// Triggers `onExpenseCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.expenses.onCreated, {
|
await this.eventPublisher.emitAsync(events.expenses.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
expenseId: expense.id,
|
expenseId: expense.id,
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
expense,
|
expense,
|
||||||
trx,
|
trx,
|
||||||
} as IExpenseCreatedPayload);
|
} as IExpenseCreatedPayload);
|
||||||
|
|
||||||
return expense;
|
return expense;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
29
packages/server/src/services/Expenses/ExpensesExportable.ts
Normal file
29
packages/server/src/services/Expenses/ExpensesExportable.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Exportable } from '../Export/Exportable';
|
||||||
|
import { IExpensesFilter } from '@/interfaces';
|
||||||
|
import { ExpensesApplication } from './ExpensesApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExpensesExportable extends Exportable {
|
||||||
|
@Inject()
|
||||||
|
private expensesApplication: ExpensesApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts data to exportable sheet.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public exportable(tenantId: number, query: IExpensesFilter) {
|
||||||
|
const parsedQuery = {
|
||||||
|
sortOrder: 'desc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
...query,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12000,
|
||||||
|
} as IExpensesFilter;
|
||||||
|
|
||||||
|
return this.expensesApplication
|
||||||
|
.getExpenses(tenantId, parsedQuery)
|
||||||
|
.then((output) => output.expenses);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
packages/server/src/services/Expenses/ExpensesImportable.ts
Normal file
46
packages/server/src/services/Expenses/ExpensesImportable.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IExpenseCreateDTO } from '@/interfaces';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import { CreateExpense } from './CRUD/CreateExpense';
|
||||||
|
import { ExpensesSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExpensesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createExpenseService: CreateExpense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: IExpenseCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createExpenseService.newExpense(
|
||||||
|
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 ExpensesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,3 +36,43 @@ export const ERRORS = {
|
|||||||
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
|
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
|
||||||
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
|
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ExpensesSampleData = [
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-01',
|
||||||
|
'Reference No.': 'REF-1',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Vel et dolorem architecto veniam.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description': 'Voluptates voluptas corporis vel.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-02',
|
||||||
|
'Reference No.': 'REF-2',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Id est molestias.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description': 'Eos voluptatem cumque et voluptate reiciendis.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-03',
|
||||||
|
'Reference No.': 'REF-3',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Quam cupiditate at nihil dicta dignissimos non fugit illo.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description':
|
||||||
|
'Hic alias rerum sed commodi dolores sint animi perferendis.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
17
packages/server/src/services/Export/ExportApplication.ts
Normal file
17
packages/server/src/services/Export/ExportApplication.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ExportResourceService } from './ExportService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExportApplication {
|
||||||
|
@Inject()
|
||||||
|
private exportResource: ExportResourceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the given resource to csv, xlsx or pdf format.
|
||||||
|
* @param {string} reosurce
|
||||||
|
* @param {string} format
|
||||||
|
*/
|
||||||
|
public export(tenantId: number, resource: string, format: string) {
|
||||||
|
return this.exportResource.export(tenantId, resource, format);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
packages/server/src/services/Export/ExportRegistery.ts
Normal file
49
packages/server/src/services/Export/ExportRegistery.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { camelCase, upperFirst } from 'lodash';
|
||||||
|
import { Exportable } from './Exportable';
|
||||||
|
|
||||||
|
export class ExportableRegistry {
|
||||||
|
private static instance: ExportableRegistry;
|
||||||
|
private exportables: Record<string, Exportable>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.exportables = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets singleton instance of registry.
|
||||||
|
* @returns {ExportableRegistry}
|
||||||
|
*/
|
||||||
|
public static getInstance(): ExportableRegistry {
|
||||||
|
if (!ExportableRegistry.instance) {
|
||||||
|
ExportableRegistry.instance = new ExportableRegistry();
|
||||||
|
}
|
||||||
|
return ExportableRegistry.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the given importable service.
|
||||||
|
* @param {string} resource
|
||||||
|
* @param {Exportable} importable
|
||||||
|
*/
|
||||||
|
public registerExportable(resource: string, importable: Exportable): void {
|
||||||
|
const _resource = this.sanitizeResourceName(resource);
|
||||||
|
this.exportables[_resource] = importable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the importable service instance of the given resource name.
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {Exportable}
|
||||||
|
*/
|
||||||
|
public getExportable(name: string): Exportable {
|
||||||
|
const _name = this.sanitizeResourceName(name);
|
||||||
|
return this.exportables[_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeResourceName(resource: string) {
|
||||||
|
return upperFirst(camelCase(resource));
|
||||||
|
}
|
||||||
|
}
|
||||||
72
packages/server/src/services/Export/ExportResources.ts
Normal file
72
packages/server/src/services/Export/ExportResources.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import Container, { Service } from 'typedi';
|
||||||
|
import { AccountsExportable } from '../Accounts/AccountsExportable';
|
||||||
|
import { ExportableRegistry } from './ExportRegistery';
|
||||||
|
import { ItemsExportable } from '../Items/ItemsExportable';
|
||||||
|
import { CustomersExportable } from '../Contacts/Customers/CustomersExportable';
|
||||||
|
import { VendorsExportable } from '../Contacts/Vendors/VendorsExportable';
|
||||||
|
import { ExpensesExportable } from '../Expenses/ExpensesExportable';
|
||||||
|
import { SaleInvoicesExportable } from '../Sales/Invoices/SaleInvoicesExportable';
|
||||||
|
import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable';
|
||||||
|
import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable';
|
||||||
|
import { BillsExportable } from '../Purchases/Bills/BillsExportable';
|
||||||
|
import { PaymentsReceivedExportable } from '../Sales/PaymentReceives/PaymentsReceivedExportable';
|
||||||
|
import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable';
|
||||||
|
import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable';
|
||||||
|
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
|
||||||
|
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
|
||||||
|
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExportableResources {
|
||||||
|
private static registry: ExportableRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consttuctor method.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importable instances.
|
||||||
|
*/
|
||||||
|
private importables = [
|
||||||
|
{ resource: 'Account', exportable: AccountsExportable },
|
||||||
|
{ resource: 'Item', exportable: ItemsExportable },
|
||||||
|
{ resource: 'ItemCategory', exportable: ItemCategoriesExportable },
|
||||||
|
{ resource: 'Customer', exportable: CustomersExportable },
|
||||||
|
{ resource: 'Vendor', exportable: VendorsExportable },
|
||||||
|
{ resource: 'Expense', exportable: ExpensesExportable },
|
||||||
|
{ resource: 'SaleInvoice', exportable: SaleInvoicesExportable },
|
||||||
|
{ resource: 'SaleEstimate', exportable: SaleEstimatesExportable },
|
||||||
|
{ resource: 'SaleReceipt', exportable: SaleReceiptsExportable },
|
||||||
|
{ resource: 'Bill', exportable: BillsExportable },
|
||||||
|
{ resource: 'PaymentReceive', exportable: PaymentsReceivedExportable },
|
||||||
|
{ resource: 'BillPayment', exportable: BillPaymentExportable },
|
||||||
|
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
|
||||||
|
{ resource: 'CreditNote', exportable: CreditNotesExportable },
|
||||||
|
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public get registry() {
|
||||||
|
return ExportableResources.registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boots all the registered importables.
|
||||||
|
*/
|
||||||
|
public boot() {
|
||||||
|
if (!ExportableResources.registry) {
|
||||||
|
const instance = ExportableRegistry.getInstance();
|
||||||
|
|
||||||
|
this.importables.forEach((importable) => {
|
||||||
|
const importableInstance = Container.get(importable.exportable);
|
||||||
|
instance.registerExportable(importable.resource, importableInstance);
|
||||||
|
});
|
||||||
|
ExportableResources.registry = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
161
packages/server/src/services/Export/ExportService.ts
Normal file
161
packages/server/src/services/Export/ExportService.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import xlsx from 'xlsx';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { sanitizeResourceName } from '../Import/_utils';
|
||||||
|
import ResourceService from '../Resource/ResourceService';
|
||||||
|
import { ExportableResources } from './ExportResources';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { Errors } from './common';
|
||||||
|
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
|
||||||
|
import { flatDataCollections, getDataAccessor } from './utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExportResourceService {
|
||||||
|
@Inject()
|
||||||
|
private resourceService: ResourceService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private exportableResources: ExportableResources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the given resource data through csv, xlsx or pdf.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {string} resourceName - Resource name.
|
||||||
|
* @param {string} format - File format.
|
||||||
|
*/
|
||||||
|
public async export(tenantId: number, resourceName: string, format: string = 'csv') {
|
||||||
|
const resource = sanitizeResourceName(resourceName);
|
||||||
|
const resourceMeta = this.getResourceMeta(tenantId, resource);
|
||||||
|
|
||||||
|
this.validateResourceMeta(resourceMeta);
|
||||||
|
|
||||||
|
const data = await this.getExportableData(tenantId, resource);
|
||||||
|
const transformed = this.transformExportedData(tenantId, resource, data);
|
||||||
|
const exportableColumns = this.getExportableColumns(resourceMeta);
|
||||||
|
const workbook = this.createWorkbook(transformed, exportableColumns);
|
||||||
|
|
||||||
|
return this.exportWorkbook(workbook, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves metadata for a specific resource.
|
||||||
|
* @param {number} tenantId - The tenant identifier.
|
||||||
|
* @param {string} resource - The name of the resource.
|
||||||
|
* @returns The metadata of the resource.
|
||||||
|
*/
|
||||||
|
private getResourceMeta(tenantId: number, resource: string) {
|
||||||
|
return this.resourceService.getResourceMeta(tenantId, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if the resource metadata is exportable.
|
||||||
|
* @param {any} resourceMeta - The metadata of the resource.
|
||||||
|
* @throws {ServiceError} If the resource is not exportable or lacks columns.
|
||||||
|
*/
|
||||||
|
private validateResourceMeta(resourceMeta: any) {
|
||||||
|
if (!resourceMeta.exportable || !resourceMeta.columns) {
|
||||||
|
throw new ServiceError(Errors.RESOURCE_NOT_EXPORTABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the exported data based on the resource metadata.
|
||||||
|
* If the resource metadata specifies a flattening attribute (`exportFlattenOn`),
|
||||||
|
* the data will be flattened based on this attribute using the `flatDataCollections` utility function.
|
||||||
|
*
|
||||||
|
* @param {number} tenantId - The tenant identifier.
|
||||||
|
* @param {string} resource - The name of the resource.
|
||||||
|
* @param {Array<Record<string, any>>} data - The original data to be transformed.
|
||||||
|
* @returns {Array<Record<string, any>>} - The transformed data.
|
||||||
|
*/
|
||||||
|
private transformExportedData(
|
||||||
|
tenantId: number,
|
||||||
|
resource: string,
|
||||||
|
data: Array<Record<string, any>>
|
||||||
|
): Array<Record<string, any>> {
|
||||||
|
const resourceMeta = this.getResourceMeta(tenantId, resource);
|
||||||
|
|
||||||
|
return R.when<Array<Record<string, any>>, Array<Record<string, any>>>(
|
||||||
|
R.always(Boolean(resourceMeta.exportFlattenOn)),
|
||||||
|
(data) => flatDataCollections(data, resourceMeta.exportFlattenOn),
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fetches exportable data for a given resource.
|
||||||
|
* @param {number} tenantId - The tenant identifier.
|
||||||
|
* @param {string} resource - The name of the resource.
|
||||||
|
* @returns A promise that resolves to the exportable data.
|
||||||
|
*/
|
||||||
|
private async getExportableData(tenantId: number, resource: string) {
|
||||||
|
const exportable =
|
||||||
|
this.exportableResources.registry.getExportable(resource);
|
||||||
|
return exportable.exportable(tenantId, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts columns that are marked as exportable from the resource metadata.
|
||||||
|
* @param {IModelMeta} resourceMeta - The metadata of the resource.
|
||||||
|
* @returns An array of exportable columns.
|
||||||
|
*/
|
||||||
|
private getExportableColumns(resourceMeta: IModelMeta) {
|
||||||
|
const processColumns = (
|
||||||
|
columns: { [key: string]: IModelMetaColumn },
|
||||||
|
parent = ''
|
||||||
|
) => {
|
||||||
|
return Object.entries(columns)
|
||||||
|
.filter(([_, value]) => value.exportable !== false)
|
||||||
|
.flatMap(([key, value]) => {
|
||||||
|
if (value.type === 'collection' && value.collectionOf === 'object') {
|
||||||
|
return processColumns(value.columns, key);
|
||||||
|
} else {
|
||||||
|
const group = parent;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: value.name,
|
||||||
|
type: value.type || 'text',
|
||||||
|
accessor: value.accessor || key,
|
||||||
|
group,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return processColumns(resourceMeta.columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a workbook from the provided data and columns.
|
||||||
|
* @param {any[]} data - The data to be included in the workbook.
|
||||||
|
* @param {any[]} exportableColumns - The columns to be included in the workbook.
|
||||||
|
* @returns The created workbook.
|
||||||
|
*/
|
||||||
|
private createWorkbook(data: any[], exportableColumns: any[]) {
|
||||||
|
const workbook = xlsx.utils.book_new();
|
||||||
|
const worksheetData = data.map((item) =>
|
||||||
|
exportableColumns.map((col) => get(item, getDataAccessor(col)))
|
||||||
|
);
|
||||||
|
|
||||||
|
worksheetData.unshift(exportableColumns.map((col) => col.name));
|
||||||
|
|
||||||
|
const worksheet = xlsx.utils.aoa_to_sheet(worksheetData);
|
||||||
|
xlsx.utils.book_append_sheet(workbook, worksheet, 'Exported Data');
|
||||||
|
|
||||||
|
return workbook;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the workbook in the specified format.
|
||||||
|
* @param {any} workbook - The workbook to be exported.
|
||||||
|
* @param {string} format - The format to export the workbook in.
|
||||||
|
* @returns The exported workbook data.
|
||||||
|
*/
|
||||||
|
private exportWorkbook(workbook: any, format: string) {
|
||||||
|
if (format.toLowerCase() === 'csv') {
|
||||||
|
return xlsx.write(workbook, { type: 'buffer', bookType: 'csv' });
|
||||||
|
} else if (format.toLowerCase() === 'xlsx') {
|
||||||
|
return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/server/src/services/Export/Exportable.ts
Normal file
22
packages/server/src/services/Export/Exportable.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export class Exportable {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async exportable(
|
||||||
|
tenantId: number,
|
||||||
|
query: Record<string, any>
|
||||||
|
): Promise<Array<Record<string, any>>> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public transform(data: Record<string, any>) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/server/src/services/Export/common.ts
Normal file
3
packages/server/src/services/Export/common.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export enum Errors {
|
||||||
|
RESOURCE_NOT_EXPORTABLE = 'RESOURCE_NOT_EXPORTABLE',
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user