mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
Compare commits
1 Commits
v0.17.5
...
organize-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b16831e9 |
23
.env.example
23
.env.example
@@ -75,9 +75,30 @@ PLAID_ENV=sandbox
|
|||||||
# Your Plaid keys, which can be found in the Plaid Dashboard.
|
# Your Plaid keys, which can be found in the Plaid Dashboard.
|
||||||
# https://dashboard.plaid.com/account/keys
|
# https://dashboard.plaid.com/account/keys
|
||||||
PLAID_CLIENT_ID=
|
PLAID_CLIENT_ID=
|
||||||
PLAID_SECRET=
|
PLAID_SECRET_DEVELOPMENT=
|
||||||
|
PLAID_SECRET_SANDBOX=
|
||||||
|
|
||||||
PLAID_LINK_WEBHOOK=
|
PLAID_LINK_WEBHOOK=
|
||||||
|
|
||||||
|
# (Optional) Redirect URI settings section
|
||||||
|
# Only required for OAuth redirect URI testing (not common on desktop):
|
||||||
|
# Sandbox Mode:
|
||||||
|
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
|
||||||
|
# The OAuth redirect flow requires an endpoint on the developer's website
|
||||||
|
# that the bank website should redirect to. You will also need to configure
|
||||||
|
# this redirect URI for your client ID through the Plaid developer dashboard
|
||||||
|
# at https://dashboard.plaid.com/team/api.
|
||||||
|
# Development mode:
|
||||||
|
# When running in development mode, you must use an https:// url.
|
||||||
|
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
|
||||||
|
# Instructions to create a self-signed certificate for localhost can be found at
|
||||||
|
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
|
||||||
|
# If your system is not set up to run localhost with https://, you will be unable to test
|
||||||
|
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
|
||||||
|
|
||||||
|
PLAID_SANDBOX_REDIRECT_URI=
|
||||||
|
PLAID_DEVELOPMENT_REDIRECT_URI=
|
||||||
|
|
||||||
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
||||||
LEMONSQUEEZY_API_KEY=
|
LEMONSQUEEZY_API_KEY=
|
||||||
LEMONSQUEEZY_STORE_ID=
|
LEMONSQUEEZY_STORE_ID=
|
||||||
|
|||||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -2,41 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
## [0.17.0] - 04-06-2024
|
|
||||||
|
|
||||||
### New
|
|
||||||
|
|
||||||
* feat: Upload and attach documents by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/461
|
|
||||||
* feat: Export resource tables to pdf by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/460
|
|
||||||
* feat: Build and deploy develop Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/476
|
|
||||||
* feat: Internal docker virtual network by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/478
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* fix: Skip send confirmation email if disabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/459
|
|
||||||
* fix: Lemon Squeezy redirect to base url by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/479
|
|
||||||
* fix: Organize Plaid env variables for development and sandbox envs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/480
|
|
||||||
* fix: Plaid syncs deposit imports as withdrawals by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/481
|
|
||||||
* fix: Validate the s3 configures exist by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/482
|
|
||||||
* fix: Run migrations only for initialized tenants by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/484
|
|
||||||
|
|
||||||
## [0.16.16] -
|
|
||||||
|
|
||||||
* feat: handle http exceptions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/456
|
|
||||||
* feat: add the missing Newrelic env vars to docker-compose.prod file by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/457
|
|
||||||
* fix: add the signup email confirmation env var by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/458
|
|
||||||
|
|
||||||
## [0.16.14] -
|
|
||||||
|
|
||||||
* fix: Typo in setup wizard by @ccantrell72 in https://github.com/bigcapitalhq/bigcapital/pull/440
|
|
||||||
* fix: Showing the real mail address on email confirmation view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/445
|
|
||||||
* fix: Auto-increment setting parsing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/453
|
|
||||||
|
|
||||||
## [0.16.12] -
|
|
||||||
|
|
||||||
* feat: Create a manifest list for `webapp` Docker image and push it to DockerHub. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/436
|
|
||||||
* feat: Combine arm64 and amd64 in one Github action runner by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/437
|
|
||||||
|
|
||||||
## [0.16.11] - 06-05-2024
|
## [0.16.11] - 06-05-2024
|
||||||
|
|
||||||
### improvements
|
### improvements
|
||||||
|
|||||||
@@ -3,17 +3,24 @@
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
proxy:
|
nginx:
|
||||||
image: envoyproxy/envoy:v1.30-latest
|
container_name: bigcapital-nginx-gateway
|
||||||
depends_on:
|
build:
|
||||||
- server
|
context: ./docker/nginx
|
||||||
- webapp
|
args:
|
||||||
|
- SERVER_PROXY_PORT=3000
|
||||||
|
- WEB_SSL=false
|
||||||
|
- SELF_SIGNED=false
|
||||||
|
volumes:
|
||||||
|
- ./data/logs/nginx/:/var/log/nginx
|
||||||
|
- ./docker/certbot/certs/:/var/certs
|
||||||
ports:
|
ports:
|
||||||
- '${PUBLIC_PROXY_PORT:-80}:80'
|
- '${PUBLIC_PROXY_PORT:-80}:80'
|
||||||
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
depends_on:
|
||||||
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
|
- server
|
||||||
|
- webapp
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
networks:
|
networks:
|
||||||
- bigcapital_network
|
- bigcapital_network
|
||||||
@@ -39,8 +46,6 @@ services:
|
|||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
environment:
|
environment:
|
||||||
# Mail
|
# Mail
|
||||||
- MAIL_HOST=${MAIL_HOST}
|
- MAIL_HOST=${MAIL_HOST}
|
||||||
@@ -88,17 +93,14 @@ services:
|
|||||||
- GOTENBERG_URL=${GOTENBERG_URL}
|
- GOTENBERG_URL=${GOTENBERG_URL}
|
||||||
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
|
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
|
||||||
|
|
||||||
# Exchange Rate
|
|
||||||
- EXCHANGE_RATE_SERVICE=${EXCHANGE_RATE_SERVICE}
|
|
||||||
- OPEN_EXCHANGE_RATE_APP_ID-${OPEN_EXCHANGE_RATE_APP_ID}
|
|
||||||
|
|
||||||
# Bank Sync
|
# Bank Sync
|
||||||
- BANKING_CONNECT=${BANKING_CONNECT}
|
- BANKING_CONNECT=${BANKING_CONNECT}
|
||||||
|
|
||||||
# Plaid
|
# Plaid
|
||||||
- PLAID_ENV=${PLAID_ENV}
|
- PLAID_ENV=${PLAID_ENV}
|
||||||
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
|
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
|
||||||
- PLAID_SECRET=${PLAID_SECRET}
|
- PLAID_SECRET_DEVELOPMENT=${PLAID_SECRET_DEVELOPMENT}
|
||||||
|
- PLAID_SECRET_SANDBOX=${PLAID_SECRET_SANDBOX}
|
||||||
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
|
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
|
||||||
|
|
||||||
# Lemon Squeez
|
# Lemon Squeez
|
||||||
@@ -122,6 +124,8 @@ services:
|
|||||||
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
||||||
- S3_ENDPOINT=${S3_ENDPOINT}
|
- S3_ENDPOINT=${S3_ENDPOINT}
|
||||||
- S3_BUCKET=${S3_BUCKET}
|
- S3_BUCKET=${S3_BUCKET}
|
||||||
|
networks:
|
||||||
|
- bigcapital_network
|
||||||
|
|
||||||
database_migration:
|
database_migration:
|
||||||
container_name: bigcapital-database-migration
|
container_name: bigcapital-database-migration
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
static_resources:
|
|
||||||
listeners:
|
|
||||||
- name: listener_0
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_value: 80
|
|
||||||
filter_chains:
|
|
||||||
- filters:
|
|
||||||
- name: envoy.filters.network.http_connection_manager
|
|
||||||
typed_config:
|
|
||||||
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
|
||||||
stat_prefix: ingress_http
|
|
||||||
route_config:
|
|
||||||
name: local_route
|
|
||||||
virtual_hosts:
|
|
||||||
- name: backend
|
|
||||||
domains: ['*']
|
|
||||||
routes:
|
|
||||||
- match:
|
|
||||||
prefix: '/api'
|
|
||||||
route:
|
|
||||||
cluster: dynamic_server
|
|
||||||
- match:
|
|
||||||
prefix: '/'
|
|
||||||
route:
|
|
||||||
cluster: webapp
|
|
||||||
http_filters:
|
|
||||||
- name: envoy.filters.http.router
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
|
||||||
|
|
||||||
clusters:
|
|
||||||
- name: dynamic_server
|
|
||||||
connect_timeout: 0.25s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: dynamic_server
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: server
|
|
||||||
port_value: 3000
|
|
||||||
|
|
||||||
- name: webapp
|
|
||||||
connect_timeout: 0.25s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: webapp
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: webapp
|
|
||||||
port_value: 80
|
|
||||||
21
docker/nginx/Dockerfile
Normal file
21
docker/nginx/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM nginx:1.11
|
||||||
|
|
||||||
|
RUN mkdir /etc/nginx/sites-available && rm /etc/nginx/conf.d/default.conf
|
||||||
|
ADD nginx.conf /etc/nginx/
|
||||||
|
|
||||||
|
COPY scripts /root/scripts/
|
||||||
|
COPY certs /etc/ssl/
|
||||||
|
|
||||||
|
COPY sites /etc/nginx/templates
|
||||||
|
|
||||||
|
ARG SERVER_PROXY_PORT=3000
|
||||||
|
ARG WEB_SSL=false
|
||||||
|
ARG SELF_SIGNED=false
|
||||||
|
|
||||||
|
ENV SERVER_PROXY_PORT=$SERVER_PROXY_PORT
|
||||||
|
ENV WEB_SSL=$WEB_SSL
|
||||||
|
ENV SELF_SIGNED=$SELF_SIGNED
|
||||||
|
|
||||||
|
RUN /bin/bash /root/scripts/build-nginx.sh
|
||||||
|
|
||||||
|
CMD nginx
|
||||||
0
docker/nginx/certs/.gitkeep
Normal file
0
docker/nginx/certs/.gitkeep
Normal file
33
docker/nginx/nginx.conf
Normal file
33
docker/nginx/nginx.conf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
user www-data;
|
||||||
|
worker_processes auto;
|
||||||
|
pid /run/nginx.pid;
|
||||||
|
daemon off;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 2048;
|
||||||
|
use epoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server_tokens off;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 15;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
open_file_cache max=100;
|
||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
include /etc/nginx/sites-available/*;
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
}
|
||||||
9
docker/nginx/scripts/build-nginx.sh
Normal file
9
docker/nginx/scripts/build-nginx.sh
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for conf in /etc/nginx/templates/*.conf; do
|
||||||
|
mv $conf "/etc/nginx/sites-available/"$(basename $conf) > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
for template in /etc/nginx/templates/*.template; do
|
||||||
|
envsubst < $template > "/etc/nginx/sites-available/"$(basename $template)".conf"
|
||||||
|
done
|
||||||
16
docker/nginx/sites/server.template
Normal file
16
docker/nginx/sites/server.template
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://server:${SERVER_PROXY_PORT};
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://webapp;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/letsencrypt/;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
"@supercharge/promise-pool": "^3.2.0",
|
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/i18n": "^0.8.7",
|
"@types/i18n": "^0.8.7",
|
||||||
"@types/knex": "^0.16.1",
|
"@types/knex": "^0.16.1",
|
||||||
|
|||||||
@@ -4,16 +4,12 @@ import { Router, Response, NextFunction, Request } from 'express';
|
|||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
|
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
|
||||||
import { AttachmentUploadPipeline } from '@/services/Attachments/S3UploadPipeline';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AttachmentsController extends BaseController {
|
export class AttachmentsController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private attachmentsApplication: AttachmentsApplication;
|
private attachmentsApplication: AttachmentsApplication;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uploadPipelineService: AttachmentUploadPipeline;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -22,8 +18,7 @@ export class AttachmentsController extends BaseController {
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
this.uploadPipelineService.validateS3Configured,
|
this.attachmentsApplication.uploadPipeline.single('file'),
|
||||||
this.uploadPipelineService.uploadPipeline().single('file'),
|
|
||||||
this.validateUploadedFileExistance,
|
this.validateUploadedFileExistance,
|
||||||
this.uploadAttachment.bind(this)
|
this.uploadAttachment.bind(this)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DashboardService from '@/services/Dashboard/DashboardService';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class DashboardMetaController {
|
export default class DashboardMetaController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private dashboardService: DashboardService;
|
dashboardService: DashboardService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor router.
|
* Constructor router.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { NextFunction, Router, Request, Response } from 'express';
|
import { Router } from 'express';
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
||||||
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
||||||
@@ -33,7 +34,7 @@ export class Webhooks extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
public async lemonWebhooks(req: Request, res: Response, next: any) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] ?? '';
|
const signature = req.headers['x-signature'] ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
@@ -56,25 +57,20 @@ export class Webhooks extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
public async plaidWebhooks(req: Request, res: Response, next: NextFunction) {
|
public async plaidWebhooks(req: Request, res: Response) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
const {
|
||||||
|
webhook_type: webhookType,
|
||||||
|
webhook_code: webhookCode,
|
||||||
|
item_id: plaidItemId,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
try {
|
await this.plaidApp.webhooks(
|
||||||
const {
|
tenantId,
|
||||||
webhook_type: webhookType,
|
plaidItemId,
|
||||||
webhook_code: webhookCode,
|
webhookType,
|
||||||
item_id: plaidItemId,
|
webhookCode
|
||||||
} = req.body;
|
);
|
||||||
|
return res.status(200).send({ code: 200, message: 'ok' });
|
||||||
await this.plaidApp.webhooks(
|
|
||||||
tenantId,
|
|
||||||
plaidItemId,
|
|
||||||
webhookType,
|
|
||||||
webhookCode
|
|
||||||
);
|
|
||||||
return res.status(200).send({ code: 200, message: 'ok' });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import color from 'colorette';
|
|||||||
import argv from 'getopts';
|
import argv from 'getopts';
|
||||||
import Knex from 'knex';
|
import Knex from 'knex';
|
||||||
import { knexSnakeCaseMappers } from 'objection';
|
import { knexSnakeCaseMappers } from 'objection';
|
||||||
import { PromisePool } from '@supercharge/promise-pool';
|
|
||||||
import '../before';
|
import '../before';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ function initSystemKnex() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTenantKnex(organizationId: string = '') {
|
function initTenantKnex(organizationId) {
|
||||||
return Knex({
|
return Knex({
|
||||||
client: config.tenant.db_client,
|
client: config.tenant.db_client,
|
||||||
connection: {
|
connection: {
|
||||||
@@ -72,12 +71,6 @@ function getAllSystemTenants(knex) {
|
|||||||
return knex('tenants');
|
return knex('tenants');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllInitializedTenants(knex) {
|
|
||||||
return knex('tenants').whereNotNull('initializedAt');
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIGRATION_CONCURRENCY = 10;
|
|
||||||
|
|
||||||
// module.exports = {
|
// module.exports = {
|
||||||
// log,
|
// log,
|
||||||
// success,
|
// success,
|
||||||
@@ -94,7 +87,6 @@ const MIGRATION_CONCURRENCY = 10;
|
|||||||
// - bigcapital tenants:migrate:make
|
// - bigcapital tenants:migrate:make
|
||||||
// - bigcapital system:migrate:make
|
// - bigcapital system:migrate:make
|
||||||
// - bigcapital tenants:list
|
// - bigcapital tenants:list
|
||||||
// - bigcapital tenants:list --all
|
|
||||||
|
|
||||||
commander
|
commander
|
||||||
.command('system:migrate:rollback')
|
.command('system:migrate:rollback')
|
||||||
@@ -153,13 +145,10 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:list')
|
.command('tenants:list')
|
||||||
.description('Retrieve a list of all system tenants databases.')
|
.description('Retrieve a list of all system tenants databases.')
|
||||||
.option('-a, --all', 'All tenants even are not initialized.')
|
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = cmd?.all
|
const tenants = await getAllSystemTenants(sysKnex);
|
||||||
? await getAllSystemTenants(sysKnex)
|
|
||||||
: await getAllInitializedTenants(sysKnex);
|
|
||||||
|
|
||||||
tenants.forEach((tenant) => {
|
tenants.forEach((tenant) => {
|
||||||
const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`;
|
const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`;
|
||||||
@@ -190,20 +179,18 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:migrate:latest')
|
.command('tenants:migrate:latest')
|
||||||
.description('Migrate all tenants or the given tenant id.')
|
.description('Migrate all tenants or the given tenant id.')
|
||||||
.option(
|
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
||||||
'-t, --tenant_id [tenant_id]',
|
|
||||||
'Which organization id do you migrate.'
|
|
||||||
)
|
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = await getAllInitializedTenants(sysKnex);
|
const tenants = await getAllSystemTenants(sysKnex);
|
||||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||||
|
|
||||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||||
}
|
}
|
||||||
// Validate the tenant id exist first of all.
|
// Validate the tenant id exist first of all.
|
||||||
|
const migrateOpers = [];
|
||||||
const migrateTenant = async (organizationId) => {
|
const migrateTenant = async (organizationId) => {
|
||||||
try {
|
try {
|
||||||
const tenantKnex = await initTenantKnex(organizationId);
|
const tenantKnex = await initTenantKnex(organizationId);
|
||||||
@@ -225,17 +212,18 @@ commander
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!cmd.tenant_id) {
|
if (!cmd.tenant_id) {
|
||||||
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
tenants.forEach((tenant) => {
|
||||||
.for(tenants)
|
const oper = migrateTenant(tenant.organizationId);
|
||||||
.process((tenant, index, pool) => {
|
migrateOpers.push(oper);
|
||||||
return migrateTenant(tenant.organizationId);
|
});
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
success('All tenants are migrated.');
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await migrateTenant(cmd.tenant_id);
|
const oper = migrateTenant(cmd.tenant_id);
|
||||||
|
migrateOpers.push(oper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Promise.all(migrateOpers).then(() => {
|
||||||
|
success('All tenants are migrated.');
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
exit(error);
|
exit(error);
|
||||||
}
|
}
|
||||||
@@ -244,21 +232,19 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:migrate:rollback')
|
.command('tenants:migrate:rollback')
|
||||||
.description('Rollback the last batch of tenants migrations.')
|
.description('Rollback the last batch of tenants migrations.')
|
||||||
.option(
|
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
||||||
'-t, --tenant_id [tenant_id]',
|
|
||||||
'Which organization id do you migrate.'
|
|
||||||
)
|
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = await getAllInitializedTenants(sysKnex);
|
const tenants = await getAllSystemTenants(sysKnex);
|
||||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||||
|
|
||||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrateTenant = async (organizationId: string) => {
|
const migrateOpers = [];
|
||||||
|
const migrateTenant = async (organizationId) => {
|
||||||
try {
|
try {
|
||||||
const tenantKnex = await initTenantKnex(organizationId);
|
const tenantKnex = await initTenantKnex(organizationId);
|
||||||
const [batchNo, _log] = await tenantKnex.migrate.rollback();
|
const [batchNo, _log] = await tenantKnex.migrate.rollback();
|
||||||
@@ -279,18 +265,19 @@ commander
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!cmd.tenant_id) {
|
if (!cmd.tenant_id) {
|
||||||
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
tenants.forEach((tenant) => {
|
||||||
.for(tenants)
|
const oper = migrateTenant(tenant.organizationId);
|
||||||
.process((tenant, index, pool) => {
|
migrateOpers.push(oper);
|
||||||
return migrateTenant(tenant.organizationId);
|
});
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
success('All tenants are rollbacked.');
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await migrateTenant(cmd.tenant_id);
|
const oper = migrateTenant(cmd.tenant_id);
|
||||||
|
migrateOpers.push(oper);
|
||||||
}
|
}
|
||||||
|
Promise.all(migrateOpers).then(() => {
|
||||||
|
success('All tenants are rollbacked.');
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
exit(error);
|
exit(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -235,6 +235,6 @@ module.exports = {
|
|||||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||||
endpoint: process.env.S3_ENDPOINT,
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
bucket: process.env.S3_BUCKET || 'bigcapital-documents',
|
bucket: process.env.S3_BUCKET,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.createTable('storage', (table) => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.string('key').notNullable();
|
|
||||||
table.string('path').notNullable();
|
|
||||||
table.string('extension').notNullable();
|
|
||||||
table.integer('expire_in');
|
|
||||||
table.timestamps();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
return knex.schema.dropTableIfExists('storage');
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.dropTableIfExists('storage');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {};
|
|
||||||
@@ -164,7 +164,3 @@ export enum TaxRateAction {
|
|||||||
DELETE = 'Delete',
|
DELETE = 'Delete',
|
||||||
VIEW = 'View',
|
VIEW = 'View',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateAccountParams {
|
|
||||||
ignoreUniqueName: boolean;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ export interface IGeneralLedgerSheetAccount {
|
|||||||
transactions: IGeneralLedgerSheetAccountTransaction[];
|
transactions: IGeneralLedgerSheetAccountTransaction[];
|
||||||
openingBalance: IGeneralLedgerSheetAccountBalance;
|
openingBalance: IGeneralLedgerSheetAccountBalance;
|
||||||
closingBalance: IGeneralLedgerSheetAccountBalance;
|
closingBalance: IGeneralLedgerSheetAccountBalance;
|
||||||
closingBalanceSubaccounts?: IGeneralLedgerSheetAccountBalance;
|
|
||||||
children?: IGeneralLedgerSheetAccount[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||||
|
|||||||
@@ -149,19 +149,13 @@ export class Transformer {
|
|||||||
return this.excludeAttributes().length > 0;
|
return this.excludeAttributes().length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
private dateFormat = 'YYYY MMM DD';
|
|
||||||
|
|
||||||
setDateFormat(format: string) {
|
|
||||||
this.dateFormat = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param date
|
* @param date
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected formatDate(date) {
|
protected formatDate(date) {
|
||||||
return date ? moment(date).format(this.dateFormat) : '';
|
return date ? moment(date).format('YYYY/MM/DD') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -199,7 +193,6 @@ export class Transformer {
|
|||||||
) {
|
) {
|
||||||
transformer.setOptions(options);
|
transformer.setOptions(options);
|
||||||
transformer.setContext(this.context);
|
transformer.setContext(this.context);
|
||||||
transformer.setDateFormat(this.dateFormat);
|
|
||||||
|
|
||||||
return transformer.work(obj);
|
return transformer.work(obj);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,17 +24,6 @@ export class TransformerInjectable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the given tenatn date format.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
async getTenantDateFormat(tenantId: number) {
|
|
||||||
const metadata = await TenantMetadata.query().findOne('tenantId', tenantId);
|
|
||||||
|
|
||||||
return metadata.dateFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the given transformer after inject the tenant context.
|
* Transformes the given transformer after inject the tenant context.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -52,11 +41,7 @@ export class TransformerInjectable {
|
|||||||
if (!isNull(tenantId)) {
|
if (!isNull(tenantId)) {
|
||||||
const context = await this.getApplicationContext(tenantId);
|
const context = await this.getApplicationContext(tenantId);
|
||||||
transformer.setContext(context);
|
transformer.setContext(context);
|
||||||
|
|
||||||
const dateFormat = await this.getTenantDateFormat(tenantId);
|
|
||||||
transformer.setDateFormat(dateFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transformer.setOptions(options);
|
transformer.setOptions(options);
|
||||||
|
|
||||||
return transformer.work(object);
|
return transformer.work(object);
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
*/
|
*/
|
||||||
private async updateUncategorizedTransactionCount(
|
private async updateUncategorizedTransactionCount(
|
||||||
queryContext: QueryContext,
|
queryContext: QueryContext,
|
||||||
increment: boolean,
|
increment: boolean
|
||||||
amount: number = 1
|
|
||||||
) {
|
) {
|
||||||
const operation = increment ? 'increment' : 'decrement';
|
const operation = increment ? 'increment' : 'decrement';
|
||||||
|
const amount = increment ? 1 : -1;
|
||||||
|
|
||||||
await Account.query(queryContext.transaction)
|
await Account.query(queryContext.transaction)
|
||||||
.findById(this.accountId)
|
.findById(this.accountId)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class Ledger implements ILedger {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters entries by the given accounts ids then returns a new ledger.
|
* Filters entries by the given accounts ids then returns a new ledger.
|
||||||
* @param {number[]} accountIds
|
* @param {number[]} accountIds
|
||||||
* @returns {ILedger}
|
* @returns {ILedger}
|
||||||
*/
|
*/
|
||||||
public whereAccountsIds(accountIds: number[]): ILedger {
|
public whereAccountsIds(accountIds: number[]): ILedger {
|
||||||
@@ -274,14 +274,4 @@ export default class Ledger implements ILedger {
|
|||||||
const entries = Ledger.mappingTransactions(transactions);
|
const entries = Ledger.mappingTransactions(transactions);
|
||||||
return new Ledger(entries);
|
return new Ledger(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction amount.
|
|
||||||
* @param {number} credit - Credit amount.
|
|
||||||
* @param {number} debit - Debit amount.
|
|
||||||
* @param {string} normal - Credit or debit.
|
|
||||||
*/
|
|
||||||
static getAmount(credit: number, debit: number, normal: string) {
|
|
||||||
return normal === 'credit' ? credit - debit : debit - credit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
IAccountEventCreatedPayload,
|
IAccountEventCreatedPayload,
|
||||||
IAccountEventCreatingPayload,
|
IAccountEventCreatingPayload,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
CreateAccountParams,
|
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
@@ -31,22 +30,19 @@ export class CreateAccount {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize the account creation.
|
* Authorize the account creation.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
*/
|
*/
|
||||||
private authorize = async (
|
private authorize = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO,
|
||||||
baseCurrency: string,
|
baseCurrency: string
|
||||||
params?: CreateAccountParams
|
|
||||||
) => {
|
) => {
|
||||||
// Validate account name uniquiness.
|
// Validate account name uniquiness.
|
||||||
if (!params.ignoreUniqueName) {
|
await this.validator.validateAccountNameUniquiness(
|
||||||
await this.validator.validateAccountNameUniquiness(
|
tenantId,
|
||||||
tenantId,
|
accountDTO.name
|
||||||
accountDTO.name
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
// Validate the account code uniquiness.
|
// Validate the account code uniquiness.
|
||||||
if (accountDTO.code) {
|
if (accountDTO.code) {
|
||||||
await this.validator.isAccountCodeUniqueOrThrowError(
|
await this.validator.isAccountCodeUniqueOrThrowError(
|
||||||
@@ -86,7 +82,7 @@ export class CreateAccount {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the create account DTO to input model.
|
* Transformes the create account DTO to input model.
|
||||||
* @param {IAccountCreateDTO} createAccountDTO
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
*/
|
*/
|
||||||
private transformDTOToModel = (
|
private transformDTOToModel = (
|
||||||
createAccountDTO: IAccountCreateDTO,
|
createAccountDTO: IAccountCreateDTO,
|
||||||
@@ -108,8 +104,7 @@ export class CreateAccount {
|
|||||||
public createAccount = async (
|
public createAccount = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO,
|
||||||
trx?: Knex.Transaction,
|
trx?: Knex.Transaction
|
||||||
params: CreateAccountParams = { ignoreUniqueName: false }
|
|
||||||
): Promise<IAccount> => {
|
): Promise<IAccount> => {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -117,12 +112,8 @@ export class CreateAccount {
|
|||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Authorize the account creation.
|
// Authorize the account creation.
|
||||||
await this.authorize(
|
await this.authorize(tenantId, accountDTO, tenantMeta.baseCurrency);
|
||||||
tenantId,
|
|
||||||
accountDTO,
|
|
||||||
tenantMeta.baseCurrency,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
// Transformes the DTO to model.
|
// Transformes the DTO to model.
|
||||||
const accountInputModel = this.transformDTOToModel(
|
const accountInputModel = this.transformDTOToModel(
|
||||||
accountDTO,
|
accountDTO,
|
||||||
@@ -157,4 +148,3 @@ export class CreateAccount {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { UploadDocument } from './UploadDocument';
|
import { UploadDocument } from './UploadDocument';
|
||||||
import { DeleteAttachment } from './DeleteAttachment';
|
import { DeleteAttachment } from './DeleteAttachment';
|
||||||
import { GetAttachment } from './GetAttachment';
|
import { GetAttachment } from './GetAttachment';
|
||||||
|
import { AttachmentUploadPipeline } from './S3UploadPipeline';
|
||||||
import { LinkAttachment } from './LinkAttachment';
|
import { LinkAttachment } from './LinkAttachment';
|
||||||
import { UnlinkAttachment } from './UnlinkAttachment';
|
import { UnlinkAttachment } from './UnlinkAttachment';
|
||||||
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
|
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
|
||||||
|
import type { Multer } from 'multer';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AttachmentsApplication {
|
export class AttachmentsApplication {
|
||||||
@@ -17,6 +19,9 @@ export class AttachmentsApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getDocumentService: GetAttachment;
|
private getDocumentService: GetAttachment;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uploadPipelineService: AttachmentUploadPipeline;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private linkDocumentService: LinkAttachment;
|
private linkDocumentService: LinkAttachment;
|
||||||
|
|
||||||
@@ -26,6 +31,14 @@ export class AttachmentsApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getPresignedUrlService: getAttachmentPresignedUrl;
|
private getPresignedUrlService: getAttachmentPresignedUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express middleware for uploading attachments to an S3 bucket.
|
||||||
|
* @returns {Multer}
|
||||||
|
*/
|
||||||
|
get uploadPipeline(): Multer {
|
||||||
|
return this.uploadPipelineService.uploadPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the metadata of uploaded document to S3 on database.
|
* Saves the metadata of uploaded document to S3 on database.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
|
|||||||
@@ -1,38 +1,12 @@
|
|||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import type { Multer } from 'multer';
|
import type { Multer } from 'multer'
|
||||||
import multerS3 from 'multer-s3';
|
import multerS3 from 'multer-s3';
|
||||||
import { s3 } from '@/lib/S3/S3';
|
import { s3 } from '@/lib/S3/S3';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AttachmentUploadPipeline {
|
export class AttachmentUploadPipeline {
|
||||||
/**
|
|
||||||
* Middleware to ensure that S3 configuration is properly set before proceeding.
|
|
||||||
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
|
|
||||||
*
|
|
||||||
* @param req The HTTP request object.
|
|
||||||
* @param res The HTTP response object.
|
|
||||||
* @param next The callback to pass control to the next middleware function.
|
|
||||||
*/
|
|
||||||
public validateS3Configured(req: Request, res: Response, next: NextFunction) {
|
|
||||||
if (
|
|
||||||
!config.s3.region ||
|
|
||||||
!config.s3.accessKeyId ||
|
|
||||||
!config.s3.secretAccessKey
|
|
||||||
) {
|
|
||||||
const missingKeys = [];
|
|
||||||
if (!config.s3.region) missingKeys.push('region');
|
|
||||||
if (!config.s3.accessKeyId) missingKeys.push('accessKeyId');
|
|
||||||
if (!config.s3.secretAccessKey) missingKeys.push('secretAccessKey');
|
|
||||||
const missing = missingKeys.join(', ');
|
|
||||||
|
|
||||||
throw new Error(`S3 configuration error: Missing ${missing}`);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Express middleware for uploading attachments to an S3 bucket.
|
* Express middleware for uploading attachments to an S3 bucket.
|
||||||
* It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads.
|
* It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Container, Inject } from 'typedi';
|
import { Container, Inject } from 'typedi';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { SystemUser, Tenant } from '@/system/models';
|
import { Tenant } from '@/system/models';
|
||||||
import {
|
import {
|
||||||
IAuthSignedInEventPayload,
|
IAuthSignedInEventPayload,
|
||||||
IAuthSigningInEventPayload,
|
IAuthSigningInEventPayload,
|
||||||
@@ -64,9 +64,7 @@ export class AuthSigninService {
|
|||||||
const { systemUserRepository } = this.sysRepositories;
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
|
||||||
// Finds the user of the given email address.
|
// Finds the user of the given email address.
|
||||||
const user = await SystemUser.query()
|
const user = await systemUserRepository.findOneByEmail(email);
|
||||||
.findOne('email', email)
|
|
||||||
.modify('inviteAccepted');
|
|
||||||
|
|
||||||
// Validate the given email and password.
|
// Validate the given email and password.
|
||||||
await this.validateSignIn(user, email, password);
|
await this.validateSignIn(user, email, password);
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import { entries, groupBy } from 'lodash';
|
import { entries, groupBy } from 'lodash';
|
||||||
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
||||||
import {
|
import { PlaidAccount, PlaidTransaction } from '@/interfaces';
|
||||||
IAccountCreateDTO,
|
|
||||||
PlaidAccount,
|
|
||||||
PlaidTransaction,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import {
|
import {
|
||||||
transformPlaidAccountToCreateAccount,
|
transformPlaidAccountToCreateAccount,
|
||||||
transformPlaidTrxsToCashflowCreate,
|
transformPlaidTrxsToCashflowCreate,
|
||||||
@@ -15,7 +11,6 @@ import {
|
|||||||
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
const CONCURRENCY_ASYNC = 10;
|
const CONCURRENCY_ASYNC = 10;
|
||||||
|
|
||||||
@@ -33,35 +28,6 @@ export class PlaidSyncDb {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
||||||
|
|
||||||
/**
|
|
||||||
* Syncs the Plaid bank account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IAccountCreateDTO} createBankAccountDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async syncBankAccount(
|
|
||||||
tenantId: number,
|
|
||||||
createBankAccountDTO: IAccountCreateDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
const plaidAccount = await Account.query().findOne(
|
|
||||||
'plaidAccountId',
|
|
||||||
createBankAccountDTO.plaidAccountId
|
|
||||||
);
|
|
||||||
// Can't continue if the Plaid account is already created.
|
|
||||||
if (plaidAccount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.createAccountService.createAccount(
|
|
||||||
tenantId,
|
|
||||||
createBankAccountDTO,
|
|
||||||
trx,
|
|
||||||
{ ignoreUniqueName: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs the plaid accounts to the system accounts.
|
* Syncs the plaid accounts to the system accounts.
|
||||||
* @param {number} tenantId Tenant ID.
|
* @param {number} tenantId Tenant ID.
|
||||||
@@ -71,8 +37,7 @@ export class PlaidSyncDb {
|
|||||||
public async syncBankAccounts(
|
public async syncBankAccounts(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccounts: PlaidAccount[],
|
plaidAccounts: PlaidAccount[],
|
||||||
institution: any,
|
institution: any
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const transformToPlaidAccounts =
|
const transformToPlaidAccounts =
|
||||||
transformPlaidAccountToCreateAccount(institution);
|
transformPlaidAccountToCreateAccount(institution);
|
||||||
@@ -82,7 +47,7 @@ export class PlaidSyncDb {
|
|||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
accountCreateDTOs,
|
accountCreateDTOs,
|
||||||
(createAccountDTO: any) =>
|
(createAccountDTO: any) =>
|
||||||
this.syncBankAccount(tenantId, createAccountDTO, trx),
|
this.createAccountService.createAccount(tenantId, createAccountDTO),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -96,16 +61,15 @@ export class PlaidSyncDb {
|
|||||||
public async syncAccountTranactions(
|
public async syncAccountTranactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountId: number,
|
plaidAccountId: number,
|
||||||
plaidTranasctions: PlaidTransaction[],
|
plaidTranasctions: PlaidTransaction[]
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const cashflowAccount = await Account.query(trx)
|
const cashflowAccount = await Account.query()
|
||||||
.findOne({ plaidAccountId })
|
.findOne({ plaidAccountId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const openingEquityBalance = await Account.query(trx).findOne(
|
const openingEquityBalance = await Account.query().findOne(
|
||||||
'slug',
|
'slug',
|
||||||
'opening-balance-equity'
|
'opening-balance-equity'
|
||||||
);
|
);
|
||||||
@@ -123,8 +87,7 @@ export class PlaidSyncDb {
|
|||||||
(uncategoriedDTO) =>
|
(uncategoriedDTO) =>
|
||||||
this.cashflowApp.createUncategorizedTransaction(
|
this.cashflowApp.createUncategorizedTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategoriedDTO,
|
uncategoriedDTO
|
||||||
trx
|
|
||||||
),
|
),
|
||||||
{ concurrency: 1 }
|
{ concurrency: 1 }
|
||||||
);
|
);
|
||||||
@@ -137,8 +100,7 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncAccountsTransactions(
|
public async syncAccountsTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountsTransactions: PlaidTransaction[],
|
plaidAccountsTransactions: PlaidTransaction[]
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const groupedTrnsxByAccountId = entries(
|
const groupedTrnsxByAccountId = entries(
|
||||||
groupBy(plaidAccountsTransactions, 'account_id')
|
groupBy(plaidAccountsTransactions, 'account_id')
|
||||||
@@ -149,8 +111,7 @@ export class PlaidSyncDb {
|
|||||||
return this.syncAccountTranactions(
|
return this.syncAccountTranactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
plaidAccountId,
|
plaidAccountId,
|
||||||
plaidTransactions,
|
plaidTransactions
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
@@ -163,12 +124,11 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncRemoveTransactions(
|
public async syncRemoveTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidTransactionsIds: string[],
|
plaidTransactionsIds: string[]
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const cashflowTransactions = await CashflowTransaction.query(trx).whereIn(
|
const cashflowTransactions = await CashflowTransaction.query().whereIn(
|
||||||
'plaidTransactionId',
|
'plaidTransactionId',
|
||||||
plaidTransactionsIds
|
plaidTransactionsIds
|
||||||
);
|
);
|
||||||
@@ -180,8 +140,7 @@ export class PlaidSyncDb {
|
|||||||
(transactionId: number) =>
|
(transactionId: number) =>
|
||||||
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionId,
|
transactionId
|
||||||
trx
|
|
||||||
),
|
),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
@@ -196,12 +155,11 @@ export class PlaidSyncDb {
|
|||||||
public async syncTransactionsCursor(
|
public async syncTransactionsCursor(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidItemId: string,
|
plaidItemId: string,
|
||||||
lastCursor: string,
|
lastCursor: string
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
const { PlaidItem } = this.tenancy.models(tenantId);
|
const { PlaidItem } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await PlaidItem.query(trx).findOne({ plaidItemId }).patch({ lastCursor });
|
await PlaidItem.query().findOne({ plaidItemId }).patch({ lastCursor });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,16 +169,13 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async updateLastFeedsUpdatedAt(
|
public async updateLastFeedsUpdatedAt(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountIds: string[],
|
plaidAccountIds: string[]
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await Account.query(trx)
|
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
||||||
.whereIn('plaid_account_id', plaidAccountIds)
|
lastFeedsUpdatedAt: new Date(),
|
||||||
.patch({
|
});
|
||||||
lastFeedsUpdatedAt: new Date(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,15 +187,12 @@ export class PlaidSyncDb {
|
|||||||
public async updateAccountsFeedsActive(
|
public async updateAccountsFeedsActive(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountIds: string[],
|
plaidAccountIds: string[],
|
||||||
isFeedsActive: boolean = true,
|
isFeedsActive: boolean = true
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await Account.query(trx)
|
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
||||||
.whereIn('plaid_account_id', plaidAccountIds)
|
isFeedsActive,
|
||||||
.patch({
|
});
|
||||||
isFeedsActive,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
||||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||||
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PlaidUpdateTransactions {
|
export class PlaidUpdateTransactions {
|
||||||
@@ -14,40 +12,12 @@ export class PlaidUpdateTransactions {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private plaidSync: PlaidSyncDb;
|
private plaidSync: PlaidSyncDb;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
* Handles the fetching and storing of new, modified, or removed transactions
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId Tenant ID.
|
||||||
* @param {number} plaidItemId
|
* @param {string} plaidItemId the Plaid ID for the item.
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
|
||||||
*/
|
*/
|
||||||
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
||||||
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
|
||||||
return this.updateTransactionsWork(tenantId, plaidItemId, trx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the fetching and storing the following:
|
|
||||||
* - New, modified, or removed transactions.
|
|
||||||
* - New bank accounts.
|
|
||||||
* - Last accounts feeds updated at.
|
|
||||||
* - Turn on the accounts feed flag.
|
|
||||||
* @param {number} tenantId - Tenant ID.
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
|
||||||
*/
|
|
||||||
public async updateTransactionsWork(
|
|
||||||
tenantId: number,
|
|
||||||
plaidItemId: string,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<{
|
|
||||||
addedCount: number;
|
|
||||||
modifiedCount: number;
|
|
||||||
removedCount: number;
|
|
||||||
}> {
|
|
||||||
// Fetch new transactions from plaid api.
|
// Fetch new transactions from plaid api.
|
||||||
const { added, modified, removed, cursor, accessToken } =
|
const { added, modified, removed, cursor, accessToken } =
|
||||||
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
||||||
@@ -59,42 +29,28 @@ export class PlaidUpdateTransactions {
|
|||||||
} = await plaidInstance.accountsGet(request);
|
} = await plaidInstance.accountsGet(request);
|
||||||
|
|
||||||
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { institution },
|
data: { institution },
|
||||||
} = await plaidInstance.institutionsGetById({
|
} = await plaidInstance.institutionsGetById({
|
||||||
institution_id: item.institution_id,
|
institution_id: item.institution_id,
|
||||||
country_codes: ['US', 'UK'],
|
country_codes: ['US', 'UK'],
|
||||||
});
|
});
|
||||||
// Sync bank accounts.
|
// Update the DB.
|
||||||
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution, trx);
|
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution);
|
||||||
// Sync bank account transactions.
|
|
||||||
await this.plaidSync.syncAccountsTransactions(
|
await this.plaidSync.syncAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
added.concat(modified),
|
added.concat(modified)
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Sync removed transactions.
|
|
||||||
await this.plaidSync.syncRemoveTransactions(tenantId, removed, trx);
|
|
||||||
// Sync transactions cursor.
|
|
||||||
await this.plaidSync.syncTransactionsCursor(
|
|
||||||
tenantId,
|
|
||||||
plaidItemId,
|
|
||||||
cursor,
|
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
|
await this.plaidSync.syncRemoveTransactions(tenantId, removed);
|
||||||
|
await this.plaidSync.syncTransactionsCursor(tenantId, plaidItemId, cursor);
|
||||||
|
|
||||||
// Update the last feeds updated at of the updated accounts.
|
// Update the last feeds updated at of the updated accounts.
|
||||||
await this.plaidSync.updateLastFeedsUpdatedAt(
|
await this.plaidSync.updateLastFeedsUpdatedAt(tenantId, plaidAccountsIds);
|
||||||
tenantId,
|
|
||||||
plaidAccountsIds,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Turn on the accounts feeds flag.
|
// Turn on the accounts feeds flag.
|
||||||
await this.plaidSync.updateAccountsFeedsActive(
|
await this.plaidSync.updateAccountsFeedsActive(tenantId, plaidAccountsIds);
|
||||||
tenantId,
|
|
||||||
plaidAccountsIds,
|
|
||||||
true,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
addedCount: added.length,
|
addedCount: added.length,
|
||||||
modifiedCount: modified.length,
|
modifiedCount: modified.length,
|
||||||
|
|||||||
@@ -42,12 +42,7 @@ export const transformPlaidTrxsToCashflowCreate = R.curry(
|
|||||||
): CreateUncategorizedTransactionDTO => {
|
): CreateUncategorizedTransactionDTO => {
|
||||||
return {
|
return {
|
||||||
date: plaidTranasction.date,
|
date: plaidTranasction.date,
|
||||||
|
amount: plaidTranasction.amount,
|
||||||
// Plaid: Positive values when money moves out of the account; negative values
|
|
||||||
// when money moves in. For example, debit card purchases are positive;
|
|
||||||
// credit card payments, direct deposits, and refunds are negative.
|
|
||||||
amount: -1 * plaidTranasction.amount,
|
|
||||||
|
|
||||||
description: plaidTranasction.name,
|
description: plaidTranasction.name,
|
||||||
payee: plaidTranasction.payment_meta?.payee,
|
payee: plaidTranasction.payment_meta?.payee,
|
||||||
currencyCode: plaidTranasction.iso_currency_code,
|
currencyCode: plaidTranasction.iso_currency_code,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { DeleteCashflowTransaction } from './DeleteCashflowTransactionService';
|
import { DeleteCashflowTransaction } from './DeleteCashflowTransactionService';
|
||||||
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
||||||
@@ -120,13 +119,11 @@ export class CashflowApplication {
|
|||||||
*/
|
*/
|
||||||
public createUncategorizedTransaction(
|
public createUncategorizedTransaction(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
|
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
return this.createUncategorizedTransactionService.create(
|
return this.createUncategorizedTransactionService.create(
|
||||||
tenantId,
|
tenantId,
|
||||||
createUncategorizedTransactionDTO,
|
createUncategorizedTransactionDTO
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,7 @@ export class CashflowTransactionTransformer extends Transformer {
|
|||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return ['formattedAmount', 'transactionTypeFormatted'];
|
||||||
'formattedAmount',
|
|
||||||
'transactionTypeFormatted',
|
|
||||||
'formattedDate',
|
|
||||||
'formattedCreatedAt',
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,28 +24,10 @@ export class CashflowTransactionTransformer extends Transformer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Formatted transaction type.
|
* Formatted transaction type.
|
||||||
* @param transaction
|
* @param transaction
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected transactionTypeFormatted = (transaction) => {
|
protected transactionTypeFormatted = (transaction) => {
|
||||||
return this.context.i18n.__(transaction.transactionTypeFormatted);
|
return this.context.i18n.__(transaction.transactionTypeFormatted);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the formatted transaction date.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedDate = (invoice): string => {
|
|
||||||
return this.formatDate(invoice.date);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the formatted created at date.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (invoice): string => {
|
|
||||||
return this.formatDate(invoice.createdAt);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Knex } from 'knex';
|
|||||||
import { transformCategorizeTransToCashflow } from './utils';
|
import { transformCategorizeTransToCashflow } from './utils';
|
||||||
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
||||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
||||||
|
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CategorizeCashflowTransaction {
|
export class CategorizeCashflowTransaction {
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ export class DeleteCashflowTransaction {
|
|||||||
*/
|
*/
|
||||||
public deleteCashflowTransaction = async (
|
public deleteCashflowTransaction = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
cashflowTransactionId: number,
|
cashflowTransactionId: number
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<{ oldCashflowTransaction: ICashflowTransaction }> => {
|
): Promise<{ oldCashflowTransaction: ICashflowTransaction }> => {
|
||||||
const { CashflowTransaction, CashflowTransactionLine } =
|
const { CashflowTransaction, CashflowTransactionLine } =
|
||||||
this.tenancy.models(tenantId);
|
this.tenancy.models(tenantId);
|
||||||
@@ -44,44 +43,34 @@ export class DeleteCashflowTransaction {
|
|||||||
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
|
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
|
||||||
|
|
||||||
// Starting database transaction.
|
// Starting database transaction.
|
||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onCashflowTransactionDelete` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleting, {
|
||||||
// Triggers `onCashflowTransactionDelete` event.
|
trx,
|
||||||
await this.eventPublisher.emitAsync(
|
tenantId,
|
||||||
events.cashflow.onTransactionDeleting,
|
oldCashflowTransaction,
|
||||||
{
|
} as ICommandCashflowDeletingPayload);
|
||||||
trx,
|
|
||||||
tenantId,
|
|
||||||
oldCashflowTransaction,
|
|
||||||
} as ICommandCashflowDeletingPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete cashflow transaction associated lines first.
|
// Delete cashflow transaction associated lines first.
|
||||||
await CashflowTransactionLine.query(trx)
|
await CashflowTransactionLine.query(trx)
|
||||||
.where('cashflow_transaction_id', cashflowTransactionId)
|
.where('cashflow_transaction_id', cashflowTransactionId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
// Delete cashflow transaction.
|
// Delete cashflow transaction.
|
||||||
await CashflowTransaction.query(trx)
|
await CashflowTransaction.query(trx)
|
||||||
.findById(cashflowTransactionId)
|
.findById(cashflowTransactionId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
// Triggers `onCashflowTransactionDeleted` event.
|
// Triggers `onCashflowTransactionDeleted` event.
|
||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleted, {
|
||||||
events.cashflow.onTransactionDeleted,
|
trx,
|
||||||
{
|
tenantId,
|
||||||
trx,
|
cashflowTransactionId,
|
||||||
tenantId,
|
oldCashflowTransaction,
|
||||||
cashflowTransactionId,
|
} as ICommandCashflowDeletedPayload);
|
||||||
oldCashflowTransaction,
|
|
||||||
} as ICommandCashflowDeletedPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
return { oldCashflowTransaction };
|
return { oldCashflowTransaction };
|
||||||
},
|
});
|
||||||
trx
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ export const CASHFLOW_TRANSACTION_TYPE_META = {
|
|||||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
||||||
type: 'OtherExpense',
|
type: 'OtherExpense',
|
||||||
direction: CASHFLOW_DIRECTION.OUT,
|
direction: CASHFLOW_DIRECTION.OUT,
|
||||||
creditType: [
|
creditType: [ACCOUNT_TYPE.EXPENSE, ACCOUNT_TYPE.OTHER_EXPENSE],
|
||||||
ACCOUNT_TYPE.EXPENSE,
|
|
||||||
ACCOUNT_TYPE.OTHER_EXPENSE,
|
|
||||||
ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { upperFirst, camelCase } from 'lodash';
|
import { upperFirst, camelCase, omit } from 'lodash';
|
||||||
import {
|
import {
|
||||||
CASHFLOW_TRANSACTION_TYPE,
|
CASHFLOW_TRANSACTION_TYPE,
|
||||||
CASHFLOW_TRANSACTION_TYPE_META,
|
CASHFLOW_TRANSACTION_TYPE_META,
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
import {
|
||||||
ICashflowNewCommandDTO,
|
ICashflowNewCommandDTO,
|
||||||
|
ICashflowTransaction,
|
||||||
ICategorizeCashflowTransactioDTO,
|
ICategorizeCashflowTransactioDTO,
|
||||||
IUncategorizedCashflowTransaction,
|
IUncategorizedCashflowTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
@@ -41,8 +42,8 @@ export const getCashflowAccountTransactionsTypes = () => {
|
|||||||
/**
|
/**
|
||||||
* Tranasformes the given uncategorized transaction and categorized DTO
|
* Tranasformes the given uncategorized transaction and categorized DTO
|
||||||
* to cashflow create DTO.
|
* to cashflow create DTO.
|
||||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
||||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
||||||
* @returns {ICashflowNewCommandDTO}
|
* @returns {ICashflowNewCommandDTO}
|
||||||
*/
|
*/
|
||||||
export const transformCategorizeTransToCashflow = (
|
export const transformCategorizeTransToCashflow = (
|
||||||
@@ -61,7 +62,6 @@ export const transformCategorizeTransToCashflow = (
|
|||||||
transactionNumber: categorizeDTO.transactionNumber,
|
transactionNumber: categorizeDTO.transactionNumber,
|
||||||
transactionType: categorizeDTO.transactionType,
|
transactionType: categorizeDTO.transactionType,
|
||||||
uncategorizedTransactionId: uncategorizeModel.id,
|
uncategorizedTransactionId: uncategorizeModel.id,
|
||||||
branchId: categorizeDTO?.branchId,
|
|
||||||
publish: true,
|
publish: true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
return [
|
return [
|
||||||
'formattedCreditsRemaining',
|
'formattedCreditsRemaining',
|
||||||
'formattedCreditNoteDate',
|
'formattedCreditNoteDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedCreditsUsed',
|
'formattedCreditsUsed',
|
||||||
'formattedSubtotal',
|
'formattedSubtotal',
|
||||||
@@ -32,15 +30,6 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
return this.formatDate(credit.creditNoteDate);
|
return this.formatDate(credit.creditNoteDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted created at date.
|
|
||||||
* @param credit
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (credit): string => {
|
|
||||||
return this.formatDate(credit.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted invoice amount.
|
* Retrieve formatted invoice amount.
|
||||||
* @param {ICreditNote} credit
|
* @param {ICreditNote} credit
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
'formattedLandedCostAmount',
|
'formattedLandedCostAmount',
|
||||||
'formattedAllocatedCostAmount',
|
'formattedAllocatedCostAmount',
|
||||||
'formattedDate',
|
'formattedDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'categories',
|
'categories',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -63,15 +62,6 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
return this.formatDate(expense.paymentDate);
|
return this.formatDate(expense.paymentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted created at date.
|
|
||||||
* @param {IExpense} expense
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (expense: IExpense): string => {
|
|
||||||
return this.formatDate(expense.createdAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the transformed expense categories.
|
* Retrieves the transformed expense categories.
|
||||||
* @param {IExpense} expense
|
* @param {IExpense} expense
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import { BalanceSheetPercentage } from './BalanceSheetPercentage';
|
|||||||
import { BalanceSheetSchema } from './BalanceSheetSchema';
|
import { BalanceSheetSchema } from './BalanceSheetSchema';
|
||||||
import { BalanceSheetBase } from './BalanceSheetBase';
|
import { BalanceSheetBase } from './BalanceSheetBase';
|
||||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||||
import { flatToNestedArray } from '@/utils';
|
|
||||||
import BalanceSheetRepository from './BalanceSheetRepository';
|
|
||||||
|
|
||||||
export const BalanceSheetAccounts = (Base: any) =>
|
export const BalanceSheetAccounts = (Base: any) =>
|
||||||
class extends R.compose(
|
class extends R.compose(
|
||||||
@@ -58,11 +56,6 @@ export const BalanceSheetAccounts = (Base: any) =>
|
|||||||
*/
|
*/
|
||||||
readonly i18n: any;
|
readonly i18n: any;
|
||||||
|
|
||||||
/**
|
|
||||||
* Balance sheet repository.
|
|
||||||
*/
|
|
||||||
readonly repository: BalanceSheetRepository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the accounts node of accounts types.
|
* Retrieve the accounts node of accounts types.
|
||||||
* @param {string} accountsTypes
|
* @param {string} accountsTypes
|
||||||
@@ -85,12 +78,8 @@ export const BalanceSheetAccounts = (Base: any) =>
|
|||||||
private reportSchemaAccountNodeMapper = (
|
private reportSchemaAccountNodeMapper = (
|
||||||
account: IAccount
|
account: IAccount
|
||||||
): IBalanceSheetAccountNode => {
|
): IBalanceSheetAccountNode => {
|
||||||
const childrenAccountsIds = this.repository.accountsGraph.dependenciesOf(
|
|
||||||
account.id
|
|
||||||
);
|
|
||||||
const accountIds = R.uniq(R.append(account.id, childrenAccountsIds));
|
|
||||||
const total = this.repository.totalAccountsLedger
|
const total = this.repository.totalAccountsLedger
|
||||||
.whereAccountsIds(accountIds)
|
.whereAccountId(account.id)
|
||||||
.getClosingBalance();
|
.getClosingBalance();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -139,19 +128,8 @@ export const BalanceSheetAccounts = (Base: any) =>
|
|||||||
private getAccountsNodesByAccountTypes = (
|
private getAccountsNodesByAccountTypes = (
|
||||||
accountsTypes: string[]
|
accountsTypes: string[]
|
||||||
): IBalanceSheetAccountNode[] => {
|
): IBalanceSheetAccountNode[] => {
|
||||||
// Retrieves accounts from the given defined node account types.
|
|
||||||
const accounts = this.getAccountsByAccountTypes(accountsTypes);
|
const accounts = this.getAccountsByAccountTypes(accountsTypes);
|
||||||
|
return R.map(this.reportSchemaAccountNodeComposer, accounts);
|
||||||
// Converts the flatten accounts to tree.
|
|
||||||
const accountsTree = flatToNestedArray(accounts, {
|
|
||||||
id: 'id',
|
|
||||||
parentId: 'parentAccountId',
|
|
||||||
});
|
|
||||||
// Maps over the accounts tree.
|
|
||||||
return this.mapNodesDeep(
|
|
||||||
accountsTree,
|
|
||||||
this.reportSchemaAccountNodeComposer
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
|
import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod';
|
||||||
|
import { FinancialHorizTotals } from '../FinancialHorizTotals';
|
||||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||||
import {
|
import {
|
||||||
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as R from 'ramda';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
|
IAccount,
|
||||||
IAccountTransactionsGroupBy,
|
IAccountTransactionsGroupBy,
|
||||||
IBalanceSheetQuery,
|
IBalanceSheetQuery,
|
||||||
ILedger,
|
ILedger,
|
||||||
@@ -11,6 +12,7 @@ import { transformToMapBy } from 'utils';
|
|||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||||
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
||||||
|
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -38,11 +40,6 @@ export default class BalanceSheetRepository extends R.compose(
|
|||||||
*/
|
*/
|
||||||
public accounts: any;
|
public accounts: any;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {}
|
|
||||||
*/
|
|
||||||
public accountsGraph: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -166,8 +163,6 @@ export default class BalanceSheetRepository extends R.compose(
|
|||||||
*/
|
*/
|
||||||
public asyncInitialize = async () => {
|
public asyncInitialize = async () => {
|
||||||
await this.initAccounts();
|
await this.initAccounts();
|
||||||
await this.initAccountsGraph();
|
|
||||||
|
|
||||||
await this.initAccountsTotalLedger();
|
await this.initAccountsTotalLedger();
|
||||||
|
|
||||||
// Date periods.
|
// Date periods.
|
||||||
@@ -209,15 +204,6 @@ export default class BalanceSheetRepository extends R.compose(
|
|||||||
this.accountsByParentType = transformToMapBy(accounts, 'accountParentType');
|
this.accountsByParentType = transformToMapBy(accounts, 'accountParentType');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize accounts graph.
|
|
||||||
*/
|
|
||||||
public initAccountsGraph = async () => {
|
|
||||||
const { Account } = this.models;
|
|
||||||
|
|
||||||
this.accountsGraph = Account.toDependencyGraph(this.accounts);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// # Closing Total
|
// # Closing Total
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
import { isEmpty, get, last, sumBy, first, head } from 'lodash';
|
import { isEmpty, get, last, sumBy } from 'lodash';
|
||||||
import moment from 'moment';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import {
|
import {
|
||||||
IGeneralLedgerSheetQuery,
|
IGeneralLedgerSheetQuery,
|
||||||
IGeneralLedgerSheetAccount,
|
IGeneralLedgerSheetAccount,
|
||||||
IGeneralLedgerSheetAccountBalance,
|
IGeneralLedgerSheetAccountBalance,
|
||||||
IGeneralLedgerSheetAccountTransaction,
|
IGeneralLedgerSheetAccountTransaction,
|
||||||
IAccount,
|
IAccount,
|
||||||
ILedgerEntry,
|
IJournalPoster,
|
||||||
|
IJournalEntry,
|
||||||
|
IContact,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
import moment from 'moment';
|
||||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
|
||||||
import { flatToNestedArray } from '@/utils';
|
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
|
||||||
import { calculateRunningBalance } from './_utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet.
|
* General ledger sheet.
|
||||||
*/
|
*/
|
||||||
export default class GeneralLedgerSheet extends R.compose(
|
export default class GeneralLedgerSheet extends FinancialSheet {
|
||||||
FinancialSheetStructure
|
tenantId: number;
|
||||||
)(FinancialSheet) {
|
accounts: IAccount[];
|
||||||
private query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
private baseCurrency: string;
|
openingBalancesJournal: IJournalPoster;
|
||||||
private i18n: any;
|
transactions: IJournalPoster;
|
||||||
private repository: GeneralLedgerRepository;
|
contactsMap: Map<number, IContact>;
|
||||||
|
baseCurrency: string;
|
||||||
|
i18n: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -36,59 +34,63 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
* @param {IJournalPoster} closingBalancesJournal -
|
* @param {IJournalPoster} closingBalancesJournal -
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
|
tenantId: number,
|
||||||
query: IGeneralLedgerSheetQuery,
|
query: IGeneralLedgerSheetQuery,
|
||||||
repository: GeneralLedgerRepository,
|
accounts: IAccount[],
|
||||||
|
contactsByIdMap: Map<number, IContact>,
|
||||||
|
transactions: IJournalPoster,
|
||||||
|
openingBalancesJournal: IJournalPoster,
|
||||||
|
baseCurrency: string,
|
||||||
i18n
|
i18n
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.tenantId = tenantId;
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.repository = repository;
|
this.accounts = accounts;
|
||||||
this.baseCurrency = this.repository.tenant.metadata.currencyCode;
|
this.contactsMap = contactsByIdMap;
|
||||||
|
this.transactions = transactions;
|
||||||
|
this.openingBalancesJournal = openingBalancesJournal;
|
||||||
|
this.baseCurrency = baseCurrency;
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry mapper.
|
* Retrieve the transaction amount.
|
||||||
* @param {ILedgerEntry} entry -
|
* @param {number} credit - Credit amount.
|
||||||
* @return {IGeneralLedgerSheetAccountTransaction}
|
* @param {number} debit - Debit amount.
|
||||||
|
* @param {string} normal - Credit or debit.
|
||||||
*/
|
*/
|
||||||
private getEntryRunningBalance(
|
getAmount(credit: number, debit: number, normal: string) {
|
||||||
entry: ILedgerEntry,
|
return normal === 'credit' ? credit - debit : debit - credit;
|
||||||
openingBalance: number,
|
|
||||||
runningBalance?: number
|
|
||||||
): number {
|
|
||||||
const lastRunningBalance = runningBalance || openingBalance;
|
|
||||||
|
|
||||||
const amount = Ledger.getAmount(
|
|
||||||
entry.credit,
|
|
||||||
entry.debit,
|
|
||||||
entry.accountNormal
|
|
||||||
);
|
|
||||||
return calculateRunningBalance(amount, lastRunningBalance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the given ledger entry to G/L transaction.
|
* Entry mapper.
|
||||||
* @param {ILedgerEntry} entry
|
* @param {IJournalEntry} entry -
|
||||||
* @param {number} runningBalance
|
* @return {IGeneralLedgerSheetAccountTransaction}
|
||||||
* @returns {IGeneralLedgerSheetAccountTransaction}
|
|
||||||
*/
|
*/
|
||||||
private transactionMapper(
|
entryReducer(
|
||||||
entry: ILedgerEntry,
|
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||||
runningBalance: number
|
entry: IJournalEntry,
|
||||||
): IGeneralLedgerSheetAccountTransaction {
|
openingBalance: number
|
||||||
const contact = this.repository.contactsById.get(entry.contactId);
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const amount = Ledger.getAmount(
|
const lastEntry = last(entries);
|
||||||
|
|
||||||
|
const contact = this.contactsMap.get(entry.contactId);
|
||||||
|
const amount = this.getAmount(
|
||||||
entry.credit,
|
entry.credit,
|
||||||
entry.debit,
|
entry.debit,
|
||||||
entry.accountNormal
|
entry.accountNormal
|
||||||
);
|
);
|
||||||
return {
|
const runningBalance =
|
||||||
id: entry.id,
|
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
|
||||||
|
|
||||||
|
const newEntry = {
|
||||||
date: entry.date,
|
date: entry.date,
|
||||||
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||||
|
entryId: entry.id,
|
||||||
|
|
||||||
transactionNumber: entry.transactionNumber,
|
transactionNumber: entry.transactionNumber,
|
||||||
referenceType: entry.referenceType,
|
referenceType: entry.referenceType,
|
||||||
@@ -107,15 +109,16 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
amount,
|
amount,
|
||||||
runningBalance,
|
runningBalance,
|
||||||
|
|
||||||
formattedAmount: this.formatNumber(amount, { excerptZero: false }),
|
formattedAmount: this.formatNumber(amount),
|
||||||
formattedCredit: this.formatNumber(entry.credit, { excerptZero: false }),
|
formattedCredit: this.formatNumber(entry.credit),
|
||||||
formattedDebit: this.formatNumber(entry.debit, { excerptZero: false }),
|
formattedDebit: this.formatNumber(entry.debit),
|
||||||
formattedRunningBalance: this.formatNumber(runningBalance, {
|
formattedRunningBalance: this.formatNumber(runningBalance),
|
||||||
excerptZero: false,
|
|
||||||
}),
|
|
||||||
|
|
||||||
currencyCode: this.baseCurrency,
|
currencyCode: this.baseCurrency,
|
||||||
} as IGeneralLedgerSheetAccountTransaction;
|
};
|
||||||
|
entries.push(newEntry);
|
||||||
|
|
||||||
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,48 +130,28 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
account: IAccount,
|
account: IAccount,
|
||||||
openingBalance: number
|
openingBalance: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const entries = this.repository.transactionsLedger
|
const entries = this.transactions.getAccountEntries(account.id);
|
||||||
.whereAccountId(account.id)
|
|
||||||
.getEntries();
|
|
||||||
|
|
||||||
return entries
|
return entries.reduce(
|
||||||
.reduce((prev: Array<[number, ILedgerEntry]>, current: ILedgerEntry) => {
|
(
|
||||||
const prevEntry = last(prev);
|
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||||
const prevRunningBalance = head(prevEntry) as number;
|
entry: IJournalEntry
|
||||||
const amount = this.getEntryRunningBalance(
|
) => {
|
||||||
current,
|
return this.entryReducer(entries, entry, openingBalance);
|
||||||
openingBalance,
|
},
|
||||||
prevRunningBalance
|
[]
|
||||||
);
|
);
|
||||||
return [...prev, [amount, current]];
|
|
||||||
}, [])
|
|
||||||
.map((entryPair: [number, ILedgerEntry]) => {
|
|
||||||
const [runningBalance, entry] = entryPair;
|
|
||||||
|
|
||||||
return this.transactionMapper(entry, runningBalance);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the given account opening balance.
|
* Retrieve account opening balance.
|
||||||
* @param {number} accountId
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private accountOpeningBalance(accountId: number): number {
|
|
||||||
return this.repository.openingBalanceTransactionsLedger
|
|
||||||
.whereAccountId(accountId)
|
|
||||||
.getClosingBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the given account opening balance.
|
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountOpeningBalanceTotal(
|
private accountOpeningBalance(
|
||||||
accountId: number
|
account: IAccount
|
||||||
): IGeneralLedgerSheetAccountBalance {
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.accountOpeningBalance(accountId);
|
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.fromDate;
|
const date = this.query.fromDate;
|
||||||
@@ -177,31 +160,15 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the given account closing balance.
|
* Retrieve account closing balance.
|
||||||
* @param {number} accountId
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private accountClosingBalance(accountId: number): number {
|
|
||||||
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
|
||||||
.whereAccountId(accountId)
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
const transactionsBalance = this.repository.transactionsLedger
|
|
||||||
.whereAccountId(accountId)
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
return openingBalance + transactionsBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the given account closing balance.
|
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountClosingBalanceTotal(
|
private accountClosingBalance(
|
||||||
accountId: number
|
openingBalance: number,
|
||||||
|
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||||
): IGeneralLedgerSheetAccountBalance {
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.accountClosingBalance(accountId);
|
const amount = this.calcClosingBalance(openingBalance, transactions);
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.toDate;
|
const date = this.query.toDate;
|
||||||
@@ -209,78 +176,31 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
return { amount, formattedAmount, currencyCode, date };
|
return { amount, formattedAmount, currencyCode, date };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private calcClosingBalance(
|
||||||
* Retrieves the given account closing balance with subaccounts.
|
openingBalance: number,
|
||||||
* @param {number} accountId
|
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||||
* @returns {number}
|
) {
|
||||||
*/
|
return openingBalance + sumBy(transactions, (trans) => trans.amount);
|
||||||
private accountClosingBalanceWithSubaccounts = (
|
}
|
||||||
accountId: number
|
|
||||||
): number => {
|
|
||||||
const depsAccountsIds =
|
|
||||||
this.repository.accountsGraph.dependenciesOf(accountId);
|
|
||||||
|
|
||||||
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
|
||||||
.whereAccountsIds([...depsAccountsIds, accountId])
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
const transactionsBalanceWithSubAccounts =
|
|
||||||
this.repository.transactionsLedger
|
|
||||||
.whereAccountsIds([...depsAccountsIds, accountId])
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
const closingBalance = openingBalance + transactionsBalanceWithSubAccounts;
|
|
||||||
|
|
||||||
return closingBalance;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the closing balance with subaccounts total node.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @returns {IGeneralLedgerSheetAccountBalance}
|
|
||||||
*/
|
|
||||||
private accountClosingBalanceWithSubaccountsTotal = (
|
|
||||||
accountId: number
|
|
||||||
): IGeneralLedgerSheetAccountBalance => {
|
|
||||||
const amount = this.accountClosingBalanceWithSubaccounts(accountId);
|
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
|
||||||
const currencyCode = this.baseCurrency;
|
|
||||||
const date = this.query.toDate;
|
|
||||||
|
|
||||||
return { amount, formattedAmount, currencyCode, date };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the closing balance subaccounts node should be exist.
|
|
||||||
* @param {number} accountId
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isAccountNodeIncludesClosingSubaccounts = (accountId: number) => {
|
|
||||||
// Retrun early if there is no accounts in the filter so
|
|
||||||
// return closing subaccounts in all cases.
|
|
||||||
if (isEmpty(this.query.accountsIds)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Returns true if the given account id includes transactions.
|
|
||||||
return this.repository.accountNodesIncludeTransactions.includes(accountId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retreive general ledger accounts sections.
|
* Retreive general ledger accounts sections.
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccount}
|
* @return {IGeneralLedgerSheetAccount}
|
||||||
*/
|
*/
|
||||||
private accountMapper = (account: IAccount): IGeneralLedgerSheetAccount => {
|
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
|
||||||
const openingBalance = this.accountOpeningBalanceTotal(account.id);
|
const openingBalance = this.accountOpeningBalance(account);
|
||||||
|
|
||||||
const transactions = this.accountTransactionsMapper(
|
const transactions = this.accountTransactionsMapper(
|
||||||
account,
|
account,
|
||||||
openingBalance.amount
|
openingBalance.amount
|
||||||
);
|
);
|
||||||
const closingBalance = this.accountClosingBalanceTotal(account.id);
|
const closingBalance = this.accountClosingBalance(
|
||||||
const closingBalanceSubaccounts =
|
openingBalance.amount,
|
||||||
this.accountClosingBalanceWithSubaccountsTotal(account.id);
|
transactions
|
||||||
|
);
|
||||||
|
|
||||||
const initialNode = {
|
return {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
code: account.code,
|
code: account.code,
|
||||||
@@ -290,90 +210,34 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
transactions,
|
transactions,
|
||||||
closingBalance,
|
closingBalance,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
() => this.isAccountNodeIncludesClosingSubaccounts(account.id),
|
|
||||||
R.assoc('closingBalanceSubaccounts', closingBalanceSubaccounts)
|
|
||||||
)
|
|
||||||
)(initialNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps over deep nodes to retrieve the G/L account node.
|
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
|
||||||
* @param {IAccount[]} accounts
|
|
||||||
* @returns {IGeneralLedgerSheetAccount[]}
|
|
||||||
*/
|
|
||||||
private accountNodesDeepMap = (
|
|
||||||
accounts: IAccount[]
|
|
||||||
): IGeneralLedgerSheetAccount[] => {
|
|
||||||
return this.mapNodesDeep(accounts, this.accountMapper);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformes the flatten nodes to nested nodes.
|
|
||||||
*/
|
|
||||||
private nestedAccountsNode = (flattenAccounts: IAccount[]): IAccount[] => {
|
|
||||||
return flatToNestedArray(flattenAccounts, {
|
|
||||||
id: 'id',
|
|
||||||
parentId: 'parentAccountId',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters account nodes.
|
|
||||||
* @param {IGeneralLedgerSheetAccount[]} nodes
|
|
||||||
* @returns {IGeneralLedgerSheetAccount[]}
|
|
||||||
*/
|
|
||||||
private filterAccountNodesByTransactionsFilter = (
|
|
||||||
nodes: IGeneralLedgerSheetAccount[]
|
|
||||||
): IGeneralLedgerSheetAccount[] => {
|
|
||||||
return this.filterNodesDeep(
|
|
||||||
nodes,
|
|
||||||
(account: IGeneralLedgerSheetAccount) =>
|
|
||||||
!(account.transactions.length === 0 && this.query.noneTransactions)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters account nodes by the acounts filter.
|
|
||||||
* @param {IAccount[]} nodes
|
|
||||||
* @returns {IAccount[]}
|
|
||||||
*/
|
|
||||||
private filterAccountNodesByAccountsFilter = (
|
|
||||||
nodes: IAccount[]
|
|
||||||
): IAccount[] => {
|
|
||||||
return this.filterNodesDeep(nodes, (node: IGeneralLedgerSheetAccount) => {
|
|
||||||
if (R.isEmpty(this.query.accountsIds)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Returns true if the given account id exists in the filter.
|
|
||||||
return this.repository.accountNodeInclude?.includes(node.id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves mapped accounts with general ledger transactions and
|
|
||||||
* opeing/closing balance.
|
|
||||||
* @param {IAccount[]} accounts -
|
* @param {IAccount[]} accounts -
|
||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
||||||
return R.compose(
|
return (
|
||||||
R.defaultTo([]),
|
accounts
|
||||||
this.filterAccountNodesByTransactionsFilter,
|
.map((account: IAccount) => this.accountMapper(account))
|
||||||
this.accountNodesDeepMap,
|
// Filter general ledger accounts that have no transactions
|
||||||
R.defaultTo([]),
|
// when`noneTransactions` is on.
|
||||||
this.filterAccountNodesByAccountsFilter,
|
.filter(
|
||||||
this.nestedAccountsNode
|
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
||||||
)(accounts);
|
!(
|
||||||
|
generalLedgerAccount.transactions.length === 0 &&
|
||||||
|
this.query.noneTransactions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves general ledger report data.
|
* Retrieve general ledger report data.
|
||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
public reportData(): IGeneralLedgerSheetAccount[] {
|
public reportData(): IGeneralLedgerSheetAccount[] {
|
||||||
return this.accountsWalker(this.repository.accounts);
|
return this.accountsWalker(this.accounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import {
|
|
||||||
IAccount,
|
|
||||||
IAccountTransaction,
|
|
||||||
IContact,
|
|
||||||
IGeneralLedgerSheetQuery,
|
|
||||||
ITenant,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
|
||||||
import { transformToMap } from '@/utils';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { flatten, isEmpty, uniq } from 'lodash';
|
|
||||||
|
|
||||||
export class GeneralLedgerRepository {
|
|
||||||
public filter: IGeneralLedgerSheetQuery;
|
|
||||||
public accounts: IAccount[];
|
|
||||||
|
|
||||||
public transactions: IAccountTransaction[];
|
|
||||||
public openingBalanceTransactions: IAccountTransaction[];
|
|
||||||
|
|
||||||
public transactionsLedger: Ledger;
|
|
||||||
public openingBalanceTransactionsLedger: Ledger;
|
|
||||||
|
|
||||||
public repositories: any;
|
|
||||||
public models: any;
|
|
||||||
public accountsGraph: any;
|
|
||||||
|
|
||||||
public contacts: IContact;
|
|
||||||
public contactsById: Map<number, IContact>;
|
|
||||||
|
|
||||||
public tenantId: number;
|
|
||||||
public tenant: ITenant;
|
|
||||||
|
|
||||||
public accountNodesIncludeTransactions: Array<number> = [];
|
|
||||||
public accountNodeInclude: Array<number> = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @param models
|
|
||||||
* @param repositories
|
|
||||||
* @param filter
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
repositories: any,
|
|
||||||
filter: IGeneralLedgerSheetQuery,
|
|
||||||
tenantId: number
|
|
||||||
) {
|
|
||||||
this.filter = filter;
|
|
||||||
this.repositories = repositories;
|
|
||||||
this.tenantId = tenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the G/L report.
|
|
||||||
*/
|
|
||||||
public async asyncInitialize() {
|
|
||||||
await this.initTenant();
|
|
||||||
await this.initAccounts();
|
|
||||||
await this.initAccountsGraph();
|
|
||||||
await this.initContacts();
|
|
||||||
await this.initAccountsOpeningBalance();
|
|
||||||
this.initAccountNodesIncludeTransactions();
|
|
||||||
await this.initTransactions();
|
|
||||||
this.initAccountNodesIncluded();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the tenant.
|
|
||||||
*/
|
|
||||||
public async initTenant() {
|
|
||||||
this.tenant = await Tenant.query()
|
|
||||||
.findById(this.tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the accounts.
|
|
||||||
*/
|
|
||||||
public async initAccounts() {
|
|
||||||
this.accounts = await this.repositories.accountRepository
|
|
||||||
.all()
|
|
||||||
.orderBy('name', 'ASC');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the accounts graph.
|
|
||||||
*/
|
|
||||||
public async initAccountsGraph() {
|
|
||||||
this.accountsGraph =
|
|
||||||
await this.repositories.accountRepository.getDependencyGraph();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the contacts.
|
|
||||||
*/
|
|
||||||
public async initContacts() {
|
|
||||||
this.contacts = await this.repositories.contactRepository.all();
|
|
||||||
this.contactsById = transformToMap(this.contacts, 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the G/L transactions from/to the given date.
|
|
||||||
*/
|
|
||||||
public async initTransactions() {
|
|
||||||
this.transactions = await this.repositories.transactionsRepository
|
|
||||||
.journal({
|
|
||||||
fromDate: this.filter.fromDate,
|
|
||||||
toDate: this.filter.toDate,
|
|
||||||
branchesIds: this.filter.branchesIds,
|
|
||||||
})
|
|
||||||
.orderBy('date', 'ASC')
|
|
||||||
.onBuild((query) => {
|
|
||||||
if (this.filter.accountsIds?.length > 0) {
|
|
||||||
query.whereIn('accountId', this.accountNodesIncludeTransactions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Transform array transactions to journal collection.
|
|
||||||
this.transactionsLedger = Ledger.fromTransactions(this.transactions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the G/L accounts opening balance.
|
|
||||||
*/
|
|
||||||
public async initAccountsOpeningBalance() {
|
|
||||||
// Retreive opening balance credit/debit sumation.
|
|
||||||
this.openingBalanceTransactions =
|
|
||||||
await this.repositories.transactionsRepository.journal({
|
|
||||||
toDate: moment(this.filter.fromDate).subtract(1, 'day'),
|
|
||||||
sumationCreditDebit: true,
|
|
||||||
branchesIds: this.filter.branchesIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Accounts opening transactions.
|
|
||||||
this.openingBalanceTransactionsLedger = Ledger.fromTransactions(
|
|
||||||
this.openingBalanceTransactions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the account nodes that should include transactions.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
public initAccountNodesIncludeTransactions() {
|
|
||||||
if (isEmpty(this.filter.accountsIds)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const childrenNodeIds = this.filter.accountsIds?.map(
|
|
||||||
(accountId: number) => {
|
|
||||||
return this.accountsGraph.dependenciesOf(accountId);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const nodeIds = R.concat(this.filter.accountsIds, childrenNodeIds);
|
|
||||||
|
|
||||||
this.accountNodesIncludeTransactions = uniq(flatten(nodeIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the account node ids should be included,
|
|
||||||
* if the filter by acounts is presented.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
public initAccountNodesIncluded() {
|
|
||||||
if (isEmpty(this.filter.accountsIds)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nodeIds = this.filter.accountsIds.map((accountId) => {
|
|
||||||
const childrenIds = this.accountsGraph.dependenciesOf(accountId);
|
|
||||||
const parentIds = this.accountsGraph.dependantsOf(accountId);
|
|
||||||
|
|
||||||
return R.concat(childrenIds, parentIds);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.accountNodeInclude = R.compose(
|
|
||||||
R.uniq,
|
|
||||||
R.flatten,
|
|
||||||
R.concat(this.filter.accountsIds)
|
|
||||||
)(nodeIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { difference } from 'lodash';
|
||||||
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import Journal from '@/services/Accounting/JournalPoster';
|
||||||
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||||
|
import { transformToMap } from 'utils';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
||||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
|
||||||
|
const ERRORS = {
|
||||||
|
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
||||||
|
};
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GeneralLedgerService {
|
export class GeneralLedgerService {
|
||||||
@@ -32,13 +40,29 @@ export class GeneralLedgerService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates accounts existance on the storage.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number[]} accountsIds
|
||||||
|
*/
|
||||||
|
async validateAccountsExistance(tenantId: number, accountsIds: number[]) {
|
||||||
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const storedAccounts = await Account.query().whereIn('id', accountsIds);
|
||||||
|
const storedAccountsIds = storedAccounts.map((a) => a.id);
|
||||||
|
|
||||||
|
if (difference(accountsIds, storedAccountsIds).length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNTS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve general ledger report statement.
|
* Retrieve general ledger report statement.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IGeneralLedgerSheetQuery} query
|
* @param {IGeneralLedgerSheetQuery} query
|
||||||
* @return {Promise<IGeneralLedgerStatement>}
|
* @return {IGeneralLedgerStatement}
|
||||||
*/
|
*/
|
||||||
public async generalLedger(
|
async generalLedger(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: IGeneralLedgerSheetQuery
|
query: IGeneralLedgerSheetQuery
|
||||||
): Promise<{
|
): Promise<{
|
||||||
@@ -46,24 +70,60 @@ export class GeneralLedgerService {
|
|||||||
query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
meta: IGeneralLedgerMeta;
|
meta: IGeneralLedgerMeta;
|
||||||
}> {
|
}> {
|
||||||
const repositories = this.tenancy.repositories(tenantId);
|
const { accountRepository, transactionsRepository, contactRepository } =
|
||||||
|
this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
|
|
||||||
|
const tenant = await Tenant.query()
|
||||||
|
.findById(tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
const genealLedgerRepository = new GeneralLedgerRepository(
|
// Retrieve all accounts with associated type from the storage.
|
||||||
repositories,
|
const accounts = await accountRepository.all();
|
||||||
query,
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
tenantId
|
|
||||||
);
|
|
||||||
await genealLedgerRepository.asyncInitialize();
|
|
||||||
|
|
||||||
|
// Retrieve all contacts on the storage.
|
||||||
|
const contacts = await contactRepository.all();
|
||||||
|
const contactsByIdMap = transformToMap(contacts, 'id');
|
||||||
|
|
||||||
|
// Retreive journal transactions from/to the given date.
|
||||||
|
const transactions = await transactionsRepository.journal({
|
||||||
|
fromDate: filter.fromDate,
|
||||||
|
toDate: filter.toDate,
|
||||||
|
branchesIds: filter.branchesIds,
|
||||||
|
});
|
||||||
|
// Retreive opening balance credit/debit sumation.
|
||||||
|
const openingBalanceTrans = await transactionsRepository.journal({
|
||||||
|
toDate: moment(filter.fromDate).subtract(1, 'day'),
|
||||||
|
sumationCreditDebit: true,
|
||||||
|
branchesIds: filter.branchesIds,
|
||||||
|
});
|
||||||
|
// Transform array transactions to journal collection.
|
||||||
|
const transactionsJournal = Journal.fromTransactions(
|
||||||
|
transactions,
|
||||||
|
tenantId,
|
||||||
|
accountsGraph
|
||||||
|
);
|
||||||
|
// Accounts opening transactions.
|
||||||
|
const openingTransJournal = Journal.fromTransactions(
|
||||||
|
openingBalanceTrans,
|
||||||
|
tenantId,
|
||||||
|
accountsGraph
|
||||||
|
);
|
||||||
// General ledger report instance.
|
// General ledger report instance.
|
||||||
const generalLedgerInstance = new GeneralLedgerSheet(
|
const generalLedgerInstance = new GeneralLedgerSheet(
|
||||||
|
tenantId,
|
||||||
filter,
|
filter,
|
||||||
genealLedgerRepository,
|
accounts,
|
||||||
|
contactsByIdMap,
|
||||||
|
transactionsJournal,
|
||||||
|
openingTransJournal,
|
||||||
|
tenant.metadata.baseCurrency,
|
||||||
i18n
|
i18n
|
||||||
);
|
);
|
||||||
// Retrieve general ledger report data.
|
// Retrieve general ledger report data.
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
*/
|
*/
|
||||||
private openingBalanceColumnsAccessors(): IColumnMapperMeta[] {
|
private openingBalanceColumnsAccessors(): IColumnMapperMeta[] {
|
||||||
return [
|
return [
|
||||||
{ key: 'date', value: 'Opening Balance' },
|
{ key: 'date', value: this.meta.fromDate },
|
||||||
{ key: 'account_name', value: '' },
|
{ key: 'account_name', value: 'Opening Balance' },
|
||||||
{ key: 'reference_type', accessor: '_empty_' },
|
{ key: 'reference_type', accessor: '_empty_' },
|
||||||
{ key: 'reference_number', accessor: '_empty_' },
|
{ key: 'reference_number', accessor: '_empty_' },
|
||||||
{ key: 'description', accessor: 'description' },
|
{ key: 'description', accessor: 'description' },
|
||||||
@@ -97,15 +97,12 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Closing balance row column accessors.
|
* Closing balance row column accessors.
|
||||||
* @param {IGeneralLedgerSheetAccount} account -
|
|
||||||
* @returns {ITableColumnAccessor[]}
|
* @returns {ITableColumnAccessor[]}
|
||||||
*/
|
*/
|
||||||
private closingBalanceColumnAccessors(
|
private closingBalanceColumnAccessors(): IColumnMapperMeta[] {
|
||||||
account: IGeneralLedgerSheetAccount
|
|
||||||
): IColumnMapperMeta[] {
|
|
||||||
return [
|
return [
|
||||||
{ key: 'date', value: `Closing balance for ${account.name}` },
|
{ key: 'date', value: this.meta.toDate },
|
||||||
{ key: 'account_name', value: `` },
|
{ key: 'account_name', value: 'Closing Balance' },
|
||||||
{ key: 'reference_type', accessor: '_empty_' },
|
{ key: 'reference_type', accessor: '_empty_' },
|
||||||
{ key: 'reference_number', accessor: '_empty_' },
|
{ key: 'reference_number', accessor: '_empty_' },
|
||||||
{ key: 'description', accessor: '_empty_' },
|
{ key: 'description', accessor: '_empty_' },
|
||||||
@@ -116,36 +113,6 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Closing balance row column accessors.
|
|
||||||
* @param {IGeneralLedgerSheetAccount} account -
|
|
||||||
* @returns {ITableColumnAccessor[]}
|
|
||||||
*/
|
|
||||||
private closingBalanceWithSubaccountsColumnAccessors(
|
|
||||||
account: IGeneralLedgerSheetAccount
|
|
||||||
): IColumnMapperMeta[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'date',
|
|
||||||
value: `Closing Balance for ${account.name} with sub-accounts`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'account_name',
|
|
||||||
value: ``,
|
|
||||||
},
|
|
||||||
{ key: 'reference_type', accessor: '_empty_' },
|
|
||||||
{ key: 'reference_number', accessor: '_empty_' },
|
|
||||||
{ key: 'description', accessor: '_empty_' },
|
|
||||||
{ key: 'credit', accessor: '_empty_' },
|
|
||||||
{ key: 'debit', accessor: '_empty_' },
|
|
||||||
{ key: 'amount', accessor: 'closingBalanceSubaccounts.formattedAmount' },
|
|
||||||
{
|
|
||||||
key: 'running_balance',
|
|
||||||
accessor: 'closingBalanceSubaccounts.formattedAmount',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the common table columns.
|
* Retrieves the common table columns.
|
||||||
* @returns {ITableColumn[]}
|
* @returns {ITableColumn[]}
|
||||||
@@ -217,22 +184,7 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
* @returns {ITableRow}
|
* @returns {ITableRow}
|
||||||
*/
|
*/
|
||||||
private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => {
|
private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => {
|
||||||
const columns = this.closingBalanceColumnAccessors(account);
|
const columns = this.closingBalanceColumnAccessors();
|
||||||
const meta = {
|
|
||||||
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
|
||||||
};
|
|
||||||
return tableRowMapper(account, columns, meta);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the given account node to opening balance table row.
|
|
||||||
* @param {IGeneralLedgerSheetAccount} account
|
|
||||||
* @returns {ITableRow}
|
|
||||||
*/
|
|
||||||
private closingBalanceWithSubaccountsMapper = (
|
|
||||||
account: IGeneralLedgerSheetAccount
|
|
||||||
): ITableRow => {
|
|
||||||
const columns = this.closingBalanceWithSubaccountsColumnAccessors(account);
|
|
||||||
const meta = {
|
const meta = {
|
||||||
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||||
};
|
};
|
||||||
@@ -269,27 +221,8 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
rowTypes: [ROW_TYPE.ACCOUNT],
|
rowTypes: [ROW_TYPE.ACCOUNT],
|
||||||
};
|
};
|
||||||
const row = tableRowMapper(account, columns, meta);
|
const row = tableRowMapper(account, columns, meta);
|
||||||
const closingBalanceWithSubaccounts =
|
|
||||||
this.closingBalanceWithSubaccountsMapper(account);
|
|
||||||
|
|
||||||
// Appends the closing balance with sub-accounts row if the account
|
return R.assoc('children', transactions)(row);
|
||||||
// has children accounts and the node is define.
|
|
||||||
const isAppendClosingSubaccounts = () =>
|
|
||||||
account.children?.length > 0 && !!account.closingBalanceSubaccounts;
|
|
||||||
|
|
||||||
const children = R.compose(
|
|
||||||
R.when(
|
|
||||||
isAppendClosingSubaccounts,
|
|
||||||
R.append(closingBalanceWithSubaccounts)
|
|
||||||
),
|
|
||||||
R.concat(R.defaultTo([], transactions)),
|
|
||||||
R.when(
|
|
||||||
() => account?.children?.length > 0,
|
|
||||||
R.concat(R.defaultTo([], account.children))
|
|
||||||
)
|
|
||||||
)([]);
|
|
||||||
|
|
||||||
return R.assoc('children', children)(row);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -300,7 +233,7 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
private accountsMapper = (
|
private accountsMapper = (
|
||||||
accounts: IGeneralLedgerSheetAccount[]
|
accounts: IGeneralLedgerSheetAccount[]
|
||||||
): ITableRow[] => {
|
): ITableRow[] => {
|
||||||
return this.mapNodesDeepReverse(accounts, this.accountMapper);
|
return this.mapNodesDeep(accounts, this.accountMapper);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -317,6 +250,7 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
*/
|
*/
|
||||||
public tableColumns(): ITableColumn[] {
|
public tableColumns(): ITableColumn[] {
|
||||||
const columns = this.commonColumns();
|
const columns = this.commonColumns();
|
||||||
|
|
||||||
return R.compose(this.tableColumnsCellIndexing)(columns);
|
return R.compose(this.tableColumnsCellIndexing)(columns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Calculate the running balance.
|
|
||||||
* @param {number} amount - Transaction amount.
|
|
||||||
* @param {number} lastRunningBalance - Last running balance.
|
|
||||||
* @param {number} openingBalance - Opening balance.
|
|
||||||
* @return {number} Running balance.
|
|
||||||
*/
|
|
||||||
export function calculateRunningBalance(
|
|
||||||
amount: number,
|
|
||||||
lastRunningBalance: number
|
|
||||||
): number {
|
|
||||||
return amount + lastRunningBalance;
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ import { ProfitLossSheetPreviousYear } from './ProfitLossSheetPreviousYear';
|
|||||||
import { ProfitLossSheetPreviousPeriod } from './ProfitLossSheetPreviousPeriod';
|
import { ProfitLossSheetPreviousPeriod } from './ProfitLossSheetPreviousPeriod';
|
||||||
import { FinancialDateRanges } from '../FinancialDateRanges';
|
import { FinancialDateRanges } from '../FinancialDateRanges';
|
||||||
import { ProfitLossSheetFilter } from './ProfitLossSheetFilter';
|
import { ProfitLossSheetFilter } from './ProfitLossSheetFilter';
|
||||||
import { flatToNestedArray } from '@/utils';
|
|
||||||
|
|
||||||
export default class ProfitLossSheet extends R.compose(
|
export default class ProfitLossSheet extends R.compose(
|
||||||
ProfitLossSheetPreviousYear,
|
ProfitLossSheetPreviousYear,
|
||||||
@@ -83,22 +82,14 @@ export default class ProfitLossSheet extends R.compose(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the sheet account node from the given account.
|
* Retrieve the sheet account node from the given account.
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @returns {IProfitLossSheetAccountNode}
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
*/
|
*/
|
||||||
private accountNodeMapper = (
|
private accountNodeMapper = (
|
||||||
account: IAccount
|
account: IAccount
|
||||||
): IProfitLossSheetAccountNode => {
|
): IProfitLossSheetAccountNode => {
|
||||||
// Retrieves the children account ids of the given account id.
|
|
||||||
const childrenAccountIds = this.repository.accountsGraph.dependenciesOf(
|
|
||||||
account.id
|
|
||||||
);
|
|
||||||
// Concat the children and the given account id.
|
|
||||||
const accountIds = R.uniq(R.append(account.id, childrenAccountIds));
|
|
||||||
|
|
||||||
// Retrieves the closing balance of the account included children accounts.
|
|
||||||
const total = this.repository.totalAccountsLedger
|
const total = this.repository.totalAccountsLedger
|
||||||
.whereAccountsIds(accountIds)
|
.whereAccountId(account.id)
|
||||||
.getClosingBalance();
|
.getClosingBalance();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -135,19 +126,18 @@ export default class ProfitLossSheet extends R.compose(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves report accounts nodes by the given accounts types.
|
* Retrieve report accounts nodes by the given accounts types.
|
||||||
* @param {string[]} types
|
* @param {string[]} types
|
||||||
* @returns {IBalanceSheetAccountNode}
|
* @returns {IBalanceSheetAccountNode}
|
||||||
*/
|
*/
|
||||||
private getAccountsNodesByTypes = (
|
private getAccountsNodesByTypes = (
|
||||||
types: string[]
|
types: string[]
|
||||||
): IProfitLossSheetAccountNode[] => {
|
): IProfitLossSheetAccountNode[] => {
|
||||||
const accounts = this.repository.getAccountsByType(types);
|
return R.compose(
|
||||||
const accountsTree = flatToNestedArray(accounts, {
|
R.map(this.accountNodeCompose),
|
||||||
id: 'id',
|
R.flatten,
|
||||||
parentId: 'parentAccountId',
|
R.map(this.repository.getAccountsByType)
|
||||||
});
|
)(types);
|
||||||
return this.mapNodesDeep(accountsTree, this.accountNodeCompose);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { castArray, defaultTo } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
@@ -31,11 +31,6 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
|||||||
*/
|
*/
|
||||||
public accounts: IAccount[];
|
public accounts: IAccount[];
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public accountsGraph: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transactions group type.
|
* Transactions group type.
|
||||||
* @param {IAccountTransactionsGroupBy}
|
* @param {IAccountTransactionsGroupBy}
|
||||||
@@ -140,8 +135,6 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
|||||||
*/
|
*/
|
||||||
public asyncInitialize = async () => {
|
public asyncInitialize = async () => {
|
||||||
await this.initAccounts();
|
await this.initAccounts();
|
||||||
await this.initAccountsGraph();
|
|
||||||
|
|
||||||
await this.initAccountsTotalLedger();
|
await this.initAccountsTotalLedger();
|
||||||
|
|
||||||
// Date Periods.
|
// Date Periods.
|
||||||
@@ -184,15 +177,6 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
|||||||
this.accountsByType = transformToMapBy(accounts, 'accountType');
|
this.accountsByType = transformToMapBy(accounts, 'accountType');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize accounts graph.
|
|
||||||
*/
|
|
||||||
private initAccountsGraph = async () => {
|
|
||||||
const { Account } = this.models;
|
|
||||||
|
|
||||||
this.accountsGraph = Account.toDependencyGraph(this.accounts);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// # Closing Total.
|
// # Closing Total.
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@@ -353,18 +337,7 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
|||||||
return Account.query();
|
return Account.query();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
public getAccountsByType = (type: string) => {
|
||||||
*
|
return defaultTo(this.accountsByType.get(type), []);
|
||||||
* @param type
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getAccountsByType = (type: string[] | string) => {
|
|
||||||
return R.compose(
|
|
||||||
R.flatten,
|
|
||||||
R.map((accountType) =>
|
|
||||||
R.defaultTo([], this.accountsByType.get(accountType))
|
|
||||||
),
|
|
||||||
castArray
|
|
||||||
)(type);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ export default class SyncSystemSendInvite {
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
active: user.active,
|
active: user.active,
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|
||||||
// Email should be verified since the user got the invite token through email.
|
|
||||||
verified: true,
|
|
||||||
});
|
});
|
||||||
// Creates a invite user token.
|
// Creates a invite user token.
|
||||||
const invite = await Invite.query().insert({
|
const invite = await Invite.query().insert({
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export class ManualJournalTransfromer extends Transformer {
|
|||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedDate',
|
'formattedDate',
|
||||||
'formattedPublishedAt',
|
'formattedPublishedAt',
|
||||||
'formattedCreatedAt',
|
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -38,15 +37,6 @@ export class ManualJournalTransfromer extends Transformer {
|
|||||||
return this.formatDate(manualJorunal.date);
|
return this.formatDate(manualJorunal.date);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted created at date.
|
|
||||||
* @param {IManualJournal} manualJournal
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (manualJorunal: IManualJournal): string => {
|
|
||||||
return this.formatDate(manualJorunal.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted published at date.
|
* Retrieve formatted published at date.
|
||||||
* @param {IManualJournal} manualJournal
|
* @param {IManualJournal} manualJournal
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export default class OrganizationService {
|
|||||||
): IOrganizationBuildDTO {
|
): IOrganizationBuildDTO {
|
||||||
return {
|
return {
|
||||||
...buildDTO,
|
...buildDTO,
|
||||||
dateFormat: defaultTo(buildDTO.dateFormat, 'DD MMM yyyy'),
|
dateFormat: defaultTo(buildDTO.dateFormat, 'DD/MM/yyyy'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { formatNumber } from '@/utils';
|
import { formatNumber } from '@/utils';
|
||||||
import { PurchaseInvoiceTransformer } from '../Bills/PurchaseInvoiceTransformer';
|
|
||||||
|
|
||||||
export class BillPaymentEntryTransformer extends Transformer {
|
export class BillPaymentEntryTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -8,14 +7,7 @@ export class BillPaymentEntryTransformer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return ['paymentAmountFormatted', 'bill'];
|
return ['paymentAmountFormatted'];
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retreives the
|
|
||||||
*/
|
|
||||||
protected bill = (entry) => {
|
|
||||||
return this.item(entry.bill, new PurchaseInvoiceTransformer());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export class BillPaymentTransformer extends Transformer {
|
|||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return [
|
||||||
'formattedPaymentDate',
|
'formattedPaymentDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'entries',
|
'entries',
|
||||||
'attachments',
|
'attachments',
|
||||||
@@ -28,15 +27,6 @@ export class BillPaymentTransformer extends Transformer {
|
|||||||
return this.formatDate(billPayment.paymentDate);
|
return this.formatDate(billPayment.paymentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted created at date.
|
|
||||||
* @param {IBillPayment} billPayment
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (billPayment: IBillPayment): string => {
|
|
||||||
return this.formatDate(billPayment.createdAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted bill amount.
|
* Retrieve formatted bill amount.
|
||||||
* @param {IBill} invoice
|
* @param {IBill} invoice
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
|||||||
return [
|
return [
|
||||||
'formattedBillDate',
|
'formattedBillDate',
|
||||||
'formattedDueDate',
|
'formattedDueDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedPaymentAmount',
|
'formattedPaymentAmount',
|
||||||
'formattedBalance',
|
'formattedBalance',
|
||||||
@@ -58,15 +57,6 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
|||||||
return this.formatDate(bill.dueDate);
|
return this.formatDate(bill.dueDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the formatted created at date.
|
|
||||||
* @param {IBill} bill
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (bill: IBill): string => {
|
|
||||||
return this.formatDate(bill.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted bill amount.
|
* Retrieve formatted bill amount.
|
||||||
* @param {IBill} bill
|
* @param {IBill} bill
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export class VendorCreditTransformer extends Transformer {
|
|||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedSubtotal',
|
'formattedSubtotal',
|
||||||
'formattedVendorCreditDate',
|
'formattedVendorCreditDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedCreditsRemaining',
|
'formattedCreditsRemaining',
|
||||||
'formattedInvoicedAmount',
|
'formattedInvoicedAmount',
|
||||||
'entries',
|
'entries',
|
||||||
@@ -31,15 +30,6 @@ export class VendorCreditTransformer extends Transformer {
|
|||||||
return this.formatDate(vendorCredit.vendorCreditDate);
|
return this.formatDate(vendorCredit.vendorCreditDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retireve formatted created at date.
|
|
||||||
* @param vendorCredit
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (vendorCredit): string => {
|
|
||||||
return this.formatDate(vendorCredit.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted vendor credit amount.
|
* Retrieve formatted vendor credit amount.
|
||||||
* @param {IVendorCredit} credit
|
* @param {IVendorCredit} credit
|
||||||
|
|||||||
@@ -108,28 +108,17 @@ export default class ResourceService {
|
|||||||
const $hasFields = (field) =>
|
const $hasFields = (field) =>
|
||||||
'undefined' !== typeof field.fields ? field : undefined;
|
'undefined' !== typeof field.fields ? field : undefined;
|
||||||
|
|
||||||
const $ColumnHasColumns = (column) =>
|
const $hasColumns = (column) =>
|
||||||
'undefined' !== typeof column.columns ? column : undefined;
|
'undefined' !== typeof column.columns ? column : undefined;
|
||||||
|
|
||||||
const $hasColumns = (columns) =>
|
|
||||||
'undefined' !== typeof columns ? columns : undefined;
|
|
||||||
|
|
||||||
const naviagations = [
|
const naviagations = [
|
||||||
['fields', qim.$each, 'name'],
|
['fields', qim.$each, 'name'],
|
||||||
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, 'name'],
|
['fields2', qim.$each, 'name'],
|
||||||
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||||
['columns', $hasColumns, qim.$each, 'name'],
|
['columns', qim.$each, 'name'],
|
||||||
[
|
['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'],
|
||||||
'columns',
|
|
||||||
$hasColumns,
|
|
||||||
qim.$each,
|
|
||||||
$ColumnHasColumns,
|
|
||||||
'columns',
|
|
||||||
qim.$each,
|
|
||||||
'name',
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export class SaleEstimateTransfromer extends Transformer {
|
|||||||
'formattedDeliveredAtDate',
|
'formattedDeliveredAtDate',
|
||||||
'formattedApprovedAtDate',
|
'formattedApprovedAtDate',
|
||||||
'formattedRejectedAtDate',
|
'formattedRejectedAtDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'entries',
|
'entries',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -42,15 +41,6 @@ export class SaleEstimateTransfromer extends Transformer {
|
|||||||
return this.formatDate(estimate.expirationDate);
|
return this.formatDate(estimate.expirationDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted estimate created at.
|
|
||||||
* @param {ISaleEstimate} estimate -
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (estimate: ISaleEstimate): string => {
|
|
||||||
return this.formatDate(estimate.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted estimate date.
|
* Retrieve formatted estimate date.
|
||||||
* @param {ISaleEstimate} invoice
|
* @param {ISaleEstimate} invoice
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
return [
|
return [
|
||||||
'invoiceDateFormatted',
|
'invoiceDateFormatted',
|
||||||
'dueDateFormatted',
|
'dueDateFormatted',
|
||||||
'createdAtFormatted',
|
|
||||||
'dueAmountFormatted',
|
'dueAmountFormatted',
|
||||||
'paymentAmountFormatted',
|
'paymentAmountFormatted',
|
||||||
'balanceAmountFormatted',
|
'balanceAmountFormatted',
|
||||||
@@ -49,15 +48,6 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
return this.formatDate(invoice.dueDate);
|
return this.formatDate(invoice.dueDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the formatted created at date.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected createdAtFormatted = (invoice): string => {
|
|
||||||
return this.formatDate(invoice.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted invoice due amount.
|
* Retrieve formatted invoice due amount.
|
||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
|
|||||||
@@ -146,7 +146,6 @@ export class EditPaymentReceive {
|
|||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
paymentReceiveDTO,
|
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
trx,
|
trx,
|
||||||
} as IPaymentReceiveEditedPayload);
|
} as IPaymentReceiveEditedPayload);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export class PaymentReceiveTransfromer extends Transformer {
|
|||||||
return [
|
return [
|
||||||
'subtotalFormatted',
|
'subtotalFormatted',
|
||||||
'formattedPaymentDate',
|
'formattedPaymentDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedExchangeRate',
|
'formattedExchangeRate',
|
||||||
'entries',
|
'entries',
|
||||||
@@ -28,18 +27,9 @@ export class PaymentReceiveTransfromer extends Transformer {
|
|||||||
return this.formatDate(payment.paymentDate);
|
return this.formatDate(payment.paymentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted created at date.
|
|
||||||
* @param {IPaymentReceive} payment
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (payment: IPaymentReceive): string => {
|
|
||||||
return this.formatDate(payment.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the formatted payment subtotal.
|
* Retrieve the formatted payment subtotal.
|
||||||
* @param {IPaymentReceive} payment
|
* @param {IPaymentReceive} payment
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected subtotalFormatted = (payment: IPaymentReceive): string => {
|
protected subtotalFormatted = (payment: IPaymentReceive): string => {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export class SaleReceiptTransformer extends Transformer {
|
|||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'formattedReceiptDate',
|
'formattedReceiptDate',
|
||||||
'formattedClosedAtDate',
|
'formattedClosedAtDate',
|
||||||
'formattedCreatedAt',
|
|
||||||
'entries',
|
'entries',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -41,15 +40,6 @@ export class SaleReceiptTransformer extends Transformer {
|
|||||||
return this.formatDate(receipt.closedAt);
|
return this.formatDate(receipt.closedAt);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted receipt created at date.
|
|
||||||
* @param receipt
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedCreatedAt = (receipt: ISaleReceipt): string => {
|
|
||||||
return this.formatDate(receipt.createdAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the estimate formatted subtotal.
|
* Retrieves the estimate formatted subtotal.
|
||||||
* @param {ISaleReceipt} receipt
|
* @param {ISaleReceipt} receipt
|
||||||
|
|||||||
@@ -90,20 +90,6 @@ export default class SystemUser extends SystemModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Model modifiers.
|
|
||||||
*/
|
|
||||||
static get modifiers() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Filters the invite accepted users.
|
|
||||||
*/
|
|
||||||
inviteAccepted(query) {
|
|
||||||
query.whereNotNull('invite_accepted_at');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify the password of the user.
|
* Verify the password of the user.
|
||||||
* @param {String} password - The given password.
|
* @param {String} password - The given password.
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { FSuggest } from '../Forms';
|
|
||||||
|
|
||||||
interface BranchSuggestFieldProps {
|
|
||||||
items: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BranchSuggestField({ ...props }: BranchSuggestFieldProps) {
|
|
||||||
return (
|
|
||||||
<FSuggest
|
|
||||||
valueAccessor={'id'}
|
|
||||||
labelAccessor={'code'}
|
|
||||||
textAccessor={'name'}
|
|
||||||
inputProps={{ placeholder: 'Select a branch' }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -21,9 +21,9 @@ import RefundCreditNoteDetailDrawer from '@/containers/Drawers/RefundCreditNoteD
|
|||||||
import RefundVendorCreditDetailDrawer from '@/containers/Drawers/RefundVendorCreditDetailDrawer';
|
import RefundVendorCreditDetailDrawer from '@/containers/Drawers/RefundVendorCreditDetailDrawer';
|
||||||
import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
||||||
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
||||||
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
|
|
||||||
|
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drawers container of the dashboard.
|
* Drawers container of the dashboard.
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const useManualJournalsColumns = () => {
|
|||||||
{
|
{
|
||||||
id: 'date',
|
id: 'date',
|
||||||
Header: intl.get('date'),
|
Header: intl.get('date'),
|
||||||
accessor: 'formatted_date',
|
accessor: 'date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
width: 115,
|
width: 115,
|
||||||
className: 'date',
|
className: 'date',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
@@ -65,7 +66,8 @@ export const useManualJournalsColumns = () => {
|
|||||||
{
|
{
|
||||||
id: 'created_at',
|
id: 'created_at',
|
||||||
Header: intl.get('created_at'),
|
Header: intl.get('created_at'),
|
||||||
accessor: 'formatted_created_at',
|
accessor: 'created_at',
|
||||||
|
Cell: FormatDateCell,
|
||||||
width: 125,
|
width: 125,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Group, Icon, If, FormattedMessage as T } from '@/components';
|
import { Icon, If, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
|
||||||
@@ -76,10 +76,7 @@ export default function MakeJournalFloatingAction() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
{/* ----------- Save And Publish ----------- */}
|
{/* ----------- Save And Publish ----------- */}
|
||||||
<If condition={!manualJournal || !manualJournal?.is_published}>
|
<If condition={!manualJournal || !manualJournal?.is_published}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
@@ -191,6 +188,6 @@ export default function MakeJournalFloatingAction() {
|
|||||||
onClick={handleCancelBtnClick}
|
onClick={handleCancelBtnClick}
|
||||||
text={<T id={'cancel'} />}
|
text={<T id={'cancel'} />}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { first } from 'lodash';
|
|
||||||
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import {
|
import {
|
||||||
@@ -35,12 +34,6 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
isLoading: isUncategorizedTransactionLoading,
|
isLoading: isUncategorizedTransactionLoading,
|
||||||
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
||||||
|
|
||||||
// Retrieves the primary branch.
|
|
||||||
const primaryBranch = useMemo(
|
|
||||||
() => branches?.find((b) => b.primary) || first(branches),
|
|
||||||
[branches],
|
|
||||||
);
|
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
uncategorizedTransaction,
|
uncategorizedTransaction,
|
||||||
@@ -49,7 +42,6 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
accounts,
|
accounts,
|
||||||
isBranchesLoading,
|
isBranchesLoading,
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
primaryBranch,
|
|
||||||
};
|
};
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { FFormGroup, FeatureCan } from '@/components';
|
|
||||||
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
|
|
||||||
import { Features } from '@/constants';
|
|
||||||
import { BranchSuggestField } from '@/components/Branches/BranchSuggestField_';
|
|
||||||
|
|
||||||
export function CategorizeTransactionBranchField() {
|
|
||||||
const { branches } = useCategorizeTransactionBoot();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FFormGroup name={'branchId'} label={'Branch'} fastField inline>
|
|
||||||
<FeatureCan feature={Features.Branches}>
|
|
||||||
<BranchSuggestField
|
|
||||||
name={'branchId'}
|
|
||||||
items={branches}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
fill
|
|
||||||
/>
|
|
||||||
</FeatureCan>
|
|
||||||
</FFormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -24,11 +24,8 @@ function CategorizeTransactionFormRoot({
|
|||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { uncategorizedTransactionId, uncategorizedTransaction } =
|
||||||
uncategorizedTransactionId,
|
useCategorizeTransactionBoot();
|
||||||
uncategorizedTransaction,
|
|
||||||
primaryBranch,
|
|
||||||
} = useCategorizeTransactionBoot();
|
|
||||||
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
||||||
|
|
||||||
// Callbacks handles form submit.
|
// Callbacks handles form submit.
|
||||||
@@ -40,28 +37,18 @@ function CategorizeTransactionFormRoot({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
|
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'The uncategorized transaction has been categorized.',
|
message: 'The uncategorized transaction has been categorized.',
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
if (
|
AppToaster.show({
|
||||||
err.response.data?.errors?.some(
|
message: 'Something went wrong!',
|
||||||
(e) => e.type === 'BRANCH_ID_REQUIRED',
|
intent: Intent.DANGER,
|
||||||
)
|
});
|
||||||
) {
|
|
||||||
setErrors({
|
|
||||||
branchId: 'The branch is required.',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong!',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Form initial values in create and edit mode.
|
// Form initial values in create and edit mode.
|
||||||
@@ -73,9 +60,6 @@ function CategorizeTransactionFormRoot({
|
|||||||
* as well.
|
* as well.
|
||||||
*/
|
*/
|
||||||
...transformToCategorizeForm(uncategorizedTransaction),
|
...transformToCategorizeForm(uncategorizedTransaction),
|
||||||
|
|
||||||
/** Assign the primary branch id as default value. */
|
|
||||||
branchId: primaryBranch?.id || null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherIncome() {
|
export default function CategorizeTransactionOtherIncome() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -69,8 +68,6 @@ export default function CategorizeTransactionOtherIncome() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerContribution() {
|
export default function CategorizeTransactionOwnerContribution() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -64,8 +63,6 @@ export default function CategorizeTransactionOwnerContribution() {
|
|||||||
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
||||||
<FTextArea name={'description'} growVertically large fill />
|
<FTextArea name={'description'} growVertically large fill />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionTransferFrom() {
|
export default function CategorizeTransactionTransferFrom() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -48,7 +47,7 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
inline
|
inline
|
||||||
>
|
>
|
||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'creditAccountId'}
|
name={'to_account_id'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['asset']}
|
filterByRootTypes={['asset']}
|
||||||
fastField
|
fastField
|
||||||
@@ -69,8 +68,6 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherExpense() {
|
export default function CategorizeTransactionOtherExpense() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -69,8 +68,6 @@ export default function CategorizeTransactionOtherExpense() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerDrawings() {
|
export default function CategorizeTransactionOwnerDrawings() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -69,8 +68,6 @@ export default function CategorizeTransactionOwnerDrawings() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
|
||||||
|
|
||||||
export default function CategorizeTransactionToAccount() {
|
export default function CategorizeTransactionToAccount() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -50,7 +49,7 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'creditAccountId'}
|
name={'creditAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['asset']}
|
filterByRootTypes={['assset']}
|
||||||
fastField
|
fastField
|
||||||
fill
|
fill
|
||||||
allowCreate
|
allowCreate
|
||||||
@@ -69,8 +68,6 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<CategorizeTransactionBranchField />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export const defaultInitialValues = {
|
|||||||
transactionType: '',
|
transactionType: '',
|
||||||
referenceNo: '',
|
referenceNo: '',
|
||||||
description: '',
|
description: '',
|
||||||
branchId: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
import { Icon, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
import { safeInvoke } from '@/utils';
|
import { safeInvoke } from '@/utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer floating actions bar.
|
* Customer floating actions bar.
|
||||||
*/
|
*/
|
||||||
@@ -50,10 +51,7 @@ export default function CustomerFloatingActions({ onCancel }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<SaveButton
|
<SaveButton
|
||||||
@@ -98,7 +96,7 @@ export default function CustomerFloatingActions({ onCancel }) {
|
|||||||
onClick={handleCancelBtnClick}
|
onClick={handleCancelBtnClick}
|
||||||
text={<T id={'cancel'} />}
|
text={<T id={'cancel'} />}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,23 +41,19 @@ export default function BillDetailHeader() {
|
|||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||||
<DetailItem label={intl.get('bill_date')}>
|
<DetailItem label={intl.get('bill_date')}>
|
||||||
{bill.formatted_bill_date}
|
<FormatDate value={bill.bill_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('due_date')}>
|
<DetailItem label={intl.get('due_date')}>
|
||||||
{bill.formatted_due_date}
|
<FormatDate value={bill.due_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('vendor_name')}>
|
<DetailItem label={intl.get('vendor_name')}>
|
||||||
<VendorDrawerLink vendorId={bill.vendor_id}>
|
<VendorDrawerLink vendorId={bill.vendor_id}>
|
||||||
{bill.vendor?.display_name}
|
{bill.vendor?.display_name}
|
||||||
</VendorDrawerLink>
|
</VendorDrawerLink>
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('bill.details.bill_number')}>
|
<DetailItem label={intl.get('bill.details.bill_number')}>
|
||||||
{defaultTo(bill.bill_number, '-')}
|
{defaultTo(bill.bill_number, '-')}
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<ExchangeRateDetailItem
|
<ExchangeRateDetailItem
|
||||||
exchangeRate={bill?.exchange_rate}
|
exchangeRate={bill?.exchange_rate}
|
||||||
toCurrency={bill?.currency_code}
|
toCurrency={bill?.currency_code}
|
||||||
@@ -79,7 +75,7 @@ export default function BillDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('bill.details.created_at')}
|
label={intl.get('bill.details.created_at')}
|
||||||
children={bill.formatted_created_at}
|
children={<FormatDate value={bill.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function CashflowTransactionDrawerHeader() {
|
|||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={<T id={'date'} />}>
|
<DetailItem label={<T id={'date'} />}>
|
||||||
{cashflowTransaction.formatted_date}
|
<FormatDate value={cashflowTransaction.date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
|
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import styled from 'styled-components';
|
|||||||
import { defaultTo } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
FormatDate,
|
||||||
T,
|
T,
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
DetailsMenu,
|
DetailsMenu,
|
||||||
DetailItem,
|
DetailItem,
|
||||||
|
ButtonLink,
|
||||||
CommercialDocHeader,
|
CommercialDocHeader,
|
||||||
CommercialDocTopHeader,
|
CommercialDocTopHeader,
|
||||||
CustomerDrawerLink,
|
CustomerDrawerLink,
|
||||||
@@ -45,7 +47,7 @@ export default function CreditNoteDetailHeader() {
|
|||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('credit_note.drawer.label_credit_note_date')}
|
label={intl.get('credit_note.drawer.label_credit_note_date')}
|
||||||
>
|
>
|
||||||
{creditNote.formatted_credit_note_date}
|
<FormatDate value={creditNote.formatted_credit_note_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem
|
<DetailItem
|
||||||
@@ -83,7 +85,7 @@ export default function CreditNoteDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={<T id={'credit_note.drawer.label_created_at'} />}
|
label={<T id={'credit_note.drawer.label_created_at'} />}
|
||||||
children={creditNote.formatted_created_at}
|
children={<FormatDate value={creditNote.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export default function EstimateDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={<T id={'estimate.details.created_at'} />}
|
label={<T id={'estimate.details.created_at'} />}
|
||||||
children={estimate.formatted_created_at}
|
children={<FormatDate value={estimate.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { defaultTo } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export default function ExpenseDrawerHeader() {
|
|||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||||
<DetailItem name={'date'} label={<T id={'date'} />}>
|
<DetailItem name={'date'} label={<T id={'date'} />}>
|
||||||
{expense.formatted_payment_date}
|
{moment(expense.payment_date).format('YYYY MMM DD')}
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
|
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
|
||||||
@@ -65,11 +66,11 @@ export default function ExpenseDrawerHeader() {
|
|||||||
minLabelSize={'180px'}
|
minLabelSize={'180px'}
|
||||||
>
|
>
|
||||||
<DetailItem label={<T id={'published_at'} />}>
|
<DetailItem label={<T id={'published_at'} />}>
|
||||||
{expense.formatted_date}
|
<FormatDate value={expense.published_at} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={<T id={'created_at'} />}>
|
<DetailItem label={<T id={'created_at'} />}>
|
||||||
{expense.formatted_created_at}
|
<FormatDate value={expense.created_at} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ export default function InvoiceDetailHeader() {
|
|||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||||
<DetailItem label={intl.get('invoice_date')}>
|
<DetailItem label={intl.get('invoice_date')}>
|
||||||
{invoice.invoice_date_formatted}
|
<FormatDate value={invoice.invoice_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('due_date')}>
|
<DetailItem label={intl.get('due_date')}>
|
||||||
{invoice.due_date_formatted}
|
<FormatDate value={invoice.due_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('customer_name')}>
|
<DetailItem label={intl.get('customer_name')}>
|
||||||
@@ -86,7 +86,7 @@ export default function InvoiceDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('invoice.details.created_at')}
|
label={intl.get('invoice.details.created_at')}
|
||||||
children={invoice.created_at_formatted}
|
children={<FormatDate value={invoice.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function PaymentMadeDetailHeader() {
|
|||||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('payment_date')}
|
label={intl.get('payment_date')}
|
||||||
children={paymentMade.formatted_payment_date}
|
children={<FormatDate value={paymentMade.payment_date} />}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('payment_made.details.payment_number')}
|
label={intl.get('payment_made.details.payment_number')}
|
||||||
@@ -58,7 +58,6 @@ export default function PaymentMadeDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<DetailsMenu
|
<DetailsMenu
|
||||||
textAlign={'right'}
|
textAlign={'right'}
|
||||||
@@ -71,7 +70,7 @@ export default function PaymentMadeDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('created_at')}
|
label={intl.get('created_at')}
|
||||||
children={paymentMade.formatted_created_at}
|
children={<FormatDate value={paymentMade.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
import { getColumnWidth } from '@/utils';
|
import { getColumnWidth } from '@/utils';
|
||||||
import { FormatNumberCell } from '@/components';
|
import { FormatNumberCell } from '@/components';
|
||||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||||
@@ -15,7 +17,7 @@ export const usePaymentMadeEntriesColumns = () => {
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: intl.get('date'),
|
Header: intl.get('date'),
|
||||||
accessor: 'bill.formatted_bill_date',
|
accessor: (row) => moment(row.date).format('YYYY MMM DD'),
|
||||||
width: 100,
|
width: 100,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
className: 'date',
|
className: 'date',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defaultTo } from 'lodash';
|
|||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
|
FormatDate,
|
||||||
DetailsMenu,
|
DetailsMenu,
|
||||||
DetailItem,
|
DetailItem,
|
||||||
CommercialDocHeader,
|
CommercialDocHeader,
|
||||||
@@ -35,7 +36,7 @@ export default function PaymentReceiveDetailHeader() {
|
|||||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('payment_date')}
|
label={intl.get('payment_date')}
|
||||||
children={paymentReceive.formatted_payment_date}
|
children={<FormatDate value={paymentReceive.payment_date} />}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('payment_receive.details.payment_number')}
|
label={intl.get('payment_receive.details.payment_number')}
|
||||||
@@ -70,7 +71,7 @@ export default function PaymentReceiveDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('created_at')}
|
label={intl.get('created_at')}
|
||||||
children={paymentReceive.formatted_created_at}
|
children={<FormatDate value={paymentReceive.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
@@ -25,7 +26,7 @@ export const usePaymentReceiveEntriesColumns = () => {
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: intl.get('date'),
|
Header: intl.get('date'),
|
||||||
accessor: 'invoice.invoice_date_formatted',
|
accessor: (row) => moment(row.payment_date).format('YYYY MMM DD'),
|
||||||
width: 100,
|
width: 100,
|
||||||
className: 'date',
|
className: 'date',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ export default function ReceiptDetailHeader() {
|
|||||||
</DetailItem>
|
</DetailItem>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('receipt_date')}
|
label={intl.get('receipt_date')}
|
||||||
children={receipt.formatted_receipt_date}
|
children={<FormatDate value={receipt.receipt_date} />}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('closed_date')}
|
label={intl.get('closed_date')}
|
||||||
children={receipt.formatted_closed_at_date}
|
children={<FormatDate value={receipt.closed_at_date} />}
|
||||||
/>
|
/>
|
||||||
<ExchangeRateDetailItem
|
<ExchangeRateDetailItem
|
||||||
exchangeRate={receipt?.exchange_rate}
|
exchangeRate={receipt?.exchange_rate}
|
||||||
@@ -82,7 +82,7 @@ export default function ReceiptDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('receipt.details.created_at')}
|
label={intl.get('receipt.details.created_at')}
|
||||||
children={receipt.formatted_created_at}
|
children={<FormatDate value={receipt.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function VendorCreditDetailHeader() {
|
|||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('vendor_credit.drawer.label_vendor_credit_date')}
|
label={intl.get('vendor_credit.drawer.label_vendor_credit_date')}
|
||||||
>
|
>
|
||||||
{vendorCredit.formatted_vendor_credit_date}
|
<FormatDate value={vendorCredit.formatted_vendor_credit_date} />
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={intl.get('vendor_credit.drawer.label_vendor_credit_no')}
|
label={intl.get('vendor_credit.drawer.label_vendor_credit_no')}
|
||||||
@@ -78,7 +78,7 @@ export default function VendorCreditDetailHeader() {
|
|||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={<T id={'vendor_credit.drawer.label_created_at'} />}
|
label={<T id={'vendor_credit.drawer.label_created_at'} />}
|
||||||
children={vendorCredit.formatted_created_at}
|
children={<FormatDate value={vendorCredit.created_at} />}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Group, FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
@@ -78,10 +78,7 @@ export default function ExpenseFloatingFooter() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
{/* ----------- Save And Publish ----------- */}
|
{/* ----------- Save And Publish ----------- */}
|
||||||
<If condition={isNewMode}>
|
<If condition={isNewMode}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
@@ -193,6 +190,6 @@ export default function ExpenseFloatingFooter() {
|
|||||||
onClick={handleCancelBtnClick}
|
onClick={handleCancelBtnClick}
|
||||||
text={<T id={'cancel'} />}
|
text={<T id={'cancel'} />}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ import clsx from 'classnames';
|
|||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
|
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
|
||||||
import { FormattedMessage as T, Icon, If, Can } from '@/components';
|
import {
|
||||||
|
FormatDateCell,
|
||||||
|
FormattedMessage as T,
|
||||||
|
Icon,
|
||||||
|
If,
|
||||||
|
Can,
|
||||||
|
} from '@/components';
|
||||||
import { safeCallback } from '@/utils';
|
import { safeCallback } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,7 +137,8 @@ export function useExpensesTableColumns() {
|
|||||||
{
|
{
|
||||||
id: 'payment_date',
|
id: 'payment_date',
|
||||||
Header: intl.get('payment_date'),
|
Header: intl.get('payment_date'),
|
||||||
accessor: 'formatted_date',
|
accessor: 'payment_date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
width: 140,
|
width: 140,
|
||||||
className: 'payment_date',
|
className: 'payment_date',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
|
|||||||
@@ -96,19 +96,12 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:not(:first-child).is-expanded .td {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&--OPENING_BALANCE,
|
&--OPENING_BALANCE,
|
||||||
&--CLOSING_BALANCE {
|
&--CLOSING_BALANCE {
|
||||||
.td {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.date {
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
.cell-inner {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.amount {
|
.amount {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -117,9 +110,6 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
.name {
|
.name {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.td {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import classNames from 'classnames';
|
|||||||
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
||||||
import { FastField, useFormikContext } from 'formik';
|
import { FastField, useFormikContext } from 'formik';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
import { Group, FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import { saveInvoke } from '@/utils';
|
import { saveInvoke } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,10 +37,7 @@ export default function ItemFormFloatingActions({ onCancel }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
@@ -80,7 +78,7 @@ export default function ItemFormFloatingActions({ onCancel }) {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import ItemForm from './ItemForm';
|
import ItemForm from './ItemForm';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form page.
|
* Item form page.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
|
||||||
export const useGLEntriesTableColumns = () => {
|
export const useGLEntriesTableColumns = () => {
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: intl.get('date'),
|
Header: intl.get('date'),
|
||||||
accessor: 'date.formatted_date',
|
accessor: ({ formatted_date }) =>
|
||||||
|
moment(formatted_date).format('YYYY MMM DD'),
|
||||||
width: 140,
|
width: 140,
|
||||||
className: 'date',
|
className: 'date',
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Group, FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -76,10 +76,7 @@ export default function BillFloatingActions() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
{/* ----------- Save And Open ----------- */}
|
{/* ----------- Save And Open ----------- */}
|
||||||
<If condition={!bill || !bill?.is_open}>
|
<If condition={!bill || !bill?.is_open}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
@@ -192,6 +189,6 @@ export default function BillFloatingActions() {
|
|||||||
onClick={handleCancelBtnClick}
|
onClick={handleCancelBtnClick}
|
||||||
text={<T id={'cancel'} />}
|
text={<T id={'cancel'} />}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ export function useBillsTableColumns() {
|
|||||||
{
|
{
|
||||||
id: 'bill_date',
|
id: 'bill_date',
|
||||||
Header: intl.get('bill_date'),
|
Header: intl.get('bill_date'),
|
||||||
accessor: 'formatted_bill_date',
|
accessor: 'bill_date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
width: 110,
|
width: 110,
|
||||||
className: 'bill_date',
|
className: 'bill_date',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { If, Icon, FormattedMessage as T, Group } from '@/components';
|
import { If, Icon, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
|
import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
|
||||||
|
|
||||||
@@ -69,15 +69,11 @@ export default function VendorCreditNoteFloatingActions() {
|
|||||||
history.goBack();
|
history.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the clear button click.
|
|
||||||
const handleClearBtnClick = (event) => {
|
const handleClearBtnClick = (event) => {
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Group
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
{/* ----------- Save And Open ----------- */}
|
{/* ----------- Save And Open ----------- */}
|
||||||
<If condition={!vendorCredit || !vendorCredit?.is_open}>
|
<If condition={!vendorCredit || !vendorCredit?.is_open}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
@@ -189,6 +185,6 @@ export default function VendorCreditNoteFloatingActions() {
|
|||||||
onClick={handleCancelBtnClick}
|
onClick={handleCancelBtnClick}
|
||||||
text={<T id={'cancel'} />}
|
text={<T id={'cancel'} />}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user