Compare commits

..

6 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
04b16a7d2f Publish
- @bigcapital/server@0.10.1
 - @bigcapital/webapp@0.10.2
2023-10-03 22:07:42 +02:00
Ahmed Bouhuolia
fd27136274 tst 2023-10-03 22:06:33 +02:00
Ahmed Bouhuolia
951e360405 chore(webapp): add missing packages 2023-10-03 22:05:57 +02:00
Ahmed Bouhuolia
d2242b7744 WIP 2023-10-03 12:11:55 +02:00
Ahmed Bouhuolia
4e9b0ae24f fix: migrate from npm to pnpm 2023-09-29 15:54:47 +02:00
Ahmed Bouhuolia
e5ffb5661a feat: migrate to pnpm 2023-09-27 22:44:54 +02:00
602 changed files with 6140 additions and 36880 deletions

View File

@@ -60,51 +60,6 @@
"contributions": [ "contributions": [
"bug" "bug"
] ]
},
{
"login": "kochie",
"name": "Robert Koch",
"avatar_url": "https://avatars.githubusercontent.com/u/10809884?v=4",
"profile": "https://me.kochie.io",
"contributions": [
"code"
]
},
{
"login": "cschuijt",
"name": "Casper Schuijt",
"avatar_url": "https://avatars.githubusercontent.com/u/5460015?v=4",
"profile": "http://cschuijt.nl",
"contributions": [
"bug"
]
},
{
"login": "ANasouf",
"name": "ANasouf",
"avatar_url": "https://avatars.githubusercontent.com/u/19536487?v=4",
"profile": "https://github.com/ANasouf",
"contributions": [
"code"
]
},
{
"login": "xprnio",
"name": "Ragnar Laud",
"avatar_url": "https://avatars.githubusercontent.com/u/3042904?v=4",
"profile": "https://ragnarlaud.dev",
"contributions": [
"bug"
]
},
{
"login": "asenawritescode",
"name": "Asena",
"avatar_url": "https://avatars.githubusercontent.com/u/67445192?v=4",
"profile": "https://github.com/asenawritescode",
"contributions": [
"bug"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -8,7 +8,7 @@ MAIL_FROM_NAME=
MAIL_FROM_ADDRESS= MAIL_FROM_ADDRESS=
# Database # Database
DB_HOST=localhost DB_HOST=mysql
DB_USER=bigcapital DB_USER=bigcapital
DB_PASSWORD=bigcapital DB_PASSWORD=bigcapital
DB_ROOT_PASSWORD=root DB_ROOT_PASSWORD=root
@@ -49,18 +49,4 @@ SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS= SIGNUP_ALLOWED_EMAILS=
# API rate limit (points,duration,block duration). # API rate limit (points,duration,block duration).
API_RATE_LIMIT=120,60,600 API_RATE_LIMIT=120,60,600
# Gotenberg API for PDF printing - (production).
GOTENBERG_URL=http://gotenberg:3000
GOTENBERG_DOCS_URL=http://server:3000/public/
# Gotenberg API - (development)
# GOTENBERG_URL=http://localhost:9000
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
# Exchange Rate Service
EXCHANGE_RATE_SERVICE=open-exchange-rate
# Open Exchange Rate
OPEN_EXCHANGE_RATE_APP_ID=

View File

@@ -1,22 +0,0 @@
tasks:
- name: Init
init: |
pnpm install &&
cp .env.example .env &&
docker-compose up -d &&
pnpm run build:server &&
node packages/server/build/commands.js system:migrate:latest
command: |
docker-compose up -d &&
pnpm run dev
ports:
- port: 4000
visibility: public
onOpen: open-preview
- port: 3000
visibility: public
onOpen: ignore
- port: 3306
visibility: public
onOpen: ignore

View File

@@ -2,69 +2,11 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
## [0.13.3] - 22-01-2024 # [0.10.1] - 25-09-2023
* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329
## [0.13.2] - 21-01-2024
* feat: show customer / vendor balance. by @asenawritescode in https://github.com/bigcapitalhq/bigcapital/pull/311
* feat: inventory valuation csv and xlsx export by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/308
* feat: sales by items export csv & xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/310
* fix(server): the invoice and payment receipt printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/315
* fix: get cashflow transaction broken cause transaction type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/318
* fix: `AccountActivateAlert` import by @xprnio in https://github.com/bigcapitalhq/bigcapital/pull/322
## [0.13.1] - 15-01-2024
* feat(webapp): add approve/reject to action bar of estimate details dr… by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/304
* docs: add ANasouf as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/305
* feat: Export general ledger & Journal to CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/303
* feat: Auto re-calculate the items rate once changing the invoice exchange rate. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/270
## [0.13.0] - 31-12-2023
* feat: Send an invoice mail the customer email by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/292
* fix: Allow non-numeric postal codes by @cschuijt in https://github.com/bigcapitalhq/bigcapital/pull/294
* docs: add cschuijt as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/295
## [0.12.1] - 17-11-2023
* feat: Add default customer message and terms conditions to the transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/291
* fix: The currency code of transaction tax rate entry by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/293
## [0.12.0] - 04-11-2023
* feat: Export reports via CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/286
* fix: Axios upgrade by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/288
* fix(server): Allow decimal amount in sale/purchase transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/289
* feat: Optimize invoice documents printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/280
* chore(deps): bump axios from 0.20.0 to 1.6.0 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/284
* chore(deps): bump axios from 0.20.0 to 1.6.0 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/283
## [0.11.0] - 28-10-2023
* feat: Migrate to pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/253
* feat: Integrate tax rates to bills by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/260
* feat: Assign default sell/purchase tax rates to items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/261
* chore(deps-dev): bump @babel/traverse from 7.23.0 to 7.23.2 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/272
* feat: Improve financial statements rows color by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/276
* fix: Trial balance sheet adjusted balance by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/273
* feat: Adds tax numbers to organization and customers by @kochie in https://github.com/bigcapitalhq/bigcapital/pull/269
* docs: Add kochie as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/277
* feat: Computed Net Income under Equity in Balance Sheet report. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/271
* fix: Change Dockerfile files with new pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/278
## [0.10.2] - 02-10-2023
fix(webapp): Disable tax rates from item entries editor table services do not support tax rates (https://github.com/bigcapitalhq/bigcapital/commit/69afa07e3ba45495a4cab3490c15f2b0c40c4790) by @abouolia
fix(server): Add missing method in ItemEntry model (https://github.com/bigcapitalhq/bigcapital/commit/07628ddc37f46c98959ced0323f28752e0a98944) by @abouolia
## [0.10.1] - 25-09-2023
* Fix: Running tenants migration on Docker migration container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/242 * Fix: Running tenants migration on Docker migration container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/242
## [0.10.0] - 24-09-2023 # [0.10.0] - 24-09-2023
* Added: Tax rates service by @abouolia @elforjani13 in https://github.com/bigcapitalhq/bigcapital/pull/204 * Added: Tax rates service by @abouolia @elforjani13 in https://github.com/bigcapitalhq/bigcapital/pull/204
* Added: Sales Tax Liability Summary report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/204 * Added: Sales Tax Liability Summary report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/204
@@ -74,7 +16,7 @@ fix(server): Add missing method in ItemEntry model (https://github.com/bigcapita
* chore(deps): bump word-wrap from 1.2.3 to 1.2.4 in /packages/webapp by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/199 * chore(deps): bump word-wrap from 1.2.3 to 1.2.4 in /packages/webapp by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/199
* chore(deps): bump mongoose from 5.13.15 to 5.13.20 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/197 * chore(deps): bump mongoose from 5.13.15 to 5.13.20 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/197
## [0.9.12] - 29-08-2023 # [0.9.12] - 29-08-2023
* Refactor: split the services to multiple service classes. (by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/202) * Refactor: split the services to multiple service classes. (by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/202)
* Fix: create quick customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/206 * Fix: create quick customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/206
@@ -88,18 +30,18 @@ fix(server): Add missing method in ItemEntry model (https://github.com/bigcapita
* Fix: filter by customers, vendors and items in reports do not work by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/224 * Fix: filter by customers, vendors and items in reports do not work by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/224
https://github.com/bigcapitalhq/bigcapital/pull/225 https://github.com/bigcapitalhq/bigcapital/pull/225
## [0.9.11] - 23-07-2023 # [0.9.11] - 23-07-2023
* added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198 * added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198
* fix: Expose and expand the rate limit to the env variables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/195 * fix: Expose and expand the rate limit to the env variables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/195
## [0.9.10] - 18-07-2023 # [0.9.10] - 18-07-2023
* feat(e2e): E2E onboarding process by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/176 * feat(e2e): E2E onboarding process by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/176
* fix(webapp): Show loading message of cost computing job on financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/196 * fix(webapp): Show loading message of cost computing job on financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/196
* fix(webapp): Change the currency code of sales and purchases transactions with foreign contacts. * fix(webapp): Change the currency code of sales and purchases transactions with foreign contacts.
## [0.9.9] - 28-06-2023 # [0.9.9] - 28-06-2023
* refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171 * refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170 * chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
@@ -111,7 +53,7 @@ https://github.com/bigcapitalhq/bigcapital/pull/225
* fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179 * fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179
* fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182 * fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182
## [0.9.8] - 19-06-2023 # [0.9.8] - 19-06-2023
`bigcapitalhq/webapp` `bigcapitalhq/webapp`

View File

@@ -33,8 +33,7 @@ Contributions via pull requests are much appreciated. Once the approach is agree
--- ---
## Local Setup Prerequisites ## Local Setup Prerequisites
- The application currently supports **Node.js v18.x**. - The application currently supports **Node.js v14.x**. Please ensure that you are using this version of Node.js when developing. (use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) to switch between node versions)
- `pnpm` packages manager, (from pnpm [guide](https://pnpm.io/installation) pick any installation method).
## Contribute to Backend ## Contribute to Backend
@@ -45,10 +44,11 @@ Contributions via pull requests are much appreciated. Once the approach is agree
cp .env.example .env cp .env.example .env
``` ```
- Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit the command on root directory and it will install dependencies of all packages. - Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit these command on root directory and it will install dependencies of all packages.
``` ```
pnpm install npm install
npm run bootstrap
``` ```
- Run all required docker containers in the development, we already configured all containers under `docker-compose.yml`. - Run all required docker containers in the development, we already configured all containers under `docker-compose.yml`.
@@ -69,7 +69,7 @@ cefa73fe2881 bigcapital-redis "docker-entrypoint.s…" 7 seconds ago Up
- There're some CLI commands we should run before running the server like databaase migration, so we need to build the `server` app first. - There're some CLI commands we should run before running the server like databaase migration, so we need to build the `server` app first.
``` ```
pnpm run build:server npm run build:server
``` ```
- Run the database migration for system database. - Run the database migration for system database.
@@ -87,7 +87,7 @@ Batch 1 run: 6 migrations
- Next, start the webapp application. - Next, start the webapp application.
``` ```
pnpm run dev:server npm run dev:server
``` ```
**[`^top^`](#)** **[`^top^`](#)**
@@ -105,13 +105,14 @@ git clone https://github.com/bigcapital/bigcapital.git && cd bigcaptial
- Install all npm dependencies of the monorepo, you don't have to change directory to the `frontend` package. just hit that command and will install all packages across all application. - Install all npm dependencies of the monorepo, you don't have to change directory to the `frontend` package. just hit that command and will install all packages across all application.
``` ```
pnpm install npm install
npm run bootstrap
``` ```
- Next, start the webapp application. - Next, start the webapp application.
``` ```
pnpm run dev:webapp npm run dev:webapp
``` ```
**[`^top^`](#)** **[`^top^`](#)**

View File

@@ -37,37 +37,6 @@ Bigcapital is a smart and open-source accounting and inventory software, Bigcapi
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/screenshot-3.png" width="270"> <img src="https://raw.githubusercontent.com/abouolia/blog/main/public/screenshot-3.png" width="270">
</p> </p>
# Getting Started
We've got serveral options on dev and prod depending on your need to get started quickly with Bigcapital.
## Self-hosted
Bigcapital is available open-source under AGPL license. You can host it on your own servers using Docker.
### Docker
To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.ly/deployment/docker).
## Development
### Local Setup
To get started locally, we have a [guide to help you](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md).
### Gitpod
- Click the Gitpod button below to open this project in development mode.
- This will open and configure the workspace in your browser with all the necessary dependencies.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/new/#https://github.com/bigcapitalhq/bigcapital)
## Headless Accounting
You can integrate Bigcapital API with your system to organize your transactions in double-entry system to get the best financial reports.
[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/bigcapital/workspace/bigcapital-api)
# Resources # Resources
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use. - [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
@@ -111,13 +80,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/suhaibaffan"><img src="https://avatars.githubusercontent.com/u/18115937?v=4?s=100" width="100px;" alt="Suhaib Affan"/><br /><sub><b>Suhaib Affan</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=suhaibaffan" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/suhaibaffan"><img src="https://avatars.githubusercontent.com/u/18115937?v=4?s=100" width="100px;" alt="Suhaib Affan"/><br /><sub><b>Suhaib Affan</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=suhaibaffan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KalliopiPliogka"><img src="https://avatars.githubusercontent.com/u/81677549?v=4?s=100" width="100px;" alt="Kalliopi Pliogka"/><br /><sub><b>Kalliopi Pliogka</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3AKalliopiPliogka" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/KalliopiPliogka"><img src="https://avatars.githubusercontent.com/u/81677549?v=4?s=100" width="100px;" alt="Kalliopi Pliogka"/><br /><sub><b>Kalliopi Pliogka</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3AKalliopiPliogka" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://me.kochie.io"><img src="https://avatars.githubusercontent.com/u/10809884?v=4?s=100" width="100px;" alt="Robert Koch"/><br /><sub><b>Robert Koch</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=kochie" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://cschuijt.nl"><img src="https://avatars.githubusercontent.com/u/5460015?v=4?s=100" width="100px;" alt="Casper Schuijt"/><br /><sub><b>Casper Schuijt</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Acschuijt" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ANasouf"><img src="https://avatars.githubusercontent.com/u/19536487?v=4?s=100" width="100px;" alt="ANasouf"/><br /><sub><b>ANasouf</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=ANasouf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ragnarlaud.dev"><img src="https://avatars.githubusercontent.com/u/3042904?v=4?s=100" width="100px;" alt="Ragnar Laud"/><br /><sub><b>Ragnar Laud</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Axprnio" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -35,8 +35,6 @@ services:
server: server:
container_name: bigcapital-server container_name: bigcapital-server
image: ghcr.io/bigcapitalhq/server:latest image: ghcr.io/bigcapitalhq/server:latest
expose:
- '3000'
links: links:
- mysql - mysql
- mongo - mongo
@@ -88,10 +86,6 @@ services:
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS} - SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS} - SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
# Gotenberg (Pdf generator)
- GOTENBERG_URL=${GOTENBERG_URL}
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
database_migration: database_migration:
container_name: bigcapital-database-migration container_name: bigcapital-database-migration
build: build:
@@ -149,11 +143,6 @@ services:
volumes: volumes:
- redis:/data - redis:/data
gotenberg:
image: gotenberg/gotenberg:7
expose:
- '9000'
# Volumes # Volumes
volumes: volumes:
mysql: mysql:

View File

@@ -47,11 +47,6 @@ services:
restart_policy: restart_policy:
condition: unless-stopped condition: unless-stopped
gotenberg:
image: gotenberg/gotenberg:7
ports:
- "9000:3000"
# Volumes # Volumes
volumes: volumes:
mysql: mysql:

View File

@@ -2,6 +2,5 @@
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent", "version": "independent",
"npmClient": "pnpm", "npmClient": "pnpm",
"useWorkspaces": true,
"packages": ["packages/*"] "packages": ["packages/*"]
} }

View File

@@ -2,6 +2,7 @@
"name": "bigcapital-monorepo", "name": "bigcapital-monorepo",
"private": true, "private": true,
"scripts": { "scripts": {
"bootstrap": "lerna exec pnpm install",
"dev": "lerna run dev", "dev": "lerna run dev",
"build": "lerna run build", "build": "lerna run build",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"", "dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
@@ -22,7 +23,7 @@
"lerna": "^6.4.1" "lerna": "^6.4.1"
}, },
"engines": { "engines": {
"node": "16.x || 17.x || 18.x" "node": "14.x"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@@ -1,5 +1,6 @@
/node_modules/ /node_modules/
/.env /.env
/storage
stdout.log stdout.log
/dist /dist
/build /build

View File

@@ -1,4 +1,4 @@
FROM node:18.16.0-alpine as build FROM node:14.20-alpine as build
USER root USER root
@@ -83,25 +83,15 @@ WORKDIR /app
RUN chown node:node / RUN chown node:node /
# Install pnpm
RUN npm install -g pnpm
# Copy application dependency manifests to the container image. # Copy application dependency manifests to the container image.
COPY ./package*.json ./ COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./lerna.json ./lerna.json
COPY ./packages/server/package*.json ./packages/server/ COPY ./packages/server/package*.json ./packages/server/
# Install application dependencies COPY ./lerna.json ./lerna.json
RUN apk update
RUN apk add python3 build-base chromium
# Set PYHTON env # Install app dependencies for production.
ENV PYTHON=/usr/bin/python3 RUN npm install
RUN npm run bootstrap
# Install packages dependencies for production.
RUN pnpm install
COPY --chown=node:node ./packages/server ./packages/server COPY --chown=node:node ./packages/server ./packages/server

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bigcapital/server", "name": "@bigcapital/server",
"version": "0.10.2", "version": "0.10.1",
"description": "", "description": "",
"main": "src/server.ts", "main": "src/server.ts",
"scripts": { "scripts": {
@@ -31,7 +31,7 @@
"agendash": "^3.1.0", "agendash": "^3.1.0",
"app-root-path": "^3.0.0", "app-root-path": "^3.0.0",
"async": "^3.2.0", "async": "^3.2.0",
"axios": "^1.6.0", "axios": "^0.20.0",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
@@ -43,7 +43,7 @@
"crypto-random-string": "^3.2.0", "crypto-random-string": "^3.2.0",
"csurf": "^1.10.0", "csurf": "^1.10.0",
"deep-map": "^2.0.0", "deep-map": "^2.0.0",
"deepdash": "^5.3.9", "deepdash": "^5.3.7",
"dotenv": "^8.1.0", "dotenv": "^8.1.0",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"es6-weak-map": "^2.0.3", "es6-weak-map": "^2.0.3",
@@ -56,7 +56,6 @@
"express-fileupload": "^1.1.7-alpha.3", "express-fileupload": "^1.1.7-alpha.3",
"express-oauth-server": "^2.0.0", "express-oauth-server": "^2.0.0",
"express-validator": "^6.12.2", "express-validator": "^6.12.2",
"form-data": "^4.0.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-sass": "^5.0.0", "gulp-sass": "^5.0.0",
"helmet": "^3.21.0", "helmet": "^3.21.0",
@@ -96,14 +95,11 @@
"rate-limiter-flexible": "^2.1.14", "rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4", "rtl-detect": "^1.0.4",
"source-map-loader": "^4.0.1",
"tmp-promise": "^3.0.3",
"ts-transformer-keys": "^0.4.2", "ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0", "tsyringe": "^4.3.0",
"typedi": "^0.8.0", "typedi": "^0.8.0",
"uniqid": "^5.2.0", "uniqid": "^5.2.0",
"winston": "^3.2.1", "winston": "^3.2.1"
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.158", "@types/lodash": "^4.14.158",

View File

@@ -176,7 +176,6 @@
"invoice.paper.conditions_title": "Conditions & terms", "invoice.paper.conditions_title": "Conditions & terms",
"invoice.paper.notes_title": "Notes", "invoice.paper.notes_title": "Notes",
"invoice.paper.total": "Total", "invoice.paper.total": "Total",
"invoice.paper.subtotal": "Subtotal",
"invoice.paper.payment_amount": "Payment Amount", "invoice.paper.payment_amount": "Payment Amount",
"invoice.paper.balance_due": "Balance Due", "invoice.paper.balance_due": "Balance Due",
@@ -588,7 +587,6 @@
"balance_sheet.long_term_liabilities": "Long-Term Liabilities", "balance_sheet.long_term_liabilities": "Long-Term Liabilities",
"balance_sheet.non_current_liabilities": "Non-Current Liabilities", "balance_sheet.non_current_liabilities": "Non-Current Liabilities",
"balance_sheet.equity": "Equity", "balance_sheet.equity": "Equity",
"balance_sheet.net_income": "Net Income",
"balance_sheet.account_name": "Account name", "balance_sheet.account_name": "Account name",
"balance_sheet.total": "Total", "balance_sheet.total": "Total",

View File

@@ -137,14 +137,10 @@
tbody tr.payment-amount td:last-child { tbody tr.payment-amount td:last-child {
color: red color: red
} }
tbody tr.blanace-due td{ tbody tr.blanace-due td {
border-top: 3px double #666; border-top: 3px double #666;
font-weight: bold; font-weight: bold;
} }
tbody tr.total td {
border-top: 1px solid #666;
font-weight: bold;
}
} }
} }

View File

@@ -22,12 +22,12 @@ block content
div.invoice__due-amount div.invoice__due-amount
div.label #{__('invoice.paper.invoice_amount')} div.label #{__('invoice.paper.invoice_amount')}
div.amount #{saleInvoice.totalFormatted} div.amount #{saleInvoice.formattedAmount}
div.invoice__meta div.invoice__meta
div.invoice__meta-item.invoice__meta-item--amount div.invoice__meta-item.invoice__meta-item--amount
span.label #{__('invoice.paper.due_amount')} span.label #{__('invoice.paper.due_amount')}
span.value #{saleInvoice.dueAmountFormatted} span.value #{saleInvoice.formattedDueAmount}
div.invoice__meta-item.invoice__meta-item--billed-to div.invoice__meta-item.invoice__meta-item--billed-to
span.label #{__("invoice.paper.billed_to")} span.label #{__("invoice.paper.billed_to")}
@@ -35,11 +35,11 @@ block content
div.invoice__meta-item.invoice__meta-item--invoice-date div.invoice__meta-item.invoice__meta-item--invoice-date
span.label #{__("invoice.paper.invoice_date")} span.label #{__("invoice.paper.invoice_date")}
span.value #{saleInvoice.invoiceDateFormatted} span.value #{saleInvoice.formattedInvoiceDate}
div.invoice__meta-item.invoice__meta-item--due-date div.invoice__meta-item.invoice__meta-item--due-date
span.label #{__("invoice.paper.due_date")} span.label #{__("invoice.paper.due_date")}
span.value #{saleInvoice.dueDateFormatted} span.value #{saleInvoice.formattedDueDate}
div.invoice__table div.invoice__table
table table
@@ -63,22 +63,15 @@ block content
div.invoice__table-total div.invoice__table-total
table table
tbody tbody
tr.subtotal
td #{__('invoice.paper.subtotal')}
td #{saleInvoice.subtotalFormatted}
each tax in saleInvoice.taxes
tr.tax_line
td #{tax.name} [#{tax.taxRate}%]
td #{tax.taxRateAmountFormatted}
tr.total tr.total
td #{__('invoice.paper.total')} td #{__('invoice.paper.total')}
td #{saleInvoice.totalFormatted} td #{saleInvoice.formattedAmount}
tr.payment-amount tr.payment-amount
td #{__('invoice.paper.payment_amount')} td #{__('invoice.paper.payment_amount')}
td #{saleInvoice.paymentAmountFormatted} td #{saleInvoice.formattedPaymentAmount}
tr.blanace-due tr.blanace-due
td #{__('invoice.paper.balance_due')} td #{__('invoice.paper.balance_due')}
td #{saleInvoice.dueAmountFormatted} td #{saleInvoice.formattedDueAmount}
div.invoice__footer div.invoice__footer
if saleInvoice.termsConditions if saleInvoice.termsConditions

View File

@@ -45,9 +45,9 @@ block content
each entry in paymentReceive.entries each entry in paymentReceive.entries
tr tr
td.item=entry.invoice.invoiceNo td.item=entry.invoice.invoiceNo
td.date=entry.invoice.invoiceDateFormatted td.date=entry.invoice.formattedInvoiceDate
td.invoiceAmount=entry.invoice.totalFormatted td.invoiceAmount=entry.invoice.formattedAmount
td.paymentAmount=entry.invoice.paymentAmountFormatted td.paymentAmount=entry.invoice.formattedPaymentAmount
div.payment__table-after div.payment__table-after
div.payment__table-total div.payment__table-total

View File

@@ -1,16 +1,19 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, oneOf } from 'express-validator'; import { check, param, query } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from './BaseController'; import BaseController from './BaseController';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { EchangeRateErrors } from '@/lib/ExchangeRate/types'; import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService';
import { ExchangeRateApplication } from '@/services/ExchangeRates/ExchangeRateApplication'; import DynamicListingService from '@/services/DynamicListing/DynamicListService';
@Service() @Service()
export default class ExchangeRatesController extends BaseController { export default class ExchangeRatesController extends BaseController {
@Inject() @Inject()
private exchangeRatesApp: ExchangeRateApplication; exchangeRatesService: ExchangeRatesService;
@Inject()
dynamicListService: DynamicListingService;
/** /**
* Constructor method. * Constructor method.
@@ -19,40 +22,164 @@ export default class ExchangeRatesController extends BaseController {
const router = Router(); const router = Router();
router.get( router.get(
'/latest', '/',
[ [...this.exchangeRatesListSchema],
oneOf([
query('to_currency').exists().isString().isISO4217(),
query('from_currency').exists().isString().isISO4217(),
]),
],
this.validationResult, this.validationResult,
asyncMiddleware(this.latestExchangeRate.bind(this)), asyncMiddleware(this.exchangeRates.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.handleServiceError,
);
router.post(
'/',
[...this.exchangeRateDTOSchema],
this.validationResult,
asyncMiddleware(this.addExchangeRate.bind(this)),
this.handleServiceError
);
router.post(
'/:id',
[...this.exchangeRateEditDTOSchema, ...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.editExchangeRate.bind(this)),
this.handleServiceError
);
router.delete(
'/:id',
[...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.deleteExchangeRate.bind(this)),
this.handleServiceError this.handleServiceError
); );
return router; return router;
} }
get exchangeRatesListSchema() {
return [
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']),
];
}
get exchangeRateDTOSchema() {
return [
check('exchange_rate').exists().isNumeric().toFloat(),
check('currency_code').exists().trim().escape(),
check('date').exists().isISO8601(),
];
}
get exchangeRateEditDTOSchema() {
return [check('exchange_rate').exists().isNumeric().toFloat()];
}
get exchangeRateIdSchema() {
return [param('id').isNumeric().toInt()];
}
get exchangeRatesIdsSchema() {
return [
query('ids').isArray({ min: 2 }),
query('ids.*').isNumeric().toInt(),
];
}
/** /**
* Retrieve exchange rates. * Retrieve exchange rates.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private async latestExchangeRate( async exchangeRates(req: Request, res: Response, next: NextFunction) {
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const exchangeRateQuery = this.matchedQueryData(req); const filter = {
page: 1,
pageSize: 12,
filterRoles: [],
columnSortBy: 'created_at',
sortOrder: 'asc',
...this.matchedQueryData(req),
};
if (filter.stringifiedFilterRoles) {
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
}
try {
const exchangeRates = await this.exchangeRatesService.listExchangeRates(
tenantId,
filter
);
return res.status(200).send({ exchange_rates: exchangeRates });
} catch (error) {
next(error);
}
}
/**
* Adds a new exchange rate on the given date.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async addExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const exchangeRateDTO = this.matchedBodyData(req);
try { try {
const exchangeRate = await this.exchangeRatesApp.latest( const exchangeRate = await this.exchangeRatesService.newExchangeRate(
tenantId, tenantId,
exchangeRateQuery exchangeRateDTO
); );
return res.status(200).send(exchangeRate); return res.status(200).send({ id: exchangeRate.id });
} catch (error) {
next(error);
}
}
/**
* Edit the given exchange rate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async editExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
const exchangeRateDTO = this.matchedBodyData(req);
try {
const exchangeRate = await this.exchangeRatesService.editExchangeRate(
tenantId,
exchangeRateId,
exchangeRateDTO
);
return res.status(200).send({
id: exchangeRateId,
message: 'The exchange rate has been edited successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Delete the given exchange rate from the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async deleteExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
try {
await this.exchangeRatesService.deleteExchangeRate(
tenantId,
exchangeRateId
);
return res.status(200).send({ id: exchangeRateId });
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -65,56 +192,26 @@ export default class ExchangeRatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private handleServiceError( handleServiceError(
error: Error, error: Error,
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) { if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') {
return res.status(400).send({ return res.status(404).send({
errors: [ errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
{
type: EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
code: 100,
message: 'The given base currency is invalid.',
},
],
}); });
} else if ( }
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') {
) {
return res.status(400).send({ return res.status(400).send({
errors: [ errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }],
{
type: EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
code: 200,
message: 'The service is not allowed',
},
],
}); });
} else if ( }
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED === error.errorType if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
) {
return res.status(400).send({ return res.status(400).send({
errors: [ errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }],
{
type: EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
code: 300,
message: 'The API key is required',
},
],
});
} else if (EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED === error.errorType) {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED,
code: 400,
message: 'The API rate limit has been exceeded',
},
],
}); });
} }
} }

View File

@@ -2,20 +2,19 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator'; import { query } from 'express-validator';
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import APAgingSummaryReportService from '@/services/FinancialStatements/AgingSummary/APAgingSummaryService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { APAgingSummaryApplication } from '@/services/FinancialStatements/AgingSummary/APAgingSummaryApplication';
export default class APAgingSummaryReportController extends BaseFinancialReportController { export default class APAgingSummaryReportController extends BaseFinancialReportController {
@Inject() @Inject()
private APAgingSummaryApp: APAgingSummaryApplication; APAgingSummaryService: APAgingSummaryReportService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -29,9 +28,8 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
/** /**
* Validation schema. * Validation schema.
* @returns {ValidationChain[]}
*/ */
private get validationSchema() { get validationSchema() {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(), query('as_date').optional().isISO8601(),
@@ -51,58 +49,42 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
} }
/** /**
* Retrieves payable aging summary report. * Retrieve payable aging summary report.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/ */
private async payableAgingSummary( async payableAgingSummary(req: Request, res: Response, next: NextFunction) {
req: Request, const { tenantId, settings } = req;
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.APAgingSummaryApp.table(tenantId, filter);
return res.status(200).send(table); switch (acceptType) {
// Retrieves the csv format. case 'application/json+table':
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { const table = await this.APAgingSummaryService.APAgingSummaryTable(
const csv = await this.APAgingSummaryApp.csv(tenantId, filter); tenantId,
filter
);
return res.status(200).send({
table: {
rows: table.rows,
columns: table.columns,
},
meta: table.meta,
query: table.query,
});
break;
default:
const { data, columns, query, meta } =
await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); return res.status(200).send({
res.setHeader('Content-Type', 'text/csv'); data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
return res.send(csv); query: this.transfromToResponse(query),
// Retrieves the xlsx format. meta: this.transfromToResponse(meta),
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { });
const buffer = await this.APAgingSummaryApp.xlsx(tenantId, filter); break;
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -5,18 +5,16 @@ import ARAgingSummaryService from '@/services/FinancialStatements/AgingSummary/A
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ARAgingSummaryApplication } from '@/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class ARAgingSummaryReportController extends BaseFinancialReportController { export default class ARAgingSummaryReportController extends BaseFinancialReportController {
@Inject() @Inject()
ARAgingSummaryApp: ARAgingSummaryApplication; ARAgingSummaryService: ARAgingSummaryService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -32,7 +30,7 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
/** /**
* AR aging summary validation roles. * AR aging summary validation roles.
*/ */
private get validationSchema() { get validationSchema() {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
@@ -54,53 +52,41 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
/** /**
* Retrieve AR aging summary report. * Retrieve AR aging summary report.
* @param {Request} req
* @param {Response} res
*/ */
private async receivableAgingSummary(req: Request, res: Response) { async receivableAgingSummary(req: Request, res: Response) {
const { tenantId } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
const acceptType = accept.types([ switch (acceptType) {
ACCEPT_TYPE.APPLICATION_JSON, case 'application/json+table':
ACCEPT_TYPE.APPLICATION_JSON_TABLE, const table = await this.ARAgingSummaryService.ARAgingSummaryTable(
ACCEPT_TYPE.APPLICATION_CSV, tenantId,
ACCEPT_TYPE.APPLICATION_XLSX, filter
]); );
// Retrieves the xlsx format. return res.status(200).send({
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { table: {
const buffer = await this.ARAgingSummaryApp.xlsx(tenantId, filter); rows: table.rows,
columns: table.columns,
},
meta: table.meta,
query: table.query,
});
break;
default:
const { data, columns, query, meta } =
await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
res.setHeader( return res.status(200).send({
'Content-Disposition', data: this.transfromToResponse(data),
'attachment; filename=output.xlsx' columns: this.transfromToResponse(columns),
); query: this.transfromToResponse(query),
res.setHeader( meta: this.transfromToResponse(meta),
'Content-Type', });
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' break;
);
return res.send(buffer);
// Retrieves the table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.ARAgingSummaryApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.ARAgingSummaryApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -3,21 +3,25 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BalanceSheetStatementService from '@/services/FinancialStatements/BalanceSheet/BalanceSheetService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { BalanceSheetApplication } from '@/services/FinancialStatements/BalanceSheet/BalanceSheetApplication'; import BalanceSheetTable from '@/services/FinancialStatements/BalanceSheet/BalanceSheetTable';
import { ACCEPT_TYPE } from '@/interfaces/Http'; import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service() @Service()
export default class BalanceSheetStatementController extends BaseFinancialReportController { export default class BalanceSheetStatementController extends BaseFinancialReportController {
@Inject() @Inject()
private balanceSheetApp: BalanceSheetApplication; balanceSheetService: BalanceSheetStatementService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -34,7 +38,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
* Balance sheet validation schecma. * Balance sheet validation schecma.
* @returns {ValidationChain[]} * @returns {ValidationChain[]}
*/ */
private get balanceSheetValidationSchema(): ValidationChain[] { get balanceSheetValidationSchema(): ValidationChain[] {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('accounting_method').optional().isIn(['cash', 'accrual']), query('accounting_method').optional().isIn(['cash', 'accrual']),
@@ -80,12 +84,10 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
/** /**
* Retrieve the balance sheet. * Retrieve the balance sheet.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/ */
private async balanceSheet(req: Request, res: Response, next: NextFunction) { async balanceSheet(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
const i18n = this.tenancy.i18n(tenantId);
let filter = this.matchedQueryData(req); let filter = this.matchedQueryData(req);
@@ -93,45 +95,29 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
...filter, ...filter,
accountsIds: castArray(filter.accountsIds), accountsIds: castArray(filter.accountsIds),
}; };
try { try {
const { data, columns, query, meta } =
await this.balanceSheetService.balanceSheet(tenantId, filter);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
const acceptType = accept.types([ const table = new BalanceSheetTable(data, query, i18n);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) {
const table = await this.balanceSheetApp.table(tenantId, filter);
return res.status(200).send(table); switch (acceptType) {
// Retrieves the csv format. case 'application/json+table':
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { return res.status(200).send({
const buffer = await this.balanceSheetApp.csv(tenantId, filter); table: {
rows: table.tableRows(),
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); columns: table.tableColumns(),
res.setHeader('Content-Type', 'text/csv'); },
query,
return res.send(buffer); meta,
// Retrieves the xlsx format. });
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { case 'json':
const buffer = await this.balanceSheetApp.xlsx(tenantId, filter); default:
return res.status(200).send({ data, columns, query, meta });
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
} else {
const sheet = await this.balanceSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -8,20 +8,29 @@ import {
ValidationChain, ValidationChain,
} from 'express'; } from 'express';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import CashFlowStatementService from '@/services/FinancialStatements/CashFlow/CashFlowService';
import {
ICashFlowStatementDOO,
ICashFlowStatement,
AbilitySubject,
ReportsAction,
} from '@/interfaces';
import CashFlowTable from '@/services/FinancialStatements/CashFlow/CashFlowTable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { CashflowSheetApplication } from '@/services/FinancialStatements/CashFlow/CashflowSheetApplication';
@Service() @Service()
export default class CashFlowController extends BaseFinancialReportController { export default class CashFlowController extends BaseFinancialReportController {
@Inject() @Inject()
private cashflowSheetApp: CashflowSheetApplication; cashFlowService: CashFlowStatementService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -38,7 +47,7 @@ export default class CashFlowController extends BaseFinancialReportController {
* Balance sheet validation schecma. * Balance sheet validation schecma.
* @returns {ValidationChain[]} * @returns {ValidationChain[]}
*/ */
private get cashflowValidationSchema(): ValidationChain[] { get cashflowValidationSchema(): ValidationChain[] {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('from_date').optional(), query('from_date').optional(),
@@ -58,6 +67,41 @@ export default class CashFlowController extends BaseFinancialReportController {
]; ];
} }
/**
* Retrieve the cashflow statment to json response.
* @param {ICashFlowStatement} cashFlow -
*/
private transformJsonResponse(cashFlowDOO: ICashFlowStatementDOO) {
const { data, query, meta } = cashFlowDOO;
return {
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
};
}
/**
* Transformes the report statement to table rows.
* @param {ITransactionsByVendorsStatement} statement -
*/
private transformToTableRows(
cashFlowDOO: ICashFlowStatementDOO,
tenantId: number
) {
const i18n = this.tenancy.i18n(tenantId);
const cashFlowTable = new CashFlowTable(cashFlowDOO, i18n);
return {
table: {
data: cashFlowTable.tableRows(),
columns: cashFlowTable.tableColumns(),
},
query: this.transfromToResponse(cashFlowDOO.query),
meta: this.transfromToResponse(cashFlowDOO.meta),
};
}
/** /**
* Retrieve the cash flow statment. * Retrieve the cash flow statment.
* @param {Request} req * @param {Request} req
@@ -65,52 +109,26 @@ export default class CashFlowController extends BaseFinancialReportController {
* @param {NextFunction} next * @param {NextFunction} next
* @returns {Response} * @returns {Response}
*/ */
public async cashFlow(req: Request, res: Response, next: NextFunction) { async cashFlow(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
const filter = { const filter = {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const cashFlow = await this.cashFlowService.cashFlow(tenantId, filter);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
const acceptType = accept.types([ switch (acceptType) {
ACCEPT_TYPE.APPLICATION_JSON, case 'application/json+table':
ACCEPT_TYPE.APPLICATION_JSON_TABLE, return res
ACCEPT_TYPE.APPLICATION_CSV, .status(200)
ACCEPT_TYPE.APPLICATION_XLSX, .send(this.transformToTableRows(cashFlow, tenantId));
]); case 'json':
// Retrieves the json table format. default:
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { return res.status(200).send(this.transformJsonResponse(cashFlow));
const table = await this.cashflowSheetApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.cashflowSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.status(200).send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.cashflowSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter);
return res.status(200).send(cashflow);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -1,21 +1,29 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator'; import { query } from 'express-validator';
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import {
AbilitySubject,
ICustomerBalanceSummaryStatement,
ReportsAction,
} from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import CustomerBalanceSummary from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import CustomerBalanceSummaryTableRows from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CustomerBalanceSummaryApplication } from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication';
export default class CustomerBalanceSummaryReportController extends BaseFinancialReportController { export default class CustomerBalanceSummaryReportController extends BaseFinancialReportController {
@Inject() @Inject()
private customerBalanceSummaryApp: CustomerBalanceSummaryApplication; customerBalanceSummaryService: CustomerBalanceSummary;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -34,7 +42,7 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia
/** /**
* Validation schema. * Validation schema.
*/ */
private get validationSchema() { get validationSchema() {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
@@ -54,67 +62,75 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia
]; ];
} }
/**
* Transformes the balance summary statement to table rows.
* @param {ICustomerBalanceSummaryStatement} statement -
*/
private transformToTableRows(
tenantId,
{ data, query }: ICustomerBalanceSummaryStatement
) {
const i18n = this.tenancy.i18n(tenantId);
const tableRows = new CustomerBalanceSummaryTableRows(data, query, i18n);
return {
table: {
columns: tableRows.tableColumns(),
data: tableRows.tableRows(),
},
query: this.transfromToResponse(query),
};
}
/**
* Transformes the balance summary statement to raw json.
* @param {ICustomerBalanceSummaryStatement} customerBalance -
*/
private transformToJsonResponse({
data,
columns,
query,
}: ICustomerBalanceSummaryStatement) {
return {
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
};
}
/** /**
* Retrieve payable aging summary report. * Retrieve payable aging summary report.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
private async customerBalanceSummary( async customerBalanceSummary(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
const { tenantId } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const customerBalanceSummary =
await this.customerBalanceSummaryService.customerBalanceSummary(
tenantId,
filter
);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the xlsx format. switch (acceptType) {
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { case 'application/json+table':
const buffer = await this.customerBalanceSummaryApp.xlsx( return res
tenantId, .status(200)
filter .send(this.transformToTableRows(tenantId, customerBalanceSummary));
); case 'application/json':
res.setHeader( default:
'Content-Disposition', return res
'attachment; filename=output.xlsx' .status(200)
); .send(this.transformToJsonResponse(customerBalanceSummary));
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.customerBalanceSummaryApp.csv(
tenantId,
filter
);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the json table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.customerBalanceSummaryApp.table(
tenantId,
filter
);
return res.status(200).send(table);
} else {
const sheet = await this.customerBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -2,16 +2,15 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication';
@Service() @Service()
export default class GeneralLedgerReportController extends BaseFinancialReportController { export default class GeneralLedgerReportController extends BaseFinancialReportController {
@Inject() @Inject()
private generalLedgerApplication: GeneralLedgerApplication; generalLedgetService: GeneralLedgerService;
/** /**
* Router constructor. * Router constructor.
@@ -62,43 +61,20 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
* @param {Response} res - * @param {Response} res -
*/ */
async generalLedger(req: Request, res: Response, next: NextFunction) { async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([ try {
ACCEPT_TYPE.APPLICATION_JSON, const { data, query, meta } =
ACCEPT_TYPE.APPLICATION_JSON_TABLE, await this.generalLedgetService.generalLedger(tenantId, filter);
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.generalLedgerApplication.table(tenantId, filter);
return res.status(200).send(table); return res.status(200).send({
// Retrieves the csv format. meta: this.transfromToResponse(meta),
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { data: this.transfromToResponse(data),
const buffer = await this.generalLedgerApplication.csv(tenantId, filter); query: this.transfromToResponse(query),
});
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); } catch (error) {
res.setHeader('Content-Type', 'text/csv'); next(error);
return res.send(buffer);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.generalLedgerApplication.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.generalLedgerApplication.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -8,20 +8,24 @@ import {
ValidationChain, ValidationChain,
} from 'express'; } from 'express';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import InventoryDetailsService from '@/services/FinancialStatements/InventoryDetails/InventoryDetailsService';
import InventoryDetailsTable from '@/services/FinancialStatements/InventoryDetails/InventoryDetailsTable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import { InventortyDetailsApplication } from '@/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class InventoryDetailsController extends BaseController { export default class InventoryDetailsController extends BaseController {
@Inject() @Inject()
private inventoryItemDetailsApp: InventortyDetailsApplication; inventoryDetailsService: InventoryDetailsService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -41,7 +45,7 @@ export default class InventoryDetailsController extends BaseController {
* Balance sheet validation schecma. * Balance sheet validation schecma.
* @returns {ValidationChain[]} * @returns {ValidationChain[]}
*/ */
private get validationSchema(): ValidationChain[] { get validationSchema(): ValidationChain[] {
return [ return [
query('number_format.precision') query('number_format.precision')
.optional() .optional()
@@ -73,66 +77,69 @@ export default class InventoryDetailsController extends BaseController {
} }
/** /**
* Retrieve the inventory item details sheet. * Retrieve the cashflow statment to json response.
* @param {ICashFlowStatement} cashFlow -
*/
private transformJsonResponse(inventoryDetails) {
const { data, query, meta } = inventoryDetails;
return {
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
};
}
/**
* Transformes the report statement to table rows.
*/
private transformToTableRows(inventoryDetails, tenantId: number) {
const i18n = this.tenancy.i18n(tenantId);
const inventoryDetailsTable = new InventoryDetailsTable(
inventoryDetails,
i18n
);
return {
table: {
data: inventoryDetailsTable.tableData(),
columns: inventoryDetailsTable.tableColumns(),
},
query: this.transfromToResponse(inventoryDetails.query),
meta: this.transfromToResponse(inventoryDetails.meta),
};
}
/**
* Retrieve the cash flow statment.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
* @returns {Response} * @returns {Response}
*/ */
private async inventoryDetails( async inventoryDetails(req: Request, res: Response, next: NextFunction) {
req: Request, const { tenantId, settings } = req;
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = { const filter = {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const inventoryDetails =
await this.inventoryDetailsService.inventoryDetails(tenantId, filter);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.inventoryItemDetailsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); switch (acceptType) {
res.setHeader('Content-Type', 'text/csv'); case 'application/json+table':
return res
return res.send(buffer); .status(200)
// Retrieves the xlsx format. .send(this.transformToTableRows(inventoryDetails, tenantId));
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) { case 'json':
const buffer = await this.inventoryItemDetailsApp.xlsx( default:
tenantId, return res
filter .status(200)
); .send(this.transformJsonResponse(inventoryDetails));
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json table format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const table = await this.inventoryItemDetailsApp.table(
tenantId,
filter
);
return res.status(200).send(table);
} else {
const sheet = await this.inventoryItemDetailsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -3,15 +3,14 @@ import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import InventoryValuationService from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { InventoryValuationSheetApplication } from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class InventoryValuationReportController extends BaseFinancialReportController { export default class InventoryValuationReportController extends BaseFinancialReportController {
@Inject() @Inject()
private inventoryValuationApp: InventoryValuationSheetApplication; inventoryValuationService: InventoryValuationService;
/** /**
* Router constructor. * Router constructor.
@@ -72,45 +71,19 @@ export default class InventoryValuationReportController extends BaseFinancialRep
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req); try {
const { data, query, meta } =
const acceptType = accept.types([ await this.inventoryValuationService.inventoryValuationSheet(
ACCEPT_TYPE.APPLICATION_JSON, tenantId,
ACCEPT_TYPE.APPLICATION_JSON_TABLE, filter
ACCEPT_TYPE.APPLICATION_XLSX, );
ACCEPT_TYPE.APPLICATION_CSV, return res.status(200).send({
]); meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
// Retrieves the json table format. query: this.transfromToResponse(query),
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { });
const table = await this.inventoryValuationApp.table(tenantId, filter); } catch (error) {
next(error);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV == acceptType) {
const buffer = await this.inventoryValuationApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xslx buffer format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.inventoryValuationApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const { data, query, meta } = await this.inventoryValuationApp.sheet(
tenantId,
filter
);
return res.status(200).send({ meta, data, query });
} }
} }
} }

View File

@@ -3,15 +3,14 @@ import { Request, Response, Router, NextFunction } from 'express';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { query, oneOf } from 'express-validator'; import { query, oneOf } from 'express-validator';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication';
@Service() @Service()
export default class JournalSheetController extends BaseFinancialReportController { export default class JournalSheetController extends BaseFinancialReportController {
@Inject() @Inject()
private journalSheetApp: JournalSheetApplication; journalService: JournalSheetService;
/** /**
* Router constructor. * Router constructor.
@@ -58,49 +57,28 @@ export default class JournalSheetController extends BaseFinancialReportControlle
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async journal(req: Request, res: Response, next: NextFunction) { async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
let filter = this.matchedQueryData(req); let filter = this.matchedQueryData(req);
filter = { filter = {
...filter, ...filter,
accountsIds: castArray(filter.accountsIds), accountsIds: castArray(filter.accountsIds),
}; };
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the json table format. try {
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { const { data, query, meta } = await this.journalService.journalSheet(
const table = await this.journalSheetApp.table(tenantId, filter); tenantId,
return res.status(200).send(table); filter
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.journalSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.journalSheetApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet); return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
} catch (error) {
next(error);
} }
} }
} }

View File

@@ -1,20 +1,24 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import ProfitLossSheetService from '@/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import { ACCEPT_TYPE } from '@/interfaces/Http'; import { ProfitLossSheetTable } from '@/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable';
import { ProfitLossSheetApplication } from '@/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication'; import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service() @Service()
export default class ProfitLossSheetController extends BaseFinancialReportController { export default class ProfitLossSheetController extends BaseFinancialReportController {
@Inject() @Inject()
private profitLossSheetApp: ProfitLossSheetApplication; profitLossSheetService: ProfitLossSheetService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -30,7 +34,7 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
/** /**
* Validation schema. * Validation schema.
*/ */
private get validationSchema(): ValidationChain[] { get validationSchema(): ValidationChain[] {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('basis').optional(), query('basis').optional(),
@@ -81,54 +85,37 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async profitLossSheet( async profitLossSheet(req: Request, res: Response, next: NextFunction) {
req: Request, const { tenantId, settings } = req;
res: Response, const i18n = this.tenancy.i18n(tenantId);
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
try { try {
// Retrieves the csv format. const { data, query, meta } =
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) { await this.profitLossSheetService.profitLossSheet(tenantId, filter);
const sheet = await this.profitLossSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); const accept = this.accepts(req);
res.setHeader('Content-Type', 'text/csv'); const acceptType = accept.types(['json', 'application/json+table']);
return res.send(sheet); switch (acceptType) {
// Retrieves the json table format. case 'application/json+table':
} else if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) { const table = new ProfitLossSheetTable(data, query, i18n);
const table = await this.profitLossSheetApp.table(tenantId, filter);
return res.status(200).send(table); return res.status(200).send({
// Retrieves the xlsx format. table: {
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) { rows: table.tableRows(),
const sheet = await this.profitLossSheetApp.xlsx(tenantId, filter); columns: table.tableColumns(),
},
res.setHeader( query,
'Content-Disposition', meta,
'attachment; filename=output.xlsx' });
); case 'json':
res.setHeader( default:
'Content-Type', return res.status(200).send({
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' data,
); query,
return res.send(sheet); meta,
// Retrieves the json format. });
} else {
const sheet = await this.profitLossSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -1,18 +1,17 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import moment from 'moment';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { PurchasesByItemsService } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService'; import PurchasesByItemsService from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { PurcahsesByItemsApplication } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication';
@Service() @Service()
export default class PurchasesByItemReportController extends BaseFinancialReportController { export default class PurchasesByItemReportController extends BaseFinancialReportController {
@Inject() @Inject()
private purchasesByItemsApp: PurcahsesByItemsApplication; purchasesByItemsService: PurchasesByItemsService;
/** /**
* Router constructor. * Router constructor.
@@ -64,47 +63,20 @@ export default class PurchasesByItemReportController extends BaseFinancialReport
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
public async purchasesByItems(req: Request, res: Response) { async purchasesByItems(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req); try {
const { data, query, meta } =
const acceptType = accept.types([ await this.purchasesByItemsService.purchasesByItems(tenantId, filter);
ACCEPT_TYPE.APPLICATION_JSON, return res.status(200).send({
ACCEPT_TYPE.APPLICATION_JSON_TABLE, meta: this.transfromToResponse(meta),
ACCEPT_TYPE.APPLICATION_XLSX, data: this.transfromToResponse(data),
ACCEPT_TYPE.APPLICATION_CSV, query: this.transfromToResponse(query),
]); });
} catch (error) {
// JSON table response format. next(error);
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.purchasesByItemsApp.table(tenantId, filter);
return res.status(200).send(table);
// CSV response format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.purchasesByItemsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Xlsx response format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.purchasesByItemsApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Json response format.
} else {
const sheet = await this.purchasesByItemsApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -1,39 +1,41 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain, ValidationSchema } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import moment from 'moment';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import SalesByItemsReportService from '@/services/FinancialStatements/SalesByItems/SalesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { SalesByItemsApplication } from '@/services/FinancialStatements/SalesByItems/SalesByItemsApplication';
@Service() @Service()
export default class SalesByItemsReportController extends BaseFinancialReportController { export default class SalesByItemsReportController extends BaseFinancialReportController {
@Inject() @Inject()
salesByItemsApp: SalesByItemsApplication; salesByItemsService: SalesByItemsReportService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
'/', '/',
CheckPolicies(ReportsAction.READ_SALES_BY_ITEMS, AbilitySubject.Report), CheckPolicies(
ReportsAction.READ_SALES_BY_ITEMS,
AbilitySubject.Report
),
this.validationSchema, this.validationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.salesByItems.bind(this)) asyncMiddleware(this.purchasesByItems.bind(this))
); );
return router; return router;
} }
/** /**
* Validation schema. * Validation schema.
* @returns {ValidationChain[]}
*/ */
private get validationSchema(): ValidationChain[] { get validationSchema(): ValidationChain[] {
return [ return [
query('from_date').optional().isISO8601(), query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(), query('to_date').optional().isISO8601(),
@@ -61,44 +63,22 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async salesByItems(req: Request, res: Response, next: NextFunction) { async purchasesByItems(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([ try {
ACCEPT_TYPE.APPLICATION_JSON, const { data, query, meta } = await this.salesByItemsService.salesByItems(
ACCEPT_TYPE.APPLICATION_JSON_TABLE, tenantId,
ACCEPT_TYPE.APPLICATION_CSV, filter
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the csv format.
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.salesByItemsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the json table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.salesByItemsApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = this.salesByItemsApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer); return res.status(200).send({
// Retrieves the json format. meta: this.transfromToResponse(meta),
} else { data: this.transfromToResponse(data),
const sheet = await this.salesByItemsApp.sheet(tenantId, filter); query: this.transfromToResponse(query),
return res.status(200).send(sheet); });
} catch (error) {
next(error);
} }
} }
} }

View File

@@ -1,21 +1,20 @@
import { Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator'; import { query } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SalesTaxLiabilitySummaryApplication } from '@/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication'; import { SalesTaxLiabilitySummaryService } from '@/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryService';
import { ACCEPT_TYPE } from '@/interfaces/Http';
export default class SalesTaxLiabilitySummary extends BaseFinancialReportController { export default class SalesTaxLiabilitySummary extends BaseFinancialReportController {
@Inject() @Inject()
private salesTaxLiabilitySummaryApp: SalesTaxLiabilitySummaryApplication; private salesTaxLiabilitySummaryService: SalesTaxLiabilitySummaryService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -32,9 +31,8 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
/** /**
* Validation schema. * Validation schema.
* @returns {ValidationChain[]}
*/ */
private get validationSchema() { get validationSchema() {
return [ return [
query('from_date').optional().isISO8601(), query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(), query('to_date').optional().isISO8601(),
@@ -47,7 +45,7 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
private async salesTaxLiabilitySummary( async salesTaxLiabilitySummary(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
@@ -57,52 +55,33 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
try { try {
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the json table format. switch (acceptType) {
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) { case 'application/json+table':
const table = await this.salesTaxLiabilitySummaryApp.table( const salesTaxLiabilityTable =
tenantId, await this.salesTaxLiabilitySummaryService.salesTaxLiabilitySummaryTable(
filter tenantId,
); filter
return res.status(200).send(table); );
// Retrieves the xlsx format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.salesTaxLiabilitySummaryApp.xlsx(
tenantId,
filter
);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the csv format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.salesTaxLiabilitySummaryApp.csv(
tenantId,
filter
);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer); return res.status(200).send({
// Retrieves the json format. table: salesTaxLiabilityTable.table,
} else { query: salesTaxLiabilityTable.query,
const sheet = await this.salesTaxLiabilitySummaryApp.sheet( meta: salesTaxLiabilityTable.meta,
tenantId, });
filter case 'json':
); default:
return res.status(200).send(sheet); const salesTaxLiability =
await this.salesTaxLiabilitySummaryService.salesTaxLiability(
tenantId,
filter
);
return res.status(200).send({
data: salesTaxLiability.data,
query: salesTaxLiability.query,
meta: salesTaxLiability.meta,
});
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -1,22 +1,30 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator'; import { query } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import {
AbilitySubject,
ITransactionsByCustomersStatement,
ReportsAction,
} from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import TransactionsByCustomersService from '@/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService';
import TransactionsByCustomersTableRows from '@/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { TransactionsByCustomerApplication } from '@/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class TransactionsByCustomersReportController extends BaseFinancialReportController { export default class TransactionsByCustomersReportController extends BaseFinancialReportController {
@Inject() @Inject()
private transactionsByCustomersApp: TransactionsByCustomerApplication; transactionsByCustomersService: TransactionsByCustomersService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -50,13 +58,45 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
]; ];
} }
/**
* Transformes the statement to table rows response.
* @param {ITransactionsByCustomersStatement} statement -
*/
private transformToTableResponse(customersTransactions, tenantId) {
const i18n = this.tenancy.i18n(tenantId);
const table = new TransactionsByCustomersTableRows(
customersTransactions,
i18n
);
return {
table: {
rows: table.tableRows(),
},
};
}
/**
* Transformes the statement to json response.
* @param {ITransactionsByCustomersStatement} statement -
*/
private transfromToJsonResponse(
data,
columns
): ITransactionsByCustomersStatement {
return {
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
};
}
/** /**
* Retrieve payable aging summary report. * Retrieve payable aging summary report.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
private async transactionsByCustomers( async transactionsByCustomers(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
@@ -64,51 +104,25 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
try { try {
// Retrieves the json table format. const report =
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { await this.transactionsByCustomersService.transactionsByCustomers(
const table = await this.transactionsByCustomersApp.table(
tenantId, tenantId,
filter filter
); );
return res.status(200).send(table); const accept = this.accepts(req);
// Retrieve the csv format. const acceptType = accept.types(['json', 'application/json+table']);
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const csv = await this.transactionsByCustomersApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); switch (acceptType) {
res.setHeader('Content-Type', 'text/csv'); case 'json':
return res
return res.send(csv); .status(200)
// Retrieve the xlsx format. .send(this.transfromToJsonResponse(report.data, report.columns));
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { case 'application/json+table':
const buffer = await this.transactionsByCustomersApp.xlsx( default:
tenantId, return res
filter .status(200)
); .send(this.transformToTableResponse(report.data, tenantId));
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieve the json format.
} else {
const sheet = await this.transactionsByCustomersApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -3,19 +3,27 @@ import { query, ValidationChain } from 'express-validator';
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import TransactionsByVendorsTableRows from '@/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorTableRows';
import TransactionsByVendorsService from '@/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService';
import {
AbilitySubject,
ITransactionsByVendorsStatement,
ReportsAction,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { TransactionsByVendorApplication } from '@/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication';
export default class TransactionsByVendorsReportController extends BaseFinancialReportController { export default class TransactionsByVendorsReportController extends BaseFinancialReportController {
@Inject() @Inject()
private transactionsByVendorsApp: TransactionsByVendorApplication; transactionsByVendorsService: TransactionsByVendorsService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -34,7 +42,7 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
/** /**
* Validation schema. * Validation schema.
*/ */
private get validationSchema(): ValidationChain[] { get validationSchema(): ValidationChain[] {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
@@ -50,64 +58,64 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
]; ];
} }
/**
* Transformes the report statement to table rows.
* @param {ITransactionsByVendorsStatement} statement -
*/
private transformToTableRows(tenantId: number, transactions: any[]) {
const i18n = this.tenancy.i18n(tenantId);
const table = new TransactionsByVendorsTableRows(transactions, i18n);
return {
table: {
data: table.tableRows(),
},
};
}
/**
* Transformes the report statement to json response.
* @param {ITransactionsByVendorsStatement} statement -
*/
private transformToJsonResponse({
data,
columns,
query,
}: ITransactionsByVendorsStatement) {
return {
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
};
}
/** /**
* Retrieve payable aging summary report. * Retrieve payable aging summary report.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
private async transactionsByVendors( async transactionsByVendors(req: Request, res: Response, next: NextFunction) {
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const report =
await this.transactionsByVendorsService.transactionsByVendors(
tenantId,
filter
);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the xlsx format. switch (acceptType) {
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { case 'application/json+table':
const buffer = await this.transactionsByVendorsApp.xlsx( return res
tenantId, .status(200)
filter .send(this.transformToTableRows(tenantId, report.data));
); case 'json':
res.setHeader('Content-Type', 'application/vnd.openxmlformats'); default:
res.setHeader( return res.status(200).send(this.transformToJsonResponse(report));
'Content-Disposition',
'attachment; filename=report.xlsx'
);
return res.send(buffer);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.transactionsByVendorsApp.csv(
tenantId,
filter
);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename=report.csv');
return res.send(buffer);
// Retrieves the json table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.transactionsByVendorsApp.table(
tenantId,
filter
);
return res.status(200).send(table);
// Retrieves the json format.
} else {
const sheet = await this.transactionsByVendorsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -3,22 +3,20 @@ import { Request, Response, Router, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetInjectable'; import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { TrialBalanceSheetApplication } from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class TrialBalanceSheetController extends BaseFinancialReportController { export default class TrialBalanceSheetController extends BaseFinancialReportController {
@Inject() @Inject()
private trialBalanceSheetApp: TrialBalanceSheetApplication; trialBalanceSheetService: TrialBalanceSheetService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -38,7 +36,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
* Validation schema. * Validation schema.
* @return {ValidationChain[]} * @return {ValidationChain[]}
*/ */
private get trialBalanceSheetValidationSchema(): ValidationChain[] { get trialBalanceSheetValidationSchema(): ValidationChain[] {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('basis').optional(), query('basis').optional(),
@@ -61,62 +59,28 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
/** /**
* Retrieve the trial balance sheet. * Retrieve the trial balance sheet.
*/ */
private async trialBalanceSheet( public async trialBalanceSheet(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
const { tenantId } = req; const { tenantId, settings } = req;
let filter = this.matchedQueryData(req); let filter = this.matchedQueryData(req);
filter = { filter = {
...filter, ...filter,
accountsIds: castArray(filter.accountsIds), accountsIds: castArray(filter.accountsIds),
}; };
try { try {
const accept = this.accepts(req); const { data, query, meta } =
await this.trialBalanceSheetService.trialBalanceSheet(tenantId, filter);
const acceptType = accept.types([ return res.status(200).send({
ACCEPT_TYPE.APPLICATION_JSON, data: this.transfromToResponse(data),
ACCEPT_TYPE.APPLICATION_JSON_TABLE, query: this.transfromToResponse(query),
ACCEPT_TYPE.APPLICATION_CSV, meta: this.transfromToResponse(meta),
ACCEPT_TYPE.APPLICATION_XLSX, });
]);
// Retrieves in json table format.
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const { table, meta, query } = await this.trialBalanceSheetApp.table(
tenantId,
filter
);
return res.status(200).send({ table, meta, query });
// Retrieves in xlsx format
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.trialBalanceSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves in csv format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.trialBalanceSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves in json format.
} else {
const { data, query, meta } = await this.trialBalanceSheetApp.sheet(
tenantId,
filter
);
return res.status(200).send({ data, query, meta });
}
} catch (error) { } catch (error) {
next(error); next(error);
} }

View File

@@ -3,19 +3,27 @@ import { query } from 'express-validator';
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController'; import BaseFinancialReportController from '../BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import VendorBalanceSummaryTableRows from '@/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows';
import VendorBalanceSummaryService from '@/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService';
import {
AbilitySubject,
IVendorBalanceSummaryStatement,
ReportsAction,
} from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { VendorBalanceSummaryApplication } from '@/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication';
export default class VendorBalanceSummaryReportController extends BaseFinancialReportController { export default class VendorBalanceSummaryReportController extends BaseFinancialReportController {
@Inject() @Inject()
private vendorBalanceSummaryApp: VendorBalanceSummaryApplication; vendorBalanceSummaryService: VendorBalanceSummaryService;
@Inject()
tenancy: HasTenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -33,7 +41,7 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
/** /**
* Validation schema. * Validation schema.
*/ */
private get validationSchema() { get validationSchema() {
return [ return [
...this.sheetNumberFormatValidationSchema, ...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(), query('as_date').optional().isISO8601(),
@@ -51,62 +59,73 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
]; ];
} }
/**
* Transformes the report statement to table rows.
* @param {IVendorBalanceSummaryStatement} statement -
*/
private transformToTableRows(
tenantId: number,
{ data, query }: IVendorBalanceSummaryStatement
) {
const i18n = this.tenancy.i18n(tenantId);
const tableData = new VendorBalanceSummaryTableRows(
data,
query,
i18n
);
return {
table: {
columns: tableData.tableColumns(),
data: tableData.tableRows(),
},
query,
};
}
/**
* Transformes the report statement to raw json.
* @param {IVendorBalanceSummaryStatement} statement -
*/
private transformToJsonResponse({
data,
columns,
}: IVendorBalanceSummaryStatement) {
return {
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
};
}
/** /**
* Retrieve vendors balance summary. * Retrieve vendors balance summary.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
public async vendorBalanceSummary( async vendorBalanceSummary(req: Request, res: Response, next: NextFunction) {
req: Request, const { tenantId, settings } = req;
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const vendorBalanceSummary =
await this.vendorBalanceSummaryService.vendorBalanceSummary(
tenantId,
filter
);
const accept = this.accepts(req); const accept = this.accepts(req);
const acceptType = accept.types([ const acceptType = accept.types(['json', 'application/json+table']);
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the csv format. switch (acceptType) {
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) { case 'application/json+table':
const buffer = await this.vendorBalanceSummaryApp.csv(tenantId, filter); return res
.status(200)
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); .send(this.transformToTableRows(tenantId, vendorBalanceSummary));
res.setHeader('Content-Type', 'text/csv'); case 'json':
default:
return res.send(buffer); return res
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) { .status(200)
const buffer = await this.vendorBalanceSummaryApp.xlsx( .send(this.transformToJsonResponse(vendorBalanceSummary));
tenantId,
filter
);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
return res.send(buffer);
// Retrieves the json table format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const table = await this.vendorBalanceSummaryApp.table(
tenantId,
filter
);
return res.status(200).send(table);
// Retrieves the json format.
} else {
const sheet = await this.vendorBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
} }
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -149,11 +149,6 @@ export default class ItemsController extends BaseController {
.trim() .trim()
.escape() .escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
check('purchase_tax_rate_id')
.optional({ nullable: true })
.isInt()
.toInt(),
check('category_id') check('category_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
@@ -513,28 +508,6 @@ export default class ItemsController extends BaseController {
], ],
}); });
} }
if (error.errorType === 'PURCHASE_TAX_RATE_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'PURCHASE_TAX_RATE_NOT_FOUND',
message: 'Purchase tax rate has not found.',
code: 410,
},
],
});
}
if (error.errorType === 'SELL_TAX_RATE_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'SELL_TAX_RATE_NOT_FOUND',
message: 'Sell tax rate is not found.',
code: 420,
},
],
});
}
} }
next(error); next(error);
} }

View File

@@ -31,14 +31,14 @@ export default class OrganizationController extends BaseController {
router.post( router.post(
'/build', '/build',
this.buildOrganizationValidationSchema, this.organizationValidationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.build.bind(this)), asyncMiddleware(this.build.bind(this)),
this.handleServiceErrors.bind(this) this.handleServiceErrors.bind(this)
); );
router.put( router.put(
'/', '/',
this.updateOrganizationValidationSchema, this.organizationValidationSchema,
this.validationResult, this.validationResult,
this.asyncMiddleware(this.updateOrganization.bind(this)), this.asyncMiddleware(this.updateOrganization.bind(this)),
this.handleServiceErrors.bind(this) this.handleServiceErrors.bind(this)
@@ -55,7 +55,7 @@ export default class OrganizationController extends BaseController {
* Organization setup schema. * Organization setup schema.
* @return {ValidationChain[]} * @return {ValidationChain[]}
*/ */
private get commonOrganizationValidationSchema(): ValidationChain[] { private get organizationValidationSchema(): ValidationChain[] {
return [ return [
check('name').exists().trim(), check('name').exists().trim(),
check('industry').optional({ nullable: true }).isString().trim().escape(), check('industry').optional({ nullable: true }).isString().trim().escape(),
@@ -68,29 +68,6 @@ export default class OrganizationController extends BaseController {
]; ];
} }
/**
* Build organization validation schema.
* @returns {ValidationChain[]}
*/
private get buildOrganizationValidationSchema(): ValidationChain[] {
return [...this.commonOrganizationValidationSchema];
}
/**
* Update organization validation schema.
* @returns {ValidationChain[]}
*/
private get updateOrganizationValidationSchema(): ValidationChain[] {
return [
...this.commonOrganizationValidationSchema,
check('tax_number')
.optional({ nullable: true })
.isString()
.trim()
.escape(),
];
}
/** /**
* Builds tenant database and migrate database schema. * Builds tenant database and migrate database schema.
* @param {Request} req - Express request. * @param {Request} req - Express request.

View File

@@ -115,14 +115,12 @@ export default class BillsController extends BaseController {
check('note').optional().trim().escape(), check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(), check('open').default(false).isBoolean().toBoolean(),
check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }), check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -139,15 +137,6 @@ export default class BillsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
.escape()
.isString(),
check('entries.*.tax_rate_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
]; ];
} }
@@ -303,7 +292,7 @@ export default class BillsController extends BaseController {
try { try {
const bill = await this.billsApplication.getBill(tenantId, billId); const bill = await this.billsApplication.getBill(tenantId, billId);
return res.status(200).send({ bill }); return res.status(200).send(this.transfromToResponse({ bill }));
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -348,11 +337,14 @@ export default class BillsController extends BaseController {
}; };
try { try {
const billsWithPagination = await this.billsApplication.getBills( const { bills, pagination, filterMeta } =
tenantId, await this.billsApplication.getBills(tenantId, filter);
filter
); return res.status(200).send({
return res.status(200).send(billsWithPagination); bills: this.transfromToResponse(bills),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -550,16 +542,6 @@ export default class BillsController extends BaseController {
], ],
}); });
} }
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND', code: 1800 }],
});
}
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
});
}
} }
next(error); next(error);
} }

View File

@@ -121,7 +121,7 @@ export default class BillsPayments extends BaseController {
check('entries').exists().isArray({ min: 1 }), check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(), check('entries.*.payment_amount').exists().isNumeric().toInt(),
]; ];
} }
@@ -158,11 +158,15 @@ export default class BillsPayments extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req); const { vendorId } = this.matchedQueryData(req);
const entries = await this.billPaymentsPages.getNewPageEntries( try {
tenantId, const entries = await this.billPaymentsPages.getNewPageEntries(
vendorId tenantId,
); vendorId
return res.status(200).send({ entries }); );
return res.status(200).send({
entries: this.transfromToResponse(entries),
});
} catch (error) {}
} }
/** /**
@@ -179,12 +183,16 @@ export default class BillsPayments extends BaseController {
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
try { try {
const billPaymentsWithEditEntries = const { billPayment, entries } =
await this.billPaymentsPages.getBillPaymentEditPage( await this.billPaymentsPages.getBillPaymentEditPage(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
return res.status(200).send(billPaymentsWithEditEntries);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
entries: this.transfromToResponse(entries),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -296,7 +304,9 @@ export default class BillsPayments extends BaseController {
tenantId, tenantId,
billPaymentId billPaymentId
); );
return res.status(200).send({ billPayment }); return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -349,12 +359,17 @@ export default class BillsPayments extends BaseController {
}; };
try { try {
const billPaymentsWithPagination = const { billPayments, pagination, filterMeta } =
await this.billPaymentsApplication.getBillPayments( await this.billPaymentsApplication.getBillPayments(
tenantId, tenantId,
billPaymentsFilter billPaymentsFilter
); );
return res.status(200).send(billPaymentsWithPagination);
return res.status(200).send({
bill_payments: this.transfromToResponse(billPayments),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }

View File

@@ -173,7 +173,7 @@ export default class VendorCreditController extends BaseController {
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -211,11 +211,12 @@ export default class VendorCreditController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({ min: 1 }), check('entries').isArray({ min: 1 }),
check('entries.*.id').optional().isNumeric().toInt(), check('entries.*.id').optional().isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -320,19 +321,20 @@ export default class VendorCreditController extends BaseController {
res: Response, res: Response,
next: NextFunction next: NextFunction
) => { ) => {
const { id: vendorCreditId } = req.params; const { id: billId } = req.params;
const { tenantId, user } = req; const { tenantId, user } = req;
const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req); const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req);
try { try {
await this.editVendorCreditService.editVendorCredit( await this.editVendorCreditService.editVendorCredit(
tenantId, tenantId,
vendorCreditId, billId,
vendorCreditEditDTO vendorCreditEditDTO,
user
); );
return res.status(200).send({ return res.status(200).send({
id: vendorCreditId, id: billId,
message: 'The vendor credit has been edited successfully.', message: 'The vendor credit has been edited successfully.',
}); });
} catch (error) { } catch (error) {

View File

@@ -26,7 +26,6 @@ import GetCreditNoteAssociatedInvoicesToApply from '@/services/CreditNotes/GetCr
import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCreditNoteAssociatedAppliedInvoices'; import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCreditNoteAssociatedAppliedInvoices';
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction'; import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf'; import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
import { ACCEPT_TYPE } from '@/interfaces/Http';
/** /**
* Credit notes controller. * Credit notes controller.
* @service * @service
@@ -223,7 +222,7 @@ export default class PaymentReceivesController extends BaseController {
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -294,7 +293,7 @@ export default class PaymentReceivesController extends BaseController {
return [ return [
check('from_account_id').exists().isNumeric().toInt(), check('from_account_id').exists().isNumeric().toInt(),
check('description').optional(), check('description').optional(),
check('amount').exists().isNumeric().toFloat(), check('amount').exists().isNumeric().toFloat(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -439,7 +438,7 @@ export default class PaymentReceivesController extends BaseController {
}; };
/** /**
* Retrieve the credit note details. * Retrieve the payment receive details.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
@@ -452,28 +451,38 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { id: creditNoteId } = req.params; const { id: creditNoteId } = req.params;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
tenantId,
creditNoteId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else {
const creditNote = await this.getCreditNoteService.getCreditNote( const creditNote = await this.getCreditNoteService.getCreditNote(
tenantId, tenantId,
creditNoteId creditNoteId
); );
return res.status(200).send({ creditNote }); const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
// Response formatter.
res.format({
// Json content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res
.status(200)
.send({ credit_note: this.transfromToResponse(creditNote) });
},
// Pdf content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
tenantId,
creditNote
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
}; };

View File

@@ -1,11 +1,10 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query, ValidationChain } from 'express-validator'; import { check, param, query, ValidationChain } from 'express-validator';
import { import {
AbilitySubject, AbilitySubject,
IPaymentReceiveDTO, IPaymentReceiveDTO,
PaymentReceiveAction, PaymentReceiveAction,
PaymentReceiveMailOptsDTO,
} from '@/interfaces'; } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -14,7 +13,6 @@ import DynamicListingService from '@/services/DynamicListing/DynamicListService'
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication'; import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class PaymentReceivesController extends BaseController { export default class PaymentReceivesController extends BaseController {
@@ -119,25 +117,6 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.deletePaymentReceive.bind(this)), asyncMiddleware(this.deletePaymentReceive.bind(this)),
this.handleServiceErrors this.handleServiceErrors
); );
router.post(
'/:id/mail',
[
...this.paymentReceiveValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.sendPaymentReceiveByMail.bind(this),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.paymentReceiveValidation],
asyncMiddleware(this.getPaymentDefaultMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -163,7 +142,7 @@ export default class PaymentReceivesController extends BaseController {
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(), check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(), check('entries.*.payment_amount').exists().isNumeric().toInt(),
]; ];
} }
@@ -349,12 +328,17 @@ export default class PaymentReceivesController extends BaseController {
}; };
try { try {
const paymentsReceivedWithPagination = const { paymentReceives, pagination, filterMeta } =
await this.paymentReceiveApplication.getPaymentReceives( await this.paymentReceiveApplication.getPaymentReceives(
tenantId, tenantId,
filter filter
); );
return res.status(200).send(paymentsReceivedWithPagination);
return res.status(200).send({
payment_receives: this.transfromToResponse(paymentReceives),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -431,34 +415,38 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Response in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceiveId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Response in json format.
} else {
const paymentReceive = const paymentReceive =
await this.paymentReceiveApplication.getPaymentReceive( await this.paymentReceiveApplication.getPaymentReceive(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
return res.status(200).send({
payment_receive: paymentReceive, const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send({
payment_receive: this.transfromToResponse(paymentReceive),
});
},
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceive
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
}); });
} catch (error) {
next(error);
} }
} }
@@ -492,7 +480,7 @@ export default class PaymentReceivesController extends BaseController {
}; };
/** /**
* Retrieves the sms details of the given payment receive. *
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
@@ -519,74 +507,14 @@ export default class PaymentReceivesController extends BaseController {
} }
}; };
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public sendPaymentReceiveByMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
const paymentMailDTO: PaymentReceiveMailOptsDTO = this.matchedBodyData(
req,
{
includeOptionals: false,
}
);
try {
await this.paymentReceiveApplication.notifyPaymentByMail(
tenantId,
paymentReceiveId,
paymentMailDTO
);
return res.status(200).send({
code: 200,
message: 'The payment notification has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given payment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getPaymentDefaultMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
try {
const data = await this.paymentReceiveApplication.getPaymentMailOptions(
tenantId,
paymentReceiveId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param error
* @param {Request} req * @param req
* @param {Response} res * @param res
* @param {NextFunction} next * @param next
*/ */
private handleServiceErrors( handleServiceErrors(
error: Error, error: Error,
req: Request, req: Request,
res: Response, res: Response,

View File

@@ -1,11 +1,10 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { import {
AbilitySubject, AbilitySubject,
ISaleEstimateDTO, ISaleEstimateDTO,
SaleEstimateAction, SaleEstimateAction,
SaleEstimateMailOptionsDTO,
} from '@/interfaces'; } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -122,27 +121,6 @@ export default class SalesEstimatesController extends BaseController {
this.handleServiceErrors, this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse this.dynamicListService.handlerErrorsToResponse
); );
router.post(
'/:id/mail',
[
...this.validateSpecificEstimateSchema,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleEstimateMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.getSaleEstimateMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -334,6 +312,7 @@ export default class SalesEstimatesController extends BaseController {
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ return res.status(200).send({
id: estimateId, id: estimateId,
message: 'The sale estimate has been approved successfully.', message: 'The sale estimate has been approved successfully.',
@@ -362,6 +341,7 @@ export default class SalesEstimatesController extends BaseController {
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ return res.status(200).send({
id: estimateId, id: estimateId,
message: 'The sale estimate has been rejected successfully.', message: 'The sale estimate has been rejected successfully.',
@@ -381,30 +361,33 @@ export default class SalesEstimatesController extends BaseController {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
const { tenantId } = req; const { tenantId } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves estimate in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimateId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves estimates in json format.
} else {
const estimate = await this.saleEstimatesApplication.getSaleEstimate( const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ estimate }); // Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(this.transfromToResponse({ estimate }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimate
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
@@ -422,11 +405,22 @@ export default class SalesEstimatesController extends BaseController {
pageSize: 12, pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesEstimatesWithPagination = const { salesEstimates, pagination, filterMeta } =
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter); await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
return res.status(200).send(salesEstimatesWithPagination); res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(
this.transfromToResponse({
salesEstimates,
pagination,
filterMeta,
})
);
},
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -484,65 +478,6 @@ export default class SalesEstimatesController extends BaseController {
} }
}; };
/**
* Send the sale estimate mail.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private sendSaleEstimateMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const saleEstimateDTO: SaleEstimateMailOptionsDTO = this.matchedBodyData(
req,
{
includeOptionals: false,
}
);
try {
await this.saleEstimatesApplication.sendSaleEstimateMail(
tenantId,
invoiceId,
saleEstimateDTO
);
return res.status(200).send({
code: 200,
message: 'The sale estimate mail has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given sale estimate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private getSaleEstimateMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleEstimatesApplication.getSaleEstimateMail(
tenantId,
invoiceId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -1,5 +1,5 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -10,12 +10,14 @@ import {
ISaleInvoiceCreateDTO, ISaleInvoiceCreateDTO,
SaleInvoiceAction, SaleInvoiceAction,
AbilitySubject, AbilitySubject,
SendInvoiceMailDTO,
} from '@/interfaces'; } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication'; import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
@Service() @Service()
export default class SaleInvoicesController extends BaseController { export default class SaleInvoicesController extends BaseController {
@Inject() @Inject()
@@ -143,48 +145,6 @@ export default class SaleInvoicesController extends BaseController {
this.handleServiceErrors, this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse this.dynamicListService.handlerErrorsToResponse
); );
router.get(
'/:id/mail-reminder',
this.specificSaleInvoiceValidation,
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMailReminder.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/mail-reminder',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleInvoiceMailReminder.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/mail',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleInvoiceMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.specificSaleInvoiceValidation],
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -400,6 +360,7 @@ export default class SaleInvoicesController extends BaseController {
saleInvoiceId, saleInvoiceId,
user user
); );
return res.status(200).send({ return res.status(200).send({
id: saleInvoiceId, id: saleInvoiceId,
message: 'The sale invoice has been deleted successfully.', message: 'The sale invoice has been deleted successfully.',
@@ -414,35 +375,43 @@ export default class SaleInvoicesController extends BaseController {
* @param {Request} req - Request object. * @param {Request} req - Request object.
* @param {Response} res - Response object. * @param {Response} res - Response object.
*/ */
private async getSaleInvoice(req: Request, res: Response) { private async getSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const { tenantId, user } = req; const { tenantId, user } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves invoice in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves invoice in json format.
} else {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice( const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId,
user user
); );
return res.status(200).send({ saleInvoice }); // Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res
.status(200)
.send(this.transfromToResponse({ saleInvoice }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoice
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
/** /**
@@ -465,10 +434,14 @@ export default class SaleInvoicesController extends BaseController {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesInvoicesWithPagination = const { salesInvoices, filterMeta, pagination } =
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter); await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
return res.status(200).send(salesInvoicesWithPagination); return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -495,7 +468,9 @@ export default class SaleInvoicesController extends BaseController {
tenantId, tenantId,
customerId customerId
); );
return res.status(200).send({ salesInvoices }); return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -523,6 +498,7 @@ export default class SaleInvoicesController extends BaseController {
invoiceId, invoiceId,
writeoffDTO writeoffDTO
); );
return res.status(200).send({ return res.status(200).send({
id: saleInvoice.id, id: saleInvoice.id,
message: 'The given sale invoice has been written-off successfully.', message: 'The given sale invoice has been written-off successfully.',
@@ -654,119 +630,6 @@ export default class SaleInvoicesController extends BaseController {
} }
}; };
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async sendSaleInvoiceMail(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleInvoiceApplication.sendSaleInvoiceMail(
tenantId,
invoiceId,
invoiceMailDTO
);
return res.status(200).send({
code: 200,
message: 'The sale invoice mail has been sent successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retreivers the sale invoice reminder options.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleInvoiceMailReminder(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
tenantId,
invoiceId
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async sendSaleInvoiceMailReminder(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleInvoiceApplication.sendSaleInvoiceMailReminder(
tenantId,
invoiceId,
invoiceMailDTO
);
return res.status(200).send({
code: 200,
message: 'The sale invoice mail reminder has been sent successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the default mail options of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleInvoiceMail(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
tenantId,
invoiceId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -1,19 +1,14 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
ISaleReceiptDTO,
SaleReceiptMailOpts,
SaleReceiptMailOptsDTO,
} from '@/interfaces/SaleReceipt';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService'; import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, SaleReceiptAction } from '@/interfaces'; import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication'; import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class SalesReceiptsController extends BaseController { export default class SalesReceiptsController extends BaseController {
@@ -51,27 +46,6 @@ export default class SalesReceiptsController extends BaseController {
this.saleReceiptSmsDetails, this.saleReceiptSmsDetails,
this.handleServiceErrors this.handleServiceErrors
); );
router.post(
'/:id/mail',
[
...this.specificReceiptValidationSchema,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_receipt').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleReceiptMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.specificReceiptValidationSchema],
this.validationResult,
asyncMiddleware(this.getSaleReceiptMail.bind(this)),
this.handleServiceErrors
);
router.post( router.post(
'/:id', '/:id',
CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt), CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt),
@@ -143,7 +117,7 @@ export default class SalesReceiptsController extends BaseController {
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toInt(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -231,6 +205,7 @@ export default class SalesReceiptsController extends BaseController {
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
return res.status(200).send({ return res.status(200).send({
id: saleReceiptId, id: saleReceiptId,
message: 'Sale receipt has been deleted successfully.', message: 'Sale receipt has been deleted successfully.',
@@ -319,10 +294,15 @@ export default class SalesReceiptsController extends BaseController {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesReceiptsWithPagination = const { data, pagination, filterMeta } =
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter); await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
return res.status(200).send(salesReceiptsWithPagination); const response = this.transfromToResponse({
data,
pagination,
filterMeta,
});
return res.status(200).send(response);
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -334,34 +314,36 @@ export default class SalesReceiptsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
public async getSaleReceipt(req: Request, res: Response) { async getSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
const { tenantId } = req; const { tenantId } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves receipt in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceiptId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves receipt in json format.
} else {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt( const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
return res.status(200).send({ saleReceipt }); res.format({
'application/json': () => {
return res
.status(200)
.send(this.transfromToResponse({ saleReceipt }));
},
'application/pdf': async () => {
const pdfContent =
await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceipt
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
@@ -423,64 +405,6 @@ export default class SalesReceiptsController extends BaseController {
} }
}; };
/**
* Sends mail notification of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public sendSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
const receiptMailDTO: SaleReceiptMailOptsDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleReceiptsApplication.sendSaleReceiptMail(
tenantId,
receiptId,
receiptMailDTO
);
return res.status(200).send({
code: 200,
message:
'The sale receipt notification via sms has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
try {
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
tenantId,
receiptId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -5,7 +5,6 @@ global.__root_dir = path.join(__dirname, '..');
global.__resources_dir = path.join(global.__root_dir, 'resources'); global.__resources_dir = path.join(global.__root_dir, 'resources');
global.__locales_dir = path.join(global.__resources_dir, 'locales'); global.__locales_dir = path.join(global.__resources_dir, 'locales');
global.__views_dir = path.join(global.__root_dir, 'views'); global.__views_dir = path.join(global.__root_dir, 'views');
global.__storage_dir = path.join(global.__root_dir, 'storage');
moment.prototype.toMySqlDateTime = function () { moment.prototype.toMySqlDateTime = function () {
return this.format('YYYY-MM-DD HH:mm:ss'); return this.format('YYYY-MM-DD HH:mm:ss');

View File

@@ -58,7 +58,6 @@ module.exports = {
secure: !!parseInt(process.env.MAIL_SECURE, 10), secure: !!parseInt(process.env.MAIL_SECURE, 10),
username: process.env.MAIL_USERNAME, username: process.env.MAIL_USERNAME,
password: process.env.MAIL_PASSWORD, password: process.env.MAIL_PASSWORD,
from: process.env.MAIL_FROM_ADDRESS,
}, },
/** /**
@@ -169,14 +168,4 @@ module.exports = {
* to application detarmines to upgrade. * to application detarmines to upgrade.
*/ */
databaseBatch: 4, databaseBatch: 4,
/**
* Exchange rate.
*/
exchangeRate: {
service: 'open-exchange-rate',
openExchangeRate: {
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
}
}
}; };

View File

@@ -59,12 +59,6 @@ export default {
auto_increment: { auto_increment: {
type: 'boolean', type: 'boolean',
}, },
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
}, },
sales_receipts: { sales_receipts: {
next_number: { next_number: {
@@ -79,12 +73,6 @@ export default {
preferred_deposit_account: { preferred_deposit_account: {
type: 'number', type: 'number',
}, },
receipt_message: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
}, },
sales_invoices: { sales_invoices: {
next_number: { next_number: {
@@ -96,12 +84,6 @@ export default {
auto_increment: { auto_increment: {
type: 'boolean', type: 'boolean',
}, },
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
}, },
payment_receives: { payment_receives: {
next_number: { next_number: {
@@ -165,12 +147,6 @@ export default {
auto_increment: { auto_increment: {
type: 'boolean', type: 'boolean',
}, },
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
}, },
vendor_credit: { vendor_credit: {
next_number: { next_number: {

View File

@@ -1,10 +0,0 @@
exports.up = (knex) => {
return knex.schema.table('bills', (table) => {
table.boolean('is_inclusive_tax').defaultTo(false);
table.decimal('tax_amount_withheld');
});
};
exports.down = (knex) => {
return knex.schema.table('bills', () => {});
};

View File

@@ -1,18 +0,0 @@
exports.up = (knex) => {
return knex.schema.table('items', (table) => {
table
.integer('sell_tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
table
.integer('purchase_tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
});
};
exports.down = (knex) => {
return knex.schema.dropTableIfExists('tax_rates');
};

View File

@@ -1,14 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable('storage', (table) => {
table.increments('id').primary();
table.string('key').notNullable();
table.string('path').notNullable();
table.string('extension').notNullable();
table.integer('expire_in');
table.timestamps();
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('storage');
};

View File

@@ -1,9 +0,0 @@
exports.up = function (knex) {
return knex.schema.alterTable('items_entries', (table) => {
table.decimal('rate', 15, 5).alter();
});
};
exports.down = function (knex) {
return knex.table('items_entries', (table) => {});
};

View File

@@ -1,11 +1,13 @@
import { import {
IAgingPeriod, IAgingPeriod,
IAgingPeriodTotal,
IAgingAmount,
IAgingSummaryQuery, IAgingSummaryQuery,
IAgingSummaryTotal, IAgingSummaryTotal,
IAgingSummaryContact, IAgingSummaryContact,
IAgingSummaryData, IAgingSummaryData,
} from './AgingReport'; } from './AgingReport';
import { IFinancialTable } from './Table'; import { INumberFormatQuery } from './FinancialStatements';
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery { export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
vendorsIds: number[]; vendorsIds: number[];
@@ -32,8 +34,3 @@ export interface IAPAgingSummaryMeta {
baseCurrency: string; baseCurrency: string;
organizationName: string; organizationName: string;
} }
export interface IAPAgingSummaryTable extends IFinancialTable {
query: IAPAgingSummaryQuery;
meta: IAPAgingSummaryMeta;
}

View File

@@ -5,7 +5,6 @@ import {
IAgingSummaryContact, IAgingSummaryContact,
IAgingSummaryData, IAgingSummaryData,
} from './AgingReport'; } from './AgingReport';
import { IFinancialTable } from './Table';
export interface IARAgingSummaryQuery extends IAgingSummaryQuery { export interface IARAgingSummaryQuery extends IAgingSummaryQuery {
customersIds: number[]; customersIds: number[];
@@ -27,8 +26,3 @@ export interface IARAgingSummaryMeta {
organizationName: string; organizationName: string;
baseCurrency: string; baseCurrency: string;
} }
export interface IARAgingSummaryTable extends IFinancialTable {
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
}

View File

@@ -3,14 +3,12 @@ import {
IFormatNumberSettings, IFormatNumberSettings,
IFinancialSheetBranchesQuery, IFinancialSheetBranchesQuery,
} from './FinancialStatements'; } from './FinancialStatements';
import { IFinancialTable } from './Table';
// Balance sheet schema nodes types. // Balance sheet schema nodes types.
export enum BALANCE_SHEET_SCHEMA_NODE_TYPE { export enum BALANCE_SHEET_SCHEMA_NODE_TYPE {
AGGREGATE = 'AGGREGATE', AGGREGATE = 'AGGREGATE',
ACCOUNTS = 'ACCOUNTS', ACCOUNTS = 'ACCOUNTS',
ACCOUNT = 'ACCOUNT', ACCOUNT = 'ACCOUNT',
NET_INCOME = 'NET_INCOME',
} }
export enum BALANCE_SHEET_NODE_TYPE { export enum BALANCE_SHEET_NODE_TYPE {
@@ -35,7 +33,6 @@ export enum BALANCE_SHEET_SCHEMA_NODE_ID {
LOGN_TERM_LIABILITY = 'LOGN_TERM_LIABILITY', LOGN_TERM_LIABILITY = 'LOGN_TERM_LIABILITY',
NON_CURRENT_LIABILITY = 'NON_CURRENT_LIABILITY', NON_CURRENT_LIABILITY = 'NON_CURRENT_LIABILITY',
EQUITY = 'EQUITY', EQUITY = 'EQUITY',
NET_INCOME = 'NET_INCOME',
} }
// Balance sheet query. // Balance sheet query.
@@ -90,6 +87,7 @@ export interface IBalanceSheetDOO {
meta: IBalanceSheetMeta; meta: IBalanceSheetMeta;
} }
export interface IBalanceSheetCommonNode { export interface IBalanceSheetCommonNode {
total: IBalanceSheetTotal; total: IBalanceSheetTotal;
horizontalTotals?: IBalanceSheetTotal[]; horizontalTotals?: IBalanceSheetTotal[];
@@ -110,7 +108,7 @@ export interface IBalanceSheetAggregateNode extends IBalanceSheetCommonNode {
id: string; id: string;
name: string; name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE; nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE;
children?: IBalanceSheetDataNode[]; children?: (IBalanceSheetAggregateNode | IBalanceSheetAccountNode)[];
} }
export interface IBalanceSheetTotal { export interface IBalanceSheetTotal {
@@ -120,13 +118,6 @@ export interface IBalanceSheetTotal {
date?: string | Date; date?: string | Date;
} }
export interface IBalanceSheetAccountsNode extends IBalanceSheetCommonNode {
id: number | string;
name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS;
children: IBalanceSheetAccountNode[];
}
export interface IBalanceSheetAccountNode extends IBalanceSheetCommonNode { export interface IBalanceSheetAccountNode extends IBalanceSheetCommonNode {
id: number; id: number;
index: number; index: number;
@@ -137,17 +128,7 @@ export interface IBalanceSheetAccountNode extends IBalanceSheetCommonNode {
children?: IBalanceSheetAccountNode[]; children?: IBalanceSheetAccountNode[];
} }
export interface IBalanceSheetNetIncomeNode extends IBalanceSheetCommonNode { export type IBalanceSheetDataNode = IBalanceSheetAggregateNode;
id: number;
name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME;
}
export type IBalanceSheetDataNode =
| IBalanceSheetAggregateNode
| IBalanceSheetAccountNode
| IBalanceSheetAccountsNode
| IBalanceSheetNetIncomeNode;
export interface IBalanceSheetPercentageAmount { export interface IBalanceSheetPercentageAmount {
amount: number; amount: number;
@@ -169,16 +150,9 @@ export interface IBalanceSheetSchemaAccountNode {
accountsTypes: string[]; accountsTypes: string[];
} }
export interface IBalanceSheetSchemaNetIncomeNode {
id: string;
name: string;
type: BALANCE_SHEET_SCHEMA_NODE_TYPE;
}
export type IBalanceSheetSchemaNode = export type IBalanceSheetSchemaNode =
| IBalanceSheetSchemaAccountNode | IBalanceSheetSchemaAccountNode
| IBalanceSheetSchemaAggregateNode | IBalanceSheetSchemaAggregateNode;
| IBalanceSheetSchemaNetIncomeNode;
export interface IBalanceSheetDatePeriods { export interface IBalanceSheetDatePeriods {
assocAccountNodeDatePeriods(node): any; assocAccountNodeDatePeriods(node): any;
@@ -216,8 +190,3 @@ export enum IAccountTransactionsGroupBy {
Month = 'month', Month = 'month',
Week = 'week', Week = 'week',
} }
export interface IBalanceSheetTable extends IFinancialTable {
meta: IBalanceSheetMeta;
query: IBalanceSheetQuery;
}

View File

@@ -2,7 +2,6 @@ import { Knex } from 'knex';
import { IDynamicListFilterDTO } from './DynamicFilter'; import { IDynamicListFilterDTO } from './DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IBillLandedCost } from './LandedCost'; import { IBillLandedCost } from './LandedCost';
export interface IBillDTO { export interface IBillDTO {
vendorId: number; vendorId: number;
billNumber: string; billNumber: string;
@@ -16,10 +15,10 @@ export interface IBillDTO {
exchangeRate?: number; exchangeRate?: number;
open: boolean; open: boolean;
entries: IItemEntryDTO[]; entries: IItemEntryDTO[];
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
projectId?: number; projectId?: number;
isInclusiveTax?: boolean;
} }
export interface IBillEditDTO { export interface IBillEditDTO {
@@ -81,15 +80,6 @@ export interface IBill {
localAmount?: number; localAmount?: number;
locatedLandedCosts?: IBillLandedCost[]; locatedLandedCosts?: IBillLandedCost[];
amountLocal: number;
subtotal: number;
subtotalLocal: number;
subtotalExcludingTax: number;
taxAmountWithheld: number;
taxAmountWithheldLocal: number;
total: number;
totalLocal: number;
} }
export interface IBillsFilter extends IDynamicListFilterDTO { export interface IBillsFilter extends IDynamicListFilterDTO {

View File

@@ -1,7 +1,7 @@
import { INumberFormatQuery } from './FinancialStatements'; import { INumberFormatQuery } from './FinancialStatements';
import { IAccount } from './Account'; import { IAccount } from './Account';
import { ILedger } from './Ledger'; import { ILedger } from './Ledger';
import { IFinancialTable, ITableRow } from './Table'; import { ITableRow } from './Table';
export interface ICashFlowStatementQuery { export interface ICashFlowStatementQuery {
fromDate: Date | string; fromDate: Date | string;
@@ -101,11 +101,6 @@ export interface ICashFlowStatementDOO {
query: ICashFlowStatementQuery; query: ICashFlowStatementQuery;
} }
export interface ICashFlowStatementTable extends IFinancialTable {
meta: ICashFlowStatementMeta;
query: ICashFlowStatementQuery;
}
export interface ICashFlowStatementService { export interface ICashFlowStatementService {
cashFlow( cashFlow(
tenantId: number, tenantId: number,

View File

@@ -1,10 +1,11 @@
import { INumberFormatQuery } from './FinancialStatements';
import { import {
IContactBalanceSummaryQuery, IContactBalanceSummaryQuery,
IContactBalanceSummaryAmount, IContactBalanceSummaryAmount,
IContactBalanceSummaryPercentage, IContactBalanceSummaryPercentage,
IContactBalanceSummaryTotal, IContactBalanceSummaryTotal,
} from './ContactBalanceSummary'; } from './ContactBalanceSummary';
import { IFinancialTable } from './Table';
export interface ICustomerBalanceSummaryQuery export interface ICustomerBalanceSummaryQuery
extends IContactBalanceSummaryQuery { extends IContactBalanceSummaryQuery {
@@ -18,7 +19,7 @@ export interface ICustomerBalanceSummaryPercentage
extends IContactBalanceSummaryPercentage {} extends IContactBalanceSummaryPercentage {}
export interface ICustomerBalanceSummaryCustomer { export interface ICustomerBalanceSummaryCustomer {
id: number; id: number,
customerName: string; customerName: string;
total: ICustomerBalanceSummaryAmount; total: ICustomerBalanceSummaryAmount;
percentageOfColumn?: ICustomerBalanceSummaryPercentage; percentageOfColumn?: ICustomerBalanceSummaryPercentage;
@@ -46,7 +47,3 @@ export interface ICustomerBalanceSummaryService {
query: ICustomerBalanceSummaryQuery query: ICustomerBalanceSummaryQuery
): Promise<ICustomerBalanceSummaryStatement>; ): Promise<ICustomerBalanceSummaryStatement>;
} }
export interface ICustomerBalanceSummaryTable extends IFinancialTable {
query: ICustomerBalanceSummaryQuery;
}

View File

@@ -1,10 +1,36 @@
export interface ExchangeRateLatestDTO { import { IFilterRole } from './DynamicFilter';
toCurrency: string;
fromCurrency: string;
}
export interface EchangeRateLatestPOJO { export interface IExchangeRate {
baseCurrency: string; id: number,
toCurrency: string; currencyCode: string,
exchangeRate: number; exchangeRate: number,
} date: Date,
createdAt: Date,
updatedAt: Date,
};
export interface IExchangeRateDTO {
currencyCode: string,
exchangeRate: number,
date: Date,
};
export interface IExchangeRateEditDTO {
exchangeRate: number,
};
export interface IExchangeRateFilter {
page: number,
pageSize: number,
filterRoles?: IFilterRole[];
columnSortBy: string;
sortOrder: string;
};
export interface IExchangeRatesService {
newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<IExchangeRate>;
editExchangeRate(tenantId: number, exchangeRateId: number, editExRateDTO: IExchangeRateEditDTO): Promise<void>;
deleteExchangeRate(tenantId: number, exchangeRateId: number): Promise<void>;
listExchangeRates(tenantId: number, exchangeRateFilter: IExchangeRateFilter): Promise<void>;
};

View File

@@ -1,4 +1,3 @@
import { IFinancialTable } from "./Table";
export interface IGeneralLedgerSheetQuery { export interface IGeneralLedgerSheetQuery {
@@ -37,7 +36,6 @@ export interface IGeneralLedgerSheetAccountTransaction {
referenceType?: string, referenceType?: string,
date: Date|string, date: Date|string,
dateFormatted: string;
}; };
export interface IGeneralLedgerSheetAccountBalance { export interface IGeneralLedgerSheetAccountBalance {
@@ -58,8 +56,6 @@ export interface IGeneralLedgerSheetAccount {
closingBalance: IGeneralLedgerSheetAccountBalance, closingBalance: IGeneralLedgerSheetAccountBalance,
} }
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
export interface IAccountTransaction { export interface IAccountTransaction {
id: number, id: number,
index: number, index: number,
@@ -82,11 +78,4 @@ export interface IGeneralLedgerMeta {
isCostComputeRunning: boolean, isCostComputeRunning: boolean,
organizationName: string, organizationName: string,
baseCurrency: string, baseCurrency: string,
fromDate: string; };
toDate: string;
};
export interface IGeneralLedgerTableData extends IFinancialTable {
meta: IGeneralLedgerMeta;
query: IGeneralLedgerSheetQuery;
}

View File

@@ -1,7 +0,0 @@
export const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
APPLICATION_JSON_TABLE: 'application/json+table',
APPLICATION_XLSX: 'application/xlsx',
APPLICATION_CSV: 'application/csv',
};

View File

@@ -1,5 +1,4 @@
import { INumberFormatQuery } from './FinancialStatements'; import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IInventoryValuationReportQuery { export interface IInventoryValuationReportQuery {
asDate: Date | string; asDate: Date | string;
@@ -40,19 +39,9 @@ export interface IInventoryValuationTotal {
quantityFormatted: string; quantityFormatted: string;
} }
export type IInventoryValuationStatement = { export type IInventoryValuationStatement =
items: IInventoryValuationItem[]; | {
total: IInventoryValuationTotal; items: IInventoryValuationItem[];
}; total: IInventoryValuationTotal;
export type IInventoryValuationSheetData = IInventoryValuationStatement; }
| {};
export interface IInventoryValuationSheet {
data: IInventoryValuationStatement;
meta: IInventoryValuationSheetMeta;
query: IInventoryValuationReportQuery;
}
export interface IInventoryValuationTable extends IFinancialTable {
meta: IInventoryValuationSheetMeta;
query: IInventoryValuationReportQuery;
}

View File

@@ -1,12 +1,13 @@
import { INumberFormatQuery } from './FinancialStatements'; import {
import { IFinancialTable } from './Table'; INumberFormatQuery,
} from './FinancialStatements';
export interface IInventoryDetailsQuery { export interface IInventoryDetailsQuery {
fromDate: Date | string; fromDate: Date | string;
toDate: Date | string; toDate: Date | string;
numberFormat: INumberFormatQuery; numberFormat: INumberFormatQuery;
noneTransactions: boolean; noneTransactions: boolean;
itemsIds: number[]; itemsIds: number[]
warehousesIds?: number[]; warehousesIds?: number[];
branchesIds?: number[]; branchesIds?: number[];
@@ -65,7 +66,7 @@ export interface IInventoryDetailsItemTransaction {
cost: IInventoryDetailsNumber; cost: IInventoryDetailsNumber;
value: IInventoryDetailsNumber; value: IInventoryDetailsNumber;
profitMargin: IInventoryDetailsNumber; profitMargin: IInventoryDetailsNumber;
rate: IInventoryDetailsNumber; rate: IInventoryDetailsNumber;
runningQuantity: IInventoryDetailsNumber; runningQuantity: IInventoryDetailsNumber;
@@ -79,6 +80,7 @@ export type IInventoryDetailsNode =
| IInventoryDetailsItemTransaction; | IInventoryDetailsItemTransaction;
export type IInventoryDetailsData = IInventoryDetailsItem[]; export type IInventoryDetailsData = IInventoryDetailsItem[];
export interface IInventoryItemDetailMeta { export interface IInventoryItemDetailMeta {
isCostComputeRunning: boolean; isCostComputeRunning: boolean;
organizationName: string; organizationName: string;
@@ -89,9 +91,4 @@ export interface IInvetoryItemDetailDOO {
data: IInventoryDetailsData; data: IInventoryDetailsData;
query: IInventoryDetailsQuery; query: IInventoryDetailsQuery;
meta: IInventoryItemDetailMeta; meta: IInventoryItemDetailMeta;
} }
export interface IInvetoryItemDetailsTable extends IFinancialTable {
query: IInventoryDetailsQuery;
meta: IInventoryItemDetailMeta;
}

View File

@@ -22,9 +22,6 @@ export interface IItem {
sellDescription: string; sellDescription: string;
purchaseDescription: string; purchaseDescription: string;
sellTaxRateId: number;
purchaseTaxRateId: number;
quantityOnHand: number; quantityOnHand: number;
note: string; note: string;
@@ -57,9 +54,6 @@ export interface IItemDTO {
sellDescription: string; sellDescription: string;
purchaseDescription: string; purchaseDescription: string;
sellTaxRateId: number;
purchaseTaxRateId: number;
quantityOnHand: number; quantityOnHand: number;
note: string; note: string;

View File

@@ -1,52 +1,36 @@
import { IJournalEntry } from './Journal'; import { IJournalEntry } from './Journal';
import { IFinancialTable } from './Table';
export interface IJournalReportQuery { export interface IJournalReportQuery {
fromDate: Date | string; fromDate: Date | string,
toDate: Date | string; toDate: Date | string,
numberFormat: { numberFormat: {
noCents: boolean; noCents: boolean,
divideOn1000: boolean; divideOn1000: boolean,
}; },
transactionType: string; transactionType: string,
transactionId: string; transactionId: string,
accountsIds: number | number[]; accountsIds: number | number[],
fromRange: number; fromRange: number,
toRange: number; toRange: number,
} }
export interface IJournalReportEntriesGroup { export interface IJournalReportEntriesGroup {
id: string; id: string,
date: Date; entries: IJournalEntry[],
dateFormatted: string; currencyCode: string,
entries: IJournalEntry[]; credit: number,
currencyCode: string; debit: number,
credit: number; formattedCredit: string,
debit: number; formattedDebit: string,
formattedCredit: string;
formattedDebit: string;
} }
export interface IJournalReport { export interface IJournalReport {
entries: IJournalReportEntriesGroup[]; entries: IJournalReportEntriesGroup[],
} }
export interface IJournalSheetMeta { export interface IJournalSheetMeta {
isCostComputeRunning: boolean; isCostComputeRunning: boolean,
organizationName: string; organizationName: string,
baseCurrency: string; baseCurrency: string,
} }
export interface IJournalTable extends IFinancialTable {
query: IJournalReportQuery;
meta: IJournalSheetMeta;
}
export type IJournalTableData = IJournalReportEntriesGroup[];
export interface IJournalSheet {
data: IJournalTableData;
query: IJournalReportQuery;
meta: IJournalSheetMeta;
}

View File

@@ -7,25 +7,18 @@ export interface ILedger {
filter(cb: (entry: ILedgerEntry) => boolean): ILedger; filter(cb: (entry: ILedgerEntry) => boolean): ILedger;
whereAccountId(accountId: number): ILedger; whereAccountId(accountId: number): ILedger;
whereAccountsIds(accountsIds: number[]): ILedger;
whereContactId(contactId: number): ILedger; whereContactId(contactId: number): ILedger;
whereFromDate(fromDate: Date | string): ILedger; whereFromDate(fromDate: Date | string): ILedger;
whereToDate(toDate: Date | string): ILedger; whereToDate(toDate: Date | string): ILedger;
whereCurrencyCode(currencyCode: string): ILedger; whereCurrencyCode(currencyCode: string): ILedger;
whereBranch(branchId: number): ILedger; whereBranch(branchId: number): ILedger;
whereItem(itemId: number): ILedger; whereItem(itemId: number): ILedger;
whereProject(projectId: number): ILedger;
getClosingBalance(): number; getClosingBalance(): number;
getForeignClosingBalance(): number; getForeignClosingBalance(): number;
getClosingDebit(): number;
getClosingCredit(): number;
getContactsIds(): number[]; getContactsIds(): number[];
getAccountsIds(): number[]; getAccountsIds(): number[];
reverse(): ILedger;
isEmpty(): boolean;
} }
export interface ILedgerEntry { export interface ILedgerEntry {

View File

@@ -1,17 +1,9 @@
export type IMailAttachment = MailAttachmentPath | MailAttachmentContent;
export interface MailAttachmentPath {
filename: string;
path: string;
cid: string;
}
export interface MailAttachmentContent {
filename: string;
content: Buffer;
}
export interface IMailable { export interface IMailable {
constructor(view: string, data?: { [key: string]: string | number }); constructor(
view: string,
data?: { [key: string]: string | number },
);
send(): Promise<any>; send(): Promise<any>;
build(): void; build(): void;
setData(data: { [key: string]: string | number }): IMailable; setData(data: { [key: string]: string | number }): IMailable;
@@ -21,27 +13,4 @@ export interface IMailable {
setView(view: string): IMailable; setView(view: string): IMailable;
render(data?: { [key: string]: string | number }): string; render(data?: { [key: string]: string | number }): string;
getViewContent(): string; getViewContent(): string;
} }
export interface AddressItem {
label: string;
mail: string;
primary?: boolean;
}
export interface CommonMailOptions {
toAddresses: AddressItem[];
fromAddresses: AddressItem[];
from: string;
to: string | string[];
subject: string;
body: string;
data?: Record<string, any>;
}
export interface CommonMailOptionsDTO {
to?: string | string[];
from?: string;
subject?: string;
body?: string;
}

View File

@@ -1,9 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import { ISystemUser } from '@/interfaces';
CommonMailOptions,
CommonMailOptionsDTO,
ISystemUser,
} from '@/interfaces';
import { ILedgerEntry } from './Ledger'; import { ILedgerEntry } from './Ledger';
import { ISaleInvoice } from './SaleInvoice'; import { ISaleInvoice } from './SaleInvoice';
@@ -23,7 +19,7 @@ export interface IPaymentReceive {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
localAmount?: number; localAmount?: number;
branchId?: number; branchId?: number
} }
export interface IPaymentReceiveCreateDTO { export interface IPaymentReceiveCreateDTO {
customerId: number; customerId: number;
@@ -169,7 +165,3 @@ export type IPaymentReceiveGLCommonEntry = Pick<
| 'createdAt' | 'createdAt'
| 'branchId' | 'branchId'
>; >;
export interface PaymentReceiveMailOpts extends CommonMailOptions {}
export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {}

View File

@@ -2,7 +2,6 @@ import {
IFinancialSheetBranchesQuery, IFinancialSheetBranchesQuery,
INumberFormatQuery, INumberFormatQuery,
} from './FinancialStatements'; } from './FinancialStatements';
import { IFinancialTable } from './Table';
export enum ProfitLossAggregateNodeId { export enum ProfitLossAggregateNodeId {
INCOME = 'INCOME', INCOME = 'INCOME',
@@ -178,9 +177,3 @@ export enum ProfitLossSheetRowType {
ACCOUNT = 'ACCOUNT', ACCOUNT = 'ACCOUNT',
TOTAL = 'TOTAL', TOTAL = 'TOTAL',
} }
export interface IProfitLossSheetTable extends IFinancialTable{
meta: IProfitLossSheetMeta;
query: IProfitLossSheetQuery;
}

View File

@@ -1,54 +0,0 @@
import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IPurchasesByItemsReportQuery {
fromDate: Date | string;
toDate: Date | string;
itemsIds: number[];
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
onlyActive: boolean;
}
export interface IPurchasesByItemsSheetMeta {
organizationName: string;
baseCurrency: string;
}
export interface IPurchasesByItemsItem {
id: number;
name: string;
code: string;
quantitySold: number;
soldCost: number;
averageSellPrice: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
averageSellPriceFormatted: string;
currencyCode: string;
}
export interface IPurchasesByItemsTotal {
quantitySold: number;
soldCost: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
currencyCode: string;
}
export type IPurchasesByItemsSheetData = {
items: IPurchasesByItemsItem[];
total: IPurchasesByItemsTotal;
};
export interface IPurchasesByItemsSheet {
data: IPurchasesByItemsSheetData;
query: IPurchasesByItemsReportQuery;
meta: IPurchasesByItemsSheetMeta;
}
export interface IPurchasesByItemsTable extends IFinancialTable {
query: IPurchasesByItemsReportQuery;
meta: IPurchasesByItemsSheetMeta;
}

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import { Ability, RawRuleOf, ForcedSubject } from '@casl/ability'; import { Ability, RawRuleOf, ForcedSubject } from '@casl/ability';
import Knex from 'knex';
export const actions = [ export const actions = [
'manage', 'manage',
@@ -96,8 +96,7 @@ export enum AbilitySubject {
Preferences = 'Preferences', Preferences = 'Preferences',
CreditNote = 'CreditNode', CreditNote = 'CreditNode',
VendorCredit = 'VendorCredit', VendorCredit = 'VendorCredit',
Project = 'Project', Project = 'Project'
TaxRate = 'TaxRate'
} }
export interface IRoleCreatedPayload { export interface IRoleCreatedPayload {

View File

@@ -1,7 +1,6 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter'; import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
export interface ISaleEstimate { export interface ISaleEstimate {
id?: number; id?: number;
@@ -125,11 +124,3 @@ export interface ISaleEstimateApprovedEvent {
saleEstimate: ISaleEstimate; saleEstimate: ISaleEstimate;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface SaleEstimateMailOptions extends CommonMailOptions {
attachEstimate?: boolean;
}
export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO {
attachEstimate?: boolean;
}

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces'; import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
import { IDynamicListFilter } from '@/interfaces/DynamicFilter'; import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
@@ -187,17 +186,3 @@ export enum SaleInvoiceAction {
Writeoff = 'Writeoff', Writeoff = 'Writeoff',
NotifyBySms = 'NotifyBySms', NotifyBySms = 'NotifyBySms',
} }
export interface SaleInvoiceMailOptions extends CommonMailOptions {
attachInvoice: boolean;
}
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
attachInvoice?: boolean;
}
export interface ISaleInvoiceNotifyPayload {
tenantId: number;
saleInvoiceId: number;
messageDTO: SendInvoiceMailDTO;
}

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntry } from './ItemEntry'; import { IItemEntry } from './ItemEntry';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
export interface ISaleReceipt { export interface ISaleReceipt {
id?: number; id?: number;
@@ -135,11 +134,3 @@ export interface ISaleReceiptDeletingPayload {
oldSaleReceipt: ISaleReceipt; oldSaleReceipt: ISaleReceipt;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface SaleReceiptMailOpts extends CommonMailOptions {
attachReceipt: boolean;
}
export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO {
attachReceipt?: boolean;
}

View File

@@ -1,54 +1,45 @@
import { INumberFormatQuery } from './FinancialStatements'; import {
import { IFinancialTable } from './Table'; INumberFormatQuery,
} from './FinancialStatements';
export interface ISalesByItemsReportQuery { export interface ISalesByItemsReportQuery {
fromDate: Date | string; fromDate: Date | string;
toDate: Date | string; toDate: Date | string;
itemsIds: number[]; itemsIds: number[],
numberFormat: INumberFormatQuery; numberFormat: INumberFormatQuery;
noneTransactions: boolean; noneTransactions: boolean;
onlyActive: boolean; onlyActive: boolean;
}
export interface ISalesByItemsSheetMeta {
organizationName: string;
baseCurrency: string;
}
export interface ISalesByItemsItem {
id: number;
name: string;
code: string;
quantitySold: number;
soldCost: number;
averageSellPrice: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
averageSellPriceFormatted: string;
currencyCode: string;
}
export interface ISalesByItemsTotal {
quantitySold: number;
soldCost: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
currencyCode: string;
}
export type ISalesByItemsSheetData = {
items: ISalesByItemsItem[];
total: ISalesByItemsTotal;
}; };
export interface ISalesByItemsSheet { export interface ISalesByItemsSheetMeta {
data: ISalesByItemsSheetData; organizationName: string,
query: ISalesByItemsReportQuery; baseCurrency: string,
meta: ISalesByItemsSheetMeta; };
}
export interface ISalesByItemsItem {
id: number,
name: string,
code: string,
quantitySold: number,
soldCost: number,
averageSellPrice: number,
quantitySoldFormatted: string,
soldCostFormatted: string,
averageSellPriceFormatted: string,
currencyCode: string,
};
export interface ISalesByItemsTotal {
quantitySold: number,
soldCost: number,
quantitySoldFormatted: string,
soldCostFormatted: string,
currencyCode: string,
};
export type ISalesByItemsSheetStatement = {
items: ISalesByItemsItem[],
total: ISalesByItemsTotal
} | {};
export interface ISalesByItemsTable extends IFinancialTable {
query: ISalesByItemsReportQuery;
meta: ISalesByItemsSheetMeta;
}

View File

@@ -1,5 +1,3 @@
import { IFinancialTable } from "./Table";
export interface SalesTaxLiabilitySummaryQuery { export interface SalesTaxLiabilitySummaryQuery {
fromDate: Date; fromDate: Date;
toDate: Date; toDate: Date;
@@ -51,8 +49,3 @@ export interface SalesTaxLiabilitySummaryMeta {
organizationName: string; organizationName: string;
baseCurrency: string; baseCurrency: string;
} }
export interface ISalesTaxLiabilitySummaryTable extends IFinancialTable {
query: SalesTaxLiabilitySummaryQuery;
meta: SalesTaxLiabilitySummaryMeta;
}

View File

@@ -25,7 +25,6 @@ export interface IOrganizationUpdateDTO {
timezone: string; timezone: string;
fiscalYear: string; fiscalYear: string;
industry: string; industry: string;
taxNumber: string;
} }
export interface IOrganizationBuildEventPayload { export interface IOrganizationBuildEventPayload {

View File

@@ -10,7 +10,7 @@ export interface ITableCell {
} }
export type ITableRow = { export type ITableRow = {
cells: ITableCell[]; rows: ITableCell[];
}; };
export interface ITableColumn { export interface ITableColumn {
@@ -28,13 +28,4 @@ export interface ITable {
export interface ITableColumnAccessor { export interface ITableColumnAccessor {
key: string; key: string;
accessor: string; accessor: string;
} }
export interface ITableData {
columns: ITableColumn[];
rows: ITableRow[];
}
export interface IFinancialTable {
table: ITableData;
}

View File

@@ -36,7 +36,6 @@ export interface ITaxRateCreatedPayload {
} }
export interface ITaxRateEditingPayload { export interface ITaxRateEditingPayload {
oldTaxRate: ITaxRate;
editTaxRateDTO: IEditTaxRateDTO; editTaxRateDTO: IEditTaxRateDTO;
tenantId: number; tenantId: number;
trx: Knex.Transaction; trx: Knex.Transaction;

View File

@@ -1,4 +1,3 @@
import { IFinancialTable, ITableData } from './Table';
import { import {
ITransactionsByContactsAmount, ITransactionsByContactsAmount,
ITransactionsByContactsTransaction, ITransactionsByContactsTransaction,
@@ -27,11 +26,6 @@ export type ITransactionsByCustomersData = ITransactionsByCustomersCustomer[];
export interface ITransactionsByCustomersStatement { export interface ITransactionsByCustomersStatement {
data: ITransactionsByCustomersData; data: ITransactionsByCustomersData;
query: ITransactionsByCustomersFilter;
}
export interface ITransactionsByCustomersTable extends IFinancialTable {
query: ITransactionsByCustomersFilter;
} }
export interface ITransactionsByCustomersService { export interface ITransactionsByCustomersService {

View File

@@ -1,4 +1,3 @@
import { IFinancialTable } from './Table';
import { import {
ITransactionsByContactsAmount, ITransactionsByContactsAmount,
ITransactionsByContactsTransaction, ITransactionsByContactsTransaction,
@@ -35,7 +34,3 @@ export interface ITransactionsByVendorsService {
filter: ITransactionsByVendorsFilter filter: ITransactionsByVendorsFilter
): Promise<ITransactionsByVendorsStatement>; ): Promise<ITransactionsByVendorsStatement>;
} }
export interface ITransactionsByVendorTable extends IFinancialTable {
query: ITransactionsByVendorsFilter;
}

View File

@@ -1,5 +1,4 @@
import { INumberFormatQuery } from './FinancialStatements'; import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface ITrialBalanceSheetQuery { export interface ITrialBalanceSheetQuery {
fromDate: Date | string; fromDate: Date | string;
@@ -34,7 +33,6 @@ export interface ITrialBalanceAccount extends ITrialBalanceTotal {
id: number; id: number;
parentAccountId: number; parentAccountId: number;
name: string; name: string;
formattedName: string;
code: string; code: string;
accountNormal: string; accountNormal: string;
} }
@@ -49,8 +47,3 @@ export interface ITrialBalanceStatement {
query: ITrialBalanceSheetQuery; query: ITrialBalanceSheetQuery;
meta: ITrialBalanceSheetMeta; meta: ITrialBalanceSheetMeta;
} }
export interface ITrialBalanceSheetTable extends IFinancialTable {
meta: ITrialBalanceSheetMeta;
query: ITrialBalanceSheetQuery;
}

View File

@@ -1,9 +1,8 @@
import { INumberFormatQuery } from './FinancialStatements'; import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IVendorBalanceSummaryQuery { export interface IVendorBalanceSummaryQuery {
asDate: Date; asDate: Date;
vendorsIds: number[]; vendorsIds: number[],
numberFormat: INumberFormatQuery; numberFormat: INumberFormatQuery;
percentageColumn: boolean; percentageColumn: boolean;
noneTransactions: boolean; noneTransactions: boolean;
@@ -46,10 +45,6 @@ export interface IVendorBalanceSummaryStatement {
export interface IVendorBalanceSummaryService { export interface IVendorBalanceSummaryService {
vendorBalanceSummary( vendorBalanceSummary(
tenantId: number, tenantId: number,
query: IVendorBalanceSummaryQuery query: IVendorBalanceSummaryQuery,
): Promise<IVendorBalanceSummaryStatement>; ): Promise<IVendorBalanceSummaryStatement>;
} }
export interface IVendorBalanceSummaryTable extends IFinancialTable {
query: IVendorBalanceSummaryQuery;
}

View File

@@ -1,26 +0,0 @@
import { ChromiumRoute, LibreOfficeRoute, PdfEngineRoute } from './_types';
export class Chromiumly {
public static readonly GOTENBERG_ENDPOINT = process.env.GOTENBERG_URL || '';
public static readonly CHROMIUM_PATH = 'forms/chromium/convert';
public static readonly PDF_ENGINES_PATH = 'forms/pdfengines';
public static readonly LIBRE_OFFICE_PATH = 'forms/libreoffice';
public static readonly GOTENBERG_DOCS_ENDPOINT =
process.env.GOTENBERG_DOCS_URL || '';
public static readonly CHROMIUM_ROUTES = {
url: ChromiumRoute.URL,
html: ChromiumRoute.HTML,
markdown: ChromiumRoute.MARKDOWN,
};
public static readonly PDF_ENGINE_ROUTES = {
merge: PdfEngineRoute.MERGE,
};
public static readonly LIBRE_OFFICE_ROUTES = {
convert: LibreOfficeRoute.CONVERT,
};
}

View File

@@ -1,66 +0,0 @@
import FormData from 'form-data';
import { GotenbergUtils } from './GotenbergUtils';
import { PageProperties } from './_types';
export class ConverterUtils {
public static injectPageProperties(
data: FormData,
pageProperties: PageProperties
): void {
if (pageProperties.size) {
GotenbergUtils.assert(
pageProperties.size.width >= 1.0 && pageProperties.size.height >= 1.5,
'size is smaller than the minimum printing requirements (i.e. 1.0 x 1.5 in)'
);
data.append('paperWidth', pageProperties.size.width);
data.append('paperHeight', pageProperties.size.height);
}
if (pageProperties.margins) {
GotenbergUtils.assert(
pageProperties.margins.top >= 0 &&
pageProperties.margins.bottom >= 0 &&
pageProperties.margins.left >= 0 &&
pageProperties.margins.left >= 0,
'negative margins are not allowed'
);
data.append('marginTop', pageProperties.margins.top);
data.append('marginBottom', pageProperties.margins.bottom);
data.append('marginLeft', pageProperties.margins.left);
data.append('marginRight', pageProperties.margins.right);
}
if (pageProperties.preferCssPageSize) {
data.append(
'preferCssPageSize',
String(pageProperties.preferCssPageSize)
);
}
if (pageProperties.printBackground) {
data.append('printBackground', String(pageProperties.printBackground));
}
if (pageProperties.landscape) {
data.append('landscape', String(pageProperties.landscape));
}
if (pageProperties.scale) {
GotenbergUtils.assert(
pageProperties.scale >= 0.1 && pageProperties.scale <= 2.0,
'scale is outside of [0.1 - 2] range'
);
data.append('scale', pageProperties.scale);
}
if (pageProperties.nativePageRanges) {
GotenbergUtils.assert(
pageProperties.nativePageRanges.from > 0 &&
pageProperties.nativePageRanges.to > 0 &&
pageProperties.nativePageRanges.to >=
pageProperties.nativePageRanges.from,
'page ranges syntax error'
);
data.append(
'nativePageRanges',
`${pageProperties.nativePageRanges.from}-${pageProperties.nativePageRanges.to}`
);
}
}
}

View File

@@ -1,10 +0,0 @@
import { Chromiumly } from './Chromiumly';
import { ChromiumRoute } from './_types';
export abstract class Converter {
readonly endpoint: string;
constructor(route: ChromiumRoute) {
this.endpoint = `${Chromiumly.GOTENBERG_ENDPOINT}/${Chromiumly.CHROMIUM_PATH}/${Chromiumly.CHROMIUM_ROUTES[route]}`;
}
}

View File

@@ -1,24 +0,0 @@
import FormData from 'form-data';
import Axios from 'axios';
export class GotenbergUtils {
public static assert(condition: boolean, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
try {
const response = await Axios.post(endpoint, data, {
headers: {
...data.getHeaders(),
},
responseType: 'arraybuffer', // This ensures you get a Buffer bac
});
return response.data;
} catch (error) {
console.error(error);
}
}
}

View File

@@ -1,38 +0,0 @@
import { constants, createReadStream, PathLike, promises } from 'fs';
import FormData from 'form-data';
import { GotenbergUtils } from './GotenbergUtils';
import { IConverter, PageProperties } from './_types';
import { PdfFormat, ChromiumRoute } from './_types';
import { ConverterUtils } from './ConvertUtils';
import { Converter } from './Converter';
export class HtmlConverter extends Converter implements IConverter {
constructor() {
super(ChromiumRoute.HTML);
}
async convert({
html,
properties,
pdfFormat,
}: {
html: PathLike;
properties?: PageProperties;
pdfFormat?: PdfFormat;
}): Promise<Buffer> {
try {
await promises.access(html, constants.R_OK);
const data = new FormData();
if (pdfFormat) {
data.append('pdfFormat', pdfFormat);
}
data.append('index.html', createReadStream(html));
if (properties) {
ConverterUtils.injectPageProperties(data, properties);
}
return GotenbergUtils.fetch(this.endpoint, data);
} catch (error) {
throw error;
}
}
}

View File

@@ -1,38 +0,0 @@
import FormData from 'form-data';
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
import { ConverterUtils } from './ConvertUtils';
import { Converter } from './Converter';
import { GotenbergUtils } from './GotenbergUtils';
export class UrlConverter extends Converter implements IConverter {
constructor() {
super(ChromiumRoute.URL);
}
async convert({
url,
properties,
pdfFormat,
}: {
url: string;
properties?: PageProperties;
pdfFormat?: PdfFormat;
}): Promise<Buffer> {
try {
const _url = new URL(url);
const data = new FormData();
if (pdfFormat) {
data.append('pdfFormat', pdfFormat);
}
data.append('url', _url.href);
if (properties) {
ConverterUtils.injectPageProperties(data, properties);
}
return GotenbergUtils.fetch(this.endpoint, data);
} catch (error) {
throw error;
}
}
}

View File

@@ -1,51 +0,0 @@
import { PathLike } from 'fs';
export type PageSize = {
width: number; // Paper width, in inches (default 8.5)
height: number; //Paper height, in inches (default 11)
};
export type PageMargins = {
top: number; // Top margin, in inches (default 0.39)
bottom: number; // Bottom margin, in inches (default 0.39)
left: number; // Left margin, in inches (default 0.39)
right: number; // Right margin, in inches (default 0.39)
};
export type PageProperties = {
size?: PageSize;
margins?: PageMargins;
preferCssPageSize?: boolean; // Define whether to prefer page size as defined by CSS (default false)
printBackground?: boolean; // Print the background graphics (default false)
landscape?: boolean; // Set the paper orientation to landscape (default false)
scale?: number; // The scale of the page rendering (default 1.0)
nativePageRanges?: { from: number; to: number }; // Page ranges to print
};
export interface IConverter {
convert({
...args
}: {
[x: string]: string | PathLike | PageProperties | PdfFormat;
}): Promise<Buffer>;
}
export enum PdfFormat {
A_1a = 'PDF/A-1a',
A_2b = 'PDF/A-2b',
A_3b = 'PDF/A-3b',
}
export enum ChromiumRoute {
URL = 'url',
HTML = 'html',
MARKDOWN = 'markdown',
}
export enum PdfEngineRoute {
MERGE = 'merge',
}
export enum LibreOfficeRoute {
CONVERT = 'convert',
}

View File

@@ -1,45 +0,0 @@
import { OpenExchangeRate } from './OpenExchangeRate';
import { ExchangeRateServiceType, IExchangeRateService } from './types';
export class ExchangeRate {
private exchangeRateService: IExchangeRateService;
private exchangeRateServiceType: ExchangeRateServiceType;
/**
* Constructor method.
* @param {ExchangeRateServiceType} service
*/
constructor(service: ExchangeRateServiceType) {
this.exchangeRateServiceType = service;
this.initService();
}
/**
* Initialize the exchange rate service based on the service type.
*/
private initService() {
if (
this.exchangeRateServiceType === ExchangeRateServiceType.OpenExchangeRate
) {
this.setExchangeRateService(new OpenExchangeRate());
}
}
/**
* Sets the exchange rate service.
* @param {IExchangeRateService} service
*/
private setExchangeRateService(service: IExchangeRateService) {
this.exchangeRateService = service;
}
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {number}
*/
public latest(baseCurrency: string, toCurrency: string): Promise<number> {
return this.exchangeRateService.latest(baseCurrency, toCurrency);
}
}

View File

@@ -1,81 +0,0 @@
import Axios, { AxiosError } from 'axios';
import {
EchangeRateErrors,
IExchangeRateService,
OPEN_EXCHANGE_RATE_LATEST_URL,
} from './types';
import config from '@/config';
import { ServiceError } from '@/exceptions';
export class OpenExchangeRate implements IExchangeRateService {
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {Promise<number}
*/
public async latest(
baseCurrency: string,
toCurrency: string
): Promise<number> {
// Vaclidates the Open Exchange Rate api id early.
this.validateApiIdExistance();
try {
const result = await Axios.get(OPEN_EXCHANGE_RATE_LATEST_URL, {
params: {
app_id: config.exchangeRate.openExchangeRate.appId,
base: baseCurrency,
symbols: toCurrency,
},
});
return result.data.rates[toCurrency] || (1 as number);
} catch (error) {
this.handleLatestErrors(error);
}
}
/**
* Validates the Open Exchange Rate api id.
* @throws {ServiceError}
*/
private validateApiIdExistance() {
const apiId = config.exchangeRate.openExchangeRate.appId;
if (!apiId) {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
}
}
/**
* Handles the latest errors.
* @param {any} error
* @throws {ServiceError}
*/
private handleLatestErrors(error: any) {
if (error.response.data?.message === 'missing_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'invalid_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'not_allowed') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
'Getting the exchange rate from the given base currency to the given currency is not allowed.'
);
} else if (error.response.data?.message === 'invalid_base') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
'The given base currency is invalid.'
);
}
}
}

View File

@@ -1,17 +0,0 @@
export interface IExchangeRateService {
latest(baseCurrency: string, toCurrency: string): Promise<number>;
}
export enum ExchangeRateServiceType {
OpenExchangeRate = 'OpenExchangeRate',
}
export enum EchangeRateErrors {
EX_RATE_SERVICE_NOT_ALLOWED = 'EX_RATE_SERVICE_NOT_ALLOWED',
EX_RATE_LIMIT_EXCEEDED = 'EX_RATE_LIMIT_EXCEEDED',
EX_RATE_SERVICE_API_KEY_REQUIRED = 'EX_RATE_SERVICE_API_KEY_REQUIRED',
EX_RATE_INVALID_BASE_CURRENCY = 'EX_RATE_INVALID_BASE_CURRENCY',
}
export const OPEN_EXCHANGE_RATE_LATEST_URL =
'https://openexchangerates.org/api/latest.json';

View File

@@ -2,13 +2,18 @@ import fs from 'fs';
import Mustache from 'mustache'; import Mustache from 'mustache';
import { Container } from 'typedi'; import { Container } from 'typedi';
import path from 'path'; import path from 'path';
import { IMailAttachment } from '@/interfaces'; import { IMailable } from '@/interfaces';
interface IMailAttachment {
filename: string;
path: string;
cid: string;
}
export default class Mail { export default class Mail {
view: string; view: string;
subject: string = ''; subject: string;
content: string = ''; to: string;
to: string | string[];
from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`; from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`;
data: { [key: string]: string | number }; data: { [key: string]: string | number };
attachments: IMailAttachment[]; attachments: IMailAttachment[];
@@ -16,24 +21,16 @@ export default class Mail {
/** /**
* Mail options. * Mail options.
*/ */
public get mailOptions() { private get mailOptions() {
return { return {
to: this.to, to: this.to,
from: this.from, from: this.from,
subject: this.subject, subject: this.subject,
html: this.html, html: this.render(this.data),
attachments: this.attachments, attachments: this.attachments,
}; };
} }
/**
* Retrieves the html content of the mail.
* @returns {string}
*/
public get html() {
return this.view ? Mail.render(this.view, this.data) : this.content;
}
/** /**
* Sends the given mail to the target address. * Sends the given mail to the target address.
*/ */
@@ -55,7 +52,7 @@ export default class Mail {
* Set send mail to address. * Set send mail to address.
* @param {string} to - * @param {string} to -
*/ */
setTo(to: string | string[]) { setTo(to: string) {
this.to = to; this.to = to;
return this; return this;
} }
@@ -65,16 +62,11 @@ export default class Mail {
* @param {string} from * @param {string} from
* @return {} * @return {}
*/ */
setFrom(from: string) { private setFrom(from: string) {
this.from = from; this.from = from;
return this; return this;
} }
/**
* Set attachments to the mail.
* @param {IMailAttachment[]} attachments
* @returns {Mail}
*/
setAttachments(attachments: IMailAttachment[]) { setAttachments(attachments: IMailAttachment[]) {
this.attachments = attachments; this.attachments = attachments;
return this; return this;
@@ -103,26 +95,21 @@ export default class Mail {
return this; return this;
} }
setContent(content: string) {
this.content = content;
return this;
}
/** /**
* Renders the view template with the given data. * Renders the view template with the given data.
* @param {object} data * @param {object} data
* @return {string} * @return {string}
*/ */
static render(view: string, data: Record<string, any>): string { render(data): string {
const viewContent = Mail.getViewContent(view); const viewContent = this.getViewContent();
return Mustache.render(viewContent, data); return Mustache.render(viewContent, data);
} }
/** /**
* Retrieve view content from the view directory. * Retrieve view content from the view directory.
*/ */
static getViewContent(view: string): string { private getViewContent(): string {
const filePath = path.join(global.__views_dir, `/${view}`); const filePath = path.join(global.__views_dir, `/${this.view}`);
return fs.readFileSync(filePath, 'utf8'); return fs.readFileSync(filePath, 'utf8');
} }
} }

View File

@@ -1,134 +0,0 @@
import xlsx, { WorkBook } from 'xlsx';
import { IFinancialTable, ITableData } from '@/interfaces';
import { FinancialTableStructure } from '@/services/FinancialStatements/FinancialTableStructure';
interface ITableSheet {
convertToXLSX(): WorkBook;
convertToCSV(): string;
convertToBuffer(workbook: WorkBook, fileType: string): Buffer;
}
export class TableSheet implements ITableSheet {
private table: ITableData;
constructor(table: ITableData) {
this.table = table;
}
/**
* Retrieves the columns labels.
* @returns {string[]}
*/
private get columns() {
return this.table.columns.map((col) => col.label);
}
/**
* Retrieves the columns accessors.
* @returns {string[]}
*/
private get columnsAccessors() {
return this.table.columns.map((col, index) => {
return `${index}`;
});
}
/**
* Retrieves the rows data cellIndex/Value.
* @returns {Record<string, string>}
*/
private get rows() {
const computedRows = FinancialTableStructure.flatNestedTree(
this.table.rows
);
return computedRows.map((row) => {
const entries = row.cells.map((cell, index) => {
return [`${index}`, cell.value];
});
return Object.fromEntries(entries);
});
}
/**
* Converts the table to a CSV string.
* @returns {string}
*/
public convertToCSV(): string {
// Define custom headers
const headers = this.columns;
// Convert data to worksheet with headers
const worksheet = xlsx.utils.json_to_sheet(this.rows, {
header: this.columnsAccessors,
});
// Add custom headers to the worksheet
xlsx.utils.sheet_add_aoa(worksheet, [headers], { origin: 'A1' });
// Convert worksheet to CSV format
const csvOutput = xlsx.utils.sheet_to_csv(worksheet);
return csvOutput;
}
/**
* Convert the array of objects to an XLSX file with styled headers
* @returns {Workbook}
*/
public convertToXLSX(): WorkBook {
// Create a new workbook and a worksheet
const workbook = xlsx.utils.book_new();
const worksheet = xlsx.utils.json_to_sheet(this.rows, {
header: this.columnsAccessors,
});
// Add custom headers to the worksheet
xlsx.utils.sheet_add_aoa(worksheet, [this.columns], {
origin: 'A1',
});
// Adjust column width.
worksheet['!cols'] = this.computeXlsxColumnsWidths(this.rows);
// Append the worksheet to the workbook
xlsx.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
return workbook;
}
/**
* Converts the given workbook to buffer of the given file type
* @param {WorkBook} workbook
* @param {string} fileType
* @returns {Promise<Buffer>}
*/
public convertToBuffer(workbook: WorkBook, fileType: 'xlsx' | 'csv'): Buffer {
return xlsx.write(workbook, {
type: 'buffer',
bookType: fileType,
cellStyles: true,
});
}
/**
* Adjusts and computes the columns width.
* @param {} rows
* @returns {{wch: number}[]}
*/
private computeXlsxColumnsWidths = (rows): { wch: number }[] => {
const cols = [{ wch: 60 }];
this.columns.map((column) => {
cols.push({ wch: column.length });
});
rows.forEach((row) => {
const entries = Object.entries(row);
entries.forEach(([key, value]) => {
if (cols[key]) {
cols[key].wch = Math.max(cols[key].wch, String(value).length);
} else {
cols[key] = { wch: String(value).length };
}
});
});
return cols;
};
}

View File

@@ -81,9 +81,6 @@ import { ProjectBillableBillSubscriber } from '@/services/Projects/Projects/Proj
import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber'; import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber';
import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber'; import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber';
import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber'; import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber';
import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber';
import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber';
import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber';
export default () => { export default () => {
return new EventPublisher(); return new EventPublisher();
@@ -191,14 +188,8 @@ export const susbcribers = () => {
ProjectBillableExpensesSubscriber, ProjectBillableExpensesSubscriber,
ProjectBillableBillSubscriber, ProjectBillableBillSubscriber,
// Tax Rates - Sale Invoice // Tax Rates
SaleInvoiceTaxRateValidateSubscriber, SaleInvoiceTaxRateValidateSubscriber,
WriteInvoiceTaxTransactionsSubscriber, WriteInvoiceTaxTransactionsSubscriber,
// Tax Rates - Bills
BillTaxRateValidateSubscriber,
WriteBillTaxTransactionsSubscriber,
SyncItemTaxRateOnEditTaxSubscriber
]; ];
}; };

View File

@@ -1,5 +1,4 @@
import { json, Request, Response, NextFunction } from 'express'; import { json, Request, Response, NextFunction } from 'express';
import express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
import boom from 'express-boom'; import boom from 'express-boom';
import errorHandler from 'errorhandler'; import errorHandler from 'errorhandler';
@@ -43,8 +42,6 @@ export default ({ app }) => {
// Middleware for intercepting and transforming json responses. // Middleware for intercepting and transforming json responses.
app.use(JSONResponseTransformer(snakecaseResponseTransformer)); app.use(JSONResponseTransformer(snakecaseResponseTransformer));
app.use('/public', express.static(path.join(global.__storage_dir)));
// Handle multi-media requests. // Handle multi-media requests.
app.use( app.use(
fileUpload({ fileUpload({

View File

@@ -5,11 +5,6 @@ import RewriteInvoicesJournalEntries from 'jobs/WriteInvoicesJEntries';
import UserInviteMailJob from 'jobs/UserInviteMail'; import UserInviteMailJob from 'jobs/UserInviteMail';
import OrganizationSetupJob from 'jobs/OrganizationSetup'; import OrganizationSetupJob from 'jobs/OrganizationSetup';
import OrganizationUpgrade from 'jobs/OrganizationUpgrade'; import OrganizationUpgrade from 'jobs/OrganizationUpgrade';
import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailJob';
import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob';
import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob';
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
export default ({ agenda }: { agenda: Agenda }) => { export default ({ agenda }: { agenda: Agenda }) => {
new ResetPasswordMailJob(agenda); new ResetPasswordMailJob(agenda);
@@ -18,11 +13,6 @@ export default ({ agenda }: { agenda: Agenda }) => {
new RewriteInvoicesJournalEntries(agenda); new RewriteInvoicesJournalEntries(agenda);
new OrganizationSetupJob(agenda); new OrganizationSetupJob(agenda);
new OrganizationUpgrade(agenda); new OrganizationUpgrade(agenda);
new SendSaleInvoiceMailJob(agenda);
new SendSaleInvoiceReminderMailJob(agenda);
new SendSaleEstimateMailJob(agenda);
new SaleReceiptMailNotificationJob(agenda);
new PaymentReceiveMailNotificationJob(agenda);
agenda.start(); agenda.start();
}; };

View File

@@ -60,7 +60,6 @@ import Time from 'models/Time';
import Task from 'models/Task'; import Task from 'models/Task';
import TaxRate from 'models/TaxRate'; import TaxRate from 'models/TaxRate';
import TaxRateTransaction from 'models/TaxRateTransaction'; import TaxRateTransaction from 'models/TaxRateTransaction';
import Attachment from 'models/Attachment';
export default (knex) => { export default (knex) => {
const models = { const models = {
@@ -124,7 +123,6 @@ export default (knex) => {
Task, Task,
TaxRate, TaxRate,
TaxRateTransaction, TaxRateTransaction,
Attachment
}; };
return mapValues(models, (model) => model.bindKnex(knex)); return mapValues(models, (model) => model.bindKnex(knex));
}; };

Some files were not shown because too many files have changed in this diff Show More