mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-11 10:20:30 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff8b41720 | ||
|
|
632cc3d72e | ||
|
|
3e437a041c | ||
|
|
e783cfeafa | ||
|
|
5dde7f5584 | ||
|
|
8e0911ec85 | ||
|
|
7b4afd3859 | ||
|
|
590715037b | ||
|
|
1e53a8e85e | ||
|
|
2ad77103ac | ||
|
|
c1fc70863b | ||
|
|
125dff8376 | ||
|
|
84da7b7df5 | ||
|
|
4c82f6f8ad | ||
|
|
0d7aad5448 | ||
|
|
74b74a2722 | ||
|
|
3a0a0db8a7 | ||
|
|
265ea9ca48 | ||
|
|
cfd37f8894 | ||
|
|
d1caa5c5ce | ||
|
|
d998d716b7 | ||
|
|
031ccc4a0b | ||
|
|
e4f61823b3 | ||
|
|
1cbc1c056f | ||
|
|
4d4ef54c56 | ||
|
|
f7fcfefc78 | ||
|
|
858f347fd4 | ||
|
|
4d73b59cf3 | ||
|
|
bc67f0cca8 | ||
|
|
ef2d1ff141 | ||
|
|
dc4cdb2a8f | ||
|
|
8b99e0938d | ||
|
|
94192bfc29 | ||
|
|
708a4dda9e | ||
|
|
10fcf94c92 | ||
|
|
5dbfd36415 | ||
|
|
044f11ff74 | ||
|
|
6afe1a09c6 |
@@ -84,8 +84,8 @@ LEMONSQUEEZY_STORE_ID=
|
||||
LEMONSQUEEZY_WEBHOOK_SECRET=
|
||||
|
||||
# S3 documents and attachments
|
||||
S3_REGION=
|
||||
S3_REGION=US
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_ENDPOINT=
|
||||
S3_BUCKET=
|
||||
S3_BUCKET=
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
## [v0.17.5] - 17-06-2024
|
||||
|
||||
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
||||
* fix: add space between buttons on floating actions bar by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/508
|
||||
* feat: Migrating to Envoy proxy instead of Nginx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/509
|
||||
* fix: Disable email confirmation does not work with invited users by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/497
|
||||
* feat: Setting up the date format in the whole system dates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/506
|
||||
|
||||
## [0.17.0] - 04-06-2024
|
||||
|
||||
### New
|
||||
|
||||
@@ -3,24 +3,17 @@
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
nginx:
|
||||
container_name: bigcapital-nginx-gateway
|
||||
build:
|
||||
context: ./docker/nginx
|
||||
args:
|
||||
- SERVER_PROXY_PORT=3000
|
||||
- WEB_SSL=false
|
||||
- SELF_SIGNED=false
|
||||
volumes:
|
||||
- ./data/logs/nginx/:/var/log/nginx
|
||||
- ./docker/certbot/certs/:/var/certs
|
||||
proxy:
|
||||
image: envoyproxy/envoy:v1.30-latest
|
||||
depends_on:
|
||||
- server
|
||||
- webapp
|
||||
ports:
|
||||
- '${PUBLIC_PROXY_PORT:-80}:80'
|
||||
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
||||
tty: true
|
||||
depends_on:
|
||||
- server
|
||||
- webapp
|
||||
volumes:
|
||||
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
@@ -46,6 +39,8 @@ services:
|
||||
- mongo
|
||||
- redis
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
environment:
|
||||
# Mail
|
||||
- MAIL_HOST=${MAIL_HOST}
|
||||
@@ -127,8 +122,6 @@ services:
|
||||
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
||||
- S3_ENDPOINT=${S3_ENDPOINT}
|
||||
- S3_BUCKET=${S3_BUCKET}
|
||||
networks:
|
||||
- bigcapital_network
|
||||
|
||||
database_migration:
|
||||
container_name: bigcapital-database-migration
|
||||
|
||||
62
docker/envoy/envoy.yaml
Normal file
62
docker/envoy/envoy.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 80
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: backend
|
||||
domains: ['*']
|
||||
routes:
|
||||
- match:
|
||||
prefix: '/api'
|
||||
route:
|
||||
cluster: dynamic_server
|
||||
- match:
|
||||
prefix: '/'
|
||||
route:
|
||||
cluster: webapp
|
||||
http_filters:
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
clusters:
|
||||
- name: dynamic_server
|
||||
connect_timeout: 0.25s
|
||||
type: STRICT_DNS
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: dynamic_server
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: server
|
||||
port_value: 3000
|
||||
|
||||
- name: webapp
|
||||
connect_timeout: 0.25s
|
||||
type: STRICT_DNS
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: webapp
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: webapp
|
||||
port_value: 80
|
||||
@@ -1,21 +0,0 @@
|
||||
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
|
||||
@@ -1,33 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,16 +0,0 @@
|
||||
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,6 +25,7 @@
|
||||
"@casl/ability": "^5.4.3",
|
||||
"@hapi/boom": "^7.4.3",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
"@supercharge/promise-pool": "^3.2.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/i18n": "^0.8.7",
|
||||
"@types/knex": "^0.16.1",
|
||||
|
||||
@@ -5,7 +5,7 @@ import DashboardService from '@/services/Dashboard/DashboardService';
|
||||
@Service()
|
||||
export default class DashboardMetaController {
|
||||
@Inject()
|
||||
dashboardService: DashboardService;
|
||||
private dashboardService: DashboardService;
|
||||
|
||||
/**
|
||||
* Constructor router.
|
||||
|
||||
@@ -4,6 +4,7 @@ import color from 'colorette';
|
||||
import argv from 'getopts';
|
||||
import Knex from 'knex';
|
||||
import { knexSnakeCaseMappers } from 'objection';
|
||||
import { PromisePool } from '@supercharge/promise-pool';
|
||||
import '../before';
|
||||
import config from '../config';
|
||||
|
||||
@@ -28,7 +29,7 @@ function initSystemKnex() {
|
||||
});
|
||||
}
|
||||
|
||||
function initTenantKnex(organizationId) {
|
||||
function initTenantKnex(organizationId: string = '') {
|
||||
return Knex({
|
||||
client: config.tenant.db_client,
|
||||
connection: {
|
||||
@@ -71,10 +72,12 @@ function getAllSystemTenants(knex) {
|
||||
return knex('tenants');
|
||||
}
|
||||
|
||||
function getAllInitializedSystemTenants(knex) {
|
||||
function getAllInitializedTenants(knex) {
|
||||
return knex('tenants').whereNotNull('initializedAt');
|
||||
}
|
||||
|
||||
const MIGRATION_CONCURRENCY = 10;
|
||||
|
||||
// module.exports = {
|
||||
// log,
|
||||
// success,
|
||||
@@ -91,6 +94,7 @@ function getAllInitializedSystemTenants(knex) {
|
||||
// - bigcapital tenants:migrate:make
|
||||
// - bigcapital system:migrate:make
|
||||
// - bigcapital tenants:list
|
||||
// - bigcapital tenants:list --all
|
||||
|
||||
commander
|
||||
.command('system:migrate:rollback')
|
||||
@@ -149,10 +153,13 @@ commander
|
||||
commander
|
||||
.command('tenants:list')
|
||||
.description('Retrieve a list of all system tenants databases.')
|
||||
.option('-a, --all', 'All tenants even are not initialized.')
|
||||
.action(async (cmd) => {
|
||||
try {
|
||||
const sysKnex = await initSystemKnex();
|
||||
const tenants = await getAllSystemTenants(sysKnex);
|
||||
const tenants = cmd?.all
|
||||
? await getAllSystemTenants(sysKnex)
|
||||
: await getAllInitializedTenants(sysKnex);
|
||||
|
||||
tenants.forEach((tenant) => {
|
||||
const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`;
|
||||
@@ -183,18 +190,20 @@ commander
|
||||
commander
|
||||
.command('tenants:migrate:latest')
|
||||
.description('Migrate all tenants or the given tenant id.')
|
||||
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
||||
.option(
|
||||
'-t, --tenant_id [tenant_id]',
|
||||
'Which organization id do you migrate.'
|
||||
)
|
||||
.action(async (cmd) => {
|
||||
try {
|
||||
const sysKnex = await initSystemKnex();
|
||||
const tenants = await getAllInitializedSystemTenants(sysKnex);
|
||||
const tenants = await getAllInitializedTenants(sysKnex);
|
||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||
|
||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||
}
|
||||
// Validate the tenant id exist first of all.
|
||||
const migrateOpers = [];
|
||||
const migrateTenant = async (organizationId) => {
|
||||
try {
|
||||
const tenantKnex = await initTenantKnex(organizationId);
|
||||
@@ -216,17 +225,17 @@ commander
|
||||
}
|
||||
};
|
||||
if (!cmd.tenant_id) {
|
||||
tenants.forEach((tenant) => {
|
||||
const oper = migrateTenant(tenant.organizationId);
|
||||
migrateOpers.push(oper);
|
||||
});
|
||||
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||
.for(tenants)
|
||||
.process((tenant, index, pool) => {
|
||||
return migrateTenant(tenant.organizationId);
|
||||
})
|
||||
.then(() => {
|
||||
success('All tenants are migrated.');
|
||||
});
|
||||
} else {
|
||||
const oper = migrateTenant(cmd.tenant_id);
|
||||
migrateOpers.push(oper);
|
||||
await migrateTenant(cmd.tenant_id);
|
||||
}
|
||||
Promise.all(migrateOpers).then(() => {
|
||||
success('All tenants are migrated.');
|
||||
});
|
||||
} catch (error) {
|
||||
exit(error);
|
||||
}
|
||||
@@ -235,19 +244,21 @@ commander
|
||||
commander
|
||||
.command('tenants:migrate:rollback')
|
||||
.description('Rollback the last batch of tenants migrations.')
|
||||
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
||||
.option(
|
||||
'-t, --tenant_id [tenant_id]',
|
||||
'Which organization id do you migrate.'
|
||||
)
|
||||
.action(async (cmd) => {
|
||||
try {
|
||||
const sysKnex = await initSystemKnex();
|
||||
const tenants = await getAllSystemTenants(sysKnex);
|
||||
const tenants = await getAllInitializedTenants(sysKnex);
|
||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||
|
||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||
}
|
||||
|
||||
const migrateOpers = [];
|
||||
const migrateTenant = async (organizationId) => {
|
||||
const migrateTenant = async (organizationId: string) => {
|
||||
try {
|
||||
const tenantKnex = await initTenantKnex(organizationId);
|
||||
const [batchNo, _log] = await tenantKnex.migrate.rollback();
|
||||
@@ -268,17 +279,17 @@ commander
|
||||
};
|
||||
|
||||
if (!cmd.tenant_id) {
|
||||
tenants.forEach((tenant) => {
|
||||
const oper = migrateTenant(tenant.organizationId);
|
||||
migrateOpers.push(oper);
|
||||
});
|
||||
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||
.for(tenants)
|
||||
.process((tenant, index, pool) => {
|
||||
return migrateTenant(tenant.organizationId);
|
||||
})
|
||||
.then(() => {
|
||||
success('All tenants are rollbacked.');
|
||||
});
|
||||
} else {
|
||||
const oper = migrateTenant(cmd.tenant_id);
|
||||
migrateOpers.push(oper);
|
||||
await migrateTenant(cmd.tenant_id);
|
||||
}
|
||||
Promise.all(migrateOpers).then(() => {
|
||||
success('All tenants are rollbacked.');
|
||||
});
|
||||
} catch (error) {
|
||||
exit(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.table('settings', (table) => {
|
||||
table.text('value').alter();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => {
|
||||
return knex.schema.table('settings', (table) => {
|
||||
table.string('value').alter();
|
||||
});
|
||||
};
|
||||
@@ -56,6 +56,8 @@ export interface IGeneralLedgerSheetAccount {
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[];
|
||||
openingBalance: IGeneralLedgerSheetAccountBalance;
|
||||
closingBalance: IGeneralLedgerSheetAccountBalance;
|
||||
closingBalanceSubaccounts?: IGeneralLedgerSheetAccountBalance;
|
||||
children?: IGeneralLedgerSheetAccount[];
|
||||
}
|
||||
|
||||
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||
|
||||
@@ -149,13 +149,19 @@ export class Transformer {
|
||||
return this.excludeAttributes().length > 0;
|
||||
};
|
||||
|
||||
private dateFormat = 'YYYY MMM DD';
|
||||
|
||||
setDateFormat(format: string) {
|
||||
this.dateFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param date
|
||||
* @returns
|
||||
*/
|
||||
protected formatDate(date) {
|
||||
return date ? moment(date).format('YYYY/MM/DD') : '';
|
||||
return date ? moment(date).format(this.dateFormat) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,6 +199,7 @@ export class Transformer {
|
||||
) {
|
||||
transformer.setOptions(options);
|
||||
transformer.setContext(this.context);
|
||||
transformer.setDateFormat(this.dateFormat);
|
||||
|
||||
return transformer.work(obj);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,17 @@ 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.
|
||||
* @param {number} tenantId
|
||||
@@ -41,7 +52,11 @@ export class TransformerInjectable {
|
||||
if (!isNull(tenantId)) {
|
||||
const context = await this.getApplicationContext(tenantId);
|
||||
transformer.setContext(context);
|
||||
|
||||
const dateFormat = await this.getTenantDateFormat(tenantId);
|
||||
transformer.setDateFormat(dateFormat);
|
||||
}
|
||||
|
||||
transformer.setOptions(options);
|
||||
|
||||
return transformer.work(object);
|
||||
|
||||
@@ -104,10 +104,10 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
*/
|
||||
private async updateUncategorizedTransactionCount(
|
||||
queryContext: QueryContext,
|
||||
increment: boolean
|
||||
increment: boolean,
|
||||
amount: number = 1
|
||||
) {
|
||||
const operation = increment ? 'increment' : 'decrement';
|
||||
const amount = increment ? 1 : -1;
|
||||
|
||||
await Account.query(queryContext.transaction)
|
||||
.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.
|
||||
* @param {number[]} accountIds
|
||||
* @param {number[]} accountIds
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public whereAccountsIds(accountIds: number[]): ILedger {
|
||||
@@ -274,4 +274,14 @@ export default class Ledger implements ILedger {
|
||||
const entries = Ledger.mappingTransactions(transactions);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Container, Inject } from 'typedi';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { SystemUser, Tenant } from '@/system/models';
|
||||
import {
|
||||
IAuthSignedInEventPayload,
|
||||
IAuthSigningInEventPayload,
|
||||
@@ -64,7 +64,9 @@ export class AuthSigninService {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
|
||||
// Finds the user of the given email address.
|
||||
const user = await systemUserRepository.findOneByEmail(email);
|
||||
const user = await SystemUser.query()
|
||||
.findOne('email', email)
|
||||
.modify('inviteAccepted');
|
||||
|
||||
// Validate the given email and password.
|
||||
await this.validateSignIn(user, email, password);
|
||||
|
||||
@@ -7,7 +7,12 @@ export class CashflowTransactionTransformer extends Transformer {
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['formattedAmount', 'transactionTypeFormatted'];
|
||||
return [
|
||||
'formattedAmount',
|
||||
'transactionTypeFormatted',
|
||||
'formattedDate',
|
||||
'formattedCreatedAt',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,10 +29,28 @@ export class CashflowTransactionTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Formatted transaction type.
|
||||
* @param transaction
|
||||
* @param transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected transactionTypeFormatted = (transaction) => {
|
||||
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,7 +12,6 @@ import { Knex } from 'knex';
|
||||
import { transformCategorizeTransToCashflow } from './utils';
|
||||
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
||||
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
|
||||
|
||||
@Service()
|
||||
export class CategorizeCashflowTransaction {
|
||||
|
||||
@@ -68,7 +68,11 @@ export const CASHFLOW_TRANSACTION_TYPE_META = {
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
||||
type: 'OtherExpense',
|
||||
direction: CASHFLOW_DIRECTION.OUT,
|
||||
creditType: [ACCOUNT_TYPE.EXPENSE, ACCOUNT_TYPE.OTHER_EXPENSE],
|
||||
creditType: [
|
||||
ACCOUNT_TYPE.EXPENSE,
|
||||
ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { upperFirst, camelCase, omit } from 'lodash';
|
||||
import { upperFirst, camelCase } from 'lodash';
|
||||
import {
|
||||
CASHFLOW_TRANSACTION_TYPE,
|
||||
CASHFLOW_TRANSACTION_TYPE_META,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from './constants';
|
||||
import {
|
||||
ICashflowNewCommandDTO,
|
||||
ICashflowTransaction,
|
||||
ICategorizeCashflowTransactioDTO,
|
||||
IUncategorizedCashflowTransaction,
|
||||
} from '@/interfaces';
|
||||
@@ -42,8 +41,8 @@ export const getCashflowAccountTransactionsTypes = () => {
|
||||
/**
|
||||
* Tranasformes the given uncategorized transaction and categorized DTO
|
||||
* to cashflow create DTO.
|
||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
||||
* @returns {ICashflowNewCommandDTO}
|
||||
*/
|
||||
export const transformCategorizeTransToCashflow = (
|
||||
@@ -62,6 +61,7 @@ export const transformCategorizeTransToCashflow = (
|
||||
transactionNumber: categorizeDTO.transactionNumber,
|
||||
transactionType: categorizeDTO.transactionType,
|
||||
uncategorizedTransactionId: uncategorizeModel.id,
|
||||
branchId: categorizeDTO?.branchId,
|
||||
publish: true,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ export class CreditNoteTransformer extends Transformer {
|
||||
return [
|
||||
'formattedCreditsRemaining',
|
||||
'formattedCreditNoteDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedCreatedAt',
|
||||
'formattedAmount',
|
||||
'formattedCreditsUsed',
|
||||
'formattedSubtotal',
|
||||
@@ -30,6 +32,15 @@ export class CreditNoteTransformer extends Transformer {
|
||||
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.
|
||||
* @param {ICreditNote} credit
|
||||
|
||||
@@ -15,6 +15,7 @@ export class ExpenseTransfromer extends Transformer {
|
||||
'formattedLandedCostAmount',
|
||||
'formattedAllocatedCostAmount',
|
||||
'formattedDate',
|
||||
'formattedCreatedAt',
|
||||
'categories',
|
||||
'attachments',
|
||||
];
|
||||
@@ -62,6 +63,15 @@ export class ExpenseTransfromer extends Transformer {
|
||||
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.
|
||||
* @param {IExpense} expense
|
||||
|
||||
@@ -20,6 +20,8 @@ import { BalanceSheetPercentage } from './BalanceSheetPercentage';
|
||||
import { BalanceSheetSchema } from './BalanceSheetSchema';
|
||||
import { BalanceSheetBase } from './BalanceSheetBase';
|
||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||
import { flatToNestedArray } from '@/utils';
|
||||
import BalanceSheetRepository from './BalanceSheetRepository';
|
||||
|
||||
export const BalanceSheetAccounts = (Base: any) =>
|
||||
class extends R.compose(
|
||||
@@ -56,6 +58,11 @@ export const BalanceSheetAccounts = (Base: any) =>
|
||||
*/
|
||||
readonly i18n: any;
|
||||
|
||||
/**
|
||||
* Balance sheet repository.
|
||||
*/
|
||||
readonly repository: BalanceSheetRepository;
|
||||
|
||||
/**
|
||||
* Retrieve the accounts node of accounts types.
|
||||
* @param {string} accountsTypes
|
||||
@@ -78,8 +85,12 @@ export const BalanceSheetAccounts = (Base: any) =>
|
||||
private reportSchemaAccountNodeMapper = (
|
||||
account: IAccount
|
||||
): IBalanceSheetAccountNode => {
|
||||
const childrenAccountsIds = this.repository.accountsGraph.dependenciesOf(
|
||||
account.id
|
||||
);
|
||||
const accountIds = R.uniq(R.append(account.id, childrenAccountsIds));
|
||||
const total = this.repository.totalAccountsLedger
|
||||
.whereAccountId(account.id)
|
||||
.whereAccountsIds(accountIds)
|
||||
.getClosingBalance();
|
||||
|
||||
return {
|
||||
@@ -128,8 +139,19 @@ export const BalanceSheetAccounts = (Base: any) =>
|
||||
private getAccountsNodesByAccountTypes = (
|
||||
accountsTypes: string[]
|
||||
): IBalanceSheetAccountNode[] => {
|
||||
// Retrieves accounts from the given defined node account types.
|
||||
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,6 +1,4 @@
|
||||
import * as R from 'ramda';
|
||||
import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod';
|
||||
import { FinancialHorizTotals } from '../FinancialHorizTotals';
|
||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||
import {
|
||||
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
IAccount,
|
||||
IAccountTransactionsGroupBy,
|
||||
IBalanceSheetQuery,
|
||||
ILedger,
|
||||
@@ -12,7 +11,6 @@ import { transformToMapBy } from 'utils';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
||||
|
||||
@Service()
|
||||
@@ -40,6 +38,11 @@ export default class BalanceSheetRepository extends R.compose(
|
||||
*/
|
||||
public accounts: any;
|
||||
|
||||
/**
|
||||
* @param {}
|
||||
*/
|
||||
public accountsGraph: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -163,6 +166,8 @@ export default class BalanceSheetRepository extends R.compose(
|
||||
*/
|
||||
public asyncInitialize = async () => {
|
||||
await this.initAccounts();
|
||||
await this.initAccountsGraph();
|
||||
|
||||
await this.initAccountsTotalLedger();
|
||||
|
||||
// Date periods.
|
||||
@@ -204,6 +209,15 @@ export default class BalanceSheetRepository extends R.compose(
|
||||
this.accountsByParentType = transformToMapBy(accounts, 'accountParentType');
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize accounts graph.
|
||||
*/
|
||||
public initAccountsGraph = async () => {
|
||||
const { Account } = this.models;
|
||||
|
||||
this.accountsGraph = Account.toDependencyGraph(this.accounts);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Closing Total
|
||||
// ----------------------------
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import { isEmpty, get, last, sumBy } from 'lodash';
|
||||
import { isEmpty, get, last, sumBy, first, head } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IGeneralLedgerSheetQuery,
|
||||
IGeneralLedgerSheetAccount,
|
||||
IGeneralLedgerSheetAccountBalance,
|
||||
IGeneralLedgerSheetAccountTransaction,
|
||||
IAccount,
|
||||
IJournalPoster,
|
||||
IJournalEntry,
|
||||
IContact,
|
||||
ILedgerEntry,
|
||||
} from '@/interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import moment from 'moment';
|
||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||
import { flatToNestedArray } from '@/utils';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import { calculateRunningBalance } from './_utils';
|
||||
|
||||
/**
|
||||
* General ledger sheet.
|
||||
*/
|
||||
export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
tenantId: number;
|
||||
accounts: IAccount[];
|
||||
query: IGeneralLedgerSheetQuery;
|
||||
openingBalancesJournal: IJournalPoster;
|
||||
transactions: IJournalPoster;
|
||||
contactsMap: Map<number, IContact>;
|
||||
baseCurrency: string;
|
||||
i18n: any;
|
||||
export default class GeneralLedgerSheet extends R.compose(
|
||||
FinancialSheetStructure
|
||||
)(FinancialSheet) {
|
||||
private query: IGeneralLedgerSheetQuery;
|
||||
private baseCurrency: string;
|
||||
private i18n: any;
|
||||
private repository: GeneralLedgerRepository;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -34,63 +36,59 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
* @param {IJournalPoster} closingBalancesJournal -
|
||||
*/
|
||||
constructor(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery,
|
||||
accounts: IAccount[],
|
||||
contactsByIdMap: Map<number, IContact>,
|
||||
transactions: IJournalPoster,
|
||||
openingBalancesJournal: IJournalPoster,
|
||||
baseCurrency: string,
|
||||
repository: GeneralLedgerRepository,
|
||||
i18n
|
||||
) {
|
||||
super();
|
||||
|
||||
this.tenantId = tenantId;
|
||||
this.query = query;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
this.accounts = accounts;
|
||||
this.contactsMap = contactsByIdMap;
|
||||
this.transactions = transactions;
|
||||
this.openingBalancesJournal = openingBalancesJournal;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.repository = repository;
|
||||
this.baseCurrency = this.repository.tenant.metadata.currencyCode;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the transaction amount.
|
||||
* @param {number} credit - Credit amount.
|
||||
* @param {number} debit - Debit amount.
|
||||
* @param {string} normal - Credit or debit.
|
||||
*/
|
||||
getAmount(credit: number, debit: number, normal: string) {
|
||||
return normal === 'credit' ? credit - debit : debit - credit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry mapper.
|
||||
* @param {IJournalEntry} entry -
|
||||
* @param {ILedgerEntry} entry -
|
||||
* @return {IGeneralLedgerSheetAccountTransaction}
|
||||
*/
|
||||
entryReducer(
|
||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||
entry: IJournalEntry,
|
||||
openingBalance: number
|
||||
): IGeneralLedgerSheetAccountTransaction[] {
|
||||
const lastEntry = last(entries);
|
||||
private getEntryRunningBalance(
|
||||
entry: ILedgerEntry,
|
||||
openingBalance: number,
|
||||
runningBalance?: number
|
||||
): number {
|
||||
const lastRunningBalance = runningBalance || openingBalance;
|
||||
|
||||
const contact = this.contactsMap.get(entry.contactId);
|
||||
const amount = this.getAmount(
|
||||
const amount = Ledger.getAmount(
|
||||
entry.credit,
|
||||
entry.debit,
|
||||
entry.accountNormal
|
||||
);
|
||||
const runningBalance =
|
||||
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
|
||||
return calculateRunningBalance(amount, lastRunningBalance);
|
||||
}
|
||||
|
||||
const newEntry = {
|
||||
/**
|
||||
* Maps the given ledger entry to G/L transaction.
|
||||
* @param {ILedgerEntry} entry
|
||||
* @param {number} runningBalance
|
||||
* @returns {IGeneralLedgerSheetAccountTransaction}
|
||||
*/
|
||||
private transactionMapper(
|
||||
entry: ILedgerEntry,
|
||||
runningBalance: number
|
||||
): IGeneralLedgerSheetAccountTransaction {
|
||||
const contact = this.repository.contactsById.get(entry.contactId);
|
||||
const amount = Ledger.getAmount(
|
||||
entry.credit,
|
||||
entry.debit,
|
||||
entry.accountNormal
|
||||
);
|
||||
return {
|
||||
id: entry.id,
|
||||
date: entry.date,
|
||||
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||
entryId: entry.id,
|
||||
|
||||
transactionNumber: entry.transactionNumber,
|
||||
referenceType: entry.referenceType,
|
||||
@@ -109,16 +107,15 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
amount,
|
||||
runningBalance,
|
||||
|
||||
formattedAmount: this.formatNumber(amount),
|
||||
formattedCredit: this.formatNumber(entry.credit),
|
||||
formattedDebit: this.formatNumber(entry.debit),
|
||||
formattedRunningBalance: this.formatNumber(runningBalance),
|
||||
formattedAmount: this.formatNumber(amount, { excerptZero: false }),
|
||||
formattedCredit: this.formatNumber(entry.credit, { excerptZero: false }),
|
||||
formattedDebit: this.formatNumber(entry.debit, { excerptZero: false }),
|
||||
formattedRunningBalance: this.formatNumber(runningBalance, {
|
||||
excerptZero: false,
|
||||
}),
|
||||
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
entries.push(newEntry);
|
||||
|
||||
return entries;
|
||||
} as IGeneralLedgerSheetAccountTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,28 +127,48 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
account: IAccount,
|
||||
openingBalance: number
|
||||
): IGeneralLedgerSheetAccountTransaction[] {
|
||||
const entries = this.transactions.getAccountEntries(account.id);
|
||||
const entries = this.repository.transactionsLedger
|
||||
.whereAccountId(account.id)
|
||||
.getEntries();
|
||||
|
||||
return entries.reduce(
|
||||
(
|
||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||
entry: IJournalEntry
|
||||
) => {
|
||||
return this.entryReducer(entries, entry, openingBalance);
|
||||
},
|
||||
[]
|
||||
);
|
||||
return entries
|
||||
.reduce((prev: Array<[number, ILedgerEntry]>, current: ILedgerEntry) => {
|
||||
const prevEntry = last(prev);
|
||||
const prevRunningBalance = head(prevEntry) as number;
|
||||
const amount = this.getEntryRunningBalance(
|
||||
current,
|
||||
openingBalance,
|
||||
prevRunningBalance
|
||||
);
|
||||
return [...prev, [amount, current]];
|
||||
}, [])
|
||||
.map((entryPair: [number, ILedgerEntry]) => {
|
||||
const [runningBalance, entry] = entryPair;
|
||||
|
||||
return this.transactionMapper(entry, runningBalance);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account opening balance.
|
||||
* Retrieves the given 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
|
||||
* @return {IGeneralLedgerSheetAccountBalance}
|
||||
*/
|
||||
private accountOpeningBalance(
|
||||
account: IAccount
|
||||
private accountOpeningBalanceTotal(
|
||||
accountId: number
|
||||
): IGeneralLedgerSheetAccountBalance {
|
||||
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
||||
const amount = this.accountOpeningBalance(accountId);
|
||||
const formattedAmount = this.formatTotalNumber(amount);
|
||||
const currencyCode = this.baseCurrency;
|
||||
const date = this.query.fromDate;
|
||||
@@ -160,15 +177,31 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account closing balance.
|
||||
* Retrieves the given 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
|
||||
* @return {IGeneralLedgerSheetAccountBalance}
|
||||
*/
|
||||
private accountClosingBalance(
|
||||
openingBalance: number,
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||
private accountClosingBalanceTotal(
|
||||
accountId: number
|
||||
): IGeneralLedgerSheetAccountBalance {
|
||||
const amount = this.calcClosingBalance(openingBalance, transactions);
|
||||
const amount = this.accountClosingBalance(accountId);
|
||||
const formattedAmount = this.formatTotalNumber(amount);
|
||||
const currencyCode = this.baseCurrency;
|
||||
const date = this.query.toDate;
|
||||
@@ -176,31 +209,78 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
return { amount, formattedAmount, currencyCode, date };
|
||||
}
|
||||
|
||||
private calcClosingBalance(
|
||||
openingBalance: number,
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||
) {
|
||||
return openingBalance + sumBy(transactions, (trans) => trans.amount);
|
||||
}
|
||||
/**
|
||||
* Retrieves the given account closing balance with subaccounts.
|
||||
* @param {number} accountId
|
||||
* @returns {number}
|
||||
*/
|
||||
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.
|
||||
* @param {IAccount} account
|
||||
* @return {IGeneralLedgerSheetAccount}
|
||||
*/
|
||||
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
|
||||
const openingBalance = this.accountOpeningBalance(account);
|
||||
|
||||
private accountMapper = (account: IAccount): IGeneralLedgerSheetAccount => {
|
||||
const openingBalance = this.accountOpeningBalanceTotal(account.id);
|
||||
const transactions = this.accountTransactionsMapper(
|
||||
account,
|
||||
openingBalance.amount
|
||||
);
|
||||
const closingBalance = this.accountClosingBalance(
|
||||
openingBalance.amount,
|
||||
transactions
|
||||
);
|
||||
const closingBalance = this.accountClosingBalanceTotal(account.id);
|
||||
const closingBalanceSubaccounts =
|
||||
this.accountClosingBalanceWithSubaccountsTotal(account.id);
|
||||
|
||||
return {
|
||||
const initialNode = {
|
||||
id: account.id,
|
||||
name: account.name,
|
||||
code: account.code,
|
||||
@@ -210,34 +290,90 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
transactions,
|
||||
closingBalance,
|
||||
};
|
||||
}
|
||||
|
||||
return R.compose(
|
||||
R.when(
|
||||
() => this.isAccountNodeIncludesClosingSubaccounts(account.id),
|
||||
R.assoc('closingBalanceSubaccounts', closingBalanceSubaccounts)
|
||||
)
|
||||
)(initialNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
|
||||
* Maps over deep nodes to retrieve the G/L account node.
|
||||
* @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 -
|
||||
* @return {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
||||
return (
|
||||
accounts
|
||||
.map((account: IAccount) => this.accountMapper(account))
|
||||
// Filter general ledger accounts that have no transactions
|
||||
// when`noneTransactions` is on.
|
||||
.filter(
|
||||
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
||||
!(
|
||||
generalLedgerAccount.transactions.length === 0 &&
|
||||
this.query.noneTransactions
|
||||
)
|
||||
)
|
||||
);
|
||||
return R.compose(
|
||||
R.defaultTo([]),
|
||||
this.filterAccountNodesByTransactionsFilter,
|
||||
this.accountNodesDeepMap,
|
||||
R.defaultTo([]),
|
||||
this.filterAccountNodesByAccountsFilter,
|
||||
this.nestedAccountsNode
|
||||
)(accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve general ledger report data.
|
||||
* Retrieves general ledger report data.
|
||||
* @return {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
public reportData(): IGeneralLedgerSheetAccount[] {
|
||||
return this.accountsWalker(this.accounts);
|
||||
return this.accountsWalker(this.repository.accounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
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,18 +1,10 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { difference } from 'lodash';
|
||||
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Journal from '@/services/Accounting/JournalPoster';
|
||||
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||
import { transformToMap } from 'utils';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
||||
|
||||
const ERRORS = {
|
||||
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
||||
};
|
||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||
|
||||
@Service()
|
||||
export class GeneralLedgerService {
|
||||
@@ -40,29 +32,13 @@ 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.
|
||||
* @param {number} tenantId
|
||||
* @param {IGeneralLedgerSheetQuery} query
|
||||
* @return {IGeneralLedgerStatement}
|
||||
* @return {Promise<IGeneralLedgerStatement>}
|
||||
*/
|
||||
async generalLedger(
|
||||
public async generalLedger(
|
||||
tenantId: number,
|
||||
query: IGeneralLedgerSheetQuery
|
||||
): Promise<{
|
||||
@@ -70,60 +46,24 @@ export class GeneralLedgerService {
|
||||
query: IGeneralLedgerSheetQuery;
|
||||
meta: IGeneralLedgerMeta;
|
||||
}> {
|
||||
const { accountRepository, transactionsRepository, contactRepository } =
|
||||
this.tenancy.repositories(tenantId);
|
||||
|
||||
const repositories = this.tenancy.repositories(tenantId);
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
// Retrieve all accounts with associated type from the storage.
|
||||
const accounts = await accountRepository.all();
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// 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
|
||||
const genealLedgerRepository = new GeneralLedgerRepository(
|
||||
repositories,
|
||||
query,
|
||||
tenantId
|
||||
);
|
||||
await genealLedgerRepository.asyncInitialize();
|
||||
|
||||
// General ledger report instance.
|
||||
const generalLedgerInstance = new GeneralLedgerSheet(
|
||||
tenantId,
|
||||
filter,
|
||||
accounts,
|
||||
contactsByIdMap,
|
||||
transactionsJournal,
|
||||
openingTransJournal,
|
||||
tenant.metadata.baseCurrency,
|
||||
genealLedgerRepository,
|
||||
i18n
|
||||
);
|
||||
// Retrieve general ledger report data.
|
||||
|
||||
@@ -83,8 +83,8 @@ export class GeneralLedgerTable extends R.compose(
|
||||
*/
|
||||
private openingBalanceColumnsAccessors(): IColumnMapperMeta[] {
|
||||
return [
|
||||
{ key: 'date', value: this.meta.fromDate },
|
||||
{ key: 'account_name', value: 'Opening Balance' },
|
||||
{ key: 'date', value: 'Opening Balance' },
|
||||
{ key: 'account_name', value: '' },
|
||||
{ key: 'reference_type', accessor: '_empty_' },
|
||||
{ key: 'reference_number', accessor: '_empty_' },
|
||||
{ key: 'description', accessor: 'description' },
|
||||
@@ -97,12 +97,15 @@ export class GeneralLedgerTable extends R.compose(
|
||||
|
||||
/**
|
||||
* Closing balance row column accessors.
|
||||
* @param {IGeneralLedgerSheetAccount} account -
|
||||
* @returns {ITableColumnAccessor[]}
|
||||
*/
|
||||
private closingBalanceColumnAccessors(): IColumnMapperMeta[] {
|
||||
private closingBalanceColumnAccessors(
|
||||
account: IGeneralLedgerSheetAccount
|
||||
): IColumnMapperMeta[] {
|
||||
return [
|
||||
{ key: 'date', value: this.meta.toDate },
|
||||
{ key: 'account_name', value: 'Closing Balance' },
|
||||
{ key: 'date', value: `Closing balance for ${account.name}` },
|
||||
{ key: 'account_name', value: `` },
|
||||
{ key: 'reference_type', accessor: '_empty_' },
|
||||
{ key: 'reference_number', accessor: '_empty_' },
|
||||
{ key: 'description', accessor: '_empty_' },
|
||||
@@ -113,6 +116,36 @@ 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.
|
||||
* @returns {ITableColumn[]}
|
||||
@@ -184,7 +217,22 @@ export class GeneralLedgerTable extends R.compose(
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => {
|
||||
const columns = this.closingBalanceColumnAccessors();
|
||||
const columns = this.closingBalanceColumnAccessors(account);
|
||||
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 = {
|
||||
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||
};
|
||||
@@ -221,8 +269,27 @@ export class GeneralLedgerTable extends R.compose(
|
||||
rowTypes: [ROW_TYPE.ACCOUNT],
|
||||
};
|
||||
const row = tableRowMapper(account, columns, meta);
|
||||
const closingBalanceWithSubaccounts =
|
||||
this.closingBalanceWithSubaccountsMapper(account);
|
||||
|
||||
return R.assoc('children', transactions)(row);
|
||||
// Appends the closing balance with sub-accounts row if the account
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -233,7 +300,7 @@ export class GeneralLedgerTable extends R.compose(
|
||||
private accountsMapper = (
|
||||
accounts: IGeneralLedgerSheetAccount[]
|
||||
): ITableRow[] => {
|
||||
return this.mapNodesDeep(accounts, this.accountMapper);
|
||||
return this.mapNodesDeepReverse(accounts, this.accountMapper);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -250,7 +317,6 @@ export class GeneralLedgerTable extends R.compose(
|
||||
*/
|
||||
public tableColumns(): ITableColumn[] {
|
||||
const columns = this.commonColumns();
|
||||
|
||||
return R.compose(this.tableColumnsCellIndexing)(columns);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -50,8 +50,8 @@ export class JournalSheetTable extends R.compose(
|
||||
{ key: 'description', accessor: 'entry.note' },
|
||||
{ key: 'account_code', accessor: 'entry.accountCode' },
|
||||
{ key: 'account_name', accessor: 'entry.accountName' },
|
||||
{ key: 'credit', accessor: 'entry.formattedCredit' },
|
||||
{ key: 'debit', accessor: 'entry.formattedDebit' },
|
||||
{ key: 'credit', accessor: 'entry.formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
@@ -67,8 +67,8 @@ export class JournalSheetTable extends R.compose(
|
||||
{ key: 'description', accessor: 'note' },
|
||||
{ key: 'account_code', accessor: 'accountCode' },
|
||||
{ key: 'account_name', accessor: 'accountName' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
{ key: 'debit', accessor: 'formattedDebit' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
@@ -84,8 +84,8 @@ export class JournalSheetTable extends R.compose(
|
||||
{ key: 'description', accessor: '_empty_' },
|
||||
{ key: 'account_code', accessor: '_empty_' },
|
||||
{ key: 'account_name', accessor: '_empty_' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
{ key: 'debit', accessor: 'formattedDebit' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
@@ -101,8 +101,8 @@ export class JournalSheetTable extends R.compose(
|
||||
{ key: 'description', value: '' },
|
||||
{ key: 'account_code', value: '' },
|
||||
{ key: 'account_name', value: '' },
|
||||
{ key: 'credit', value: '' },
|
||||
{ key: 'debit', value: '' },
|
||||
{ key: 'credit', value: '' },
|
||||
];
|
||||
};
|
||||
|
||||
@@ -118,8 +118,8 @@ export class JournalSheetTable extends R.compose(
|
||||
{ key: 'description', label: 'Description' },
|
||||
{ key: 'account_code', label: 'Acc. Code' },
|
||||
{ key: 'account_name', label: 'Account' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ProfitLossSheetPreviousYear } from './ProfitLossSheetPreviousYear';
|
||||
import { ProfitLossSheetPreviousPeriod } from './ProfitLossSheetPreviousPeriod';
|
||||
import { FinancialDateRanges } from '../FinancialDateRanges';
|
||||
import { ProfitLossSheetFilter } from './ProfitLossSheetFilter';
|
||||
import { flatToNestedArray } from '@/utils';
|
||||
|
||||
export default class ProfitLossSheet extends R.compose(
|
||||
ProfitLossSheetPreviousYear,
|
||||
@@ -82,14 +83,22 @@ export default class ProfitLossSheet extends R.compose(
|
||||
|
||||
/**
|
||||
* Retrieve the sheet account node from the given account.
|
||||
* @param {IAccount} account
|
||||
* @param {IAccount} account
|
||||
* @returns {IProfitLossSheetAccountNode}
|
||||
*/
|
||||
private accountNodeMapper = (
|
||||
account: IAccount
|
||||
): 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
|
||||
.whereAccountId(account.id)
|
||||
.whereAccountsIds(accountIds)
|
||||
.getClosingBalance();
|
||||
|
||||
return {
|
||||
@@ -126,18 +135,19 @@ export default class ProfitLossSheet extends R.compose(
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve report accounts nodes by the given accounts types.
|
||||
* @param {string[]} types
|
||||
* Retrieves report accounts nodes by the given accounts types.
|
||||
* @param {string[]} types
|
||||
* @returns {IBalanceSheetAccountNode}
|
||||
*/
|
||||
private getAccountsNodesByTypes = (
|
||||
types: string[]
|
||||
): IProfitLossSheetAccountNode[] => {
|
||||
return R.compose(
|
||||
R.map(this.accountNodeCompose),
|
||||
R.flatten,
|
||||
R.map(this.repository.getAccountsByType)
|
||||
)(types);
|
||||
const accounts = this.repository.getAccountsByType(types);
|
||||
const accountsTree = flatToNestedArray(accounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
return this.mapNodesDeep(accountsTree, this.accountNodeCompose);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defaultTo } from 'lodash';
|
||||
import { castArray, defaultTo } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import { isEmpty } from 'lodash';
|
||||
@@ -31,6 +31,11 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||
*/
|
||||
public accounts: IAccount[];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public accountsGraph: any;
|
||||
|
||||
/**
|
||||
* Transactions group type.
|
||||
* @param {IAccountTransactionsGroupBy}
|
||||
@@ -135,6 +140,8 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||
*/
|
||||
public asyncInitialize = async () => {
|
||||
await this.initAccounts();
|
||||
await this.initAccountsGraph();
|
||||
|
||||
await this.initAccountsTotalLedger();
|
||||
|
||||
// Date Periods.
|
||||
@@ -177,6 +184,15 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||
this.accountsByType = transformToMapBy(accounts, 'accountType');
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize accounts graph.
|
||||
*/
|
||||
private initAccountsGraph = async () => {
|
||||
const { Account } = this.models;
|
||||
|
||||
this.accountsGraph = Account.toDependencyGraph(this.accounts);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Closing Total.
|
||||
// ----------------------------
|
||||
@@ -337,7 +353,18 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||
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,6 +46,9 @@ export default class SyncSystemSendInvite {
|
||||
email: user.email,
|
||||
active: user.active,
|
||||
tenantId,
|
||||
|
||||
// Email should be verified since the user got the invite token through email.
|
||||
verified: true,
|
||||
});
|
||||
// Creates a invite user token.
|
||||
const invite = await Invite.query().insert({
|
||||
|
||||
@@ -13,6 +13,7 @@ export class ManualJournalTransfromer extends Transformer {
|
||||
'formattedAmount',
|
||||
'formattedDate',
|
||||
'formattedPublishedAt',
|
||||
'formattedCreatedAt',
|
||||
'attachments',
|
||||
];
|
||||
};
|
||||
@@ -37,6 +38,15 @@ export class ManualJournalTransfromer extends Transformer {
|
||||
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.
|
||||
* @param {IManualJournal} manualJournal
|
||||
|
||||
@@ -207,7 +207,7 @@ export default class OrganizationService {
|
||||
): IOrganizationBuildDTO {
|
||||
return {
|
||||
...buildDTO,
|
||||
dateFormat: defaultTo(buildDTO.dateFormat, 'DD/MM/yyyy'),
|
||||
dateFormat: defaultTo(buildDTO.dateFormat, 'DD MMM yyyy'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from '@/utils';
|
||||
import { PurchaseInvoiceTransformer } from '../Bills/PurchaseInvoiceTransformer';
|
||||
|
||||
export class BillPaymentEntryTransformer extends Transformer {
|
||||
/**
|
||||
@@ -7,7 +8,14 @@ export class BillPaymentEntryTransformer extends Transformer {
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['paymentAmountFormatted'];
|
||||
return ['paymentAmountFormatted', 'bill'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the
|
||||
*/
|
||||
protected bill = (entry) => {
|
||||
return this.item(entry.bill, new PurchaseInvoiceTransformer());
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ export class BillPaymentTransformer extends Transformer {
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedPaymentDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedAmount',
|
||||
'entries',
|
||||
'attachments',
|
||||
@@ -27,6 +28,15 @@ export class BillPaymentTransformer extends Transformer {
|
||||
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.
|
||||
* @param {IBill} invoice
|
||||
|
||||
@@ -14,6 +14,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
return [
|
||||
'formattedBillDate',
|
||||
'formattedDueDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedAmount',
|
||||
'formattedPaymentAmount',
|
||||
'formattedBalance',
|
||||
@@ -57,6 +58,15 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
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.
|
||||
* @param {IBill} bill
|
||||
|
||||
@@ -14,6 +14,7 @@ export class VendorCreditTransformer extends Transformer {
|
||||
'formattedAmount',
|
||||
'formattedSubtotal',
|
||||
'formattedVendorCreditDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedCreditsRemaining',
|
||||
'formattedInvoicedAmount',
|
||||
'entries',
|
||||
@@ -30,6 +31,15 @@ export class VendorCreditTransformer extends Transformer {
|
||||
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.
|
||||
* @param {IVendorCredit} credit
|
||||
|
||||
@@ -108,17 +108,28 @@ export default class ResourceService {
|
||||
const $hasFields = (field) =>
|
||||
'undefined' !== typeof field.fields ? field : undefined;
|
||||
|
||||
const $hasColumns = (column) =>
|
||||
const $ColumnHasColumns = (column) =>
|
||||
'undefined' !== typeof column.columns ? column : undefined;
|
||||
|
||||
const $hasColumns = (columns) =>
|
||||
'undefined' !== typeof columns ? columns : undefined;
|
||||
|
||||
const naviagations = [
|
||||
['fields', qim.$each, 'name'],
|
||||
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||
['fields2', qim.$each, 'name'],
|
||||
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||
['columns', qim.$each, 'name'],
|
||||
['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'],
|
||||
['columns', $hasColumns, qim.$each, 'name'],
|
||||
[
|
||||
'columns',
|
||||
$hasColumns,
|
||||
qim.$each,
|
||||
$ColumnHasColumns,
|
||||
'columns',
|
||||
qim.$each,
|
||||
'name',
|
||||
],
|
||||
];
|
||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class SaleEstimateTransfromer extends Transformer {
|
||||
'formattedDeliveredAtDate',
|
||||
'formattedApprovedAtDate',
|
||||
'formattedRejectedAtDate',
|
||||
'formattedCreatedAt',
|
||||
'entries',
|
||||
'attachments',
|
||||
];
|
||||
@@ -41,6 +42,15 @@ export class SaleEstimateTransfromer extends Transformer {
|
||||
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.
|
||||
* @param {ISaleEstimate} invoice
|
||||
|
||||
@@ -13,6 +13,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
||||
return [
|
||||
'invoiceDateFormatted',
|
||||
'dueDateFormatted',
|
||||
'createdAtFormatted',
|
||||
'dueAmountFormatted',
|
||||
'paymentAmountFormatted',
|
||||
'balanceAmountFormatted',
|
||||
@@ -48,6 +49,15 @@ export class SaleInvoiceTransformer extends Transformer {
|
||||
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.
|
||||
* @param {ISaleInvoice} invoice
|
||||
|
||||
@@ -146,6 +146,7 @@ export class EditPaymentReceive {
|
||||
paymentReceiveId,
|
||||
paymentReceive,
|
||||
oldPaymentReceive,
|
||||
paymentReceiveDTO,
|
||||
authorizedUser,
|
||||
trx,
|
||||
} as IPaymentReceiveEditedPayload);
|
||||
|
||||
@@ -12,6 +12,7 @@ export class PaymentReceiveTransfromer extends Transformer {
|
||||
return [
|
||||
'subtotalFormatted',
|
||||
'formattedPaymentDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedAmount',
|
||||
'formattedExchangeRate',
|
||||
'entries',
|
||||
@@ -27,9 +28,18 @@ export class PaymentReceiveTransfromer extends Transformer {
|
||||
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.
|
||||
* @param {IPaymentReceive} payment
|
||||
* @param {IPaymentReceive} payment
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalFormatted = (payment: IPaymentReceive): string => {
|
||||
|
||||
@@ -17,6 +17,7 @@ export class SaleReceiptTransformer extends Transformer {
|
||||
'formattedAmount',
|
||||
'formattedReceiptDate',
|
||||
'formattedClosedAtDate',
|
||||
'formattedCreatedAt',
|
||||
'entries',
|
||||
'attachments',
|
||||
];
|
||||
@@ -40,6 +41,15 @@ export class SaleReceiptTransformer extends Transformer {
|
||||
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.
|
||||
* @param {ISaleReceipt} receipt
|
||||
|
||||
@@ -90,6 +90,20 @@ 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.
|
||||
* @param {String} password - The given password.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// @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 WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
||||
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
||||
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
|
||||
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
|
||||
|
||||
/**
|
||||
* Drawers container of the dashboard.
|
||||
|
||||
@@ -16,8 +16,7 @@ export const useManualJournalsColumns = () => {
|
||||
{
|
||||
id: 'date',
|
||||
Header: intl.get('date'),
|
||||
accessor: 'date',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'formatted_date',
|
||||
width: 115,
|
||||
className: 'date',
|
||||
clickable: true,
|
||||
@@ -66,8 +65,7 @@ export const useManualJournalsColumns = () => {
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: intl.get('created_at'),
|
||||
accessor: 'created_at',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'formatted_created_at',
|
||||
width: 125,
|
||||
clickable: true,
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useFormikContext } from 'formik';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, If, FormattedMessage as T } from '@/components';
|
||||
import { Group, Icon, If, FormattedMessage as T } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
|
||||
@@ -76,7 +76,10 @@ export default function MakeJournalFloatingAction() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Publish ----------- */}
|
||||
<If condition={!manualJournal || !manualJournal?.is_published}>
|
||||
<ButtonGroup>
|
||||
@@ -188,6 +191,6 @@ export default function MakeJournalFloatingAction() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,16 +111,16 @@ export const useJournalTableEntriesColumns = () => {
|
||||
fieldProps: { allowCreate: true },
|
||||
},
|
||||
{
|
||||
Header: CreditHeaderCell,
|
||||
accessor: 'credit',
|
||||
Header: DebitHeaderCell,
|
||||
accessor: 'debit',
|
||||
Cell: MoneyFieldCell,
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: DebitHeaderCell,
|
||||
accessor: 'debit',
|
||||
Header: CreditHeaderCell,
|
||||
accessor: 'credit',
|
||||
Cell: MoneyFieldCell,
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { first } from 'lodash';
|
||||
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import {
|
||||
@@ -34,6 +35,12 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
||||
isLoading: isUncategorizedTransactionLoading,
|
||||
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
||||
|
||||
// Retrieves the primary branch.
|
||||
const primaryBranch = useMemo(
|
||||
() => branches?.find((b) => b.primary) || first(branches),
|
||||
[branches],
|
||||
);
|
||||
|
||||
const provider = {
|
||||
uncategorizedTransactionId,
|
||||
uncategorizedTransaction,
|
||||
@@ -42,6 +49,7 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
||||
accounts,
|
||||
isBranchesLoading,
|
||||
isAccountsLoading,
|
||||
primaryBranch,
|
||||
};
|
||||
const isLoading =
|
||||
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// @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,8 +24,11 @@ function CategorizeTransactionFormRoot({
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { uncategorizedTransactionId, uncategorizedTransaction } =
|
||||
useCategorizeTransactionBoot();
|
||||
const {
|
||||
uncategorizedTransactionId,
|
||||
uncategorizedTransaction,
|
||||
primaryBranch,
|
||||
} = useCategorizeTransactionBoot();
|
||||
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
||||
|
||||
// Callbacks handles form submit.
|
||||
@@ -37,18 +40,28 @@ function CategorizeTransactionFormRoot({
|
||||
.then(() => {
|
||||
setSubmitting(false);
|
||||
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
|
||||
|
||||
|
||||
AppToaster.show({
|
||||
message: 'The uncategorized transaction has been categorized.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
setSubmitting(false);
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong!',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
if (
|
||||
err.response.data?.errors?.some(
|
||||
(e) => e.type === 'BRANCH_ID_REQUIRED',
|
||||
)
|
||||
) {
|
||||
setErrors({
|
||||
branchId: 'The branch is required.',
|
||||
});
|
||||
} else {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong!',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// Form initial values in create and edit mode.
|
||||
@@ -60,6 +73,9 @@ function CategorizeTransactionFormRoot({
|
||||
* as well.
|
||||
*/
|
||||
...transformToCategorizeForm(uncategorizedTransaction),
|
||||
|
||||
/** Assign the primary branch id as default value. */
|
||||
branchId: primaryBranch?.id || null,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionOtherIncome() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherIncome() {
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionOwnerContribution() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -63,6 +64,8 @@ export default function CategorizeTransactionOwnerContribution() {
|
||||
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
||||
<FTextArea name={'description'} growVertically large fill />
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionTransferFrom() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -47,7 +48,7 @@ export default function CategorizeTransactionTransferFrom() {
|
||||
inline
|
||||
>
|
||||
<AccountsSelect
|
||||
name={'to_account_id'}
|
||||
name={'creditAccountId'}
|
||||
items={accounts}
|
||||
filterByRootTypes={['asset']}
|
||||
fastField
|
||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionTransferFrom() {
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionOtherExpense() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherExpense() {
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionOwnerDrawings() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOwnerDrawings() {
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FTextArea,
|
||||
} from '@/components';
|
||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||
|
||||
export default function CategorizeTransactionToAccount() {
|
||||
const { accounts } = useCategorizeTransactionBoot();
|
||||
@@ -49,7 +50,7 @@ export default function CategorizeTransactionToAccount() {
|
||||
<AccountsSelect
|
||||
name={'creditAccountId'}
|
||||
items={accounts}
|
||||
filterByRootTypes={['assset']}
|
||||
filterByRootTypes={['asset']}
|
||||
fastField
|
||||
fill
|
||||
allowCreate
|
||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionToAccount() {
|
||||
fill={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<CategorizeTransactionBranchField />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export const defaultInitialValues = {
|
||||
transactionType: '',
|
||||
referenceNo: '',
|
||||
description: '',
|
||||
branchId: '',
|
||||
};
|
||||
|
||||
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
||||
|
||||
@@ -13,12 +13,11 @@ import {
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
import { safeInvoke } from '@/utils';
|
||||
|
||||
|
||||
/**
|
||||
* Customer floating actions bar.
|
||||
*/
|
||||
@@ -51,7 +50,10 @@ export default function CustomerFloatingActions({ onCancel }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
<ButtonGroup>
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<SaveButton
|
||||
@@ -96,7 +98,7 @@ export default function CustomerFloatingActions({ onCancel }) {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,14 @@ export const useAccountReadEntriesColumns = () => {
|
||||
width: 100,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: isFCYCurrencyType ? 'formatted_fc_debit' : 'formatted_debit',
|
||||
width: 80,
|
||||
className: 'debit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: isFCYCurrencyType
|
||||
@@ -36,14 +44,6 @@ export const useAccountReadEntriesColumns = () => {
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: isFCYCurrencyType ? 'formatted_fc_debit' : 'formatted_debit',
|
||||
width: 80,
|
||||
className: 'debit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[isFCYCurrencyType],
|
||||
);
|
||||
|
||||
@@ -41,19 +41,23 @@ export default function BillDetailHeader() {
|
||||
<Col xs={6}>
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||
<DetailItem label={intl.get('bill_date')}>
|
||||
<FormatDate value={bill.bill_date} />
|
||||
{bill.formatted_bill_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('due_date')}>
|
||||
<FormatDate value={bill.due_date} />
|
||||
{bill.formatted_due_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('vendor_name')}>
|
||||
<VendorDrawerLink vendorId={bill.vendor_id}>
|
||||
{bill.vendor?.display_name}
|
||||
</VendorDrawerLink>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('bill.details.bill_number')}>
|
||||
{defaultTo(bill.bill_number, '-')}
|
||||
</DetailItem>
|
||||
|
||||
<ExchangeRateDetailItem
|
||||
exchangeRate={bill?.exchange_rate}
|
||||
toCurrency={bill?.currency_code}
|
||||
@@ -75,7 +79,7 @@ export default function BillDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('bill.details.created_at')}
|
||||
children={<FormatDate value={bill.created_at} />}
|
||||
children={bill.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function CashflowTransactionDrawerHeader() {
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={<T id={'date'} />}>
|
||||
<FormatDate value={cashflowTransaction.date} />
|
||||
{cashflowTransaction.formatted_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
|
||||
|
||||
@@ -5,13 +5,11 @@ import styled from 'styled-components';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
import {
|
||||
FormatDate,
|
||||
T,
|
||||
Row,
|
||||
Col,
|
||||
DetailsMenu,
|
||||
DetailItem,
|
||||
ButtonLink,
|
||||
CommercialDocHeader,
|
||||
CommercialDocTopHeader,
|
||||
CustomerDrawerLink,
|
||||
@@ -47,7 +45,7 @@ export default function CreditNoteDetailHeader() {
|
||||
<DetailItem
|
||||
label={intl.get('credit_note.drawer.label_credit_note_date')}
|
||||
>
|
||||
<FormatDate value={creditNote.formatted_credit_note_date} />
|
||||
{creditNote.formatted_credit_note_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem
|
||||
@@ -85,7 +83,7 @@ export default function CreditNoteDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'credit_note.drawer.label_created_at'} />}
|
||||
children={<FormatDate value={creditNote.created_at} />}
|
||||
children={creditNote.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function EstimateDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'estimate.details.created_at'} />}
|
||||
children={<FormatDate value={estimate.created_at} />}
|
||||
children={estimate.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
@@ -42,7 +41,7 @@ export default function ExpenseDrawerHeader() {
|
||||
<Col xs={6}>
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||
<DetailItem name={'date'} label={<T id={'date'} />}>
|
||||
{moment(expense.payment_date).format('YYYY MMM DD')}
|
||||
{expense.formatted_payment_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
|
||||
@@ -66,11 +65,11 @@ export default function ExpenseDrawerHeader() {
|
||||
minLabelSize={'180px'}
|
||||
>
|
||||
<DetailItem label={<T id={'published_at'} />}>
|
||||
<FormatDate value={expense.published_at} />
|
||||
{expense.formatted_date}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={<T id={'created_at'} />}>
|
||||
<FormatDate value={expense.created_at} />
|
||||
{expense.formatted_created_at}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -43,11 +43,11 @@ export default function InvoiceDetailHeader() {
|
||||
<Col xs={6}>
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||
<DetailItem label={intl.get('invoice_date')}>
|
||||
<FormatDate value={invoice.invoice_date} />
|
||||
{invoice.invoice_date_formatted}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('due_date')}>
|
||||
<FormatDate value={invoice.due_date} />
|
||||
{invoice.due_date_formatted}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('customer_name')}>
|
||||
@@ -86,7 +86,7 @@ export default function InvoiceDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('invoice.details.created_at')}
|
||||
children={<FormatDate value={invoice.created_at} />}
|
||||
children={invoice.created_at_formatted}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -82,20 +82,6 @@ export const useManualJournalEntriesColumns = () => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'credit',
|
||||
Cell: FormatNumberCell,
|
||||
width: getColumnWidth(entries, 'credit', {
|
||||
minWidth: 60,
|
||||
magicSpacing: 5,
|
||||
}),
|
||||
disableResizable: true,
|
||||
disableSortBy: true,
|
||||
textOverview: true,
|
||||
formatNumber: { noZero: true },
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'debit',
|
||||
@@ -110,6 +96,20 @@ export const useManualJournalEntriesColumns = () => {
|
||||
formatNumber: { noZero: true },
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'credit',
|
||||
Cell: FormatNumberCell,
|
||||
width: getColumnWidth(entries, 'credit', {
|
||||
minWidth: 60,
|
||||
magicSpacing: 5,
|
||||
}),
|
||||
disableResizable: true,
|
||||
disableSortBy: true,
|
||||
textOverview: true,
|
||||
formatNumber: { noZero: true },
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function PaymentMadeDetailHeader() {
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||
<DetailItem
|
||||
label={intl.get('payment_date')}
|
||||
children={<FormatDate value={paymentMade.payment_date} />}
|
||||
children={paymentMade.formatted_payment_date}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('payment_made.details.payment_number')}
|
||||
@@ -58,6 +58,7 @@ export default function PaymentMadeDetailHeader() {
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
<Col xs={6}>
|
||||
<DetailsMenu
|
||||
textAlign={'right'}
|
||||
@@ -70,7 +71,7 @@ export default function PaymentMadeDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('created_at')}
|
||||
children={<FormatDate value={paymentMade.created_at} />}
|
||||
children={paymentMade.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import { FormatNumberCell } from '@/components';
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
@@ -17,7 +15,7 @@ export const usePaymentMadeEntriesColumns = () => {
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: (row) => moment(row.date).format('YYYY MMM DD'),
|
||||
accessor: 'bill.formatted_bill_date',
|
||||
width: 100,
|
||||
disableSortBy: true,
|
||||
className: 'date',
|
||||
|
||||
@@ -5,7 +5,6 @@ import { defaultTo } from 'lodash';
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
FormatDate,
|
||||
DetailsMenu,
|
||||
DetailItem,
|
||||
CommercialDocHeader,
|
||||
@@ -36,7 +35,7 @@ export default function PaymentReceiveDetailHeader() {
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
|
||||
<DetailItem
|
||||
label={intl.get('payment_date')}
|
||||
children={<FormatDate value={paymentReceive.payment_date} />}
|
||||
children={paymentReceive.formatted_payment_date}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('payment_receive.details.payment_number')}
|
||||
@@ -71,7 +70,7 @@ export default function PaymentReceiveDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('created_at')}
|
||||
children={<FormatDate value={paymentReceive.created_at} />}
|
||||
children={paymentReceive.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
@@ -26,7 +25,7 @@ export const usePaymentReceiveEntriesColumns = () => {
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: (row) => moment(row.payment_date).format('YYYY MMM DD'),
|
||||
accessor: 'invoice.invoice_date_formatted',
|
||||
width: 100,
|
||||
className: 'date',
|
||||
disableSortBy: true,
|
||||
|
||||
@@ -54,11 +54,11 @@ export default function ReceiptDetailHeader() {
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label={intl.get('receipt_date')}
|
||||
children={<FormatDate value={receipt.receipt_date} />}
|
||||
children={receipt.formatted_receipt_date}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('closed_date')}
|
||||
children={<FormatDate value={receipt.closed_at_date} />}
|
||||
children={receipt.formatted_closed_at_date}
|
||||
/>
|
||||
<ExchangeRateDetailItem
|
||||
exchangeRate={receipt?.exchange_rate}
|
||||
@@ -82,7 +82,7 @@ export default function ReceiptDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('receipt.details.created_at')}
|
||||
children={<FormatDate value={receipt.created_at} />}
|
||||
children={receipt.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function VendorCreditDetailHeader() {
|
||||
<DetailItem
|
||||
label={intl.get('vendor_credit.drawer.label_vendor_credit_date')}
|
||||
>
|
||||
<FormatDate value={vendorCredit.formatted_vendor_credit_date} />
|
||||
{vendorCredit.formatted_vendor_credit_date}
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label={intl.get('vendor_credit.drawer.label_vendor_credit_no')}
|
||||
@@ -78,7 +78,7 @@ export default function VendorCreditDetailHeader() {
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'vendor_credit.drawer.label_created_at'} />}
|
||||
children={<FormatDate value={vendorCredit.created_at} />}
|
||||
children={vendorCredit.formatted_created_at}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</Col>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import { Group, FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
@@ -78,7 +78,10 @@ export default function ExpenseFloatingFooter() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Publish ----------- */}
|
||||
<If condition={isNewMode}>
|
||||
<ButtonGroup>
|
||||
@@ -190,6 +193,6 @@ export default function ExpenseFloatingFooter() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,13 +17,7 @@ import clsx from 'classnames';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
import {
|
||||
FormatDateCell,
|
||||
FormattedMessage as T,
|
||||
Icon,
|
||||
If,
|
||||
Can,
|
||||
} from '@/components';
|
||||
import { FormattedMessage as T, Icon, If, Can } from '@/components';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
/**
|
||||
@@ -137,8 +131,7 @@ export function useExpensesTableColumns() {
|
||||
{
|
||||
id: 'payment_date',
|
||||
Header: intl.get('payment_date'),
|
||||
accessor: 'payment_date',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'formatted_date',
|
||||
width: 140,
|
||||
className: 'payment_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -96,12 +96,19 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:first-child).is-expanded .td {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
&--OPENING_BALANCE,
|
||||
&--CLOSING_BALANCE {
|
||||
.td {
|
||||
color: #000;
|
||||
}
|
||||
.date {
|
||||
font-weight: 500;
|
||||
|
||||
.cell-inner {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
.amount {
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -110,6 +117,9 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
||||
.name {
|
||||
font-weight: 500;
|
||||
}
|
||||
.td {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ import classNames from 'classnames';
|
||||
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
||||
import { FastField, useFormikContext } from 'formik';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import { Group, FormattedMessage as T } from '@/components';
|
||||
import { saveInvoke } from '@/utils';
|
||||
|
||||
/**
|
||||
@@ -37,7 +36,10 @@ export default function ItemFormFloatingActions({ onCancel }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
<SaveButton
|
||||
intent={Intent.PRIMARY}
|
||||
disabled={isSubmitting}
|
||||
@@ -78,7 +80,7 @@ export default function ItemFormFloatingActions({ onCancel }) {
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ItemForm from './ItemForm';
|
||||
|
||||
|
||||
/**
|
||||
* Item form page.
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
export const useGLEntriesTableColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: ({ formatted_date }) =>
|
||||
moment(formatted_date).format('YYYY MMM DD'),
|
||||
accessor: 'date.formatted_date',
|
||||
width: 140,
|
||||
className: 'date',
|
||||
textOverview: true,
|
||||
@@ -28,14 +25,6 @@ export const useGLEntriesTableColumns = () => {
|
||||
width: 140,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: ({ credit }) => credit.formatted_amount,
|
||||
width: 100,
|
||||
className: 'credit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: ({ debit }) => debit.formatted_amount,
|
||||
@@ -44,6 +33,14 @@ export const useGLEntriesTableColumns = () => {
|
||||
textOverview: true,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: ({ credit }) => credit.formatted_amount,
|
||||
width: 100,
|
||||
className: 'credit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import { Group, FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import classNames from 'classnames';
|
||||
@@ -76,7 +76,10 @@ export default function BillFloatingActions() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Open ----------- */}
|
||||
<If condition={!bill || !bill?.is_open}>
|
||||
<ButtonGroup>
|
||||
@@ -189,6 +192,6 @@ export default function BillFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,8 +161,7 @@ export function useBillsTableColumns() {
|
||||
{
|
||||
id: 'bill_date',
|
||||
Header: intl.get('bill_date'),
|
||||
accessor: 'bill_date',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'formatted_bill_date',
|
||||
width: 110,
|
||||
className: 'bill_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { If, Icon, FormattedMessage as T } from '@/components';
|
||||
import { If, Icon, FormattedMessage as T, Group } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
|
||||
|
||||
@@ -69,11 +69,15 @@ export default function VendorCreditNoteFloatingActions() {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
// Handle the clear button click.
|
||||
const handleClearBtnClick = (event) => {
|
||||
resetForm();
|
||||
};
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Open ----------- */}
|
||||
<If condition={!vendorCredit || !vendorCredit?.is_open}>
|
||||
<ButtonGroup>
|
||||
@@ -185,6 +189,6 @@ export default function VendorCreditNoteFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,7 @@ import clsx from 'classnames';
|
||||
import { Intent, Tag, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import {
|
||||
FormatDateCell,
|
||||
FormattedMessage as T,
|
||||
Choose,
|
||||
If,
|
||||
Icon,
|
||||
Can,
|
||||
} from '@/components';
|
||||
import { FormattedMessage as T, Choose, If, Icon, Can } from '@/components';
|
||||
import { safeCallback } from '@/utils';
|
||||
import { VendorCreditAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
@@ -119,7 +112,6 @@ export function useVendorsCreditNoteTableColumns() {
|
||||
id: 'credit_date',
|
||||
Header: intl.get('date'),
|
||||
accessor: 'formatted_vendor_credit_date',
|
||||
Cell: FormatDateCell,
|
||||
width: 110,
|
||||
className: 'credit_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -11,14 +11,12 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Payment made floating actions bar.
|
||||
*/
|
||||
@@ -56,11 +54,14 @@ export default function PaymentMadeFloatingActions() {
|
||||
// Handle submit & continue editing button click.
|
||||
const handleSubmitContinueEditingBtnClick = (event) => {
|
||||
setSubmitPayload({ redirect: false, publish: true });
|
||||
submitForm()
|
||||
submitForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
@@ -109,6 +110,6 @@ export default function PaymentMadeFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { Icon, Money, FormatDateCell, Can } from '@/components';
|
||||
import { Icon, Money, Can } from '@/components';
|
||||
import { PaymentMadeAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import { safeCallback } from '@/utils';
|
||||
@@ -29,7 +29,7 @@ export function ActionsMenu({
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
@@ -79,8 +79,7 @@ export function usePaymentMadesTableColumns() {
|
||||
{
|
||||
id: 'payment_date',
|
||||
Header: intl.get('payment_date'),
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'payment_date',
|
||||
accessor: 'formatted_payment_date',
|
||||
width: 140,
|
||||
className: 'payment_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { If, Icon, FormattedMessage as T } from '@/components';
|
||||
import { If, Icon, FormattedMessage as T, Group } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import classNames from 'classnames';
|
||||
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
|
||||
@@ -69,12 +69,16 @@ export default function CreditNoteFloatingActions() {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
// Handle clear button click.
|
||||
const handleClearBtnClick = (event) => {
|
||||
resetForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Open ----------- */}
|
||||
<If condition={!creditNote || !creditNote?.is_open}>
|
||||
<ButtonGroup>
|
||||
@@ -186,6 +190,6 @@ export default function CreditNoteFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import clsx from 'classnames';
|
||||
import { Intent, Tag, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import {
|
||||
FormatDateCell,
|
||||
FormattedMessage as T,
|
||||
Choose,
|
||||
If,
|
||||
@@ -112,7 +111,6 @@ export function useCreditNoteTableColumns() {
|
||||
id: 'credit_date',
|
||||
Header: intl.get('credit_note.column.credit_date'),
|
||||
accessor: 'formatted_credit_note_date',
|
||||
Cell: FormatDateCell,
|
||||
width: 110,
|
||||
className: 'credit_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { If, Icon, FormattedMessage as T } from '@/components';
|
||||
import { If, Icon, FormattedMessage as T, Group } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useFormikContext } from 'formik';
|
||||
@@ -63,16 +63,21 @@ export default function EstimateFloatingActions() {
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle the cancel button click.
|
||||
const handleCancelBtnClick = (event) => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
// Handle the clear button click.
|
||||
const handleClearBtnClick = (event) => {
|
||||
resetForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Deliver ----------- */}
|
||||
<If condition={!estimate || !estimate?.is_delivered}>
|
||||
<ButtonGroup>
|
||||
@@ -188,6 +193,6 @@ export default function EstimateFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,8 +164,7 @@ export function useEstiamtesTableColumns() {
|
||||
{
|
||||
id: 'estimate_date',
|
||||
Header: intl.get('estimate_date'),
|
||||
accessor: 'estimate_date',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'formatted_estimate_date',
|
||||
width: 140,
|
||||
className: 'estimate_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { If, Icon, FormattedMessage as T } from '@/components';
|
||||
import { If, Icon, FormattedMessage as T, Group } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||
|
||||
@@ -76,7 +76,10 @@ export default function InvoiceFloatingActions() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save And Deliver ----------- */}
|
||||
<If condition={!invoice || !invoice?.is_delivered}>
|
||||
<ButtonGroup>
|
||||
@@ -189,6 +192,6 @@ export default function InvoiceFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,17 @@ export const defaultInvoice = {
|
||||
attachments: [],
|
||||
};
|
||||
|
||||
// Invoice entry request schema.
|
||||
export const defaultReqInvoiceEntry = {
|
||||
index: 0,
|
||||
item_id: '',
|
||||
rate: '',
|
||||
discount: '',
|
||||
quantity: '',
|
||||
description: '',
|
||||
tax_rate_id: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform invoice to initial values in edit mode.
|
||||
*/
|
||||
@@ -175,13 +186,27 @@ export const ITEMS_FILTER_ROLES_QUERY = JSON.stringify([
|
||||
},
|
||||
]);
|
||||
|
||||
/**
|
||||
* Transformes bill entries to submit request.
|
||||
*/
|
||||
const transformEntriesToRequest = (entries) => {
|
||||
return R.compose(
|
||||
R.map(R.compose(R.curry(transformToForm)(R.__, defaultReqInvoiceEntry))),
|
||||
filterNonZeroEntries,
|
||||
)(entries);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the givne non-zero entries.
|
||||
*/
|
||||
const filterNonZeroEntries = (entries) => {
|
||||
return entries.filter((item) => item.item_id && item.quantity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the form values to request body values.
|
||||
*/
|
||||
export function transformValueToRequest(values) {
|
||||
const entries = values.entries.filter(
|
||||
(item) => item.item_id && item.quantity,
|
||||
);
|
||||
return {
|
||||
...omit(values, [
|
||||
'invoice_no',
|
||||
@@ -194,9 +219,7 @@ export function transformValueToRequest(values) {
|
||||
invoice_no: values.invoice_no,
|
||||
}),
|
||||
is_inclusive_tax: values.inclusive_exclusive_tax === TaxType.Inclusive,
|
||||
entries: entries.map((entry) => ({
|
||||
...omit(entry, ['amount', 'tax_amount', 'tax_rate']),
|
||||
})),
|
||||
entries: transformEntriesToRequest(values.entries),
|
||||
delivered: false,
|
||||
attachments: transformAttachmentsToRequest(values),
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ export function ActionsMenu({
|
||||
onQuick,
|
||||
onViewDetails,
|
||||
onPrint,
|
||||
onSendMail
|
||||
onSendMail,
|
||||
},
|
||||
row: { original },
|
||||
}) {
|
||||
@@ -202,8 +202,7 @@ export function useInvoicesTableColumns() {
|
||||
{
|
||||
id: 'invoice_date',
|
||||
Header: intl.get('invoice_date'),
|
||||
accessor: 'invoice_date',
|
||||
Cell: FormatDateCell,
|
||||
accessor: 'invoice_date_formatted',
|
||||
width: 110,
|
||||
className: 'invoice_date',
|
||||
clickable: true,
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
/**
|
||||
* Payment receive floating actions bar.
|
||||
@@ -55,7 +54,10 @@ export default function PaymentReceiveFormFloatingActions() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group
|
||||
spacing={10}
|
||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
||||
>
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
@@ -107,6 +109,6 @@ export default function PaymentReceiveFormFloatingActions() {
|
||||
onClick={handleCancelBtnClick}
|
||||
text={<T id={'cancel'} />}
|
||||
/>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user