mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
Compare commits
86 Commits
patch-3
...
v0.23.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cf6bf099 | ||
|
|
6da7e8185c | ||
|
|
785c49f2e6 | ||
|
|
d7331554ad | ||
|
|
78b1e9136a | ||
|
|
fea9bb5caa | ||
|
|
db5caa138a | ||
|
|
bf821885c0 | ||
|
|
5ce5d8b899 | ||
|
|
458093fca2 | ||
|
|
97e17848f8 | ||
|
|
3dfe884413 | ||
|
|
f26a59f0fb | ||
|
|
7ee161733f | ||
|
|
4efc0b3eb4 | ||
|
|
532aa07e7f | ||
|
|
abacb543c7 | ||
|
|
769eaebc76 | ||
|
|
e0fb345a48 | ||
|
|
c21301061f | ||
|
|
2bbc154f18 | ||
|
|
3c1273becb | ||
|
|
16f1d57279 | ||
|
|
8726b4b3b0 | ||
|
|
5ace03ea99 | ||
|
|
5b6c473780 | ||
|
|
2186828516 | ||
|
|
3f2ab6e8f0 | ||
|
|
f0fae7d148 | ||
|
|
e063597a80 | ||
|
|
9b3f6b22d1 | ||
|
|
0475ce136a | ||
|
|
987ad992a4 | ||
|
|
ee92c2815b | ||
|
|
5767f1f603 | ||
|
|
885d8014c2 | ||
|
|
3ffab896ed | ||
|
|
92a5086f1f | ||
|
|
1bf9038ddc | ||
|
|
2736b76ced | ||
|
|
9e921b074f | ||
|
|
0f377e19f3 | ||
|
|
5d872798ff | ||
|
|
0ef78a19fe | ||
|
|
70b0a4833c | ||
|
|
ead4fc9b97 | ||
|
|
a91a7c612f | ||
|
|
339289be9f | ||
|
|
350d229e98 | ||
|
|
8152a16fd5 | ||
|
|
00aad6e35c | ||
|
|
30d8fdb4c0 | ||
|
|
872fc661ce | ||
|
|
054cd1fae4 | ||
|
|
7cb169bce9 | ||
|
|
f2663c4af3 | ||
|
|
6fea7779da | ||
|
|
c00af18327 | ||
|
|
37f0f4e227 | ||
|
|
8662c5899e | ||
|
|
a9a7cd8617 | ||
|
|
e50fc3b523 | ||
|
|
b294a72a26 | ||
|
|
62ae49941b | ||
|
|
31f5cbf335 | ||
|
|
b22328cff9 | ||
|
|
58f609353c | ||
|
|
8a2a8eed3b | ||
|
|
636d206b0e | ||
|
|
63922c391a | ||
|
|
6ecfe1ff12 | ||
|
|
17651e0768 | ||
|
|
151b623771 | ||
|
|
2d4459c2f9 | ||
|
|
3cbdc3ec96 | ||
|
|
3cfb5cdde8 | ||
|
|
736f2c4109 | ||
|
|
2e21437056 | ||
|
|
340b78d968 | ||
|
|
d006362be2 | ||
|
|
bc21dcb37e | ||
|
|
578b0deb3e | ||
|
|
c3dc26a1e4 | ||
|
|
32d74b0413 | ||
|
|
71b1206f8a | ||
|
|
cb1bcaae77 |
@@ -168,6 +168,16 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Daniel15",
|
||||||
|
"name": "Daniel Lo Nigro",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/91933?v=4",
|
||||||
|
"profile": "https://d.sb/",
|
||||||
|
"contributions": [
|
||||||
|
"bug",
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
@@ -35,17 +35,10 @@ TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
|||||||
BASE_URL=http://example.com
|
BASE_URL=http://example.com
|
||||||
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
||||||
|
|
||||||
# Jobs MongoDB
|
|
||||||
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
|
||||||
|
|
||||||
# App proxy
|
# App proxy
|
||||||
PUBLIC_PROXY_PORT=80
|
PUBLIC_PROXY_PORT=80
|
||||||
PUBLIC_PROXY_SSL_PORT=443
|
PUBLIC_PROXY_SSL_PORT=443
|
||||||
|
|
||||||
# Agendash
|
|
||||||
AGENDASH_AUTH_USER=agendash
|
|
||||||
AGENDASH_AUTH_PASSWORD=123123
|
|
||||||
|
|
||||||
# Sign-up restrictions
|
# Sign-up restrictions
|
||||||
SIGNUP_DISABLED=false
|
SIGNUP_DISABLED=false
|
||||||
SIGNUP_ALLOWED_DOMAINS=
|
SIGNUP_ALLOWED_DOMAINS=
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://d.sb/"><img src="https://avatars.githubusercontent.com/u/91933?v=4?s=100" width="100px;" alt="Daniel Lo Nigro"/><br /><sub><b>Daniel Lo Nigro</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3ADaniel15" title="Bug reports">🐛</a> <a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Daniel15" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -32,11 +32,9 @@ services:
|
|||||||
- '3000'
|
- '3000'
|
||||||
links:
|
links:
|
||||||
- mysql
|
- mysql
|
||||||
- mongo
|
|
||||||
- redis
|
- redis
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
- mongo
|
|
||||||
- redis
|
- redis
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
networks:
|
networks:
|
||||||
@@ -66,16 +64,9 @@ services:
|
|||||||
# Authentication
|
# Authentication
|
||||||
- JWT_SECRET=${JWT_SECRET}
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
|
|
||||||
# MongoDB
|
|
||||||
- MONGODB_DATABASE_URL=mongodb://mongo/bigcapital
|
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
- BASE_URL=${BASE_URL}
|
- BASE_URL=${BASE_URL}
|
||||||
|
|
||||||
# Agendash
|
|
||||||
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
|
|
||||||
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
|
|
||||||
|
|
||||||
# Sign-up restrictions
|
# Sign-up restrictions
|
||||||
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
|
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
|
||||||
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
||||||
@@ -122,7 +113,7 @@ services:
|
|||||||
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
||||||
- S3_ENDPOINT=${S3_ENDPOINT}
|
- S3_ENDPOINT=${S3_ENDPOINT}
|
||||||
- S3_BUCKET=${S3_BUCKET}
|
- S3_BUCKET=${S3_BUCKET}
|
||||||
|
|
||||||
# Stripe
|
# Stripe
|
||||||
- STRIPE_PAYMENT_SECRET_KEY=${STRIPE_PAYMENT_SECRET_KEY}
|
- STRIPE_PAYMENT_SECRET_KEY=${STRIPE_PAYMENT_SECRET_KEY}
|
||||||
- STRIPE_PAYMENT_PUBLISHABLE_KEY=${STRIPE_PAYMENT_PUBLISHABLE_KEY}
|
- STRIPE_PAYMENT_PUBLISHABLE_KEY=${STRIPE_PAYMENT_PUBLISHABLE_KEY}
|
||||||
@@ -166,17 +157,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- bigcapital_network
|
- bigcapital_network
|
||||||
|
|
||||||
mongo:
|
|
||||||
container_name: bigcapital-mongo
|
|
||||||
restart: on-failure
|
|
||||||
build: ./docker/mongo
|
|
||||||
expose:
|
|
||||||
- '27017'
|
|
||||||
volumes:
|
|
||||||
- mongo:/var/lib/mongodb
|
|
||||||
networks:
|
|
||||||
- bigcapital_network
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: bigcapital-redis
|
container_name: bigcapital-redis
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
@@ -202,10 +182,6 @@ volumes:
|
|||||||
name: bigcapital_prod_mysql
|
name: bigcapital_prod_mysql
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
mongo:
|
|
||||||
name: bigcapital_prod_mongo
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
name: bigcapital_prod_redis
|
name: bigcapital_prod_redis
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
@@ -24,25 +24,13 @@ services:
|
|||||||
restart_policy:
|
restart_policy:
|
||||||
condition: unless-stopped
|
condition: unless-stopped
|
||||||
|
|
||||||
mongo:
|
|
||||||
build: ./docker/mongo
|
|
||||||
expose:
|
|
||||||
- '27017'
|
|
||||||
volumes:
|
|
||||||
- mongo:/var/lib/mongodb
|
|
||||||
ports:
|
|
||||||
- '27017:27017'
|
|
||||||
deploy:
|
|
||||||
restart_policy:
|
|
||||||
condition: unless-stopped
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
build:
|
build:
|
||||||
context: ./docker/redis
|
context: ./docker/redis
|
||||||
expose:
|
expose:
|
||||||
- "6379"
|
- '6379'
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- '6379:6379'
|
||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
deploy:
|
deploy:
|
||||||
@@ -52,7 +40,7 @@ services:
|
|||||||
gotenberg:
|
gotenberg:
|
||||||
image: gotenberg/gotenberg:7
|
image: gotenberg/gotenberg:7
|
||||||
ports:
|
ports:
|
||||||
- "9000:3000"
|
- '9000:3000'
|
||||||
|
|
||||||
# Volumes
|
# Volumes
|
||||||
volumes:
|
volumes:
|
||||||
@@ -60,10 +48,6 @@ volumes:
|
|||||||
name: bigcapital_dev_mysql
|
name: bigcapital_dev_mysql
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
mongo:
|
|
||||||
name: bigcapital_dev_mongo
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
name: bigcapital_dev_redis
|
name: bigcapital_dev_redis
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
FROM mongo:5.0
|
|
||||||
@@ -35,17 +35,10 @@ TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
|||||||
BASE_URL=http://example.com
|
BASE_URL=http://example.com
|
||||||
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
||||||
|
|
||||||
# Jobs MongoDB
|
|
||||||
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
|
||||||
|
|
||||||
# App proxy
|
# App proxy
|
||||||
PUBLIC_PROXY_PORT=80
|
PUBLIC_PROXY_PORT=80
|
||||||
PUBLIC_PROXY_SSL_PORT=443
|
PUBLIC_PROXY_SSL_PORT=443
|
||||||
|
|
||||||
# Agendash
|
|
||||||
AGENDASH_AUTH_USER=agendash
|
|
||||||
AGENDASH_AUTH_PASSWORD=123123
|
|
||||||
|
|
||||||
# Sign-up restrictions
|
# Sign-up restrictions
|
||||||
SIGNUP_DISABLED=false
|
SIGNUP_DISABLED=false
|
||||||
SIGNUP_ALLOWED_DOMAINS=
|
SIGNUP_ALLOWED_DOMAINS=
|
||||||
|
|||||||
100
packages/server/Dockerfile
Normal file
100
packages/server/Dockerfile
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
FROM node:18.16.0-alpine as build
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ARG MAIL_HOST= \
|
||||||
|
MAIL_USERNAME= \
|
||||||
|
MAIL_PASSWORD= \
|
||||||
|
MAIL_PORT= \
|
||||||
|
MAIL_SECURE= \
|
||||||
|
MAIL_FROM_NAME= \
|
||||||
|
MAIL_FROM_ADDRESS= \
|
||||||
|
# Database
|
||||||
|
DB_HOST= \
|
||||||
|
DB_USER= \
|
||||||
|
DB_PASSWORD= \
|
||||||
|
DB_CHARSET= \
|
||||||
|
# System database.
|
||||||
|
SYSTEM_DB_NAME= \
|
||||||
|
SYSTEM_DB_PASSWORD= \
|
||||||
|
SYSTEM_DB_USER= \
|
||||||
|
SYSTEM_DB_HOST= \
|
||||||
|
SYSTEM_DB_CHARSET= \
|
||||||
|
# Tenant databases.
|
||||||
|
TENANT_DB_USER= \
|
||||||
|
TENANT_DB_PASSWORD= \
|
||||||
|
TENANT_DB_HOST= \
|
||||||
|
TENANT_DB_NAME_PERFIX= \
|
||||||
|
TENANT_DB_CHARSET= \
|
||||||
|
# Authentication
|
||||||
|
JWT_SECRET= \
|
||||||
|
# Application
|
||||||
|
BASE_URL= \
|
||||||
|
# Sign-up restriction
|
||||||
|
SIGNUP_DISABLED= \
|
||||||
|
SIGNUP_ALLOWED_DOMAINS= \
|
||||||
|
SIGNUP_ALLOWED_EMAILS=
|
||||||
|
|
||||||
|
ENV MAIL_HOST=$MAIL_HOST \
|
||||||
|
MAIL_USERNAME=$MAIL_USERNAME \
|
||||||
|
MAIL_PASSWORD=$MAIL_PASSWORD \
|
||||||
|
MAIL_PORT=$MAIL_PORT \
|
||||||
|
MAIL_SECURE=$MAIL_SECURE \
|
||||||
|
MAIL_FROM_NAME=$MAIL_FROM_NAME \
|
||||||
|
MAIL_FROM_ADDRESS=$MAIL_FROM_ADDRESS \
|
||||||
|
# Database
|
||||||
|
DB_HOST=$DB_HOST \
|
||||||
|
DB_USER=$DB_USER \
|
||||||
|
DB_PASSWORD=$DB_PASSWORD \
|
||||||
|
DB_CHARSET=$DB_CHARSET \
|
||||||
|
# System database.
|
||||||
|
SYSTEM_DB_HOST=$SYSTEM_DB_HOST \
|
||||||
|
SYSTEM_DB_USER=$SYSTEM_DB_USER \
|
||||||
|
SYSTEM_DB_PASSWORD=$SYSTEM_DB_PASSWORD \
|
||||||
|
SYSTEM_DB_NAME=$SYSTEM_DB_NAME \
|
||||||
|
SYSTEM_DB_CHARSET=$SYSTEM_DB_CHARSET \
|
||||||
|
# Tenant databases.
|
||||||
|
TENANT_DB_NAME_PERFIX=$TENANT_DB_NAME_PERFIX \
|
||||||
|
TENANT_DB_HOST=$TENANT_DB_HOST \
|
||||||
|
TENANT_DB_PASSWORD=$TENANT_DB_PASSWORD \
|
||||||
|
TENANT_DB_USER=$TENANT_DB_USER \
|
||||||
|
TENANT_DB_CHARSET=$TENANT_DB_CHARSET \
|
||||||
|
# Authentication
|
||||||
|
JWT_SECRET=$JWT_SECRET \
|
||||||
|
# Application
|
||||||
|
BASE_URL=$BASE_URL \
|
||||||
|
# Sign-up restriction
|
||||||
|
SIGNUP_DISABLED=$SIGNUP_DISABLED \
|
||||||
|
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||||
|
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||||
|
|
||||||
|
# New Relic config file.
|
||||||
|
ENV NEW_RELIC_NO_CONFIG_FILE=true
|
||||||
|
|
||||||
|
# Create app directory.
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN chown node:node /
|
||||||
|
|
||||||
|
# Install pnpm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# Copy application dependency manifests to the container image.
|
||||||
|
COPY --chown=node:node ./ ./
|
||||||
|
|
||||||
|
# Install application dependencies
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add python3 build-base chromium
|
||||||
|
|
||||||
|
# Set PYHTON env
|
||||||
|
ENV PYTHON=/usr/bin/python3
|
||||||
|
|
||||||
|
# Install packages dependencies for production.
|
||||||
|
RUN pnpm install
|
||||||
|
|
||||||
|
COPY --chown=node:node ./packages/server ./packages/server
|
||||||
|
|
||||||
|
# # Creates a "dist" folder with the production build
|
||||||
|
RUN pnpm run build:server --skip-nx-cache
|
||||||
|
|
||||||
|
CMD [ "node", "./packages/server/build/index.js" ]
|
||||||
@@ -167,6 +167,9 @@
|
|||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@/(.*)$": "<rootDir>/$1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/server/src/common/config/app.ts
Normal file
5
packages/server/src/common/config/app.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('app', () => ({
|
||||||
|
baseUrl: process.env.BASE_URL,
|
||||||
|
}));
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import app from './app';
|
||||||
import systemDatabase from './system-database';
|
import systemDatabase from './system-database';
|
||||||
import tenantDatabase from './tenant-database';
|
import tenantDatabase from './tenant-database';
|
||||||
import signup from './signup';
|
import signup from './signup';
|
||||||
@@ -18,6 +19,7 @@ import throttle from './throttle';
|
|||||||
import cloud from './cloud';
|
import cloud from './cloud';
|
||||||
|
|
||||||
export const config = [
|
export const config = [
|
||||||
|
app,
|
||||||
systemDatabase,
|
systemDatabase,
|
||||||
cloud,
|
cloud,
|
||||||
tenantDatabase,
|
tenantDatabase,
|
||||||
|
|||||||
@@ -7,53 +7,46 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { type Observable } from 'rxjs';
|
import { type Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { mapKeysDeep } from '@/utils/deepdash';
|
||||||
|
|
||||||
export function camelToSnake<T = any>(value: T) {
|
export function camelToSnake<T = any>(value: T): T {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
return mapKeysDeep(
|
||||||
return value.map(camelToSnake);
|
value,
|
||||||
}
|
(_value: string, key: any, parent: any, context: any) => {
|
||||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
if (Array.isArray(parent)) {
|
||||||
return Object.fromEntries(
|
// tell mapKeysDeep to skip mapping inside this branch
|
||||||
Object.entries(value).map(([key, value]) => [
|
context.skipChildren = true;
|
||||||
key
|
return key;
|
||||||
.split(/(?=[A-Z])/)
|
}
|
||||||
.join('_')
|
return key
|
||||||
.toLowerCase(),
|
.split(/(?=[A-Z])/)
|
||||||
camelToSnake(value),
|
.join('_')
|
||||||
]),
|
.toLowerCase();
|
||||||
);
|
},
|
||||||
}
|
) as T;
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snakeToCamel<T = any>(value: T) {
|
export function snakeToCamel<T = any>(value: T): T {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
return mapKeysDeep(
|
||||||
if (Array.isArray(value)) {
|
value,
|
||||||
return value.map(snakeToCamel);
|
(_value: string, key: any, parent: any, context: any) => {
|
||||||
}
|
if (Array.isArray(parent)) {
|
||||||
|
// tell mapKeysDeep to skip mapping inside this branch
|
||||||
const impl = (str: string) => {
|
context.skipChildren = true;
|
||||||
const converted = str.replace(/([-_]\w)/g, (group) =>
|
return key;
|
||||||
group[1].toUpperCase(),
|
}
|
||||||
);
|
const converted = key.replace(/([-_]\w)/g, (group) =>
|
||||||
return converted[0].toLowerCase() + converted.slice(1);
|
group[1].toUpperCase(),
|
||||||
};
|
);
|
||||||
|
return converted[0].toLowerCase() + converted.slice(1);
|
||||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
},
|
||||||
return Object.fromEntries(
|
) as T;
|
||||||
Object.entries(value).map(([key, value]) => [
|
|
||||||
impl(key),
|
|
||||||
snakeToCamel(value),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_STRATEGY = {
|
export const DEFAULT_STRATEGY = {
|
||||||
@@ -63,7 +56,7 @@ export const DEFAULT_STRATEGY = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SerializeInterceptor implements NestInterceptor<any, any> {
|
export class SerializeInterceptor implements NestInterceptor<any, any> {
|
||||||
constructor(@Optional() readonly strategy = DEFAULT_STRATEGY) {}
|
constructor(@Optional() readonly strategy = DEFAULT_STRATEGY) { }
|
||||||
|
|
||||||
intercept(
|
intercept(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import {
|
|||||||
NestInterceptor,
|
NestInterceptor,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Observable, map } from 'rxjs';
|
import { Observable, map } from 'rxjs';
|
||||||
import { mapValues, mapValuesDeep } from '@/utils/deepdash';
|
import { mapValuesDeep } from '@/utils/deepdash';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ToJsonInterceptor implements NestInterceptor {
|
export class ToJsonInterceptor implements NestInterceptor {
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
map((data) => {
|
map((data) => {
|
||||||
|
if (data === null || data === undefined) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
return mapValuesDeep(data, (value) => {
|
return mapValuesDeep(data, (value) => {
|
||||||
if (value && typeof value.toJSON === 'function') {
|
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
|
||||||
return value.toJSON();
|
return value.toJSON();
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -1,4 +1,27 @@
|
|||||||
// import { getTransactionsLockingSettingsSchema } from '@/api/controllers/TransactionsLocking/utils';
|
import { chain, mapKeys } from 'lodash';
|
||||||
|
|
||||||
|
const getTransactionsLockingSettingsSchema = (modules: string[]) => {
|
||||||
|
const moduleSchema = {
|
||||||
|
active: { type: 'boolean' },
|
||||||
|
lock_to_date: { type: 'date' },
|
||||||
|
unlock_from_date: { type: 'date' },
|
||||||
|
unlock_to_date: { type: 'date' },
|
||||||
|
lock_reason: { type: 'string' },
|
||||||
|
unlock_reason: { type: 'string' },
|
||||||
|
};
|
||||||
|
return chain(modules)
|
||||||
|
.map((module: string) => {
|
||||||
|
return mapKeys(moduleSchema, (value, key: string) => `${module}.${key}`);
|
||||||
|
})
|
||||||
|
.flattenDeep()
|
||||||
|
.reduce((result, value) => {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
...value,
|
||||||
|
};
|
||||||
|
}, {})
|
||||||
|
.value();
|
||||||
|
};
|
||||||
|
|
||||||
export const SettingsOptions = {
|
export const SettingsOptions = {
|
||||||
organization: {
|
organization: {
|
||||||
@@ -223,12 +246,12 @@ export const SettingsOptions = {
|
|||||||
'locking-type': {
|
'locking-type': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
// ...getTransactionsLockingSettingsSchema([
|
...getTransactionsLockingSettingsSchema([
|
||||||
// 'all',
|
'all',
|
||||||
// 'sales',
|
'sales',
|
||||||
// 'purchases',
|
'purchases',
|
||||||
// 'financial',
|
'financial',
|
||||||
// ]),
|
]),
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
'multi-warehouses': {
|
'multi-warehouses': {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"field.description": "Description",
|
"field.description": "Description",
|
||||||
"field.slug": "Account slug",
|
"field.slug": "Account slug",
|
||||||
"field.code": "Account code",
|
"field.code": "Account code",
|
||||||
|
"field.code_hint": "Unique number to identify the account.",
|
||||||
"field.root_type": "Root type",
|
"field.root_type": "Root type",
|
||||||
"field.normal": "Account normal",
|
"field.normal": "Account normal",
|
||||||
"field.normal.credit": "Credit",
|
"field.normal.credit": "Credit",
|
||||||
@@ -13,5 +14,6 @@
|
|||||||
"field.balance": "Balance",
|
"field.balance": "Balance",
|
||||||
"field.bank_balance": "Bank Balance",
|
"field.bank_balance": "Bank Balance",
|
||||||
"field.parent_account": "Parent Account",
|
"field.parent_account": "Parent Account",
|
||||||
"field.created_at": "Created at"
|
"field.created_at": "Created at",
|
||||||
|
"field.account_hint": "Matches the account name or code."
|
||||||
}
|
}
|
||||||
|
|||||||
30
packages/server/src/i18n/en/bill.json
Normal file
30
packages/server/src/i18n/en/bill.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"field.vendor": "Vendor",
|
||||||
|
"field.bill_number": "Bill No.",
|
||||||
|
"field.bill_date": "Date",
|
||||||
|
"field.due_date": "Due Date",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.note": "Note",
|
||||||
|
"field.open": "Open",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.item": "Item",
|
||||||
|
"field.item_hint": "Matches the item name or code.",
|
||||||
|
"field.rate": "Rate",
|
||||||
|
"field.quantity": "Quantity",
|
||||||
|
"field.description": "Line Description",
|
||||||
|
"field.amount": "Amount",
|
||||||
|
"field.payment_amount": "Payment Amount",
|
||||||
|
"field.status": "Status",
|
||||||
|
"field.status.paid": "Paid",
|
||||||
|
"field.status.partially-paid": "Partially Paid",
|
||||||
|
"field.status.overdue": "Overdue",
|
||||||
|
"field.status.unpaid": "Unpaid",
|
||||||
|
"field.status.opened": "Opened",
|
||||||
|
"field.status.draft": "Draft",
|
||||||
|
"field.created_at": "Created At",
|
||||||
|
"allocation_method": "Allocation Method",
|
||||||
|
"allocation_method.quantity": "Quantity",
|
||||||
|
"allocation_method.value": "Valuation"
|
||||||
|
}
|
||||||
|
|
||||||
15
packages/server/src/i18n/en/bill_payment.json
Normal file
15
packages/server/src/i18n/en/bill_payment.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"field.vendor": "Vendor",
|
||||||
|
"field.payment_date": "Payment Date",
|
||||||
|
"field.payment_number": "Payment No.",
|
||||||
|
"field.payment_account": "Payment Account",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.note": "Note",
|
||||||
|
"field.reference": "Reference",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.entries.bill": "Bill",
|
||||||
|
"field.entries.payment_amount": "Payment Amount",
|
||||||
|
"field.payment_number_hint": "The payment number should be unique.",
|
||||||
|
"field.bill_hint": "Matches the bill number."
|
||||||
|
}
|
||||||
|
|
||||||
16
packages/server/src/i18n/en/credit_note.json
Normal file
16
packages/server/src/i18n/en/credit_note.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"field.customer": "Customer",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.credit_note_date": "Credit Note Date",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.note": "Note",
|
||||||
|
"field.terms_conditions": "Terms & Conditions",
|
||||||
|
"field.credit_note_number": "Credit Note Number",
|
||||||
|
"field.open": "Open",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.item": "Item",
|
||||||
|
"field.rate": "Rate",
|
||||||
|
"field.quantity": "Quantity",
|
||||||
|
"field.description": "Description"
|
||||||
|
}
|
||||||
|
|
||||||
5
packages/server/src/i18n/en/customer.json
Normal file
5
packages/server/src/i18n/en/customer.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"type.business": "Business",
|
||||||
|
"type.individual": "Individual"
|
||||||
|
}
|
||||||
|
|
||||||
21
packages/server/src/i18n/en/estimate.json
Normal file
21
packages/server/src/i18n/en/estimate.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"field.customer": "Customer",
|
||||||
|
"field.estimate_date": "Estimate Date",
|
||||||
|
"field.expiration_date": "Expiration Date",
|
||||||
|
"field.estimate_number": "Estimate No.",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.currency": "Currency",
|
||||||
|
"field.note": "Note",
|
||||||
|
"field.terms_conditions": "Terms & Conditions",
|
||||||
|
"field.delivered": "Delivered",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.amount": "Amount",
|
||||||
|
"field.status": "Status",
|
||||||
|
"field.status.draft": "Draft",
|
||||||
|
"field.status.delivered": "Delivered",
|
||||||
|
"field.status.rejected": "Rejected",
|
||||||
|
"field.status.approved": "Approved",
|
||||||
|
"field.created_at": "Created At"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"field.due_amount": "Due amount",
|
"field.due_amount": "Due amount",
|
||||||
"field.delivered": "Delivered",
|
"field.delivered": "Delivered",
|
||||||
"field.item_name": "Item Name",
|
"field.item_name": "Item Name",
|
||||||
|
"field.item_hint": "Matches the item name or code.",
|
||||||
"field.rate": "Rate",
|
"field.rate": "Rate",
|
||||||
"field.quantity": "Quantity",
|
"field.quantity": "Quantity",
|
||||||
"field.description": "Description",
|
"field.description": "Description",
|
||||||
@@ -38,5 +39,7 @@
|
|||||||
"field.status.draft": "Draft",
|
"field.status.draft": "Draft",
|
||||||
"field.created_at": "Created at",
|
"field.created_at": "Created at",
|
||||||
"field.currency": "Currency",
|
"field.currency": "Currency",
|
||||||
"field.entries": "Entries"
|
"field.entries": "Entries",
|
||||||
|
"field.branch": "Branch",
|
||||||
|
"field.warehouse": "Warehouse"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"field.quantity_on_hand": "Quantity on Hand",
|
"field.quantity_on_hand": "Quantity on Hand",
|
||||||
"field.note": "Note",
|
"field.note": "Note",
|
||||||
"field.category": "Category",
|
"field.category": "Category",
|
||||||
|
"field.category_hint": "Matches the category name.",
|
||||||
"field.active": "Active",
|
"field.active": "Active",
|
||||||
"field.created_at": "Created At"
|
"field.created_at": "Created At"
|
||||||
}
|
}
|
||||||
|
|||||||
17
packages/server/src/i18n/en/payment_receive.json
Normal file
17
packages/server/src/i18n/en/payment_receive.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"field.customer": "Customer",
|
||||||
|
"field.payment_date": "Payment Date",
|
||||||
|
"field.amount": "Amount",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.deposit_account": "Deposit Account",
|
||||||
|
"field.payment_receive_no": "Payment No.",
|
||||||
|
"field.statement": "Statement",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.invoice": "Invoice",
|
||||||
|
"field.entries.payment_amount": "Payment Amount",
|
||||||
|
"field.created_at": "Created At",
|
||||||
|
"field.payment_no_hint": "The payment number should be unique.",
|
||||||
|
"field.invoice_hint": "Matches the invoice number."
|
||||||
|
}
|
||||||
|
|
||||||
@@ -10,5 +10,21 @@
|
|||||||
"paper.receipt_amount": "Receipt amount",
|
"paper.receipt_amount": "Receipt amount",
|
||||||
"paper.total": "Total",
|
"paper.total": "Total",
|
||||||
"paper.balance_due": "Balance Due",
|
"paper.balance_due": "Balance Due",
|
||||||
"paper.payment_amount": "Payment Amount"
|
"paper.payment_amount": "Payment Amount",
|
||||||
|
|
||||||
|
"field.receipt_date": "Receipt Date",
|
||||||
|
"field.customer": "Customer",
|
||||||
|
"field.deposit_account": "Deposit Account",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.receipt_number": "Receipt Number",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.closed": "Closed",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.statement": "Statement",
|
||||||
|
"field.receipt_message": "Receipt Message",
|
||||||
|
"field.amount": "Amount",
|
||||||
|
"field.status": "Status",
|
||||||
|
"field.status.draft": "Draft",
|
||||||
|
"field.status.closed": "Closed",
|
||||||
|
"field.created_at": "Created At"
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,18 @@
|
|||||||
"view.draft": "Draft",
|
"view.draft": "Draft",
|
||||||
"view.published": "Published",
|
"view.published": "Published",
|
||||||
"view.open": "Open",
|
"view.open": "Open",
|
||||||
"view.closed": "Closed"
|
"view.closed": "Closed",
|
||||||
|
|
||||||
|
"field.vendor": "Vendor",
|
||||||
|
"field.vendor_credit_number": "Vendor Credit No.",
|
||||||
|
"field.vendor_credit_date": "Vendor Credit Date",
|
||||||
|
"field.reference_no": "Reference No.",
|
||||||
|
"field.exchange_rate": "Exchange Rate",
|
||||||
|
"field.note": "Note",
|
||||||
|
"field.open": "Open",
|
||||||
|
"field.entries": "Entries",
|
||||||
|
"field.item": "Item",
|
||||||
|
"field.rate": "Rate",
|
||||||
|
"field.quantity": "Quantity",
|
||||||
|
"field.description": "Description"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { ClsMiddleware } from 'nestjs-cls';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import './utils/moment-mysql';
|
import './utils/moment-mysql';
|
||||||
import { AppModule } from './modules/App/App.module';
|
import { AppModule } from './modules/App/App.module';
|
||||||
import { ServiceErrorFilter } from './common/filters/service-error.filter';
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
import { ModelHasRelationsFilter } from './common/filters/model-has-relations.filter';
|
|
||||||
import { ValidationPipe } from './common/pipes/ClassValidation.pipe';
|
|
||||||
import { ToJsonInterceptor } from './common/interceptors/to-json.interceptor';
|
|
||||||
|
|
||||||
global.__public_dirname = path.join(__dirname, '..', 'public');
|
global.__public_dirname = path.join(__dirname, '..', 'public');
|
||||||
global.__static_dirname = path.join(__dirname, '../static');
|
global.__static_dirname = path.join(__dirname, '../static');
|
||||||
@@ -15,17 +12,15 @@ global.__views_dirname = path.join(global.__static_dirname, '/views');
|
|||||||
global.__images_dirname = path.join(global.__static_dirname, '/images');
|
global.__images_dirname = path.join(global.__static_dirname, '/images');
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, { rawBody: true });
|
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||||
|
rawBody: true,
|
||||||
|
});
|
||||||
|
app.set('query parser', 'extended');
|
||||||
app.setGlobalPrefix('/api');
|
app.setGlobalPrefix('/api');
|
||||||
|
|
||||||
// create and mount the middleware manually here
|
// create and mount the middleware manually here
|
||||||
app.use(new ClsMiddleware({}).use);
|
app.use(new ClsMiddleware({}).use);
|
||||||
|
|
||||||
app.useGlobalInterceptors(new ToJsonInterceptor());
|
|
||||||
|
|
||||||
// use the validation pipe globally
|
|
||||||
app.useGlobalPipes(new ValidationPipe());
|
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Bigcapital')
|
.setTitle('Bigcapital')
|
||||||
.setDescription('Financial accounting software')
|
.setDescription('Financial accounting software')
|
||||||
@@ -35,9 +30,6 @@ async function bootstrap() {
|
|||||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('swagger', app, documentFactory);
|
SwaggerModule.setup('swagger', app, documentFactory);
|
||||||
|
|
||||||
app.useGlobalFilters(new ServiceErrorFilter());
|
|
||||||
app.useGlobalFilters(new ModelHasRelationsFilter());
|
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -48,14 +48,30 @@ export class PaginationQueryBuilder<
|
|||||||
// No relations defined
|
// No relations defined
|
||||||
return this.delete();
|
return this.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only check HasManyRelation and ManyToManyRelation relations, as BelongsToOneRelation are just
|
||||||
|
// foreign key references and shouldn't prevent deletion. Only dependent records should block deletion.
|
||||||
|
const dependentRelationNames = relationNames.filter((name) => {
|
||||||
|
const relation = relationMappings[name];
|
||||||
|
return relation && (
|
||||||
|
relation.relation === Model.HasManyRelation ||
|
||||||
|
relation.relation === Model.ManyToManyRelation
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dependentRelationNames.length === 0) {
|
||||||
|
// No dependent relations defined, safe to delete
|
||||||
|
return this.delete();
|
||||||
|
}
|
||||||
|
|
||||||
const recordQuery = this.clone();
|
const recordQuery = this.clone();
|
||||||
|
|
||||||
relationNames.forEach((relationName: string) => {
|
dependentRelationNames.forEach((relationName: string) => {
|
||||||
recordQuery.withGraphFetched(relationName);
|
recordQuery.withGraphFetched(relationName);
|
||||||
});
|
});
|
||||||
const record = await recordQuery;
|
const record = await recordQuery;
|
||||||
|
|
||||||
const hasRelations = relationNames.some((name) => {
|
const hasRelations = dependentRelationNames.some((name) => {
|
||||||
const val = record[name];
|
const val = record[name];
|
||||||
return Array.isArray(val) ? val.length > 0 : val != null;
|
return Array.isArray(val) ? val.length > 0 : val != null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
Put,
|
Put,
|
||||||
|
HttpCode,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { AccountsApplication } from './AccountsApplication.service';
|
import { AccountsApplication } from './AccountsApplication.service';
|
||||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||||
@@ -43,6 +44,7 @@ export class AccountsController {
|
|||||||
constructor(private readonly accountsApplication: AccountsApplication) { }
|
constructor(private readonly accountsApplication: AccountsApplication) { }
|
||||||
|
|
||||||
@Post('validate-bulk-delete')
|
@Post('validate-bulk-delete')
|
||||||
|
@HttpCode(200)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary:
|
summary:
|
||||||
'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.',
|
'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.',
|
||||||
@@ -64,6 +66,7 @@ export class AccountsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('bulk-delete')
|
@Post('bulk-delete')
|
||||||
|
@HttpCode(200)
|
||||||
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
|
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -125,6 +128,7 @@ export class AccountsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/activate')
|
@Post(':id/activate')
|
||||||
|
@HttpCode(200)
|
||||||
@ApiOperation({ summary: 'Activate the given account.' })
|
@ApiOperation({ summary: 'Activate the given account.' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -142,6 +146,7 @@ export class AccountsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/inactivate')
|
@Post(':id/inactivate')
|
||||||
|
@HttpCode(200)
|
||||||
@ApiOperation({ summary: 'Inactivate the given account.' })
|
@ApiOperation({ summary: 'Inactivate the given account.' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class DeleteAccount {
|
|||||||
private eventEmitter: EventEmitter2,
|
private eventEmitter: EventEmitter2,
|
||||||
private uow: UnitOfWork,
|
private uow: UnitOfWork,
|
||||||
private validator: CommandAccountValidators,
|
private validator: CommandAccountValidators,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize account delete.
|
* Authorize account delete.
|
||||||
@@ -57,7 +57,10 @@ export class DeleteAccount {
|
|||||||
trx?: Knex.Transaction,
|
trx?: Knex.Transaction,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
// Retrieve account or not found service error.
|
// Retrieve account or not found service error.
|
||||||
const oldAccount = await this.accountModel().query().findById(accountId);
|
const oldAccount = await this.accountModel()
|
||||||
|
.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Authorize before delete account.
|
// Authorize before delete account.
|
||||||
await this.authorize(accountId, oldAccount);
|
await this.authorize(accountId, oldAccount);
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export const AccountMeta = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
maxLength: 6,
|
maxLength: 6,
|
||||||
unique: true,
|
unique: true,
|
||||||
importHint: 'Unique number to identify the account.',
|
importHint: 'account.field.code_hint',
|
||||||
},
|
},
|
||||||
accountType: {
|
accountType: {
|
||||||
name: 'account.field.type',
|
name: 'account.field.type',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE, APP_FILTER } from '@nestjs/core';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import { RedisModule } from '@liaoliaots/nestjs-redis';
|
import { RedisModule } from '@liaoliaots/nestjs-redis';
|
||||||
@@ -36,6 +36,10 @@ import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
|
|||||||
import { BranchesModule } from '../Branches/Branches.module';
|
import { BranchesModule } from '../Branches/Branches.module';
|
||||||
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
||||||
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor';
|
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor';
|
||||||
|
import { ToJsonInterceptor } from '@/common/interceptors/to-json.interceptor';
|
||||||
|
import { ValidationPipe } from '@/common/pipes/ClassValidation.pipe';
|
||||||
|
import { ServiceErrorFilter } from '@/common/filters/service-error.filter';
|
||||||
|
import { ModelHasRelationsFilter } from '@/common/filters/model-has-relations.filter';
|
||||||
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
|
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
|
||||||
import { CustomersModule } from '../Customers/Customers.module';
|
import { CustomersModule } from '../Customers/Customers.module';
|
||||||
import { VendorsModule } from '../Vendors/Vendors.module';
|
import { VendorsModule } from '../Vendors/Vendors.module';
|
||||||
@@ -234,6 +238,10 @@ import { AppThrottleModule } from './AppThrottle.module';
|
|||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_PIPE,
|
||||||
|
useClass: ValidationPipe,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
useClass: ThrottlerGuard,
|
useClass: ThrottlerGuard,
|
||||||
@@ -242,6 +250,10 @@ import { AppThrottleModule } from './AppThrottle.module';
|
|||||||
provide: APP_INTERCEPTOR,
|
provide: APP_INTERCEPTOR,
|
||||||
useClass: SerializeInterceptor,
|
useClass: SerializeInterceptor,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ToJsonInterceptor,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: APP_INTERCEPTOR,
|
provide: APP_INTERCEPTOR,
|
||||||
useClass: UserIpInterceptor,
|
useClass: UserIpInterceptor,
|
||||||
@@ -250,6 +262,14 @@ import { AppThrottleModule } from './AppThrottle.module';
|
|||||||
provide: APP_INTERCEPTOR,
|
provide: APP_INTERCEPTOR,
|
||||||
useClass: ExcludeNullInterceptor,
|
useClass: ExcludeNullInterceptor,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: ServiceErrorFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: ModelHasRelationsFilter,
|
||||||
|
},
|
||||||
AppService,
|
AppService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,27 @@ import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'
|
|||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: (configService: ConfigService) => {
|
useFactory: (configService: ConfigService) => {
|
||||||
|
// Use in-memory storage with very high limits for test environment
|
||||||
|
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
return {
|
||||||
|
throttlers: [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
ttl: 60000,
|
||||||
|
limit: 1000000, // Effectively disable throttling in tests
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'auth',
|
||||||
|
ttl: 60000,
|
||||||
|
limit: 1000000, // Effectively disable throttling in tests
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// No storage specified = uses in-memory storage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const host = configService.get<string>('redis.host') || 'localhost';
|
const host = configService.get<string>('redis.host') || 'localhost';
|
||||||
const port = Number(configService.get<number>('redis.port') || 6379);
|
const port = Number(configService.get<number>('redis.port') || 6379);
|
||||||
const password = configService.get<string>('redis.password');
|
const password = configService.get<string>('redis.password');
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
import { Controller, Get, Param, Post, Query } from '@nestjs/common';
|
import { Controller, Get, Param, Post, Query } from '@nestjs/common';
|
||||||
import { BankAccountsApplication } from './BankAccountsApplication.service';
|
import { BankAccountsApplication } from './BankAccountsApplication.service';
|
||||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { ICashflowAccountsFilter } from './types/BankAccounts.types';
|
import { BankAccountsQueryDto } from './dtos/BankAccountsQuery.dto';
|
||||||
|
import { BankAccountResponseDto } from './dtos/BankAccountResponse.dto';
|
||||||
|
|
||||||
@Controller('banking/accounts')
|
@Controller('banking/accounts')
|
||||||
@ApiTags('Bank Accounts')
|
@ApiTags('Bank Accounts')
|
||||||
export class BankAccountsController {
|
export class BankAccountsController {
|
||||||
constructor(private bankAccountsApplication: BankAccountsApplication) {}
|
constructor(private bankAccountsApplication: BankAccountsApplication) { }
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: 'Retrieve the bank accounts.' })
|
@ApiOperation({ summary: 'Retrieve the bank accounts.' })
|
||||||
getBankAccounts(@Query() filterDto: ICashflowAccountsFilter) {
|
@ApiQuery({
|
||||||
|
name: 'query',
|
||||||
|
description: 'Query parameters for the bank accounts list.',
|
||||||
|
type: BankAccountsQueryDto,
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'List of bank accounts retrieved successfully.',
|
||||||
|
type: [BankAccountResponseDto],
|
||||||
|
})
|
||||||
|
getBankAccounts(@Query() filterDto: BankAccountsQueryDto) {
|
||||||
return this.bankAccountsApplication.getBankAccounts(filterDto);
|
return this.bankAccountsApplication.getBankAccounts(filterDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { PauseBankAccountFeeds } from './commands/PauseBankAccountFeeds.service'
|
|||||||
import { GetBankAccountsService } from './queries/GetBankAccounts';
|
import { GetBankAccountsService } from './queries/GetBankAccounts';
|
||||||
import { ICashflowAccountsFilter } from './types/BankAccounts.types';
|
import { ICashflowAccountsFilter } from './types/BankAccounts.types';
|
||||||
import { GetBankAccountSummary } from './queries/GetBankAccountSummary';
|
import { GetBankAccountSummary } from './queries/GetBankAccountSummary';
|
||||||
|
import { BankAccountsQueryDto } from './dtos/BankAccountsQuery.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankAccountsApplication {
|
export class BankAccountsApplication {
|
||||||
@@ -16,13 +17,13 @@ export class BankAccountsApplication {
|
|||||||
private readonly refreshBankAccountService: RefreshBankAccountService,
|
private readonly refreshBankAccountService: RefreshBankAccountService,
|
||||||
private readonly resumeBankAccountFeedsService: ResumeBankAccountFeedsService,
|
private readonly resumeBankAccountFeedsService: ResumeBankAccountFeedsService,
|
||||||
private readonly pauseBankAccountFeedsService: PauseBankAccountFeeds,
|
private readonly pauseBankAccountFeedsService: PauseBankAccountFeeds,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the bank accounts.
|
* Retrieves the bank accounts.
|
||||||
* @param {ICashflowAccountsFilter} filterDto -
|
* @param {ICashflowAccountsFilter} filterDto -
|
||||||
*/
|
*/
|
||||||
getBankAccounts(filterDto: ICashflowAccountsFilter) {
|
getBankAccounts(filterDto: BankAccountsQueryDto) {
|
||||||
return this.getBankAccountsService.getCashflowAccounts(filterDto);
|
return this.getBankAccountsService.getCashflowAccounts(filterDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bank Account Response DTO
|
||||||
|
* Based on AccountResponseDto but excludes fields that are filtered out by CashflowAccountTransformer:
|
||||||
|
* - predefined
|
||||||
|
* - index
|
||||||
|
* - accountTypeLabel
|
||||||
|
*/
|
||||||
|
export class BankAccountResponseDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The unique identifier of the account',
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The name of the account',
|
||||||
|
example: 'Cash Account',
|
||||||
|
})
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The slug of the account',
|
||||||
|
example: 'cash-account',
|
||||||
|
})
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The code of the account',
|
||||||
|
example: '1001',
|
||||||
|
})
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The type of the account',
|
||||||
|
example: 'bank',
|
||||||
|
})
|
||||||
|
accountType: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The parent account ID',
|
||||||
|
example: null,
|
||||||
|
})
|
||||||
|
parentAccountId: number | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The currency code of the account',
|
||||||
|
example: 'USD',
|
||||||
|
})
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Whether the account is active',
|
||||||
|
example: true,
|
||||||
|
})
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The bank balance of the account',
|
||||||
|
example: 5000.0,
|
||||||
|
})
|
||||||
|
bankBalance: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The formatted bank balance',
|
||||||
|
example: '$5,000.00',
|
||||||
|
})
|
||||||
|
bankBalanceFormatted: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The last feeds update timestamp',
|
||||||
|
example: '2024-03-20T10:30:00Z',
|
||||||
|
})
|
||||||
|
lastFeedsUpdatedAt: string | Date | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The formatted last feeds update timestamp',
|
||||||
|
example: 'Mar 20, 2024 10:30 AM',
|
||||||
|
})
|
||||||
|
lastFeedsUpdatedAtFormatted: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The last feeds updated from now (relative time)',
|
||||||
|
example: '2 hours ago',
|
||||||
|
})
|
||||||
|
lastFeedsUpdatedFromNow: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The amount of the account',
|
||||||
|
example: 5000.0,
|
||||||
|
})
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The formatted amount',
|
||||||
|
example: '$5,000.00',
|
||||||
|
})
|
||||||
|
formattedAmount: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The Plaid item ID',
|
||||||
|
example: 'plaid-item-123',
|
||||||
|
})
|
||||||
|
plaidItemId: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The Plaid account ID',
|
||||||
|
example: 'plaid-account-456',
|
||||||
|
})
|
||||||
|
plaidAccountId: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Whether the feeds are active',
|
||||||
|
example: true,
|
||||||
|
})
|
||||||
|
isFeedsActive: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Whether the account is syncing owner',
|
||||||
|
example: true,
|
||||||
|
})
|
||||||
|
isSyncingOwner: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Whether the feeds are paused',
|
||||||
|
example: false,
|
||||||
|
})
|
||||||
|
isFeedsPaused: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The account normal',
|
||||||
|
example: 'debit',
|
||||||
|
})
|
||||||
|
accountNormal: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The formatted account normal',
|
||||||
|
example: 'Debit',
|
||||||
|
})
|
||||||
|
accountNormalFormatted: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The flatten name with all dependant accounts names',
|
||||||
|
example: 'Assets: Cash Account',
|
||||||
|
})
|
||||||
|
flattenName: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The account level in the hierarchy',
|
||||||
|
example: 2,
|
||||||
|
})
|
||||||
|
accountLevel?: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The creation timestamp',
|
||||||
|
example: '2024-03-20T10:00:00Z',
|
||||||
|
})
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The update timestamp',
|
||||||
|
example: '2024-03-20T10:30:00Z',
|
||||||
|
})
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator';
|
||||||
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
|
import { IFilterRole, ISortOrder } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||||
|
import { parseBoolean } from '@/utils/parse-boolean';
|
||||||
|
|
||||||
|
export class BankAccountsQueryDto {
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Custom view ID',
|
||||||
|
type: Number,
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
|
customViewId?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Filter roles array',
|
||||||
|
type: Array,
|
||||||
|
isArray: true,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsOptional()
|
||||||
|
filterRoles?: IFilterRole[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Column to sort by',
|
||||||
|
type: String,
|
||||||
|
example: 'created_at',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
columnSortBy?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Sort order',
|
||||||
|
enum: ISortOrder,
|
||||||
|
example: ISortOrder.DESC,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(ISortOrder)
|
||||||
|
sortOrder?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Stringified filter roles',
|
||||||
|
type: String,
|
||||||
|
example: '{"fieldKey":"status","value":"active"}',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
stringifiedFilterRoles?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Search keyword',
|
||||||
|
type: String,
|
||||||
|
example: 'bank account',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
searchKeyword?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'View slug',
|
||||||
|
type: String,
|
||||||
|
example: 'active-accounts',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
viewSlug?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Page number',
|
||||||
|
type: Number,
|
||||||
|
example: 1,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Page size',
|
||||||
|
type: Number,
|
||||||
|
example: 25,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
pageSize?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Include inactive accounts',
|
||||||
|
type: Boolean,
|
||||||
|
example: false,
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@Transform(({ value }) => parseBoolean(value, false))
|
||||||
|
@IsBoolean()
|
||||||
|
inactiveMode?: boolean;
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|||||||
import { ICashflowAccountsFilter } from '../types/BankAccounts.types';
|
import { ICashflowAccountsFilter } from '../types/BankAccounts.types';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||||
|
import { BankAccountsQueryDto } from '../dtos/BankAccountsQuery.dto';
|
||||||
|
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetBankAccountsService {
|
export class GetBankAccountsService {
|
||||||
@@ -15,14 +17,14 @@ export class GetBankAccountsService {
|
|||||||
|
|
||||||
@Inject(Account.name)
|
@Inject(Account.name)
|
||||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cash flow accounts.
|
* Retrieve the cash flow accounts.
|
||||||
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
|
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
|
||||||
* @returns {ICashflowAccount[]}
|
* @returns {ICashflowAccount[]}
|
||||||
*/
|
*/
|
||||||
public async getCashflowAccounts(filterDTO: ICashflowAccountsFilter) {
|
public async getCashflowAccounts(filterDTO: BankAccountsQueryDto) {
|
||||||
const _filterDto = {
|
const _filterDto = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
@@ -30,12 +32,14 @@ export class GetBankAccountsService {
|
|||||||
...filterDTO,
|
...filterDTO,
|
||||||
};
|
};
|
||||||
// Parsees accounts list filter DTO.
|
// Parsees accounts list filter DTO.
|
||||||
const filter = this.dynamicListService.parseStringifiedFilter(_filterDto);
|
const filter = this.dynamicListService.parseStringifiedFilter<BankAccountsQueryDto>(
|
||||||
|
_filterDto,
|
||||||
|
);
|
||||||
|
|
||||||
// Dynamic list service.
|
// Dynamic list service.
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
this.accountModel(),
|
this.accountModel(),
|
||||||
filter,
|
filter as IDynamicListFilter,
|
||||||
);
|
);
|
||||||
// Retrieve accounts model based on the given query.
|
// Retrieve accounts model based on the given query.
|
||||||
const accounts = await this.accountModel()
|
const accounts = await this.accountModel()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import uniqid from 'uniqid';
|
import * as uniqid from 'uniqid';
|
||||||
import { Importable } from '../../Import/Importable';
|
import { Importable } from '../../Import/Importable';
|
||||||
import { CreateUncategorizedTransactionService } from './CreateUncategorizedTransaction.service';
|
import { CreateUncategorizedTransactionService } from './CreateUncategorizedTransaction.service';
|
||||||
import { ImportableContext } from '../../Import/interfaces';
|
import { ImportableContext } from '../../Import/interfaces';
|
||||||
@@ -9,8 +9,10 @@ import { BankTransactionsSampleData } from '../../BankingTransactions/constants'
|
|||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { CreateUncategorizedTransactionDTO } from '../types/BankingCategorize.types';
|
import { CreateUncategorizedTransactionDTO } from '../types/BankingCategorize.types';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { ImportableService } from '../../Import/decorators/Import.decorator';
|
||||||
|
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ImportableService({ name: UncategorizedBankTransaction.name })
|
||||||
export class UncategorizedTransactionsImportable extends Importable {
|
export class UncategorizedTransactionsImportable extends Importable {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService,
|
private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
||||||
import { BankingMatchingApplication } from './BankingMatchingApplication';
|
import { BankingMatchingApplication } from './BankingMatchingApplication';
|
||||||
import { GetMatchedTransactionsFilter } from './types';
|
import { GetMatchedTransactionsFilter } from './types';
|
||||||
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
||||||
@@ -34,7 +34,7 @@ export class BankingMatchingController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/unmatch/:uncategorizedTransactionId')
|
@Patch('/unmatch/:uncategorizedTransactionId')
|
||||||
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
|
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
|
||||||
async unmatchMatchedTransaction(
|
async unmatchMatchedTransaction(
|
||||||
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number,
|
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { isEmpty, sumBy } from 'lodash';
|
import { isEmpty, round, sumBy } from 'lodash';
|
||||||
import { ERRORS, MatchedTransactionPOJO } from './types';
|
import { ERRORS, MatchedTransactionPOJO } from './types';
|
||||||
import { ServiceError } from '../Items/ServiceError';
|
import { ServiceError } from '../Items/ServiceError';
|
||||||
|
|
||||||
@@ -22,18 +22,24 @@ export const sortClosestMatchTransactions = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sumMatchTranasctions = (transactions: Array<any>) => {
|
export const sumMatchTranasctions = (transactions: Array<any>) => {
|
||||||
return transactions.reduce(
|
const total = transactions.reduce(
|
||||||
(total, item) =>
|
(sum, item) => {
|
||||||
total +
|
const amount = parseFloat(item.amount) || 0;
|
||||||
(item.transactionNormal === 'debit' ? 1 : -1) * parseFloat(item.amount),
|
const multiplier = item.transactionNormal === 'debit' ? 1 : -1;
|
||||||
|
return sum + multiplier * amount;
|
||||||
|
},
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
// Round to 2 decimal places to avoid floating-point precision issues
|
||||||
|
return round(total, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sumUncategorizedTransactions = (
|
export const sumUncategorizedTransactions = (
|
||||||
uncategorizedTransactions: Array<any>
|
uncategorizedTransactions: Array<any>
|
||||||
) => {
|
) => {
|
||||||
return sumBy(uncategorizedTransactions, 'amount');
|
const total = sumBy(uncategorizedTransactions, 'amount');
|
||||||
|
// Round to 2 decimal places to avoid floating-point precision issues
|
||||||
|
return round(total, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateUncategorizedTransactionsNotMatched = (
|
export const validateUncategorizedTransactionsNotMatched = (
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class MatchBankTransactions {
|
|||||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
typeof UncategorizedBankTransaction
|
typeof UncategorizedBankTransaction
|
||||||
>,
|
>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the match bank transactions DTO.
|
* Validates the match bank transactions DTO.
|
||||||
@@ -100,7 +100,10 @@ export class MatchBankTransactions {
|
|||||||
);
|
);
|
||||||
// Validates the total given matching transcations whether is not equal
|
// Validates the total given matching transcations whether is not equal
|
||||||
// uncategorized transaction amount.
|
// uncategorized transaction amount.
|
||||||
if (totalUncategorizedTransactions !== totalMatchedTranasctions) {
|
// Use tolerance-based comparison to handle floating-point precision issues
|
||||||
|
const tolerance = 0.01; // Allow 0.01 difference for floating-point precision
|
||||||
|
const difference = Math.abs(totalUncategorizedTransactions - totalMatchedTranasctions);
|
||||||
|
if (difference > tolerance) {
|
||||||
throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID);
|
throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,24 +12,30 @@ export class MatchTransactionsTypes {
|
|||||||
private static registry: MatchTransactionsTypesRegistry;
|
private static registry: MatchTransactionsTypesRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consttuctor method.
|
* Constructor method.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor(
|
||||||
|
private readonly getMatchedInvoicesService: GetMatchedTransactionsByInvoices,
|
||||||
|
private readonly getMatchedBillsService: GetMatchedTransactionsByBills,
|
||||||
|
private readonly getMatchedExpensesService: GetMatchedTransactionsByExpenses,
|
||||||
|
private readonly getMatchedManualJournalsService: GetMatchedTransactionsByManualJournals,
|
||||||
|
private readonly getMatchedCashflowService: GetMatchedTransactionsByCashflow,
|
||||||
|
) {
|
||||||
this.boot();
|
this.boot();
|
||||||
}
|
}
|
||||||
|
|
||||||
get registered() {
|
get registered() {
|
||||||
return [
|
return [
|
||||||
{ type: 'SaleInvoice', service: GetMatchedTransactionsByInvoices },
|
{ type: 'SaleInvoice', service: this.getMatchedInvoicesService },
|
||||||
{ type: 'Bill', service: GetMatchedTransactionsByBills },
|
{ type: 'Bill', service: this.getMatchedBillsService },
|
||||||
{ type: 'Expense', service: GetMatchedTransactionsByExpenses },
|
{ type: 'Expense', service: this.getMatchedExpensesService },
|
||||||
{
|
{
|
||||||
type: 'ManualJournal',
|
type: 'ManualJournal',
|
||||||
service: GetMatchedTransactionsByManualJournals,
|
service: this.getMatchedManualJournalsService,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'CashflowTransaction',
|
type: 'CashflowTransaction',
|
||||||
service: GetMatchedTransactionsByCashflow,
|
service: this.getMatchedCashflowService,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -50,14 +56,13 @@ export class MatchTransactionsTypes {
|
|||||||
* Boots all the registered importables.
|
* Boots all the registered importables.
|
||||||
*/
|
*/
|
||||||
public boot() {
|
public boot() {
|
||||||
if (!MatchTransactionsTypes.registry) {
|
const instance = MatchTransactionsTypesRegistry.getInstance();
|
||||||
const instance = MatchTransactionsTypesRegistry.getInstance();
|
|
||||||
|
|
||||||
this.registered.forEach((registered) => {
|
// Always register services to ensure they're available
|
||||||
// const serviceInstanace = Container.get(registered.service);
|
this.registered.forEach((registered) => {
|
||||||
// instance.register(registered.type, serviceInstanace);
|
instance.register(registered.type, registered.service);
|
||||||
});
|
});
|
||||||
MatchTransactionsTypes.registry = instance;
|
|
||||||
}
|
MatchTransactionsTypes.registry = instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,4 +83,4 @@ const models = [
|
|||||||
CreateBankTransactionService
|
CreateBankTransactionService
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BankingTransactionsModule {}
|
export class BankingTransactionsModule { }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { Transform, Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
IsPositive,
|
IsPositive,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { parseBoolean } from '@/utils/parse-boolean';
|
||||||
|
|
||||||
export class NumberFormatQueryDto {
|
export class NumberFormatQueryDto {
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
@@ -24,6 +25,7 @@ export class NumberFormatQueryDto {
|
|||||||
example: false,
|
example: false,
|
||||||
})
|
})
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
@Transform(({ value }) => parseBoolean(value, false))
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
readonly divideOn1000: boolean;
|
readonly divideOn1000: boolean;
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ export class NumberFormatQueryDto {
|
|||||||
example: true,
|
example: true,
|
||||||
})
|
})
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
@Transform(({ value }) => parseBoolean(value, false))
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
readonly showZero: boolean;
|
readonly showZero: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
export const UncategorizedBankTransactionMeta = {
|
||||||
|
defaultFilterField: 'createdAt',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
importable: true,
|
||||||
|
fields: {
|
||||||
|
date: {
|
||||||
|
name: 'Date',
|
||||||
|
column: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
payee: {
|
||||||
|
name: 'Payee',
|
||||||
|
column: 'payee',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'Amount',
|
||||||
|
fieldType: 'numeric',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
name: 'Account',
|
||||||
|
column: 'account_id',
|
||||||
|
fieldType: 'relation',
|
||||||
|
to: { model: 'Account', to: 'id' },
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
name: 'Created At',
|
||||||
|
column: 'createdAt',
|
||||||
|
fieldType: 'date',
|
||||||
|
importable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
date: {
|
||||||
|
name: 'Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
payee: {
|
||||||
|
name: 'Payee',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { UncategorizedBankTransactionMeta } from './UncategorizedBankTransaction.meta';
|
||||||
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
|
|
||||||
|
@InjectModelMeta(UncategorizedBankTransactionMeta)
|
||||||
export class UncategorizedBankTransaction extends TenantBaseModel {
|
export class UncategorizedBankTransaction extends TenantBaseModel {
|
||||||
readonly amount!: number;
|
readonly amount!: number;
|
||||||
readonly date!: Date | string;
|
readonly date!: Date | string;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
validateTransactionShouldBeExcluded,
|
validateTransactionShouldBeExcluded,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
import {
|
||||||
IBankTransactionExcludedEventPayload,
|
IBankTransactionUnexcludedEventPayload,
|
||||||
IBankTransactionExcludingEventPayload,
|
IBankTransactionUnexcludingEventPayload,
|
||||||
} from '../types/BankTransactionsExclude.types';
|
} from '../types/BankTransactionsExclude.types';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
@@ -24,7 +24,7 @@ export class UnexcludeBankTransactionService {
|
|||||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
typeof UncategorizedBankTransaction
|
typeof UncategorizedBankTransaction
|
||||||
>,
|
>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given bank transaction as excluded.
|
* Marks the given bank transaction as excluded.
|
||||||
@@ -50,7 +50,8 @@ export class UnexcludeBankTransactionService {
|
|||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, {
|
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, {
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
} as IBankTransactionExcludingEventPayload);
|
trx,
|
||||||
|
} as IBankTransactionUnexcludingEventPayload);
|
||||||
|
|
||||||
await this.uncategorizedBankTransactionModel()
|
await this.uncategorizedBankTransactionModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
@@ -61,7 +62,8 @@ export class UnexcludeBankTransactionService {
|
|||||||
|
|
||||||
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, {
|
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, {
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
} as IBankTransactionExcludedEventPayload);
|
trx,
|
||||||
|
} as IBankTransactionUnexcludedEventPayload);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class DecrementUncategorizedTransactionOnExclude {
|
|||||||
private readonly uncategorizedBankTransaction: TenantModelProxy<
|
private readonly uncategorizedBankTransaction: TenantModelProxy<
|
||||||
typeof UncategorizedBankTransaction
|
typeof UncategorizedBankTransaction
|
||||||
>,
|
>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
||||||
@@ -50,7 +50,7 @@ export class DecrementUncategorizedTransactionOnExclude {
|
|||||||
trx,
|
trx,
|
||||||
}: IBankTransactionUnexcludedEventPayload) {
|
}: IBankTransactionUnexcludedEventPayload) {
|
||||||
const transaction = await this.uncategorizedBankTransaction()
|
const transaction = await this.uncategorizedBankTransaction()
|
||||||
.query()
|
.query(trx)
|
||||||
.findById(uncategorizedTransactionId);
|
.findById(uncategorizedTransactionId);
|
||||||
//
|
//
|
||||||
await this.account()
|
await this.account()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class BillAllocateLandedCostController {
|
|||||||
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
|
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
|
||||||
private revertAllocatedLandedCost: RevertAllocatedLandedCost,
|
private revertAllocatedLandedCost: RevertAllocatedLandedCost,
|
||||||
private landedCostTransactions: LandedCostTranasctions,
|
private landedCostTransactions: LandedCostTranasctions,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@Get('/transactions')
|
@Get('/transactions')
|
||||||
@ApiOperation({ summary: 'Get landed cost transactions' })
|
@ApiOperation({ summary: 'Get landed cost transactions' })
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class BillAllocatedLandedCostTransactions {
|
|||||||
private readonly billLandedCostModel: TenantModelProxy<
|
private readonly billLandedCostModel: TenantModelProxy<
|
||||||
typeof BillLandedCost
|
typeof BillLandedCost
|
||||||
>,
|
>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the bill associated landed cost transactions.
|
* Retrieve the bill associated landed cost transactions.
|
||||||
@@ -77,6 +77,13 @@ export class BillAllocatedLandedCostTransactions {
|
|||||||
transaction.fromTransactionType,
|
transaction.fromTransactionType,
|
||||||
transaction,
|
transaction,
|
||||||
);
|
);
|
||||||
|
const allocationMethodFormattedKey = transaction.allocationMethodFormatted;
|
||||||
|
const allocationMethodFormatted = allocationMethodFormattedKey
|
||||||
|
? this.i18nService.t(allocationMethodFormattedKey, {
|
||||||
|
defaultValue: allocationMethodFormattedKey,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formattedAmount: formatNumber(transaction.amount, {
|
formattedAmount: formatNumber(transaction.amount, {
|
||||||
currencyCode: transaction.currencyCode,
|
currencyCode: transaction.currencyCode,
|
||||||
@@ -84,12 +91,14 @@ export class BillAllocatedLandedCostTransactions {
|
|||||||
...omit(transaction, [
|
...omit(transaction, [
|
||||||
'allocatedFromBillEntry',
|
'allocatedFromBillEntry',
|
||||||
'allocatedFromExpenseEntry',
|
'allocatedFromExpenseEntry',
|
||||||
|
'allocationMethodFormatted',
|
||||||
]),
|
]),
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
formattedLocalAmount: formatNumber(transaction.localAmount, {
|
formattedLocalAmount: formatNumber(transaction.localAmount, {
|
||||||
currencyCode: 'USD',
|
currencyCode: 'USD',
|
||||||
}),
|
}),
|
||||||
|
allocationMethodFormatted,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { LandedCostTransactionsQueryDto } from '../dtos/LandedCostTransactionsQu
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LandedCostTranasctions {
|
export class LandedCostTranasctions {
|
||||||
constructor(private readonly transactionLandedCost: TransactionLandedCost) {}
|
constructor(private readonly transactionLandedCost: TransactionLandedCost) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the landed costs based on the given query.
|
* Retrieve the landed costs based on the given query.
|
||||||
@@ -45,8 +45,8 @@ export class LandedCostTranasctions {
|
|||||||
)(transactionType);
|
)(transactionType);
|
||||||
|
|
||||||
return pipe(
|
return pipe(
|
||||||
this.transformLandedCostTransactions,
|
|
||||||
R.map(transformLandedCost),
|
R.map(transformLandedCost),
|
||||||
|
this.transformLandedCostTransactions,
|
||||||
)(transactions);
|
)(transactions);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export class LandedCostTranasctions {
|
|||||||
const entries = R.map<
|
const entries = R.map<
|
||||||
ILandedCostTransactionEntry,
|
ILandedCostTransactionEntry,
|
||||||
ILandedCostTransactionEntryDOJO
|
ILandedCostTransactionEntryDOJO
|
||||||
>(transformLandedCostEntry)(transaction.entries);
|
>(transformLandedCostEntry)(transaction.entries ?? []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...transaction,
|
...transaction,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
IsOptional,
|
IsOptional,
|
||||||
IsArray,
|
IsArray,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
IsDecimal,
|
|
||||||
IsString,
|
IsString,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
@@ -17,8 +16,9 @@ export class AllocateBillLandedCostItemDto {
|
|||||||
@ToNumber()
|
@ToNumber()
|
||||||
entryId: number;
|
entryId: number;
|
||||||
|
|
||||||
@IsDecimal()
|
@IsNumber()
|
||||||
cost: string; // Use string for IsDecimal, or use @IsNumber() if you want a number
|
@ToNumber()
|
||||||
|
cost: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AllocateBillLandedCostDto {
|
export class AllocateBillLandedCostDto {
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ export class BillLandedCost extends BaseModel {
|
|||||||
const allocationMethod = lowerCase(this.allocationMethod);
|
const allocationMethod = lowerCase(this.allocationMethod);
|
||||||
|
|
||||||
const keyLabelsPairs = {
|
const keyLabelsPairs = {
|
||||||
value: 'allocation_method.value.label',
|
value: 'bill.allocation_method.value',
|
||||||
quantity: 'allocation_method.quantity.label',
|
quantity: 'bill.allocation_method.quantity',
|
||||||
};
|
};
|
||||||
return keyLabelsPairs[allocationMethod] || '';
|
return keyLabelsPairs[allocationMethod] || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { BranchesSettingsService } from '../Branches/BranchesSettings';
|
|||||||
import { BillPaymentsController } from './BillPayments.controller';
|
import { BillPaymentsController } from './BillPayments.controller';
|
||||||
import { BillPaymentGLEntries } from './commands/BillPaymentGLEntries';
|
import { BillPaymentGLEntries } from './commands/BillPaymentGLEntries';
|
||||||
import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber';
|
import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber';
|
||||||
|
import { BillPaymentBillSyncSubscriber } from './subscribers/BillPaymentBillSyncSubscriber';
|
||||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||||
import { BillPaymentsExportable } from './queries/BillPaymentsExportable';
|
import { BillPaymentsExportable } from './queries/BillPaymentsExportable';
|
||||||
@@ -39,6 +40,7 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
|
|||||||
TenancyContext,
|
TenancyContext,
|
||||||
BillPaymentGLEntries,
|
BillPaymentGLEntries,
|
||||||
BillPaymentGLEntriesSubscriber,
|
BillPaymentGLEntriesSubscriber,
|
||||||
|
BillPaymentBillSyncSubscriber,
|
||||||
BillPaymentsExportable,
|
BillPaymentsExportable,
|
||||||
BillPaymentsImportable,
|
BillPaymentsImportable,
|
||||||
GetBillPaymentsService,
|
GetBillPaymentsService,
|
||||||
@@ -52,4 +54,4 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service';
|
|||||||
],
|
],
|
||||||
controllers: [BillPaymentsController],
|
controllers: [BillPaymentsController],
|
||||||
})
|
})
|
||||||
export class BillPaymentsModule {}
|
export class BillPaymentsModule { }
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class CreateBillPaymentService {
|
|||||||
|
|
||||||
@Inject(BillPayment.name)
|
@Inject(BillPayment.name)
|
||||||
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
|
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new bill payment transcations and store it to the storage
|
* Creates a new bill payment transcations and store it to the storage
|
||||||
@@ -103,11 +103,19 @@ export class CreateBillPaymentService {
|
|||||||
} as IBillPaymentCreatingPayload);
|
} as IBillPaymentCreatingPayload);
|
||||||
|
|
||||||
// Writes the bill payment graph to the storage.
|
// Writes the bill payment graph to the storage.
|
||||||
const billPayment = await this.billPaymentModel()
|
const insertedBillPayment = await this.billPaymentModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.insertGraphAndFetch({
|
.insertGraphAndFetch({
|
||||||
...billPaymentObj,
|
...billPaymentObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch the bill payment with entries to ensure they're loaded for the subscriber.
|
||||||
|
const billPayment = await this.billPaymentModel()
|
||||||
|
.query(trx)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.findById(insertedBillPayment.id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Triggers `onBillPaymentCreated` event.
|
// Triggers `onBillPaymentCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
||||||
billPayment,
|
billPayment,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class EditBillPayment {
|
|||||||
|
|
||||||
@Inject(Vendor.name)
|
@Inject(Vendor.name)
|
||||||
private readonly vendorModel: TenantModelProxy<typeof Vendor>,
|
private readonly vendorModel: TenantModelProxy<typeof Vendor>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits the details of the given bill payment.
|
* Edits the details of the given bill payment.
|
||||||
@@ -116,12 +116,20 @@ export class EditBillPayment {
|
|||||||
} as IBillPaymentEditingPayload);
|
} as IBillPaymentEditingPayload);
|
||||||
|
|
||||||
// Edits the bill payment transaction graph on the storage.
|
// Edits the bill payment transaction graph on the storage.
|
||||||
const billPayment = await this.billPaymentModel()
|
await this.billPaymentModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.upsertGraphAndFetch({
|
.upsertGraph({
|
||||||
id: billPaymentId,
|
id: billPaymentId,
|
||||||
...billPaymentObj,
|
...billPaymentObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch the bill payment with entries to ensure they're loaded for the subscriber.
|
||||||
|
const billPayment = await this.billPaymentModel()
|
||||||
|
.query(trx)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.findById(billPaymentId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Triggers `onBillPaymentEdited` event.
|
// Triggers `onBillPaymentEdited` event.
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
||||||
billPaymentId,
|
billPaymentId,
|
||||||
|
|||||||
@@ -104,6 +104,12 @@ export class BillPaymentResponseDto {
|
|||||||
@ApiProperty({ description: 'The formatted amount', example: '100.00 USD' })
|
@ApiProperty({ description: 'The formatted amount', example: '100.00 USD' })
|
||||||
formattedAmount: string;
|
formattedAmount: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'The formatted total', example: '100.00 USD' })
|
||||||
|
formattedTotal: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'The formatted subtotal', example: '100.00 USD' })
|
||||||
|
formattedSubtotal: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The date when the payment was created',
|
description: 'The date when the payment was created',
|
||||||
example: '2024-01-01T12:00:00Z',
|
example: '2024-01-01T12:00:00Z',
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export const BillPaymentMeta = {
|
|||||||
name: 'bill_payment.field.payment_number',
|
name: 'bill_payment.field.payment_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
unique: true,
|
unique: true,
|
||||||
importHint: 'The payment number should be unique.',
|
importHint: 'bill_payment.field.payment_number_hint',
|
||||||
},
|
},
|
||||||
paymentAccountId: {
|
paymentAccountId: {
|
||||||
name: 'bill_payment.field.payment_account',
|
name: 'bill_payment.field.payment_account',
|
||||||
@@ -175,7 +175,7 @@ export const BillPaymentMeta = {
|
|||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'account.field.account_hint',
|
||||||
},
|
},
|
||||||
exchangeRate: {
|
exchangeRate: {
|
||||||
name: 'bill_payment.field.exchange_rate',
|
name: 'bill_payment.field.exchange_rate',
|
||||||
@@ -203,7 +203,7 @@ export const BillPaymentMeta = {
|
|||||||
relationModel: 'Bill',
|
relationModel: 'Bill',
|
||||||
relationImportMatch: 'billNumber',
|
relationImportMatch: 'billNumber',
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the bill number.',
|
importHint: 'bill_payment.field.bill_hint',
|
||||||
},
|
},
|
||||||
paymentAmount: {
|
paymentAmount: {
|
||||||
name: 'bill_payment.field.entries.payment_amount',
|
name: 'bill_payment.field.entries.payment_amount',
|
||||||
@@ -213,7 +213,7 @@ export const BillPaymentMeta = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
branchId: {
|
branchId: {
|
||||||
name: 'Branch',
|
name: 'invoice.field.branch',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Branch',
|
relationModel: 'Branch',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export class BillPaymentTransformer extends Transformer {
|
|||||||
'formattedPaymentDate',
|
'formattedPaymentDate',
|
||||||
'formattedCreatedAt',
|
'formattedCreatedAt',
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
|
'formattedTotal',
|
||||||
|
'formattedSubtotal',
|
||||||
'entries',
|
'entries',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -47,6 +49,29 @@ export class BillPaymentTransformer extends Transformer {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted total.
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedTotal = (billPayment: BillPayment): string => {
|
||||||
|
return this.formatNumber(billPayment.amount, {
|
||||||
|
currencyCode: billPayment.currencyCode,
|
||||||
|
money: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted subtotal.
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedSubtotal = (billPayment: BillPayment): string => {
|
||||||
|
return this.formatNumber(billPayment.amount, {
|
||||||
|
currencyCode: billPayment.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retreives the bill payment entries.
|
* Retreives the bill payment entries.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ export class BillPaymentsExportable extends Exportable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the accounts data to exportable sheet.
|
* Retrieves the accounts data to exportable sheet.
|
||||||
* @param {number} tenantId
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
public async exportable(query: any) {
|
public async exportable(query: any) {
|
||||||
const filterQuery = (builder) => {
|
const filterQuery = (builder) => {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
IBillPaymentEventCreatedPayload,
|
||||||
|
IBillPaymentEventDeletedPayload,
|
||||||
|
IBillPaymentEventEditedPayload,
|
||||||
|
} from '../types/BillPayments.types';
|
||||||
|
import { BillPaymentBillSync } from '../commands/BillPaymentBillSync.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BillPaymentBillSyncSubscriber {
|
||||||
|
/**
|
||||||
|
* @param {BillPaymentBillSync} billPaymentBillSync - Bill payment bill sync service.
|
||||||
|
*/
|
||||||
|
constructor(private readonly billPaymentBillSync: BillPaymentBillSync) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle bill increment/decrement payment amount
|
||||||
|
* once created, edited or deleted.
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billPayment.onCreated)
|
||||||
|
async handleBillIncrementPaymentOnceCreated({
|
||||||
|
billPayment,
|
||||||
|
trx,
|
||||||
|
}: IBillPaymentEventCreatedPayload) {
|
||||||
|
// Ensure entries are available - they should be included in insertGraphAndFetch
|
||||||
|
const entries = billPayment.entries || [];
|
||||||
|
await this.billPaymentBillSync.saveChangeBillsPaymentAmount(
|
||||||
|
entries.map((entry) => ({
|
||||||
|
billId: entry.billId,
|
||||||
|
paymentAmount: entry.paymentAmount,
|
||||||
|
})),
|
||||||
|
null,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle bill increment/decrement payment amount once edited.
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billPayment.onEdited)
|
||||||
|
async handleBillIncrementPaymentOnceEdited({
|
||||||
|
billPayment,
|
||||||
|
oldBillPayment,
|
||||||
|
trx,
|
||||||
|
}: IBillPaymentEventEditedPayload) {
|
||||||
|
const entries = billPayment.entries || [];
|
||||||
|
const oldEntries = oldBillPayment?.entries || null;
|
||||||
|
|
||||||
|
await this.billPaymentBillSync.saveChangeBillsPaymentAmount(
|
||||||
|
entries.map((entry) => ({
|
||||||
|
billId: entry.billId,
|
||||||
|
paymentAmount: entry.paymentAmount,
|
||||||
|
})),
|
||||||
|
oldEntries,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle revert bills payment amount once bill payment deleted.
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billPayment.onDeleted)
|
||||||
|
async handleBillDecrementPaymentAmount({
|
||||||
|
oldBillPayment,
|
||||||
|
trx,
|
||||||
|
}: IBillPaymentEventDeletedPayload) {
|
||||||
|
const oldEntries = oldBillPayment.entries || [];
|
||||||
|
|
||||||
|
await this.billPaymentBillSync.saveChangeBillsPaymentAmount(
|
||||||
|
oldEntries.map((entry) => ({
|
||||||
|
billId: entry.billId,
|
||||||
|
paymentAmount: 0,
|
||||||
|
})),
|
||||||
|
oldEntries,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,10 +11,12 @@ import {
|
|||||||
Post,
|
Post,
|
||||||
Body,
|
Body,
|
||||||
Put,
|
Put,
|
||||||
|
Patch,
|
||||||
Param,
|
Param,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
Query,
|
Query,
|
||||||
|
HttpCode,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { BillsApplication } from './Bills.application';
|
import { BillsApplication } from './Bills.application';
|
||||||
import { IBillsFilter } from './Bills.types';
|
import { IBillsFilter } from './Bills.types';
|
||||||
@@ -40,6 +42,7 @@ export class BillsController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Validate which bills can be deleted and return the results.',
|
summary: 'Validate which bills can be deleted and return the results.',
|
||||||
})
|
})
|
||||||
|
@HttpCode(200)
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
description:
|
description:
|
||||||
@@ -56,6 +59,7 @@ export class BillsController {
|
|||||||
|
|
||||||
@Post('bulk-delete')
|
@Post('bulk-delete')
|
||||||
@ApiOperation({ summary: 'Deletes multiple bills.' })
|
@ApiOperation({ summary: 'Deletes multiple bills.' })
|
||||||
|
@HttpCode(200)
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
description: 'Bills deleted successfully',
|
description: 'Bills deleted successfully',
|
||||||
@@ -160,7 +164,7 @@ export class BillsController {
|
|||||||
return this.billsApplication.getBill(billId);
|
return this.billsApplication.getBill(billId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/open')
|
@Patch(':id/open')
|
||||||
@ApiOperation({ summary: 'Open the given bill.' })
|
@ApiOperation({ summary: 'Open the given bill.' })
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
|||||||
@@ -184,76 +184,76 @@ export const BillMeta = {
|
|||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
billNumber: {
|
billNumber: {
|
||||||
name: 'Bill No.',
|
name: 'bill.field.bill_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'Reference No.',
|
name: 'bill.field.reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
billDate: {
|
billDate: {
|
||||||
name: 'Date',
|
name: 'bill.field.bill_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
dueDate: {
|
dueDate: {
|
||||||
name: 'Due Date',
|
name: 'bill.field.due_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
vendorId: {
|
vendorId: {
|
||||||
name: 'Vendor',
|
name: 'bill.field.vendor',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Contact',
|
relationModel: 'Contact',
|
||||||
relationImportMatch: 'displayName',
|
relationImportMatch: 'displayName',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
exchangeRate: {
|
exchangeRate: {
|
||||||
name: 'Exchange Rate',
|
name: 'bill.field.exchange_rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'Note',
|
name: 'bill.field.note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
open: {
|
open: {
|
||||||
name: 'Open',
|
name: 'bill.field.open',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
entries: {
|
entries: {
|
||||||
name: 'Entries',
|
name: 'bill.field.entries',
|
||||||
fieldType: 'collection',
|
fieldType: 'collection',
|
||||||
collectionOf: 'object',
|
collectionOf: 'object',
|
||||||
collectionMinLength: 1,
|
collectionMinLength: 1,
|
||||||
required: true,
|
required: true,
|
||||||
fields: {
|
fields: {
|
||||||
itemId: {
|
itemId: {
|
||||||
name: 'Item',
|
name: 'bill.field.item',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Item',
|
relationModel: 'Item',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the item name or code.',
|
importHint: 'bill.field.item_hint',
|
||||||
},
|
},
|
||||||
rate: {
|
rate: {
|
||||||
name: 'Rate',
|
name: 'bill.field.rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
quantity: {
|
quantity: {
|
||||||
name: 'Quantity',
|
name: 'bill.field.quantity',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'Line Description',
|
name: 'bill.field.description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
branchId: {
|
branchId: {
|
||||||
name: 'Branch',
|
name: 'invoice.field.branch',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Branch',
|
relationModel: 'Branch',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
@@ -261,7 +261,7 @@ export const BillMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
warehouseId: {
|
warehouseId: {
|
||||||
name: 'Warehouse',
|
name: 'invoice.field.warehouse',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Warehouse',
|
relationModel: 'Warehouse',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export class Bill extends TenantBaseModel {
|
|||||||
public updatedAt: Date | null;
|
public updatedAt: Date | null;
|
||||||
|
|
||||||
public entries?: ItemEntry[];
|
public entries?: ItemEntry[];
|
||||||
|
public attachments!: Document[];
|
||||||
public locatedLandedCosts?: BillLandedCost[];
|
public locatedLandedCosts?: BillLandedCost[];
|
||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
@@ -633,7 +634,7 @@ export class Bill extends TenantBaseModel {
|
|||||||
|
|
||||||
return this.query(trx)
|
return this.query(trx)
|
||||||
.where('id', billId)
|
.where('id', billId)
|
||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[changeMethod]('payment_amount', Math.abs(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Transformer } from '@/modules/Transformer/Transformer';
|
import { Transformer } from '@/modules/Transformer/Transformer';
|
||||||
import { Bill } from '../models/Bill';
|
import { Bill } from '../models/Bill';
|
||||||
|
import { ItemEntryTransformer } from '@/modules/TransactionItemEntry/ItemEntry.transformer';
|
||||||
|
import { AttachmentTransformer } from '@/modules/Attachments/Attachment.transformer';
|
||||||
|
|
||||||
export class BillTransformer extends Transformer {
|
export class BillTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -94,6 +96,7 @@ export class BillTransformer extends Transformer {
|
|||||||
protected formattedDueAmount = (bill: Bill): string => {
|
protected formattedDueAmount = (bill: Bill): string => {
|
||||||
return this.formatNumber(bill.dueAmount, {
|
return this.formatNumber(bill.dueAmount, {
|
||||||
currencyCode: bill.currencyCode,
|
currencyCode: bill.currencyCode,
|
||||||
|
money: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -169,6 +172,7 @@ export class BillTransformer extends Transformer {
|
|||||||
protected totalFormatted = (bill: Bill): string => {
|
protected totalFormatted = (bill: Bill): string => {
|
||||||
return this.formatNumber(bill.total, {
|
return this.formatNumber(bill.total, {
|
||||||
currencyCode: bill.currencyCode,
|
currencyCode: bill.currencyCode,
|
||||||
|
money: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,20 +233,18 @@ export class BillTransformer extends Transformer {
|
|||||||
/**
|
/**
|
||||||
* Retrieves the entries of the bill.
|
* Retrieves the entries of the bill.
|
||||||
* @param {Bill} credit
|
* @param {Bill} credit
|
||||||
* @returns {}
|
|
||||||
*/
|
*/
|
||||||
// protected entries = (bill: Bill) => {
|
protected entries = (bill: Bill) => {
|
||||||
// return this.item(bill.entries, new ItemEntryTransformer(), {
|
return this.item(bill.entries, new ItemEntryTransformer(), {
|
||||||
// currencyCode: bill.currencyCode,
|
currencyCode: bill.currencyCode,
|
||||||
// });
|
});
|
||||||
// };
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the bill attachments.
|
* Retrieves the bill attachments.
|
||||||
* @param {ISaleInvoice} invoice
|
* @param {Bill} bill
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
// protected attachments = (bill: Bill) => {
|
protected attachments = (bill: Bill) => {
|
||||||
// return this.item(bill.attachments, new AttachmentTransformer());
|
return this.item(bill.attachments, new AttachmentTransformer());
|
||||||
// };
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Query,
|
Query,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Patch,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
|
||||||
@@ -27,7 +27,7 @@ export class ContactsController {
|
|||||||
return this.getAutoCompleteService.autocompleteContacts(query);
|
return this.getAutoCompleteService.autocompleteContacts(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/activate')
|
@Patch(':id/activate')
|
||||||
@ApiOperation({ summary: 'Activate a contact' })
|
@ApiOperation({ summary: 'Activate a contact' })
|
||||||
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
|
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
|
||||||
async activateContact(@Param('id', ParseIntPipe) contactId: number) {
|
async activateContact(@Param('id', ParseIntPipe) contactId: number) {
|
||||||
@@ -35,7 +35,7 @@ export class ContactsController {
|
|||||||
return { id: contactId, activated: true };
|
return { id: contactId, activated: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/inactivate')
|
@Patch(':id/inactivate')
|
||||||
@ApiOperation({ summary: 'Inactivate a contact' })
|
@ApiOperation({ summary: 'Inactivate a contact' })
|
||||||
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
|
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
|
||||||
async inactivateContact(@Param('id', ParseIntPipe) contactId: number) {
|
async inactivateContact(@Param('id', ParseIntPipe) contactId: number) {
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.s
|
|||||||
import { CreditNoteRefundsController } from './CreditNoteRefunds.controller';
|
import { CreditNoteRefundsController } from './CreditNoteRefunds.controller';
|
||||||
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
|
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
|
||||||
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
|
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
|
||||||
|
import { RefundCreditNoteGLEntries } from './commands/RefundCreditNoteGLEntries';
|
||||||
|
import { RefundCreditNoteGLEntriesSubscriber } from '../CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber';
|
||||||
|
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||||
|
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [forwardRef(() => CreditNotesModule)],
|
imports: [forwardRef(() => CreditNotesModule), LedgerModule, AccountsModule],
|
||||||
providers: [
|
providers: [
|
||||||
CreateRefundCreditNoteService,
|
CreateRefundCreditNoteService,
|
||||||
DeleteRefundCreditNoteService,
|
DeleteRefundCreditNoteService,
|
||||||
@@ -17,8 +21,10 @@ import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.serv
|
|||||||
RefundSyncCreditNoteBalanceService,
|
RefundSyncCreditNoteBalanceService,
|
||||||
CreditNotesRefundsApplication,
|
CreditNotesRefundsApplication,
|
||||||
GetCreditNoteRefundsService,
|
GetCreditNoteRefundsService,
|
||||||
|
RefundCreditNoteGLEntries,
|
||||||
|
RefundCreditNoteGLEntriesSubscriber,
|
||||||
],
|
],
|
||||||
exports: [RefundSyncCreditNoteBalanceService],
|
exports: [RefundSyncCreditNoteBalanceService],
|
||||||
controllers: [CreditNoteRefundsController],
|
controllers: [CreditNoteRefundsController],
|
||||||
})
|
})
|
||||||
export class CreditNoteRefundsModule {}
|
export class CreditNoteRefundsModule { }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
|
import { ToNumber, IsOptional } from '@/common/decorators/Validators';
|
||||||
|
import { IsDateString, IsNotEmpty, IsPositive, IsString } from 'class-validator';
|
||||||
import { IsDate } from 'class-validator';
|
import { IsDate } from 'class-validator';
|
||||||
import { IsNumber } from 'class-validator';
|
import { IsNumber } from 'class-validator';
|
||||||
|
|
||||||
@@ -10,8 +11,13 @@ export class CreditNoteRefundDto {
|
|||||||
description: 'The id of the from account',
|
description: 'The id of the from account',
|
||||||
example: 1,
|
example: 1,
|
||||||
})
|
})
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The id of the from account',
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
fromAccountId: number;
|
fromAccountId: number;
|
||||||
|
|
||||||
|
@ToNumber()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@@ -21,6 +27,7 @@ export class CreditNoteRefundDto {
|
|||||||
})
|
})
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
||||||
|
@ToNumber()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@@ -30,23 +37,23 @@ export class CreditNoteRefundDto {
|
|||||||
})
|
})
|
||||||
exchangeRate?: number;
|
exchangeRate?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The reference number of the credit note refund',
|
description: 'The reference number of the credit note refund',
|
||||||
example: '123456',
|
example: '123456',
|
||||||
})
|
})
|
||||||
referenceNo: string;
|
referenceNo: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The description of the credit note refund',
|
description: 'The description of the credit note refund',
|
||||||
example: 'Credit note refund',
|
example: 'Credit note refund',
|
||||||
})
|
})
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@IsDate()
|
@IsDateString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The date of the credit note refund',
|
description: 'The date of the credit note refund',
|
||||||
@@ -54,6 +61,7 @@ export class CreditNoteRefundDto {
|
|||||||
})
|
})
|
||||||
date: Date;
|
date: Date;
|
||||||
|
|
||||||
|
@ToNumber()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
|
|||||||
@@ -164,73 +164,73 @@ export const CreditNoteMeta = {
|
|||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
customerId: {
|
customerId: {
|
||||||
name: 'Customer',
|
name: 'credit_note.field.customer',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Contact',
|
relationModel: 'Contact',
|
||||||
relationImportMatch: 'displayName',
|
relationImportMatch: 'displayName',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
exchangeRate: {
|
exchangeRate: {
|
||||||
name: 'Exchange Rate',
|
name: 'credit_note.field.exchange_rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
creditNoteDate: {
|
creditNoteDate: {
|
||||||
name: 'Credit Note Date',
|
name: 'credit_note.field.credit_note_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'Reference No.',
|
name: 'credit_note.field.reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'Note',
|
name: 'credit_note.field.note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
termsConditions: {
|
termsConditions: {
|
||||||
name: 'Terms & Conditions',
|
name: 'credit_note.field.terms_conditions',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
creditNoteNumber: {
|
creditNoteNumber: {
|
||||||
name: 'Credit Note Number',
|
name: 'credit_note.field.credit_note_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
open: {
|
open: {
|
||||||
name: 'Open',
|
name: 'credit_note.field.open',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
entries: {
|
entries: {
|
||||||
name: 'Entries',
|
name: 'credit_note.field.entries',
|
||||||
fieldType: 'collection',
|
fieldType: 'collection',
|
||||||
collectionOf: 'object',
|
collectionOf: 'object',
|
||||||
collectionMinLength: 1,
|
collectionMinLength: 1,
|
||||||
fields: {
|
fields: {
|
||||||
itemId: {
|
itemId: {
|
||||||
name: 'Item',
|
name: 'credit_note.field.item',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Item',
|
relationModel: 'Item',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the item name or code.',
|
importHint: 'invoice.field.item_hint',
|
||||||
},
|
},
|
||||||
rate: {
|
rate: {
|
||||||
name: 'Rate',
|
name: 'credit_note.field.rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
quantity: {
|
quantity: {
|
||||||
name: 'Quantity',
|
name: 'credit_note.field.quantity',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'Description',
|
name: 'credit_note.field.description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
branchId: {
|
branchId: {
|
||||||
name: 'Branch',
|
name: 'invoice.field.branch',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Branch',
|
relationModel: 'Branch',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
@@ -238,7 +238,7 @@ export const CreditNoteMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
warehouseId: {
|
warehouseId: {
|
||||||
name: 'Warehouse',
|
name: 'invoice.field.warehouse',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Warehouse',
|
relationModel: 'Warehouse',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedSubtotal = (credit): string => {
|
protected formattedSubtotal = (credit): string => {
|
||||||
return this.formatNumber(credit.amount, { money: false });
|
return this.formatNumber(credit.amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,7 +130,7 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected adjustmentFormatted = (credit): string => {
|
protected adjustmentFormatted = (credit): string => {
|
||||||
return this.formatMoney(credit.adjustment, {
|
return this.formatNumber(credit.adjustment, {
|
||||||
currencyCode: credit.currencyCode,
|
currencyCode: credit.currencyCode,
|
||||||
excerptZero: true,
|
excerptZero: true,
|
||||||
});
|
});
|
||||||
@@ -156,6 +156,7 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
protected totalFormatted = (credit): string => {
|
protected totalFormatted = (credit): string => {
|
||||||
return this.formatNumber(credit.total, {
|
return this.formatNumber(credit.total, {
|
||||||
currencyCode: credit.currencyCode,
|
currencyCode: credit.currencyCode,
|
||||||
|
money: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -167,6 +168,7 @@ export class CreditNoteTransformer extends Transformer {
|
|||||||
protected totalLocalFormatted = (credit): string => {
|
protected totalLocalFormatted = (credit): string => {
|
||||||
return this.formatNumber(credit.totalLocal, {
|
return this.formatNumber(credit.totalLocal, {
|
||||||
currencyCode: credit.currencyCode,
|
currencyCode: credit.currencyCode,
|
||||||
|
money: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { renderCreditNotePaperTemplateHtml } from '@bigcapital/pdf-templates';
|
||||||
import { GetCreditNoteService } from './GetCreditNote.service';
|
import { GetCreditNoteService } from './GetCreditNote.service';
|
||||||
import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service';
|
import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service';
|
||||||
import { transformCreditNoteToPdfTemplate } from '../utils';
|
import { transformCreditNoteToPdfTemplate } from '../utils';
|
||||||
import { CreditNote } from '../models/CreditNote';
|
import { CreditNote } from '../models/CreditNote';
|
||||||
import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service';
|
import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service';
|
||||||
import { TemplateInjectable } from '@/modules/TemplateInjectable/TemplateInjectable.service';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
|
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
|
||||||
import { CreditNotePdfTemplateAttributes } from '../types/CreditNotes.types';
|
import { CreditNotePdfTemplateAttributes } from '../types/CreditNotes.types';
|
||||||
@@ -15,7 +15,6 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|||||||
export class GetCreditNotePdf {
|
export class GetCreditNotePdf {
|
||||||
/**
|
/**
|
||||||
* @param {ChromiumlyTenancy} chromiumlyTenancy - Chromiumly tenancy service.
|
* @param {ChromiumlyTenancy} chromiumlyTenancy - Chromiumly tenancy service.
|
||||||
* @param {TemplateInjectable} templateInjectable - Template injectable service.
|
|
||||||
* @param {GetCreditNote} getCreditNoteService - Get credit note service.
|
* @param {GetCreditNote} getCreditNoteService - Get credit note service.
|
||||||
* @param {CreditNoteBrandingTemplate} creditNoteBrandingTemplate - Credit note branding template service.
|
* @param {CreditNoteBrandingTemplate} creditNoteBrandingTemplate - Credit note branding template service.
|
||||||
* @param {EventEmitter2} eventPublisher - Event publisher service.
|
* @param {EventEmitter2} eventPublisher - Event publisher service.
|
||||||
@@ -24,7 +23,6 @@ export class GetCreditNotePdf {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
||||||
private readonly templateInjectable: TemplateInjectable,
|
|
||||||
private readonly getCreditNoteService: GetCreditNoteService,
|
private readonly getCreditNoteService: GetCreditNoteService,
|
||||||
private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate,
|
private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate,
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
@@ -36,23 +34,40 @@ export class GetCreditNotePdf {
|
|||||||
private readonly pdfTemplateModel: TenantModelProxy<
|
private readonly pdfTemplateModel: TenantModelProxy<
|
||||||
typeof PdfTemplateModel
|
typeof PdfTemplateModel
|
||||||
>,
|
>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves sale invoice pdf content.
|
* Retrieves credit note html content.
|
||||||
|
* @param {number} creditNoteId - Credit note id.
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public async getCreditNoteHtml(creditNoteId: number): Promise<string> {
|
||||||
|
const brandingAttributes =
|
||||||
|
await this.getCreditNoteBrandingAttributes(creditNoteId);
|
||||||
|
|
||||||
|
// Map attributes to match the React component props
|
||||||
|
// The branding template returns companyLogoUri, but type may have companyLogo
|
||||||
|
const props = {
|
||||||
|
...brandingAttributes,
|
||||||
|
companyLogoUri:
|
||||||
|
(brandingAttributes as any).companyLogoUri ||
|
||||||
|
(brandingAttributes as any).companyLogo ||
|
||||||
|
'',
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderCreditNotePaperTemplateHtml(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves credit note pdf content.
|
||||||
* @param {number} creditNoteId - Credit note id.
|
* @param {number} creditNoteId - Credit note id.
|
||||||
* @returns {Promise<[Buffer, string]>}
|
* @returns {Promise<[Buffer, string]>}
|
||||||
*/
|
*/
|
||||||
public async getCreditNotePdf(
|
public async getCreditNotePdf(
|
||||||
creditNoteId: number,
|
creditNoteId: number,
|
||||||
): Promise<[Buffer, string]> {
|
): Promise<[Buffer, string]> {
|
||||||
const brandingAttributes =
|
|
||||||
await this.getCreditNoteBrandingAttributes(creditNoteId);
|
|
||||||
const htmlContent = await this.templateInjectable.render(
|
|
||||||
'modules/credit-note-standard',
|
|
||||||
brandingAttributes,
|
|
||||||
);
|
|
||||||
const filename = await this.getCreditNoteFilename(creditNoteId);
|
const filename = await this.getCreditNoteFilename(creditNoteId);
|
||||||
|
const htmlContent = await this.getCreditNoteHtml(creditNoteId);
|
||||||
|
|
||||||
const document =
|
const document =
|
||||||
await this.chromiumlyTenancy.convertHtmlContent(htmlContent);
|
await this.chromiumlyTenancy.convertHtmlContent(htmlContent);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class DynamicListService {
|
|||||||
private dynamicListSearch: DynamicListSearch,
|
private dynamicListSearch: DynamicListSearch,
|
||||||
private dynamicListSortBy: DynamicListSortBy,
|
private dynamicListSortBy: DynamicListSortBy,
|
||||||
private dynamicListView: DynamicListCustomView,
|
private dynamicListView: DynamicListCustomView,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses filter DTO.
|
* Parses filter DTO.
|
||||||
@@ -31,9 +31,9 @@ export class DynamicListService {
|
|||||||
// Merges the default properties with filter object.
|
// Merges the default properties with filter object.
|
||||||
...(model.defaultSort
|
...(model.defaultSort
|
||||||
? {
|
? {
|
||||||
sortOrder: model.defaultSort.sortOrder,
|
sortOrder: model.defaultSort.sortOrder,
|
||||||
columnSortBy: model.defaultSort.sortOrder,
|
columnSortBy: model.defaultSort.sortOrder,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...filterDTO,
|
...filterDTO,
|
||||||
};
|
};
|
||||||
@@ -93,7 +93,7 @@ export class DynamicListService {
|
|||||||
* Parses stringified filter roles.
|
* Parses stringified filter roles.
|
||||||
* @param {string} stringifiedFilterRoles - Stringified filter roles.
|
* @param {string} stringifiedFilterRoles - Stringified filter roles.
|
||||||
*/
|
*/
|
||||||
public parseStringifiedFilter<T extends IDynamicListFilter>(
|
public parseStringifiedFilter<T extends { stringifiedFilterRoles?: string }>(
|
||||||
filterRoles: T,
|
filterRoles: T,
|
||||||
): T {
|
): T {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { ExpensesSampleData } from './constants';
|
|||||||
import { CreateExpense } from './commands/CreateExpense.service';
|
import { CreateExpense } from './commands/CreateExpense.service';
|
||||||
import { CreateExpenseDto } from './dtos/Expense.dto';
|
import { CreateExpenseDto } from './dtos/Expense.dto';
|
||||||
import { ImportableService } from '../Import/decorators/Import.decorator';
|
import { ImportableService } from '../Import/decorators/Import.decorator';
|
||||||
import { ManualJournal } from '../ManualJournals/models/ManualJournal';
|
import { Expense } from './models/Expense.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ImportableService({ name: ManualJournal.name })
|
@ImportableService({ name: Expense.name })
|
||||||
export class ExpensesImportable extends Importable {
|
export class ExpensesImportable extends Importable {
|
||||||
constructor(private readonly createExpenseService: CreateExpense) {
|
constructor(private readonly createExpenseService: CreateExpense) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const ExpenseMeta = {
|
|||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'account.field.account_hint',
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'expense.field.reference_no',
|
name: 'expense.field.reference_no',
|
||||||
@@ -169,7 +169,7 @@ export const ExpenseMeta = {
|
|||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
required: true,
|
required: true,
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'account.field.account_hint',
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
name: 'expense.field.amount',
|
name: 'expense.field.amount',
|
||||||
@@ -187,7 +187,7 @@ export const ExpenseMeta = {
|
|||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
branchId: {
|
branchId: {
|
||||||
name: 'Branch',
|
name: 'invoice.field.branch',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Branch',
|
relationModel: 'Branch',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy.service';
|
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy.service';
|
||||||
import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable.service';
|
import { renderExportResourceTableTemplateHtml } from '@bigcapital/pdf-templates';
|
||||||
import { mapPdfRows } from './utils';
|
import { mapPdfRows } from './utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportPdf {
|
export class ExportPdf {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly templateInjectable: TemplateInjectable,
|
|
||||||
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the pdf table sheet for the given data and columns.
|
* Generates the pdf table sheet for the given data and columns.
|
||||||
@@ -19,21 +18,18 @@ export class ExportPdf {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public async pdf(
|
public async pdf(
|
||||||
columns: { accessor: string },
|
columns: { accessor: string; name?: string; style?: string; group?: string }[],
|
||||||
data: Record<string, any>,
|
data: Record<string, any>,
|
||||||
sheetTitle: string = '',
|
sheetTitle: string = '',
|
||||||
sheetDescription: string = ''
|
sheetDescription: string = ''
|
||||||
) {
|
) {
|
||||||
const rows = mapPdfRows(columns, data);
|
const rows = mapPdfRows(columns, data);
|
||||||
|
|
||||||
const htmlContent = await this.templateInjectable.render(
|
const htmlContent = renderExportResourceTableTemplateHtml({
|
||||||
'modules/export-resource-table',
|
table: { rows, columns },
|
||||||
{
|
sheetTitle,
|
||||||
table: { rows, columns },
|
sheetDescription,
|
||||||
sheetTitle,
|
});
|
||||||
sheetDescription,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Convert the HTML content to PDF
|
// Convert the HTML content to PDF
|
||||||
return this.chromiumlyTenancy.convertHtmlContent(htmlContent, {
|
return this.chromiumlyTenancy.convertHtmlContent(htmlContent, {
|
||||||
margins: { top: 0.2, bottom: 0.2, left: 0.2, right: 0.2 },
|
margins: { top: 0.2, bottom: 0.2, left: 0.2, right: 0.2 },
|
||||||
|
|||||||
@@ -3,29 +3,30 @@ import { ITableColumn, ITableData, ITableRow } from '../types/Table.types';
|
|||||||
import { FinancialTableStructure } from './FinancialTableStructure';
|
import { FinancialTableStructure } from './FinancialTableStructure';
|
||||||
import { tableClassNames } from '../utils';
|
import { tableClassNames } from '../utils';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TemplateInjectable } from '../../TemplateInjectable/TemplateInjectable.service';
|
|
||||||
import { ChromiumlyTenancy } from '../../ChromiumlyTenancy/ChromiumlyTenancy.service';
|
import { ChromiumlyTenancy } from '../../ChromiumlyTenancy/ChromiumlyTenancy.service';
|
||||||
|
import { renderFinancialSheetTemplateHtml } from '@bigcapital/pdf-templates';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TableSheetPdf {
|
export class TableSheetPdf {
|
||||||
/**
|
/**
|
||||||
* @param {TemplateInjectable} templateInjectable - The template injectable service.
|
|
||||||
* @param {ChromiumlyTenancy} chromiumlyTenancy - The chromiumly tenancy service.
|
* @param {ChromiumlyTenancy} chromiumlyTenancy - The chromiumly tenancy service.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly templateInjectable: TemplateInjectable,
|
|
||||||
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
private readonly chromiumlyTenancy: ChromiumlyTenancy,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the table data into a PDF format.
|
* Converts the table data into a PDF format.
|
||||||
* @param {ITableData} table - The table data to be converted.
|
* @param {ITableData} table - The table data to be converted.
|
||||||
|
* @param {string} organizationName - The organization name.
|
||||||
* @param {string} sheetName - The name of the sheet.
|
* @param {string} sheetName - The name of the sheet.
|
||||||
* @param {string} sheetDate - The date of the sheet.
|
* @param {string} sheetDate - The date of the sheet.
|
||||||
|
* @param {string} customCSS - Optional custom CSS to inject.
|
||||||
* @returns A promise that resolves with the PDF conversion result.
|
* @returns A promise that resolves with the PDF conversion result.
|
||||||
*/
|
*/
|
||||||
public async convertToPdf(
|
public async convertToPdf(
|
||||||
table: ITableData,
|
table: ITableData,
|
||||||
|
organizationName: string,
|
||||||
sheetName: string,
|
sheetName: string,
|
||||||
sheetDate: string,
|
sheetDate: string,
|
||||||
customCSS?: string,
|
customCSS?: string,
|
||||||
@@ -33,19 +34,26 @@ export class TableSheetPdf {
|
|||||||
// Prepare columns and rows for PDF conversion
|
// Prepare columns and rows for PDF conversion
|
||||||
const columns = this.tablePdfColumns(table.columns);
|
const columns = this.tablePdfColumns(table.columns);
|
||||||
const rows = this.tablePdfRows(table.rows);
|
const rows = this.tablePdfRows(table.rows);
|
||||||
|
|
||||||
const landscape = columns.length > 4;
|
const landscape = columns.length > 4;
|
||||||
|
|
||||||
// Generate HTML content from the template
|
// Generate HTML content from the React template
|
||||||
const htmlContent = await this.templateInjectable.render(
|
const htmlContent = renderFinancialSheetTemplateHtml({
|
||||||
'financial-sheet',
|
organizationName,
|
||||||
{
|
sheetName,
|
||||||
table: { rows, columns },
|
sheetDate,
|
||||||
sheetName,
|
table: {
|
||||||
sheetDate,
|
columns: columns.map((col) => ({
|
||||||
customCSS,
|
key: col.key,
|
||||||
|
label: col.label,
|
||||||
|
style: (col as any).style, // style may be added during transformation
|
||||||
|
})),
|
||||||
|
rows: rows.map((row) => ({
|
||||||
|
cells: row.cells,
|
||||||
|
classNames: (row as any).classNames,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
);
|
customCSS,
|
||||||
|
});
|
||||||
// Convert the HTML content to PDF
|
// Convert the HTML content to PDF
|
||||||
return this.chromiumlyTenancy.convertHtmlContent(htmlContent, {
|
return this.chromiumlyTenancy.convertHtmlContent(htmlContent, {
|
||||||
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||||
@@ -74,7 +82,6 @@ export class TableSheetPdf {
|
|||||||
const flatNestedTree = curriedFlatNestedTree(R.__, {
|
const flatNestedTree = curriedFlatNestedTree(R.__, {
|
||||||
nestedPrefix: '<span style="padding-left: 15px;"></span>',
|
nestedPrefix: '<span style="padding-left: 15px;"></span>',
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return R.compose(tableClassNames, flatNestedTree)(rows);
|
return R.compose(tableClassNames, flatNestedTree)(rows);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
|||||||
@ApiTags('Reports')
|
@ApiTags('Reports')
|
||||||
@ApiCommonHeaders()
|
@ApiCommonHeaders()
|
||||||
export class APAgingSummaryController {
|
export class APAgingSummaryController {
|
||||||
constructor(private readonly APAgingSummaryApp: APAgingSummaryApplication) {}
|
constructor(private readonly APAgingSummaryApp: APAgingSummaryApplication) { }
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: 'Get payable aging summary' })
|
@ApiOperation({ summary: 'Get payable aging summary' })
|
||||||
@@ -37,12 +37,13 @@ export class APAgingSummaryController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.APAgingSummaryApp.table(filter);
|
return this.APAgingSummaryApp.table(filter);
|
||||||
|
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const csv = await this.APAgingSummaryApp.csv(filter);
|
const csv = await this.APAgingSummaryApp.csv(filter);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -50,7 +51,7 @@ export class APAgingSummaryController {
|
|||||||
|
|
||||||
res.send(csv);
|
res.send(csv);
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.APAgingSummaryApp.xlsx(filter);
|
const buffer = await this.APAgingSummaryApp.xlsx(filter);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -60,7 +61,7 @@ export class APAgingSummaryController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.APAgingSummaryApp.pdf(filter);
|
const pdfContent = await this.APAgingSummaryApp.pdf(filter);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class APAgingSummaryPdfInjectable {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedAsDate,
|
table.meta.formattedAsDate,
|
||||||
HtmlTableCss,
|
HtmlTableCss,
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ export class ARAgingSummaryController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.ARAgingSummaryApp.xlsx(filter);
|
const buffer = await this.ARAgingSummaryApp.xlsx(filter);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -49,11 +50,11 @@ export class ARAgingSummaryController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the table format.
|
// Retrieves the table format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
} else if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.ARAgingSummaryApp.table(filter);
|
return this.ARAgingSummaryApp.table(filter);
|
||||||
|
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.ARAgingSummaryApp.csv(filter);
|
const buffer = await this.ARAgingSummaryApp.csv(filter);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -61,7 +62,7 @@ export class ARAgingSummaryController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.ARAgingSummaryApp.pdf(filter);
|
const pdfContent = await this.ARAgingSummaryApp.pdf(filter);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class ARAgingSummaryPdfInjectable {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCss,
|
HtmlTableCss,
|
||||||
|
|||||||
@@ -43,13 +43,14 @@ export class BalanceSheetStatementController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
const table = await this.balanceSheetApp.table(query);
|
const table = await this.balanceSheetApp.table(query);
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.balanceSheetApp.csv(query);
|
const buffer = await this.balanceSheetApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -57,7 +58,7 @@ export class BalanceSheetStatementController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.balanceSheetApp.xlsx(query);
|
const buffer = await this.balanceSheetApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -67,7 +68,7 @@ export class BalanceSheetStatementController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.balanceSheetApp.pdf(query);
|
const pdfContent = await this.balanceSheetApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class BalanceSheetPdfInjectable {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly balanceSheetTable: BalanceSheetTableInjectable,
|
private readonly balanceSheetTable: BalanceSheetTableInjectable,
|
||||||
private readonly tableSheetPdf: TableSheetPdf,
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given balance sheet table to pdf.
|
* Converts the given balance sheet table to pdf.
|
||||||
@@ -21,6 +21,7 @@ export class BalanceSheetPdfInjectable {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
|||||||
})
|
})
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => NumberFormatQueryDto)
|
@Type(() => NumberFormatQueryDto)
|
||||||
|
@IsOptional()
|
||||||
numberFormat: NumberFormatQueryDto;
|
numberFormat: NumberFormatQueryDto;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
|||||||
@ApiTags('Reports')
|
@ApiTags('Reports')
|
||||||
@ApiCommonHeaders()
|
@ApiCommonHeaders()
|
||||||
export class CashflowController {
|
export class CashflowController {
|
||||||
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {}
|
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) { }
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
@@ -37,11 +37,12 @@ export class CashflowController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.cashflowSheetApp.table(query);
|
return this.cashflowSheetApp.table(query);
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.cashflowSheetApp.csv(query);
|
const buffer = await this.cashflowSheetApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -49,7 +50,7 @@ export class CashflowController {
|
|||||||
|
|
||||||
res.status(200).send(buffer);
|
res.status(200).send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.cashflowSheetApp.xlsx(query);
|
const buffer = await this.cashflowSheetApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -59,7 +60,7 @@ export class CashflowController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.cashflowSheetApp.pdf(query);
|
const pdfContent = await this.cashflowSheetApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export class CashflowTablePdfInjectable {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ export class CustomerBalanceSummaryController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.customerBalanceSummaryApp.xlsx(filter);
|
const buffer = await this.customerBalanceSummaryApp.xlsx(filter);
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
@@ -44,18 +45,18 @@ export class CustomerBalanceSummaryController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.customerBalanceSummaryApp.csv(filter);
|
const buffer = await this.customerBalanceSummaryApp.csv(filter);
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
res.setHeader('Content-Type', 'text/csv');
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
} else if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.customerBalanceSummaryApp.table(filter);
|
return this.customerBalanceSummaryApp.table(filter);
|
||||||
|
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const buffer = await this.customerBalanceSummaryApp.pdf(filter);
|
const buffer = await this.customerBalanceSummaryApp.pdf(filter);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class CustomerBalanceSummaryPdf {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ export class GeneralLedgerController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the table format.
|
// Retrieves the table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.generalLedgerApplication.table(query);
|
return this.generalLedgerApplication.table(query);
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.generalLedgerApplication.csv(query);
|
const buffer = await this.generalLedgerApplication.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -51,7 +52,7 @@ export class GeneralLedgerController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.generalLedgerApplication.xlsx(query);
|
const buffer = await this.generalLedgerApplication.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -61,7 +62,7 @@ export class GeneralLedgerController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.generalLedgerApplication.pdf(query);
|
const pdfContent = await this.generalLedgerApplication.pdf(query);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class GeneralLedgerPdf {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export class InventoryItemDetailsController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
const accept = acceptHeader || '';
|
||||||
|
if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.inventoryItemDetailsApp.csv(query);
|
const buffer = await this.inventoryItemDetailsApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -29,7 +30,7 @@ export class InventoryItemDetailsController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.inventoryItemDetailsApp.xlsx(query);
|
const buffer = await this.inventoryItemDetailsApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -39,10 +40,10 @@ export class InventoryItemDetailsController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
} else if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.inventoryItemDetailsApp.table(query);
|
return this.inventoryItemDetailsApp.table(query);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const buffer = await this.inventoryItemDetailsApp.pdf(query);
|
const buffer = await this.inventoryItemDetailsApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class InventoryItemDetailsQueryDto {
|
|||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'Number format for the inventory item details',
|
description: 'Number format for the inventory item details',
|
||||||
})
|
})
|
||||||
numberFormat: INumberFormatQuery;
|
numberFormat: NumberFormatQueryDto;
|
||||||
|
|
||||||
@Transform(({ value }) => parseBoolean(value, false))
|
@Transform(({ value }) => parseBoolean(value, false))
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class InventoryDetailsTablePdf {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ export class InventoryValuationController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.inventoryValuationApp.table(query);
|
return this.inventoryValuationApp.table(query);
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.inventoryValuationApp.csv(query);
|
const buffer = await this.inventoryValuationApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -49,7 +50,7 @@ export class InventoryValuationController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the xslx buffer format.
|
// Retrieves the xslx buffer format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.inventoryValuationApp.xlsx(query);
|
const buffer = await this.inventoryValuationApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -59,7 +60,7 @@ export class InventoryValuationController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the pdf format.
|
// Retrieves the pdf format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.inventoryValuationApp.pdf(query);
|
const pdfContent = await this.inventoryValuationApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export class InventoryValuationSheet extends FinancialSheet {
|
|||||||
* Detarmines whether the items post filter is active.
|
* Detarmines whether the items post filter is active.
|
||||||
*/
|
*/
|
||||||
private isItemsPostFilter = (): boolean => {
|
private isItemsPostFilter = (): boolean => {
|
||||||
return isEmpty(this.query.itemsIds);
|
return !isEmpty(this.query.itemsIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export class InventoryValuationSheetPdf {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class InventoryValuationSheetService {
|
|||||||
private readonly inventoryValuationMeta: InventoryValuationMetaInjectable,
|
private readonly inventoryValuationMeta: InventoryValuationMetaInjectable,
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
private readonly inventoryValuationSheetRepository: InventoryValuationSheetRepository,
|
private readonly inventoryValuationSheetRepository: InventoryValuationSheetRepository,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inventory valuation sheet.
|
* Inventory valuation sheet.
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ export class JournalSheetController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.journalSheetApp.table(query);
|
return this.journalSheetApp.table(query);
|
||||||
|
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
} else if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const buffer = await this.journalSheetApp.csv(query);
|
const buffer = await this.journalSheetApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -50,7 +51,7 @@ export class JournalSheetController {
|
|||||||
|
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const buffer = await this.journalSheetApp.xlsx(query);
|
const buffer = await this.journalSheetApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -60,7 +61,7 @@ export class JournalSheetController {
|
|||||||
);
|
);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
// Retrieves the json format.
|
// Retrieves the json format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.journalSheetApp.pdf(query);
|
const pdfContent = await this.journalSheetApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
import { JournalSheetRepository } from './JournalSheetRepository';
|
import { JournalSheetRepository } from './JournalSheetRepository';
|
||||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils';
|
||||||
|
|
||||||
export class JournalSheet extends FinancialSheet {
|
export class JournalSheet extends FinancialSheet {
|
||||||
readonly query: IJournalReportQuery;
|
readonly query: IJournalReportQuery;
|
||||||
@@ -97,10 +98,13 @@ export class JournalSheet extends FinancialSheet {
|
|||||||
|
|
||||||
transactionType: groupEntry.transactionType,
|
transactionType: groupEntry.transactionType,
|
||||||
referenceId: groupEntry.transactionId,
|
referenceId: groupEntry.transactionId,
|
||||||
referenceTypeFormatted: this.i18n.t(groupEntry.transactionType),
|
referenceTypeFormatted: this.i18n.t(
|
||||||
|
getTransactionTypeLabel(
|
||||||
|
groupEntry.transactionType,
|
||||||
|
groupEntry.transactionSubType,
|
||||||
|
),
|
||||||
|
),
|
||||||
entries: this.entriesMapper(entriesGroup),
|
entries: this.entriesMapper(entriesGroup),
|
||||||
|
|
||||||
currencyCode: this.baseCurrency,
|
currencyCode: this.baseCurrency,
|
||||||
|
|
||||||
credit: totalCredit,
|
credit: totalCredit,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class JournalSheetPdfInjectable {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly journalSheetTable: JournalSheetTableInjectable,
|
private readonly journalSheetTable: JournalSheetTableInjectable,
|
||||||
private readonly tableSheetPdf: TableSheetPdf,
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given journal sheet table to pdf.
|
* Converts the given journal sheet table to pdf.
|
||||||
@@ -22,6 +22,7 @@ export class JournalSheetPdfInjectable {
|
|||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
return this.tableSheetPdf.convertToPdf(
|
||||||
table.table,
|
table.table,
|
||||||
|
table.meta.organizationName,
|
||||||
table.meta.sheetName,
|
table.meta.sheetName,
|
||||||
table.meta.formattedDateRange,
|
table.meta.formattedDateRange,
|
||||||
HtmlTableCustomCss,
|
HtmlTableCustomCss,
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ export class ProfitLossSheetController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Headers('accept') acceptHeader: string,
|
@Headers('accept') acceptHeader: string,
|
||||||
) {
|
) {
|
||||||
|
const accept = acceptHeader || '';
|
||||||
// Retrieves the csv format.
|
// Retrieves the csv format.
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
if (accept.includes(AcceptType.ApplicationCsv)) {
|
||||||
const sheet = await this.profitLossSheetApp.csv(query);
|
const sheet = await this.profitLossSheetApp.csv(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
@@ -54,11 +55,11 @@ export class ProfitLossSheetController {
|
|||||||
|
|
||||||
res.send(sheet);
|
res.send(sheet);
|
||||||
// Retrieves the json table format.
|
// Retrieves the json table format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
} else if (accept.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
return this.profitLossSheetApp.table(query);
|
return this.profitLossSheetApp.table(query);
|
||||||
|
|
||||||
// Retrieves the xlsx format.
|
// Retrieves the xlsx format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
} else if (accept.includes(AcceptType.ApplicationXlsx)) {
|
||||||
const sheet = await this.profitLossSheetApp.xlsx(query);
|
const sheet = await this.profitLossSheetApp.xlsx(query);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
@@ -68,7 +69,7 @@ export class ProfitLossSheetController {
|
|||||||
);
|
);
|
||||||
res.send(sheet);
|
res.send(sheet);
|
||||||
// Retrieves the json format.
|
// Retrieves the json format.
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
} else if (accept.includes(AcceptType.ApplicationPdf)) {
|
||||||
const pdfContent = await this.profitLossSheetApp.pdf(query);
|
const pdfContent = await this.profitLossSheetApp.pdf(query);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user