Compare commits

...

91 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
8f431597d9 fix(server): hotbug the invoice and payment receipt printing 2024-01-20 15:51:57 +02:00
Ahmed Bouhuolia
03bc78a068 feat(webapp): remove the un-used functions 2024-01-19 23:33:15 +02:00
Ahmed Bouhuolia
ecf5d60db0 Merge pull request #310 from bigcapitalhq/big-99-purchases-by-items
feat: sales by items export csv & xlsx
2024-01-19 11:41:56 +02:00
Ahmed Bouhuolia
3a5fd2782a fix: sales by items issues 2024-01-19 11:39:00 +02:00
Ahmed Bouhuolia
1d8416ebfe fix: sales by items TS types 2024-01-19 11:25:19 +02:00
Ahmed Bouhuolia
3672abe7a5 feat: inventory valuation csv and xlsx export (#308)
* feat: inventory valuation csv and xlsx export

* feat(server): inventory valuation sheet exporting

* feat(webapp): inventory valuation sheet dyanmic columns

* feat: inventory valuation dynamic columns

* feat: inventory valuation TS types
2024-01-18 20:16:29 +02:00
Ahmed Bouhuolia
2753908b83 feat: sales by items dynamic columns 2024-01-18 19:52:11 +02:00
Asena
8495990ec2 Show customer / vendor balance. (#311)
* feat : Update the Customer select prop to balance
* feat : Update the Vendor select prop to balance
* feat: Update balance to formatted_balance
2024-01-18 14:47:07 +02:00
Ahmed Bouhuolia
471ce1b7af feat(webapp): dynamic columns of sales by items sheet 2024-01-18 14:39:56 +02:00
Ahmed Bouhuolia
4a920176f4 feat(server): sales by items table 2024-01-17 18:49:38 +02:00
Ahmed Bouhuolia
74fd76ce77 feat(server): sales by items export csv & xlsx 2024-01-17 00:24:13 +02:00
Ahmed Bouhuolia
c9f57d9a75 chore: update CHANGELOG.md file 2024-01-15 00:30:08 +02:00
Ahmed Bouhuolia
6c9810ea4c Merge pull request #270 from bigcapitalhq/big-44-auto-re-calculate-the-items-rate-once-changing-the-invoice
feat: Auto re-calculate the items rate once changing the invoice exchange rate.
2024-01-14 16:08:56 +02:00
Ahmed Bouhuolia
1e4b29f83c fix: content tweaks in rates re-calc popover 2024-01-14 15:59:25 +02:00
Ahmed Bouhuolia
2b03ac7f16 feat: implement auto entries rates re-calculation after change the exchange rate 2024-01-14 14:44:48 +02:00
Ahmed Bouhuolia
2cb8c2932f Merge branch 'develop' into big-44-auto-re-calculate-the-items-rate-once-changing-the-invoice 2024-01-11 20:27:42 +02:00
Ahmed Bouhuolia
2814d23f75 Update README.md 2024-01-10 20:24:11 +02:00
Ahmed Bouhuolia
35fa2389e1 feat: add headless accounting on README.md file 2024-01-10 20:21:46 +02:00
Ahmed Bouhuolia
c18b450129 Merge pull request #303 from bigcapitalhq/journal-sheet-export
Export general ledger & Journal to CSV and XLSX
2024-01-10 17:45:04 +02:00
Ahmed Bouhuolia
8db6e9f049 fix: remove blank files 2024-01-10 17:43:26 +02:00
Ahmed Bouhuolia
b63a2006d3 Merge pull request #305 from bigcapitalhq/all-contributors/add-ANasouf
docs: add ANasouf as a contributor for code
2024-01-09 22:23:15 +02:00
allcontributors[bot]
8c1ca503bd docs: update .all-contributorsrc [skip ci] 2024-01-09 20:22:36 +00:00
allcontributors[bot]
0ac79e661c docs: update README.md [skip ci] 2024-01-09 20:22:35 +00:00
Ahmed Bouhuolia
66ba4d35aa Merge pull request #304 from ANasouf/BIG-53-Add-approve-reject-buttons-on-action-bar-of-the-estimate-details-drawer
feat(webapp): add approve/reject to action bar of estimate details dr…
2024-01-09 22:17:43 +02:00
Ahmed Bouhuolia
46d25dff4c fix: move approve/reject estimate buttons to more menu 2024-01-09 22:14:10 +02:00
Ahmed Bouhuolia
5ef99f2cb3 feat: general ledger and journal exporting 2024-01-07 18:48:14 +02:00
Ahmed Bouhuolia
7aee76e461 feat: general ledger and journal rows types 2024-01-06 22:30:41 +02:00
Ahmed Bouhuolia
79f3f1b63d feat: wip journal and general ledger dyanmic columns 2024-01-06 20:16:22 +02:00
a.nasouf
26e104b9f1 feat(webapp): add approve/reject to action bar of estimate details drawer 2024-01-06 15:28:02 +02:00
Ahmed Bouhuolia
c71836ec27 feat: wip general ledger table 2024-01-04 21:43:57 +02:00
Ahmed Bouhuolia
60b1bc9ed7 feat(server): wip journal and general ledger table layer 2024-01-04 17:22:13 +02:00
Ahmed Bouhuolia
e6a3daa2c3 feat(server): general ledger exporting to csv/xlsx 2024-01-02 21:54:10 +02:00
Ahmed Bouhuolia
276ef1c907 feat(server): journal sheet csv/xlsx export 2024-01-02 21:53:37 +02:00
Ahmed Bouhuolia
83e48cce42 feat: remove hint popup of mail notification form 2024-01-01 20:59:31 +02:00
Ahmed Bouhuolia
99ca683d13 chore: update CHANGELOG file 2024-01-01 20:57:08 +02:00
Ahmed Bouhuolia
5062d891e1 feat: journal sheet export 2024-01-01 20:43:16 +02:00
Ahmed Bouhuolia
92d1fc670b Merge pull request #292 from bigcapitalhq/big-83-send-an-invoice-to-the-customer-email
feat: Send mail notifications of the sales transactions
2023-12-30 19:14:31 +02:00
Ahmed Bouhuolia
ab7abfea35 feat: mail notifications of sales transactions 2023-12-30 17:49:02 +02:00
Ahmed Bouhuolia
0d15c16d40 feat(server): contact mail notification service 2023-12-29 17:35:34 +02:00
Ahmed Bouhuolia
2a85fe2f3c feat(webapp): the mail notifications dialogs 2023-12-29 17:31:51 +02:00
Ahmed Bouhuolia
dc762567b5 feat(webapp): send mail notififcation of sale transactions 2023-12-28 17:53:51 +02:00
Ahmed Bouhuolia
a676e09537 feat: rich editor component 2023-12-27 23:56:34 +02:00
Ahmed Bouhuolia
c46948049c feat: add send mail icon 2023-12-26 22:57:41 +02:00
Ahmed Bouhuolia
de1b7f132c feat(webapp): send mail notification dialogs 2023-12-26 15:52:38 +02:00
allcontributors[bot]
90b552d5c9 docs: add cschuijt as a contributor for bug (#295)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-12-26 15:46:04 +02:00
Casper Schuijt
39115b3000 fix: do not validate numericality on postal codes of customer/vendor form (#294) 2023-12-26 15:42:29 +02:00
Ahmed Bouhuolia
3c8c3d8253 feat: add endpoints to retrieving the default mail options 2023-12-25 18:21:02 +02:00
Ahmed Bouhuolia
657400c671 feat: tweak mail notifications content 2023-12-24 23:41:04 +02:00
Ahmed Bouhuolia
6356cb5e63 feat: send mail notifications of sale transactions 2023-12-24 21:53:37 +02:00
Ahmed Bouhuolia
13c6e7a62d feat: send mail notifications of payment 2023-12-24 21:51:23 +02:00
Ahmed Bouhuolia
b6d99b1d4b feat: send mail notifications of sale receipts 2023-12-24 21:49:59 +02:00
Ahmed Bouhuolia
f0e15d43d3 feat: send sale estimate mail notification 2023-12-22 23:56:37 +02:00
Ahmed Bouhuolia
50d5ddba8e feat: send invoice notifications 2023-12-22 02:45:44 +02:00
Ahmed Bouhuolia
d2c63878ed feat: send invoices and reminder notifications to the customers 2023-12-21 22:57:17 +02:00
Ahmed Bouhuolia
ef52eaf91a Merge branch 'develop' into big-83-send-an-invoice-to-the-customer-email 2023-12-19 16:08:21 +02:00
Ahmed Bouhuolia
cd71900bdd feat: send invoice through mail 2023-12-18 21:28:53 +02:00
Ahmed Bouhuolia
34e97fc56b chore: update CHANGELOG.md 2023-12-17 23:07:25 +02:00
Ahmed Bouhuolia
e6e119682b Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2023-12-17 23:05:21 +02:00
Ahmed Bouhuolia
e096db2ab8 chore: update CAHNGELOG.md 2023-12-17 23:04:54 +02:00
Ahmed Bouhuolia
cdff641cdb Merge pull request #293 from bigcapitalhq/big-84-fix-the-transaction-tax-rate-currency
fix: the currency code of transaction tax rate entry
2023-12-17 17:52:14 +02:00
Ahmed Bouhuolia
d0126ffc2c fix: the currency code of transaction tax rate entry 2023-12-17 17:51:17 +02:00
Ahmed Bouhuolia
fe043eb594 Merge pull request #291 from bigcapitalhq/BIG-81
feat: Add default customer message and terms conditions to sales transactions
2023-12-16 22:41:58 +02:00
Ahmed Bouhuolia
eb1ff7c151 chore: doc comments 2023-12-16 22:34:36 +02:00
Ahmed Bouhuolia
a862ee9ccf feat: localize the preferences menu 2023-12-16 19:37:20 +02:00
Ahmed Bouhuolia
6953f7c4a3 feat: assign default default messages on sales transactions 2023-12-16 19:26:41 +02:00
Ahmed Bouhuolia
ad53ddb9dd feat: inject default message value to sales forms 2023-12-15 20:15:42 +02:00
Ahmed Bouhuolia
cfd4540a65 feat: wip send an invoice mail the customer email 2023-12-14 23:49:23 +02:00
Ahmed Bouhuolia
217321380a feat: wip default message to sales transactions. 2023-12-14 20:47:52 +02:00
Ahmed Bouhuolia
33e5d1a979 feat: Add default customer message and terms and conditions to the transactions. 2023-12-14 11:01:25 +02:00
Ahmed Bouhuolia
e5bcb1c19a Merge pull request #280 from bigcapitalhq/optimize-printing
feat: optimize documents printing
2023-12-04 22:36:43 +02:00
Ahmed Bouhuolia
bf2dc844d2 feat: add gotenberg configure of prod 2023-12-04 22:34:21 +02:00
Ahmed Bouhuolia
c9d3a3cd7c Merge pull request #289 from bigcapitalhq/big-80-entering-decimal-amount-in-salepurchase-transactions
fix(server): allow decimal amount in sale/purchase transactions.
2023-12-04 19:52:22 +02:00
Ahmed Bouhuolia
33afa3af64 fix(webapp): use the server formatted value 2023-12-04 08:31:18 +02:00
Ahmed Bouhuolia
fb70c94465 fix(server): add formatted values to responses transformers 2023-12-04 08:21:06 +02:00
Ahmed Bouhuolia
0f39cfb3af fix: change rate column to decimal of item entries table 2023-12-02 15:15:12 +02:00
Ahmed Bouhuolia
7fc1320834 fix(server): allow decimal amount in sale/purchase transactions. 2023-12-01 17:04:11 +02:00
Ahmed Bouhuolia
6091bc73cf fix: axios upgrade (#288) 2023-11-30 15:51:10 +02:00
Ahmed Bouhuolia
30b5f8120a fix: invoice pdf document 2023-11-30 13:49:43 +02:00
Ahmed Bouhuolia
5a958cc9fa Merge branch 'develop' into optimize-printing 2023-11-29 15:35:06 +02:00
Ahmed Bouhuolia
d15c5890ed feat: export reports csv and xlsx (#286) 2023-11-28 19:53:13 +02:00
dependabot[bot]
151aff4c8e chore(deps): bump axios from 0.20.0 to 1.6.0 (#283)
Bumps [axios](https://github.com/axios/axios) from 0.20.0 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.20.0...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-17 02:58:38 +02:00
dependabot[bot]
b167284c8e chore(deps): bump axios from 0.20.0 to 1.6.0 in /packages/server (#284)
Bumps [axios](https://github.com/axios/axios) from 0.20.0 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.20.0...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-17 02:58:28 +02:00
Ahmed Bouhuolia
b75d44a3dd WIP 2023-11-13 20:50:48 +02:00
Ahmed Bouhuolia
f0f0045353 chore(server): remove duplicated locales files 2023-11-07 14:55:48 +02:00
Ahmed Bouhuolia
50164873ce chore: remove the duplicated keys in lang files 2023-11-05 20:11:27 +02:00
Ahmed Bouhuolia
a2cbab0bc3 chore: update README.md to add Gitpod 2023-11-05 03:28:04 +02:00
Ahmed Bouhuolia
5027f9fe5d chore: configure Gitpod 2023-11-05 02:58:48 +02:00
Ahmed Bouhuolia
eeb67d4005 chore: configure Gitpod 2023-11-05 02:22:26 +02:00
Ahmed Bouhuolia
a3d9e8ef2b chore: configure gitpod 2023-11-04 15:35:05 +02:00
Ahmed Bouhuolia
6634144d82 feat: optimize documents printing 2023-10-31 02:08:20 +02:00
Ahmed Bouhuolia
9531730d7a feat: Auto re-calculate the items rate once changing the invoice exchange rate. 2023-10-16 19:14:27 +02:00
430 changed files with 24371 additions and 10504 deletions

View File

@@ -69,6 +69,24 @@
"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"
]
}
],
"contributorsPerLine": 7,

View File

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

22
.gitpod.yml Normal file
View File

@@ -0,0 +1,22 @@
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,11 +2,56 @@
All notable changes to Bigcapital server-side will be in this file.
# [0.10.1] - 25-09-2023
## [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
## [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
@@ -16,7 +61,7 @@ All notable changes to Bigcapital server-side will be in this file.
* 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
## [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
@@ -30,18 +75,18 @@ All notable changes to Bigcapital server-side will be in this file.
* 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
## [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
## [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
@@ -53,7 +98,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`

View File

@@ -37,6 +37,37 @@ 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.
@@ -82,6 +113,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<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>
</tr>
</tbody>
</table>

View File

@@ -35,6 +35,8 @@ services:
server:
container_name: bigcapital-server
image: ghcr.io/bigcapitalhq/server:latest
expose:
- '3000'
links:
- mysql
- mongo
@@ -86,6 +88,10 @@ 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:
@@ -143,6 +149,11 @@ services:
volumes:
- redis:/data
gotenberg:
image: gotenberg/gotenberg:7
expose:
- '9000'
# Volumes
volumes:
mysql:

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@
"agendash": "^3.1.0",
"app-root-path": "^3.0.0",
"async": "^3.2.0",
"axios": "^0.20.0",
"axios": "^1.6.0",
"babel-loader": "^9.1.2",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
@@ -56,6 +56,7 @@
"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",
@@ -96,11 +97,13 @@
"reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4",
"source-map-loader": "^4.0.1",
"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"
"winston": "^3.2.1",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/lodash": "^4.14.158",

View File

@@ -176,6 +176,7 @@
"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",

View File

@@ -137,10 +137,14 @@
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

@@ -22,12 +22,12 @@ block content
div.invoice__due-amount
div.label #{__('invoice.paper.invoice_amount')}
div.amount #{saleInvoice.formattedAmount}
div.amount #{saleInvoice.totalFormatted}
div.invoice__meta
div.invoice__meta-item.invoice__meta-item--amount
span.label #{__('invoice.paper.due_amount')}
span.value #{saleInvoice.formattedDueAmount}
span.value #{saleInvoice.dueAmountFormatted}
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.formattedInvoiceDate}
span.value #{saleInvoice.invoiceDateFormatted}
div.invoice__meta-item.invoice__meta-item--due-date
span.label #{__("invoice.paper.due_date")}
span.value #{saleInvoice.formattedDueDate}
span.value #{saleInvoice.dueDateFormatted}
div.invoice__table
table
@@ -63,15 +63,22 @@ 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.formattedAmount}
td #{saleInvoice.totalFormatted}
tr.payment-amount
td #{__('invoice.paper.payment_amount')}
td #{saleInvoice.formattedPaymentAmount}
td #{saleInvoice.paymentAmountFormatted}
tr.blanace-due
td #{__('invoice.paper.balance_due')}
td #{saleInvoice.formattedDueAmount}
td #{saleInvoice.dueAmountFormatted}
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.formattedInvoiceDate
td.invoiceAmount=entry.invoice.formattedAmount
td.paymentAmount=entry.invoice.formattedPaymentAmount
td.date=entry.invoice.invoiceDateFormatted
td.invoiceAmount=entry.invoice.totalFormatted
td.paymentAmount=entry.invoice.paymentAmountFormatted
div.payment__table-after
div.payment__table-total

View File

@@ -2,19 +2,20 @@ 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()
APAgingSummaryService: APAgingSummaryReportService;
private APAgingSummaryApp: APAgingSummaryApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -28,8 +29,9 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
/**
* Validation schema.
* @returns {ValidationChain[]}
*/
get validationSchema() {
private get validationSchema() {
return [
...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
@@ -49,42 +51,58 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
}
/**
* Retrieve payable aging summary report.
* Retrieves payable aging summary report.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
async payableAgingSummary(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
private async payableAgingSummary(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
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,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.APAgingSummaryApp.table(tenantId, filter);
switch (acceptType) {
case 'application/json+table':
const table = await this.APAgingSummaryService.APAgingSummaryTable(
tenantId,
filter
);
return res.status(200).send({
table: {
rows: table.rows,
columns: table.columns,
},
meta: table.meta,
query: table.query,
});
break;
default:
const { data, columns, query, meta } =
await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
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);
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
break;
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 json format.
} else {
const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

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

View File

@@ -3,25 +3,21 @@ 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 BalanceSheetTable from '@/services/FinancialStatements/BalanceSheet/BalanceSheetTable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { BalanceSheetApplication } from '@/services/FinancialStatements/BalanceSheet/BalanceSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class BalanceSheetStatementController extends BaseFinancialReportController {
@Inject()
balanceSheetService: BalanceSheetStatementService;
@Inject()
tenancy: HasTenancyService;
private balanceSheetApp: BalanceSheetApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -38,7 +34,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
* Balance sheet validation schecma.
* @returns {ValidationChain[]}
*/
get balanceSheetValidationSchema(): ValidationChain[] {
private get balanceSheetValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('accounting_method').optional().isIn(['cash', 'accrual']),
@@ -84,10 +80,12 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
/**
* Retrieve the balance sheet.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async balanceSheet(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const i18n = this.tenancy.i18n(tenantId);
private async balanceSheet(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
let filter = this.matchedQueryData(req);
@@ -95,29 +93,45 @@ 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 table = new BalanceSheetTable(data, query, i18n);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) {
const table = await this.balanceSheetApp.table(tenantId, filter);
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 });
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);
} else {
const sheet = await this.balanceSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -8,29 +8,20 @@ import {
ValidationChain,
} from 'express';
import BaseFinancialReportController from '../BaseFinancialReportController';
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 { AbilitySubject, ReportsAction } from '@/interfaces';
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()
cashFlowService: CashFlowStatementService;
@Inject()
tenancy: HasTenancyService;
private cashflowSheetApp: CashflowSheetApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -47,7 +38,7 @@ export default class CashFlowController extends BaseFinancialReportController {
* Balance sheet validation schecma.
* @returns {ValidationChain[]}
*/
get cashflowValidationSchema(): ValidationChain[] {
private get cashflowValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('from_date').optional(),
@@ -67,41 +58,6 @@ 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
@@ -109,26 +65,52 @@ export default class CashFlowController extends BaseFinancialReportController {
* @param {NextFunction} next
* @returns {Response}
*/
async cashFlow(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
public async cashFlow(req: Request, res: Response, next: NextFunction) {
const { tenantId } = 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']);
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));
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.cashflowSheetApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.cashflowSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.status(200).send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.cashflowSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter);
return res.status(200).send(cashflow);
}
} catch (error) {
next(error);

View File

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

View File

@@ -2,15 +2,16 @@ 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()
generalLedgetService: GeneralLedgerService;
private generalLedgerApplication: GeneralLedgerApplication;
/**
* Router constructor.
@@ -61,20 +62,43 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
* @param {Response} res -
*/
async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const { tenantId } = req;
const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
try {
const { data, query, meta } =
await this.generalLedgetService.generalLedger(tenantId, filter);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.generalLedgerApplication.table(tenantId, filter);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
});
} catch (error) {
next(error);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.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 json format.
} else {
const sheet = await this.generalLedgerApplication.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
}
}

View File

@@ -8,24 +8,20 @@ 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()
inventoryDetailsService: InventoryDetailsService;
@Inject()
tenancy: HasTenancyService;
private inventoryItemDetailsApp: InventortyDetailsApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -45,7 +41,7 @@ export default class InventoryDetailsController extends BaseController {
* Balance sheet validation schecma.
* @returns {ValidationChain[]}
*/
get validationSchema(): ValidationChain[] {
private get validationSchema(): ValidationChain[] {
return [
query('number_format.precision')
.optional()
@@ -77,69 +73,66 @@ export default class InventoryDetailsController extends BaseController {
}
/**
* 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.
* Retrieve the inventory item details sheet.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response}
*/
async inventoryDetails(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
private async inventoryDetails(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = {
...this.matchedQueryData(req),
};
try {
const inventoryDetails =
await this.inventoryDetailsService.inventoryDetails(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,
]);
// Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.inventoryItemDetailsApp.csv(tenantId, filter);
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));
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);
} else {
const sheet = await this.inventoryItemDetailsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -3,14 +3,15 @@ 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()
inventoryValuationService: InventoryValuationService;
private inventoryValuationApp: InventoryValuationSheetApplication;
/**
* Router constructor.
@@ -71,19 +72,45 @@ export default class InventoryValuationReportController extends BaseFinancialRep
const { tenantId } = req;
const filter = this.matchedQueryData(req);
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),
});
} catch (error) {
next(error);
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
]);
// Retrieves the json table format.
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 json format.
} else {
const { data, query, meta } = await this.inventoryValuationApp.sheet(
tenantId,
filter
);
return res.status(200).send({ meta, data, query });
}
}
}

View File

@@ -3,14 +3,15 @@ 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()
journalService: JournalSheetService;
private journalSheetApp: JournalSheetApplication;
/**
* Router constructor.
@@ -57,28 +58,49 @@ export default class JournalSheetController extends BaseFinancialReportControlle
* @param {Request} req -
* @param {Response} res -
*/
async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
private async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId } = 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,
]);
try {
const { data, query, meta } = await this.journalService.journalSheet(
tenantId,
filter
// 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'
);
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
} catch (error) {
next(error);
return res.status(200).send(sheet);
}
}
}

View File

@@ -1,24 +1,20 @@
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 { ProfitLossSheetTable } from '@/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { ProfitLossSheetApplication } from '@/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication';
@Service()
export default class ProfitLossSheetController extends BaseFinancialReportController {
@Inject()
profitLossSheetService: ProfitLossSheetService;
@Inject()
tenancy: HasTenancyService;
private profitLossSheetApp: ProfitLossSheetApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -34,7 +30,7 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
/**
* Validation schema.
*/
get validationSchema(): ValidationChain[] {
private get validationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('basis').optional(),
@@ -85,37 +81,54 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
* @param {Request} req -
* @param {Response} res -
*/
async profitLossSheet(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const i18n = this.tenancy.i18n(tenantId);
private async profitLossSheet(
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,
]);
try {
const { data, query, meta } =
await this.profitLossSheetService.profitLossSheet(tenantId, filter);
// Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const sheet = await this.profitLossSheetApp.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');
switch (acceptType) {
case 'application/json+table':
const table = new ProfitLossSheetTable(data, query, i18n);
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);
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,
});
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 {
const sheet = await this.profitLossSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

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

View File

@@ -1,20 +1,21 @@
import { Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
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 CheckPolicies from '@/api/middleware/CheckPolicies';
import { SalesTaxLiabilitySummaryService } from '@/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryService';
import { SalesTaxLiabilitySummaryApplication } from '@/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
export default class SalesTaxLiabilitySummary extends BaseFinancialReportController {
@Inject()
private salesTaxLiabilitySummaryService: SalesTaxLiabilitySummaryService;
private salesTaxLiabilitySummaryApp: SalesTaxLiabilitySummaryApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -31,8 +32,9 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
/**
* Validation schema.
* @returns {ValidationChain[]}
*/
get validationSchema() {
private get validationSchema() {
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
@@ -45,7 +47,7 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
* @param {Response} res -
* @param {NextFunction} next -
*/
async salesTaxLiabilitySummary(
private async salesTaxLiabilitySummary(
req: Request,
res: Response,
next: NextFunction
@@ -55,33 +57,52 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
try {
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,
]);
switch (acceptType) {
case 'application/json+table':
const salesTaxLiabilityTable =
await this.salesTaxLiabilitySummaryService.salesTaxLiabilitySummaryTable(
tenantId,
filter
);
// 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.status(200).send({
table: salesTaxLiabilityTable.table,
query: salesTaxLiabilityTable.query,
meta: salesTaxLiabilityTable.meta,
});
case 'json':
default:
const salesTaxLiability =
await this.salesTaxLiabilitySummaryService.salesTaxLiability(
tenantId,
filter
);
return res.status(200).send({
data: salesTaxLiability.data,
query: salesTaxLiability.query,
meta: salesTaxLiability.meta,
});
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.salesTaxLiabilitySummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -1,30 +1,22 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import { Inject, Service } from 'typedi';
import {
AbilitySubject,
ITransactionsByCustomersStatement,
ReportsAction,
} from '@/interfaces';
import { AbilitySubject, 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()
transactionsByCustomersService: TransactionsByCustomersService;
@Inject()
tenancy: HasTenancyService;
private transactionsByCustomersApp: TransactionsByCustomerApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -58,45 +50,13 @@ 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 -
*/
async transactionsByCustomers(
private async transactionsByCustomers(
req: Request,
res: Response,
next: NextFunction
@@ -104,25 +64,51 @@ 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,
]);
try {
const report =
await this.transactionsByCustomersService.transactionsByCustomers(
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.transactionsByCustomersApp.table(
tenantId,
filter
);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
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);
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));
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 {
const sheet = await this.transactionsByCustomersApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -3,27 +3,19 @@ import { query, ValidationChain } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController';
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 { AbilitySubject, ReportsAction } from '@/interfaces';
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()
transactionsByVendorsService: TransactionsByVendorsService;
@Inject()
tenancy: HasTenancyService;
private transactionsByVendorsApp: TransactionsByVendorApplication;
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -42,7 +34,7 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
/**
* Validation schema.
*/
get validationSchema(): ValidationChain[] {
private get validationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
@@ -58,64 +50,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 -
*/
async transactionsByVendors(req: Request, res: Response, next: NextFunction) {
private async transactionsByVendors(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
const report =
await this.transactionsByVendorsService.transactionsByVendors(
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,
]);
// Retrieves the xlsx format.
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.transactionsByVendorsApp.xlsx(
tenantId,
filter
);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
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));
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 json format.
} else {
const sheet = await this.transactionsByVendorsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -3,15 +3,17 @@ 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 TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetInjectable';
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()
trialBalanceSheetService: TrialBalanceSheetService;
private trialBalanceSheetApp: TrialBalanceSheetApplication;
/**
* Router constructor.
@@ -73,21 +75,46 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
};
try {
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
if (acceptType === 'application/json+table') {
const { table, meta, query } =
await this.trialBalanceSheetService.trialBalanceSheetTable(
tenantId,
filter
);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
]);
// Retrieves in json table format.
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const { table, meta, query } = await this.trialBalanceSheetApp.table(
tenantId,
filter
);
return res.status(200).send({ table, meta, query });
// Retrieves in xlsx format
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.trialBalanceSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves in csv format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.trialBalanceSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves in json format.
} else {
const { data, query, meta } =
await this.trialBalanceSheetService.trialBalanceSheet(
tenantId,
filter
);
const { data, query, meta } = await this.trialBalanceSheetApp.sheet(
tenantId,
filter
);
return res.status(200).send({ data, query, meta });
}
} catch (error) {

View File

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

View File

@@ -122,7 +122,7 @@ export default class BillsController 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().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()

View File

@@ -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().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
];
}

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().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -211,12 +211,11 @@ 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().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()

View File

@@ -222,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().toFloat(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()

View File

@@ -1,10 +1,11 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query, ValidationChain } from 'express-validator';
import { body, check, param, query, ValidationChain } from 'express-validator';
import {
AbilitySubject,
IPaymentReceiveDTO,
PaymentReceiveAction,
PaymentReceiveMailOptsDTO,
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -117,6 +118,25 @@ 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;
}
@@ -142,7 +162,7 @@ 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().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
];
}
@@ -416,27 +436,26 @@ export default class PaymentReceivesController extends BaseController {
const { id: paymentReceiveId } = req.params;
try {
const paymentReceive =
await this.paymentReceiveApplication.getPaymentReceive(
tenantId,
paymentReceiveId
);
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
[ACCEPT_TYPE.APPLICATION_JSON]: async () => {
const paymentReceive =
await this.paymentReceiveApplication.getPaymentReceive(
tenantId,
paymentReceiveId
);
return res.status(200).send({
payment_receive: this.transfromToResponse(paymentReceive),
payment_receive: paymentReceive,
});
},
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceive
paymentReceiveId
);
res.set({
'Content-Type': 'application/pdf',
@@ -507,6 +526,66 @@ 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
@@ -514,7 +593,7 @@ export default class PaymentReceivesController extends BaseController {
* @param res
* @param next
*/
handleServiceErrors(
private handleServiceErrors(
error: Error,
req: Request,
res: Response,

View File

@@ -1,10 +1,11 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { body, check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi';
import {
AbilitySubject,
ISaleEstimateDTO,
SaleEstimateAction,
SaleEstimateMailOptionsDTO,
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -121,6 +122,27 @@ 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;
}
@@ -362,22 +384,22 @@ export default class SalesEstimatesController extends BaseController {
const { tenantId } = req;
try {
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId,
estimateId
);
// Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(this.transfromToResponse({ estimate }));
[ACCEPT_TYPE.APPLICATION_JSON]: async () => {
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId,
estimateId
);
return res.status(200).send({ estimate });
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimate
estimateId
);
res.set({
'Content-Type': 'application/pdf',
@@ -478,6 +500,65 @@ 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,5 +1,5 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { body, check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import BaseController from '../BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -10,6 +10,7 @@ import {
ISaleInvoiceCreateDTO,
SaleInvoiceAction,
AbilitySubject,
SendInvoiceMailDTO,
} from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
@@ -145,6 +146,48 @@ 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;
}
@@ -375,44 +418,34 @@ export default class SaleInvoicesController extends BaseController {
* @param {Request} req - Request object.
* @param {Response} res - Response object.
*/
private async getSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
private async getSaleInvoice(req: Request, res: Response) {
const { id: saleInvoiceId } = req.params;
const { tenantId, user } = req;
try {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
tenantId,
saleInvoiceId,
user
);
// Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res
.status(200)
.send(this.transfromToResponse({ saleInvoice }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoice
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
}
// Response formatter.
return res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: async () => {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
tenantId,
saleInvoiceId,
user
);
return res.status(200).send(this.transfromToResponse({ saleInvoice }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
}
/**
* Retrieve paginated sales invoices with custom view metadata.
@@ -630,6 +663,119 @@ 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

View File

@@ -1,9 +1,9 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { body, check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController';
import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
import { ISaleReceiptDTO, SaleReceiptMailOpts, SaleReceiptMailOptsDTO } from '@/interfaces/SaleReceipt';
import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CheckPolicies from '@/api/middleware/CheckPolicies';
@@ -46,6 +46,29 @@ 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),
@@ -117,7 +140,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().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -314,26 +337,24 @@ export default class SalesReceiptsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
async getSaleReceipt(req: Request, res: Response, next: NextFunction) {
public async getSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { id: saleReceiptId } = req.params;
const { tenantId } = req;
try {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId,
saleReceiptId
);
res.format({
'application/json': () => {
return res
.status(200)
.send(this.transfromToResponse({ saleReceipt }));
'application/json': async () => {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId,
saleReceiptId
);
return res.status(200).send({ saleReceipt });
},
'application/pdf': async () => {
const pdfContent =
await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceipt
saleReceiptId
);
res.set({
'Content-Type': 'application/pdf',
@@ -405,6 +426,64 @@ export default class SalesReceiptsController extends BaseController {
}
};
/**
* Sends mail notification of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public sendSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
const receiptMailDTO: SaleReceiptMailOptsDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleReceiptsApplication.sendSaleReceiptMail(
tenantId,
receiptId,
receiptMailDTO
);
return res.status(200).send({
code: 200,
message:
'The sale receipt notification via sms has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
try {
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
tenantId,
receiptId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error

View File

@@ -5,6 +5,7 @@ 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

@@ -58,6 +58,7 @@ 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,
},
/**

View File

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

View File

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,9 @@
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

@@ -8,6 +8,7 @@ import {
IAgingSummaryData,
} from './AgingReport';
import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
vendorsIds: number[];
@@ -34,3 +35,8 @@ export interface IAPAgingSummaryMeta {
baseCurrency: string;
organizationName: string;
}
export interface IAPAgingSummaryTable extends IFinancialTable {
query: IAPAgingSummaryQuery;
meta: IAPAgingSummaryMeta;
}

View File

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

View File

@@ -3,6 +3,7 @@ import {
IFormatNumberSettings,
IFinancialSheetBranchesQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table';
// Balance sheet schema nodes types.
export enum BALANCE_SHEET_SCHEMA_NODE_TYPE {
@@ -215,3 +216,8 @@ export enum IAccountTransactionsGroupBy {
Month = 'month',
Week = 'week',
}
export interface IBalanceSheetTable extends IFinancialTable {
meta: IBalanceSheetMeta;
query: IBalanceSheetQuery;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface ITrialBalanceSheetQuery {
fromDate: Date | string;
@@ -48,3 +49,8 @@ export interface ITrialBalanceStatement {
query: ITrialBalanceSheetQuery;
meta: ITrialBalanceSheetMeta;
}
export interface ITrialBalanceSheetTable extends IFinancialTable {
meta: ITrialBalanceSheetMeta;
query: ITrialBalanceSheetQuery;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,640 +0,0 @@
{
"Petty Cash": "العهدة",
"Cash": "النقدية",
"Bank": "المصرف",
"Other Income": "إيرادات اخري",
"Interest Income": "إيرادات الفوائد",
"Depreciation Expense": "مصاريف الاهلاك",
"Interest Expense": "مصروفات الفوائد",
"Sales of Product Income": "مبيعات دخل المنتجات",
"Inventory Asset": "المخزون",
"Cost of Goods Sold (COGS)": "تكلفة البضائع المباعة (COGS)",
"Cost of Goods Sold": "تكلفة البضاعة المباعة",
"Accounts Payable": "الذمم الدائنة",
"Other Expense": "مصاريف أخرى",
"Payroll Expenses": "مصاريف المرتبات",
"Fixed Asset": "أصول ثابتة",
"Credit Card": "بطاقة إئتمان",
"Non-Current Asset": "أصول غير متداولة",
"Current Asset": "أصول متداولة",
"Other Asset": "أصول اخري",
"Long Term Liability": "التزامات طويلة الاجل",
"Current Liability": "التزامات قصيرة الاجل",
"Other Liability": "التزمات اخري",
"Equity": "حقوق الملكية",
"Expense": "مصروف",
"Income": "إيراد",
"Accounts Receivable (A/R)": "الذمم المدينة",
"Accounts Receivable": "الذمم المدينة",
"Accounts Payable (A/P)": "الذمم الدائنة",
"Inactive": "غير نشط",
"Other Current Asset": "أصول متداولة اخرى",
"Tax Payable": "الضريبة المستحقة",
"Other Current Liability": "التزامات قصيرة الأجر اخرى",
"Non-Current Liability": "التزامات طويلة الأجر",
"Assets": "أصول",
"Liabilities": "الالتزمات",
"Account name": "أسم الحساب",
"Account type": "نوع الحساب",
"Account normal": "حساب عادي",
"Description": "وصف",
"Account code": "رمز الحساب",
"Currency": "عملة",
"Balance": "توازن",
"Active": "نشيط",
"Created at": "أنشئت في",
"fixed_asset": "أصل ثابت",
"Journal": "قيد",
"Reconciliation": "تسوية",
"Credit": "دائن",
"Debit": "مدين",
"Interest": "فائدة",
"Depreciation": "اهلاك",
"Payroll": "كشف رواتب",
"Type": "نوع",
"Name": "الأسم",
"Sellable": "قابل للبيع",
"Purchasable": "قابل للشراء",
"Sell price": "سعر البيع",
"Cost price": "سعر الكلفة",
"User": "المستخدم",
"Category": "تصنيف",
"Note": "ملحوظة",
"Quantity on hand": "كمية في اليد",
"Purchase description": "وصف الشراء",
"Sell description": "وصف البيع",
"Sell account": "حساب البيع",
"Cost account": "حساب التكلفة",
"Inventory account": "حساب المخزون",
"Payment date": "تاريخ الدفع",
"Payment account": "حساب الدفع",
"Amount": "كمية",
"Reference No.": "رقم المرجع.",
"Published": "نشرت",
"Journal number": "رقم القيد",
"Status": "حالة",
"Journal type": "نوع القيد",
"Date": "تاريخ",
"Asset": "أصل",
"Liability": "التزام",
"First-in first-out (FIFO)": "الوارد أولاً يصرف أولاً (FIFO)",
"Last-in first-out (LIFO)": "الوارد أخيرًا يصرف أولاً (LIFO)",
"Average rate": "المعدل المتوسط",
"Total": "الإجمالي",
"Transaction type": "نوع المعاملة",
"Transaction #": "عملية #",
"Running Value": "القيمة الجارية",
"Running quantity": "الكمية الجارية",
"Profit Margin": "هامش الربح",
"Value": "القيمة",
"Rate": "السعر",
"OPERATING ACTIVITIES": "الأنشطة التشغيلية",
"FINANCIAL ACTIVITIES": "الأنشطة التمويلية",
"INVESTMENT ACTIVITIES": "الانشطة الاستثمارية",
"Net income": "صافي الدخل",
"Adjustments net income by operating activities.": "تسويات صافي الدخل من الأنشطة التشغيلية.",
"Net cash provided by operating activities": "صافي التدفقات النقدية من أنشطة التشغيل",
"Net cash provided by investing activities": "صافي التدفقات النقدية من أنشطة الاستثمار",
"Net cash provided by financing activities": "صافي التدفقات النقدية من أنشطة التمويلية",
"Cash at beginning of period": "التدفقات النقدية في بداية الفترة",
"NET CASH INCREASE FOR PERIOD": "زيادة التدفقات النقدية للفترة",
"CASH AT END OF PERIOD": "صافي التدفقات النقدية في نهاية الفترة",
"Expenses": "مصاريف",
"Services": "خدمات",
"Inventory": "المخزون",
"Non Inventory": "غير المخزون",
"Draft": "مسودة",
"Delivered": "تم التوصيل",
"Overdue": "متأخر",
"Partially paid": "المدفوعة جزئيا",
"Paid": "مدفوع",
"Opened": "افتتح",
"Unpaid": "غير مدفوعة",
"Approved": "وافق",
"Rejected": "مرفوض",
"Invoiced": "مفوترة",
"Expired": "منتهي الصلاحية",
"Closed": "مغلق",
"Manual journal": "قيد اليدوي",
"Owner contribution": "زيادة رأس المال",
"Transfer to account": "تحويل إلى الحساب",
"Transfer from account": "تحويل من الحساب",
"Other income": "إيراد اخر",
"Other expense": "مصاريف أخرى",
"Owner drawing": "سحب رأس المال",
"Inventory adjustment": "تسوية المخزون",
"Customer opening balance": "الرصيد الافتتاحي للزبون",
"Vendor opening balance": "رصيد افتتاحي للمورد",
"Payment made": "سند الزبون",
"Bill": "فاتورة الشراء",
"Payment receive": "استلام الدفع",
"Sale receipt": "إيصال البيع",
"Sale invoice": "فاتورة البيع",
"Quantity": "الكمية",
"Bank Account": "حساب البنك",
"Saving Bank Account": "حساب التوفير البنكي",
"Undeposited Funds": "الأموال غير المودعة",
"Computer Equipment": "معدات كمبيوتر",
"Office Equipment": "معدات مكتبية",
"Uncategorized Income": "الدخل غير مصنف",
"Sales of Service Income": "دخل مبيعات الخدمات",
"Bank Fees and Charges": "رسوم المصرفية",
"Exchange Gain or Loss": "ربح أو خسارة فروقات الصرف",
"Rent": "إيجار",
"Office expenses": "مصاريف المكتب",
"Other Expenses": "مصاريف اخري",
"Drawings": "السحوبات",
"Owner's Equity": "حقوق الملكية",
"Opening Balance Equity": "الارصدة الافتتاحية ",
"Retained Earnings": "الأرباح المحتجزة",
"Sales Tax Payable": "ضريبة المبيعات المستحقة",
"Revenue Received in Advance": "الإيرادات المقبوضة مقدما",
"Opening Balance Liabilities": "رصيد الالتزامات الافتتاحي",
"Loan": "اقراض",
"Owner A Drawings": "مسحوبات المالك",
"An account that holds valuation of products or goods that available 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.": "لا ترتبط انشطة الدخل إلى الأعمال الأساسية.",
"Cash and cash equivalents": "النقد والنقد المكافئ",
"Inventories": "مخزون البضاعة",
"Other current assets": "الأصول متداولة الأخرى",
"Non-Current Assets": "أصول غير المتداولة",
"Current Liabilties": "التزامات متداولة",
"Long-Term Liabilities": "التزامات طويلة الاجل",
"Non-Current Liabilities": "التزامات غير متداولة",
"Liabilities and Equity": "التزامات وحقوق الملكية",
"Closing balance": "الرصيد الختامي",
"Opening balance": "الرصيد الفتاحي",
"Total {{accountName}}": "إجمالي {{accountName}}",
"invoice.paper.invoice": "فاتورة",
"invoice.paper.due_amount": "القيمة المستحقة",
"invoice.paper.billed_to": "فاتورة إلي",
"invoice.paper.invoice_date": "تاريخ الفاتورة",
"invoice.paper.invoice_number": "رقم الفاتورة",
"invoice.paper.due_date": "تاريخ الاستحقاق",
"invoice.paper.conditions_title": "الشروط والأحكام",
"invoice.paper.notes_title": "ملاحظات",
"invoice.paper.total": "المجموع",
"invoice.paper.balance_due": "مبلغ المستحق",
"invoice.paper.payment_amount": "مبلغ المدفوع",
"invoice.paper.invoice_amount": "قيمة الفاتورة",
"item_entry.paper.item_name": "اسم الصنف",
"item_entry.paper.rate": "السعر",
"item_entry.paper.quantity": "الكمية",
"item_entry.paper.total": "إجمالي",
"estimate.paper.estimate": "عرض أسعار",
"estimate.paper.billed_to": "عرض أسعار إلي",
"estimate.paper.estimate_date": "تاريخ العرض",
"estimate.paper.estimate_number": "رقم العرض",
"estimate.paper.expiration_date": "تاريخ انتهاء الصلاحية",
"estimate.paper.conditions_title": "الشروط والأحكام",
"estimate.paper.notes_title": "ملاحظات",
"estimate.paper.amount": "قيمة العرض",
"estimate.paper.subtotal": "المجموع",
"estimate.paper.total": "إجمالي",
"estimate.paper.estimate_amount": "قيمة العرض",
"receipt.paper.receipt": "إيصال",
"receipt.paper.billed_to": "الإيصال إلي",
"receipt.paper.receipt_date": "تاريخ الإيصال",
"receipt.paper.receipt_number": "رقم الإيصال",
"receipt.paper.conditions_title": "الشروط والأحكام",
"receipt.paper.notes_title": "ملاحظات",
"receipt.paper.receipt_amount": "قيمة الإيصال",
"receipt.paper.total": "إجمالي",
"receipt.paper.payment_amount": "مبلغ المدفوع",
"receipt.paper.balance_due": "مبلغ المستحق",
"receipt.paper.statement": "البيان",
"receipt.paper.notes": "ملاحظات",
"payment.paper.payment_receipt": "إيصال قبض",
"payment.paper.amount_received": "القيمة المستلمه",
"payment.paper.billed_to": "إيصال إلي",
"payment.paper.payment_date": "تاريخ الدفع",
"payment.paper.invoice_number": "رقم الفاتورة",
"payment.paper.invoice_date": "تاريخ الفاتورة",
"payment.paper.invoice_amount": "قيمة الفاتورة",
"payment.paper.payment_amount": "قيمة الدفع",
"payment.paper.balance_due": "المبلغ المستحق",
"payment.paper.statement": "البيان",
"credit.paper.credit_note": "اشعار دائن",
"credit.paper.amount": "قيمة الاشعار",
"credit.paper.remaining": "رصيد المتبقي",
"credit.paper.billed_to": "إيصال إلي",
"credit.paper.credit_date": "تاريخ الاشعار",
"credit.paper.terms_conditions": "الشروط والاحكام",
"credit.paper.notes": "ملاحظات",
"credit.paper.total": "إجمالي",
"credit.paper.credits_used": "قيمة المستخدمه",
"credit.paper.credits_remaining": "قيمة المتبقية",
"account.field.name": "إسم الحساب",
"account.field.description": "الوصف",
"account.field.slug": "Account slug",
"account.field.code": "رقم الحساب",
"account.field.root_type": "جذر الحساب",
"account.field.normal": "طبيعة الحساب",
"account.field.normal.credit": "دائن",
"account.field.normal.debit": "مدين",
"account.field.type": "نوع الحساب",
"account.field.active": "Activity",
"account.field.balance": "الرصيد",
"account.field.created_at": "أنشئت في",
"item.field.type": "نوع الصنف",
"item.field.type.inventory": "مخزون",
"item.field.type.service": "خدمة",
"item.field.type.non-inventory": "غير مخزون",
"item.field.name": "اسم الصنف",
"item.field.code": "رمز الصنف",
"item.field.sellable": "قابل للبيع",
"item.field.purchasable": "قابل للشراء",
"item.field.cost_price": "سعر التكلفة",
"item.field.cost_account": "حساب التكلفة",
"item.field.sell_account": "حساب البيع",
"item.field.sell_description": "وصف البيع",
"item.field.inventory_account": "حساب المخزون",
"item.field.purchase_description": "وصف الشراء",
"item.field.quantity_on_hand": "الكمية",
"item.field.note": "ملاحظة",
"item.field.category": "التصنيف",
"item.field.active": "Active",
"item.field.created_at": "أنشئت في",
"item_category.field.name": "الاسم",
"item_category.field.description": "الوصف",
"item_category.field.count": "العدد",
"item_category.field.created_at": "أنشئت في",
"invoice.field.customer": "الزبون",
"invoice.field.invoice_date": "تاريخ الفاتورة",
"invoice.field.due_date": "تاريخ الاستحقاق",
"invoice.field.invoice_no": "رقم الفاتورة",
"invoice.field.reference_no": "رقم الإشاري",
"invoice.field.invoice_message": "رسالة الفاتورة",
"invoice.field.terms_conditions": "الشروط والأحكام",
"invoice.field.amount": "القيمة",
"invoice.field.payment_amount": "القيمة المدفوعة",
"invoice.field.due_amount": "القيمة المستحقة",
"invoice.field.status": "الحالة",
"invoice.field.status.paid": "مدفوعة",
"invoice.field.status.partially-paid": "المدفوعة جزئيا",
"invoice.field.status.overdue": "متأخرة",
"invoice.field.status.unpaid": "غير مدفوعة",
"invoice.field.status.delivered": "تم تسليمها",
"invoice.field.status.draft": "مسودة",
"invoice.field.created_at": "أنشئت في",
"estimate.field.amount": "القيمة",
"estimate.field.estimate_number": "رقم العرض",
"estimate.field.customer": "الزبون",
"estimate.field.estimate_date": "تاريخ العرض",
"estimate.field.expiration_date": "تاريخ انتهاء الصلاحية",
"estimate.field.reference_no": "رقم الإشاري",
"estimate.field.note": "ملاحظة",
"estimate.field.terms_conditions": "الشروط والأحكام",
"estimate.field.status": "الحالة",
"estimate.field.status.delivered": "تم تسليمها",
"estimate.field.status.rejected": "مرفوضة",
"estimate.field.status.approved": "تم الموافقة",
"estimate.field.status.draft": "مسودة",
"estimate.field.created_at": "أنشئت في",
"payment_receive.field.customer": "الزبون",
"payment_receive.field.payment_date": "تاريخ الدفع",
"payment_receive.field.amount": "القيمة",
"payment_receive.field.reference_no": "رقم الإشاري",
"payment_receive.field.deposit_account": "حساب الإيداع",
"payment_receive.field.payment_receive_no": "رقم عملية الدفع",
"payment_receive.field.statement": "البيان",
"payment_receive.field.created_at": "أنشئت في",
"bill_payment.field.vendor": "المورد",
"bill_payment.field.amount": "القيمة",
"bill_payment.field.due_amount": "قيمة المستحقة",
"bill_payment.field.payment_account": "حساب الدفع",
"bill_payment.field.payment_number": "قيمة الدفع",
"bill_payment.field.payment_date": "تاريخ الدفع",
"bill_payment.field.reference_no": "رقم الإشاري",
"bill_payment.field.description": "الوصف",
"bill_payment.field.created_at": "أنشئت في",
"bill.field.vendor": "المورد",
"bill.field.bill_number": "رقم الفاتورة",
"bill.field.bill_date": "تاريخ الفاتورة",
"bill.field.due_date": "تاريخ الاستحقاق",
"bill.field.reference_no": "رقم الإشاري",
"bill.field.status": "الحالة",
"bill.field.status.paid": "مدفوعة",
"bill.field.status.partially-paid": "مدفوعة جزئيا",
"bill.field.status.unpaid": "غير مدفوعة",
"bill.field.status.opened": "مفتوحة",
"bill.field.status.draft": "مسودة",
"bill.field.status.overdue": "متأخرة",
"bill.field.amount": "القيمة",
"bill.field.payment_amount": "قيم الدفع",
"bill.field.note": "ملاحظة",
"bill.field.created_at": "أنشئت في",
"inventory_adjustment.field.date": "التاريخ",
"inventory_adjustment.field.type": "النوع",
"inventory_adjustment.field.type.increment": "زيادة",
"inventory_adjustment.field.type.decrement": "نقصان",
"inventory_adjustment.field.adjustment_account": "حساب التسوية",
"inventory_adjustment.field.reason": "السبب",
"inventory_adjustment.field.reference_no": "رقم الإشاري",
"inventory_adjustment.field.description": "الوصف",
"inventory_adjustment.field.published_at": "نشرت في",
"inventory_adjustment.field.created_at": "أنشئت في",
"expense.field.payment_date": "تاريخ الدفع",
"expense.field.payment_account": "حساب الدفع",
"expense.field.amount": "القيمة",
"expense.field.reference_no": "رقم الإشاري",
"expense.field.description": "الوصف",
"expense.field.published": "Published",
"expense.field.status": "الحالة",
"expense.field.status.draft": "مسودة",
"expense.field.status.published": "نشرت",
"expense.field.created_at": "أنشئت في",
"manual_journal.field.date": "التاريخ",
"manual_journal.field.journal_number": "رقم القيد",
"manual_journal.field.reference": "رقم الإشاري",
"manual_journal.field.journal_type": "نوع القيد",
"manual_journal.field.amount": "القيمة",
"manual_journal.field.description": "الوصف",
"manual_journal.field.status": "الحالة",
"manual_journal.field.created_at": "أنشئت في",
"receipt.field.amount": "القيمة",
"receipt.field.deposit_account": "حساب الإيداع",
"receipt.field.customer": "الزبون",
"receipt.field.receipt_date": "تاريخ الإيصال",
"receipt.field.receipt_number": "رقم الإيصال",
"receipt.field.reference_no": "رقم الإشاري",
"receipt.field.receipt_message": "رسالة الإيصال",
"receipt.field.statement": "البيان",
"receipt.field.created_at": "أنشئت في",
"receipt.field.status": "الحالة",
"receipt.field.status.draft": "مسودة",
"receipt.field.status.closed": "مغلقة",
"customer.field.first_name": "الاسم الأول",
"customer.field.last_name": "الاسم الاخير",
"customer.field.display_name": "اسم العرض",
"customer.field.email": "بريد الالكتروني",
"customer.field.work_phone": "هاتف عمل",
"customer.field.personal_phone": "هاتف شخصي",
"customer.field.company_name": "اسم الشركة",
"customer.field.website": "موقع الكتروني",
"customer.field.opening_balance_at": "الرصيد الافتتاحي في",
"customer.field.opening_balance": "الرصيد الافتتاحي",
"customer.field.created_at": "أنشئت في",
"customer.field.balance": "الرصيد",
"customer.field.status": "الحالة",
"customer.field.currency": "العملة",
"customer.field.status.active": "مفعل",
"customer.field.status.inactive": "غير مفعل",
"customer.field.status.overdue": "متأخر",
"customer.field.status.unpaid": "غير دافع",
"vendor.field.first_name": "الاسم الأول",
"vendor.field.last_name": "الاسم الاخير",
"vendor.field.display_name": "اسم العرض",
"vendor.field.email": "بريد الالكتروني",
"vendor.field.work_phone": "هاتف عمل",
"vendor.field.personal_phone": "هاتف شخصي",
"vendor.field.company_name": "اسم الشركة",
"vendor.field.website": "موقع الكتروني",
"vendor.field.opening_balance_at": "الرصيد الافتتاحي في",
"vendor.field.opening_balance": "الرصيد الافتتاحي",
"vendor.field.created_at": "أنشئت في",
"vendor.field.balance": "الرصيد",
"vendor.field.status": "الحالة",
"vendor.field.currency": "العملة",
"vendor.field.status.active": "مفعل",
"vendor.field.status.inactive": "غير مفعل",
"vendor.field.status.overdue": "متأخر",
"vendor.field.status.unpaid": "غير دافع",
"Invoice write-off": "شطب فاتورة",
"transaction_type.credit_note": "اشعار دائن",
"transaction_type.refund_credit_note": "استرجاع اموال اشعار دائن",
"transaction_type.vendor_credit": "اشعار مدين",
"transaction_type.refund_vendor_credit": "استرجاع اموال اشعار مدين",
"transaction_type.landed_cost": "تحميل تكلفة",
"sms_notification.invoice_details.label": "تفاصيل فاتورة البيع ",
"sms_notification.invoice_reminder.label": "تذكير بفاتورة البيع ",
"sms_notification.receipt_details.label": "تفاصيل إيصال البيع ",
"sms_notification.sale_estimate_details.label": "تفاصيل فاتورة عرض اسعار ",
"sms_notification.payment_receive_details.label": "تفاصيل سند الزبون",
"sms_notification.customer_balance.label": "رصيد الزبون",
"sms_notification.invoice_details.description": "سيتم إرسال إشعار عبر الرسائل القصيرة إلى العميل بمجرد إنشاء الفاتورة ونشرها أو عند إشعار العميل عبر رسالة نصية قصيرة بالفاتورة. ",
"sms_notification.payment_receive.description": "سيتم إرسال إشعار رسالة شكر للدفع إلى العميل بمجرد إنشاء الدفعة ونشرها أو إشعار العميل بالدفع يدويًا. ",
"sms_notification.receipt_details.description": "سيتم إرسال إشعار عبر الرسائل القصيرة إلى العميل بمجرد إنشاء ونشر الإيصال أو عند إشعار العميل بالإيصال يدويًا.",
"sms_notification.customer_balance.description": "إرسال رسالة نصية قصيرة إشعار العملاء برصيدهم الحالي المستحق. ",
"sms_notification.estimate_details.description": "سيتم إرسال إشعار عبر الرسائل القصيرة إلى عميلك بمجرد نشر العرض أو إشعار العميل بالعرض يدويًا.",
"sms_notification.invoice_reminder.description": "سيتم ارسال إشعار SMS لتذكير الزبون بالدفع باكراً ، سواء ارسال بشكل تلقائي او يدوي.",
"sms_notification.customer_balance.default_message": "عزيزي {CustomerName} ، هذا تذكير بشأن رصيد الحالي المستحق {Balance} ، يُرجى الدفع في أقرب وقت ممكن. - {CompanyName}",
"sms_notification.payment_receive.default_message": "مرحبًا {CustomerName} ، تم القبض بقيمة {Amount} للفاتورة - {InvoiceNumber}. نحن نتطلع إلى خدمتك مرة أخرى. شكرا لك. - {CompanyName}",
"sms_notification.estimate.default_message": "مرحبًا , {CustomerName} ، تم أنشاء فاتورة عرض اسعار - {EstimateNumber} لك. يرجى إلقاء نظرة وقبوله للمضي قدما. بانتظار ردك. - {CompanyName}",
"sms_notification.invoice_details.default_message": "مرحبًا {CustomerName}, لديك مبلغ مستحق قدره {DueAmount} للفاتورة {InvoiceNumber}. - {CompanyName}",
"sms_notification.receipt_details.default_message": "مرحبًا {CustomerName} ، لقد تم إنشاء إيصال - {ReceiptNumber} من أجلك. نتطلع إلى خدمتك مرة أخرى. شكرًا لك - {CompanyName}",
"sms_notification.invoice_reminder.default_message": "عزيزي {CustomerName} ، يرجي سداد فاتورة - {InvoiceNumber} المستحقة. يرجى الدفع قبل تاريخ {DueDate}. شكرا لك. - {CompanyName}",
"module.sale_invoices.label": "فواتير البيع",
"module.sale_receipts.label": "إيصالات البيع",
"module.sale_estimates.label": "فاتورة عرض اسعار ",
"module.payment_receives.label": "سندات الزبائن ",
"module.customers.label": "العملاء",
"sms_notification.invoice.var.invoice_number": "يشير إلى رقم الفاتورة.",
"sms_notification.invoice.var.reference_number": "يشير إلى رقم إشاري للفاتورة.",
"sms_notification.invoice.var.customer_name": "يشير إلى اسم العميل الفاتورة",
"sms_notification.invoice.var.due_amount": "يشير إلى مبلغ الفاتورة المستحق",
"sms_notification.invoice.var.amount": "يشير إلى مبلغ الفاتورة.",
"sms_notification.invoice.var.company_name": "يشير إلي اسم الشركة.",
"sms_notification.invoice.var.due_date": "يشير إلي تاريخ استحقاق الفاتورة.",
"sms_notification.receipt.var.receipt_number": "يشير إلى رقم الإيصال.",
"sms_notification.receipt.var.reference_number": "يشير إلى رقم الإشاري للإيصال.",
"sms_notification.receipt.var.customer_name": "يشير إلى اسم العميل الإيصال.",
"sms_notification.receipt.var.amount": "يشير إلى مبلغ الإيصال. ",
"sms_notification.receipt.var.company_name": "يشير إلي اسم الشركة.",
"sms_notification.payment.var.payment_number": "يشير إلى رقم معاملة الدفع.",
"sms_notification.payment.var.reference_number": "يشير إلى رقم الإشاري لعملية الدفع ",
"sms_notification.payment.var.customer_name": "يشير إلى اسم العميل الدفع",
"sms_notification.payment.var.amount": "يشير إلى مبلغ معاملة الدفع.",
"sms_notification.payment.company_name": "يشير إلي اسم الشركة.",
"sms_notification.payment.var.invoice_number": "يشير إلي رقم فاتورة التي تم دفعها.",
"sms_notification.estimate.var.estimate_number": "يشير إلى رقم فاتورة عرض اسعار.",
"sms_notification.estimate.var.reference_number": "يشير إلى رقم الإشاري لفاتورة عرض اسعار.",
"sms_notification.estimate.var.customer_name": "يشير إلى اسم العميل الفاتورة",
"sms_notification.estimate.var.amount": "يشير إلى قيمة الفاتورة",
"sms_notification.estimate.var.company_name": "يشير إلي اسم الشركة.",
"sms_notification.estimate.var.expiration_date": "يشير إلي تاريخ الصلاحية الفاتورة.",
"sms_notification.estimate.var.estimate_date": "يشير إلي تاريخ الفاتورة.",
"sms_notification.customer.var.customer_name": "يشير إلي اسم الزبون",
"sms_notification.customer.var.balance": "يشير إلي رصيد زبون المستحق.",
"sms_notification.customer.var.company_name": "يشير إلي اسم الشركة.",
"ability.accounts": "شجرة الحسابات",
"ability.manual_journal": "القيود اليدوية",
"ability.cashflow": "التدفقات النقدية",
"ability.inventory_adjustment": "تسويات المخزون",
"ability.customers": "الزبائن",
"ability.vendors": "الموردين",
"ability.sale_estimates": "فواتير عرض الاسعار",
"ability.sale_invoices": "فواتير البيع",
"ability.sale_receipts": "إيصالات البيع",
"ability.expenses": "المصاريف",
"ability.payments_receive": "سندات الزبائن",
"ability.purchase_invoices": "فواتير الشراء",
"ability.all_reports": "كل التقارير",
"ability.payments_made": "سندات الموردين",
"ability.preferences": "التفضيلات",
"ability.mutate_system_preferences": "تعديل تفضيلات النظام.",
"ability.items": "الأصناف",
"ability.view": "عرض",
"ability.create": "إضافة",
"ability.edit": "تعديل",
"ability.delete": "حذف",
"ability.transactions_locking": "إمكانية اغلاق المعاملات.",
"ability.balance_sheet_report": "ميزانية العمومية",
"ability.profit_loss_sheet": "قائمة الدخل",
"ability.journal": "اليومية العامة",
"ability.general_ledger": "دفتر الأستاذ العام",
"ability.cashflow_report": "تقرير التدفقات النقدية",
"ability.AR_aging_summary_report": "ملخص اعمار الديون للذمم المدينة",
"ability.AP_aging_summary_report": "ملخص اعمار الديون للذمم الدائنة",
"ability.purchases_by_items": "المشتريات حسب المنتجات",
"ability.sales_by_items_report": "المبيعات حسب المنتجات",
"ability.customers_transactions_report": "معاملات الزبائن",
"ability.vendors_transactions_report": "معاملات الموردين",
"ability.customers_summary_balance_report": "ملخص أرصدة الزبائن",
"ability.vendors_summary_balance_report": "ملخص أرصدة الموردين",
"ability.inventory_valuation_summary": "ملخص تقييم المخزون",
"ability.inventory_items_details": "تفاصيل منتج المخزون",
"vendor_credit.field.vendor": "المورد",
"vendor_credit.field.amount": "القيمة",
"vendor_credit.field.currency_code": "العملة",
"vendor_credit.field.credit_date": "تاريخ الاشعار",
"vendor_credit.field.credit_number": "رقم الاشعار",
"vendor_credit.field.note": "ملاحظة",
"vendor_credit.field.created_at": "أنشئت في",
"vendor_credit.field.reference_no": "رقم الإشاري",
"vendor_credit.field.status": "الحالة",
"vendor_credit.field.status.draft": "مسودة",
"vendor_credit.field.status.published": "تم نشرها",
"vendor_credit.field.status.open": "مفتوحة",
"vendor_credit.field.status.closed": "مغلقة",
"credit_note.field.terms_conditions": "الشروط والاحكام",
"credit_note.field.note": "ملاحظة",
"credit_note.field.currency_code": "العملة",
"credit_note.field.created_at": "أنشئت في",
"credit_note.field.amount": "القيمة",
"credit_note.field.credit_note_number": "رقم الاشعار",
"credit_note.field.credit_note_date": "تاريخ الاشعار",
"credit_note.field.customer": "الزبون",
"credit_note.field.reference_no": "رقم الإشاري",
"credit_note.field.status": "الحالة",
"credit_note.field.status.draft": "مسودة",
"credit_note.field.status.published": "تم نشرها",
"credit_note.field.status.open": "مفتوحة",
"credit_note.field.status.closed": "مغلقة",
"transactions_locking.module.sales.label": "المبيعات",
"transactions_locking.module.purchases.label": "المشتريات",
"transactions_locking.module.financial.label": "المالية",
"transactions_locking.module.all_transactions": "كل المعاملات",
"transactions_locking.module.sales.desc": "فواتير البيع ، والإيصالات ، والإشعارات الدائنة ، واستلام مدفوعات الزبائن ، والأرصدة الافتتاحية للزبائن.",
"transactions_locking.module.purchases.desc": "فواتير الشراء ومدفوعات الموردين وإشعارات المدينة والأرصدة الافتتاحية للموردين.",
"transactions_locking.module.financial.desc": "القيود اليدوية والمصروفات وتسويات المخزون.",
"inventory_adjustment.type.increment": "زيادة",
"inventory_adjustment.type.decrement": "نقصان",
"customer.type.individual": "فرد",
"customer.type.business": "اعمال",
"credit_note.view.draft": "مسودة",
"credit_note.view.closed": "مغلقة",
"credit_note.view.open": "مفتوحة",
"credit_note.view.published": "نشرت",
"vendor_credit.view.draft": "مسودة",
"vendor_credit.view.closed": "مغلقة",
"vendor_credit.view.open": "مفتوحة",
"vendor_credit.view.published": "نشرت",
"allocation_method.value.label": "القيمة",
"allocation_method.quantity.label": "الكمية",
"balance_sheet.assets": "الأصول",
"balance_sheet.current_asset": "الأصول المتداولة",
"balance_sheet.cash_and_cash_equivalents": "النقدية وما يعادلها",
"balance_sheet.accounts_receivable": "الذمم المدينة",
"balance_sheet.inventory": "المخزون",
"balance_sheet.other_current_assets": "اصول متداولة اخرى",
"balance_sheet.fixed_asset": "الأصول الثابتة",
"balance_sheet.non_current_assets": "الاصول غير المتداولة",
"balance_sheet.liabilities_and_equity": "الالتزامات وحقوق الملكية",
"balance_sheet.liabilities": "الإلتزامات",
"balance_sheet.current_liabilties": "الالتزامات المتداولة",
"balance_sheet.long_term_liabilities": "الالتزامات طويلة الاجل",
"balance_sheet.non_current_liabilities": "الالتزامات غير المتداولة",
"balance_sheet.equity": "حقوق الملكية",
"balance_sheet.account_name": "اسم الحساب",
"balance_sheet.total": "إجمالي",
"balance_sheet.percentage_of_column": "٪ التغير العمودي",
"balance_sheet.percentage_of_row": "٪ التغير الأفقي",
"financial_sheet.previoud_period_date": "(ف.س) {{date}}",
"fianncial_sheet.previous_period_change": "التغيرات (ف.س)",
"financial_sheet.previous_period_percentage": "٪ التغير (ف.س)",
"financial_sheet.previous_year_date": "(س.س) {{date}}",
"financial_sheet.previous_year_change": "التغيرات (س.س)",
"financial_sheet.previous_year_percentage": "٪ التغير (س.س)",
"financial_sheet.total_row": "إجمالي {{value}}",
"profit_loss_sheet.income": "الإيرادات",
"profit_loss_sheet.cost_of_sales": "تكلفة المبيعات",
"profit_loss_sheet.gross_profit": "إجمالي الدخل",
"profit_loss_sheet.expenses": "المصروفات",
"profit_loss_sheet.net_operating_income": "صافي الدخل التشغيلي",
"profit_loss_sheet.other_income": "إيرادات اخري",
"profit_loss_sheet.other_expenses": "مصاريف اخري",
"profit_loss_sheet.net_income": "صافي الدخل",
"profit_loss_sheet.account_name": "اسم الحساب",
"profit_loss_sheet.total": "إجمالي",
"profit_loss_sheet.percentage_of_income": "٪ التغير في الإيرادات",
"profit_loss_sheet.percentage_of_expenses": "٪ التغير في المصاريف",
"profit_loss_sheet.percentage_of_column": "٪ التغير العمودي",
"profit_loss_sheet.percentage_of_row": "٪ التغير الأفقي",
"warehouses.primary_warehouse": "المستودع الرئيسي",
"branches.head_branch": "الفرع الرئيسي",
"account.accounts_payable.currency": "الذمم الدائنة - {{currency}}",
"account.accounts_receivable.currency": "الذمم المدينة - {{currency}}",
"role.admin.name": "الادارة",
"role.admin.desc": "وصول غير مقيد لجميع الوحدات.",
"role.staff.name": "العاملين",
"role.staff.desc": "الوصول إلى جميع الوحدات باستثناء التقارير والإعدادات والمحاسبة.",
"warehouse_transfer.view.draft.name": "مسودة",
"warehouse_transfer.view.in_transit.name": "في النقل",
"warehouse_transfer.view.transferred.name": "تم النقل"
}

View File

@@ -1,642 +0,0 @@
{
"Petty Cash": "Petty Cash",
"Cash": "Cash",
"Bank": "Bank",
"Other Income": "Other Income",
"Interest Income": "Interest Income",
"Depreciation Expense": "Depreciation Expense",
"Interest Expense": "Interest Expense",
"Sales of Product Income": "Sales of Product Income",
"Inventory Asset": "Inventory Asset",
"Cost of Goods Sold (COGS)": "Cost of Goods Sold (COGS)",
"Cost of Goods Sold": "Cost of Goods Sold",
"Accounts Payable": "Accounts Payable",
"Other Expense": "Other Expense",
"Payroll Expenses": "Payroll Expenses",
"Fixed Asset": "Fixed Asset",
"Credit Card": "Credit Card",
"Non-Current Asset": "Non-Current Asset",
"Current Asset": "Current Asset",
"Other Asset": "Other Asset",
"Long Term Liability": "Long Term Liability",
"Current Liability": "Current Liability",
"Other Liability": "Other Liability",
"Equity": "Equity",
"Expense": "Expense",
"Income": "Income",
"Accounts Receivable (A/R)": "Accounts Receivable (A/R)",
"Accounts Receivable": "Accounts Receivable",
"Accounts Payable (A/P)": "Accounts Payable (A/P)",
"Inactive": "Inactive",
"Other Current Asset": "Other Current Asset",
"Tax Payable": "Tax Payable",
"Other Current Liability": "Other Current Liability",
"Non-Current Liability": "Non-Current Liability",
"Assets": "Assets",
"Liabilities": "Liabilities",
"Account name": "Account name",
"Account type": "Account type",
"Account normal": "Account normal",
"Description": "Description",
"Account code": "Account code",
"Currency": "Currency",
"Balance": "Balance",
"Active": "Active",
"Created at": "Created at",
"fixed_asset": "Fixed asset",
"Journal": "Journal",
"Reconciliation": "Reconciliation",
"Credit": "Credit",
"Debit": "Debit",
"Interest": "Interest",
"Depreciation": "Depreciation",
"Payroll": "Payroll",
"Type": "Type",
"Name": "Name",
"Sellable": "Sellable",
"Purchasable": "Purchasable",
"Sell price": "Sell price",
"Cost price": "Cost price",
"User": "User",
"Category": "Category",
"Note": "Note",
"Quantity on hand": "Quantity on hand",
"Quantity": "Quantity",
"Purchase description": "Purchase description",
"Sell description": "Sell description",
"Sell account": "Sell account",
"Cost account": "Cost account",
"Inventory account": "Inventory account",
"Payment date": "Payment date",
"Payment account": "Payment account",
"Amount": "Amount",
"Reference No.": "Reference No.",
"Journal number": "Journal number",
"Status": "Status",
"Journal type": "Journal type",
"Date": "Date",
"Asset": "Asset",
"Liability": "Liability",
"First-in first-out (FIFO)": "First-in first-out (FIFO)",
"Last-in first-out (LIFO)": "Last-in first-out (LIFO)",
"Average rate": "Average rate",
"Total": "Total",
"Transaction type": "Transaction type",
"Transaction #": "Transaction #",
"Running Value": "Running Value",
"Running quantity": "Running quantity",
"Profit Margin": "Profit Margin",
"Value": "Value",
"Rate": "Rate",
"OPERATING ACTIVITIES": "OPERATING ACTIVITIES",
"FINANCIAL ACTIVITIES": "FINANCIAL ACTIVITIES",
"Net income": "Net income",
"Adjustments net income by operating activities.": "Adjustments net income by operating activities.",
"Net cash provided by operating activities": "Net cash provided by operating activities",
"Net cash provided by investing activities": "Net cash provided by investing activities",
"Net cash provided by financing activities": "Net cash provided by financing activities",
"Cash at beginning of period": "Cash at beginning of period",
"NET CASH INCREASE FOR PERIOD": "NET CASH INCREASE FOR PERIOD",
"CASH AT END OF PERIOD": "CASH AT END OF PERIOD",
"Expenses": "Expenses",
"Services": "Services",
"Inventory": "Inventory",
"Non Inventory": "Non Inventory",
"Draft": "Draft",
"Published": "Published",
"Delivered": "Delivered",
"Overdue": "Overdue",
"Partially paid": "Partially paid",
"Paid": "Paid",
"Opened": "Opened",
"Unpaid": "Unpaid",
"Approved": "Approved",
"Rejected": "Rejected",
"Invoiced": "Invoiced",
"Expired": "Expired",
"Closed": "Closed",
"Manual journal": "Manual journal",
"Owner contribution": "Owner contribution",
"Transfer to account": "Transfer to account",
"Transfer from account": "Transfer from account",
"Other income": "Other income",
"Other expense": "Other expense",
"Owner drawing": "Owner drawing",
"Inventory adjustment": "Inventory adjustment",
"Customer opening balance": "Customer opening balance",
"Vendor opening balance": "Vendor opening balance",
"Payment made": "Payment made",
"Bill": "Bill",
"Payment receive": "Payment receive",
"Sale receipt": "Sale receipt",
"Sale invoice": "Sale invoice",
"Bank Account": "Bank Account",
"Saving Bank Account": "Saving Bank Account",
"Undeposited Funds": "Undeposited Funds",
"Computer Equipment": "Computer Equipment",
"Office Equipment": "Office Equipment",
"Uncategorized Income": "Uncategorized Income",
"Sales of Service Income": "Sales of Service Income",
"Bank Fees and Charges": "Bank Fees and Charges",
"Exchange Gain or Loss": "Exchange Gain or Loss",
"Rent": "Rent",
"Office expenses": "Office expenses",
"Other Expenses": "Other Expenses",
"Drawings": "Drawings",
"Owner's Equity": "Owner's Equity",
"Opening Balance Equity": "Opening Balance Equity",
"Retained Earnings": "Retained Earnings",
"Sales Tax Payable": "Sales Tax Payable",
"Revenue Received in Advance": "Revenue Received in Advance",
"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.",
"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.",
"Cash and cash equivalents": "Cash and cash equivalents",
"Inventories": "Inventories",
"Other current assets": "Other current assets",
"Non-Current Assets": "Non-Current Assets",
"Current Liabilties": "Current Liabilties",
"Long-Term Liabilities": "Long-Term Liabilities",
"Non-Current Liabilities": "Non-Current Liabilities",
"Liabilities and Equity": "Liabilities and Equity",
"Closing balance": "Closing balance",
"Opening Balance": "Opening balance",
"Total {{accountName}}": "Total {{accountName}}",
"invoice.paper.invoice": "Invoice",
"invoice.paper.invoice_amount": "Invoice amount",
"invoice.paper.due_amount": "Due amount",
"invoice.paper.billed_to": "Billed to",
"invoice.paper.invoice_date": "Invoice date",
"invoice.paper.invoice_number": "Invoice No.",
"invoice.paper.due_date": "Due date",
"invoice.paper.conditions_title": "Conditions & terms",
"invoice.paper.notes_title": "Notes",
"invoice.paper.total": "Total",
"invoice.paper.payment_amount": "Payment Amount",
"invoice.paper.balance_due": "Balance Due",
"item_entry.paper.item_name": "Item name",
"item_entry.paper.rate": "Rate",
"item_entry.paper.quantity": "Quantity",
"item_entry.paper.total": "Total",
"estimate.paper.estimate": "Estimate",
"estimate.paper.estimate_amount": "Estimate amount",
"estimate.paper.billed_to": "Billed to",
"estimate.paper.estimate_date": "Estimate date",
"estimate.paper.estimate_number": "Estimate number",
"estimate.paper.expiration_date": "Expiration date",
"estimate.paper.conditions_title": "Conditions & terms",
"estimate.paper.notes_title": "Notes",
"estimate.paper.amount": "Estimate amount",
"estimate.paper.subtotal": "Subtotal",
"estimate.paper.total": "Total",
"receipt.paper.receipt": "Receipt",
"receipt.paper.billed_to": "Billed to",
"receipt.paper.receipt_date": "Receipt date",
"receipt.paper.receipt_number": "Receipt number",
"receipt.paper.expiration_date": "Expiration date",
"receipt.paper.conditions_title": "Conditions & terms",
"receipt.paper.notes": "Notes",
"receipt.paper.statement": "Statement",
"receipt.paper.receipt_amount": "Receipt amount",
"receipt.paper.total": "Total",
"receipt.paper.balance_due": "Balance Due",
"receipt.paper.payment_amount": "Payment Amount",
"credit.paper.credit_note": "Credit Note",
"credit.paper.remaining": "Credit remaining",
"credit.paper.amount": "Credit amount",
"credit.paper.billed_to": "Bill to",
"credit.paper.credit_date": "Credit date",
"credit.paper.total": "Total",
"credit.paper.credits_used": "Credits used",
"credit.paper.credits_remaining": "Credits remaining",
"credit.paper.conditions_title": "Conditions & terms",
"credit.paper.notes": "Notes",
"payment.paper.payment_receipt": "Payment Receipt",
"payment.paper.amount_received": "Amount received",
"payment.paper.billed_to": "Billed to",
"payment.paper.payment_date": "Payment date",
"payment.paper.invoice_number": "Invoice number",
"payment.paper.invoice_date": "Invoice date",
"payment.paper.invoice_amount": "Invoice amount",
"payment.paper.payment_amount": "Payment amount",
"payment.paper.balance_due": "Balance Due",
"payment.paper.statement": "Statement",
"account.field.name": "Account name",
"account.field.description": "Description",
"account.field.slug": "Account slug",
"account.field.code": "Account code",
"account.field.root_type": "Root type",
"account.field.normal": "Account normal",
"account.field.normal.credit": "Credit",
"account.field.normal.debit": "Debit",
"account.field.type": "Type",
"account.field.active": "Activity",
"account.field.balance": "Balance",
"account.field.created_at": "Created at",
"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": "Name",
"item.field.code": "Code",
"item.field.sellable": "Sellable",
"item.field.purchasable": "Purchasable",
"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_category.field.name": "Name",
"item_category.field.description": "Description",
"item_category.field.count": "Count",
"item_category.field.created_at": "Created at",
"invoice.field.customer": "Customer",
"invoice.field.invoice_date": "Invoice date",
"invoice.field.due_date": "Due date",
"invoice.field.invoice_no": "Invoice No.",
"invoice.field.reference_no": "Reference No.",
"invoice.field.invoice_message": "Invoice message",
"invoice.field.terms_conditions": "Terms & conditions",
"invoice.field.amount": "Amount",
"invoice.field.payment_amount": "Payment amount",
"invoice.field.due_amount": "Due amount",
"invoice.field.status": "Status",
"invoice.field.status.paid": "Paid",
"invoice.field.status.partially-paid": "Partially paid",
"invoice.field.status.overdue": "Overdue",
"invoice.field.status.unpaid": "Unpaid",
"invoice.field.status.delivered": "Delivered",
"invoice.field.status.draft": "Draft",
"invoice.field.created_at": "Created at",
"estimate.field.amount": "Amount",
"estimate.field.estimate_number": "Estimate number",
"estimate.field.customer": "Customer",
"estimate.field.estimate_date": "Estimate date",
"estimate.field.expiration_date": "Expiration date",
"estimate.field.reference_no": "Reference No.",
"estimate.field.note": "Note",
"estimate.field.terms_conditions": "Terms & conditions",
"estimate.field.status": "Status",
"estimate.field.status.delivered": "Delivered",
"estimate.field.status.rejected": "Rejected",
"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",
"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 number",
"bill_payment.field.payment_date": "Payment date",
"bill_payment.field.reference_no": "Reference No.",
"bill_payment.field.description": "Description",
"bill_payment.field.created_at": "Created at",
"bill.field.vendor": "Vendor",
"bill.field.bill_number": "Bill number",
"bill.field.bill_date": "Bill date",
"bill.field.due_date": "Due date",
"bill.field.reference_no": "Reference No.",
"bill.field.status": "Status",
"bill.field.status.paid": "Paid",
"bill.field.status.partially-paid": "Partially paid",
"bill.field.status.unpaid": "Unpaid",
"bill.field.status.opened": "Opened",
"bill.field.status.draft": "Draft",
"bill.field.status.overdue": "overdue",
"bill.field.amount": "Amount",
"bill.field.payment_amount": "Payment amount",
"bill.field.note": "Note",
"bill.field.created_at": "Created at",
"inventory_adjustment.field.date": "Date",
"inventory_adjustment.field.type": "Type",
"inventory_adjustment.field.type.increment": "Increment",
"inventory_adjustment.field.type.decrement": "Decrement",
"inventory_adjustment.field.adjustment_account": "Adjustment account",
"inventory_adjustment.field.reason": "Reason",
"inventory_adjustment.field.reference_no": "Reference No.",
"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.amount": "Amount",
"expense.field.reference_no": "Reference No.",
"expense.field.description": "Description",
"expense.field.published": "Published",
"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 number",
"manual_journal.field.reference": "Reference No.",
"manual_journal.field.journal_type": "Journal type",
"manual_journal.field.amount": "Amount",
"manual_journal.field.description": "Description",
"manual_journal.field.status": "Status",
"manual_journal.field.created_at": "Created at",
"receipt.field.amount": "Amount",
"receipt.field.deposit_account": "Deposit account",
"receipt.field.customer": "Customer",
"receipt.field.receipt_date": "Receipt date",
"receipt.field.receipt_number": "Receipt number",
"receipt.field.reference_no": "Reference No.",
"receipt.field.receipt_message": "Receipt message",
"receipt.field.statement": "Statement",
"receipt.field.created_at": "Created at",
"receipt.field.status": "Status",
"receipt.field.status.draft": "Draft",
"receipt.field.status.closed": "Closed",
"customer.field.first_name": "First name",
"customer.field.last_name": "Last name",
"customer.field.display_name": "Display name",
"customer.field.email": "Email",
"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",
"customer.field.opening_balance": "Opening balance",
"customer.field.created_at": "Created at",
"customer.field.balance": "Balance",
"customer.field.status": "Status",
"customer.field.currency": "Curreny",
"customer.field.status.active": "Active",
"customer.field.status.inactive": "Inactive",
"customer.field.status.overdue": "Overdue",
"customer.field.status.unpaid": "Unpaid",
"vendor.field.first_name": "First name",
"vendor.field.last_name": "Last name",
"vendor.field.display_name": "Display name",
"vendor.field.email": "Email",
"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",
"vendor.field.opening_balance": "Opening balance",
"vendor.field.created_at": "Created at",
"vendor.field.balance": "Balance",
"vendor.field.status": "Status",
"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",
"transaction_type.refund_vendor_credit": "Refund vendor credit",
"transaction_type.landed_cost": "Landed cost",
"sms_notification.invoice_details.label": "Sale invoice details",
"sms_notification.invoice_reminder.label": "Sale invoice reminder",
"sms_notification.receipt_details.label": "Sale receipt details",
"sms_notification.sale_estimate_details.label": "Sale estimate details",
"sms_notification.payment_receive_details.label": "Payment receive details",
"sms_notification.customer_balance.label": "Customer balance",
"sms_notification.invoice_details.description": "SMS notification will be sent to your customer once invoice created and published or when notify customer via SMS about the invoice.",
"sms_notification.payment_receive.description": "Payment thank you message notification will be sent to customer once the payment created and published or notify customer about payment manually.",
"sms_notification.receipt_details.description": "SMS notification will be sent to your cusotmer once receipt created and published or when notify customer about the receipt manually.",
"sms_notification.customer_balance.description": "Send SMS to notify customers about their current outstanding balance.",
"sms_notification.estimate_details.description": "SMS notification will be sent to your customer once estimate publish or notify customer about estimate manually.",
"sms_notification.invoice_reminder.description": "SMS notification will be sent to remind the customer to pay earliest, either automatically or manually.",
"sms_notification.customer_balance.default_message": "Dear {CustomerName}, This is reminder about your current outstanding balance of {Balance}, Please pay at the earliest. - {CompanyName}",
"sms_notification.payment_receive.default_message": "'Hi, {CustomerName}, We have received your payment for the invoice - {InvoiceNumber}. We look forward to serving you again. Thank you. - {CompanyName}'",
"sms_notification.estimate.default_message": "Hi, {CustomerName}, We have created an estimate - {EstimateNumber} for you. Please take a look and accept it to proceed further. Looking forward to hearing from you. - {CompanyName}",
"sms_notification.invoice_details.default_message": "Hi, {CustomerName}, You have an outstanding amount of {DueAmount} for the invoice {InvoiceNumber}. - {CompanyName}",
"sms_notification.receipt_details.default_message": "Hi, {CustomerName}, We have created receipt - {ReceiptNumber} for you. we look forward to serveing you again. Thank your - {CompanyName}",
"sms_notification.invoice_reminder.default_message": "Dear {CustomerName}, The payment towards the invoice - {InvoiceNumber} is due. Please pay before {DueDate}. Thank you. - {CompanyName}",
"module.sale_invoices.label": "Sale invoices",
"module.sale_receipts.label": "Sale receipts",
"module.sale_estimates.label": "Sale estimates",
"module.payment_receives.label": "Payment receive",
"module.customers.label": "Customers",
"sms_notification.invoice.var.invoice_number": "References to invoice number.",
"sms_notification.invoice.var.reference_number": "References to invoice reference number.",
"sms_notification.invoice.var.customer_name": "References to invoice customer name.",
"sms_notification.invoice.var.due_amount": "References to invoice due amount.",
"sms_notification.invoice.var.amount": "References to invoice amount.",
"sms_notification.invoice.var.company_name": "References to company name.",
"sms_notification.invoice.var.due_date": "References to invoice due date.",
"sms_notification.receipt.var.receipt_number": "References to receipt number.",
"sms_notification.receipt.var.reference_number": "References to receipt reference number.",
"sms_notification.receipt.var.customer_name": "References to receipt customer name.",
"sms_notification.receipt.var.amount": "References to receipt amount.",
"sms_notification.receipt.var.company_name": "References to company name.",
"sms_notification.payment.var.payment_number": "References to payment transaction number.",
"sms_notification.payment.var.reference_number": "References to payment reference number",
"sms_notification.payment.var.customer_name": "References to payment customer name.",
"sms_notification.payment.var.amount": "References to payment transaction amount.",
"sms_notification.payment.company_name": "References to company name",
"sms_notification.payment.var.invoice_number": "Reference to payment invoice number.",
"sms_notification.estimate.var.estimate_number": "References to estimate number.",
"sms_notification.estimate.var.reference_number": "References to estimate reference number.",
"sms_notification.estimate.var.customer_name": "References to estimate customer name.",
"sms_notification.estimate.var.amount": "References to estimate amount.",
"sms_notification.estimate.var.company_name": "References to company name.",
"sms_notification.estimate.var.expiration_date": "References to estimate expirtaion date.",
"sms_notification.estimate.var.estimate_date": "References to estimate date.",
"sms_notification.customer.var.customer_name": "References to customer name.",
"sms_notification.customer.var.balance": "References to customer outstanding balance.",
"sms_notification.customer.var.company_name": "References to company name.",
"ability.accounts": "Chart of accounts",
"ability.manual_journal": "Manual journals",
"ability.cashflow": "Cash flow",
"ability.inventory_adjustment": "Inventory adjustments",
"ability.customers": "Customers",
"ability.vendors": "vendors",
"ability.sale_estimates": "Sale estimates",
"ability.sale_invoices": "Sale invoices",
"ability.sale_receipts": "Sale receipts",
"ability.expenses": "Expenses",
"ability.payments_receive": "Payments receive",
"ability.purchase_invoices": "Purchase invoices",
"ability.all_reports": "All reports",
"ability.payments_made": "Payments made",
"ability.preferences": "Preferences",
"ability.mutate_system_preferences": "Mutate the system preferences.",
"ability.items": "Items",
"ability.view": "View",
"ability.create": "Create",
"ability.edit": "Edit",
"ability.delete": "Delete",
"ability.transactions_locking": "Ability to transactions locking.",
"ability.balance_sheet_report": "Balance sheet.",
"ability.profit_loss_sheet": "Profit/loss sheet",
"ability.journal": "Journal",
"ability.general_ledger": "General ledger",
"ability.cashflow_report": "Cashflow",
"ability.AR_aging_summary_report": "A/R aging summary",
"ability.AP_aging_summary_report": "A/P aging summary",
"ability.purchases_by_items": "Purchases by items",
"ability.sales_by_items_report": "Sales by items",
"ability.customers_transactions_report": "Customers transactions",
"ability.vendors_transactions_report": "Vendors transactions",
"ability.customers_summary_balance_report": "Customers summary balance",
"ability.vendors_summary_balance_report": "Vendors summary balance",
"ability.inventory_valuation_summary": "Inventory valuation summary",
"ability.inventory_items_details": "Inventory items details",
"vendor_credit.field.vendor": "Vendor name",
"vendor_credit.field.amount": "Amount",
"vendor_credit.field.currency_code": "Currency code",
"vendor_credit.field.credit_date": "Credit date",
"vendor_credit.field.credit_number": "Credit number",
"vendor_credit.field.note": "Note",
"vendor_credit.field.created_at": "Created at",
"vendor_credit.field.reference_no": "Reference No.",
"credit_note.field.terms_conditions": "Terms and conditions",
"credit_note.field.note": "Note",
"credit_note.field.currency_code": "Currency code",
"credit_note.field.created_at": "Created at",
"credit_note.field.amount": "Amount",
"credit_note.field.credit_note_number": "Credit note number",
"credit_note.field.credit_note_date": "Credit date",
"credit_note.field.customer": "Customer",
"credit_note.field.reference_no": "Reference No.",
"Credit note": "Credit note",
"Vendor credit": "Vendor credit",
"Refund credit note": "Refund credit note",
"Refund vendor credit": "Refund vendor credit",
"credit_note.field.status": "Status",
"credit_note.field.status.draft": "Draft",
"credit_note.field.status.published": "Published",
"credit_note.field.status.open": "Open",
"credit_note.field.status.closed": "Closed",
"transactions_locking.module.sales.label": "Sales",
"transactions_locking.module.purchases.label": "Purchases",
"transactions_locking.module.financial.label": "Financial",
"transactions_locking.module.all_transactions": "All transactions",
"transactions_locking.module.sales.desc": "Sale invoices, Receipts, credit notes, customers payment receive and customers opening balances.",
"transactions_locking.module.purchases.desc": "Purchase invoices, vendors payments, vendor credit notes and vendors opening balances.",
"transactions_locking.module.financial.desc": "Manual journal, expenses and inventory adjustments.",
"inventory_adjustment.type.increment": "Increment",
"inventory_adjustment.type.decrement": "Decrement",
"customer.type.individual": "Individual",
"customer.type.business": "Business",
"credit_note.view.draft": "Draft",
"credit_note.view.closed": "Closed",
"credit_note.view.open": "Open",
"credit_note.view.published": "Published",
"vendor_credit.view.draft": "Draft",
"vendor_credit.view.closed": "Closed",
"vendor_credit.view.open": "Open",
"vendor_credit.view.published": "Published",
"allocation_method.value.label": "Value",
"allocation_method.quantity.label": "Quantity",
"balance_sheet.assets": "Assets",
"balance_sheet.current_asset": "Current Asset",
"balance_sheet.cash_and_cash_equivalents": "Cash and cash equivalents",
"balance_sheet.accounts_receivable": "Accounts Receivable",
"balance_sheet.inventory": "Inventory",
"balance_sheet.other_current_assets": "Other current assets",
"balance_sheet.fixed_asset": "Fixed Asset",
"balance_sheet.non_current_assets": "Non-Current Assets",
"balance_sheet.liabilities_and_equity": "Liabilities and Equity",
"balance_sheet.liabilities": "Liabilities",
"balance_sheet.current_liabilties": "Current Liabilties",
"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",
"balance_sheet.percentage_of_column": "% of Column",
"balance_sheet.percentage_of_row": "% of Row",
"financial_sheet.previoud_period_date": "{{date}} (PP)",
"fianncial_sheet.previous_period_change": "Change (PP)",
"financial_sheet.previous_period_percentage": "% Change (PP)",
"financial_sheet.previous_year_date": "{{date}} (PY)",
"financial_sheet.previous_year_change": "Change (PY)",
"financial_sheet.previous_year_percentage": "% Change (PY)",
"financial_sheet.total_row": "Total {{value}}",
"profit_loss_sheet.income": "Income",
"profit_loss_sheet.cost_of_sales": "Cost of sales",
"profit_loss_sheet.gross_profit": "GROSS PROFIT",
"profit_loss_sheet.expenses": "Expenses",
"profit_loss_sheet.net_operating_income": "NET OPERATING INCOME",
"profit_loss_sheet.other_income": "Other income",
"profit_loss_sheet.other_expenses": "Other expenses",
"profit_loss_sheet.net_income": "NET INCOME",
"profit_loss_sheet.account_name": "Account name",
"profit_loss_sheet.total": "Total",
"profit_loss_sheet.percentage_of_income": "% of Income",
"profit_loss_sheet.percentage_of_expenses": "% of Expenses",
"profit_loss_sheet.percentage_of_column": "% of Column",
"profit_loss_sheet.percentage_of_row": "% of Row",
"contact_summary_balance.account_name": "Account name",
"contact_summary_balance.total": "Total",
"contact_summary_balance.percentage_column": "% of Column",
"warehouses.primary_warehouse": "Primary warehouse",
"branches.head_branch": "Head Branch",
"account.accounts_payable.currency": "Accounts Payable (A/P) - {{currency}}",
"account.accounts_receivable.currency": "Accounts Receivable (A/R) - {{currency}}",
"role.admin.name": "Admin",
"role.admin.desc": "Unrestricted access to all modules.",
"role.staff.name": "Staff",
"role.staff.desc": "Access to all modules except reports, settings and accountant.",
"warehouse_transfer.view.draft.name": "Draft",
"warehouse_transfer.view.in_transit.name": "In Transit",
"warehouse_transfer.view.transferred.name": "Transferred"
}

View File

@@ -0,0 +1,23 @@
import { mixin } from 'objection';
import TenantModel from 'models/TenantModel';
import ModelSetting from './ModelSetting';
import ModelSearchable from './ModelSearchable';
export default class Attachment extends mixin(TenantModel, [
ModelSetting,
ModelSearchable,
]) {
/**
* Table name
*/
static get tableName() {
return 'storage';
}
/**
* Model timestamps.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
}

View File

@@ -2,6 +2,9 @@ import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
export default class Contact extends TenantModel {
email: string;
displayName: string;
/**
* Table name
*/

View File

@@ -24,6 +24,9 @@ export default class Customer extends mixin(TenantModel, [
CustomViewBaseModel,
ModelSearchable,
]) {
email: string;
displayName: string;
/**
* Query builder.
*/
@@ -76,6 +79,19 @@ export default class Customer extends mixin(TenantModel, [
return 'debit';
}
/**
*
*/
get contactAddresses() {
return [
{
mail: this.email,
label: this.displayName,
primary: true
},
].filter((c) => c.mail);
}
/**
* Model modifiers.
*/

View File

@@ -0,0 +1,75 @@
import { Inject, Service } from 'typedi';
import path from 'path';
import { promises as fs } from 'fs';
import { PageProperties, PdfFormat } from '@/lib/Chromiumly/_types';
import { UrlConverter } from '@/lib/Chromiumly/UrlConvert';
import HasTenancyService from '../Tenancy/TenancyService';
import { Chromiumly } from '@/lib/Chromiumly/Chromiumly';
import { PDF_FILE_EXPIRE_IN, getPdfFilesStorageDir } from './utils';
@Service()
export class ChromiumlyHtmlConvert {
@Inject()
private tenancy: HasTenancyService;
/**
* Write HTML content to temporary file.
* @param {number} tenantId - Tenant id.
* @param {string} content - HTML content.
* @returns {Promise<[string, () => Promise<void>]>}
*/
async writeTempHtmlFile(
tenantId: number,
content: string
): Promise<[string, () => Promise<void>]> {
const { Attachment } = this.tenancy.models(tenantId);
const filename = `document-${Date.now()}.html`;
const storageDir = getPdfFilesStorageDir(filename);
const filePath = path.join(global.__storage_dir, storageDir);
await fs.writeFile(filePath, content);
await Attachment.query().insert({
key: filename,
path: storageDir,
expire_in: PDF_FILE_EXPIRE_IN, // ms
extension: 'html',
});
const cleanup = async () => {
await fs.unlink(filePath);
await Attachment.query().where('key', filename).delete();
};
return [filename, cleanup];
}
/**
* Converts the given HTML content to PDF.
* @param {string} html
* @param {PageProperties} properties
* @param {PdfFormat} pdfFormat
* @returns {Array<Buffer>}
*/
async convert(
tenantId: number,
html: string,
properties?: PageProperties,
pdfFormat?: PdfFormat
): Promise<Buffer> {
const [filename, cleanupTempFile] = await this.writeTempHtmlFile(
tenantId,
html
);
const fileDir = getPdfFilesStorageDir(filename);
const url = path.join(Chromiumly.GOTENBERG_DOCS_ENDPOINT, fileDir);
const urlConverter = new UrlConverter();
const buffer = await urlConverter.convert({
url,
properties,
pdfFormat,
});
await cleanupTempFile();
return buffer;
}
}

View File

@@ -0,0 +1,25 @@
import { Inject, Service } from 'typedi';
import { PageProperties, PdfFormat } from '@/lib/Chromiumly/_types';
import { ChromiumlyHtmlConvert } from './ChromiumlyHtmlConvert';
@Service()
export class ChromiumlyTenancy {
@Inject()
private htmlConvert: ChromiumlyHtmlConvert;
/**
* Converts the given HTML content to PDF.
* @param {string} content
* @param {PageProperties} properties
* @param {PdfFormat} pdfFormat
* @returns {Promise<Buffer>}
*/
public convertHtmlContent(
tenantId: number,
content: string,
properties?: PageProperties,
pdfFormat?: PdfFormat
) {
return this.htmlConvert.convert(tenantId, content, properties, pdfFormat);
}
}

View File

@@ -0,0 +1,8 @@
import path from 'path';
export const PDF_FILE_SUB_DIR = '/pdf';
export const PDF_FILE_EXPIRE_IN = 40; // ms
export const getPdfFilesStorageDir = (filename: string) => {
return path.join(PDF_FILE_SUB_DIR, filename);
}

View File

@@ -1,5 +1,7 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { ItemEntryTransformer } from '../Sales/Invoices/ItemEntryTransformer';
import { ICreditNote } from '@/interfaces';
export class CreditNoteTransformer extends Transformer {
/**
@@ -11,7 +13,8 @@ export class CreditNoteTransformer extends Transformer {
'formattedCreditsRemaining',
'formattedCreditNoteDate',
'formattedAmount',
'formattedCreditsUsed'
'formattedCreditsUsed',
'entries',
];
};
@@ -51,9 +54,20 @@ export class CreditNoteTransformer extends Transformer {
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedCreditsUsed = (credit) => {
protected formattedCreditsUsed = (credit) => {
return formatNumber(credit.creditsUsed, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the entries of the credit note.
* @param {ICreditNote} credit
* @returns {}
*/
protected entries = (credit) => {
return this.item(credit.entries, new ItemEntryTransformer(), {
currencyCode: credit.currencyCode,
});
};
}

View File

@@ -1,37 +1,29 @@
import { Inject, Service } from 'typedi';
import PdfService from '@/services/PDF/PdfService';
import { templateRender } from 'utils';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Tenant } from '@/system/models';
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable';
@Service()
export default class GetCreditNotePdf {
@Inject()
pdfService: PdfService;
private chromiumlyTenancy: ChromiumlyTenancy;
@Inject()
tenancy: HasTenancyService;
private templateInjectable: TemplateInjectable;
/**
* Retrieve sale invoice pdf content.
* @param {} saleInvoice -
*/
async getCreditNotePdf(tenantId: number, creditNote) {
const i18n = this.tenancy.i18n(tenantId);
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
const htmlContent = templateRender('modules/credit-note-standard', {
organization,
organizationName: organization.metadata.name,
organizationEmail: organization.metadata.email,
creditNote,
...i18n,
public async getCreditNotePdf(tenantId: number, creditNote) {
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/credit-note-standard',
{
creditNote,
}
);
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, {
margins: { top: 0, bottom: 0, left: 0, right: 0 },
});
const pdfContent = await this.pdfService.pdfDocument(htmlContent);
return pdfContent;
}
}

View File

@@ -0,0 +1,53 @@
import { Inject, Service } from 'typedi';
import { APAgingSummaryExportInjectable } from './APAgingSummaryExportInjectable';
import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable';
import { IAPAgingSummaryQuery } from '@/interfaces';
import { APAgingSummaryService } from './APAgingSummaryService';
@Service()
export class APAgingSummaryApplication {
@Inject()
private APAgingSummaryTable: APAgingSummaryTableInjectable;
@Inject()
private APAgingSummaryExport: APAgingSummaryExportInjectable;
@Inject()
private APAgingSummarySheet: APAgingSummaryService;
/**
* Retrieve the A/P aging summary in sheet format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public sheet(tenantId: number, query: IAPAgingSummaryQuery) {
return this.APAgingSummarySheet.APAgingSummary(tenantId, query);
}
/**
* Retrieve the A/P aging summary in table format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public table(tenantId: number, query: IAPAgingSummaryQuery) {
return this.APAgingSummaryTable.table(tenantId, query);
}
/**
* Retrieve the A/P aging summary in CSV format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public csv(tenantId: number, query: IAPAgingSummaryQuery) {
return this.APAgingSummaryExport.csv(tenantId, query);
}
/**
* Retrieve the A/P aging summary in XLSX format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public xlsx(tenantId: number, query: IAPAgingSummaryQuery) {
return this.APAgingSummaryExport.xlsx(tenantId, query);
}
}

View File

@@ -0,0 +1,43 @@
import { Inject, Service } from 'typedi';
import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable';
import { TableSheet } from '@/lib/Xlsx/TableSheet';
import { IAPAgingSummaryQuery } from '@/interfaces';
@Service()
export class APAgingSummaryExportInjectable {
@Inject()
private APAgingSummaryTable: APAgingSummaryTableInjectable;
/**
* Retrieves the A/P aging summary sheet in XLSX format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
* @returns {Promise<Buffer>}
*/
public async xlsx(tenantId: number, query: IAPAgingSummaryQuery) {
const table = await this.APAgingSummaryTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToXLSX();
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
}
/**
* Retrieves the A/P aging summary sheet in CSV format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
* @returns {Promise<Buffer>}
*/
public async csv(
tenantId: number,
query: IAPAgingSummaryQuery
): Promise<string> {
const table = await this.APAgingSummaryTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToCSV();
return tableCsv;
}
}

View File

@@ -1,14 +1,13 @@
import moment from 'moment';
import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
import { IAPAgingSummaryQuery, IARAgingSummaryMeta } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import APAgingSummarySheet from './APAgingSummarySheet';
import { Tenant } from '@/system/models';
import { isEmpty } from 'lodash';
import APAgingSummaryTable from './APAgingSummaryTable';
@Service()
export default class PayableAgingSummaryService {
export class APAgingSummaryService {
@Inject()
tenancy: TenancyService;
@@ -18,7 +17,7 @@ export default class PayableAgingSummaryService {
/**
* Default report query.
*/
get defaultQuery(): IAPAgingSummaryQuery {
private get defaultQuery(): IAPAgingSummaryQuery {
return {
asDate: moment().format('YYYY-MM-DD'),
agingDaysBefore: 30,
@@ -119,21 +118,4 @@ export default class PayableAgingSummaryService {
meta: this.reportMetadata(tenantId),
};
}
/**
* Retrieves A/P aging summary in table format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
async APAgingSummaryTable(tenantId: number, query: IAPAgingSummaryQuery) {
const report = await this.APAgingSummary(tenantId, query);
const table = new APAgingSummaryTable(report.data, query, {});
return {
columns: table.tableColumns(),
rows: table.tableRows(),
meta: report.meta,
query: report.query,
};
}
}

View File

@@ -0,0 +1,36 @@
import { Inject, Service } from 'typedi';
import { IAPAgingSummaryQuery, IAPAgingSummaryTable } from '@/interfaces';
import { APAgingSummaryService } from './APAgingSummaryService';
import APAgingSummaryTable from './APAgingSummaryTable';
@Service()
export class APAgingSummaryTableInjectable {
@Inject()
private APAgingSummarySheet: APAgingSummaryService;
/**
* Retrieves A/P aging summary in table format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
* @returns {Promise<IAPAgingSummaryTable>}
*/
public async table(
tenantId: number,
query: IAPAgingSummaryQuery
): Promise<IAPAgingSummaryTable> {
const report = await this.APAgingSummarySheet.APAgingSummary(
tenantId,
query
);
const table = new APAgingSummaryTable(report.data, query, {});
return {
table: {
columns: table.tableColumns(),
rows: table.tableRows(),
},
meta: report.meta,
query: report.query,
};
}
}

View File

@@ -0,0 +1,53 @@
import { Inject, Service } from 'typedi';
import { IARAgingSummaryQuery } from '@/interfaces';
import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable';
import { ARAgingSummaryExportInjectable } from './ARAgingSummaryExportInjectable';
import ARAgingSummaryService from './ARAgingSummaryService';
@Service()
export class ARAgingSummaryApplication {
@Inject()
private ARAgingSummaryTable: ARAgingSummaryTableInjectable;
@Inject()
private ARAgingSummaryExport: ARAgingSummaryExportInjectable;
@Inject()
private ARAgingSummarySheet: ARAgingSummaryService;
/**
* Retrieve the A/R aging summary sheet.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public sheet(tenantId: number, query: IARAgingSummaryQuery) {
return this.ARAgingSummarySheet.ARAgingSummary(tenantId, query);
}
/**
* Retrieve the A/R aging summary in table format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public table(tenantId: number, query: IARAgingSummaryQuery) {
return this.ARAgingSummaryTable.table(tenantId, query);
}
/**
* Retrieve the A/R aging summary in XLSX format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public xlsx(tenantId: number, query: IARAgingSummaryQuery) {
return this.ARAgingSummaryExport.xlsx(tenantId, query);
}
/**
* Retrieve the A/R aging summary in CSV format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
public csv(tenantId: number, query: IARAgingSummaryQuery) {
return this.ARAgingSummaryExport.csv(tenantId, query);
}
}

View File

@@ -0,0 +1,46 @@
import { Inject, Service } from 'typedi';
import { TableSheet } from '@/lib/Xlsx/TableSheet';
import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable';
import { IARAgingSummaryQuery } from '@/interfaces';
@Service()
export class ARAgingSummaryExportInjectable {
@Inject()
private ARAgingSummaryTable: ARAgingSummaryTableInjectable;
/**
* Retrieves the A/R aging summary sheet in XLSX format.
* @param {number} tenantId
* @param {IARAgingSummaryQuery} query
* @returns {Promise<Buffer>}
*/
public async xlsx(
tenantId: number,
query: IARAgingSummaryQuery
): Promise<Buffer> {
const table = await this.ARAgingSummaryTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToXLSX();
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
}
/**
* Retrieves the A/R aging summary sheet in CSV format.
* @param {number} tenantId
* @param {ICashFlowStatementQuery} query
* @returns {Promise<string>}
*/
public async csv(
tenantId: number,
query: IARAgingSummaryQuery
): Promise<string> {
const table = await this.ARAgingSummaryTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToCSV();
return tableCsv;
}
}

View File

@@ -5,7 +5,6 @@ import { IARAgingSummaryQuery, IARAgingSummaryMeta } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import ARAgingSummarySheet from './ARAgingSummarySheet';
import { Tenant } from '@/system/models';
import ARAgingSummaryTable from './ARAgingSummaryTable';
@Service()
export default class ARAgingSummaryService {
@@ -118,21 +117,4 @@ export default class ARAgingSummaryService {
meta: this.reportMetadata(tenantId),
};
}
/**
* Retrieves A/R aging summary in table format.
* @param {number} tenantId
* @param {IARAgingSummaryQuery} query
*/
async ARAgingSummaryTable(tenantId: number, query: IARAgingSummaryQuery) {
const report = await this.ARAgingSummary(tenantId, query);
const table = new ARAgingSummaryTable(report.data, query, {});
return {
columns: table.tableColumns(),
rows: table.tableRows(),
meta: report.meta,
query,
};
}
}

View File

@@ -0,0 +1,36 @@
import { IARAgingSummaryQuery, IARAgingSummaryTable } from '@/interfaces';
import { Inject, Service } from 'typedi';
import ARAgingSummaryTable from './ARAgingSummaryTable';
import ARAgingSummaryService from './ARAgingSummaryService';
@Service()
export class ARAgingSummaryTableInjectable {
@Inject()
private ARAgingSummarySheet: ARAgingSummaryService;
/**
* Retrieves A/R aging summary in table format.
* @param {number} tenantId
* @param {IARAgingSummaryQuery} query
* @returns {Promise<IARAgingSummaryTable>}
*/
public async table(
tenantId: number,
query: IARAgingSummaryQuery
): Promise<IARAgingSummaryTable> {
const report = await this.ARAgingSummarySheet.ARAgingSummary(
tenantId,
query
);
const table = new ARAgingSummaryTable(report.data, query, {});
return {
table: {
columns: table.tableColumns(),
rows: table.tableRows(),
},
meta: report.meta,
query,
};
}
}

View File

@@ -0,0 +1,57 @@
import { Inject, Service } from 'typedi';
import { IBalanceSheetQuery } from '@/interfaces';
import { BalanceSheetExportInjectable } from './BalanceSheetExportInjectable';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import BalanceSheetStatementService from './BalanceSheetInjectable';
@Service()
export class BalanceSheetApplication {
@Inject()
public balanceSheetExport: BalanceSheetExportInjectable;
@Inject()
public balanceSheetTable: BalanceSheetTableInjectable;
@Inject()
public balanceSheet: BalanceSheetStatementService;
/**
* Retrieves the balnace sheet in json format.
* @param {numnber} tenantId
* @param {IBalanceSheetQuery} query
* @returns {Promise<IBalanceSheetStatement>}
*/
public sheet(tenantId: number, query: IBalanceSheetQuery) {
return this.balanceSheet.balanceSheet(tenantId, query);
}
/**
* Retrieves the balance sheet in table format.
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
* @returns {Promise<IBalanceSheetTable>}
*/
public table(tenantId: number, query: IBalanceSheetQuery) {
return this.balanceSheetTable.table(tenantId, query);
}
/**
* Retrieves the balance sheet in XLSX format.
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public xlsx(tenantId: number, query: IBalanceSheetQuery) {
return this.balanceSheetExport.xlsx(tenantId, query);
}
/**
* Retrieves the balance sheet in CSV format.
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public csv(tenantId: number, query: IBalanceSheetQuery): Promise<string> {
return this.balanceSheetExport.csv(tenantId, query);
}
}

View File

@@ -0,0 +1,43 @@
import { Inject, Service } from 'typedi';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import { TableSheet } from '@/lib/Xlsx/TableSheet';
import { IBalanceSheetQuery } from '@/interfaces';
@Service()
export class BalanceSheetExportInjectable {
@Inject()
private balanceSheetTable: BalanceSheetTableInjectable;
/**
* Retrieves the trial balance sheet in XLSX format.
* @param {number} tenantId
* @param {ITrialBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public async xlsx(tenantId: number, query: IBalanceSheetQuery) {
const table = await this.balanceSheetTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToXLSX();
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
}
/**
* Retrieves the trial balance sheet in CSV format.
* @param {number} tenantId
* @param {ITrialBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public async csv(
tenantId: number,
query: IBalanceSheetQuery
): Promise<string> {
const table = await this.balanceSheetTable.table(tenantId, query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToCSV();
return tableCsv;
}
}

View File

@@ -19,13 +19,10 @@ export default class BalanceSheetStatementService
implements IBalanceSheetStatementService
{
@Inject()
tenancy: TenancyService;
@Inject('logger')
logger: any;
private tenancy: TenancyService;
@Inject()
inventoryService: InventoryService;
private inventoryService: InventoryService;
/**
* Defaults balance sheet filter query.
@@ -94,10 +91,8 @@ export default class BalanceSheetStatementService
/**
* Retrieve balance sheet statement.
* -------------
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
*
* @return {IBalanceSheetStatement}
*/
public async balanceSheet(

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