Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
dbd2f77d12 chore: update mysql docker container restart policy 2023-07-23 20:03:57 +02:00
1606 changed files with 38683 additions and 99163 deletions

View File

@@ -42,69 +42,6 @@
"contributions": [
"bug"
]
},
{
"login": "suhaibaffan",
"name": "Suhaib Affan",
"avatar_url": "https://avatars.githubusercontent.com/u/18115937?v=4",
"profile": "https://github.com/suhaibaffan",
"contributions": [
"code"
]
},
{
"login": "KalliopiPliogka",
"name": "Kalliopi Pliogka",
"avatar_url": "https://avatars.githubusercontent.com/u/81677549?v=4",
"profile": "https://github.com/KalliopiPliogka",
"contributions": [
"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,

View File

@@ -8,7 +8,7 @@ MAIL_FROM_NAME=
MAIL_FROM_ADDRESS=
# Database
DB_HOST=localhost
DB_HOST=mysql
DB_USER=bigcapital
DB_PASSWORD=bigcapital
DB_ROOT_PASSWORD=root
@@ -47,51 +47,3 @@ AGENDASH_AUTH_PASSWORD=123123
SIGNUP_DISABLED=false
SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS=
# API rate limit (points,duration,block duration).
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=
# The Plaid environment to use ('sandbox' or 'development').
# https://plaid.com/docs/#api-host
PLAID_ENV=sandbox
# Your Plaid keys, which can be found in the Plaid Dashboard.
# https://dashboard.plaid.com/account/keys
PLAID_CLIENT_ID=
PLAID_SECRET_DEVELOPMENT=
PLAID_SECRET_SANDBOX=
PLAID_LINK_WEBHOOK=
# (Optional) Redirect URI settings section
# Only required for OAuth redirect URI testing (not common on desktop):
# Sandbox Mode:
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
# The OAuth redirect flow requires an endpoint on the developer's website
# that the bank website should redirect to. You will also need to configure
# this redirect URI for your client ID through the Plaid developer dashboard
# at https://dashboard.plaid.com/team/api.
# Development mode:
# When running in development mode, you must use an https:// url.
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
# Instructions to create a self-signed certificate for localhost can be found at
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
# If your system is not set up to run localhost with https://, you will be unable to test
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
PLAID_SANDBOX_REDIRECT_URI=
PLAID_DEVELOPMENT_REDIRECT_URI=

13
.github/FUNDING.yml vendored
View File

@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: Bigcapital # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

7
.gitignore vendored
View File

@@ -1,9 +1,4 @@
node_modules/
# Docker volumes data directory
/data
# Production env file
data
.env
test-results/

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,112 +2,7 @@
All notable changes to Bigcapital server-side will be in this file.
## [0.14.0] - 30-01-2024
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339
* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340
* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335
* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342
## [0.13.3] - 22-01-2024
* 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
## [0.10.0] - 24-09-2023
* 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: Tax rates tracking with sale invoices by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/204
* fix(webapp): Table headers sticky for all reports. by @elforjani13 in https://github.com/bigcapitalhq/bigcapital/pull/240
* chore(deps): bump word-wrap from 1.2.3 to 1.2.4 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/200
* 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
## [0.9.12] - 29-08-2023
* 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: typo in bill success message without bill number by @KalliopiPliogka in https://github.com/bigcapitalhq/bigcapital/pull/219
* Fix: AP/AR aging summary issue by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/229
* Fix: shouldn't write GL entries when save transaction as draft. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/221
* Fix: Transaction type of credit note and vendor credit are not defined on account transactions by @abouolia in
* Fix: date format of filtering transactions by date range by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/231
* Fix: change the default from/date date value of reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/230
* Fix: typos in words start with `A` letter by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/227
* 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
## [0.9.11] - 23-07-2023
* 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
## [0.9.10] - 18-07-2023
* 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): 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
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
@@ -119,7 +14,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 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`
@@ -191,7 +86,7 @@ https://github.com/bigcapitalhq/bigcapital/pull/225
- fix: delete invoice transaction issue.
`@bigcapital/webapp`
- fix: general, accountant and items preferences.
- fix: general, accoutant and items preferences.
- fix: auto-increment sale invoices, estiamtes, credit notes, payments and manual journals.
- refactor: the setup organization form to use binded Formik components.

View File

@@ -7,7 +7,6 @@ Please read through this document before submitting any issues or pull requests
## Sections
- [General Instructions](#general-instructions)
- [Local Setup Prerequisites](#local-setup-prerequisites)
- [Contribute to Backend](#contribute-to-backend)
- [Contribute to Frontend](#contribute-to-frontend)
- [Other Ways to Contribute](#other-ways-to-contribute)
@@ -32,23 +31,14 @@ Contributions via pull requests are much appreciated. Once the approach is agree
---
## Local Setup Prerequisites
- The application currently supports **Node.js v18.x**.
- `pnpm` packages manager, (from pnpm [guide](https://pnpm.io/installation) pick any installation method).
## Contribute to Backend
- Clone the `bigcapital` repository and `cd` into `bigcapital` directory.
- Create `.env` file by copying `.env.example` file to `.env`. (The ``.env.example`` file has all the necessary values of variables to start development directly).
- 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.
```
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.
```
pnpm install
npm install
npm run bootstrap
```
- Run all required docker containers in the development, we already configured all containers under `docker-compose.yml`.
@@ -69,7 +59,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.
```
pnpm run build:server
npm run build:server
```
- Run the database migration for system database.
@@ -87,7 +77,7 @@ Batch 1 run: 6 migrations
- Next, start the webapp application.
```
pnpm run dev:server
npm run dev:server
```
**[`^top^`](#)**
@@ -105,13 +95,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.
```
pnpm install
npm install
npm run bootstrap
```
- Next, start the webapp application.
```
pnpm run dev:webapp
npm run dev:webapp
```
**[`^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">
</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
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
@@ -79,12 +48,6 @@ You can integrate Bigcapital API with your system to organize your transactions
Please see [Releases](https://github.com/bigcapitalhq/bigcapital/releases) for more information what has changed recently.
# Contact us
Meet our sales team for any commercial inquiries.
<a target="_blank" href="https://cal.com/ahmed-bouhuolia-ekk3ph/30min"><img src="https://cal.com/book-with-cal-dark.svg" alt="Book us with Cal.com"></a>
# Recognition
<a href="https://news.ycombinator.com/item?id=36118990">
@@ -106,18 +69,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/abouolia"><img src="https://avatars.githubusercontent.com/u/2197422?v=4?s=100" width="100px;" alt="Ahmed Bouhuolia"/><br /><sub><b>Ahmed Bouhuolia</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=abouolia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ameir.net"><img src="https://avatars.githubusercontent.com/u/374330?v=4?s=100" width="100px;" alt="Ameir Abdeldayem"/><br /><sub><b>Ameir Abdeldayem</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aameir" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/elforjani13"><img src="https://avatars.githubusercontent.com/u/39470382?v=4?s=100" width="100px;" alt="ElforJani13"/><br /><sub><b>ElforJani13</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=elforjani13" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ameir.net"><img src="https://avatars.githubusercontent.com/u/374330?v=4?s=100" width="100px;" alt="Ameir Abdeldayem"/><br /><sub><b>Ameir Abdeldayem</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aameir" 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/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>
</tbody>
</table>

View File

@@ -15,8 +15,8 @@ services:
- ./data/logs/nginx/:/var/log/nginx
- ./docker/certbot/certs/:/var/certs
ports:
- '${PUBLIC_PROXY_PORT:-80}:80'
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
- "${PUBLIC_PROXY_PORT:-80}:80"
- "${PUBLIC_PROXY_SSL_PORT:-443}:443"
tty: true
depends_on:
- server
@@ -35,8 +35,6 @@ services:
server:
container_name: bigcapital-server
image: ghcr.io/bigcapitalhq/server:latest
expose:
- '3000'
links:
- mysql
- mongo
@@ -73,7 +71,7 @@ services:
# Authentication
- JWT_SECRET=${JWT_SECRET}
# MongoDB
# MongoDB
- MONGODB_DATABASE_URL=mongodb://mongo/bigcapital
# Application
@@ -88,24 +86,17 @@ services:
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
# Gotenberg (Pdf generator)
- GOTENBERG_URL=${GOTENBERG_URL}
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
database_migration:
container_name: bigcapital-database-migration
build:
context: ./
dockerfile: docker/migration/Dockerfile
environment:
# Database
- DB_HOST=mysql
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- DB_CHARSET=${DB_CHARSET}
- SYSTEM_DB_NAME=${SYSTEM_DB_NAME}
# Tenants databases
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
depends_on:
- mysql
@@ -145,15 +136,10 @@ services:
build:
context: ./docker/redis
expose:
- '6379'
- "6379"
volumes:
- redis:/data
gotenberg:
image: gotenberg/gotenberg:7
expose:
- '9000'
# Volumes
volumes:
mysql:

View File

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

View File

@@ -34,7 +34,5 @@ WORKDIR /app/packages/server
RUN git clone https://github.com/vishnubob/wait-for-it.git
ADD docker/migration/start.sh /
RUN chmod +x /start.sh
CMD ["/start.sh"]
# Once we listen the mysql port run the migration task.
CMD ["./wait-for-it/wait-for-it.sh", "mysql:3306", "--", "node", "./build/commands.js", "system:migrate:latest"]

View File

@@ -1,5 +0,0 @@
# Migrate the master system database.
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js system:migrate:latest
# Migrate all tenants.
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js tenants:migrate:latest

View File

@@ -1,7 +0,0 @@
import { test, expect, Page } from '@playwright/test';
test.describe('item', () => {
test('should validate all required fields.', () => {});
test('should save the item successfully.', () => {});
test('should item code be unqiue.', () => {});
});

View File

@@ -1,7 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true,
"packages": ["packages/*"]
}
"version": "0.9.6",
"npmClient": "npm"
}

5730
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
"name": "bigcapital-monorepo",
"private": true,
"scripts": {
"bootstrap": "lerna exec npm install",
"dev": "lerna run dev",
"build": "lerna run build",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
@@ -12,6 +13,10 @@
"test:e2e": "playwright test",
"prepare": "husky install"
},
"workspaces": [
"packages/*",
"shared/*"
],
"devDependencies": {
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2",
@@ -22,7 +27,7 @@
"lerna": "^6.4.1"
},
"engines": {
"node": "16.x || 17.x || 18.x"
"node": "14.x"
},
"husky": {
"hooks": {

View File

@@ -1,6 +1,7 @@
/node_modules/
/.env
/storage
package-lock.json
stdout.log
/dist
/build
/public/imports
/build

View File

@@ -1,4 +1,4 @@
FROM node:18.16.0-alpine as build
FROM node:14.20-alpine as build
USER root
@@ -83,25 +83,15 @@ WORKDIR /app
RUN chown node:node /
# Install pnpm
RUN npm install -g pnpm
# Copy application dependency manifests to the container image.
COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./lerna.json ./lerna.json
COPY ./packages/server/package*.json ./packages/server/
# Install application dependencies
RUN apk update
RUN apk add python3 build-base chromium
COPY ./lerna.json ./lerna.json
# Set PYHTON env
ENV PYTHON=/usr/bin/python3
# Install packages dependencies for production.
RUN pnpm install
# Install app dependencies for production.
RUN npm install
RUN npm run bootstrap
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",
"version": "0.10.2",
"version": "0.9.5",
"description": "",
"main": "src/server.ts",
"scripts": {
@@ -25,18 +25,16 @@
"@types/i18n": "^0.8.7",
"@types/knex": "^0.16.1",
"@types/mathjs": "^6.0.12",
"@types/yup": "^0.29.13",
"accepts": "^1.3.7",
"accounting": "^0.4.1",
"agenda": "^4.2.1",
"agendash": "^3.1.0",
"app-root-path": "^3.0.0",
"async": "^3.2.0",
"axios": "^1.6.0",
"axios": "^0.20.0",
"babel-loader": "^9.1.2",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"country-codes-list": "^1.6.8",
"cpy": "^8.1.2",
@@ -44,7 +42,7 @@
"crypto-random-string": "^3.2.0",
"csurf": "^1.10.0",
"deep-map": "^2.0.0",
"deepdash": "^5.3.9",
"deepdash": "^5.3.7",
"dotenv": "^8.1.0",
"errorhandler": "^1.5.1",
"es6-weak-map": "^2.0.3",
@@ -54,9 +52,9 @@
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"express-boom": "^3.0.0",
"express-fileupload": "^1.1.7-alpha.3",
"express-oauth-server": "^2.0.0",
"express-validator": "^6.12.2",
"form-data": "^4.0.0",
"gulp": "^4.0.2",
"gulp-sass": "^5.0.0",
"helmet": "^3.21.0",
@@ -74,10 +72,7 @@
"memory-cache": "^0.2.0",
"moment": "^2.24.0",
"moment-range": "^4.0.2",
"moment-timezone": "^0.5.43",
"mongodb": "^6.1.0",
"mongoose": "^5.10.0",
"multer": "1.4.5-lts.1",
"mustache": "^3.0.3",
"mysql": "^2.17.1",
"mysql2": "^1.6.5",
@@ -92,22 +87,16 @@
"pluralize": "^8.0.0",
"pug": "^3.0.2",
"puppeteer": "^10.2.0",
"plaid": "^10.3.0",
"qim": "0.0.52",
"ramda": "^0.27.1",
"rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4",
"source-map-loader": "^4.0.1",
"socket.io": "^4.7.4",
"tmp-promise": "^3.0.3",
"ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0",
"typedi": "^0.8.0",
"uniqid": "^5.2.0",
"winston": "^3.2.1",
"xlsx": "^0.18.5",
"yup": "^0.28.1"
"winston": "^3.2.1"
},
"devDependencies": {
"@types/lodash": "^4.14.158",
@@ -142,7 +131,7 @@
"rimraf": "^3.0.2",
"rtlcss": "^3.3.0",
"run-script-webpack-plugin": "^0.1.1",
"sass": "^1.58.0",
"sass": "^1.37.5",
"sinon": "^7.4.2",
"start-server-webpack-plugin": "^2.2.5",
"ts-loader": "^9.4.2",

Binary file not shown.

Binary file not shown.

View File

@@ -152,7 +152,7 @@
"Opening Balance Liabilities": "رصيد الالتزامات الافتتاحي",
"Loan": "اقراض",
"Owner A Drawings": "مسحوبات المالك",
"An account that holds valuation of products or goods that available for sale.": "حساب يحمل قيم مخزون البضاعة أو السلع المتاحة للبيع.",
"An account that holds valuation of products or goods that availiable for sale.": "حساب يحمل قيم مخزون البضاعة أو السلع المتاحة للبيع.",
"Tracks the gain and losses of the exchange differences.": "يسجل مكاسب وخسائر فروق الصرف.",
"Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.": "يتم تسجيل أي رسوم مصرفية يتم فرضها في حساب الرسوم والمصروفات البنكية. ومن الأمثلة على ذلك رسوم صيانة الحساب المصرفي ورسوم المعاملات ورسوم الدفع المتأخر.",
"The income activities are not associated to the core business.": "لا ترتبط انشطة الدخل إلى الأعمال الأساسية.",
@@ -242,7 +242,7 @@
"account.field.normal.credit": "دائن",
"account.field.normal.debit": "مدين",
"account.field.type": "نوع الحساب",
"account.field.active": "Active",
"account.field.active": "Activity",
"account.field.balance": "الرصيد",
"account.field.created_at": "أنشئت في",
"item.field.type": "نوع الصنف",

View File

@@ -151,7 +151,7 @@
"Opening Balance Liabilities": "Opening Balance Liabilities",
"Loan": "Loan",
"Owner A Drawings": "Owner A Drawings",
"An account that holds valuation of products or goods that available for sale.": "An account that holds valuation of products or goods that available for sale.",
"An account that holds valuation of products or goods that availiable for sale.": "An account that holds valuation of products or goods that availiable for sale.",
"Tracks the gain and losses of the exchange differences.": "Tracks the gain and losses of the exchange differences.",
"Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.": "Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.",
"The income activities are not associated to the core business.": "The income activities are not associated to the core business.",
@@ -176,7 +176,6 @@
"invoice.paper.conditions_title": "Conditions & terms",
"invoice.paper.notes_title": "Notes",
"invoice.paper.total": "Total",
"invoice.paper.subtotal": "Subtotal",
"invoice.paper.payment_amount": "Payment Amount",
"invoice.paper.balance_due": "Balance Due",
@@ -241,31 +240,28 @@
"account.field.normal.credit": "Credit",
"account.field.normal.debit": "Debit",
"account.field.type": "Type",
"account.field.active": "Active",
"account.field.currency": "Currency",
"account.field.active": "Activity",
"account.field.balance": "Balance",
"account.field.parent_account": "Parent Account",
"account.field.created_at": "Created at",
"item.field.type": "Item Type",
"item.field.type": "Item type",
"item.field.type.inventory": "Inventory",
"item.field.type.service": "Service",
"item.field.type.non-inventory": "Non Inventory",
"item.field.name": "Item Name",
"item.field.code": "Item Code",
"item.field.type.non-inventory": "Non inventory",
"item.field.name": "Name",
"item.field.code": "Code",
"item.field.sellable": "Sellable",
"item.field.purchasable": "Purchasable",
"item.field.cost_price": "Cost Price",
"item.field.sell_price": "Sell Price",
"item.field.cost_account": "Cost Account",
"item.field.sell_account": "Sell Account",
"item.field.sell_description": "Sell Description",
"item.field.inventory_account": "Inventory Account",
"item.field.purchase_description": "Purchase Description",
"item.field.quantity_on_hand": "Quantity on Hand",
"item.field.cost_price": "Cost price",
"item.field.cost_account": "Cost account",
"item.field.sell_account": "Sell account",
"item.field.sell_description": "Sell description",
"item.field.inventory_account": "Inventory account",
"item.field.purchase_description": "Purchase description",
"item.field.quantity_on_hand": "Quantity on hand",
"item.field.note": "Note",
"item.field.category": "Category",
"item.field.active": "Active",
"item.field.created_at": "Created At",
"item.field.created_at": "Created at",
"item_category.field.name": "Name",
"item_category.field.description": "Description",
"item_category.field.count": "Count",
@@ -278,14 +274,8 @@
"invoice.field.invoice_message": "Invoice message",
"invoice.field.terms_conditions": "Terms & conditions",
"invoice.field.amount": "Amount",
"invoice.field.exchange_rate": "Exchange Rate",
"invoice.field.payment_amount": "Payment amount",
"invoice.field.due_amount": "Due amount",
"invoice.field.delivered": "Delivered",
"invoice.field.item_name": "Item Name",
"invoice.field.rate": "Rate",
"invoice.field.quantity": "Quantity",
"invoice.field.description": "Description",
"invoice.field.status": "Status",
"invoice.field.status.paid": "Paid",
"invoice.field.status.partially-paid": "Partially paid",
@@ -294,8 +284,6 @@
"invoice.field.status.delivered": "Delivered",
"invoice.field.status.draft": "Draft",
"invoice.field.created_at": "Created at",
"invoice.field.currency": "Currency",
"invoice.field.entries": "Entries",
"estimate.field.amount": "Amount",
"estimate.field.estimate_number": "Estimate number",
"estimate.field.customer": "Customer",
@@ -310,31 +298,22 @@
"estimate.field.status.approved": "Approved",
"estimate.field.status.draft": "Draft",
"estimate.field.created_at": "Created at",
"payment_receive.field.customer": "Customer",
"payment_receive.field.payment_date": "Payment date",
"payment_receive.field.amount": "Amount",
"payment_receive.field.reference_no": "Reference No.",
"payment_receive.field.deposit_account": "Deposit account",
"payment_receive.field.payment_receive_no": "Payment receive No.",
"payment_receive.field.statement": "Statement",
"payment_receive.field.created_at": "Created at",
"payment_receive.field.customer": "Customer",
"payment_receive.field.exchange_rate": "Exchange Rate",
"payment_receive.field.payment_date": "Payment Date",
"payment_receive.field.reference_no": "Reference No.",
"payment_receive.field.deposit_account": "Deposit Account",
"payment_receive.field.entries": "Entries",
"payment_receive.field.invoice": "Invoice",
"payment_receive.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.vendor": "Vendor",
"bill_payment.field.amount": "Amount",
"bill_payment.field.due_amount": "Due Amount",
"bill_payment.field.payment_account": "Payment Account",
"bill_payment.field.payment_number": "Payment No.",
"bill_payment.field.payment_date": "Payment Date",
"bill_payment.field.due_amount": "Due amount",
"bill_payment.field.payment_account": "Payment account",
"bill_payment.field.payment_number": "Payment number",
"bill_payment.field.payment_date": "Payment date",
"bill_payment.field.reference_no": "Reference No.",
"bill_payment.field.description": "Description",
"bill_payment.field.exchange_rate": "Exchange Rate",
"bill_payment.field.statement": "Statement",
"bill_payment.field.entries.bill": "Bill No.",
"bill_payment.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.reference": "Reference No.",
"bill_payment.field.created_at": "Created at",
"bill.field.vendor": "Vendor",
"bill.field.bill_number": "Bill number",
@@ -362,30 +341,22 @@
"inventory_adjustment.field.description": "Description",
"inventory_adjustment.field.published_at": "Published at",
"inventory_adjustment.field.created_at": "Created at",
"expense.field.payment_date": "Payment Date",
"expense.field.payment_account": "Payment Account",
"expense.field.payment_date": "Payment date",
"expense.field.payment_account": "Payment account",
"expense.field.amount": "Amount",
"expense.field.currency_code": "Currency",
"expense.field.exchange_rate": "Exchange Rate",
"expense.field.reference_no": "Reference No.",
"expense.field.description": "Description",
"expense.field.line_description": "Line Description",
"expense.field.published": "Published",
"expense.field.categories": "Categories",
"expense.field.expense_account": "Expense Account",
"expense.field.publish": "Publish",
"expense.field.status": "Status",
"expense.field.status.draft": "Draft",
"expense.field.status.published": "Published",
"expense.field.created_at": "Created at",
"manual_journal.field.date": "Date",
"manual_journal.field.journal_number": "Journal No.",
"manual_journal.field.journal_number": "Journal number",
"manual_journal.field.reference": "Reference No.",
"manual_journal.field.journal_type": "Journal Type",
"manual_journal.field.journal_type": "Journal type",
"manual_journal.field.amount": "Amount",
"manual_journal.field.description": "Description",
"manual_journal.field.currency": "Currency",
"manual_journal.field.exchange_rate": "Exchange Rate",
"manual_journal.field.status": "Status",
"manual_journal.field.created_at": "Created at",
"receipt.field.amount": "Amount",
@@ -404,8 +375,8 @@
"customer.field.last_name": "Last name",
"customer.field.display_name": "Display name",
"customer.field.email": "Email",
"customer.field.work_phone": "Work Phone Number",
"customer.field.personal_phone": "Personal Phone Number",
"customer.field.work_phone": "Work phone",
"customer.field.personal_phone": "Personal phone",
"customer.field.company_name": "Company name",
"customer.field.website": "Website",
"customer.field.opening_balance_at": "Opening balance at",
@@ -413,7 +384,7 @@
"customer.field.created_at": "Created at",
"customer.field.balance": "Balance",
"customer.field.status": "Status",
"customer.field.currency": "Currency",
"customer.field.currency": "Curreny",
"customer.field.status.active": "Active",
"customer.field.status.inactive": "Inactive",
"customer.field.status.overdue": "Overdue",
@@ -422,8 +393,8 @@
"vendor.field.last_name": "Last name",
"vendor.field.display_name": "Display name",
"vendor.field.email": "Email",
"vendor.field.work_phone": "Work Phone Number",
"vendor.field.personal_phone": "Personal Phone Number",
"vendor.field.work_phone": "Work phone",
"vendor.field.personal_phone": "Personal phone",
"vendor.field.company_name": "Company name",
"vendor.field.website": "Website",
"vendor.field.opening_balance_at": "Opening balance at",
@@ -431,15 +402,13 @@
"vendor.field.created_at": "Created at",
"vendor.field.balance": "Balance",
"vendor.field.status": "Status",
"vendor.field.currency": "Currency",
"vendor.field.currency": "Curreny",
"vendor.field.status.active": "Active",
"vendor.field.status.inactive": "Inactive",
"vendor.field.status.overdue": "Overdue",
"vendor.field.status.unpaid": "Unpaid",
"Invoice write-off": "Invoice write-off",
"transaction_type.credit_note": "Credit note",
"transaction_type.refund_credit_note": "Refund credit note",
"transaction_type.vendor_credit": "Vendor credit",
@@ -618,7 +587,6 @@
"balance_sheet.long_term_liabilities": "Long-Term Liabilities",
"balance_sheet.non_current_liabilities": "Non-Current Liabilities",
"balance_sheet.equity": "Equity",
"balance_sheet.net_income": "Net Income",
"balance_sheet.account_name": "Account name",
"balance_sheet.total": "Total",

View File

@@ -1,57 +0,0 @@
@import "../base.scss";
html,
body {
font-size: 14px;
}
body{
font-weight: 400;
letter-spacing: 0;
line-height: 1.28581;
text-transform: none;
color: #000;
font-family: Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif;
}
.sheet{
padding: 20px;
}
.sheet__company-name{
margin: 0;
font-size: 1.4rem;
}
.sheet__sheet-type {
margin: 0
}
.sheet__sheet-date {
margin-top: 0.35rem;
}
.sheet__header {
text-align: center;
margin-bottom: 1rem;
}
.sheet__table {
border-top: 1px solid #000;
table-layout: fixed;
border-spacing: 0;
text-align: left;
font-size: inherit;
width: 100%;
}
.sheet__table thead th {
color: #000;
border-bottom: 1px solid #000000;
padding: 0.5rem;
}
.sheet__table tbody td {
border-bottom: 0;
padding-top: 0.28rem;
padding-bottom: 0.28rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
color: #252A31;
border-bottom: 1px solid transparent;
}

View File

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

View File

@@ -1,25 +0,0 @@
block head
style
include ../../css/modules/financial-sheet.css
style.
!{customCSS}
block content
.sheet
.sheet__header
.sheet__company-name=organizationName
.sheet__sheet-type=sheetName
.sheet__sheet-date=sheetDate
table.sheet__table
thead
tr
each column in table.columns
th(style=column.style class='column--' + column.key)= column.label
tbody
each row in table.rows
tr(class=row.classNames)
each cell in row.cells
td(class='cell--' + cell.key)
span!= cell.value

View File

@@ -22,12 +22,12 @@ block content
div.invoice__due-amount
div.label #{__('invoice.paper.invoice_amount')}
div.amount #{saleInvoice.totalFormatted}
div.amount #{saleInvoice.formattedAmount}
div.invoice__meta
div.invoice__meta-item.invoice__meta-item--amount
span.label #{__('invoice.paper.due_amount')}
span.value #{saleInvoice.dueAmountFormatted}
span.value #{saleInvoice.formattedDueAmount}
div.invoice__meta-item.invoice__meta-item--billed-to
span.label #{__("invoice.paper.billed_to")}
@@ -35,11 +35,11 @@ block content
div.invoice__meta-item.invoice__meta-item--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
span.label #{__("invoice.paper.due_date")}
span.value #{saleInvoice.dueDateFormatted}
span.value #{saleInvoice.formattedDueDate}
div.invoice__table
table
@@ -63,22 +63,15 @@ block content
div.invoice__table-total
table
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
td #{__('invoice.paper.total')}
td #{saleInvoice.totalFormatted}
td #{saleInvoice.formattedAmount}
tr.payment-amount
td #{__('invoice.paper.payment_amount')}
td #{saleInvoice.paymentAmountFormatted}
td #{saleInvoice.formattedPaymentAmount}
tr.blanace-due
td #{__('invoice.paper.balance_due')}
td #{saleInvoice.dueAmountFormatted}
td #{saleInvoice.formattedDueAmount}
div.invoice__footer
if saleInvoice.termsConditions

View File

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

View File

@@ -66,10 +66,12 @@ module.exports = {
// sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it.
// minify: true, // Allow to enable/disable minify the source.
},
{
src: `${RESOURCES_PATH}/scss/modules/financial-sheet.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
},
// {
// src: './assets/sass/editor-style.scss',
// dest: './assets/css',
// sourcemaps: true,
// minify: true,
// },
],
// RTL builds.
rtl: [
@@ -112,7 +114,7 @@ module.exports = {
// SASS Configuration for all builds.
sass: {
errLogToConsole: true,
// outputStyle: 'compact',
// outputStyle: 'compact',
},
// CSS MQ Packer configuration for all builds and style tasks.

View File

@@ -27,7 +27,7 @@ export default class AccountsController extends BaseController {
/**
* Router constructor method.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -98,7 +98,7 @@ export default class AccountsController extends BaseController {
/**
* Create account DTO Schema validation.
*/
private get createAccountDTOSchema() {
get createAccountDTOSchema() {
return [
check('name')
.exists()
@@ -131,7 +131,7 @@ export default class AccountsController extends BaseController {
/**
* Account DTO Schema validation.
*/
private get editAccountDTOSchema() {
get editAccountDTOSchema() {
return [
check('name')
.exists()
@@ -160,14 +160,14 @@ export default class AccountsController extends BaseController {
];
}
private get accountParamSchema() {
get accountParamSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Accounts list validation schema.
*/
private get accountsListSchema() {
get accountsListSchema() {
return [
query('view_slug').optional({ nullable: true }).isString().trim(),
query('stringified_filter_roles').optional().isJSON(),

View File

@@ -1,18 +0,0 @@
import Container, { Inject, Service } from 'typedi';
import { Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { PlaidBankingController } from './PlaidBankingController';
@Service()
export class BankingController extends BaseController {
/**
* Router constructor.
*/
router() {
const router = Router();
router.use('/plaid', Container.get(PlaidBankingController).router());
return router;
}
}

View File

@@ -1,53 +0,0 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
@Service()
export class PlaidBankingController extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post('/link-token', this.linkToken.bind(this));
router.post('/exchange-token', this.exchangeToken.bind(this));
return router;
}
/**
* Retrieves the Plaid link token.
* @param {Request} req
* @param {response} res
* @returns {Response}
*/
private async linkToken(req: Request, res: Response) {
const { tenantId } = req;
const linkToken = await this.plaidApp.getLinkToken(tenantId);
return res.status(200).send(linkToken);
}
/**
* Exchanges the given public token.
* @param {Request} req
* @param {response} res
* @returns {Response}
*/
public async exchangeToken(req: Request, res: Response) {
const { tenantId } = req;
const { public_token, institution_id } = req.body;
await this.plaidApp.exchangeToken(tenantId, {
institutionId: institution_id,
publicToken: public_token,
});
return res.status(200).send({});
}
}

View File

@@ -13,9 +13,9 @@ export default class CashflowController {
router() {
const router = Router();
router.use(Container.get(CommandCashflowTransaction).router());
router.use(Container.get(GetCashflowTransaction).router());
router.use(Container.get(GetCashflowAccounts).router());
router.use(Container.get(CommandCashflowTransaction).router());
router.use(Container.get(DeleteCashflowTransaction).router());
return router;

View File

@@ -3,15 +3,14 @@ import { Router, Request, Response, NextFunction } from 'express';
import { param } from 'express-validator';
import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions';
import DeleteCashflowTransactionService from '../../../services/Cashflow/DeleteCashflowTransactionService';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export default class DeleteCashflowTransactionController extends BaseController {
export default class DeleteCashflowTransaction extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
deleteCashflowService: DeleteCashflowTransactionService;
/**
* Controller router.
@@ -45,7 +44,7 @@ export default class DeleteCashflowTransactionController extends BaseController
try {
const { oldCashflowTransaction } =
await this.cashflowApplication.deleteTransaction(
await this.deleteCashflowService.deleteCashflowTransaction(
tenantId,
transactionId
);
@@ -93,19 +92,6 @@ export default class DeleteCashflowTransactionController extends BaseController
],
});
}
if (
error.errorType ===
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED'
) {
return res.boom.badRequest(null, {
errors: [
{
type: 'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
code: 4100,
},
],
});
}
}
next(error);
}

View File

@@ -1,16 +1,20 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { param, query } from 'express-validator';
import GetCashflowAccountsService from '@/services/Cashflow/GetCashflowAccountsService';
import BaseController from '../BaseController';
import GetCashflowTransactionsService from '@/services/Cashflow/GetCashflowTransactionsService';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export default class GetCashflowAccounts extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
getCashflowAccountsService: GetCashflowAccountsService;
@Inject()
getCashflowTransactionsService: GetCashflowTransactionsService;
/**
* Controller router.
@@ -58,7 +62,10 @@ export default class GetCashflowAccounts extends BaseController {
try {
const cashflowAccounts =
await this.cashflowApplication.getCashflowAccounts(tenantId, filter);
await this.getCashflowAccountsService.getCashflowAccounts(
tenantId,
filter
);
return res.status(200).send({
cashflow_accounts: this.transfromToResponse(cashflowAccounts),

View File

@@ -2,15 +2,15 @@ import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { param } from 'express-validator';
import BaseController from '../BaseController';
import GetCashflowTransactionsService from '@/services/Cashflow/GetCashflowTransactionsService';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export default class GetCashflowAccounts extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
getCashflowTransactionsService: GetCashflowTransactionsService;
/**
* Controller router.
@@ -43,10 +43,11 @@ export default class GetCashflowAccounts extends BaseController {
const { transactionId } = req.params;
try {
const cashflowTransaction = await this.cashflowApplication.getTransaction(
tenantId,
transactionId
);
const cashflowTransaction =
await this.getCashflowTransactionsService.getCashflowTransaction(
tenantId,
transactionId
);
return res.status(200).send({
cashflow_transaction: this.transfromToResponse(cashflowTransaction),

View File

@@ -1,16 +1,16 @@
import { Service, Inject } from 'typedi';
import { ValidationChain, check, param, query } from 'express-validator';
import { check } from 'express-validator';
import { Router, Request, Response, NextFunction } from 'express';
import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions';
import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export default class NewCashflowTransactionController extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
private newCashflowTranscationService: NewCashflowTransactionService;
/**
* Router constructor.
@@ -18,18 +18,6 @@ export default class NewCashflowTransactionController extends BaseController {
public router() {
const router = Router();
router.get(
'/transactions/uncategorized/:id',
this.asyncMiddleware(this.getUncategorizedCashflowTransaction),
this.catchServiceErrors
);
router.get(
'/transactions/:id/uncategorized',
this.getUncategorizedTransactionsValidationSchema,
this.validationResult,
this.asyncMiddleware(this.getUncategorizedCashflowTransactions),
this.catchServiceErrors
);
router.post(
'/transactions',
CheckPolicies(CashflowAction.Create, AbilitySubject.Cashflow),
@@ -38,72 +26,13 @@ export default class NewCashflowTransactionController extends BaseController {
this.asyncMiddleware(this.newCashflowTransaction),
this.catchServiceErrors
);
router.post(
'/transactions/:id/uncategorize',
this.revertCategorizedCashflowTransaction,
this.catchServiceErrors
);
router.post(
'/transactions/:id/categorize',
this.categorizeCashflowTransactionValidationSchema,
this.validationResult,
this.categorizeCashflowTransaction,
this.catchServiceErrors
);
router.post(
'/transaction/:id/categorize/expense',
this.categorizeAsExpenseValidationSchema,
this.validationResult,
this.categorizesCashflowTransactionAsExpense,
this.catchServiceErrors
);
return router;
}
/**
* Getting uncategorized transactions validation schema.
* @returns {ValidationChain}
*/
public get getUncategorizedTransactionsValidationSchema() {
return [
param('id').exists().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
];
}
/**
* Categorize as expense validation schema.
*/
public get categorizeAsExpenseValidationSchema() {
return [
check('expense_account_id').exists(),
check('date').isISO8601().exists(),
check('reference_no').optional(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
];
}
/**
* Categorize cashflow tranasction validation schema.
*/
public get categorizeCashflowTransactionValidationSchema() {
return [
check('date').exists().isISO8601().toDate(),
check('credit_account_id').exists().isInt().toInt(),
check('transaction_number').optional(),
check('transaction_type').exists(),
check('reference_no').optional(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('description').optional(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
/**
* New cashflow transaction validation schema.
*/
public get newTransactionValidationSchema() {
get newTransactionValidationSchema() {
return [
check('date').exists().isISO8601().toDate(),
check('reference_no').optional({ nullable: true }).trim().escape(),
@@ -119,7 +48,9 @@ export default class NewCashflowTransactionController extends BaseController {
check('credit_account_id').exists().isInt().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('publish').default(false).isBoolean().toBoolean(),
];
}
@@ -139,12 +70,13 @@ export default class NewCashflowTransactionController extends BaseController {
const ownerContributionDTO = this.matchedBodyData(req);
try {
const cashflowTransaction =
await this.cashflowApplication.createTransaction(
const { cashflowTransaction } =
await this.newCashflowTranscationService.newCashflowTransaction(
tenantId,
ownerContributionDTO,
userId
);
return res.status(200).send({
id: cashflowTransaction.id,
message: 'New cashflow transaction has been created successfully.',
@@ -154,147 +86,11 @@ export default class NewCashflowTransactionController extends BaseController {
}
};
/**
* Revert the categorized cashflow transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private revertCategorizedCashflowTransaction = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: cashflowTransactionId } = req.params;
try {
const data = await this.cashflowApplication.uncategorizeTransaction(
tenantId,
cashflowTransactionId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Categorize the cashflow transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private categorizeCashflowTransaction = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: cashflowTransactionId } = req.params;
const cashflowTransaction = this.matchedBodyData(req);
try {
await this.cashflowApplication.categorizeTransaction(
tenantId,
cashflowTransactionId,
cashflowTransaction
);
return res.status(200).send({
message: 'The cashflow transaction has been created successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Categorize the transaction as expense transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private categorizesCashflowTransactionAsExpense = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: cashflowTransactionId } = req.params;
const cashflowTransaction = this.matchedBodyData(req);
try {
await this.cashflowApplication.categorizeAsExpense(
tenantId,
cashflowTransactionId,
cashflowTransaction
);
return res.status(200).send({
message: 'The cashflow transaction has been created successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the uncategorized cashflow transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getUncategorizedCashflowTransaction = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: transactionId } = req.params;
try {
const data = await this.cashflowApplication.getUncategorizedTransaction(
tenantId,
transactionId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Retrieves the uncategorized cashflow transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getUncategorizedCashflowTransactions = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: accountId } = req.params;
const query = this.matchedQueryData(req);
try {
const data = await this.cashflowApplication.getUncategorizedTransactions(
tenantId,
accountId,
query
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
};
/**
* Handle the service errors.
* @param error
* @param {Request} req
* @param {res
* @param req
* @param res
* @param next
* @returns
*/
@@ -344,16 +140,6 @@ export default class NewCashflowTransactionController extends BaseController {
],
});
}
if (error.errorType === 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID') {
return res.boom.badRequest(null, {
errors: [
{
type: 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
code: 4100,
},
],
});
}
}
next(error);
}

View File

@@ -26,27 +26,27 @@ export default class ContactsController extends BaseController {
[...this.autocompleteQuerySchema],
this.validationResult,
this.asyncMiddleware(this.autocompleteContacts.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.dynamicListService.handlerErrorsToResponse
);
router.get(
'/:id',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.getContact.bind(this)),
this.asyncMiddleware(this.getContact.bind(this))
);
router.post(
'/:id/inactivate',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.inactivateContact.bind(this)),
this.handlerServiceErrors,
this.handlerServiceErrors
);
router.post(
'/:id/activate',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.activateContact.bind(this)),
this.handlerServiceErrors,
this.handlerServiceErrors
);
return router;
}
@@ -77,7 +77,7 @@ export default class ContactsController extends BaseController {
try {
const contact = await this.contactsService.getContact(
tenantId,
contactId,
contactId
);
return res.status(200).send({
customer: this.transfromToResponse(contact),
@@ -105,7 +105,7 @@ export default class ContactsController extends BaseController {
try {
const contacts = await this.contactsService.autocompleteContacts(
tenantId,
filter,
filter
);
return res.status(200).send({ contacts });
} catch (error) {
@@ -153,6 +153,7 @@ export default class ContactsController extends BaseController {
check('email')
.optional({ nullable: true })
.isString()
.normalizeEmail()
.isEmail()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('website')
@@ -379,7 +380,7 @@ export default class ContactsController extends BaseController {
error: Error,
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'contact_not_found') {

View File

@@ -160,8 +160,10 @@ export default class CustomersController extends ContactsController {
try {
const contact = await this.customersApplication.createCustomer(
tenantId,
contactDTO
contactDTO,
user
);
return res.status(200).send({
id: contact.id,
message: 'The customer has been created successfully.',

View File

@@ -1,16 +1,19 @@
import { Service, Inject } from 'typedi';
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 BaseController from './BaseController';
import { ServiceError } from '@/exceptions';
import { EchangeRateErrors } from '@/lib/ExchangeRate/types';
import { ExchangeRateApplication } from '@/services/ExchangeRates/ExchangeRateApplication';
import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
@Service()
export default class ExchangeRatesController extends BaseController {
@Inject()
private exchangeRatesApp: ExchangeRateApplication;
exchangeRatesService: ExchangeRatesService;
@Inject()
dynamicListService: DynamicListingService;
/**
* Constructor method.
@@ -19,40 +22,164 @@ export default class ExchangeRatesController extends BaseController {
const router = Router();
router.get(
'/latest',
[
oneOf([
query('to_currency').exists().isString().isISO4217(),
query('from_currency').exists().isString().isISO4217(),
]),
],
'/',
[...this.exchangeRatesListSchema],
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
);
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.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async latestExchangeRate(
req: Request,
res: Response,
next: NextFunction
) {
async exchangeRates(req: Request, res: Response, next: NextFunction) {
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 {
const exchangeRate = await this.exchangeRatesApp.latest(
const exchangeRate = await this.exchangeRatesService.newExchangeRate(
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) {
next(error);
}
@@ -65,56 +192,26 @@ export default class ExchangeRatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceError(
handleServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
code: 100,
message: 'The given base currency is invalid.',
},
],
if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') {
return res.status(404).send({
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
});
} else if (
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType
) {
}
if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
code: 200,
message: 'The service is not allowed',
},
],
errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }],
});
} else if (
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED === error.errorType
) {
}
if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
return res.status(400).send({
errors: [
{
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',
},
],
errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }],
});
}
}

View File

@@ -20,7 +20,6 @@ import InventoryDetailsController from './FinancialStatements/InventoryDetails';
import TransactionsByReferenceController from './FinancialStatements/TransactionsByReference';
import CashflowAccountTransactions from './FinancialStatements/CashflowAccountTransactions';
import ProjectProfitabilityController from './FinancialStatements/ProjectProfitabilitySummary';
import SalesTaxLiabilitySummary from './FinancialStatements/SalesTaxLiabilitySummary';
@Service()
export default class FinancialStatementsService {
@@ -69,44 +68,40 @@ export default class FinancialStatementsService {
);
router.use(
'/customer-balance-summary',
Container.get(CustomerBalanceSummaryController).router()
Container.get(CustomerBalanceSummaryController).router(),
);
router.use(
'/vendor-balance-summary',
Container.get(VendorBalanceSummaryController).router()
Container.get(VendorBalanceSummaryController).router(),
);
router.use(
'/transactions-by-customers',
Container.get(TransactionsByCustomers).router()
Container.get(TransactionsByCustomers).router(),
);
router.use(
'/transactions-by-vendors',
Container.get(TransactionsByVendors).router()
Container.get(TransactionsByVendors).router(),
);
router.use(
'/cash-flow',
Container.get(CashFlowStatementController).router()
Container.get(CashFlowStatementController).router(),
);
router.use(
'/inventory-item-details',
Container.get(InventoryDetailsController).router()
Container.get(InventoryDetailsController).router(),
);
router.use(
'/transactions-by-reference',
Container.get(TransactionsByReferenceController).router()
Container.get(TransactionsByReferenceController).router(),
);
router.use(
'/cashflow-account-transactions',
Container.get(CashflowAccountTransactions).router()
Container.get(CashflowAccountTransactions).router(),
);
router.use(
'/project-profitability-summary',
Container.get(ProjectProfitabilityController).router()
);
router.use(
'/sales-tax-liability-summary',
Container.get(SalesTaxLiabilitySummary).router()
);
Container.get(ProjectProfitabilityController).router(),
)
return router;
}
}

View File

@@ -2,20 +2,19 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import APAgingSummaryReportService from '@/services/FinancialStatements/AgingSummary/APAgingSummaryService';
import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
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 {
@Inject()
private APAgingSummaryApp: APAgingSummaryApplication;
APAgingSummaryService: APAgingSummaryReportService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -29,19 +28,15 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
/**
* Validation schema.
* @returns {ValidationChain[]}
*/
private get validationSchema() {
get validationSchema() {
return [
...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
query('aging_days_before').default(30).isInt({ max: 500 }).toInt(),
query('aging_periods').default(3).isInt({ max: 12 }).toInt(),
query('aging_days_before').optional().isNumeric().toInt(),
query('aging_periods').optional().isNumeric().toInt(),
query('vendors_ids').optional().isArray({ min: 1 }),
query('vendors_ids.*').isInt({ min: 1 }).toInt(),
query('none_zero').default(true).isBoolean().toBoolean(),
// Filtering by branches.
@@ -51,69 +46,22 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
}
/**
* Retrieves payable aging summary report.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
* Retrieve payable aging summary report.
*/
private async payableAgingSummary(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
async payableAgingSummary(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const filter = this.matchedQueryData(req);
try {
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,
ACCEPT_TYPE.APPLICATION_PDF
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.APAgingSummaryApp.table(tenantId, filter);
const { data, columns, query, meta } =
await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const csv = await this.APAgingSummaryApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(csv);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.APAgingSummaryApp.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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.APAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
} catch (error) {
next(error);
}

View File

@@ -5,18 +5,16 @@ import ARAgingSummaryService from '@/services/FinancialStatements/AgingSummary/A
import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ARAgingSummaryApplication } from '@/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class ARAgingSummaryReportController extends BaseFinancialReportController {
@Inject()
private ARAgingSummaryApp: ARAgingSummaryApplication;
ARAgingSummaryService: ARAgingSummaryService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -32,14 +30,14 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
/**
* AR aging summary validation roles.
*/
private get validationSchema() {
get validationSchema() {
return [
...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
query('aging_days_before').default(30).isInt({ max: 500 }).toInt(),
query('aging_periods').default(3).isInt({ max: 12 }).toInt(),
query('aging_days_before').optional().isInt({ max: 500 }).toInt(),
query('aging_periods').optional().isInt({ max: 12 }).toInt(),
query('customers_ids').optional().isArray({ min: 1 }),
query('customers_ids.*').isInt({ min: 1 }).toInt(),
@@ -54,64 +52,21 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
/**
* Retrieve AR aging summary report.
* @param {Request} req
* @param {Response} res
*/
private async receivableAgingSummary(req: Request, res: Response) {
const { tenantId } = req;
async receivableAgingSummary(req: Request, res: Response) {
const { tenantId, settings } = req;
const filter = this.matchedQueryData(req);
try {
const accept = this.accepts(req);
const { data, columns, query, meta } =
await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF
]);
// Retrieves the xlsx format.
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.ARAgingSummaryApp.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 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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.ARAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
} catch (error) {
console.log(error);
}

View File

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

View File

@@ -8,20 +8,29 @@ import {
ValidationChain,
} from 'express';
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 { ACCEPT_TYPE } from '@/interfaces/Http';
import { CashflowSheetApplication } from '@/services/FinancialStatements/CashFlow/CashflowSheetApplication';
@Service()
export default class CashFlowController extends BaseFinancialReportController {
@Inject()
private cashflowSheetApp: CashflowSheetApplication;
cashFlowService: CashFlowStatementService;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -38,7 +47,7 @@ export default class CashFlowController extends BaseFinancialReportController {
* Balance sheet validation schecma.
* @returns {ValidationChain[]}
*/
private get cashflowValidationSchema(): ValidationChain[] {
get cashflowValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
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.
* @param {Request} req
@@ -65,62 +109,26 @@ export default class CashFlowController extends BaseFinancialReportController {
* @param {NextFunction} next
* @returns {Response}
*/
public async cashFlow(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
async cashFlow(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const filter = {
...this.matchedQueryData(req),
};
try {
const cashFlow = await this.cashFlowService.cashFlow(tenantId, filter);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.cashflowSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter);
return res.status(200).send(cashflow);
switch (acceptType) {
case 'application/json+table':
return res
.status(200)
.send(this.transformToTableRows(cashFlow, tenantId));
case 'json':
default:
return res.status(200).send(this.transformJsonResponse(cashFlow));
}
} catch (error) {
next(error);

View File

@@ -1,21 +1,29 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { Inject } from 'typedi';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import {
AbilitySubject,
ICustomerBalanceSummaryStatement,
ReportsAction,
} from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import CustomerBalanceSummary from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService';
import BaseFinancialReportController from '../BaseFinancialReportController';
import CustomerBalanceSummaryTableRows from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { CustomerBalanceSummaryApplication } from '@/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication';
import HasTenancyService from '@/services/Tenancy/TenancyService';
export default class CustomerBalanceSummaryReportController extends BaseFinancialReportController {
@Inject()
private customerBalanceSummaryApp: CustomerBalanceSummaryApplication;
customerBalanceSummaryService: CustomerBalanceSummary;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -34,7 +42,7 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia
/**
* Validation schema.
*/
private get validationSchema() {
get validationSchema() {
return [
...this.sheetNumberFormatValidationSchema,
@@ -54,81 +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.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async customerBalanceSummary(
async customerBalanceSummary(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { tenantId, settings } = req;
const filter = this.matchedQueryData(req);
try {
const customerBalanceSummary =
await this.customerBalanceSummaryService.customerBalanceSummary(
tenantId,
filter
);
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const acceptType = accept.types(['json', 'application/json+table']);
// Retrieves the xlsx format.
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.customerBalanceSummaryApp.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 (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);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const buffer = await this.customerBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.customerBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
switch (acceptType) {
case 'application/json+table':
return res
.status(200)
.send(this.transformToTableRows(tenantId, customerBalanceSummary));
case 'application/json':
default:
return res
.status(200)
.send(this.transformToJsonResponse(customerBalanceSummary));
}
} catch (error) {
next(error);

View File

@@ -2,21 +2,20 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService';
import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication';
@Service()
export default class GeneralLedgerReportController extends BaseFinancialReportController {
@Inject()
private generalLedgerApplication: GeneralLedgerApplication;
generalLedgetService: GeneralLedgerService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -32,7 +31,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
/**
* Validation schema.
*/
private get validationSchema(): ValidationChain[] {
get validationSchema(): ValidationChain[] {
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
@@ -61,56 +60,21 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
* @param {Request} req -
* @param {Response} res -
*/
private async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = 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_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.generalLedgerApplication.table(tenantId, filter);
try {
const { data, query, meta } =
await this.generalLedgetService.generalLedger(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.generalLedgerApplication.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.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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.generalLedgerApplication.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.generalLedgerApplication.sheet(tenantId, filter);
return res.status(200).send(sheet);
} catch (error) {
next(error);
}
}
}

View File

@@ -8,20 +8,24 @@ import {
ValidationChain,
} from 'express';
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 { InventortyDetailsApplication } from '@/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class InventoryDetailsController extends BaseController {
@Inject()
private inventoryItemDetailsApp: InventortyDetailsApplication;
inventoryDetailsService: InventoryDetailsService;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -41,7 +45,7 @@ export default class InventoryDetailsController extends BaseController {
* Balance sheet validation schecma.
* @returns {ValidationChain[]}
*/
private get validationSchema(): ValidationChain[] {
get validationSchema(): ValidationChain[] {
return [
query('number_format.precision')
.optional()
@@ -73,76 +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 {Response} res
* @param {NextFunction} next
* @returns {Response}
*/
private async inventoryDetails(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
async inventoryDetails(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const filter = {
...this.matchedQueryData(req),
};
try {
const inventoryDetails =
await this.inventoryDetailsService.inventoryDetails(tenantId, filter);
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.inventoryItemDetailsApp.csv(tenantId, filter);
const acceptType = accept.types(['json', 'application/json+table']);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.inventoryItemDetailsApp.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 table format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const table = await this.inventoryItemDetailsApp.table(
tenantId,
filter
);
return res.status(200).send(table);
// Retrieves the pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const buffer = await this.inventoryItemDetailsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
} else {
const sheet = await this.inventoryItemDetailsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
switch (acceptType) {
case 'application/json+table':
return res
.status(200)
.send(this.transformToTableRows(inventoryDetails, tenantId));
case 'json':
default:
return res
.status(200)
.send(this.transformJsonResponse(inventoryDetails));
}
} catch (error) {
next(error);

View File

@@ -3,15 +3,14 @@ import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController';
import InventoryValuationService from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { InventoryValuationSheetApplication } from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class InventoryValuationReportController extends BaseFinancialReportController {
@Inject()
private inventoryValuationApp: InventoryValuationSheetApplication;
inventoryValuationService: InventoryValuationService;
/**
* Router constructor.
@@ -72,55 +71,19 @@ export default class InventoryValuationReportController extends BaseFinancialRep
const { tenantId } = 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_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.inventoryValuationApp.table(tenantId, filter);
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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.inventoryValuationApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
try {
const { data, query, meta } =
await this.inventoryValuationService.inventoryValuationSheet(
tenantId,
filter
);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
});
return res.status(200).send(pdfContent);
// Retrieves the json format.
} else {
const { data, query, meta } = await this.inventoryValuationApp.sheet(
tenantId,
filter
);
return res.status(200).send({ meta, data, query });
} catch (error) {
next(error);
}
}
}

View File

@@ -3,15 +3,14 @@ import { Request, Response, Router, NextFunction } from 'express';
import { castArray } from 'lodash';
import { query, oneOf } from 'express-validator';
import BaseFinancialReportController from './BaseFinancialReportController';
import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication';
@Service()
export default class JournalSheetController extends BaseFinancialReportController {
@Inject()
private journalSheetApp: JournalSheetApplication;
journalService: JournalSheetService;
/**
* Router constructor.
@@ -58,58 +57,28 @@ export default class JournalSheetController extends BaseFinancialReportControlle
* @param {Request} req -
* @param {Response} res -
*/
private async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
let filter = this.matchedQueryData(req);
filter = {
...filter,
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.journalSheetApp.table(tenantId, filter);
return res.status(200).send(table);
// 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'
try {
const { data, query, meta } = await this.journalService.journalSheet(
tenantId,
filter
);
return res.send(buffer);
// Retrieves the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.journalSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
res.send(pdfContent);
} else {
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} catch (error) {
next(error);
}
}
}

View File

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

View File

@@ -1,18 +1,17 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import moment from 'moment';
import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController';
import { PurchasesByItemsService } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService';
import PurchasesByItemsService from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { PurcahsesByItemsApplication } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication';
@Service()
export default class PurchasesByItemReportController extends BaseFinancialReportController {
@Inject()
private purchasesByItemsApp: PurcahsesByItemsApplication;
purchasesByItemsService: PurchasesByItemsService;
/**
* Router constructor.
@@ -64,56 +63,20 @@ export default class PurchasesByItemReportController extends BaseFinancialReport
* @param {Request} req -
* @param {Response} res -
*/
public async purchasesByItems(req: Request, res: Response) {
async purchasesByItems(req: Request, res: Response, next: NextFunction) {
const { tenantId } = 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_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// JSON table response format.
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);
// PDF response format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.purchasesByItemsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
try {
const { data, query, meta } =
await this.purchasesByItemsService.purchasesByItems(tenantId, filter);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
});
return res.send(pdfContent);
// Json response format.
} else {
const sheet = await this.purchasesByItemsApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} catch (error) {
next(error);
}
}
}

View File

@@ -1,39 +1,41 @@
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 asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController';
import SalesByItemsReportService from '@/services/FinancialStatements/SalesByItems/SalesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { SalesByItemsApplication } from '@/services/FinancialStatements/SalesByItems/SalesByItemsApplication';
@Service()
export default class SalesByItemsReportController extends BaseFinancialReportController {
@Inject()
private salesByItemsApp: SalesByItemsApplication;
salesByItemsService: SalesByItemsReportService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
'/',
CheckPolicies(ReportsAction.READ_SALES_BY_ITEMS, AbilitySubject.Report),
CheckPolicies(
ReportsAction.READ_SALES_BY_ITEMS,
AbilitySubject.Report
),
this.validationSchema,
this.validationResult,
asyncMiddleware(this.salesByItems.bind(this))
asyncMiddleware(this.purchasesByItems.bind(this))
);
return router;
}
/**
* Validation schema.
* @returns {ValidationChain[]}
*/
private get validationSchema(): ValidationChain[] {
get validationSchema(): ValidationChain[] {
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
@@ -61,53 +63,22 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon
* @param {Request} req -
* @param {Response} res -
*/
private async salesByItems(req: Request, res: Response, next: NextFunction) {
async purchasesByItems(req: Request, res: Response, next: NextFunction) {
const { tenantId } = 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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'
try {
const { data, query, meta } = await this.salesByItemsService.salesByItems(
tenantId,
filter
);
return res.send(buffer);
// Retrieves the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.salesByItemsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
});
return res.send(pdfContent);
} else {
const sheet = await this.salesByItemsApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} catch (error) {
next(error);
}
}
}

View File

@@ -1,122 +0,0 @@
import { Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SalesTaxLiabilitySummaryApplication } from '@/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
export default class SalesTaxLiabilitySummary extends BaseFinancialReportController {
@Inject()
private salesTaxLiabilitySummaryApp: SalesTaxLiabilitySummaryApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.get(
'/',
CheckPolicies(
ReportsAction.READ_SALES_TAX_LIABILITY_SUMMARY,
AbilitySubject.Report
),
this.validationSchema,
asyncMiddleware(this.salesTaxLiabilitySummary.bind(this))
);
return router;
}
/**
* Validation schema.
* @returns {ValidationChain[]}
*/
private get validationSchema() {
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
];
}
/*
* Retrieves the sales tax liability summary.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async salesTaxLiabilitySummary(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the json table format.
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const table = await this.salesTaxLiabilitySummaryApp.table(
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);
// Retrieves the json format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.status(200).send(pdfContent);
} else {
const sheet = await this.salesTaxLiabilitySummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);
}
}
}

View File

@@ -1,22 +1,30 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { Inject, Service } from 'typedi';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import {
AbilitySubject,
ITransactionsByCustomersStatement,
ReportsAction,
} from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
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 { TransactionsByCustomerApplication } from '@/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class TransactionsByCustomersReportController extends BaseFinancialReportController {
@Inject()
private transactionsByCustomersApp: TransactionsByCustomerApplication;
transactionsByCustomersService: TransactionsByCustomersService;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
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.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async transactionsByCustomers(
async transactionsByCustomers(
req: Request,
res: Response,
next: NextFunction
@@ -64,62 +104,25 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
const { tenantId } = 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
try {
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.transactionsByCustomersApp.table(
const report =
await this.transactionsByCustomersService.transactionsByCustomers(
tenantId,
filter
);
return res.status(200).send(table);
// Retrieve the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const csv = await this.transactionsByCustomersApp.csv(tenantId, filter);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(csv);
// Retrieve the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.transactionsByCustomersApp.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);
// Retrieve the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByCustomersApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
} else {
const sheet = await this.transactionsByCustomersApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
switch (acceptType) {
case 'json':
return res
.status(200)
.send(this.transfromToJsonResponse(report.data, report.columns));
case 'application/json+table':
default:
return res
.status(200)
.send(this.transformToTableResponse(report.data, tenantId));
}
} catch (error) {
next(error);

View File

@@ -3,19 +3,27 @@ import { query, ValidationChain } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
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 { ACCEPT_TYPE } from '@/interfaces/Http';
import { TransactionsByVendorApplication } from '@/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication';
export default class TransactionsByVendorsReportController extends BaseFinancialReportController {
@Inject()
private transactionsByVendorsApp: TransactionsByVendorApplication;
transactionsByVendorsService: TransactionsByVendorsService;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -34,7 +42,7 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
/**
* Validation schema.
*/
private get validationSchema(): ValidationChain[] {
get validationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
@@ -50,76 +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.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async transactionsByVendors(
req: Request,
res: Response,
next: NextFunction
) {
async transactionsByVendors(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
const report =
await this.transactionsByVendorsService.transactionsByVendors(
tenantId,
filter
);
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const acceptType = accept.types(['json', 'application/json+table']);
// Retrieves the xlsx format.
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.transactionsByVendorsApp.xlsx(
tenantId,
filter
);
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
res.setHeader(
'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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByVendorsApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.transactionsByVendorsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
switch (acceptType) {
case 'application/json+table':
return res
.status(200)
.send(this.transformToTableRows(tenantId, report.data));
case 'json':
default:
return res.status(200).send(this.transformToJsonResponse(report));
}
} catch (error) {
next(error);

View File

@@ -3,21 +3,20 @@ import { Request, Response, Router, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService';
import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { TrialBalanceSheetApplication } from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class TrialBalanceSheetController extends BaseFinancialReportController {
@Inject()
private trialBalanceSheetApp: TrialBalanceSheetApplication;
trialBalanceSheetService: TrialBalanceSheetService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -37,7 +36,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
* Validation schema.
* @return {ValidationChain[]}
*/
private get trialBalanceSheetValidationSchema(): ValidationChain[] {
get trialBalanceSheetValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('basis').optional(),
@@ -60,74 +59,28 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
/**
* Retrieve the trial balance sheet.
*/
private async trialBalanceSheet(
public async trialBalanceSheet(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { tenantId, settings } = req;
let filter = this.matchedQueryData(req);
filter = {
...filter,
accountsIds: castArray(filter.accountsIds),
};
try {
const accept = this.accepts(req);
const { data, query, meta } =
await this.trialBalanceSheetService.trialBalanceSheet(tenantId, filter);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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 pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.trialBalanceSheetApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves in json format.
} else {
const { data, query, meta } = await this.trialBalanceSheetApp.sheet(
tenantId,
filter
);
return res.status(200).send({ data, query, meta });
}
return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
} catch (error) {
next(error);
}

View File

@@ -3,19 +3,27 @@ import { query } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
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 { ACCEPT_TYPE } from '@/interfaces/Http';
import { VendorBalanceSummaryApplication } from '@/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication';
import HasTenancyService from '@/services/Tenancy/TenancyService';
export default class VendorBalanceSummaryReportController extends BaseFinancialReportController {
@Inject()
private vendorBalanceSummaryApp: VendorBalanceSummaryApplication;
vendorBalanceSummaryService: VendorBalanceSummaryService;
@Inject()
tenancy: HasTenancyService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.get(
@@ -33,7 +41,7 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
/**
* Validation schema.
*/
private get validationSchema() {
get validationSchema() {
return [
...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
@@ -51,74 +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.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
public async vendorBalanceSummary(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
async vendorBalanceSummary(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const filter = this.matchedQueryData(req);
try {
const vendorBalanceSummary =
await this.vendorBalanceSummaryService.vendorBalanceSummary(
tenantId,
filter
);
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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const acceptType = accept.types(['json', 'application/json+table']);
// Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.vendorBalanceSummaryApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.vendorBalanceSummaryApp.xlsx(
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 pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.vendorBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.vendorBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
switch (acceptType) {
case 'application/json+table':
return res
.status(200)
.send(this.transformToTableRows(tenantId, vendorBalanceSummary));
case 'json':
default:
return res
.status(200)
.send(this.transformToJsonResponse(vendorBalanceSummary));
}
} catch (error) {
next(error);

View File

@@ -1,250 +0,0 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { body, param, query } from 'express-validator';
import { defaultTo } from 'lodash';
import BaseController from '@/api/controllers/BaseController';
import { ServiceError } from '@/exceptions';
import { ImportResourceApplication } from '@/services/Import/ImportResourceApplication';
import { uploadImportFile } from './_utils';
import { parseJsonSafe } from '@/utils/parse-json-safe';
@Service()
export class ImportController extends BaseController {
@Inject()
private importResourceApp: ImportResourceApplication;
/**
* Router constructor method.
*/
router() {
const router = Router();
router.post(
'/file',
uploadImportFile.single('file'),
this.importValidationSchema,
this.validationResult,
this.asyncMiddleware(this.fileUpload.bind(this)),
this.catchServiceErrors
);
router.post(
'/:import_id/import',
this.asyncMiddleware(this.import.bind(this)),
this.catchServiceErrors
);
router.post(
'/:import_id/mapping',
[
param('import_id').exists().isString(),
body('mapping').exists().isArray({ min: 1 }),
body('mapping.*.group').optional(),
body('mapping.*.from').exists(),
body('mapping.*.to').exists(),
],
this.validationResult,
this.asyncMiddleware(this.mapping.bind(this)),
this.catchServiceErrors
);
router.get(
'/sample',
[query('resource').exists(), query('format').optional()],
this.validationResult,
this.downloadImportSample.bind(this),
this.catchServiceErrors
);
router.get(
'/:import_id',
this.asyncMiddleware(this.getImportFileMeta.bind(this)),
this.catchServiceErrors
);
router.get(
'/:import_id/preview',
this.asyncMiddleware(this.preview.bind(this)),
this.catchServiceErrors
);
return router;
}
/**
* Import validation schema.
* @returns {ValidationSchema[]}
*/
private get importValidationSchema() {
return [body('resource').exists(), body('params').optional()];
}
/**
* Imports xlsx/csv to the given resource type.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async fileUpload(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const body = this.matchedBodyData(req);
const params = defaultTo(parseJsonSafe(body.params), {});
try {
const data = await this.importResourceApp.import(
tenantId,
body.resource,
req.file.filename,
params
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Maps the columns of the imported file.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async mapping(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { import_id: importId } = req.params;
const body = this.matchedBodyData(req);
try {
const mapping = await this.importResourceApp.mapping(
tenantId,
importId,
body?.mapping
);
return res.status(200).send(mapping);
} catch (error) {
next(error);
}
}
/**
* Preview the imported file before actual importing.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async preview(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { import_id: importId } = req.params;
try {
const preview = await this.importResourceApp.preview(tenantId, importId);
return res.status(200).send(preview);
} catch (error) {
next(error);
}
}
/**
* Importing the imported file to the application storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async import(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { import_id: importId } = req.params;
try {
const result = await this.importResourceApp.process(tenantId, importId);
return res.status(200).send(result);
} catch (error) {
next(error);
}
}
/**
* Retrieves the csv/xlsx sample sheet of the given resource name.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async downloadImportSample(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { format, resource } = this.matchedQueryData(req);
try {
const result = this.importResourceApp.sample(tenantId, resource, format);
return res.status(200).send(result);
} catch (error) {
next(error);
}
}
/**
* Retrieves the import file meta.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getImportFileMeta(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { import_id: importId } = req.params;
try {
const result = await this.importResourceApp.importMeta(
tenantId,
importId
);
return res.status(200).send(result);
} catch (error) {
next(error);
}
}
/**
* Transforms service errors to response.
* @param {Error}
* @param {Request} req
* @param {Response} res
* @param {ServiceError} error
*/
private catchServiceErrors(
error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'INVALID_MAP_ATTRS') {
return res.status(400).send({
errors: [{ type: 'INVALID_MAP_ATTRS' }],
});
}
if (error.errorType === 'DUPLICATED_FROM_MAP_ATTR') {
return res.status(400).send({
errors: [{ type: 'DUPLICATED_FROM_MAP_ATTR' }],
});
}
if (error.errorType === 'DUPLICATED_TO_MAP_ATTR') {
return res.status(400).send({
errors: [{ type: 'DUPLICATED_TO_MAP_ATTR' }],
});
}
if (error.errorType === 'IMPORTED_FILE_EXTENSION_INVALID') {
return res.status(400).send({
errors: [{ type: 'IMPORTED_FILE_EXTENSION_INVALID' }],
});
}
return res.status(400).send({
errors: [{ type: error.errorType }],
});
}
next(error);
}
}

View File

@@ -1,32 +0,0 @@
import Multer from 'multer';
import { ServiceError } from '@/exceptions';
export function allowSheetExtensions(req, file, cb) {
if (
file.mimetype !== 'text/csv' &&
file.mimetype !== 'application/vnd.ms-excel' &&
file.mimetype !==
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
) {
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
return;
}
cb(null, true);
}
const storage = Multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/imports');
},
filename: function (req, file, cb) {
// Add the creation timestamp to clean up temp files later.
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, uniqueSuffix);
},
});
export const uploadImportFile = Multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: allowSheetExtensions,
});

View File

@@ -149,11 +149,6 @@ export default class ItemsController extends BaseController {
.trim()
.escape()
.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')
.optional({ nullable: true })
.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);
}

View File

@@ -6,7 +6,7 @@ import ItemTransactionsController from './ItemsTransactions';
@Service()
export default class ItemsBaseController {
public router() {
router() {
const router = Router();
router.use('/', Container.get(ItemsController).router());

View File

@@ -387,7 +387,7 @@ export default class ManualJournalsController extends BaseController {
errors: [{ type: 'CREDIT.DEBIT.NOT.EQUALS', code: 300 }],
});
}
if (error.errorType === 'accounts_ids_not_found') {
if (error.errorType === 'acccounts_ids_not_found') {
return res.boom.badRequest(
'Journal entries some of accounts ids not exists.',
{ errors: [{ type: 'ACCOUNTS.IDS.NOT.FOUND', code: 400 }] }

View File

@@ -31,14 +31,14 @@ export default class OrganizationController extends BaseController {
router.post(
'/build',
this.buildOrganizationValidationSchema,
this.organizationValidationSchema,
this.validationResult,
asyncMiddleware(this.build.bind(this)),
this.handleServiceErrors.bind(this)
);
router.put(
'/',
this.updateOrganizationValidationSchema,
this.organizationValidationSchema,
this.validationResult,
this.asyncMiddleware(this.updateOrganization.bind(this)),
this.handleServiceErrors.bind(this)
@@ -55,7 +55,7 @@ export default class OrganizationController extends BaseController {
* Organization setup schema.
* @return {ValidationChain[]}
*/
private get commonOrganizationValidationSchema(): ValidationChain[] {
private get organizationValidationSchema(): ValidationChain[] {
return [
check('name').exists().trim(),
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.
* @param {Request} req - Express request.

View File

@@ -1,27 +1,26 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import {
AbilitySubject,
BillAction,
IBillDTO,
IBillEditDTO,
} from '@/interfaces';
import { Service, Inject } from 'typedi';
import { AbilitySubject, BillAction, IBillDTO, IBillEditDTO } from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BillsService from '@/services/Purchases/Bills';
import BaseController from '@/api/controllers/BaseController';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { BillsApplication } from '@/services/Purchases/Bills/BillsApplication';
import BillPaymentsService from '@/services/Purchases/BillPaymentsService';
@Service()
export default class BillsController extends BaseController {
@Inject()
private billsApplication: BillsApplication;
private billsService: BillsService;
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private billPayments: BillPaymentsService;
/**
* Router constructor.
*/
@@ -98,7 +97,7 @@ export default class BillsController extends BaseController {
/**
* Common validation schema.
*/
private get billValidationSchema() {
get billValidationSchema() {
return [
check('bill_number').exists().trim().escape(),
check('reference_no').optional().trim().escape(),
@@ -115,14 +114,12 @@ export default class BillsController extends BaseController {
check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(),
check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -139,22 +136,13 @@ export default class BillsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
.escape()
.isString(),
check('entries.*.tax_rate_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
];
}
/**
* Common validation schema.
*/
private get billEditValidationSchema() {
get billEditValidationSchema() {
return [
check('bill_number').optional().trim().escape(),
check('reference_no').optional().trim().escape(),
@@ -196,14 +184,14 @@ export default class BillsController extends BaseController {
/**
* Bill validation schema.
*/
private get specificBillValidationSchema() {
get specificBillValidationSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Bills list validation schema.
*/
private get billsListingValidationSchema() {
get billsListingValidationSchema() {
return [
query('view_slug').optional().isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
@@ -215,7 +203,7 @@ export default class BillsController extends BaseController {
];
}
private get dueBillsListingValidationSchema() {
get dueBillsListingValidationSchema() {
return [
query('vendor_id').optional().trim().escape(),
query('payment_made_id').optional().trim().escape(),
@@ -228,16 +216,17 @@ export default class BillsController extends BaseController {
* @param {Response} res
* @param {Function} next
*/
private async newBill(req: Request, res: Response, next: NextFunction) {
async newBill(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const billDTO: IBillDTO = this.matchedBodyData(req);
try {
const storedBill = await this.billsApplication.createBill(
const storedBill = await this.billsService.createBill(
tenantId,
billDTO,
user
);
return res.status(200).send({
id: storedBill.id,
message: 'The bill has been created successfully.',
@@ -252,13 +241,13 @@ export default class BillsController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async editBill(req: Request, res: Response, next: NextFunction) {
async editBill(req: Request, res: Response, next: NextFunction) {
const { id: billId } = req.params;
const { tenantId, user } = req;
const billDTO: IBillEditDTO = this.matchedBodyData(req);
try {
await this.billsApplication.editBill(tenantId, billId, billDTO, user);
await this.billsService.editBill(tenantId, billId, billDTO, user);
return res.status(200).send({
id: billId,
@@ -274,12 +263,12 @@ export default class BillsController extends BaseController {
* @param {Request} req -
* @param {Response} res -
*/
private async openBill(req: Request, res: Response, next: NextFunction) {
async openBill(req: Request, res: Response, next: NextFunction) {
const { id: billId } = req.params;
const { tenantId } = req;
try {
await this.billsApplication.openBill(tenantId, billId);
await this.billsService.openBill(tenantId, billId);
return res.status(200).send({
id: billId,
@@ -296,14 +285,14 @@ export default class BillsController extends BaseController {
* @param {Response} res
* @return {Response}
*/
private async getBill(req: Request, res: Response, next: NextFunction) {
async getBill(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billId } = req.params;
try {
const bill = await this.billsApplication.getBill(tenantId, billId);
const bill = await this.billsService.getBill(tenantId, billId);
return res.status(200).send({ bill });
return res.status(200).send(this.transfromToResponse({ bill }));
} catch (error) {
next(error);
}
@@ -315,12 +304,12 @@ export default class BillsController extends BaseController {
* @param {Response} res -
* @return {Response}
*/
private async deleteBill(req: Request, res: Response, next: NextFunction) {
async deleteBill(req: Request, res: Response, next: NextFunction) {
const billId = req.params.id;
const { tenantId } = req;
try {
await this.billsApplication.deleteBill(tenantId, billId);
await this.billsService.deleteBill(tenantId, billId);
return res.status(200).send({
id: billId,
@@ -337,7 +326,7 @@ export default class BillsController extends BaseController {
* @param {Response} res -
* @return {Response}
*/
private async billsList(req: Request, res: Response, next: NextFunction) {
public async billsList(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = {
page: 1,
@@ -348,11 +337,14 @@ export default class BillsController extends BaseController {
};
try {
const billsWithPagination = await this.billsApplication.getBills(
tenantId,
filter
);
return res.status(200).send(billsWithPagination);
const { bills, pagination, filterMeta } =
await this.billsService.getBills(tenantId, filter);
return res.status(200).send({
bills: this.transfromToResponse(bills),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) {
next(error);
}
@@ -364,13 +356,12 @@ export default class BillsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async getDueBills(req: Request, res: Response, next: NextFunction) {
public async getDueBills(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req);
try {
const bills = await this.billsApplication.getDueBills(tenantId, vendorId);
const bills = await this.billsService.getDueBills(tenantId, vendorId);
return res.status(200).send({ bills });
} catch (error) {
next(error);
@@ -383,7 +374,7 @@ export default class BillsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private getBillPaymentsTransactions = async (
public getBillPaymentsTransactions = async (
req: Request,
res: Response,
next: NextFunction
@@ -392,7 +383,7 @@ export default class BillsController extends BaseController {
const { id: billId } = req.params;
try {
const billPayments = await this.billsApplication.getBillPayments(
const billPayments = await this.billPayments.getBillPayments(
tenantId,
billId
);
@@ -550,26 +541,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 }],
});
}
if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') {
return res.boom.badRequest(null, {
errors: [
{
type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
code: 2000,
},
],
});
}
}
next(error);
}

View File

@@ -4,7 +4,7 @@ import { check, param, query, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import { ServiceError } from '@/exceptions';
import BaseController from '@/api/controllers/BaseController';
import { BillPaymentsApplication } from '@/services/Purchases/BillPayments/BillPaymentsApplication';
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
import BillPaymentsPages from '@/services/Purchases/BillPayments/BillPaymentsPages';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CheckPolicies from '@/api/middleware/CheckPolicies';
@@ -17,18 +17,18 @@ import { AbilitySubject, IPaymentMadeAction } from '@/interfaces';
@Service()
export default class BillsPayments extends BaseController {
@Inject()
private billPaymentsApplication: BillPaymentsApplication;
billPaymentService: BillPaymentsService;
@Inject()
private dynamicListService: DynamicListingService;
dynamicListService: DynamicListingService;
@Inject()
private billPaymentsPages: BillPaymentsPages;
billPaymentsPages: BillPaymentsPages;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.post(
@@ -106,7 +106,7 @@ export default class BillsPayments extends BaseController {
* Bill payments schema validation.
* @return {ValidationChain[]}
*/
private get billPaymentSchemaValidation(): ValidationChain[] {
get billPaymentSchemaValidation(): ValidationChain[] {
return [
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -121,7 +121,7 @@ export default class BillsPayments extends BaseController {
check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
}
@@ -129,7 +129,7 @@ export default class BillsPayments extends BaseController {
* Specific bill payment schema validation.
* @returns {ValidationChain[]}
*/
private get specificBillPaymentValidateSchema(): ValidationChain[] {
get specificBillPaymentValidateSchema(): ValidationChain[] {
return [param('id').exists().isNumeric().toInt()];
}
@@ -137,7 +137,7 @@ export default class BillsPayments extends BaseController {
* Bills payment list validation schema.
* @returns {ValidationChain[]}
*/
private get listingValidationSchema(): ValidationChain[] {
get listingValidationSchema(): ValidationChain[] {
return [
query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(),
@@ -154,15 +154,19 @@ export default class BillsPayments extends BaseController {
* @param {Request} req -
* @param {Response} res -
*/
private async getBillPaymentNewPageEntries(req: Request, res: Response) {
async getBillPaymentNewPageEntries(req: Request, res: Response) {
const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req);
const entries = await this.billPaymentsPages.getNewPageEntries(
tenantId,
vendorId
);
return res.status(200).send({ entries });
try {
const entries = await this.billPaymentsPages.getNewPageEntries(
tenantId,
vendorId
);
return res.status(200).send({
entries: this.transfromToResponse(entries),
});
} catch (error) {}
}
/**
@@ -170,7 +174,7 @@ export default class BillsPayments extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async getBillPaymentEditPage(
async getBillPaymentEditPage(
req: Request,
res: Response,
next: NextFunction
@@ -179,12 +183,16 @@ export default class BillsPayments extends BaseController {
const { id: paymentReceiveId } = req.params;
try {
const billPaymentsWithEditEntries =
const { billPayment, entries } =
await this.billPaymentsPages.getBillPaymentEditPage(
tenantId,
paymentReceiveId
);
return res.status(200).send(billPaymentsWithEditEntries);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
entries: this.transfromToResponse(entries),
});
} catch (error) {
next(error);
}
@@ -197,19 +205,16 @@ export default class BillsPayments extends BaseController {
* @param {Response} res
* @param {Response} res
*/
private async createBillPayment(
req: Request,
res: Response,
next: NextFunction
) {
async createBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentDTO = this.matchedBodyData(req);
try {
const billPayment = await this.billPaymentsApplication.createBillPayment(
const billPayment = await this.billPaymentService.createBillPayment(
tenantId,
billPaymentDTO
);
return res.status(200).send({
id: billPayment.id,
message: 'Payment made has been created successfully.',
@@ -224,17 +229,13 @@ export default class BillsPayments extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async editBillPayment(
req: Request,
res: Response,
next: NextFunction
) {
async editBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentDTO = this.matchedBodyData(req);
const { id: billPaymentId } = req.params;
try {
const paymentMade = await this.billPaymentsApplication.editBillPayment(
const paymentMade = await this.billPaymentService.editBillPayment(
tenantId,
billPaymentId,
billPaymentDTO
@@ -255,19 +256,12 @@ export default class BillsPayments extends BaseController {
* @param {Response} res -
* @return {Response} res -
*/
private async deleteBillPayment(
req: Request,
res: Response,
next: NextFunction
) {
async deleteBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
await this.billPaymentsApplication.deleteBillPayment(
tenantId,
billPaymentId
);
await this.billPaymentService.deleteBillPayment(tenantId, billPaymentId);
return res.status(200).send({
id: billPaymentId,
@@ -283,20 +277,19 @@ export default class BillsPayments extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async getBillPayment(
req: Request,
res: Response,
next: NextFunction
) {
async getBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
const billPayment = await this.billPaymentsApplication.getBillPayment(
const billPayment = await this.billPaymentService.getBillPayment(
tenantId,
billPaymentId
);
return res.status(200).send({ billPayment });
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
});
} catch (error) {
next(error);
}
@@ -308,16 +301,12 @@ export default class BillsPayments extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async getPaymentBills(
req: Request,
res: Response,
next: NextFunction
) {
async getPaymentBills(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
const bills = await this.billPaymentsApplication.getPaymentBills(
const bills = await this.billPaymentService.getPaymentBills(
tenantId,
billPaymentId
);
@@ -333,11 +322,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res -
* @return {Response}
*/
private async getBillsPayments(
req: Request,
res: Response,
next: NextFunction
) {
async getBillsPayments(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentsFilter = {
page: 1,
@@ -349,12 +334,17 @@ export default class BillsPayments extends BaseController {
};
try {
const billPaymentsWithPagination =
await this.billPaymentsApplication.getBillPayments(
const { billPayments, pagination, filterMeta } =
await this.billPaymentService.listBillPayments(
tenantId,
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) {
next(error);
}
@@ -367,7 +357,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceError(
handleServiceError(
error: Error,
req: Request,
res: Response,

View File

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

View File

@@ -26,7 +26,6 @@ import GetCreditNoteAssociatedInvoicesToApply from '@/services/CreditNotes/GetCr
import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCreditNoteAssociatedAppliedInvoices';
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
import { ACCEPT_TYPE } from '@/interfaces/Http';
/**
* Credit notes controller.
* @service
@@ -223,7 +222,7 @@ export default class PaymentReceivesController extends BaseController {
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -294,7 +293,7 @@ export default class PaymentReceivesController extends BaseController {
return [
check('from_account_id').exists().isNumeric().toInt(),
check('description').optional(),
check('amount').exists().isNumeric().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 {Response} res
* @param {NextFunction} next
@@ -452,28 +451,38 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req;
const { id: creditNoteId } = req.params;
const accept = this.accepts(req);
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 {
try {
const creditNote = await this.getCreditNoteService.getCreditNote(
tenantId,
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,31 +1,42 @@
import { Inject, Service } from 'typedi';
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 { Inject, Service } from 'typedi';
import {
AbilitySubject,
IPaymentReceiveDTO,
PaymentReceiveAction,
PaymentReceiveMailOptsDTO,
SaleInvoiceAction,
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import PaymentReceiveNotifyBySms from '@/services/Sales/PaymentReceives/PaymentReceiveSmsNotify';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import GetPaymentReceivePdf from '@/services/Sales/PaymentReceives/GetPaymentReeceivePdf';
/**
* Payments receives controller.
* @service
*/
@Service()
export default class PaymentReceivesController extends BaseController {
@Inject()
private paymentReceiveApplication: PaymentReceivesApplication;
paymentReceiveService: PaymentReceiveService;
@Inject()
private PaymentReceivesPages: PaymentReceivesPages;
PaymentReceivesPages: PaymentReceivesPages;
@Inject()
private dynamicListService: DynamicListingService;
dynamicListService: DynamicListingService;
@Inject()
paymentReceiveSmsNotify: PaymentReceiveNotifyBySms;
@Inject()
paymentReceivePdf: GetPaymentReceivePdf;
/**
* Router constructor.
@@ -119,25 +130,6 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.deletePaymentReceive.bind(this)),
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;
}
@@ -145,7 +137,7 @@ export default class PaymentReceivesController extends BaseController {
* Payment receive schema.
* @return {Array}
*/
private get paymentReceiveSchema(): ValidationChain[] {
get paymentReceiveSchema(): ValidationChain[] {
return [
check('customer_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -163,14 +155,14 @@ export default class PaymentReceivesController extends BaseController {
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
}
/**
* Payment receive list validation schema.
*/
private get validatePaymentReceiveList(): ValidationChain[] {
get validatePaymentReceiveList(): ValidationChain[] {
return [
query('stringified_filter_roles').optional().isJSON(),
@@ -189,7 +181,7 @@ export default class PaymentReceivesController extends BaseController {
/**
* Validate payment receive parameters.
*/
private get paymentReceiveValidation() {
get paymentReceiveValidation() {
return [param('id').exists().isNumeric().toInt()];
}
@@ -197,14 +189,14 @@ export default class PaymentReceivesController extends BaseController {
* New payment receive validation schema.
* @return {Array}
*/
private get newPaymentReceiveValidation() {
get newPaymentReceiveValidation() {
return [...this.paymentReceiveSchema];
}
/**
* Edit payment receive validation.
*/
private get editPaymentReceiveValidation() {
get editPaymentReceiveValidation() {
return [
param('id').exists().isNumeric().toInt(),
...this.paymentReceiveSchema,
@@ -217,17 +209,13 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res
* @return {Response}
*/
private async newPaymentReceive(
req: Request,
res: Response,
next: NextFunction
) {
async newPaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
try {
const storedPaymentReceive =
await this.paymentReceiveApplication.createPaymentReceive(
await this.paymentReceiveService.createPaymentReceive(
tenantId,
paymentReceive,
user
@@ -247,18 +235,14 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res
* @return {Response}
*/
private async editPaymentReceive(
req: Request,
res: Response,
next: NextFunction
) {
async editPaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const { id: paymentReceiveId } = req.params;
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
try {
await this.paymentReceiveApplication.editPaymentReceive(
await this.paymentReceiveService.editPaymentReceive(
tenantId,
paymentReceiveId,
paymentReceive,
@@ -278,20 +262,17 @@ export default class PaymentReceivesController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async deletePaymentReceive(
req: Request,
res: Response,
next: NextFunction
) {
async deletePaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const { id: paymentReceiveId } = req.params;
try {
await this.paymentReceiveApplication.deletePaymentReceive(
await this.paymentReceiveService.deletePaymentReceive(
tenantId,
paymentReceiveId,
user
);
return res.status(200).send({
id: paymentReceiveId,
message: 'The payment receive has been deleted successfully',
@@ -307,7 +288,7 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async getPaymentReceiveInvoices(
async getPaymentReceiveInvoices(
req: Request,
res: Response,
next: NextFunction
@@ -317,7 +298,7 @@ export default class PaymentReceivesController extends BaseController {
try {
const saleInvoices =
await this.paymentReceiveApplication.getPaymentReceiveInvoices(
await this.paymentReceiveService.getPaymentReceiveInvoices(
tenantId,
paymentReceiveId
);
@@ -334,11 +315,7 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res
* @return {Response}
*/
private async getPaymentReceiveList(
req: Request,
res: Response,
next: NextFunction
) {
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = {
sortOrder: 'desc',
@@ -349,12 +326,14 @@ export default class PaymentReceivesController extends BaseController {
};
try {
const paymentsReceivedWithPagination =
await this.paymentReceiveApplication.getPaymentReceives(
tenantId,
filter
);
return res.status(200).send(paymentsReceivedWithPagination);
const { paymentReceives, pagination, filterMeta } =
await this.paymentReceiveService.listPaymentReceives(tenantId, filter);
return res.status(200).send({
payment_receives: this.transfromToResponse(paymentReceives),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) {
next(error);
}
@@ -365,7 +344,7 @@ export default class PaymentReceivesController extends BaseController {
* @param {Request} req - Request.
* @param {Response} res - Response.
*/
private async getPaymentReceiveNewPageEntries(
async getPaymentReceiveNewPageEntries(
req: Request,
res: Response,
next: NextFunction
@@ -388,11 +367,11 @@ export default class PaymentReceivesController extends BaseController {
/**
* Retrieve the given payment receive details.
* @async
* @asycn
* @param {Request} req -
* @param {Response} res -
*/
private async getPaymentReceiveEditPage(
async getPaymentReceiveEditPage(
req: Request,
res: Response,
next: NextFunction
@@ -423,42 +402,40 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async getPaymentReceive(
req: Request,
res: Response,
next: NextFunction
) {
async getPaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
const accept = this.accepts(req);
try {
const paymentReceive = await this.paymentReceiveService.getPaymentReceive(
tenantId,
paymentReceiveId
);
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 =
await this.paymentReceiveApplication.getPaymentReceive(
tenantId,
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.paymentReceivePdf.getPaymentReceivePdf(
tenantId,
paymentReceive
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
}
}
@@ -477,11 +454,10 @@ export default class PaymentReceivesController extends BaseController {
const { id: paymentReceiveId } = req.params;
try {
const paymentReceive =
await this.paymentReceiveApplication.notifyPaymentBySms(
tenantId,
paymentReceiveId
);
const paymentReceive = await this.paymentReceiveSmsNotify.notifyBySms(
tenantId,
paymentReceiveId
);
return res.status(200).send({
id: paymentReceive.id,
message: 'The payment notification has been sent successfully.',
@@ -492,7 +468,7 @@ export default class PaymentReceivesController extends BaseController {
};
/**
* Retrieves the sms details of the given payment receive.
*
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
@@ -506,11 +482,10 @@ export default class PaymentReceivesController extends BaseController {
const { id: paymentReceiveId } = req.params;
try {
const smsDetails =
await this.paymentReceiveApplication.getPaymentSmsDetails(
tenantId,
paymentReceiveId
);
const smsDetails = await this.paymentReceiveSmsNotify.smsDetails(
tenantId,
paymentReceiveId
);
return res.status(200).send({
data: smsDetails,
});
@@ -519,74 +494,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.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @param error
* @param req
* @param res
* @param next
*/
private handleServiceErrors(
handleServiceErrors(
error: Error,
req: Request,
res: Response,

View File

@@ -1,18 +1,20 @@
import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator';
import { check, param, query, matchedData } from 'express-validator';
import { Inject, Service } from 'typedi';
import {
AbilitySubject,
ISaleEstimateDTO,
SaleEstimateAction,
SaleEstimateMailOptionsDTO,
SaleInvoiceAction,
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import SaleEstimateService from '@/services/Sales/SalesEstimate';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { ServiceError } from '@/exceptions';
import SaleEstimatesPdfService from '@/services/Sales/Estimates/SaleEstimatesPdf';
import SaleEstimateNotifyBySms from '@/services/Sales/Estimates/SaleEstimateSmsNotify';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleEstimatesApplication } from '@/services/Sales/Estimates/SaleEstimatesApplication';
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
@@ -21,15 +23,21 @@ const ACCEPT_TYPE = {
@Service()
export default class SalesEstimatesController extends BaseController {
@Inject()
private saleEstimatesApplication: SaleEstimatesApplication;
saleEstimateService: SaleEstimateService;
@Inject()
private dynamicListService: DynamicListingService;
dynamicListService: DynamicListingService;
@Inject()
saleEstimatesPdf: SaleEstimatesPdfService;
@Inject()
saleEstimateNotifySms: SaleEstimateNotifyBySms;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.post(
@@ -122,34 +130,13 @@ export default class SalesEstimatesController extends BaseController {
this.handleServiceErrors,
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;
}
/**
* Estimate validation schema.
*/
private get estimateValidationSchema() {
get estimateValidationSchema() {
return [
check('customer_id').exists().isNumeric().toInt(),
check('estimate_date').exists().isISO8601().toDate(),
@@ -190,14 +177,14 @@ export default class SalesEstimatesController extends BaseController {
/**
* Specific sale estimate validation schema.
*/
private get validateSpecificEstimateSchema() {
get validateSpecificEstimateSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Sales estimates list validation schema.
*/
private get validateEstimateListSchema() {
get validateEstimateListSchema() {
return [
query('view_slug').optional().isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
@@ -215,16 +202,15 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res -
* @return {Response} res -
*/
private async newEstimate(req: Request, res: Response, next: NextFunction) {
async newEstimate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
try {
const storedEstimate =
await this.saleEstimatesApplication.createSaleEstimate(
tenantId,
estimateDTO
);
const storedEstimate = await this.saleEstimateService.createEstimate(
tenantId,
estimateDTO
);
return res.status(200).send({
id: storedEstimate.id,
@@ -240,18 +226,19 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async editEstimate(req: Request, res: Response, next: NextFunction) {
async editEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
try {
// Update estimate with associated estimate entries.
await this.saleEstimatesApplication.editSaleEstimate(
await this.saleEstimateService.editEstimate(
tenantId,
estimateId,
estimateDTO
);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been created successfully.',
@@ -266,19 +253,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async deleteEstimate(
req: Request,
res: Response,
next: NextFunction
) {
async deleteEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
try {
await this.saleEstimatesApplication.deleteSaleEstimate(
tenantId,
estimateId
);
await this.saleEstimateService.deleteEstimate(tenantId, estimateId);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been deleted successfully.',
@@ -293,19 +274,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async deliverSaleEstimate(
req: Request,
res: Response,
next: NextFunction
) {
async deliverSaleEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
try {
await this.saleEstimatesApplication.deliverSaleEstimate(
tenantId,
estimateId
);
await this.saleEstimateService.deliverSaleEstimate(tenantId, estimateId);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been delivered successfully.',
@@ -321,19 +296,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async approveSaleEstimate(
req: Request,
res: Response,
next: NextFunction
) {
async approveSaleEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
try {
await this.saleEstimatesApplication.approveSaleEstimate(
tenantId,
estimateId
);
await this.saleEstimateService.approveSaleEstimate(tenantId, estimateId);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been approved successfully.',
@@ -349,19 +318,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async rejectSaleEstimate(
req: Request,
res: Response,
next: NextFunction
) {
async rejectSaleEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
try {
await this.saleEstimatesApplication.rejectSaleEstimate(
tenantId,
estimateId
);
await this.saleEstimateService.rejectSaleEstimate(tenantId, estimateId);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been rejected successfully.',
@@ -377,34 +340,36 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async getEstimate(req: Request, res: Response, next: NextFunction) {
async getEstimate(req: Request, res: Response, next: NextFunction) {
const { id: estimateId } = req.params;
const { tenantId } = req;
const accept = this.accepts(req);
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(
try {
const estimate = await this.saleEstimateService.getEstimate(
tenantId,
estimateId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
// 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.saleEstimatesPdf.saleEstimatePdf(
tenantId,
estimate
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
res.send(pdfContent);
// Retrieves estimates in json format.
} else {
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId,
estimateId
);
return res.status(200).send({ estimate });
} catch (error) {
next(error);
}
}
@@ -413,7 +378,7 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async getEstimates(req: Request, res: Response, next: NextFunction) {
async getEstimates(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = {
sortOrder: 'desc',
@@ -422,17 +387,28 @@ export default class SalesEstimatesController extends BaseController {
pageSize: 12,
...this.matchedQueryData(req),
};
try {
const salesEstimatesWithPagination =
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
return res.status(200).send(salesEstimatesWithPagination);
try {
const { salesEstimates, pagination, filterMeta } =
await this.saleEstimateService.estimatesList(tenantId, filter);
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(
this.transfromToResponse({
salesEstimates,
pagination,
filterMeta,
})
);
},
});
} catch (error) {
next(error);
}
}
private saleEstimateNotifyBySms = async (
public saleEstimateNotifyBySms = async (
req: Request,
res: Response,
next: NextFunction
@@ -441,11 +417,10 @@ export default class SalesEstimatesController extends BaseController {
const { id: estimateId } = req.params;
try {
const saleEstimate =
await this.saleEstimatesApplication.notifySaleEstimateBySms(
tenantId,
estimateId
);
const saleEstimate = await this.saleEstimateNotifySms.notifyBySms(
tenantId,
estimateId
);
return res.status(200).send({
id: saleEstimate.id,
message:
@@ -462,7 +437,7 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private saleEstimateSmsDetails = async (
public saleEstimateSmsDetails = async (
req: Request,
res: Response,
next: NextFunction
@@ -471,11 +446,10 @@ export default class SalesEstimatesController extends BaseController {
const { id: estimateId } = req.params;
try {
const estimateSmsDetails =
await this.saleEstimatesApplication.getSaleEstimateSmsDetails(
tenantId,
estimateId
);
const estimateSmsDetails = await this.saleEstimateNotifySms.smsDetails(
tenantId,
estimateId
);
return res.status(200).send({
data: estimateSmsDetails,
});
@@ -484,65 +458,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.
* @param {Error} error

View File

@@ -1,8 +1,9 @@
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 BaseController from '../BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import SaleInvoiceService from '@/services/Sales/SalesInvoices';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { ServiceError } from '@/exceptions';
import {
@@ -10,24 +11,41 @@ import {
ISaleInvoiceCreateDTO,
SaleInvoiceAction,
AbilitySubject,
SendInvoiceMailDTO,
} from '@/interfaces';
import SaleInvoicePdf from '@/services/Sales/SaleInvoicePdf';
import SaleInvoiceWriteoff from '@/services/Sales/SaleInvoiceWriteoff';
import SaleInvoiceNotifyBySms from '@/services/Sales/SaleInvoiceNotifyBySms';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import InvoicePaymentsService from '@/services/Sales/Invoices/InvoicePaymentsService';
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
@Service()
export default class SaleInvoicesController extends BaseController {
@Inject()
private saleInvoiceApplication: SaleInvoiceApplication;
saleInvoiceService: SaleInvoiceService;
@Inject()
private dynamicListService: DynamicListingService;
dynamicListService: DynamicListingService;
@Inject()
saleInvoicePdf: SaleInvoicePdf;
@Inject()
saleInvoiceWriteoff: SaleInvoiceWriteoff;
@Inject()
saleInvoiceSmsNotify: SaleInvoiceNotifyBySms;
@Inject()
invoicePaymentsSerivce: InvoicePaymentsService;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.post(
@@ -143,55 +161,13 @@ export default class SaleInvoicesController extends BaseController {
this.handleServiceErrors,
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;
}
/**
* Sale invoice validation schema.
*/
private get saleInvoiceValidationSchema() {
get saleInvoiceValidationSchema() {
return [
check('customer_id').exists().isNumeric().toInt(),
check('invoice_date').exists().isISO8601().toDate(),
@@ -209,9 +185,8 @@ export default class SaleInvoicesController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('is_inclusive_tax').optional().isBoolean().toBoolean(),
check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
@@ -224,15 +199,6 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
.escape()
.isString(),
check('entries.*.tax_rate_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
@@ -261,14 +227,14 @@ export default class SaleInvoicesController extends BaseController {
/**
* Specific sale invoice validation schema.
*/
private get specificSaleInvoiceValidation() {
get specificSaleInvoiceValidation() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Sales invoices list validation schema.
*/
private get saleInvoiceListValidationSchema() {
get saleInvoiceListValidationSchema() {
return [
query('view_slug').optional({ nullable: true }).isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
@@ -283,7 +249,7 @@ export default class SaleInvoicesController extends BaseController {
/**
* Due sale invoice list validation schema.
*/
private get dueSalesInvoicesListValidationSchema() {
get dueSalesInvoicesListValidationSchema() {
return [query('customer_id').optional().isNumeric().toInt()];
}
@@ -293,22 +259,17 @@ export default class SaleInvoicesController extends BaseController {
* @param {Response} res
* @param {Function} next
*/
private async newSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
async newSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const saleInvoiceDTO: ISaleInvoiceCreateDTO = this.matchedBodyData(req);
try {
// Creates a new sale invoice with associated entries.
const storedSaleInvoice =
await this.saleInvoiceApplication.createSaleInvoice(
tenantId,
saleInvoiceDTO,
user
);
const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice(
tenantId,
saleInvoiceDTO,
user
);
return res.status(200).send({
id: storedSaleInvoice.id,
message: 'The sale invoice has been created successfully.',
@@ -324,18 +285,14 @@ export default class SaleInvoicesController extends BaseController {
* @param {Response} res
* @param {Function} next
*/
private async editSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
async editSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const { id: saleInvoiceId } = req.params;
const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
try {
// Update the given sale invoice details.
await this.saleInvoiceApplication.editSaleInvoice(
await this.saleInvoiceService.editSaleInvoice(
tenantId,
saleInvoiceId,
saleInvoiceOTD,
@@ -356,16 +313,12 @@ export default class SaleInvoicesController extends BaseController {
* @param {Response} res -
* @param {NextFunction} next -
*/
private async deliverSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
async deliverSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const { id: saleInvoiceId } = req.params;
try {
await this.saleInvoiceApplication.deliverSaleInvoice(
await this.saleInvoiceService.deliverSaleInvoice(
tenantId,
saleInvoiceId,
user
@@ -385,21 +338,18 @@ export default class SaleInvoicesController extends BaseController {
* @param {Response} res
* @param {Function} next
*/
private async deleteSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
async deleteSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { id: saleInvoiceId } = req.params;
const { tenantId, user } = req;
try {
// Deletes the sale invoice with associated entries and journal transaction.
await this.saleInvoiceApplication.deleteSaleInvoice(
await this.saleInvoiceService.deleteSaleInvoice(
tenantId,
saleInvoiceId,
user
);
return res.status(200).send({
id: saleInvoiceId,
message: 'The sale invoice has been deleted successfully.',
@@ -414,35 +364,39 @@ export default class SaleInvoicesController extends BaseController {
* @param {Request} req - Request object.
* @param {Response} res - Response object.
*/
private async getSaleInvoice(req: Request, res: Response) {
async getSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { id: saleInvoiceId } = req.params;
const { tenantId, user } = req;
const accept = this.accepts(req);
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(
try {
const saleInvoice = await this.saleInvoiceService.getSaleInvoice(
tenantId,
saleInvoiceId,
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.saleInvoicePdf.saleInvoicePdf(
tenantId,
saleInvoice
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
}
}
/**
@@ -465,10 +419,14 @@ export default class SaleInvoicesController extends BaseController {
...this.matchedQueryData(req),
};
try {
const salesInvoicesWithPagination =
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
const { salesInvoices, filterMeta, pagination } =
await this.saleInvoiceService.salesInvoicesList(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) {
next(error);
}
@@ -490,12 +448,13 @@ export default class SaleInvoicesController extends BaseController {
const { customerId } = this.matchedQueryData(req);
try {
const salesInvoices =
await this.saleInvoiceApplication.getReceivableSaleInvoices(
tenantId,
customerId
);
return res.status(200).send({ salesInvoices });
const salesInvoices = await this.saleInvoiceService.getPayableInvoices(
tenantId,
customerId
);
return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
});
} catch (error) {
next(error);
}
@@ -518,14 +477,15 @@ export default class SaleInvoicesController extends BaseController {
const writeoffDTO = this.matchedBodyData(req);
try {
const saleInvoice = await this.saleInvoiceApplication.writeOff(
const saleInvoice = await this.saleInvoiceWriteoff.writeOff(
tenantId,
invoiceId,
writeoffDTO
);
return res.status(200).send({
id: saleInvoice.id,
message: 'The given sale invoice has been written-off successfully.',
message: 'The given sale invoice has been writte-off successfully.',
});
} catch (error) {
next(error);
@@ -547,7 +507,7 @@ export default class SaleInvoicesController extends BaseController {
const { id: invoiceId } = req.params;
try {
const saleInvoice = await this.saleInvoiceApplication.cancelWrittenoff(
const saleInvoice = await this.saleInvoiceWriteoff.cancelWrittenoff(
tenantId,
invoiceId
);
@@ -578,12 +538,11 @@ export default class SaleInvoicesController extends BaseController {
const invoiceNotifySmsDTO = this.matchedBodyData(req);
try {
const saleInvoice =
await this.saleInvoiceApplication.notifySaleInvoiceBySms(
tenantId,
invoiceId,
invoiceNotifySmsDTO.notificationKey
);
const saleInvoice = await this.saleInvoiceSmsNotify.notifyBySms(
tenantId,
invoiceId,
invoiceNotifySmsDTO.notificationKey
);
return res.status(200).send({
id: saleInvoice.id,
message:
@@ -610,12 +569,11 @@ export default class SaleInvoicesController extends BaseController {
const smsDetailsDTO = this.matchedQueryData(req);
try {
const invoiceSmsDetails =
await this.saleInvoiceApplication.getSaleInvoiceSmsDetails(
tenantId,
invoiceId,
smsDetailsDTO
);
const invoiceSmsDetails = await this.saleInvoiceSmsNotify.smsDetails(
tenantId,
invoiceId,
smsDetailsDTO
);
return res.status(200).send({
data: invoiceSmsDetails,
});
@@ -641,7 +599,7 @@ export default class SaleInvoicesController extends BaseController {
try {
const invoicePayments =
await this.saleInvoiceApplication.getInvoicePayments(
await this.invoicePaymentsSerivce.getInvoicePayments(
tenantId,
invoiceId
);
@@ -654,119 +612,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.
* @param {Error} error
@@ -903,16 +748,6 @@ export default class SaleInvoicesController 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: 5000 }],
});
}
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: 5100 }],
});
}
}
next(error);
}

View File

@@ -1,32 +1,35 @@
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 asyncMiddleware from '@/api/middleware/asyncMiddleware';
import SaleReceiptService from '@/services/Sales/SalesReceipts';
import SaleReceiptsPdfService from '@/services/Sales/Receipts/SaleReceiptsPdfService';
import BaseController from '../BaseController';
import {
ISaleReceiptDTO,
SaleReceiptMailOpts,
SaleReceiptMailOptsDTO,
} from '@/interfaces/SaleReceipt';
import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import SaleReceiptNotifyBySms from '@/services/Sales/SaleReceiptNotifyBySms';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class SalesReceiptsController extends BaseController {
@Inject()
private saleReceiptsApplication: SaleReceiptApplication;
saleReceiptService: SaleReceiptService;
@Inject()
private dynamicListService: DynamicListingService;
saleReceiptsPdf: SaleReceiptsPdfService;
@Inject()
dynamicListService: DynamicListingService;
@Inject()
saleReceiptSmsNotify: SaleReceiptNotifyBySms;
/**
* Router constructor.
*/
public router() {
router() {
const router = Router();
router.post(
@@ -51,27 +54,6 @@ export default class SalesReceiptsController extends BaseController {
this.saleReceiptSmsDetails,
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(
'/:id',
CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt),
@@ -123,7 +105,7 @@ export default class SalesReceiptsController extends BaseController {
* Sales receipt validation schema.
* @return {Array}
*/
private get salesReceiptsValidationSchema() {
get salesReceiptsValidationSchema() {
return [
check('customer_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -143,7 +125,7 @@ export default class SalesReceiptsController extends BaseController {
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.rate').exists().isNumeric().toInt(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -164,14 +146,14 @@ export default class SalesReceiptsController extends BaseController {
/**
* Specific sale receipt validation schema.
*/
private get specificReceiptValidationSchema() {
get specificReceiptValidationSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* List sales receipts validation schema.
*/
private get listSalesReceiptsValidationSchema() {
get listSalesReceiptsValidationSchema() {
return [
query('view_slug').optional().isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
@@ -188,21 +170,16 @@ export default class SalesReceiptsController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async newSaleReceipt(
req: Request,
res: Response,
next: NextFunction
) {
async newSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const saleReceiptDTO: ISaleReceiptDTO = this.matchedBodyData(req);
try {
// Store the given sale receipt details with associated entries.
const storedSaleReceipt =
await this.saleReceiptsApplication.createSaleReceipt(
tenantId,
saleReceiptDTO
);
const storedSaleReceipt = await this.saleReceiptService.createSaleReceipt(
tenantId,
saleReceiptDTO
);
return res.status(200).send({
id: storedSaleReceipt.id,
message: 'Sale receipt has been created successfully.',
@@ -217,20 +194,14 @@ export default class SalesReceiptsController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async deleteSaleReceipt(
req: Request,
res: Response,
next: NextFunction
) {
async deleteSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params;
try {
// Deletes the sale receipt.
await this.saleReceiptsApplication.deleteSaleReceipt(
tenantId,
saleReceiptId
);
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
return res.status(200).send({
id: saleReceiptId,
message: 'Sale receipt has been deleted successfully.',
@@ -246,18 +217,14 @@ export default class SalesReceiptsController extends BaseController {
* @param {Request} req -
* @param {Response} res -
*/
private async editSaleReceipt(
req: Request,
res: Response,
next: NextFunction
) {
async editSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params;
const saleReceipt = this.matchedBodyData(req);
try {
// Update the given sale receipt details.
await this.saleReceiptsApplication.editSaleReceipt(
await this.saleReceiptService.editSaleReceipt(
tenantId,
saleReceiptId,
saleReceipt
@@ -277,20 +244,13 @@ export default class SalesReceiptsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private async closeSaleReceipt(
req: Request,
res: Response,
next: NextFunction
) {
async closeSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params;
try {
// Update the given sale receipt details.
await this.saleReceiptsApplication.closeSaleReceipt(
tenantId,
saleReceiptId
);
await this.saleReceiptService.closeSaleReceipt(tenantId, saleReceiptId);
return res.status(200).send({
id: saleReceiptId,
message: 'Sale receipt has been closed successfully.',
@@ -305,11 +265,7 @@ export default class SalesReceiptsController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async getSalesReceipts(
req: Request,
res: Response,
next: NextFunction
) {
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = {
sortOrder: 'desc',
@@ -318,11 +274,17 @@ export default class SalesReceiptsController extends BaseController {
pageSize: 12,
...this.matchedQueryData(req),
};
try {
const salesReceiptsWithPagination =
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
return res.status(200).send(salesReceiptsWithPagination);
try {
const { data, pagination, filterMeta } =
await this.saleReceiptService.salesReceiptsList(tenantId, filter);
const response = this.transfromToResponse({
data,
pagination,
filterMeta,
});
return res.status(200).send(response);
} catch (error) {
next(error);
}
@@ -334,34 +296,36 @@ export default class SalesReceiptsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleReceipt(req: Request, res: Response) {
async getSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { id: saleReceiptId } = req.params;
const { tenantId } = req;
const accept = this.accepts(req);
try {
const saleReceipt = await this.saleReceiptService.getSaleReceipt(
tenantId,
saleReceiptId
);
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.format({
'application/json': () => {
return res
.status(200)
.send(this.transfromToResponse({ saleReceipt }));
},
'application/pdf': async () => {
const pdfContent = await this.saleReceiptsPdf.saleReceiptPdf(
tenantId,
saleReceipt
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
res.send(pdfContent);
// Retrieves receipt in json format.
} else {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId,
saleReceiptId
);
return res.status(200).send({ saleReceipt });
} catch (error) {
next(error);
}
}
@@ -380,11 +344,10 @@ export default class SalesReceiptsController extends BaseController {
const { id: receiptId } = req.params;
try {
const saleReceipt =
await this.saleReceiptsApplication.saleReceiptNotifyBySms(
tenantId,
receiptId
);
const saleReceipt = await this.saleReceiptSmsNotify.notifyBySms(
tenantId,
receiptId
);
return res.status(200).send({
id: saleReceipt.id,
message:
@@ -410,72 +373,13 @@ export default class SalesReceiptsController extends BaseController {
const { id: receiptId } = req.params;
try {
const smsDetails =
await this.saleReceiptsApplication.getSaleReceiptSmsDetails(
tenantId,
receiptId
);
return res.status(200).send({
data: smsDetails,
});
} catch (error) {
next(error);
}
};
/**
* 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(
const smsDetails = await this.saleReceiptSmsNotify.smsDetails(
tenantId,
receiptId
);
return res.status(200).send({ data });
return res.status(200).send({
data: smsDetails,
});
} catch (error) {
next(error);
}

View File

@@ -1,10 +1,10 @@
import { Router } from 'express';
import { Container, Service } from 'typedi';
import SalesInvoices from './SalesInvoices'
import SalesEstimates from './SalesEstimates';
import SalesReceipts from './SalesReceipts';
import CreditNotes from './CreditNotes';
import SalesInvoices from './SalesInvoices'
import PaymentReceives from './PaymentReceives';
import CreditNotes from './CreditNotes';
@Service()
export default class SalesController {
/**

View File

@@ -1,278 +0,0 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express';
import { body, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import { TaxRatesApplication } from '@/services/TaxRates/TaxRatesApplication';
import CheckAbilities from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions';
import { ERRORS } from '@/services/TaxRates/constants';
import { AbilitySubject, TaxRateAction } from '@/interfaces';
@Service()
export class TaxRatesController extends BaseController {
@Inject()
private taxRatesApplication: TaxRatesApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/',
CheckAbilities(TaxRateAction.CREATE, AbilitySubject.TaxRate),
this.taxRateValidationSchema,
this.validationResult,
asyncMiddleware(this.createTaxRate.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id',
CheckAbilities(TaxRateAction.EDIT, AbilitySubject.TaxRate),
[param('id').exists().toInt(), ...this.taxRateValidationSchema],
this.validationResult,
asyncMiddleware(this.editTaxRate.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/active',
CheckAbilities(TaxRateAction.EDIT, AbilitySubject.TaxRate),
[param('id').exists().toInt()],
this.validationResult,
asyncMiddleware(this.activateTaxRate.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/inactive',
CheckAbilities(TaxRateAction.EDIT, AbilitySubject.TaxRate),
[param('id').exists().toInt()],
this.validationResult,
asyncMiddleware(this.inactivateTaxRate.bind(this)),
this.handleServiceErrors
);
router.delete(
'/:id',
CheckAbilities(TaxRateAction.DELETE, AbilitySubject.TaxRate),
[param('id').exists().toInt()],
this.validationResult,
asyncMiddleware(this.deleteTaxRate.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckAbilities(TaxRateAction.VIEW, AbilitySubject.TaxRate),
[param('id').exists().toInt()],
this.validationResult,
asyncMiddleware(this.getTaxRate.bind(this)),
this.handleServiceErrors
);
router.get(
'/',
CheckAbilities(TaxRateAction.VIEW, AbilitySubject.TaxRate),
this.validationResult,
asyncMiddleware(this.getTaxRates.bind(this)),
this.handleServiceErrors
);
return router;
}
/**
* Tax rate validation schema.
*/
private get taxRateValidationSchema() {
return [
body('name').exists(),
body('code').exists().isString(),
body('rate').exists().isNumeric().toFloat(),
body('description').optional().trim().isString(),
body('is_non_recoverable').optional().isBoolean().default(false),
body('is_compound').optional().isBoolean().default(false),
body('active').optional().isBoolean().default(false),
];
}
/**
* Creates a new tax rate.
* @param {Request} req -
* @param {Response} res -
*/
public async createTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const createTaxRateDTO = this.matchedBodyData(req);
try {
const taxRate = await this.taxRatesApplication.createTaxRate(
tenantId,
createTaxRateDTO
);
return res.status(200).send({
data: taxRate,
});
} catch (error) {
next(error);
}
}
/**
* Edits the given tax rate.
* @param {Request} req -
* @param {Response} res -
*/
public async editTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const editTaxRateDTO = this.matchedBodyData(req);
const { id: taxRateId } = req.params;
try {
const taxRate = await this.taxRatesApplication.editTaxRate(
tenantId,
taxRateId,
editTaxRateDTO
);
return res.status(200).send({
data: taxRate,
});
} catch (error) {
next(error);
}
}
/**
* Deletes the given tax rate.
* @param {Request} req -
* @param {Response} res -
*/
public async deleteTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const { id: taxRateId } = req.params;
try {
await this.taxRatesApplication.deleteTaxRate(tenantId, taxRateId);
return res.status(200).send({
code: 200,
message: 'The tax rate has been deleted successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the given tax rate.
* @param {Request} req -
* @param {Response} res -
*/
public async getTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const { id: taxRateId } = req.params;
try {
const taxRate = await this.taxRatesApplication.getTaxRate(
tenantId,
taxRateId
);
return res.status(200).send({ data: taxRate });
} catch (error) {
next(error);
}
}
/**
* Retrieves the tax rates list.
* @param {Request} req -
* @param {Response} res -
*/
public async getTaxRates(req: Request, res: Response, next) {
const { tenantId } = req;
try {
const taxRates = await this.taxRatesApplication.getTaxRates(tenantId);
return res.status(200).send({ data: taxRates });
} catch (error) {
next(error);
}
}
/**
* Inactivates the given tax rate.
* @param req
* @param res
* @param next
* @returns
*/
public async inactivateTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const { id: taxRateId } = req.params;
try {
await this.taxRatesApplication.inactivateTaxRate(tenantId, taxRateId);
return res.status(200).send({
id: taxRateId,
message: 'The given tax rate has been inactivated successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Inactivates the given tax rate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns
*/
public async activateTaxRate(req: Request, res: Response, next) {
const { tenantId } = req;
const { id: taxRateId } = req.params;
try {
await this.taxRatesApplication.activateTaxRate(tenantId, taxRateId);
return res.status(200).send({
id: taxRateId,
message: 'The given tax rate has been activated successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceErrors(error: Error, req: Request, res: Response, next) {
if (error instanceof ServiceError) {
if (error.errorType === ERRORS.TAX_CODE_NOT_UNIQUE) {
return res.boom.badRequest(null, {
errors: [{ type: ERRORS.TAX_CODE_NOT_UNIQUE, code: 100 }],
});
}
if (error.errorType === ERRORS.TAX_RATE_NOT_FOUND) {
return res.boom.badRequest(null, {
errors: [{ type: ERRORS.TAX_RATE_NOT_FOUND, code: 200 }],
});
}
if (error.errorType === ERRORS.TAX_RATE_ALREADY_INACTIVE) {
return res.boom.badRequest(null, {
errors: [{ type: ERRORS.TAX_RATE_ALREADY_INACTIVE, code: 300 }],
});
}
if (error.errorType === ERRORS.TAX_RATE_ALREADY_ACTIVE) {
return res.boom.badRequest(null, {
errors: [{ type: ERRORS.TAX_RATE_ALREADY_ACTIVE, code: 400 }],
});
}
}
next(error);
}
}

View File

@@ -1,47 +0,0 @@
import { Router } from 'express';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
import { Request, Response } from 'express';
import { Inject, Service } from 'typedi';
import BaseController from '../BaseController';
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
@Service()
export class Webhooks extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use(PlaidWebhookTenantBootMiddleware);
router.post('/plaid', this.plaidWebhooks.bind(this));
return router;
}
/**
* Listens to Plaid webhooks.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async plaidWebhooks(req: Request, res: Response) {
const { tenantId } = req;
const {
webhook_type: webhookType,
webhook_code: webhookCode,
item_id: plaidItemId,
} = req.body;
await this.plaidApp.webhooks(
tenantId,
plaidItemId,
webhookType,
webhookCode
);
return res.status(200).send({ code: 200, message: 'ok' });
}
}

View File

@@ -55,10 +55,6 @@ import { InventoryItemsCostController } from './controllers/Inventory/Inventorty
import { ProjectsController } from './controllers/Projects/Projects';
import { ProjectTasksController } from './controllers/Projects/Tasks';
import { ProjectTimesController } from './controllers/Projects/Times';
import { TaxRatesController } from './controllers/TaxRates/TaxRates';
import { ImportController } from './controllers/Import/ImportController';
import { BankingController } from './controllers/Banking/BankingController';
import { Webhooks } from './controllers/Webhooks/Webhooks';
export default () => {
const app = Router();
@@ -74,7 +70,6 @@ export default () => {
app.use('/ping', Container.get(Ping).router());
app.use('/jobs', Container.get(Jobs).router());
app.use('/account', Container.get(Account).router());
app.use('/webhooks', Container.get(Webhooks).router());
// - Dashboard routes.
// ---------------------------
@@ -122,7 +117,6 @@ export default () => {
Container.get(InventoryItemsCostController).router()
);
dashboard.use('/cashflow', Container.get(CashflowController).router());
dashboard.use('/banking', Container.get(BankingController).router());
dashboard.use('/roles', Container.get(RolesController).router());
dashboard.use(
'/transactions-locking',
@@ -135,10 +129,6 @@ export default () => {
);
dashboard.use('/warehouses', Container.get(WarehousesController).router());
dashboard.use('/projects', Container.get(ProjectsController).router());
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
dashboard.use('/import', Container.get(ImportController).router());
dashboard.use('/', Container.get(ProjectTasksController).router());
dashboard.use('/', Container.get(ProjectTimesController).router());

View File

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

View File

@@ -1,12 +1,9 @@
import dotenv from 'dotenv';
import path from 'path';
import { toInteger } from 'lodash';
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
dotenv.config();
const API_RATE_LIMIT = process.env.API_RATE_LIMIT?.split(',') || [];
module.exports = {
/**
* Your favorite port
@@ -58,7 +55,6 @@ module.exports = {
secure: !!parseInt(process.env.MAIL_SECURE, 10),
username: process.env.MAIL_USERNAME,
password: process.env.MAIL_PASSWORD,
from: process.env.MAIL_FROM_ADDRESS,
},
/**
@@ -101,7 +97,7 @@ module.exports = {
jwtSecret: process.env.JWT_SECRET,
/**
*
*
*/
resetPasswordSeconds: 600,
@@ -134,9 +130,9 @@ module.exports = {
blockDuration: 60 * 15,
},
requests: {
points: API_RATE_LIMIT[0] ? toInteger(API_RATE_LIMIT[0]) : 120,
duration: API_RATE_LIMIT[1] ? toInteger(API_RATE_LIMIT[1]) : 60,
blockDuration: API_RATE_LIMIT[2] ? toInteger(API_RATE_LIMIT[2]) : 60 * 10,
points: 60,
duration: 60,
blockDuration: 60 * 10,
},
},
@@ -169,27 +165,4 @@ module.exports = {
* to application detarmines to upgrade.
*/
databaseBatch: 4,
/**
* Exchange rate.
*/
exchangeRate: {
service: 'open-exchange-rate',
openExchangeRate: {
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
},
},
/**
* Plaid.
*/
plaid: {
env: process.env.PLAID_ENV || 'sandbox',
clientId: process.env.PLAID_CLIENT_ID,
secretDevelopment: process.env.PLAID_SECRET_DEVELOPMENT,
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
linkWebhook: process.env.PLAID_LINK_WEBHOOK
},
};

View File

@@ -1,28 +0,0 @@
export const TransactionTypes = {
SaleInvoice: 'Sale invoice',
SaleReceipt: 'Sale receipt',
PaymentReceive: 'Payment receive',
Bill: 'Bill',
BillPayment: 'Payment made',
VendorOpeningBalance: 'Vendor opening balance',
CustomerOpeningBalance: 'Customer opening balance',
InventoryAdjustment: 'Inventory adjustment',
ManualJournal: 'Manual journal',
Journal: 'Manual journal',
Expense: 'Expense',
OwnerContribution: 'Owner contribution',
TransferToAccount: 'Transfer to account',
TransferFromAccount: 'Transfer from account',
OtherIncome: 'Other income',
OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing',
InvoiceWriteOff: 'Invoice write-off',
CreditNote: 'transaction_type.credit_note',
VendorCredit: 'transaction_type.vendor_credit',
RefundCreditNote: 'transaction_type.refund_credit_note',
RefundVendorCredit: 'transaction_type.refund_vendor_credit',
LandedCost: 'transaction_type.landed_cost',
};

View File

@@ -59,12 +59,6 @@ export default {
auto_increment: {
type: 'boolean',
},
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
},
sales_receipts: {
next_number: {
@@ -79,12 +73,6 @@ export default {
preferred_deposit_account: {
type: 'number',
},
receipt_message: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
},
sales_invoices: {
next_number: {
@@ -96,12 +84,6 @@ export default {
auto_increment: {
type: 'boolean',
},
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
},
payment_receives: {
next_number: {
@@ -136,11 +118,6 @@ export default {
type: 'number',
},
},
inventory: {
cost_compute_running: {
type: 'boolean',
},
},
accounts: {
account_code_required: {
type: 'boolean',
@@ -170,12 +147,6 @@ export default {
auto_increment: {
type: 'boolean',
},
customer_notes: {
type: 'string',
},
terms_conditions: {
type: 'string',
},
},
vendor_credit: {
next_number: {

View File

@@ -1,52 +0,0 @@
exports.up = (knex) => {
return knex.schema
.createTable('tax_rates', (table) => {
table.increments();
table.string('name');
table.string('code');
table.decimal('rate');
table.string('description');
table.boolean('is_non_recoverable').defaultTo(false);
table.boolean('is_compound').defaultTo(false);
table.boolean('active').defaultTo(false);
table.date('deleted_at');
table.timestamps();
})
.table('items_entries', (table) => {
table.boolean('is_inclusive_tax').defaultTo(false);
table
.integer('tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
table.decimal('tax_rate').unsigned();
})
.table('sales_invoices', (table) => {
table.boolean('is_inclusive_tax').defaultTo(false);
table.decimal('tax_amount_withheld');
})
.createTable('tax_rate_transactions', (table) => {
table.increments('id');
table
.integer('tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
table.string('reference_type');
table.integer('reference_id');
table.decimal('rate').unsigned();
table.integer('tax_account_id').unsigned();
})
.table('accounts_transactions', (table) => {
table
.integer('tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
table.decimal('tax_rate').unsigned();
});
};
exports.down = (knex) => {
return knex.schema.dropTableIfExists('tax_rates');
};

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,14 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable('plaid_items', (table) => {
table.increments('id');
table.integer('tenant_id').unsigned();
table.string('plaid_item_id');
table.string('plaid_institution_id');
table.string('plaid_access_token');
table.string('last_cursor');
table.string('status');
table.timestamps();
});
};
exports.down = function (knex) {};

View File

@@ -1,9 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('accounts', (table) => {
table.string('plaid_account_id');
table.string('account_mask').nullable();
table.decimal('bank_balance', 15, 5);
});
};
exports.down = function (knex) {};

View File

@@ -1,7 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('cashflow_transactions', (table) => {
table.string('plaid_transaction_id');
});
};
exports.down = function (knex) {};

View File

@@ -1,28 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable(
'uncategorized_cashflow_transactions',
(table) => {
table.increments('id');
table.date('date').index();
table.decimal('amount');
table.string('currency_code');
table.string('reference_no').index();
table.string('payee');
table
.integer('account_id')
.unsigned()
.references('id')
.inTable('accounts');
table.string('description');
table.string('categorize_ref_type');
table.integer('categorize_ref_id').unsigned();
table.boolean('categorized').defaultTo(false);
table.string('plaid_transaction_id');
table.timestamps();
}
);
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('uncategorized_cashflow_transactions');
};

View File

@@ -1,10 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('accounts', (table) => {
table.integer('uncategorized_transactions').defaultTo(0);
table.boolean('is_system_account').defaultTo(true);
table.boolean('is_feeds_active').defaultTo(false);
table.datetime('last_feeds_updated_at').nullable();
});
};
exports.down = function (knex) {};

View File

@@ -1,15 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('cashflow_transactions', (table) => {
table
.integer('uncategorized_transaction_id')
.unsigned()
.references('id')
.inTable('uncategorized_cashflow_transactions');
});
};
exports.down = function (knex) {
return knex.schema.table('cashflow_transactions', (table) => {
table.dropColumn('uncategorized_transaction_id');
});
};

View File

@@ -1,14 +0,0 @@
import { TenantSeeder } from '@/lib/Seeder/TenantSeeder';
import { InitialTaxRates } from '../data/TaxRates';
export default class SeedTaxRates extends TenantSeeder {
/**
* Seeds initial tax rates to the organization.
*/
up(knex) {
return knex('tax_rates').then(async () => {
// Inserts seed entries.
return knex('tax_rates').insert(InitialTaxRates);
});
}
}

View File

@@ -1,16 +0,0 @@
import { TenantSeeder } from '@/lib/Seeder/TenantSeeder';
import { InitialTaxRates } from '../data/TaxRates';
export default class UpdateTaxPayableAccount extends TenantSeeder {
/**
* Seeds initial tax rates to the organization.
*/
up(knex) {
return knex('accounts').then(async () => {
// Inserts seed entries.
return knex('accounts').where('slug', 'tax-payable').update({
account_type: 'tax-payable',
});
});
}
}

View File

@@ -1,30 +0,0 @@
export const InitialTaxRates = [
{
name: 'Tax Exempt',
code: 'TAX-EXEMPT',
description: 'Exempts goods or services from taxes.',
rate: 0,
active: 1,
},
{
name: 'Tax on Purchases',
code: 'TAX-PURCHASES',
description: 'Fee added to the cost when you buy items.',
rate: 0,
active: 1,
},
{
name: 'Tax on Sales',
code: 'TAX-SALES',
description: 'Fee added to the cost when you sell items.',
rate: 0,
active: 1,
},
{
name: 'Sales Tax on Imports',
code: 'TAX-IMPORTS',
description: 'Fee added to the cost when you sale to another country.',
rate: 0,
active: 1,
},
];

View File

@@ -1,17 +1,7 @@
export const TaxPayableAccount = {
name: 'Tax Payable',
slug: 'tax-payable',
account_type: 'tax-payable',
code: '20006',
description: '',
active: 1,
index: 1,
predefined: 1,
};
export default [
{
name: 'Bank Account',
name:'Bank Account',
slug: 'bank-account',
account_type: 'bank',
code: '10001',
@@ -21,7 +11,7 @@ export default [
predefined: 1,
},
{
name: 'Saving Bank Account',
name:'Saving Bank Account',
slug: 'saving-bank-account',
account_type: 'bank',
code: '10002',
@@ -31,7 +21,7 @@ export default [
predefined: 0,
},
{
name: 'Undeposited Funds',
name:'Undeposited Funds',
slug: 'undeposited-funds',
account_type: 'cash',
code: '10003',
@@ -41,7 +31,7 @@ export default [
predefined: 1,
},
{
name: 'Petty Cash',
name:'Petty Cash',
slug: 'petty-cash',
account_type: 'cash',
code: '10004',
@@ -51,7 +41,7 @@ export default [
predefined: 1,
},
{
name: 'Computer Equipment',
name:'Computer Equipment',
slug: 'computer-equipment',
code: '10005',
account_type: 'fixed-asset',
@@ -62,7 +52,7 @@ export default [
description: '',
},
{
name: 'Office Equipment',
name:'Office Equipment',
slug: 'office-equipment',
code: '10006',
account_type: 'fixed-asset',
@@ -73,7 +63,7 @@ export default [
description: '',
},
{
name: 'Accounts Receivable (A/R)',
name:'Accounts Receivable (A/R)',
slug: 'accounts-receivable',
account_type: 'accounts-receivable',
code: '10007',
@@ -83,7 +73,7 @@ export default [
predefined: 1,
},
{
name: 'Inventory Asset',
name:'Inventory Asset',
slug: 'inventory-asset',
code: '10008',
account_type: 'inventory',
@@ -91,13 +81,12 @@ export default [
parent_account_id: null,
index: 1,
active: 1,
description:
'An account that holds valuation of products or goods that available for sale.',
description:'An account that holds valuation of products or goods that availiable for sale.',
},
// Libilities
{
name: 'Accounts Payable (A/P)',
name:'Accounts Payable (A/P)',
slug: 'accounts-payable',
account_type: 'accounts-payable',
parent_account_id: null,
@@ -108,39 +97,38 @@ export default [
predefined: 1,
},
{
name: 'Owner A Drawings',
name:'Owner A Drawings',
slug: 'owner-drawings',
account_type: 'other-current-liability',
parent_account_id: null,
code: '20002',
description: 'Withdrawals by the owners.',
description:'Withdrawals by the owners.',
active: 1,
index: 1,
predefined: 0,
},
{
name: 'Loan',
name:'Loan',
slug: 'owner-drawings',
account_type: 'other-current-liability',
code: '20003',
description: 'Money that has been borrowed from a creditor.',
description:'Money that has been borrowed from a creditor.',
active: 1,
index: 1,
predefined: 0,
},
{
name: 'Opening Balance Liabilities',
name:'Opening Balance Liabilities',
slug: 'opening-balance-liabilities',
account_type: 'other-current-liability',
code: '20004',
description:
'This account will hold the difference in the debits and credits entered during the opening balance..',
description:'This account will hold the difference in the debits and credits entered during the opening balance..',
active: 1,
index: 1,
predefined: 0,
},
{
name: 'Revenue Received in Advance',
name:'Revenue Received in Advance',
slug: 'revenue-received-in-advance',
account_type: 'other-current-liability',
parent_account_id: null,
@@ -150,27 +138,34 @@ export default [
index: 1,
predefined: 0,
},
TaxPayableAccount,
{
name:'Sales Tax Payable',
slug: 'owner-drawings',
account_type: 'other-current-liability',
code: '20006',
description: '',
active: 1,
index: 1,
predefined: 1,
},
// Equity
{
name: 'Retained Earnings',
name:'Retained Earnings',
slug: 'retained-earnings',
account_type: 'equity',
code: '30001',
description:
'Retained earnings tracks net income from previous fiscal years.',
description:'Retained earnings tracks net income from previous fiscal years.',
active: 1,
index: 1,
predefined: 1,
},
{
name: 'Opening Balance Equity',
name:'Opening Balance Equity',
slug: 'opening-balance-equity',
account_type: 'equity',
code: '30002',
description:
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
description:'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
active: 1,
index: 1,
predefined: 1,
@@ -186,12 +181,11 @@ export default [
predefined: 1,
},
{
name: `Drawings`,
name:`Drawings`,
slug: 'drawings',
account_type: 'equity',
code: '30003',
description:
'Goods purchased with the intention of selling these to customers',
description:'Goods purchased with the intention of selling these to customers',
active: 1,
index: 1,
predefined: 1,
@@ -199,7 +193,7 @@ export default [
// Expenses
{
name: 'Other Expenses',
name:'Other Expenses',
slug: 'other-expenses',
account_type: 'other-expense',
parent_account_id: null,
@@ -210,18 +204,18 @@ export default [
predefined: 1,
},
{
name: 'Cost of Goods Sold',
name:'Cost of Goods Sold',
slug: 'cost-of-goods-sold',
account_type: 'cost-of-goods-sold',
parent_account_id: null,
code: '40002',
description: 'Tracks the direct cost of the goods sold.',
description:'Tracks the direct cost of the goods sold.',
active: 1,
index: 1,
predefined: 1,
},
{
name: 'Office expenses',
name:'Office expenses',
slug: 'office-expenses',
account_type: 'expense',
parent_account_id: null,
@@ -232,7 +226,7 @@ export default [
predefined: 0,
},
{
name: 'Rent',
name:'Rent',
slug: 'rent',
account_type: 'expense',
parent_account_id: null,
@@ -243,30 +237,29 @@ export default [
predefined: 0,
},
{
name: 'Exchange Gain or Loss',
name:'Exchange Gain or Loss',
slug: 'exchange-grain-loss',
account_type: 'other-expense',
parent_account_id: null,
code: '40005',
description: 'Tracks the gain and losses of the exchange differences.',
description:'Tracks the gain and losses of the exchange differences.',
active: 1,
index: 1,
predefined: 1,
},
{
name: 'Bank Fees and Charges',
name:'Bank Fees and Charges',
slug: 'bank-fees-and-charges',
account_type: 'expense',
parent_account_id: null,
code: '40006',
description:
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
description: 'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
active: 1,
index: 1,
predefined: 0,
},
{
name: 'Depreciation Expense',
name:'Depreciation Expense',
slug: 'depreciation-expense',
account_type: 'expense',
parent_account_id: null,
@@ -279,7 +272,7 @@ export default [
// Income
{
name: 'Sales of Product Income',
name:'Sales of Product Income',
slug: 'sales-of-product-income',
account_type: 'income',
predefined: 1,
@@ -290,7 +283,7 @@ export default [
description: '',
},
{
name: 'Sales of Service Income',
name:'Sales of Service Income',
slug: 'sales-of-service-income',
account_type: 'income',
predefined: 0,
@@ -301,7 +294,7 @@ export default [
description: '',
},
{
name: 'Uncategorized Income',
name:'Uncategorized Income',
slug: 'uncategorized-income',
account_type: 'income',
parent_account_id: null,
@@ -312,15 +305,14 @@ export default [
predefined: 1,
},
{
name: 'Other Income',
name:'Other Income',
slug: 'other-income',
account_type: 'other-income',
parent_account_id: null,
code: '50004',
description:
'The income activities are not associated to the core business.',
description:'The income activities are not associated to the core business.',
active: 1,
index: 1,
predefined: 0,
},
];
}
];

View File

@@ -1,45 +1,51 @@
import {
IAgingPeriod,
IAgingSummaryQuery,
IAgingSummaryTotal,
IAgingSummaryContact,
IAgingSummaryData,
IAgingPeriodTotal,
IAgingAmount
} from './AgingReport';
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table';
import {
INumberFormatQuery
} from './FinancialStatements';
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
export interface IAPAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
vendorsIds: number[];
noneZero: boolean;
branchesIds?: number[]
}
export interface IAPAgingSummaryVendor extends IAgingSummaryContact {
vendorName: string;
}
export interface IAPAgingSummaryVendor {
vendorName: string,
current: IAgingAmount,
aging: IAgingPeriodTotal[],
total: IAgingAmount,
};
export interface IAPAgingSummaryTotal extends IAgingSummaryTotal {}
export interface IAPAgingSummaryTotal {
current: IAgingAmount,
aging: IAgingPeriodTotal[],
total: IAgingAmount,
};
export interface IAPAgingSummaryData extends IAgingSummaryData {
vendors: IAPAgingSummaryVendor[];
}
export interface IAPAgingSummaryData {
vendors: IAPAgingSummaryVendor[],
total: IAPAgingSummaryTotal,
};
export type IAPAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
export interface IARAgingSummaryMeta {
baseCurrency: string,
organizationName: string,
}
export interface IAPAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
}
export interface IAPAgingSummaryTable extends IFinancialTable {
query: IAPAgingSummaryQuery;
meta: IAPAgingSummaryMeta;
}
export interface IAPAgingSummarySheet {
data: IAPAgingSummaryData;
meta: IAPAgingSummaryMeta;
query: IAPAgingSummaryQuery;
columns: any;
}
export interface IAPAgingSummaryMeta {
baseCurrency: string,
organizationName: string,
}

View File

@@ -1,42 +1,37 @@
import {
IAgingPeriod,
IAgingSummaryQuery,
IAgingSummaryTotal,
IAgingSummaryContact,
IAgingSummaryData,
} from './AgingReport';
import { IFinancialTable } from './Table';
import { IAgingPeriod, IAgingPeriodTotal, IAgingAmount } from './AgingReport';
import { INumberFormatQuery } from './FinancialStatements';
export interface IARAgingSummaryQuery extends IAgingSummaryQuery {
export interface IARAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
customersIds: number[];
branchesIds: number[];
noneZero: boolean;
}
export interface IARAgingSummaryCustomer extends IAgingSummaryContact {
export interface IARAgingSummaryCustomer {
customerName: string;
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IARAgingSummaryTotal extends IAgingSummaryTotal {}
export interface IARAgingSummaryTotal {
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IARAgingSummaryData extends IAgingSummaryData {
export interface IARAgingSummaryData {
customers: IARAgingSummaryCustomer[];
total: IARAgingSummaryTotal;
}
export type IARAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta {
organizationName: string;
baseCurrency: string;
}
export interface IARAgingSummaryTable extends IFinancialTable {
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
}
export interface IARAgingSummarySheet {
data: IARAgingSummaryData;
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
columns: IARAgingSummaryColumns;
}
organizationName: string,
baseCurrency: string,
}

View File

@@ -6,15 +6,12 @@ export interface IAccountDTO {
code: string;
description: string;
accountType: string;
parentAccountId?: number;
parentAccountId: number;
active: boolean;
bankBalance?: number;
accountMask?: string;
}
export interface IAccountCreateDTO extends IAccountDTO {
currencyCode?: string;
plaidAccountId?: string;
}
export interface IAccountEditDTO extends IAccountDTO {}
@@ -36,7 +33,6 @@ export interface IAccount {
type?: any[];
accountNormal: string;
accountParentType: string;
bankBalance: string;
}
export enum AccountNormal {
@@ -62,7 +58,6 @@ export interface IAccountTransaction {
date: string | Date;
referenceType: string;
referenceTypeFormatted: string;
referenceId: number;
referenceNumber?: string;
@@ -81,9 +76,6 @@ export interface IAccountTransaction {
projectId?: number;
account?: IAccount;
taxRateId?: number;
taxRate?: number;
}
export interface IAccountResponse extends IAccount {}
@@ -157,10 +149,3 @@ export enum AccountAction {
VIEW = 'View',
TransactionsLocking = 'TransactionsLocking',
}
export enum TaxRateAction {
CREATE = 'Create',
EDIT = 'Edit',
DELETE = 'Delete',
VIEW = 'View',
}

View File

@@ -1,11 +1,6 @@
import {
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
export interface IAgingPeriodTotal extends IAgingPeriod {
total: IAgingAmount;
}
};
export interface IAgingAmount {
amount: number;
@@ -25,27 +20,3 @@ export interface IAgingSummaryContact {
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
branchesIds: number[];
noneZero: boolean;
}
export interface IAgingSummaryTotal {
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IAgingSummaryData {
total: IAgingSummaryTotal;
}
export interface IAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}

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