mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
Compare commits
33 Commits
big-163-us
...
v0.16.14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ada57a2b4 | ||
|
|
e380c598d3 | ||
|
|
370a8a4b91 | ||
|
|
c9ba9500cc | ||
|
|
23d27cafc1 | ||
|
|
92e3d31360 | ||
|
|
8aefa7709c | ||
|
|
3020295841 | ||
|
|
7f31a48755 | ||
|
|
8c0ef61038 | ||
|
|
76bb82f2b4 | ||
|
|
4c0dc276dd | ||
|
|
a69c4b4067 | ||
|
|
d81e544e82 | ||
|
|
6eeda23559 | ||
|
|
cd046cbe27 | ||
|
|
9b7bc1e5b9 | ||
|
|
987341ed29 | ||
|
|
d7cad17f1b | ||
|
|
f6a0476fb4 | ||
|
|
63cdc45295 | ||
|
|
83a5010dc5 | ||
|
|
55aab76c9b | ||
|
|
495941f43a | ||
|
|
00a1e070c6 | ||
|
|
fab71d2b65 | ||
|
|
9504bb5ccd | ||
|
|
7e89966f20 | ||
|
|
8a96c41258 | ||
|
|
4a713980bf | ||
|
|
2a1cbf6ced | ||
|
|
9103b60653 | ||
|
|
7e5c6b6487 |
@@ -123,6 +123,15 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ccantrell72",
|
||||
"name": "Chris Cantrell",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/104120598?v=4",
|
||||
"profile": "http://www.pivoten.com",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
45
.github/workflows/build-deploy-container.yml
vendored
45
.github/workflows/build-deploy-container.yml
vendored
@@ -6,18 +6,13 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp
|
||||
SERVER_IMAGE_NAME: bigcapital/bigcapital-server
|
||||
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
|
||||
SERVER_IMAGE_NAME: bigcapitalhq/server
|
||||
|
||||
jobs:
|
||||
build-publish-webapp:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
name: Build and deploy webapp container
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
@@ -30,9 +25,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -40,27 +32,26 @@ jobs:
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }}
|
||||
images: ${{ env.WEBAPP_IMAGE_NAME }}
|
||||
|
||||
# Builds and push the Docker image.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
context: ./
|
||||
file: ./packages/webapp/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/bigcapitalhq/webapp:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -71,7 +62,7 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-main-${{ env.PLATFORM_PAIR }}
|
||||
name: digests-webapp
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -93,9 +84,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -103,9 +91,8 @@ jobs:
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# Builds and push the Docker image.
|
||||
- name: Build and push Docker image
|
||||
@@ -114,9 +101,9 @@ jobs:
|
||||
with:
|
||||
context: ./
|
||||
file: ./packages/server/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/bigcapitalhq/server:latest
|
||||
tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Export digest
|
||||
@@ -128,13 +115,13 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-main-${{ env.PLATFORM_PAIR }}
|
||||
name: digests-server
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
|
||||
# Send notification to Slack channel.
|
||||
- name: Slack Notification built and published server container successfully.
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
|
||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -2,6 +2,76 @@
|
||||
|
||||
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
|
||||
|
||||
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<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">
|
||||
</a>
|
||||
</p>
|
||||
@@ -27,7 +27,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.bigcapital.ly">Bigcapital Cloud</a>
|
||||
<a href="https://my.bigcapital.app">Bigcapital Cloud</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
@@ -51,7 +51,7 @@ Bigcapital is available open-source under AGPL license. You can host it on your
|
||||
|
||||
### 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
|
||||
|
||||
@@ -74,7 +74,7 @@ You can integrate Bigcapital API with your system to organize your transactions
|
||||
|
||||
# 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.
|
||||
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
|
||||
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
|
||||
@@ -124,6 +124,7 @@ 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/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://snyder.tech"><img src="https://avatars.githubusercontent.com/u/707567?v=4?s=100" width="100px;" alt="Ben Snyder"/><br /><sub><b>Ben Snyder</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=benpsnyder" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -25,12 +25,12 @@ services:
|
||||
|
||||
webapp:
|
||||
container_name: bigcapital-webapp
|
||||
image: ghcr.io/bigcapitalhq/webapp:latest
|
||||
image: bigcapitalhq/webapp:latest
|
||||
restart: on-failure
|
||||
|
||||
server:
|
||||
container_name: bigcapital-server
|
||||
image: ghcr.io/bigcapitalhq/server:latest
|
||||
image: bigcapitalhq/server:latest
|
||||
expose:
|
||||
- '3000'
|
||||
links:
|
||||
@@ -102,6 +102,14 @@ services:
|
||||
- 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:
|
||||
container_name: bigcapital-database-migration
|
||||
build:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ghcr.io/bigcapitalhq/server:latest as build
|
||||
FROM bigcapitalhq/server:latest as build
|
||||
|
||||
ARG DB_HOST= \
|
||||
DB_USER= \
|
||||
@@ -34,7 +34,5 @@ WORKDIR /app/packages/server
|
||||
|
||||
RUN git clone https://github.com/vishnubob/wait-for-it.git
|
||||
|
||||
ADD docker/migration/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
|
||||
@@ -78,6 +78,9 @@ ENV MAIL_HOST=$MAIL_HOST \
|
||||
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||
|
||||
# New Relic config file.
|
||||
ENV NEW_RELIC_NO_CONFIG_FILE=true
|
||||
|
||||
# Create app directory.
|
||||
WORKDIR /app
|
||||
|
||||
@@ -89,8 +92,8 @@ RUN npm install -g pnpm
|
||||
# Copy application dependency manifests to the container image.
|
||||
COPY ./package*.json ./
|
||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY ./lerna.json ./lerna.json
|
||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY ./packages/server/package*.json ./packages/server/
|
||||
|
||||
# Install application dependencies
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"mustache": "^3.0.3",
|
||||
"mysql": "^2.17.1",
|
||||
"mysql2": "^1.6.5",
|
||||
"newrelic": "^11.15.0",
|
||||
"node-cache": "^4.2.1",
|
||||
"nodemailer": "^6.3.0",
|
||||
"nodemon": "^1.19.1",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"account.field.active": "Active",
|
||||
"account.field.currency": "Currency",
|
||||
"account.field.balance": "Balance",
|
||||
"account.field.bank_balance": "Bank Balance",
|
||||
"account.field.parent_account": "Parent Account",
|
||||
"account.field.created_at": "Created at",
|
||||
"item.field.type": "Item Type",
|
||||
@@ -331,7 +332,7 @@
|
||||
"bill_payment.field.reference_no": "Reference No.",
|
||||
"bill_payment.field.description": "Description",
|
||||
"bill_payment.field.exchange_rate": "Exchange Rate",
|
||||
"bill_payment.field.statement": "Statement",
|
||||
"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.",
|
||||
@@ -431,6 +432,7 @@
|
||||
"vendor.field.created_at": "Created at",
|
||||
"vendor.field.balance": "Balance",
|
||||
"vendor.field.status": "Status",
|
||||
"vendor.field.note": "Note",
|
||||
"vendor.field.currency": "Currency",
|
||||
"vendor.field.status.active": "Active",
|
||||
"vendor.field.status.inactive": "Inactive",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ import { TaxRatesController } from './controllers/TaxRates/TaxRates';
|
||||
import { ImportController } from './controllers/Import/ImportController';
|
||||
import { BankingController } from './controllers/Banking/BankingController';
|
||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||
import { ExportController } from './controllers/Export/ExportController';
|
||||
|
||||
export default () => {
|
||||
const app = Router();
|
||||
@@ -141,6 +142,7 @@ export default () => {
|
||||
dashboard.use('/projects', Container.get(ProjectsController).router());
|
||||
dashboard.use('/tax-rates', Container.get(TaxRatesController).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(ProjectTimesController).router());
|
||||
|
||||
@@ -55,7 +55,7 @@ module.exports = {
|
||||
mail: {
|
||||
host: process.env.MAIL_HOST,
|
||||
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,
|
||||
password: process.env.MAIL_PASSWORD,
|
||||
from: process.env.MAIL_FROM_ADDRESS,
|
||||
|
||||
@@ -126,13 +126,16 @@ export interface IModelMeta {
|
||||
defaultFilterField: string;
|
||||
defaultSort: IModelMetaDefaultSort;
|
||||
|
||||
importable?: boolean;
|
||||
exportable?: boolean;
|
||||
exportFlattenOn?: string;
|
||||
|
||||
importable?: boolean;
|
||||
importAggregator?: string;
|
||||
importAggregateOn?: string;
|
||||
importAggregateBy?: string;
|
||||
|
||||
fields: { [key: string]: IModelMetaField };
|
||||
columns: { [key: string]: IModelMetaColumn };
|
||||
}
|
||||
|
||||
// ----
|
||||
@@ -161,3 +164,22 @@ export type IModelMetaField2 = IModelMetaFieldCommon2 &
|
||||
| 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);
|
||||
|
||||
@@ -7,6 +7,7 @@ export default {
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
fields: {
|
||||
name: {
|
||||
name: 'account.field.name',
|
||||
@@ -85,6 +86,55 @@ export default {
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
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',
|
||||
|
||||
@@ -5,6 +5,8 @@ export default {
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
exportable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'billNumber',
|
||||
@@ -80,6 +82,84 @@ export default {
|
||||
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.',
|
||||
@@ -132,7 +212,7 @@ export default {
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the item name or code."
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
|
||||
@@ -4,6 +4,7 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
exportable: true,
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
@@ -67,6 +68,46 @@ export default {
|
||||
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',
|
||||
@@ -84,7 +125,7 @@ export default {
|
||||
name: 'bill_payment.field.payment_number',
|
||||
fieldType: 'text',
|
||||
unique: true,
|
||||
importHint: "The payment number should be unique."
|
||||
importHint: 'The payment number should be unique.',
|
||||
},
|
||||
paymentAccountId: {
|
||||
name: 'bill_payment.field.payment_account',
|
||||
@@ -92,14 +133,14 @@ export default {
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the account name or code."
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'bill_payment.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
statement: {
|
||||
name: 'bill_payment.field.statement',
|
||||
name: 'bill_payment.field.note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference: {
|
||||
@@ -120,7 +161,7 @@ export default {
|
||||
relationModel: 'Bill',
|
||||
relationImportMatch: 'billNumber',
|
||||
required: true,
|
||||
importHint: "Matches the bill number."
|
||||
importHint: 'Matches the bill number.',
|
||||
},
|
||||
paymentAmount: {
|
||||
name: 'bill_payment.field.entries.payment_amount',
|
||||
|
||||
@@ -12,10 +12,14 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'creditNoteNumber',
|
||||
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'credit_note.field.customer',
|
||||
@@ -81,6 +85,67 @@ export default {
|
||||
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',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default {
|
||||
importable: true,
|
||||
exportable: true,
|
||||
defaultFilterField: 'displayName',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
@@ -90,6 +91,138 @@ 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',
|
||||
},
|
||||
// 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: {
|
||||
name: 'Customer Type',
|
||||
|
||||
@@ -8,6 +8,8 @@ export default {
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'categories',
|
||||
exportable: true,
|
||||
fields: {
|
||||
payment_date: {
|
||||
name: 'expense.field.payment_date',
|
||||
@@ -61,6 +63,56 @@ export default {
|
||||
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',
|
||||
@@ -68,7 +120,7 @@ export default {
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the account name or code."
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'expense.field.reference_no',
|
||||
@@ -102,7 +154,7 @@ export default {
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the account name or code."
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
amount: {
|
||||
name: 'expense.field.amount',
|
||||
|
||||
@@ -4,6 +4,54 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
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: {
|
||||
date: {
|
||||
name: 'inventory_adjustment.field.date',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default {
|
||||
importable: true,
|
||||
exportable: true,
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
@@ -121,6 +122,97 @@ export default {
|
||||
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',
|
||||
@@ -195,7 +287,7 @@ export default {
|
||||
fieldType: 'relation',
|
||||
relationModel: 'ItemCategory',
|
||||
relationImportMatch: ['name'],
|
||||
importHint: "Matches the category name."
|
||||
importHint: 'Matches the category name.',
|
||||
},
|
||||
active: {
|
||||
name: 'item.field.active',
|
||||
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
fields: {
|
||||
name: {
|
||||
name: 'item_category.field.name',
|
||||
@@ -28,6 +29,24 @@ export default {
|
||||
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',
|
||||
|
||||
@@ -5,6 +5,9 @@ export default {
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
exportable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'journalNumber',
|
||||
@@ -56,6 +59,76 @@ export default {
|
||||
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',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default {
|
||||
importable: true,
|
||||
exportable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'paymentReceiveNo',
|
||||
@@ -57,6 +58,42 @@ export default {
|
||||
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',
|
||||
@@ -84,12 +121,12 @@ export default {
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the account name or code."
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
paymentReceiveNo: {
|
||||
name: 'payment_receive.field.payment_receive_no',
|
||||
fieldType: 'text',
|
||||
importHint: "The payment number should be unique."
|
||||
importHint: 'The payment number should be unique.',
|
||||
},
|
||||
statement: {
|
||||
name: 'payment_receive.field.statement',
|
||||
@@ -108,7 +145,7 @@ export default {
|
||||
relationModel: 'SaleInvoice',
|
||||
relationImportMatch: 'invoiceNo',
|
||||
required: true,
|
||||
importHint: "Matches the invoice number."
|
||||
importHint: 'Matches the invoice number.',
|
||||
},
|
||||
paymentAmount: {
|
||||
name: 'payment_receive.field.entries.payment_amount',
|
||||
|
||||
@@ -4,6 +4,9 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'estimate_date',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
@@ -73,6 +76,91 @@ export default {
|
||||
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',
|
||||
@@ -132,7 +220,7 @@ export default {
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the item name or code."
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
|
||||
@@ -4,6 +4,9 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
@@ -87,6 +90,89 @@ export default {
|
||||
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',
|
||||
@@ -142,7 +228,7 @@ export default {
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the item name or code."
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
|
||||
@@ -4,6 +4,9 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
@@ -77,6 +80,86 @@ export default {
|
||||
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',
|
||||
@@ -126,7 +209,7 @@ export default {
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the item name or code."
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
sortField: 'created_at',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
fields: {
|
||||
first_name: {
|
||||
name: 'vendor.field.first_name',
|
||||
@@ -32,7 +33,7 @@ export default {
|
||||
fieldType: 'text',
|
||||
},
|
||||
personal_phone: {
|
||||
name: 'vendor.field.personal_pone',
|
||||
name: 'vendor.field.personal_phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
@@ -90,6 +91,154 @@ 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',
|
||||
|
||||
@@ -12,10 +12,14 @@ export default {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'vendorCreditNumber',
|
||||
|
||||
fields: {
|
||||
vendor: {
|
||||
name: 'vendor_credit.field.vendor',
|
||||
@@ -76,6 +80,79 @@ export default {
|
||||
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',
|
||||
@@ -122,7 +199,7 @@ export default {
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: "Matches the item name or code."
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
|
||||
@@ -8,6 +8,34 @@ export default {
|
||||
sortField: 'name',
|
||||
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: {
|
||||
date: {
|
||||
name: 'warehouse_transfer.field.date',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'reflect-metadata'; // We need this in order to use @Decorators
|
||||
import 'newrelic';
|
||||
import './before';
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export default class ListCreditNotes extends BaseCreditNotes {
|
||||
);
|
||||
const { results, pagination } = await CreditNote.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('entries.item');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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',
|
||||
}
|
||||
27
packages/server/src/services/Export/utils.ts
Normal file
27
packages/server/src/services/Export/utils.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { flatMap } from 'lodash';
|
||||
/**
|
||||
* Flattens the data based on a specified attribute.
|
||||
* @param data - The data to be flattened.
|
||||
* @param flattenAttr - The attribute to be flattened.
|
||||
* @returns - The flattened data.
|
||||
*/
|
||||
export const flatDataCollections = (
|
||||
data: Record<string, any>,
|
||||
flattenAttr: string
|
||||
): Record<string, any>[] => {
|
||||
return flatMap(data, (item) =>
|
||||
item[flattenAttr].map((entry) => ({
|
||||
...item,
|
||||
[flattenAttr]: entry,
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the data accessor for a given column.
|
||||
* @param col - The column to get the data accessor for.
|
||||
* @returns - The data accessor.
|
||||
*/
|
||||
export const getDataAccessor = (col: any) => {
|
||||
return col.group ? `${col.group}.${col.accessor}` : col.accessor;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Exportable } from '../Export/Exportable';
|
||||
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||
import ItemCategoriesService from './ItemCategoriesService';
|
||||
|
||||
@Service()
|
||||
export class ItemCategoriesExportable extends Exportable {
|
||||
@Inject()
|
||||
private itemCategoriesApplication: ItemCategoriesService;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
} as IAccountsFilter;
|
||||
|
||||
return this.itemCategoriesApplication
|
||||
.getItemCategoriesList(tenantId, parsedQuery, {})
|
||||
.then((output) => output.itemCategories);
|
||||
}
|
||||
}
|
||||
29
packages/server/src/services/Items/ItemsExportable.ts
Normal file
29
packages/server/src/services/Items/ItemsExportable.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Exportable } from '../Export/Exportable';
|
||||
import { IItemsFilter } from '@/interfaces';
|
||||
import { ItemsApplication } from './ItemsApplication';
|
||||
|
||||
@Service()
|
||||
export class ItemsExportable extends Exportable {
|
||||
@Inject()
|
||||
private itemsApplication: ItemsApplication;
|
||||
|
||||
/**
|
||||
* 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.itemsApplication
|
||||
.getItems(tenantId, parsedQuery)
|
||||
.then((output) => output.items);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { difference } from 'lodash';
|
||||
import { difference, isEmpty } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
@@ -244,16 +244,12 @@ export class CommandManualJournalValidators {
|
||||
/**
|
||||
* Validates the manual journal number require.
|
||||
* @param {string} journalNumber
|
||||
* @throws {ServiceError(ERRORS.MANUAL_JOURNAL_NO_REQUIRED)}
|
||||
*/
|
||||
public validateJournalNoRequireWhenAutoNotEnabled = (
|
||||
tenantId: number,
|
||||
journalNumber: string
|
||||
) => {
|
||||
// Retrieve the next manual journal number.
|
||||
const autoIncrmenetEnabled =
|
||||
this.autoIncrement.autoIncrementEnabled(tenantId);
|
||||
|
||||
if (!journalNumber || !autoIncrmenetEnabled) {
|
||||
if (isEmpty(journalNumber)) {
|
||||
throw new ServiceError(ERRORS.MANUAL_JOURNAL_NO_REQUIRED);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,7 +99,6 @@ export class CreateManualJournalService {
|
||||
|
||||
// Validate manual journal number require when auto-increment not enabled.
|
||||
this.validator.validateJournalNoRequireWhenAutoNotEnabled(
|
||||
tenantId,
|
||||
manualJournalDTO.journalNumber
|
||||
);
|
||||
// Validate manual journal uniquiness on the storage.
|
||||
|
||||
@@ -39,7 +39,7 @@ export class GetManualJournals {
|
||||
tenantId: number,
|
||||
filterDTO: IManualJournalsFilter
|
||||
): Promise<{
|
||||
manualJournals: IManualJournal;
|
||||
manualJournals: IManualJournal[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IManualJournalsFilter } from '@/interfaces';
|
||||
import { Exportable } from '../Export/Exportable';
|
||||
import { ManualJournalsApplication } from './ManualJournalsApplication';
|
||||
|
||||
@Service()
|
||||
export class ManualJournalsExportable extends Exportable {
|
||||
@Inject()
|
||||
private manualJournalsApplication: ManualJournalsApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the manual journals data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: IManualJournalsFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 12000,
|
||||
} as IManualJournalsFilter;
|
||||
|
||||
return this.manualJournalsApplication
|
||||
.getManualJournals(tenantId, parsedQuery)
|
||||
.then((output) => output.manualJournals);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
import { BillPaymentsApplication } from './BillPaymentsApplication';
|
||||
|
||||
@Service()
|
||||
export class BillPaymentExportable extends Exportable {
|
||||
@Inject()
|
||||
private billPaymentsApplication: BillPaymentsApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: any) {
|
||||
const parsedQuery = {
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...query,
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
} as any;
|
||||
|
||||
return this.billPaymentsApplication
|
||||
.getBillPayments(tenantId, parsedQuery)
|
||||
.then((output) => output.billPayments);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class GetBillPayments {
|
||||
tenantId: number,
|
||||
filterDTO: IBillPaymentsFilter
|
||||
): Promise<{
|
||||
billPayments: IBillPayment;
|
||||
billPayments: IBillPayment[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class BillsApplication {
|
||||
tenantId: number,
|
||||
filterDTO: IBillsFilter
|
||||
): Promise<{
|
||||
bills: IBill;
|
||||
bills: IBill[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IBillsFilter } from '@/interfaces';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
import { BillsApplication } from './BillsApplication';
|
||||
|
||||
@Service()
|
||||
export class BillsExportable extends Exportable {
|
||||
@Inject()
|
||||
private billsApplication: BillsApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: IBillsFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 12000,
|
||||
} as IBillsFilter;
|
||||
|
||||
return this.billsApplication
|
||||
.getBills(tenantId, parsedQuery)
|
||||
.then((output) => output.bills);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export class GetBills {
|
||||
const { results, pagination } = await Bill.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
builder.withGraphFetched('entries.item');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
@@ -14,6 +14,7 @@ export class VendorCreditTransformer extends Transformer {
|
||||
'formattedSubtotal',
|
||||
'formattedVendorCreditDate',
|
||||
'formattedCreditsRemaining',
|
||||
'formattedInvoicedAmount',
|
||||
'entries',
|
||||
];
|
||||
};
|
||||
@@ -58,6 +59,17 @@ export class VendorCreditTransformer extends Transformer {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted invoiced amount.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedInvoicedAmount = (credit) => {
|
||||
return formatNumber(credit.invoicedAmount, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the entries of the bill.
|
||||
* @param {IVendorCredit} vendorCredit
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IVendorCreditsQueryDTO } from '@/interfaces';
|
||||
import ListVendorCredits from './ListVendorCredits';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
|
||||
@Service()
|
||||
export class VendorCreditsExportable extends Exportable {
|
||||
@Inject()
|
||||
private getVendorCredits: ListVendorCredits;
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credits data to exportable sheet.
|
||||
* @param {number} tenantId -
|
||||
* @param {IVendorCreditsQueryDTO} query -
|
||||
* @returns {}
|
||||
*/
|
||||
public exportable(tenantId: number, query: IVendorCreditsQueryDTO) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 12000,
|
||||
} as IVendorCreditsQueryDTO;
|
||||
|
||||
return this.getVendorCredits
|
||||
.getVendorCredits(tenantId, parsedQuery)
|
||||
.then((output) => output.vendorCredits);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,11 @@ export default class ResourceService {
|
||||
const $enumerationType = (field) =>
|
||||
field.fieldType === 'enumeration' ? field : undefined;
|
||||
|
||||
const $hasFields = (field) => 'undefined' !== typeof field.fields ? field : undefined;
|
||||
const $hasFields = (field) =>
|
||||
'undefined' !== typeof field.fields ? field : undefined;
|
||||
|
||||
const $hasColumns = (column) =>
|
||||
'undefined' !== typeof column.columns ? column : undefined;
|
||||
|
||||
const naviagations = [
|
||||
['fields', qim.$each, 'name'],
|
||||
@@ -113,6 +117,8 @@ export default class ResourceService {
|
||||
['fields2', qim.$each, 'name'],
|
||||
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||
['columns', qim.$each, 'name'],
|
||||
['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'],
|
||||
];
|
||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,8 @@ export default class AutoIncrementOrdersService {
|
||||
const group = settingsGroup;
|
||||
|
||||
// Settings service transaction number and prefix.
|
||||
const autoIncrement = settings.get({ group, key: 'auto_increment' }, false);
|
||||
|
||||
return parseBoolean(autoIncrement, false);
|
||||
}
|
||||
return settings.get({ group, key: 'auto_increment' }, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the next service transaction number.
|
||||
@@ -27,17 +25,16 @@ export default class AutoIncrementOrdersService {
|
||||
* @param {Function} getMaxTransactionNo
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
getNextTransactionNumber(tenantId: number, settingsGroup: string): string {
|
||||
getNextTransactionNumber(tenantId: number, group: string): string {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const group = settingsGroup;
|
||||
|
||||
// Settings service transaction number and prefix.
|
||||
const autoIncrement = settings.get({ group, key: 'auto_increment' }, false);
|
||||
const autoIncrement = this.autoIncrementEnabled(tenantId, group);
|
||||
|
||||
const settingNo = settings.get({ group, key: 'next_number' }, '');
|
||||
const settingPrefix = settings.get({ group, key: 'number_prefix' }, '');
|
||||
|
||||
return parseBoolean(autoIncrement, false) ? `${settingPrefix}${settingNo}` : '';
|
||||
return autoIncrement ? `${settingPrefix}${settingNo}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +50,9 @@ export default class AutoIncrementOrdersService {
|
||||
const autoIncrement = settings.get({ group, key: 'auto_increment' });
|
||||
|
||||
// Can't continue if the auto-increment of the service was disabled.
|
||||
if (!autoIncrement) { return; }
|
||||
if (!autoIncrement) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.set(
|
||||
{ group, key: 'next_number' },
|
||||
|
||||
@@ -51,6 +51,7 @@ export class GetSaleEstimates {
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('entries.item');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISalesInvoicesFilter } from '@/interfaces';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
import { SaleEstimatesApplication } from './SaleEstimatesApplication';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimatesExportable extends Exportable {
|
||||
@Inject()
|
||||
private saleEstimatesApplication: SaleEstimatesApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: ISalesInvoicesFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 12000,
|
||||
} as ISalesInvoicesFilter;
|
||||
|
||||
return this.saleEstimatesApplication
|
||||
.getSaleEstimates(tenantId, parsedQuery)
|
||||
.then((output) => output.salesEstimates);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export class GetSaleInvoices {
|
||||
);
|
||||
const { results, pagination } = await SaleInvoice.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('entries.item');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISalesInvoicesFilter } from '@/interfaces';
|
||||
import { SaleInvoiceApplication } from './SaleInvoicesApplication';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoicesExportable extends Exportable {
|
||||
@Inject()
|
||||
private saleInvoicesApplication: SaleInvoiceApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: ISalesInvoicesFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 120000,
|
||||
} as ISalesInvoicesFilter;
|
||||
|
||||
return this.saleInvoicesApplication
|
||||
.getSaleInvoices(tenantId, parsedQuery)
|
||||
.then((output) => output.salesInvoices);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IAccountsStructureType, IPaymentReceivesFilter } from '@/interfaces';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
import { PaymentReceivesApplication } from './PaymentReceivesApplication';
|
||||
|
||||
@Service()
|
||||
export class PaymentsReceivedExportable extends Exportable {
|
||||
@Inject()
|
||||
private paymentReceivedApp: PaymentReceivesApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @param {IPaymentReceivesFilter} query -
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: IPaymentReceivesFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
inactiveMode: false,
|
||||
...query,
|
||||
structure: IAccountsStructureType.Flat,
|
||||
} as IPaymentReceivesFilter;
|
||||
|
||||
return this.paymentReceivedApp
|
||||
.getPaymentReceives(tenantId, parsedQuery)
|
||||
.then((output) => output.paymentReceives);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import { SaleReceiptTransformer } from './SaleReceiptTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
|
||||
interface GetSaleReceiptsSettings {
|
||||
fetchEntriesGraph?: boolean;
|
||||
}
|
||||
@Service()
|
||||
export class GetSaleReceipts {
|
||||
@Inject()
|
||||
@@ -50,7 +53,7 @@ export class GetSaleReceipts {
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('depositAccount');
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('entries.item');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISalesReceiptsFilter } from '@/interfaces';
|
||||
import { Exportable } from '@/services/Export/Exportable';
|
||||
import { SaleReceiptApplication } from './SaleReceiptApplication';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptsExportable extends Exportable {
|
||||
@Inject()
|
||||
private saleReceiptsApp: SaleReceiptApplication;
|
||||
|
||||
/**
|
||||
* Retrieves the accounts data to exportable sheet.
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
public exportable(tenantId: number, query: ISalesReceiptsFilter) {
|
||||
const parsedQuery = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
...query,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
} as ISalesReceiptsFilter;
|
||||
|
||||
return this.saleReceiptsApp
|
||||
.getSaleReceipts(tenantId, parsedQuery)
|
||||
.then((output) => output.data);
|
||||
}
|
||||
}
|
||||
@@ -337,6 +337,9 @@ const booleanValues: string[] = [
|
||||
].map((value) => normalizeValue(value));
|
||||
|
||||
export const parseBoolean = <T>(value: any, defaultValue: T): T | boolean => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value; // Retrun early we have nothing to parse.
|
||||
}
|
||||
const normalizedValue = normalizeValue(value);
|
||||
if (isEmpty(value) || booleanValues.indexOf(normalizedValue) === -1) {
|
||||
return defaultValue;
|
||||
|
||||
@@ -7,8 +7,8 @@ WORKDIR /app
|
||||
# Copy application dependency manifests to the container image.
|
||||
COPY ./package*.json ./
|
||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY ./lerna.json ./lerna.json
|
||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY ./packages/webapp/package*.json ./packages/webapp/
|
||||
|
||||
# Install application dependencies
|
||||
@@ -24,7 +24,7 @@ RUN pnpm install
|
||||
|
||||
# Build webapp package
|
||||
COPY ./packages/webapp /app/packages/webapp
|
||||
RUN npm run build:webapp
|
||||
RUN pnpm run build:webapp
|
||||
|
||||
FROM nginx
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/
|
||||
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
|
||||
import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog';
|
||||
import { ConnectBankDialog } from '@/containers/CashFlow/ConnectBankDialog';
|
||||
import { ExportDialog } from '@/containers/Dialogs/ExportDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -148,6 +149,8 @@ export default function DialogsContainer() {
|
||||
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
|
||||
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
|
||||
<ConnectBankDialog dialogName={DialogsName.ConnectBankCreditCard} />
|
||||
|
||||
<ExportDialog dialogName={DialogsName.Export} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,5 +73,6 @@ export enum DialogsName {
|
||||
CustomerTransactionsPdfPreview = 'CustomerTransactionsPdfPreview',
|
||||
VendorTransactionsPdfPreview = 'VendorTransactionsPdfPreview',
|
||||
GeneralLedgerPdfPreview = 'GeneralLedgerPdfPreview',
|
||||
SalesTaxLiabilitySummaryPdfPreview = 'SalesTaxLiabilitySummaryPdfPreview'
|
||||
SalesTaxLiabilitySummaryPdfPreview = 'SalesTaxLiabilitySummaryPdfPreview',
|
||||
Export = 'Export',
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Can,
|
||||
If,
|
||||
DashboardActionViewsList,
|
||||
DashboardActionsBar
|
||||
DashboardActionsBar,
|
||||
} from '@/components';
|
||||
import { useRefreshJournals } from '@/hooks/query/manualJournals';
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
@@ -31,6 +31,7 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Manual journal actions bar.
|
||||
@@ -47,6 +48,9 @@ function ManualJournalActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -75,13 +79,18 @@ function ManualJournalActionsBar({
|
||||
// Handle import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/manual-journals/import');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('manualJournals', 'tableSize', size);
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'manual_journal' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -140,6 +149,7 @@ function ManualJournalActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
|
||||
@@ -118,6 +118,10 @@ function AccountsActionsBar({
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/accounts/import');
|
||||
};
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'account' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -182,17 +186,18 @@ function AccountsActionsBar({
|
||||
icon={<Icon icon="print-16" iconSize={16} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
onClick={handleImportBtnClick}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
initialValue={accountsTableSize}
|
||||
|
||||
@@ -9,7 +9,11 @@ import AuthInsider from '@/containers/Authentication/AuthInsider';
|
||||
import { useAuthLogin, useAuthRegister } from '@/hooks/query/authentication';
|
||||
|
||||
import RegisterForm from './RegisterForm';
|
||||
import { RegisterSchema, transformRegisterErrorsToForm, transformRegisterToastMessages } from './utils';
|
||||
import {
|
||||
RegisterSchema,
|
||||
transformRegisterErrorsToForm,
|
||||
transformRegisterToastMessages,
|
||||
} from './utils';
|
||||
import {
|
||||
AuthFooterLinks,
|
||||
AuthFooterLink,
|
||||
@@ -32,7 +36,7 @@ export default function RegisterUserForm() {
|
||||
|
||||
const handleSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
authRegisterMutate(values)
|
||||
.then((response) => {
|
||||
.then(() => {
|
||||
authLoginMutate({
|
||||
crediential: values.email,
|
||||
password: values.password,
|
||||
@@ -87,7 +91,10 @@ function RegisterFooterLinks() {
|
||||
return (
|
||||
<AuthFooterLinks>
|
||||
<AuthFooterLink>
|
||||
<T id={'return_to'} /> <Link to={'/auth/login'}><T id={'sign_in'} /></Link>
|
||||
<T id={'return_to'} />{' '}
|
||||
<Link to={'/auth/login'}>
|
||||
<T id={'sign_in'} />
|
||||
</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
<AuthFooterLink>
|
||||
|
||||
@@ -4,7 +4,7 @@ import AuthInsider from './AuthInsider';
|
||||
import { AuthInsiderCard } from './_components';
|
||||
import styles from './RegisterVerify.module.scss';
|
||||
import { AppToaster, Stack } from '@/components';
|
||||
import { useAuthActions } from '@/hooks/state';
|
||||
import { useAuthActions, useAuthUserVerifyEmail } from '@/hooks/state';
|
||||
import { useAuthSignUpVerifyResendMail } from '@/hooks/query';
|
||||
import { AuthContainer } from './AuthContainer';
|
||||
|
||||
@@ -13,6 +13,8 @@ export default function RegisterVerify() {
|
||||
const { mutateAsync: resendSignUpVerifyMail, isLoading } =
|
||||
useAuthSignUpVerifyResendMail();
|
||||
|
||||
const emailAddress = useAuthUserVerifyEmail();
|
||||
|
||||
const handleResendMailBtnClick = () => {
|
||||
resendSignUpVerifyMail()
|
||||
.then(() => {
|
||||
@@ -38,8 +40,8 @@ export default function RegisterVerify() {
|
||||
<AuthInsiderCard className={styles.root}>
|
||||
<h2 className={styles.title}>Please verify your email</h2>
|
||||
<p className={styles.description}>
|
||||
We sent an email to <strong>asdahmed@gmail.com</strong> Click the
|
||||
link inside to get started.
|
||||
We sent an email to <strong>{emailAddress}</strong> Click the link
|
||||
inside to get started.
|
||||
</p>
|
||||
|
||||
<Stack spacing={4}>
|
||||
|
||||
@@ -92,13 +92,13 @@ function CashFlowAccountsActionsBar({
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Can I={CashflowAction.Edit} a={AbilitySubject.Cashflow}>
|
||||
@@ -117,7 +117,7 @@ function CashFlowAccountsActionsBar({
|
||||
text={'Connect to Bank / Credit Card'}
|
||||
onClick={handleConnectToBank}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<NavbarDivider />
|
||||
</FeatureCan>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
|
||||
@@ -31,9 +31,11 @@ import withCustomersActions from './withCustomersActions';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import { CustomerAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { CustomerAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Customers actions bar.
|
||||
@@ -55,6 +57,9 @@ function CustomerActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -100,6 +105,11 @@ function CustomerActionsBar({
|
||||
history.push('/customers/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'customer' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -154,6 +164,7 @@ function CustomerActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -192,4 +203,5 @@ export default compose(
|
||||
customersTableSize: customersSettings?.tableSize,
|
||||
})),
|
||||
withAlertActions,
|
||||
withDialogActions,
|
||||
)(CustomerActionsBar);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
.root{
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.footer{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.resourceFormGroup{
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.paragraph{
|
||||
color: #5F6B7C;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ExportDialogForm } from './ExportDialogForm';
|
||||
import { ExportFormInitialValues } from './type';
|
||||
|
||||
interface ExportDialogContentProps {
|
||||
initialValues?: ExportFormInitialValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account dialog content.
|
||||
*/
|
||||
export default function ExportDialogContent({
|
||||
initialValues,
|
||||
}: ExportDialogContentProps) {
|
||||
return <ExportDialogForm initialValues={initialValues} />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const Schema = Yup.object().shape({
|
||||
resource: Yup.string().required().label('Resource'),
|
||||
format: Yup.string().required().label('Format'),
|
||||
});
|
||||
|
||||
export const ExportDialogFormSchema = Schema;
|
||||
@@ -0,0 +1,78 @@
|
||||
// @ts-nocheck
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import { compose, transformToForm } from '@/utils';
|
||||
|
||||
import { ExportDialogFormSchema } from './ExportDialogForm.schema';
|
||||
import { ExportDialogFormContent } from './ExportDialogFormContent';
|
||||
import { useResourceExport } from '@/hooks/query/FinancialReports/use-export';
|
||||
import { ExportFormInitialValues } from './type';
|
||||
import { AppToaster } from '@/components';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
// Default initial form values.
|
||||
const defaultInitialValues = {
|
||||
resource: '',
|
||||
format: 'csv',
|
||||
};
|
||||
|
||||
interface ExportDialogFormProps {
|
||||
initialValues?: ExportFormInitialValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account form dialog content.
|
||||
*/
|
||||
function ExportDialogFormRoot({
|
||||
// #ownProps
|
||||
initialValues,
|
||||
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}: ExportDialogFormProps) {
|
||||
const { mutateAsync: mutateExport } = useResourceExport();
|
||||
|
||||
// Callbacks handles form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
setSubmitting(true);
|
||||
const { resource, format } = values;
|
||||
|
||||
mutateExport({ resource, format })
|
||||
.then(() => {
|
||||
setSubmitting(false);
|
||||
closeDialog(DialogsName.Export);
|
||||
})
|
||||
.catch(() => {
|
||||
setSubmitting(false);
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong!',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Form initial values in create and edit mode.
|
||||
const initialFormValues = {
|
||||
...defaultInitialValues,
|
||||
/**
|
||||
* We only care about the fields in the form. Previously unfilled optional
|
||||
* values such as `notes` come back from the API as null, so remove those
|
||||
* as well.
|
||||
*/
|
||||
...transformToForm(initialValues, defaultInitialValues),
|
||||
};
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={ExportDialogFormSchema}
|
||||
initialValues={initialFormValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
<ExportDialogFormContent />
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExportDialogForm =
|
||||
compose(withDialogActions)(ExportDialogFormRoot);
|
||||
@@ -0,0 +1,63 @@
|
||||
// @ts-nocheck
|
||||
import { FFormGroup, FRadioGroup, FSelect, Group } from '@/components';
|
||||
import { Button, Intent, Radio } from '@blueprintjs/core';
|
||||
import { Form, useFormikContext } from 'formik';
|
||||
import { ExportResources } from './constants';
|
||||
import styles from './ExportDialogContent.module.scss';
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
function ExportDialogFormContentRoot({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
const handleCancelBtnClick = () => {
|
||||
closeDialog(DialogsName.Export);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<div className={styles.root}>
|
||||
<p className={styles.paragraph}>
|
||||
You can export data from Bigcapital in CSV or XLSX format
|
||||
</p>
|
||||
|
||||
<FFormGroup
|
||||
name={'resource'}
|
||||
label={'Select Resource'}
|
||||
className={styles.resourceFormGroup}
|
||||
>
|
||||
<FSelect
|
||||
name={'resource'}
|
||||
items={ExportResources}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FRadioGroup label={'Export As'} name={'format'}>
|
||||
<Radio value={'xlsx'}>XLSX (Microsoft Excel)</Radio>
|
||||
<Radio value={'csv'}>CSV (Comma Seperated Value)</Radio>
|
||||
</FRadioGroup>
|
||||
|
||||
<Group position={'right'} spacing={10} className={styles.footer}>
|
||||
<Button intent={Intent.NONE} onClick={handleCancelBtnClick}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type={'submit'}
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExportDialogFormContent = compose(withDialogActions)(
|
||||
ExportDialogFormContentRoot,
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const ExportResources = [
|
||||
{ value: 'account', text: 'Accounts' },
|
||||
{ value: 'item', text: 'Items' },
|
||||
{ value: 'item_category', text: 'Item Categories' },
|
||||
{ value: 'customer', text: 'Customers' },
|
||||
{ value: 'vendor', text: 'Vendors' },
|
||||
{ value: 'manual_journal', text: 'Manual Journal' },
|
||||
{ value: 'expense', text: 'Expenses' },
|
||||
{ value: 'sale_invoice', text: 'Invoices' },
|
||||
{ value: 'sale_estimate', text: ' Estimates' },
|
||||
{ value: 'sale_receipt', text: 'Receipts' },
|
||||
{ value: 'payment_receive', text: 'Payments Received' },
|
||||
{ value: 'credit_note', text: 'Credit Notes' },
|
||||
{ value: 'bill', text: 'Bills' },
|
||||
{ value: 'bill_payment', text: 'Bill Payments' },
|
||||
{ value: 'vendor_credit', text: 'Vendor Credits' },
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
// @ts-nocheck
|
||||
import React, { lazy } from 'react';
|
||||
import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const ExportDialogContent = lazy(() => import('./ExportDialogContent'));
|
||||
|
||||
// User form dialog.
|
||||
function ExportDialogRoot({ dialogName, payload, isOpen }) {
|
||||
const { resource = null, format = null } = payload;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={'Export Data'}
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<ExportDialogContent
|
||||
dialogName={dialogName}
|
||||
initialValues={{ resource, format }}
|
||||
/>
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExportDialog = compose(withDialogRedux())(ExportDialogRoot);
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
export interface ExportFormInitialValues {
|
||||
resource?: string;
|
||||
format?: string;
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Expenses actions bar.
|
||||
@@ -49,6 +50,9 @@ function ExpensesActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -63,7 +67,6 @@ function ExpensesActionsBar({
|
||||
const onClickNewExpense = () => {
|
||||
history.push('/expenses/new');
|
||||
};
|
||||
|
||||
// Handle delete button click.
|
||||
const handleBulkDelete = () => {};
|
||||
|
||||
@@ -73,21 +76,23 @@ function ExpensesActionsBar({
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle click a refresh
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
// Handle the import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/expenses/import');
|
||||
}
|
||||
|
||||
};
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('expenses', 'tableSize', size);
|
||||
};
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'expense' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -146,6 +151,7 @@ function ExpensesActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
|
||||
@@ -33,7 +33,9 @@ import withItemsActions from './withItemsActions';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withDialogActions from '../Dialog/withDialogActions';
|
||||
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
@@ -56,6 +58,9 @@ function ItemsActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog
|
||||
}) {
|
||||
// Items list context.
|
||||
const { itemsViews, fields } = useItemsListContext();
|
||||
@@ -99,6 +104,11 @@ function ItemsActionsBar({
|
||||
history.push('/items/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'item' });
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -154,6 +164,7 @@ function ItemsActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -193,4 +204,5 @@ export default compose(
|
||||
})),
|
||||
withItemsActions,
|
||||
withAlertActions,
|
||||
withDialogActions
|
||||
)(ItemsActionsBar);
|
||||
|
||||
@@ -23,6 +23,7 @@ import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import { compose } from '@/utils';
|
||||
import { useItemsCategoriesContext } from './ItemsCategoriesProvider';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Items categories actions bar.
|
||||
@@ -58,6 +59,10 @@ function ItemsCategoryActionsBar({
|
||||
itemCategoriesIds: itemCategoriesSelectedRows,
|
||||
});
|
||||
};
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'item_category' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -105,6 +110,7 @@ function ItemsCategoryActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
|
||||
@@ -32,6 +32,8 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import { useBillsListContext } from './BillsListProvider';
|
||||
import { useRefreshBills } from '@/hooks/query/bills';
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Bills actions bar.
|
||||
@@ -48,6 +50,9 @@ function BillActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -81,7 +86,12 @@ function BillActionsBar({
|
||||
// Handle the import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/bills/import');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'bill' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -141,6 +151,7 @@ function BillActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
@@ -170,4 +181,5 @@ export default compose(
|
||||
withSettings(({ billsettings }) => ({
|
||||
billsTableSize: billsettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(BillActionsBar);
|
||||
|
||||
@@ -22,14 +22,16 @@ import {
|
||||
|
||||
import { useVendorsCreditNoteListContext } from './VendorsCreditNoteListProvider';
|
||||
import { VendorCreditAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withVendorsCreditNotes from './withVendorsCreditNotes';
|
||||
import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import withVendorActions from './withVendorActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Vendors Credit note table actions bar.
|
||||
@@ -48,6 +50,9 @@ function VendorsCreditNoteActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -77,8 +82,13 @@ function VendorsCreditNoteActionsBar({
|
||||
|
||||
// Handle import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/vendor-credits/import')
|
||||
}
|
||||
history.push('/vendor-credits/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'vendor_credit' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -128,6 +138,7 @@ function VendorsCreditNoteActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -157,4 +168,5 @@ export default compose(
|
||||
withSettings(({ vendorsCreditNoteSetting }) => ({
|
||||
creditNoteTableSize: vendorsCreditNoteSetting?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(VendorsCreditNoteActionsBar);
|
||||
|
||||
@@ -33,6 +33,8 @@ import { useRefreshPaymentMades } from '@/hooks/query/paymentMades';
|
||||
import { PaymentMadeAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Payment made actions bar.
|
||||
@@ -47,6 +49,9 @@ function PaymentMadeActionsBar({
|
||||
// #withSettings
|
||||
paymentMadesTableSize,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
@@ -81,7 +86,12 @@ function PaymentMadeActionsBar({
|
||||
// Handle the import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/payment-mades/import');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'bill_payment' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -139,6 +149,7 @@ function PaymentMadeActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
@@ -168,4 +179,5 @@ export default compose(
|
||||
withSettings(({ billPaymentSettings }) => ({
|
||||
paymentMadesTableSize: billPaymentSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(PaymentMadeActionsBar);
|
||||
|
||||
@@ -25,8 +25,10 @@ import withCreditNotes from './withCreditNotes';
|
||||
import withCreditNotesActions from './withCreditNotesActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Credit note table actions bar.
|
||||
@@ -43,6 +45,9 @@ function CreditNotesActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -74,6 +79,11 @@ function CreditNotesActionsBar({
|
||||
history.push('/credit-notes/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'credit_note' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -122,6 +132,7 @@ function CreditNotesActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -150,4 +161,5 @@ export default compose(
|
||||
withSettings(({ creditNoteSettings }) => ({
|
||||
creditNoteTableSize: creditNoteSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(CreditNotesActionsBar);
|
||||
|
||||
@@ -31,6 +31,8 @@ import { useEstimatesListContext } from './EstimatesListProvider';
|
||||
import { useRefreshEstimates } from '@/hooks/query/estimates';
|
||||
import { SaleEstimateAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Estimates list actions bar.
|
||||
@@ -45,6 +47,9 @@ function EstimateActionsBar({
|
||||
// #withSettings
|
||||
estimatesTableSize,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
@@ -80,7 +85,11 @@ function EstimateActionsBar({
|
||||
// Handle the import button click.
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/estimates/import');
|
||||
}
|
||||
};
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'sale_estimate' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -141,6 +150,7 @@ function EstimateActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -170,4 +180,5 @@ export default compose(
|
||||
withSettings(({ estimatesSettings }) => ({
|
||||
estimatesTableSize: estimatesSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions
|
||||
)(EstimateActionsBar);
|
||||
|
||||
@@ -29,6 +29,8 @@ import withInvoiceActions from './withInvoiceActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Invoices table actions bar.
|
||||
@@ -45,6 +47,9 @@ function InvoiceActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogsActions
|
||||
openDialog
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -79,6 +84,11 @@ function InvoiceActionsBar({
|
||||
history.push('/invoices/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'sale_invoice' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -135,6 +145,7 @@ function InvoiceActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -163,4 +174,5 @@ export default compose(
|
||||
withSettings(({ invoiceSettings }) => ({
|
||||
invoicesTableSize: invoiceSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(InvoiceActionsBar);
|
||||
|
||||
@@ -26,6 +26,7 @@ import withPaymentReceives from './withPaymentReceives';
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import {
|
||||
PaymentReceiveAction,
|
||||
AbilitySubject,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider';
|
||||
import { useRefreshPaymentReceive } from '@/hooks/query/paymentReceives';
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Payment receives actions bar.
|
||||
@@ -49,6 +51,9 @@ function PaymentReceiveActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -82,6 +87,10 @@ function PaymentReceiveActionsBar({
|
||||
const handleImportBtnClick = () => {
|
||||
history.push('/payment-receives/import');
|
||||
};
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'payment_receive' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -139,6 +148,7 @@ function PaymentReceiveActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
@@ -169,4 +179,5 @@ export default compose(
|
||||
withSettings(({ paymentReceiveSettings }) => ({
|
||||
paymentReceivesTableSize: paymentReceiveSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(PaymentReceiveActionsBar);
|
||||
|
||||
@@ -35,6 +35,8 @@ import { useRefreshReceipts } from '@/hooks/query/receipts';
|
||||
import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Receipts actions bar.
|
||||
@@ -49,6 +51,9 @@ function ReceiptActionsBar({
|
||||
// #withSettings
|
||||
receiptsTableSize,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
@@ -86,6 +91,11 @@ function ReceiptActionsBar({
|
||||
history.push('/receipts/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'sale_receipt' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -145,6 +155,7 @@ function ReceiptActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -173,4 +184,5 @@ export default compose(
|
||||
withSettings(({ receiptSettings }) => ({
|
||||
receiptsTableSize: receiptSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(ReceiptActionsBar);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function SetupWizardContent({
|
||||
<SetupOrganizationPage id="organization" />
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step label={'Initiializing'}>
|
||||
<Stepper.Step label={'Initializing'}>
|
||||
<SetupInitializingForm id={'initializing'} />
|
||||
</Stepper.Step>
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ import withVendors from './withVendors';
|
||||
import withVendorsActions from './withVendorsActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Vendors actions bar.
|
||||
@@ -50,6 +52,9 @@ function VendorActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -83,10 +88,17 @@ function VendorActionsBar({
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('vendors', 'tableSize', size);
|
||||
};
|
||||
|
||||
// Handle import button success.
|
||||
const handleImportBtnSuccess = () => {
|
||||
history.push('/vendors/import');
|
||||
};
|
||||
|
||||
// Handle the export button click.
|
||||
const handleExportBtnClick = () => {
|
||||
openDialog(DialogsName.Export, { resource: 'vendor' });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -138,6 +150,7 @@ function VendorActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
onClick={handleExportBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
@@ -175,4 +188,5 @@ export default compose(
|
||||
withSettings(({ vendorsSettings }) => ({
|
||||
vendorsTableSize: vendorsSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(VendorActionsBar);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// @ts-nocheck
|
||||
import { downloadFile } from '@/hooks/useDownloadFile';
|
||||
import useApiRequest from '@/hooks/useRequest';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
interface ResourceExportValues {
|
||||
resource: string;
|
||||
format: string;
|
||||
}
|
||||
/**
|
||||
* Initiates a download of the balance sheet in XLSX format.
|
||||
* @param {Object} query - The query parameters for the request.
|
||||
* @param {Object} args - Additional configurations for the download.
|
||||
* @returns {Function} A function to trigger the file download.
|
||||
*/
|
||||
export const useResourceExport = () => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<void, AxiosError, any>((data: ResourceExportValues) => {
|
||||
return apiRequest
|
||||
.get('/export', {
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
accept:
|
||||
data.format === 'xlsx' ? 'application/xlsx' : 'application/csv',
|
||||
},
|
||||
params: {
|
||||
resource: data.resource,
|
||||
format: data.format,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
downloadFile(res.data, `${data.resource}.${data.format}`);
|
||||
return res;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation } from 'react-query';
|
||||
import { batch } from 'react-redux';
|
||||
import useApiRequest from '../useRequest';
|
||||
import { setCookie } from '../../utils';
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import t from './types';
|
||||
import {
|
||||
useSetAuthToken,
|
||||
useSetAuthUserId,
|
||||
useSetLocale,
|
||||
useSetOrganizationId,
|
||||
useSetTenantId,
|
||||
} from '../state';
|
||||
|
||||
/**
|
||||
* Saves the response data to cookies.
|
||||
@@ -24,14 +32,30 @@ function setAuthLoginCookies(data) {
|
||||
export const useAuthLogin = (props) => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
const setAuthToken = useSetAuthToken();
|
||||
const setOrganizationId = useSetOrganizationId();
|
||||
const setUserId = useSetAuthUserId();
|
||||
const setTenantId = useSetTenantId();
|
||||
const setLocale = useSetLocale();
|
||||
|
||||
return useMutation((values) => apiRequest.post('auth/login', values), {
|
||||
select: (res) => res.data,
|
||||
onSuccess: (data) => {
|
||||
onSuccess: (res) => {
|
||||
// Set authentication cookies.
|
||||
setAuthLoginCookies(data.data);
|
||||
setAuthLoginCookies(res.data);
|
||||
|
||||
// Reboot the application.
|
||||
window.location.reload();
|
||||
batch(() => {
|
||||
// Sets the auth metadata to global state.
|
||||
setAuthToken(res.data.token);
|
||||
setOrganizationId(res.data.tenant.organization_id);
|
||||
setUserId(res.data.user.id);
|
||||
setTenantId(res.data.tenant.id);
|
||||
|
||||
if (res.data?.tenant?.metadata?.language) {
|
||||
setLocale(res.data?.tenant?.metadata?.language);
|
||||
}
|
||||
});
|
||||
props?.onSuccess && props?.onSuccess(...args);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user