Compare commits

...

333 Commits

Author SHA1 Message Date
allcontributors[bot]
22d1e4df89 docs: update .all-contributorsrc [skip ci] 2024-05-13 06:42:17 +00:00
allcontributors[bot]
0e72ca4b4a docs: update README.md [skip ci] 2024-05-13 06:42:16 +00:00
Chris Cantrell
c9ba9500cc fix: typo in setup wizard (#440)
Fixed spelling error
2024-05-13 08:39:51 +02:00
Ahmed Bouhuolia
23d27cafc1 fix: Update Dockerfile 2024-05-10 11:36:01 +02:00
Ahmed Bouhuolia
92e3d31360 fix: use server container from Docker registry instead 2024-05-09 20:10:49 +02:00
Ahmed Bouhuolia
8aefa7709c feat: Combine arm64 and amd64 in one Github action runner (#437) 2024-05-09 19:25:51 +02:00
Ahmed Bouhuolia
3020295841 feat: Create a manifest list for webapp Docker image and push it to DockerHub. (#436) 2024-05-09 15:37:07 +02:00
Ahmed Bouhuolia
7f31a48755 chore: dump the CHANGELOG.md file 2024-05-06 23:38:11 +02:00
Ahmed Bouhuolia
8c0ef61038 hotfix: add the pnpm-workspace.yaml file to Dockerfile 2024-05-06 22:28:13 +02:00
Ahmed Bouhuolia
76bb82f2b4 chore: update the pnpm-lock file 2024-05-06 22:25:45 +02:00
Ahmed Bouhuolia
4c0dc276dd hotfix: rollback the pnpm-workspace.yaml file 2024-05-06 22:04:15 +02:00
Ahmed Bouhuolia
a69c4b4067 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-05-06 21:13:06 +02:00
Ahmed Bouhuolia
d81e544e82 chore: update pnpm-lock.yaml version 2024-05-06 21:11:39 +02:00
Ahmed Bouhuolia
6eeda23559 Update README.md 2024-05-06 20:04:21 +02:00
Ahmed Bouhuolia
cd046cbe27 hotbug: remove the pnpm-workspace.yaml from Dockerfile 2024-05-06 17:55:26 +02:00
Ahmed Bouhuolia
9b7bc1e5b9 Merge branch 'main' into develop 2024-05-06 17:51:24 +02:00
Ahmed Bouhuolia
987341ed29 Merge branch 'main' into develop 2024-05-06 17:50:02 +02:00
Ahmed Bouhuolia
d7cad17f1b Merge pull request #426 from bigcapitalhq/big-163-user-email-verification-after-signing-up
feat: User email verification after signing-up.
2024-05-06 17:46:26 +02:00
Ahmed Bouhuolia
dd02ae471e feat: redirect to the setup page if user is verified 2024-05-06 17:45:32 +02:00
Ahmed Bouhuolia
a5bfb0b02b fix: the email confirmation link on mail message 2024-05-06 17:36:36 +02:00
Ahmed Bouhuolia
f4440c9a03 feat: ability to enable/disable email confirmation from env variables 2024-05-03 18:30:19 +02:00
Ahmed Bouhuolia
cb88c234d1 feat: sync the isVerified state of authed user 2024-05-03 16:00:31 +02:00
Ahmed Bouhuolia
f6a0476fb4 feat: bump CHANGELOG 2024-05-03 11:28:11 +02:00
Ahmed Bouhuolia
63cdc45295 Merge pull request #430 from bigcapitalhq/export-resources
feat: Export resource data to csv, xlsx
2024-05-02 16:20:20 +02:00
Ahmed Bouhuolia
83a5010dc5 feat: flatten the nested columns of exported data 2024-05-02 15:38:57 +02:00
Ahmed Bouhuolia
55aab76c9b feat: configure columns of resources export 2024-05-01 20:15:35 +02:00
Ahmed Bouhuolia
495941f43a feat: style the export dialog form 2024-05-01 19:09:53 +02:00
Ahmed Bouhuolia
00a1e070c6 feat: export dialog on resource table 2024-05-01 16:26:10 +02:00
Ahmed Bouhuolia
fab71d2b65 feat: wip configure resources to be exportable 2024-05-01 12:45:24 +02:00
Ahmed Bouhuolia
9504bb5ccd fix: Running migration Docker container on Windows (#432) 2024-05-01 00:52:23 +02:00
Ahmed Bouhuolia
7e89966f20 feat: wip export resource data 2024-05-01 00:20:13 +02:00
Ahmed Bouhuolia
8a96c41258 feat: export resource data to csv, xlsx 2024-04-29 23:45:11 +02:00
Vederis Leunardus
4a713980bf feat: Pushing docker containers by version tag (#421)
* feat: build docker image based on tag version

* refactor: tabbing

* refactor: revert conflict
2024-04-29 11:39:47 +02:00
Ahmed Bouhuolia
2a1cbf6ced Merge branch 'main' into develop 2024-04-28 18:18:31 +02:00
Ahmed Bouhuolia
9103b60653 feat: New Relic tracking (#429) 2024-04-28 18:12:59 +02:00
Ahmed Bouhuolia
b9fc0cdd9e feat: wip email confirmation 2024-04-28 17:51:11 +02:00
Ahmed Bouhuolia
4368c18479 feat: User email verification after signing-up. 2024-04-26 12:21:40 +02:00
Ahmed Bouhuolia
7e5c6b6487 hotfix: parse the mail secure env variable (#424) 2024-04-24 21:09:56 +02:00
Ahmed Bouhuolia
b7214044bb Merge branch 'main' into develop 2024-04-24 20:05:49 +02:00
Ahmed Bouhuolia
93cb3615c3 Merge branch 'main' into develop 2024-04-24 20:05:33 +02:00
Ahmed Bouhuolia
7abfa6a162 feat: ability to enable/disable the bank connect feature (#423) 2024-04-24 20:01:04 +02:00
Ahmed Bouhuolia
1372a1f0a8 hotfix: fix the subscription plan when subscribe on cloud (#422) 2024-04-24 15:30:36 +02:00
allcontributors[bot]
484024ec28 docs: add cloudsbird as a contributor for code (#418)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-23 19:37:47 +02:00
Vederis Leunardus
46639c7b86 feat: Update Docker Build-Push Action and Add ARM64 Support (#412)
* feat: build for arm64 platform

* fix: typo and add buildx and qemu

* feat: update the docker login-action version

* feat: update the docker login-action version

* feat: add the digest
2024-04-23 19:33:27 +02:00
allcontributors[bot]
d10d1654c1 docs: add benpsnyder as a contributor for code (#417)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-22 10:21:09 +02:00
Ahmed Bouhuolia
2f06070ecb Merge pull request #414 from benpsnyder/feat/upgrade-to-latest-lerna
feat(repo): upgrade to latest lerna v8 and pnpm v9
2024-04-22 10:16:50 +02:00
Ahmed Bouhuolia
deefdb9bfd fix: update pnpm-lock.yaml file 2024-04-22 10:04:18 +02:00
Ben Snyder
3cc62d80de fix(repo): replace usage of yarn with pnpm/pnpx 2024-04-21 20:34:35 -04:00
Ben Snyder
4962c5d4d3 feat(repo): upgrade to latest lerna v8 and pnpm v9 2024-04-21 20:29:38 -04:00
Ahmed Bouhuolia
571a332658 Merge pull request #410 from bigcapitalhq/seed-free-subscription-to-tenants
feat: seed free subscription to tenants that have no subscription.
2024-04-19 09:36:35 +02:00
Ahmed Bouhuolia
b44c318a5d feat: seed free subscription to tenants that have no subscription. 2024-04-19 09:34:47 +02:00
Ahmed Bouhuolia
bd9717f4dc chore: Add Bigcapital Cloud link on README.md file 2024-04-17 19:51:05 +02:00
Ahmed Bouhuolia
f48aea8e5a feat: add the new env variables to docker compose 2024-04-17 19:21:35 +02:00
Ahmed Bouhuolia
0ac3a5dea9 fix: add /imports directory to storage dir 2024-04-17 18:25:17 +02:00
Ahmed Bouhuolia
56b40ad4cb fix: TS and linit errors 2024-04-17 17:51:21 +02:00
Ahmed Bouhuolia
9b6f934990 fix: add @lemonsqueezy/lemonsqueezy package dependencies. 2024-04-17 17:44:35 +02:00
Ahmed Bouhuolia
80e3522f8a Merge pull request #408 from bigcapitalhq/fix-import-store-absolute-path
fix: absolute storage imports path.
2024-04-17 17:37:39 +02:00
Ahmed Bouhuolia
7975643765 fix: absolute storage imports path. 2024-04-17 17:36:35 +02:00
Ahmed Bouhuolia
2ac7f86bdb Merge pull request #407 from bigcapitalhq/auto-subscribe-free
chore: add default value to env variable
2024-04-16 21:24:49 +02:00
Ahmed Bouhuolia
956b9b6812 chore: add default value to env variable 2024-04-16 21:22:40 +02:00
Ahmed Bouhuolia
60248ec3f6 Merge pull request #406 from bigcapitalhq/auto-subscribe-free
feat: auto subscribe to free plan once signup on community version.
2024-04-16 20:58:29 +02:00
Ahmed Bouhuolia
9d3f1541eb feat: auto subscribe to free plan once signup on community version. 2024-04-16 20:57:05 +02:00
Ahmed Bouhuolia
9b5f1a36ab Merge branch 'main' into develop 2024-04-16 12:58:22 +02:00
Ahmed Bouhuolia
8ee691e1ed Merge pull request #405 from bigcapitalhq/subscription-page-content
feat: subscription page content
2024-04-16 12:55:57 +02:00
Ahmed Bouhuolia
f9cb14da9e feat: subscription page content 2024-04-16 12:54:36 +02:00
Ahmed Bouhuolia
5e87581f4e hotfix: creating a vendor 2024-04-15 22:48:54 +02:00
Ahmed Bouhuolia
8ca9cf39da Merge pull request #404 from bigcapitalhq/optimize-ui-onboarding
feat: optimize the onboarding subscription experience.
2024-04-15 14:53:52 +02:00
Ahmed Bouhuolia
9001fea524 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-04-15 14:51:19 +02:00
Ahmed Bouhuolia
dea0d71732 Merge pull request #402 from bigcapitalhq/lemon-squeezy-payment
feat: Integrate Lemon Squeezy payment
2024-04-15 14:49:39 +02:00
Ahmed Bouhuolia
c191c4bd26 feat: remove other payment methods 2024-04-15 14:49:27 +02:00
Ahmed Bouhuolia
47d82ce591 feat: optimize the onboarding subscription experience. 2024-04-15 12:48:16 +02:00
Ahmed Bouhuolia
9321db2a3a feat: sweep up lemon squeezy webhooks. 2024-04-14 12:58:53 +02:00
Ahmed Bouhuolia
e486333c96 feat: sweep up the Lemon Squeezy integration 2024-04-14 12:44:02 +02:00
Ahmed Bouhuolia
a9748b23c0 feat: listen LemonSqueezy webhooks 2024-04-14 11:55:36 +02:00
Ahmed Bouhuolia
693ae61141 feat: integrate LemonSqueezy to subscription payment 2024-04-14 10:33:29 +02:00
Ahmed Bouhuolia
9807ac04b0 Revert "feat(webapp): deprecate the subscription step in onboarding process"
This reverts commit 0c1bf302e5.
2024-04-13 15:18:59 +02:00
Ahmed Bouhuolia
bddfde4138 Revert "feat(webapp): deprecate the subscription step in onboarding process"
This reverts commit 0c1bf302e5.
2024-04-13 14:07:32 +02:00
Ahmed Bouhuolia
a39dcd00d5 Revert "feat(server): deprecated the subscription module."
This reverts commit 3b79ac66ae.
2024-04-13 11:05:53 +02:00
Ahmed Bouhuolia
4d616e9287 Revert "feat(server): deprecated the subscription module."
This reverts commit 44fc26b156.
2024-04-13 10:17:48 +02:00
Ahmed Bouhuolia
dc52fb1de5 fix: lint error 2024-04-09 22:24:03 +02:00
Ahmed Bouhuolia
21a1777424 Merge branch 'release/v0.15.2' into develop 2024-04-09 22:11:10 +02:00
Ahmed Bouhuolia
16b721db91 Merge pull request #401 from bigcapitalhq/import-fields-hints
feat: add hints to import fields
2024-04-09 22:01:16 +02:00
Ahmed Bouhuolia
079491823d feat: add hints to import fields 2024-04-09 22:00:04 +02:00
Ahmed Bouhuolia
f7a87a6e9c Merge pull request #400 from bigcapitalhq/clean-up-templ-import-files
feat: clean up the imported temp files
2024-04-09 00:16:36 +02:00
Ahmed Bouhuolia
e0cdf42980 chore: update the server .gitignore 2024-04-09 00:13:58 +02:00
Ahmed Bouhuolia
ee56653f4b fix: remove the import table from tenants dbs 2024-04-09 00:13:32 +02:00
Ahmed Bouhuolia
2310b09778 fix: delete imported file if error has thrown 2024-04-09 00:11:34 +02:00
Ahmed Bouhuolia
0684e50ebd feat: clean up the imported temp files 2024-04-09 00:09:32 +02:00
Ahmed Bouhuolia
aaa8f39e50 feat: add section label to import mapping 2024-04-08 00:39:18 +02:00
Ahmed Bouhuolia
af981ce630 Merge pull request #396 from bigcapitalhq/aggregate-rows-import
feat: Aggregate rows import
2024-04-07 23:55:06 +02:00
Ahmed Bouhuolia
a1f8417b5d feat: revert the resource columns 2024-04-07 23:48:23 +02:00
Ahmed Bouhuolia
086b060351 feat: add sample import sheet to invoices 2024-04-07 15:26:23 +02:00
Ahmed Bouhuolia
bbafdcd8bd feat: configuring import services on more resources 2024-04-06 04:06:15 +02:00
Ahmed Bouhuolia
dd9098bdc1 feat: more resources support importing 2024-04-05 02:00:12 +02:00
Ahmed Bouhuolia
3851d34ba4 feat: aggregate rows on import feature 2024-04-04 05:01:09 +02:00
Ahmed Bouhuolia
b9651f30d5 chore: remove console.log 2024-04-01 04:40:40 +02:00
Ahmed Bouhuolia
45b5fb4088 fix: syntax error 2024-04-01 03:06:01 +02:00
Ahmed Bouhuolia
aa64bcf69b Merge pull request #393 from bigcapitalhq/import-relations-mapping
feat: linking relation with id in importing
2024-04-01 03:02:04 +02:00
Ahmed Bouhuolia
cbd867b334 Merge branch 'develop' into import-relations-mapping 2024-04-01 03:01:24 +02:00
Ahmed Bouhuolia
1a8ca83786 Merge pull request #395 from bigcapitalhq/validate-imported-sheet-empty
feat: validate the given imported sheet whether is empty
2024-04-01 02:58:41 +02:00
Ahmed Bouhuolia
80c14ba1a0 Merge branch 'develop' into validate-imported-sheet-empty 2024-04-01 02:58:33 +02:00
Ahmed Bouhuolia
785045dbad feat: validate the given imported sheet whether is empty 2024-04-01 02:57:30 +02:00
Ahmed Bouhuolia
291301c1e3 Merge pull request #394 from bigcapitalhq/advanced-import-parser
feat: advanced parser for numeric and boolean import values
2024-04-01 02:27:34 +02:00
Ahmed Bouhuolia
824e4e13d1 feat: advanced parser for numeric and boolean import values 2024-04-01 02:26:08 +02:00
Ahmed Bouhuolia
74da28b464 feat: linking relation with id in importing 2024-04-01 01:13:31 +02:00
Ahmed Bouhuolia
22a016b56e Merge pull request #392 from bigcapitalhq/import-show-unique-value-preview
fix: show the unique row value in the import preview
2024-03-28 05:39:40 +02:00
Ahmed Bouhuolia
040f016273 fix: show the unique row value in the import preview 2024-03-28 05:38:24 +02:00
Ahmed Bouhuolia
8ab809fc71 Merge pull request #389 from bigcapitalhq/accounts-bank-transactions-demo-sheet
feat: add sample sheet to accounts and bank transactions
2024-03-28 00:57:58 +02:00
Ahmed Bouhuolia
f9e5028e0d feat: add sample sheet to accounts and bank transactions 2024-03-28 00:57:01 +02:00
Ahmed Bouhuolia
7a3e121942 feat: import customers/vendors 2024-03-28 00:05:02 +02:00
Ahmed Bouhuolia
fc1d123c6b Merge pull request #388 from bigcapitalhq/fix-import-bugs
fix: import resources improvements
2024-03-27 04:10:18 +02:00
Ahmed Bouhuolia
ad4e51d81d fix: import resource imporements 2024-03-27 04:01:01 +02:00
Ahmed Bouhuolia
973d1832bd fix: import accounts issue 2024-03-22 20:45:05 +02:00
Ahmed Bouhuolia
858e3541cb Merge pull request #386 from bigcapitalhq/frontend-import-resource
feat(webapp): import resource UI
2024-03-22 01:45:19 +02:00
Ahmed Bouhuolia
a5ab535d3b feat(webapp): import preview page 2024-03-22 00:05:10 +02:00
Ahmed Bouhuolia
1d8cec5069 feat: wip import resource UI 2024-03-20 04:55:35 +02:00
Ahmed Bouhuolia
2baa667c5d fix(webapp): hotfix pdf request hook 2024-03-19 05:22:15 +02:00
Ahmed Bouhuolia
aba06991d4 Merge branch 'develop' into frontend-import-resource 2024-03-19 03:58:41 +02:00
Ahmed Bouhuolia
ff5730d8a7 feat(webapp): import resource UI 2024-03-19 03:57:57 +02:00
Ahmed Bouhuolia
a27c877321 Merge pull request #382 from bigcapitalhq/draft-import-resources
Import resources from csv/xlsx
2024-03-15 00:26:58 +02:00
Ahmed Bouhuolia
c5063fc5b5 chore: update the frozen pnpm-lock.yaml file 2024-03-15 00:26:23 +02:00
Ahmed Bouhuolia
ab4c0ab7a7 feat: wip import resources 2024-03-15 00:18:41 +02:00
Ahmed Bouhuolia
084d9d3d10 feat: import resource 2024-03-14 22:18:12 +02:00
Ahmed Bouhuolia
daa1e3a6bd feat: wip import resource 2024-03-13 02:14:25 +02:00
Ahmed Bouhuolia
4270d66928 feat(server): wip import resources 2024-03-11 20:05:12 +02:00
Ahmed Bouhuolia
90b4f3ef6d feat: import resources from csv/xlsx 2024-03-11 00:21:36 +02:00
Ahmed Bouhuolia
1fc6445123 Merge branch 'develop' into draft-import-resources 2024-03-10 14:54:32 +02:00
Ahmed Bouhuolia
b1d5390bfc WIP 2024-03-10 14:53:10 +02:00
Ahmed Bouhuolia
1ba26a3b85 Merge pull request #381 from bigcapitalhq/big-152-uncategorize-the-cashflow-transaction
feat: uncategorize the cashflow transaction
2024-03-10 03:09:43 +02:00
Ahmed Bouhuolia
2c98376162 feat: uncategorize transaciton catch validation errors 2024-03-10 03:08:24 +02:00
Ahmed Bouhuolia
b71c79fef5 feat: uncategorize the cashflow transaction 2024-03-10 02:53:57 +02:00
Ahmed Bouhuolia
2baf407814 Merge pull request #377 from bigcapitalhq/big-150-categorized-the-bank-synced-transactions
feat: Categorize the bank synced transactions
2024-03-07 20:59:42 +02:00
Ahmed Bouhuolia
83fbb7225d feat: remove uncategorized transaction from expenses 2024-03-07 20:58:44 +02:00
Ahmed Bouhuolia
b9a00418fa feat: abstract the uncategorized and all transactions boot wrappers 2024-03-07 14:31:59 +02:00
Ahmed Bouhuolia
62d3e386dd feat(server): move all cashflow under application service 2024-03-07 14:19:11 +02:00
Ahmed Bouhuolia
d87d674aba feat: wip categorize the cashflow transactions 2024-03-06 22:15:31 +02:00
Ahmed Bouhuolia
a17b4f6a8a feat: categorize cashflow transaction drawer 2024-03-05 22:27:42 +02:00
Ahmed Bouhuolia
db839137d0 feat(server): change the Plaid synced bank name. 2024-03-05 16:36:35 +02:00
Ahmed Bouhuolia
b602f28696 Merge branch 'develop' into big-150-categorized-the-bank-synced-transactions 2024-03-04 21:03:11 +02:00
Ahmed Bouhuolia
68f2f4ee84 feat: wip categorized transactions 2024-03-04 21:01:36 +02:00
Ahmed Bouhuolia
f23e8d98f6 feat(server): sync Plaid transactions to uncategorized transactions 2024-03-04 13:42:17 +02:00
Ahmed Bouhuolia
9db03350e0 feat(webapp): categorize the cashflow uncategorized transactions 2024-03-04 13:41:15 +02:00
Ahmed Bouhuolia
0273714a07 feat(webapp): Filter account transactions by categorized/uncategorized transactions 2024-03-02 17:01:58 +02:00
Ahmed Bouhuolia
ea497bfdea Merge pull request #378 from bigcapitalhq/picking-the-sync-bank-service-provider
feat(webapp): Dialog to choose the bank service provider
2024-03-01 17:56:45 +02:00
Ahmed Bouhuolia
685a6150e6 feat(webapp): add the text of connect bank dialog 2024-03-01 17:55:21 +02:00
Ahmed Bouhuolia
daf87a8ec7 feat(webapp): Dialog to choose the bank service provider 2024-03-01 17:22:28 +02:00
Ahmed Bouhuolia
5b4ddadcf6 feat(server): categorize the synced bank transactions 2024-03-01 17:12:56 +02:00
Ahmed Bouhuolia
ea8c5458ff feat: Categorize the bank synced transactions 2024-02-29 23:53:26 +02:00
Ahmed Bouhuolia
0833baabda Merge pull request #361 from ANasouf/BIG-111-add-Convert-to-invoice-button-on-estimate-drawer-toolbar
Big 111 add convert to invoice button on estimate drawer toolbar
2024-02-26 15:32:19 +02:00
Ahmed Bouhuolia
ab7eb40ea9 feat(webapp): add icon to convert to invoice button 2024-02-26 15:29:51 +02:00
Ahmed Bouhuolia
00e6c141ee Merge pull request #371 from bigcapitalhq/big-129-universal-search-should-auto-focus-when-it-opens
fix: Universal search should auto-focus when it opens
2024-02-26 01:17:32 +02:00
Ahmed Bouhuolia
0aea339c1c fix: Universal search should auto-focus when it opens 2024-02-26 01:16:55 +02:00
Ahmed Bouhuolia
2cf75e0136 hotfix: systax error 2024-02-25 20:28:16 +02:00
Ahmed Bouhuolia
5a8c394ad7 Merge pull request #370 from bigcapitalhq/big-145-optimize-the-print-style-some-financial-reports
fix: Optimize the print style some financial reports
2024-02-25 17:41:17 +02:00
Ahmed Bouhuolia
71742c3480 fix: Optimize the print style some financial reports 2024-02-25 17:40:33 +02:00
Ahmed Bouhuolia
b340776278 Merge pull request #369 from bigcapitalhq/big-144-fix-transactions-by-vendors-broken
fix: broken transactions by vendor report
2024-02-25 13:21:39 +02:00
Ahmed Bouhuolia
6ea870edb2 fix: broken transactions by vendor report 2024-02-25 13:20:34 +02:00
Ahmed Bouhuolia
dd072f8585 Merge pull request #346 from bigcapitalhq/big-116-open-up-the-link-component
Bigcapital <> Plaid Integration
2024-02-24 00:47:52 +02:00
Ahmed Bouhuolia
ecfdba9634 Merge branch 'develop' into big-116-open-up-the-link-component 2024-02-24 00:47:35 +02:00
Ahmed Bouhuolia
5be3ca75f4 feat: disable the connect button. 2024-02-24 00:44:55 +02:00
Ahmed Bouhuolia
7239b0ec7e feat: add path to socket connection 2024-02-24 00:43:43 +02:00
Ahmed Bouhuolia
2d3544fe37 feat: add socker connection between client and server 2024-02-24 00:18:48 +02:00
Ahmed Bouhuolia
3faeca20a9 Merge pull request #332 from bigcapitalhq/big-105-convert-invoice-status-after-sending-mail-notification
feat: convert invoice status after sending mail notification
2024-02-21 20:29:55 +02:00
Ahmed Bouhuolia
b713b6922b Merge branch 'develop' into big-105-convert-invoice-status-after-sending-mail-notification 2024-02-21 20:20:36 +02:00
Ahmed Bouhuolia
4e40009ba0 revert: form changes 2024-02-21 20:16:57 +02:00
Ahmed Bouhuolia
e09f8e4334 Merge pull request #363 from bigcapitalhq/big-132-financial-reports-pdf-printing
feat: printing financial reports
2024-02-21 17:39:56 +02:00
Ahmed Bouhuolia
1752c32eec feat: code review notes 2024-02-21 17:17:34 +02:00
Ahmed Bouhuolia
d16ec7cda9 feat(server): table sheet pdf 2024-02-21 14:55:06 +02:00
Ahmed Bouhuolia
f6f6bc31b6 feat(server): styling financial reports 2024-02-20 21:36:16 +02:00
Ahmed Bouhuolia
c06a8d9ca3 feat(server): styling financial reports pdf 2024-02-19 21:41:15 +02:00
Ahmed Bouhuolia
3509026ad8 feat: wip financial reports printing 2024-02-18 17:32:17 +02:00
Ahmed Bouhuolia
465bb66d6b feat: financial reports meta 2024-02-17 23:58:26 +02:00
Ahmed Bouhuolia
06e78db49d feat(server): financial reports meta 2024-02-17 23:36:29 +02:00
Ahmed Bouhuolia
d2e6cc0036 feat(server): financial sheet meta 2024-02-17 13:17:18 +02:00
Ahmed Bouhuolia
7690789031 feat(server): balance sheet meta 2024-02-17 00:21:48 +02:00
Ahmed Bouhuolia
27fed5f18a feat(webapp): wip printing financial reports 2024-02-17 00:15:20 +02:00
Ahmed Bouhuolia
1fd8a53ed1 feat(server): listen to Plaid webhooks 2024-02-14 17:11:07 +02:00
Ahmed Bouhuolia
d67189587e feat(server): financial reports meta 2024-02-13 19:42:11 +02:00
Ahmed Bouhuolia
d229378957 feat(webapp): wip printing financial reports 2024-02-12 19:07:57 +02:00
Ahmed Bouhuolia
eb4491f44a feat(webapp): refactor http api hooks to seperate files 2024-02-12 19:01:55 +02:00
Ahmed Bouhuolia
09ad725a67 feat(webapp): wip print preview financial reports 2024-02-11 16:12:41 +02:00
Ahmed Bouhuolia
b11c531cf5 feat(server): wip priting financial reports 2024-02-11 01:14:31 +02:00
Ahmed Bouhuolia
0868eeaa0e Merge pull request #353 from ANasouf/BIG-112-Customer-emails-addresses-gets-the-.-removed-when-a-new-one-is-added
fix: gmail email addresses dots gets removed
2024-02-10 23:39:18 +02:00
Ahmed Bouhuolia
9395ef094a feat(server): wip printing financial reports 2024-02-10 22:20:54 +02:00
a.nasouf
a8671a8d99 feat(webapp): add convert to invoice on estimate drawer toolbar 2024-02-10 22:18:13 +02:00
a.nasouf
cd8f64dfdc feat(webapp): add mark as delivered to action bar of invoice details drawer 2024-02-10 21:04:54 +02:00
a.nasouf
17dbe9713b fix: remove normalizeEmail function 2024-02-10 19:59:12 +02:00
Ahmed Bouhuolia
706a324121 feat(server): Plaid webhooks 2024-02-08 20:18:52 +02:00
Ahmed Bouhuolia
ecaf8c99bb Merge pull request #354 from ANasouf/BIG-109-some-keywords-are-not-localized
fix: some keywords are not localized
2024-02-08 20:12:27 +02:00
Ahmed Bouhuolia
90bde20674 Merge pull request #358 from bigcapitalhq/big-127-amount-decimals-are-set-to-zero-when-creating-an-new-payment
fix: payment receive subtotal shouldn't be rounded
2024-02-06 20:39:27 +02:00
Ahmed Bouhuolia
0c61f85707 chore: remove format number from estimate quantity 2024-02-06 20:38:25 +02:00
Ahmed Bouhuolia
0f678e61c5 fix: Decimal amounts are rounded when create a new transaction on some transactions types 2024-02-06 20:31:48 +02:00
Ahmed Bouhuolia
374f1acf8a fix: payment receive subtotal shouldn't be rounded 2024-02-06 10:54:41 +02:00
Ahmed Bouhuolia
ada1428193 Merge pull request #357 from bigcapitalhq/big-139-trial-balance-total-row-shouldnt-show-if-accounts-have-no
fix(server): Trial balance total row shouldn't show if accounts have no balances
2024-02-05 23:05:02 +02:00
Ahmed Bouhuolia
528d447443 fix(server): Trial balance total row shouldn't show if accounts have no balances 2024-02-05 23:04:02 +02:00
Ahmed Bouhuolia
d6d67b9a51 Merge pull request #355 from bigcapitalhq/big-126-invalid-bill-payment-amount-on-editing-bill-payment
fix: Invalid bill payment amount on editing bill payment
2024-02-05 22:39:52 +02:00
Ahmed Bouhuolia
12740223a8 fix: Invalid bill payment amount on editing bill payment 2024-02-05 22:38:56 +02:00
a.nasouf
f7f77b12c9 fix: file formatting 2024-02-05 20:05:10 +02:00
a.nasouf
a6db4fb6df fix: some keywords are not localized 2024-02-05 19:52:48 +02:00
a.nasouf
b38020d397 fix: gmail email addresses dots gets removed 2024-02-05 18:58:02 +02:00
Ahmed Bouhuolia
d090d5a026 Merge pull request #352 from bigcapitalhq/big-138-revert-the-paid-amount-to-bill-transaction-after-editing
fix(server): Revert the paid amount to bill transaction after editing bill payment amount
2024-02-05 18:52:09 +02:00
Ahmed Bouhuolia
7b5287ee80 fix(server): Revert the paid amount to bill transaction after editing bill payment amount 2024-02-05 18:50:34 +02:00
Ahmed Bouhuolia
00d9bc537c feat: ability to remove the removed Plaid transactions in updating 2024-02-04 22:17:37 +02:00
Ahmed Bouhuolia
c688190acc feat: add bank balance to accounts chart table 2024-02-04 22:16:59 +02:00
Ahmed Bouhuolia
2e0b3d0d5e feat: add bank balance column to account 2024-02-04 22:16:12 +02:00
Ahmed Bouhuolia
6d888060d3 feat: reset plaid lanch link token 2024-02-04 19:59:04 +02:00
Ahmed Bouhuolia
299a943153 feat: add connect to bank dialog 2024-02-04 18:48:03 +02:00
Ahmed Bouhuolia
e0ddcb022a feat(server): move updating plaid transactions to background job 2024-02-03 13:59:46 +02:00
Ahmed Bouhuolia
b940c6dd17 feat(server): wip syncing Plaid transactions 2024-02-02 02:23:49 +02:00
Ahmed Bouhuolia
b43cd26ecc feat(webapp): popup Plaid Link component 2024-01-30 23:09:29 +02:00
Ahmed Bouhuolia
b9886cfac3 feat(server): api endpoint to get Plaid link token 2024-01-30 22:51:55 +02:00
Ahmed Bouhuolia
ba3ea93a2d chore: dump CHANGELOG.md 2024-01-30 00:28:23 +02:00
Ahmed Bouhuolia
6b77bda77f Merge pull request #342 from bigcapitalhq/big-130-edited-transactions-does-not-reflect-on-reports
hotfix: editing sales and expense transactions don't reflect GL entries
2024-01-29 23:27:42 +02:00
Ahmed Bouhuolia
ba387e81f7 hotfix: editing sales and expense transactions don't reflect GL entries 2024-01-29 23:25:37 +02:00
Ahmed Bouhuolia
0414c090ea Merge pull request #335 from bigcapitalhq/big-128-inconsistency-in-currency-display-on-the-quick-find-feature
fix(webapp): inconsistency in currency of universal search items
2024-01-29 19:24:16 +02:00
Ahmed Bouhuolia
a52f3a933f fix(webapp): inconsistency in currency of universal search items 2024-01-29 19:19:46 +02:00
Ahmed Bouhuolia
d01eacf8d9 feat(server): add formattedAmount to purchase invoice to POJO 2024-01-29 19:18:59 +02:00
Ahmed Bouhuolia
0da151faaa Merge pull request #340 from bigcapitalhq/big-65-get-realtime-exchange-rate-from-third-party-service
feat: get latest exchange rate from third party services
2024-01-29 18:44:05 +02:00
Ahmed Bouhuolia
f93c8b46dc chore: change env name in .env.example 2024-01-29 18:42:54 +02:00
Ahmed Bouhuolia
59c5c8979d feat(webapp): add default exchange rate value to 1 in cause the ex. rate service returned error or failed. 2024-01-29 18:39:28 +02:00
Ahmed Bouhuolia
74a07847a4 feat(server): ability to assign the base currency as api query when getting latest ex. rate 2024-01-28 20:11:44 +02:00
Ahmed Bouhuolia
1740226294 feat(webapp): hook up latest exchange rate api 2024-01-28 18:48:35 +02:00
Ahmed Bouhuolia
1b20d1b073 feat(server): add application layer to exchange rate service 2024-01-28 18:47:49 +02:00
Ahmed Bouhuolia
ac7175d83b feat: get latest exchange rate from third party services 2024-01-28 15:52:54 +02:00
Ahmed Bouhuolia
d72d8e60d6 Merge pull request #339 from bigcapitalhq/big-122-bug-expense-details-amount-should-not-be-rounded
fix: Expense amounts should not be rounded
2024-01-26 23:52:30 +02:00
Ahmed Bouhuolia
de5920f910 fix: Expense amounts should not be rounded 2024-01-26 23:46:45 +02:00
Ahmed Bouhuolia
475c4e9967 fix(webapp): inconsistency in currency of universal search items 2024-01-26 00:05:24 +02:00
Ahmed Bouhuolia
63708ae839 feat(webapp): showing up mail popup once saving invoice, receipt and estimate 2024-01-25 21:56:18 +02:00
Ahmed Bouhuolia
760dbc6cfc feat(server): change estimate and receipts status once delivering mail 2024-01-25 21:52:07 +02:00
Ahmed Bouhuolia
9d4e7cec9e feat(webapp): add ability to redirect to list to mail dialogs 2024-01-24 20:03:04 +02:00
Ahmed Bouhuolia
21eb88ef53 chore: dump CHANGELOG.md file 2024-01-24 00:26:56 +02:00
Ahmed Bouhuolia
c8e7a2c7d9 feat(server): convert invoice status after sending mail notification 2024-01-24 00:00:21 +02:00
Ahmed Bouhuolia
429159acf9 feat: export purchases by items to csv/xlsx (#327) 2024-01-23 12:33:43 +02:00
Ahmed Bouhuolia
7eb84474a5 hotfix(server): Unhandled thrown errors of services (#329) 2024-01-22 21:57:14 +02:00
allcontributors[bot]
e42adcae63 docs: add asenawritescode as a contributor for bug (#325)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-01-21 14:46:11 +02:00
Ahmed Bouhuolia
abffdd1029 fix(server): hotbug retireving empty results of inventory valuation and sales by items sheets 2024-01-21 14:34:36 +02:00
allcontributors[bot]
d052c23560 docs: add xprnio as a contributor for bug (#324)
* docs: update README.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-01-21 00:21:46 +02:00
Ragnar Laud
f02afd3c9f fix(webapp): AccountActivateAlert import (#322) 2024-01-21 00:18:31 +02:00
Ahmed Bouhuolia
3df17390e2 Merge pull request #318 from bigcapitalhq/big-120-get-cashflow-transaction-broken-cause-transaction-type
fix: `BIG-120` get cashflow transaction broken cause transaction type
2024-01-20 18:04:51 +02:00
Ahmed Bouhuolia
df38f8893d fix(server): get cashflow transaction type 2024-01-20 18:03:43 +02:00
Ahmed Bouhuolia
8882bc677e fix(webapp): undefined transactionNumber function 2024-01-20 18:03:26 +02:00
Ahmed Bouhuolia
f03d01113c Merge pull request #315 from bigcapitalhq/hotfix-pdf-printing
fix(server): the invoice and payment receipt printing
2024-01-20 15:53:59 +02:00
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
1165 changed files with 44541 additions and 45713 deletions

View File

@@ -69,6 +69,69 @@
"contributions": [
"code"
]
},
{
"login": "cschuijt",
"name": "Casper Schuijt",
"avatar_url": "https://avatars.githubusercontent.com/u/5460015?v=4",
"profile": "http://cschuijt.nl",
"contributions": [
"bug"
]
},
{
"login": "ANasouf",
"name": "ANasouf",
"avatar_url": "https://avatars.githubusercontent.com/u/19536487?v=4",
"profile": "https://github.com/ANasouf",
"contributions": [
"code"
]
},
{
"login": "xprnio",
"name": "Ragnar Laud",
"avatar_url": "https://avatars.githubusercontent.com/u/3042904?v=4",
"profile": "https://ragnarlaud.dev",
"contributions": [
"bug"
]
},
{
"login": "asenawritescode",
"name": "Asena",
"avatar_url": "https://avatars.githubusercontent.com/u/67445192?v=4",
"profile": "https://github.com/asenawritescode",
"contributions": [
"bug"
]
},
{
"login": "benpsnyder",
"name": "Ben Snyder",
"avatar_url": "https://avatars.githubusercontent.com/u/707567?v=4",
"profile": "https://snyder.tech",
"contributions": [
"code"
]
},
{
"login": "cloudsbird",
"name": "Vederis Leunardus",
"avatar_url": "https://avatars.githubusercontent.com/u/13505006?v=4",
"profile": "http://vederis.id",
"contributions": [
"code"
]
},
{
"login": "ccantrell72",
"name": "Chris Cantrell",
"avatar_url": "https://avatars.githubusercontent.com/u/104120598?v=4",
"profile": "http://www.pivoten.com",
"contributions": [
"bug"
]
}
],
"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
@@ -48,5 +48,58 @@ SIGNUP_DISABLED=false
SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS=
# Sign-up Email Confirmation
SIGNUP_EMAIL_CONFIRMATION=false
# API rate limit (points,duration,block duration).
API_RATE_LIMIT=120,60,600
API_RATE_LIMIT=120,60,600
# Gotenberg API for PDF printing - (production).
GOTENBERG_URL=http://gotenberg:3000
GOTENBERG_DOCS_URL=http://server:3000/public/
# Gotenberg API - (development)
# GOTENBERG_URL=http://localhost:9000
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
# Exchange Rate Service
EXCHANGE_RATE_SERVICE=open-exchange-rate
# Open Exchange Rate
OPEN_EXCHANGE_RATE_APP_ID=
# The Plaid environment to use ('sandbox' or 'development').
# https://plaid.com/docs/#api-host
PLAID_ENV=sandbox
# Your Plaid keys, which can be found in the Plaid Dashboard.
# https://dashboard.plaid.com/account/keys
PLAID_CLIENT_ID=
PLAID_SECRET_DEVELOPMENT=
PLAID_SECRET_SANDBOX=
PLAID_LINK_WEBHOOK=
# (Optional) Redirect URI settings section
# Only required for OAuth redirect URI testing (not common on desktop):
# Sandbox Mode:
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
# The OAuth redirect flow requires an endpoint on the developer's website
# that the bank website should redirect to. You will also need to configure
# this redirect URI for your client ID through the Plaid developer dashboard
# at https://dashboard.plaid.com/team/api.
# Development mode:
# When running in development mode, you must use an https:// url.
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
# Instructions to create a self-signed certificate for localhost can be found at
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
# If your system is not set up to run localhost with https://, you will be unable to test
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
PLAID_SANDBOX_REDIRECT_URI=
PLAID_DEVELOPMENT_REDIRECT_URI=
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
LEMONSQUEEZY_API_KEY=
LEMONSQUEEZY_STORE_ID=
LEMONSQUEEZY_WEBHOOK_SECRET=

View File

@@ -6,43 +6,66 @@ on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp
SERVER_IMAGE_NAME: bigcapital/bigcapital-server
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
SERVER_IMAGE_NAME: bigcapitalhq/server
jobs:
build-publish-webapp:
strategy:
fail-fast: false
name: Build and deploy webapp container
runs-on: ubuntu-latest
environment: production
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }}
images: ${{ env.WEBAPP_IMAGE_NAME }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
id: build
with:
context: .
file: ./packages/webapp/Dockerfile
push: true
tags: ghcr.io/bigcapitalhq/webapp:latest
labels: ${{ steps.meta.outputs.labels }}
context: ./
file: ./packages/webapp/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-webapp
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published webapp container successfully.
uses: rtCamp/action-slack-notify@v2
@@ -53,29 +76,52 @@ jobs:
name: Build and deploy server container
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
id: build
with:
context: ./
file: ./packages/server/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/bigcapitalhq/server:latest
tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}}
labels: ${{ steps.meta.outputs.labels }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-server
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published server container successfully.
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -8,14 +8,14 @@ on:
- '**.ts'
- '**.tsx'
- '**/tsconfig.json'
- 'yarn.lock'
- 'pnpm-lock.yaml'
- '.github/workflows/e2e.yml'
pull_request:
paths:
- '**.ts'
- '**.tsx'
- '**/tsconfig.json'
- 'yarn.lock'
- 'pnpm-lock.yaml'
- '.github/workflows/e2e.yml'
defaults:

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

@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn commitlint --edit
pnpx commitlint --edit

View File

@@ -2,11 +2,147 @@
All notable changes to Bigcapital server-side will be in this file.
# [0.10.1] - 25-09-2023
## [0.16.11] - 06-05-2024
### improvements
* feat: Export resource data to csv, xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/430
* feat: User email verification after signing-up. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/426
### Fixes
* feat(repo): upgrade to latest lerna v8 and pnpm v9 by @benpsnyder in https://github.com/bigcapitalhq/bigcapital/pull/414
* feat: Update Docker Build-Push Action and Add ARM64 Support by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/412
* feat: Pushing docker containers by version tag by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/421
## [0.16.10]
* fix: Running migration Docker container on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/432
## [0.16.9]
* feat: New Relic for tracking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/429
## [0.16.8]
* feat: Ability to enable/disable the bank connect feature by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/423
## [0.16.6]
* hotfix: fix the subscription plan when subscribe on cloud by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/422
## [0.16.5]
IMPORTANT: If you upgraded to the v0.16 recently you should upgrade to v0.16.4 as soon as possible, because there're some breaking changes affected the sign-in and some users reported couldn't sign-in.
* feat: Seed free subscription to tenants that have no subscription. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/410
## [0.16.3]
* feat: Integrate Lemon Squeezy payment by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/402
* feat: optimize the onboarding subscription experience. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/404
* feat: subscription page content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/405
* feat: auto subscribe to free plan once signup on community version. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/406
* chore: add default value to env variable by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/407
* fix: absolute storage imports path. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/408
## [0.16.0]
* feat: add convert to invoice button on estimate drawer toolbar by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/361
* feat(webapp): add mark as delivered to action bar of invoice details … by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/360
* feat(webapp): Dialog to choose the bank service provider by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/378
* feat: Categorize the bank synced transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/377
* feat: uncategorize the cashflow transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/381
* Import resources from csv/xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/382
* feat(webapp): import resource UI by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/386
* fix: import resources improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/388
* feat: add sample sheet to accounts and bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/389
* fix: show the unique row value in the import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/392
* feat: advanced parser for numeric and boolean import values by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/394
* feat: validate the given imported sheet whether is empty by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/395
* feat: linking relation with id in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/393
* feat: Aggregate rows import by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/396
* feat: clean up the imported temp files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/400
* feat: add hints to import fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/401
## [0.15.0]
* feat: Printing financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/363
* feat: Convert invoice status after sending mail notification by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/332
* feat: Bigcapital <> Plaid Integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/346
* fix: Broken transactions by vendor report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/369
* fix: Optimize the print style some financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/370
## [0.14.0] - 30-01-2024
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339
* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340
* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335
* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342
## [0.13.3] - 22-01-2024
* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329
## [0.13.2] - 21-01-2024
* feat: show customer / vendor balance. by @asenawritescode in https://github.com/bigcapitalhq/bigcapital/pull/311
* feat: inventory valuation csv and xlsx export by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/308
* feat: sales by items export csv & xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/310
* fix(server): the invoice and payment receipt printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/315
* fix: get cashflow transaction broken cause transaction type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/318
* fix: `AccountActivateAlert` import by @xprnio in https://github.com/bigcapitalhq/bigcapital/pull/322
## [0.13.1] - 15-01-2024
* feat(webapp): add approve/reject to action bar of estimate details dr… by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/304
* docs: add ANasouf as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/305
* feat: Export general ledger & Journal to CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/303
* feat: Auto re-calculate the items rate once changing the invoice exchange rate. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/270
## [0.13.0] - 31-12-2023
* feat: Send an invoice mail the customer email by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/292
* fix: Allow non-numeric postal codes by @cschuijt in https://github.com/bigcapitalhq/bigcapital/pull/294
* docs: add cschuijt as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/295
## [0.12.1] - 17-11-2023
* feat: Add default customer message and terms conditions to the transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/291
* fix: The currency code of transaction tax rate entry by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/293
## [0.12.0] - 04-11-2023
* feat: Export reports via CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/286
* fix: Axios upgrade by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/288
* fix(server): Allow decimal amount in sale/purchase transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/289
* feat: Optimize invoice documents printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/280
* chore(deps): bump axios from 0.20.0 to 1.6.0 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/284
* chore(deps): bump axios from 0.20.0 to 1.6.0 by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/283
## [0.11.0] - 28-10-2023
* feat: Migrate to pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/253
* feat: Integrate tax rates to bills by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/260
* feat: Assign default sell/purchase tax rates to items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/261
* chore(deps-dev): bump @babel/traverse from 7.23.0 to 7.23.2 in /packages/server by @dependabot in https://github.com/bigcapitalhq/bigcapital/pull/272
* feat: Improve financial statements rows color by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/276
* fix: Trial balance sheet adjusted balance by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/273
* feat: Adds tax numbers to organization and customers by @kochie in https://github.com/bigcapitalhq/bigcapital/pull/269
* docs: Add kochie as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/277
* feat: Computed Net Income under Equity in Balance Sheet report. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/271
* fix: Change Dockerfile files with new pnpm by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/278
## [0.10.2] - 02-10-2023
fix(webapp): Disable tax rates from item entries editor table services do not support tax rates (https://github.com/bigcapitalhq/bigcapital/commit/69afa07e3ba45495a4cab3490c15f2b0c40c4790) by @abouolia
fix(server): Add missing method in ItemEntry model (https://github.com/bigcapitalhq/bigcapital/commit/07628ddc37f46c98959ced0323f28752e0a98944) by @abouolia
## [0.10.1] - 25-09-2023
* Fix: Running tenants migration on Docker migration container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/242
# [0.10.0] - 24-09-2023
## [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 +152,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 +166,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 +189,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

@@ -1,6 +1,6 @@
<p align="center">
<p align="center">
<a href="https://bigcapital.ly" target="_blank">
<a href="https://bigcapital.app" target="_blank">
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/bigcapital.svg" alt="Bigcapital" width="280" height="75">
</a>
</p>
@@ -25,6 +25,10 @@
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
</a>
</p>
<p align="center">
<a href="https://my.bigcapital.app">Bigcapital Cloud</a>
</p>
</p>
# What's Bigcapital?
@@ -37,9 +41,40 @@ 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.app/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.
- [Documentation](https://docs.bigcapital.app/) - Learn how to use.
- [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions.
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
@@ -82,6 +117,15 @@ 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>
<td align="center" valign="top" width="14.28%"><a href="https://ragnarlaud.dev"><img src="https://avatars.githubusercontent.com/u/3042904?v=4?s=100" width="100px;" alt="Ragnar Laud"/><br /><sub><b>Ragnar Laud</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Axprnio" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://snyder.tech"><img src="https://avatars.githubusercontent.com/u/707567?v=4?s=100" width="100px;" alt="Ben Snyder"/><br /><sub><b>Ben Snyder</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=benpsnyder" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
</tr>
</tbody>
</table>

View File

@@ -21,20 +21,18 @@ services:
depends_on:
- server
- webapp
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
webapp:
container_name: bigcapital-webapp
image: ghcr.io/bigcapitalhq/webapp:latest
deploy:
restart_policy:
condition: unless-stopped
image: bigcapitalhq/webapp:latest
restart: on-failure
server:
container_name: bigcapital-server
image: ghcr.io/bigcapitalhq/server:latest
image: bigcapitalhq/server:latest
expose:
- '3000'
links:
- mysql
- mongo
@@ -43,9 +41,7 @@ services:
- mysql
- mongo
- redis
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
environment:
# Mail
- MAIL_HOST=${MAIL_HOST}
@@ -86,6 +82,34 @@ 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}
# Bank Sync
- BANKING_CONNECT=${BANKING_CONNECT}
# Plaid
- PLAID_ENV=${PLAID_ENV}
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
- PLAID_SECRET_DEVELOPMENT=${PLAID_SECRET_DEVELOPMENT}
- PLAID_SECRET_SANDBOX=${b8cf42b441e110451e2f69ad7e1e9f}
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
# Lemon Squeez
- LEMONSQUEEZY_API_KEY=${LEMONSQUEEZY_API_KEY}
- LEMONSQUEEZY_STORE_ID=${LEMONSQUEEZY_STORE_ID}
- LEMONSQUEEZY_WEBHOOK_SECRET=${LEMONSQUEEZY_WEBHOOK_SECRET}
- HOSTED_ON_BIGCAPITAL_CLOUD=${HOSTED_ON_BIGCAPITAL_CLOUD}
# New Relic matrics tracking.
- NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=${NEW_RELIC_DISTRIBUTED_TRACING_ENABLED}
- NEW_RELIC_LOG=${NEW_RELIC_LOG}
- NEW_RELIC_AI_MONITORING_ENABLED=${NEW_RELIC_AI_MONITORING_ENABLED}
- NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED}
- NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED}
database_migration:
container_name: bigcapital-database-migration
build:
@@ -105,9 +129,7 @@ services:
mysql:
container_name: bigcapital-mysql
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build:
context: ./docker/mariadb
environment:
@@ -122,9 +144,7 @@ services:
mongo:
container_name: bigcapital-mongo
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build: ./docker/mongo
expose:
- '27017'
@@ -133,9 +153,7 @@ services:
redis:
container_name: bigcapital-redis
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build:
context: ./docker/redis
expose:
@@ -143,6 +161,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,4 +1,4 @@
FROM ghcr.io/bigcapitalhq/server:latest as build
FROM bigcapitalhq/server:latest as build
ARG DB_HOST= \
DB_USER= \
@@ -34,7 +34,5 @@ WORKDIR /app/packages/server
RUN git clone https://github.com/vishnubob/wait-for-it.git
ADD docker/migration/start.sh /
RUN chmod +x /start.sh
CMD ["/start.sh"]
# Once we listen the mysql port run the migration task.
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest"

View File

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

View File

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

View File

@@ -19,7 +19,8 @@
"@faker-js/faker": "^8.0.2",
"@playwright/test": "^1.32.3",
"husky": "^8.0.3",
"lerna": "^6.4.1"
"lerna": "^8.1.2",
"pnpm": "^9.0.5"
},
"engines": {
"node": "16.x || 17.x || 18.x"

View File

@@ -1,6 +1,8 @@
/node_modules/
/.env
/storage
stdout.log
/dist
/build
/public/imports
dist

View File

@@ -78,6 +78,9 @@ ENV MAIL_HOST=$MAIL_HOST \
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
# New Relic config file.
ENV NEW_RELIC_NO_CONFIG_FILE=true
# Create app directory.
WORKDIR /app
@@ -89,8 +92,8 @@ RUN npm install -g pnpm
# Copy application dependency manifests to the container image.
COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./lerna.json ./lerna.json
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./packages/server/package*.json ./packages/server/
# Install application dependencies

File diff suppressed because it is too large Load Diff

View File

@@ -22,16 +22,18 @@
"dependencies": {
"@casl/ability": "^5.4.3",
"@hapi/boom": "^7.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@types/i18n": "^0.8.7",
"@types/knex": "^0.16.1",
"@types/mathjs": "^6.0.12",
"@types/yup": "^0.29.13",
"accepts": "^1.3.7",
"accounting": "^0.4.1",
"agenda": "^4.2.1",
"agendash": "^3.1.0",
"app-root-path": "^3.0.0",
"async": "^3.2.0",
"axios": "^0.20.0",
"axios": "^1.6.0",
"babel-loader": "^9.1.2",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
@@ -53,9 +55,9 @@
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"express-boom": "^3.0.0",
"express-fileupload": "^1.1.7-alpha.3",
"express-oauth-server": "^2.0.0",
"express-validator": "^6.12.2",
"form-data": "^4.0.0",
"gulp": "^4.0.2",
"gulp-sass": "^5.0.0",
"helmet": "^3.21.0",
@@ -76,9 +78,11 @@
"moment-timezone": "^0.5.43",
"mongodb": "^6.1.0",
"mongoose": "^5.10.0",
"multer": "1.4.5-lts.1",
"mustache": "^3.0.3",
"mysql": "^2.17.1",
"mysql2": "^1.6.5",
"newrelic": "^11.15.0",
"node-cache": "^4.2.1",
"nodemailer": "^6.3.0",
"nodemon": "^1.19.1",
@@ -87,6 +91,7 @@
"objection-filter": "^4.0.1",
"objection-soft-delete": "^1.0.7",
"objection-unique": "^1.2.2",
"plaid": "^10.3.0",
"pluralize": "^8.0.0",
"pug": "^3.0.2",
"puppeteer": "^10.2.0",
@@ -95,12 +100,16 @@
"rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4",
"socket.io": "^4.7.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",
"yup": "^0.28.1"
},
"devDependencies": {
"@types/lodash": "^4.14.158",

BIN
packages/server/public/.DS_Store vendored Normal file

Binary file not shown.

BIN
packages/server/public/imports/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -242,7 +242,7 @@
"account.field.normal.credit": "دائن",
"account.field.normal.debit": "مدين",
"account.field.type": "نوع الحساب",
"account.field.active": "Activity",
"account.field.active": "Active",
"account.field.balance": "الرصيد",
"account.field.created_at": "أنشئت في",
"item.field.type": "نوع الصنف",

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",
@@ -240,28 +241,32 @@
"account.field.normal.credit": "Credit",
"account.field.normal.debit": "Debit",
"account.field.type": "Type",
"account.field.active": "Activity",
"account.field.active": "Active",
"account.field.currency": "Currency",
"account.field.balance": "Balance",
"account.field.bank_balance": "Bank Balance",
"account.field.parent_account": "Parent Account",
"account.field.created_at": "Created at",
"item.field.type": "Item type",
"item.field.type": "Item Type",
"item.field.type.inventory": "Inventory",
"item.field.type.service": "Service",
"item.field.type.non-inventory": "Non inventory",
"item.field.name": "Name",
"item.field.code": "Code",
"item.field.type.non-inventory": "Non Inventory",
"item.field.name": "Item Name",
"item.field.code": "Item 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.cost_price": "Cost Price",
"item.field.sell_price": "Sell Price",
"item.field.cost_account": "Cost Account",
"item.field.sell_account": "Sell Account",
"item.field.sell_description": "Sell Description",
"item.field.inventory_account": "Inventory Account",
"item.field.purchase_description": "Purchase Description",
"item.field.quantity_on_hand": "Quantity on Hand",
"item.field.note": "Note",
"item.field.category": "Category",
"item.field.active": "Active",
"item.field.created_at": "Created at",
"item.field.created_at": "Created At",
"item_category.field.name": "Name",
"item_category.field.description": "Description",
"item_category.field.count": "Count",
@@ -274,8 +279,14 @@
"invoice.field.invoice_message": "Invoice message",
"invoice.field.terms_conditions": "Terms & conditions",
"invoice.field.amount": "Amount",
"invoice.field.exchange_rate": "Exchange Rate",
"invoice.field.payment_amount": "Payment amount",
"invoice.field.due_amount": "Due amount",
"invoice.field.delivered": "Delivered",
"invoice.field.item_name": "Item Name",
"invoice.field.rate": "Rate",
"invoice.field.quantity": "Quantity",
"invoice.field.description": "Description",
"invoice.field.status": "Status",
"invoice.field.status.paid": "Paid",
"invoice.field.status.partially-paid": "Partially paid",
@@ -284,6 +295,8 @@
"invoice.field.status.delivered": "Delivered",
"invoice.field.status.draft": "Draft",
"invoice.field.created_at": "Created at",
"invoice.field.currency": "Currency",
"invoice.field.entries": "Entries",
"estimate.field.amount": "Amount",
"estimate.field.estimate_number": "Estimate number",
"estimate.field.customer": "Customer",
@@ -298,22 +311,31 @@
"estimate.field.status.approved": "Approved",
"estimate.field.status.draft": "Draft",
"estimate.field.created_at": "Created at",
"payment_receive.field.customer": "Customer",
"payment_receive.field.payment_date": "Payment date",
"payment_receive.field.amount": "Amount",
"payment_receive.field.reference_no": "Reference No.",
"payment_receive.field.deposit_account": "Deposit account",
"payment_receive.field.payment_receive_no": "Payment receive No.",
"payment_receive.field.statement": "Statement",
"payment_receive.field.created_at": "Created at",
"payment_receive.field.customer": "Customer",
"payment_receive.field.exchange_rate": "Exchange Rate",
"payment_receive.field.payment_date": "Payment Date",
"payment_receive.field.reference_no": "Reference No.",
"payment_receive.field.deposit_account": "Deposit Account",
"payment_receive.field.entries": "Entries",
"payment_receive.field.invoice": "Invoice",
"payment_receive.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.vendor": "Vendor",
"bill_payment.field.amount": "Amount",
"bill_payment.field.due_amount": "Due amount",
"bill_payment.field.payment_account": "Payment account",
"bill_payment.field.payment_number": "Payment number",
"bill_payment.field.payment_date": "Payment date",
"bill_payment.field.due_amount": "Due Amount",
"bill_payment.field.payment_account": "Payment Account",
"bill_payment.field.payment_number": "Payment No.",
"bill_payment.field.payment_date": "Payment Date",
"bill_payment.field.reference_no": "Reference No.",
"bill_payment.field.description": "Description",
"bill_payment.field.exchange_rate": "Exchange Rate",
"bill_payment.field.note": "Note",
"bill_payment.field.entries.bill": "Bill No.",
"bill_payment.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.reference": "Reference No.",
"bill_payment.field.created_at": "Created at",
"bill.field.vendor": "Vendor",
"bill.field.bill_number": "Bill number",
@@ -341,22 +363,30 @@
"inventory_adjustment.field.description": "Description",
"inventory_adjustment.field.published_at": "Published at",
"inventory_adjustment.field.created_at": "Created at",
"expense.field.payment_date": "Payment date",
"expense.field.payment_account": "Payment account",
"expense.field.payment_date": "Payment Date",
"expense.field.payment_account": "Payment Account",
"expense.field.amount": "Amount",
"expense.field.currency_code": "Currency",
"expense.field.exchange_rate": "Exchange Rate",
"expense.field.reference_no": "Reference No.",
"expense.field.description": "Description",
"expense.field.line_description": "Line Description",
"expense.field.published": "Published",
"expense.field.categories": "Categories",
"expense.field.expense_account": "Expense Account",
"expense.field.publish": "Publish",
"expense.field.status": "Status",
"expense.field.status.draft": "Draft",
"expense.field.status.published": "Published",
"expense.field.created_at": "Created at",
"manual_journal.field.date": "Date",
"manual_journal.field.journal_number": "Journal number",
"manual_journal.field.journal_number": "Journal No.",
"manual_journal.field.reference": "Reference No.",
"manual_journal.field.journal_type": "Journal type",
"manual_journal.field.journal_type": "Journal Type",
"manual_journal.field.amount": "Amount",
"manual_journal.field.description": "Description",
"manual_journal.field.currency": "Currency",
"manual_journal.field.exchange_rate": "Exchange Rate",
"manual_journal.field.status": "Status",
"manual_journal.field.created_at": "Created at",
"receipt.field.amount": "Amount",
@@ -375,8 +405,8 @@
"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.work_phone": "Work Phone Number",
"customer.field.personal_phone": "Personal Phone Number",
"customer.field.company_name": "Company name",
"customer.field.website": "Website",
"customer.field.opening_balance_at": "Opening balance at",
@@ -384,7 +414,7 @@
"customer.field.created_at": "Created at",
"customer.field.balance": "Balance",
"customer.field.status": "Status",
"customer.field.currency": "Curreny",
"customer.field.currency": "Currency",
"customer.field.status.active": "Active",
"customer.field.status.inactive": "Inactive",
"customer.field.status.overdue": "Overdue",
@@ -393,8 +423,8 @@
"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.work_phone": "Work Phone Number",
"vendor.field.personal_phone": "Personal Phone Number",
"vendor.field.company_name": "Company name",
"vendor.field.website": "Website",
"vendor.field.opening_balance_at": "Opening balance at",
@@ -402,13 +432,16 @@
"vendor.field.created_at": "Created at",
"vendor.field.balance": "Balance",
"vendor.field.status": "Status",
"vendor.field.currency": "Curreny",
"vendor.field.note": "Note",
"vendor.field.currency": "Currency",
"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",

View File

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

View File

@@ -137,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

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

View File

@@ -22,12 +22,12 @@ block content
div.invoice__due-amount
div.label #{__('invoice.paper.invoice_amount')}
div.amount #{saleInvoice.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

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

View File

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

View File

@@ -9,6 +9,8 @@ import { DATATYPES_LENGTH } from '@/data/DataTypes';
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
import JWTAuth from '@/api/middleware/jwtAuth';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
@Service()
export default class AuthenticationController extends BaseController {
@Inject()
@@ -28,6 +30,20 @@ export default class AuthenticationController extends BaseController {
asyncMiddleware(this.login.bind(this)),
this.handlerErrors
);
router.use('/register/verify/resend', JWTAuth);
router.use('/register/verify/resend', AttachCurrentTenantUser);
router.post(
'/register/verify/resend',
asyncMiddleware(this.registerVerifyResendMail.bind(this)),
this.handlerErrors
);
router.post(
'/register/verify',
this.signupVerifySchema,
this.validationResult,
asyncMiddleware(this.registerVerify.bind(this)),
this.handlerErrors
);
router.post(
'/register',
this.registerSchema,
@@ -99,6 +115,17 @@ export default class AuthenticationController extends BaseController {
];
}
private get signupVerifySchema(): ValidationChain[] {
return [
check('email')
.exists()
.isString()
.isEmail()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('token').exists().isString(),
];
}
/**
* Reset password schema.
* @returns {ValidationChain[]}
@@ -166,6 +193,58 @@ export default class AuthenticationController extends BaseController {
}
}
/**
* Verifies the provider user's email after signin-up.
* @param {Request} req
* @param {Response}| res
* @param {Function} next
* @returns {Response|void}
*/
private async registerVerify(req: Request, res: Response, next: Function) {
const signUpVerifyDTO: { email: string; token: string } =
this.matchedBodyData(req);
try {
const user = await this.authApplication.signUpConfirm(
signUpVerifyDTO.email,
signUpVerifyDTO.token
);
return res.status(200).send({
type: 'success',
message: 'The given user has verified successfully',
user,
});
} catch (error) {
next(error);
}
}
/**
* Resends the confirmation email to the user.
* @param {Request} req
* @param {Response}| res
* @param {Function} next
*/
private async registerVerifyResendMail(
req: Request,
res: Response,
next: Function
) {
const { user } = req;
try {
const data = await this.authApplication.signUpConfirmResend(user.id);
return res.status(200).send({
type: 'success',
message: 'The given user has verified successfully',
data,
});
} catch (error) {
next(error);
}
}
/**
* Send reset password handler
* @param {Request} req

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -144,10 +144,8 @@ export default class VendorsController extends ContactsController {
try {
const vendor = await this.vendorsApplication.createVendor(
tenantId,
contactDTO,
user
contactDTO
);
return res.status(200).send({
id: vendor.id,
message: 'The vendor has been created successfully.',

View File

@@ -8,10 +8,10 @@ export default class DashboardMetaController {
dashboardService: DashboardService;
/**
*
* Constructor router.
* @returns
*/
router() {
public router() {
const router = Router();
router.get('/boot', this.getDashboardBoot);
@@ -25,7 +25,7 @@ export default class DashboardMetaController {
* @param {Response} res -
* @param {NextFunction} next -
*/
getDashboardBoot = async (
private getDashboardBoot = async (
req: Request,
res: Response,
next: NextFunction

View File

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

View File

@@ -0,0 +1,99 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { ServiceError } from '@/exceptions';
import { ExportApplication } from '@/services/Export/ExportApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export class ExportController extends BaseController {
@Inject()
private exportResourceApp: ExportApplication;
/**
* Router constructor method.
*/
router() {
const router = Router();
router.get(
'/',
[
query('resource').exists(),
query('format').isIn(['csv', 'xlsx']).optional(),
],
this.validationResult,
this.export.bind(this),
this.catchServiceErrors
);
return router;
}
/**
* Imports xlsx/csv to the given resource type.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async export(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const data = await this.exportResourceApp.export(
tenantId,
query.resource,
acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv'
);
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(data);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(data);
}
} catch (error) {
next(error);
}
}
/**
* Transforms service errors to response.
* @param {Error}
* @param {Request} req
* @param {Response} res
* @param {ServiceError} error
*/
private catchServiceErrors(
error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
return res.status(400).send({
errors: [{ type: error.errorType }],
});
}
next(error);
}
}

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,68 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF
]);
// 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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.APAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} 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;
private 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,63 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF
]);
// 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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.ARAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} 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,55 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.balanceSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else {
const sheet = await this.balanceSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} 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,62 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.cashflowSheetApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.cashflowSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.status(200).send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.cashflowSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.cashflowSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter);
return res.status(200).send(cashflow);
}
} 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,81 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const buffer = await this.customerBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
// Retrieves the json format.
} else {
const sheet = await this.customerBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -2,20 +2,21 @@ 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.
*/
router() {
public router() {
const router = Router();
router.get(
@@ -31,7 +32,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
/**
* Validation schema.
*/
get validationSchema(): ValidationChain[] {
private get validationSchema(): ValidationChain[] {
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
@@ -60,21 +61,56 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
* @param {Request} req -
* @param {Response} res -
*/
async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
private async generalLedger(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.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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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),
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.generalLedgerApplication.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.generalLedgerApplication.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.generalLedgerApplication.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
} catch (error) {
next(error);
return res.send(pdfContent);
// 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,76 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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);
// Retrieves the pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const buffer = await this.inventoryItemDetailsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
} else {
const sheet = await this.inventoryItemDetailsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} 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,55 @@ 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),
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.inventoryValuationApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV == acceptType) {
const buffer = await this.inventoryValuationApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xslx buffer format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.inventoryValuationApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.inventoryValuationApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
} catch (error) {
next(error);
return res.status(200).send(pdfContent);
// Retrieves the json format.
} else {
const { data, query, meta } = await this.inventoryValuationApp.sheet(
tenantId,
filter
);
return res.status(200).send({ meta, data, query });
}
}
}

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,58 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
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 if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.journalSheetApp.pdf(tenantId, filter);
return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
} catch (error) {
next(error);
res.send(pdfContent);
} else {
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
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,63 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
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 if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.profitLossSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
} else {
const sheet = await this.profitLossSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

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

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;
private 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,53 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the csv format.
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.salesByItemsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the json table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.salesByItemsApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = this.salesByItemsApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
return res.send(buffer);
// Retrieves the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.salesByItemsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
} catch (error) {
next(error);
return res.send(pdfContent);
} 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,63 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
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 if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.status(200).send(pdfContent);
} else {
const sheet = await this.salesTaxLiabilitySummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -1,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,62 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
const { tenantId } = req;
const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]);
try {
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 if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByCustomersApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
} else {
const sheet = await this.transactionsByCustomersApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} 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,76 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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 pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByVendorsApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.transactionsByVendorsApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

@@ -3,15 +3,16 @@ import { Request, Response, Router, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService';
import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { TrialBalanceSheetApplication } from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class TrialBalanceSheetController extends BaseFinancialReportController {
@Inject()
trialBalanceSheetService: TrialBalanceSheetService;
private trialBalanceSheetApp: TrialBalanceSheetApplication;
/**
* Router constructor.
@@ -73,21 +74,58 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves in json table format.
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
const { table, meta, query } = await this.trialBalanceSheetApp.table(
tenantId,
filter
);
return res.status(200).send({ table, meta, query });
// Retrieves in xlsx format
} else if (acceptType === ACCEPT_TYPE.APPLICATION_XLSX) {
const buffer = await this.trialBalanceSheetApp.xlsx(tenantId, filter);
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves in csv format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
const buffer = await this.trialBalanceSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves in pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.trialBalanceSheetApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves in json format.
} else {
const { data, query, meta } =
await this.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,74 @@ 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,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// 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 pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.vendorBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format.
} else {
const sheet = await this.vendorBalanceSummaryApp.sheet(
tenantId,
filter
);
return res.status(200).send(sheet);
}
} catch (error) {
next(error);

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import { check, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import JWTAuth from '@/api/middleware/jwtAuth';
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import OrganizationService from '@/services/Organization/OrganizationService';
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
@@ -17,7 +18,7 @@ import BaseController from '@/api/controllers/BaseController';
@Service()
export default class OrganizationController extends BaseController {
@Inject()
private organizationService: OrganizationService;
organizationService: OrganizationService;
/**
* Router constructor.
@@ -25,10 +26,13 @@ export default class OrganizationController extends BaseController {
router() {
const router = Router();
// Should before build tenant database the user be authorized and
// most important than that, should be subscribed to any plan.
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.use('/build', SubscriptionMiddleware('main'));
router.post(
'/build',
this.buildOrganizationValidationSchema,

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()
@@ -303,7 +303,7 @@ export default class BillsController extends BaseController {
try {
const bill = await this.billsApplication.getBill(tenantId, billId);
return res.status(200).send(this.transfromToResponse({ bill }));
return res.status(200).send({ bill });
} catch (error) {
next(error);
}
@@ -348,14 +348,11 @@ export default class BillsController extends BaseController {
};
try {
const { bills, pagination, filterMeta } =
await this.billsApplication.getBills(tenantId, filter);
return res.status(200).send({
bills: this.transfromToResponse(bills),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
const billsWithPagination = await this.billsApplication.getBills(
tenantId,
filter
);
return res.status(200).send(billsWithPagination);
} catch (error) {
next(error);
}
@@ -563,6 +560,16 @@ export default class BillsController extends BaseController {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
});
}
if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') {
return res.boom.badRequest(null, {
errors: [
{
type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
code: 2000,
},
],
});
}
}
next(error);
}

View File

@@ -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(),
];
}
@@ -158,15 +158,11 @@ export default class BillsPayments extends BaseController {
const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req);
try {
const entries = await this.billPaymentsPages.getNewPageEntries(
tenantId,
vendorId
);
return res.status(200).send({
entries: this.transfromToResponse(entries),
});
} catch (error) {}
const entries = await this.billPaymentsPages.getNewPageEntries(
tenantId,
vendorId
);
return res.status(200).send({ entries });
}
/**
@@ -183,16 +179,12 @@ export default class BillsPayments extends BaseController {
const { id: paymentReceiveId } = req.params;
try {
const { billPayment, entries } =
const billPaymentsWithEditEntries =
await this.billPaymentsPages.getBillPaymentEditPage(
tenantId,
paymentReceiveId
);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
entries: this.transfromToResponse(entries),
});
return res.status(200).send(billPaymentsWithEditEntries);
} catch (error) {
next(error);
}
@@ -304,9 +296,7 @@ export default class BillsPayments extends BaseController {
tenantId,
billPaymentId
);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
});
return res.status(200).send({ billPayment });
} catch (error) {
next(error);
}
@@ -359,17 +349,12 @@ export default class BillsPayments extends BaseController {
};
try {
const { billPayments, pagination, filterMeta } =
const billPaymentsWithPagination =
await this.billPaymentsApplication.getBillPayments(
tenantId,
billPaymentsFilter
);
return res.status(200).send({
bill_payments: this.transfromToResponse(billPayments),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
return res.status(200).send(billPaymentsWithPagination);
} catch (error) {
next(error);
}

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()
@@ -298,8 +297,7 @@ export default class VendorCreditController extends BaseController {
try {
const vendorCredit = await this.createVendorCreditService.newVendorCredit(
tenantId,
vendorCreditCreateDTO,
user
vendorCreditCreateDTO
);
return res.status(200).send({
@@ -321,20 +319,19 @@ export default class VendorCreditController extends BaseController {
res: Response,
next: NextFunction
) => {
const { id: billId } = req.params;
const { id: vendorCreditId } = req.params;
const { tenantId, user } = req;
const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req);
try {
await this.editVendorCreditService.editVendorCredit(
tenantId,
billId,
vendorCreditEditDTO,
user
vendorCreditId,
vendorCreditEditDTO
);
return res.status(200).send({
id: billId,
id: vendorCreditId,
message: 'The vendor credit has been edited successfully.',
});
} catch (error) {

View File

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

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';
@@ -13,6 +14,7 @@ import DynamicListingService from '@/services/DynamicListing/DynamicListService'
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class PaymentReceivesController extends BaseController {
@@ -117,6 +119,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 +163,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(),
];
}
@@ -328,17 +349,12 @@ export default class PaymentReceivesController extends BaseController {
};
try {
const { paymentReceives, pagination, filterMeta } =
const paymentsReceivedWithPagination =
await this.paymentReceiveApplication.getPaymentReceives(
tenantId,
filter
);
return res.status(200).send({
payment_receives: this.transfromToResponse(paymentReceives),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
return res.status(200).send(paymentsReceivedWithPagination);
} catch (error) {
next(error);
}
@@ -415,38 +431,34 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
try {
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Response in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceiveId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Response in json format.
} else {
const paymentReceive =
await this.paymentReceiveApplication.getPaymentReceive(
tenantId,
paymentReceiveId
);
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send({
payment_receive: this.transfromToResponse(paymentReceive),
});
},
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceive
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
return res.status(200).send({
payment_receive: paymentReceive,
});
} catch (error) {
next(error);
}
}
@@ -480,7 +492,7 @@ export default class PaymentReceivesController extends BaseController {
};
/**
*
* Retrieves the sms details of the given payment receive.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
@@ -508,13 +520,73 @@ export default class PaymentReceivesController extends BaseController {
};
/**
* Handles service errors.
* @param error
* @param req
* @param res
* @param next
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
handleServiceErrors(
public sendPaymentReceiveByMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
const paymentMailDTO: PaymentReceiveMailOptsDTO = this.matchedBodyData(
req,
{
includeOptionals: false,
}
);
try {
await this.paymentReceiveApplication.notifyPaymentByMail(
tenantId,
paymentReceiveId,
paymentMailDTO
);
return res.status(200).send({
code: 200,
message: 'The payment notification has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given payment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getPaymentDefaultMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
try {
const data = await this.paymentReceiveApplication.getPaymentMailOptions(
tenantId,
paymentReceiveId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
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;
}
@@ -312,7 +334,6 @@ export default class SalesEstimatesController extends BaseController {
tenantId,
estimateId
);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been approved successfully.',
@@ -341,7 +362,6 @@ export default class SalesEstimatesController extends BaseController {
tenantId,
estimateId
);
return res.status(200).send({
id: estimateId,
message: 'The sale estimate has been rejected successfully.',
@@ -361,33 +381,30 @@ export default class SalesEstimatesController extends BaseController {
const { id: estimateId } = req.params;
const { tenantId } = req;
try {
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves estimate in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimateId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves estimates in json format.
} else {
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId,
estimateId
);
// Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(this.transfromToResponse({ estimate }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimate
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
return res.status(200).send({ estimate });
}
}
@@ -405,22 +422,11 @@ export default class SalesEstimatesController extends BaseController {
pageSize: 12,
...this.matchedQueryData(req),
};
try {
const { salesEstimates, pagination, filterMeta } =
const salesEstimatesWithPagination =
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(
this.transfromToResponse({
salesEstimates,
pagination,
filterMeta,
})
);
},
});
return res.status(200).send(salesEstimatesWithPagination);
} catch (error) {
next(error);
}
@@ -478,6 +484,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,14 +10,12 @@ import {
ISaleInvoiceCreateDTO,
SaleInvoiceAction,
AbilitySubject,
SendInvoiceMailDTO,
} from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
@Service()
export default class SaleInvoicesController extends BaseController {
@Inject()
@@ -145,6 +143,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;
}
@@ -360,7 +400,6 @@ export default class SaleInvoicesController extends BaseController {
saleInvoiceId,
user
);
return res.status(200).send({
id: saleInvoiceId,
message: 'The sale invoice has been deleted successfully.',
@@ -375,43 +414,35 @@ 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 accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves invoice in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves invoice in json format.
} else {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
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);
return res.status(200).send({ saleInvoice });
}
}
/**
@@ -434,14 +465,10 @@ export default class SaleInvoicesController extends BaseController {
...this.matchedQueryData(req),
};
try {
const { salesInvoices, filterMeta, pagination } =
const salesInvoicesWithPagination =
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
return res.status(200).send(salesInvoicesWithPagination);
} catch (error) {
next(error);
}
@@ -468,9 +495,7 @@ export default class SaleInvoicesController extends BaseController {
tenantId,
customerId
);
return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
});
return res.status(200).send({ salesInvoices });
} catch (error) {
next(error);
}
@@ -498,7 +523,6 @@ export default class SaleInvoicesController extends BaseController {
invoiceId,
writeoffDTO
);
return res.status(200).send({
id: saleInvoice.id,
message: 'The given sale invoice has been written-off successfully.',
@@ -630,6 +654,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,14 +1,19 @@
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';
import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service()
export default class SalesReceiptsController extends BaseController {
@@ -46,6 +51,27 @@ 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 +143,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()
@@ -205,7 +231,6 @@ export default class SalesReceiptsController extends BaseController {
tenantId,
saleReceiptId
);
return res.status(200).send({
id: saleReceiptId,
message: 'Sale receipt has been deleted successfully.',
@@ -294,15 +319,10 @@ export default class SalesReceiptsController extends BaseController {
...this.matchedQueryData(req),
};
try {
const { data, pagination, filterMeta } =
const salesReceiptsWithPagination =
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
const response = this.transfromToResponse({
data,
pagination,
filterMeta,
});
return res.status(200).send(response);
return res.status(200).send(salesReceiptsWithPagination);
} catch (error) {
next(error);
}
@@ -314,36 +334,34 @@ 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) {
const { id: saleReceiptId } = req.params;
const { tenantId } = req;
try {
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves receipt in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceiptId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves receipt in json format.
} else {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId,
saleReceiptId
);
res.format({
'application/json': () => {
return res
.status(200)
.send(this.transfromToResponse({ saleReceipt }));
},
'application/pdf': async () => {
const pdfContent =
await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceipt
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
return res.status(200).send({ saleReceipt });
}
}
@@ -405,6 +423,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

@@ -0,0 +1,88 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Service, Inject } from 'typedi';
import { body } from 'express-validator';
import JWTAuth from '@/api/middleware/jwtAuth';
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import SubscriptionService from '@/services/Subscription/SubscriptionService';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController';
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
@Service()
export class SubscriptionController extends BaseController {
@Inject()
private subscriptionService: SubscriptionService;
@Inject()
private lemonSqueezyService: LemonSqueezyService;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.post(
'/lemon/checkout_url',
[body('variantId').exists().trim()],
this.validationResult,
this.getCheckoutUrl.bind(this)
);
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
return router;
}
/**
* Retrieve all subscriptions of the authenticated user's tenant.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getSubscriptions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
const subscriptions = await this.subscriptionService.getSubscriptions(
tenantId
);
return res.status(200).send({ subscriptions });
} catch (error) {
next(error);
}
}
/**
* Retrieves the LemonSqueezy checkout url.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getCheckoutUrl(
req: Request,
res: Response,
next: NextFunction
) {
const { variantId } = this.matchedBodyData(req);
const { user } = req;
try {
const checkout = await this.lemonSqueezyService.getCheckout(
variantId,
user
);
return res.status(200).send(checkout);
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1 @@
export * from './SubscriptionController';

View File

@@ -0,0 +1,69 @@
import { Router } from 'express';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
import { Request, Response } from 'express';
import { Inject, Service } from 'typedi';
import BaseController from '../BaseController';
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
@Service()
export class Webhooks extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
@Inject()
private lemonWebhooksService: LemonSqueezyWebhooks;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use('/plaid', PlaidWebhookTenantBootMiddleware);
router.post('/plaid', this.plaidWebhooks.bind(this));
router.post('/lemon', this.lemonWebhooks.bind(this));
return router;
}
/**
* Listens to Lemon Squeezy webhooks events.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async lemonWebhooks(req: Request, res: Response) {
const data = req.body;
const signature = req.headers['x-signature'] ?? '';
const rawBody = req.rawBody;
await this.lemonWebhooksService.handlePostWebhook(rawBody, data, signature);
return res.status(200).send();
}
/**
* Listens to Plaid webhooks.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async plaidWebhooks(req: Request, res: Response) {
const { tenantId } = req;
const {
webhook_type: webhookType,
webhook_code: webhookCode,
item_id: plaidItemId,
} = req.body;
await this.plaidApp.webhooks(
tenantId,
plaidItemId,
webhookType,
webhookCode
);
return res.status(200).send({ code: 200, message: 'ok' });
}
}

View File

@@ -4,6 +4,7 @@ import { Container } from 'typedi';
// Middlewares
import JWTAuth from '@/api/middleware/jwtAuth';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized';
import SettingsMiddleware from '@/api/middleware/SettingsMiddleware';
@@ -36,6 +37,7 @@ import Resources from './controllers/Resources';
import ExchangeRates from '@/api/controllers/ExchangeRates';
import Media from '@/api/controllers/Media';
import Ping from '@/api/controllers/Ping';
import { SubscriptionController } from '@/api/controllers/Subscription';
import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments';
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
import Jobs from './controllers/Jobs';
@@ -56,6 +58,10 @@ import { ProjectsController } from './controllers/Projects/Projects';
import { ProjectTasksController } from './controllers/Projects/Tasks';
import { ProjectTimesController } from './controllers/Projects/Times';
import { TaxRatesController } from './controllers/TaxRates/TaxRates';
import { ImportController } from './controllers/Import/ImportController';
import { BankingController } from './controllers/Banking/BankingController';
import { Webhooks } from './controllers/Webhooks/Webhooks';
import { ExportController } from './controllers/Export/ExportController';
export default () => {
const app = Router();
@@ -67,10 +73,12 @@ export default () => {
app.use('/auth', Container.get(Authentication).router());
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
app.use('/subscription', Container.get(SubscriptionController).router());
app.use('/organization', Container.get(Organization).router());
app.use('/ping', Container.get(Ping).router());
app.use('/jobs', Container.get(Jobs).router());
app.use('/account', Container.get(Account).router());
app.use('/webhooks', Container.get(Webhooks).router());
// - Dashboard routes.
// ---------------------------
@@ -79,6 +87,7 @@ export default () => {
dashboard.use(JWTAuth);
dashboard.use(AttachCurrentTenantUser);
dashboard.use(TenancyMiddleware);
dashboard.use(SubscriptionMiddleware('main'));
dashboard.use(EnsureTenantIsInitialized);
dashboard.use(SettingsMiddleware);
dashboard.use(I18nAuthenticatedMiddlware);
@@ -118,6 +127,7 @@ export default () => {
Container.get(InventoryItemsCostController).router()
);
dashboard.use('/cashflow', Container.get(CashflowController).router());
dashboard.use('/banking', Container.get(BankingController).router());
dashboard.use('/roles', Container.get(RolesController).router());
dashboard.use(
'/transactions-locking',
@@ -131,9 +141,11 @@ export default () => {
dashboard.use('/warehouses', Container.get(WarehousesController).router());
dashboard.use('/projects', Container.get(ProjectsController).router());
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
dashboard.use('/import', Container.get(ImportController).router());
dashboard.use('/export', Container.get(ExportController).router())
dashboard.use('/', Container.get(ProjectTasksController).router());
dashboard.use('/', Container.get(ProjectTimesController).router());
dashboard.use('/', Container.get(WarehousesItemController).router());
dashboard.use('/dashboard', Container.get(DashboardController).router());

View File

@@ -0,0 +1,29 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
export default (subscriptionSlug = 'main') =>
async (req: Request, res: Response, next: NextFunction) => {
const { tenant, tenantId } = req;
const { subscriptionRepository } = Container.get('repositories');
if (!tenant) {
throw new Error('Should load `TenancyMiddlware` before this middleware.');
}
const subscription = await subscriptionRepository.getBySlugInTenant(
subscriptionSlug,
tenantId
);
// Validate in case there is no any already subscription.
if (!subscription) {
return res.boom.badRequest('Tenant has no subscription.', {
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
});
}
// Validate in case the subscription is inactive.
else if (subscription.inactive()) {
return res.boom.badRequest(null, {
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
});
}
next();
};

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

@@ -1,6 +1,6 @@
import dotenv from 'dotenv';
import path from 'path';
import { toInteger } from 'lodash';
import { defaultTo, toInteger } from 'lodash';
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
dotenv.config();
@@ -55,9 +55,10 @@ module.exports = {
mail: {
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
secure: !!parseInt(process.env.MAIL_SECURE, 10),
secure: parseBoolean(defaultTo(process.env.MAIL_SECURE, false), false),
username: process.env.MAIL_USERNAME,
password: process.env.MAIL_PASSWORD,
from: process.env.MAIL_FROM_ADDRESS,
},
/**
@@ -152,6 +153,13 @@ module.exports = {
),
},
/**
* Sign-up email confirmation
*/
signupConfirmation: {
enabled: parseBoolean<boolean>(process.env.SIGNUP_EMAIL_CONFIRMATION, false),
},
/**
* Puppeteer remote browserless connection.
*/
@@ -168,4 +176,53 @@ module.exports = {
* to application detarmines to upgrade.
*/
databaseBatch: 4,
/**
* Exchange rate.
*/
exchangeRate: {
service: 'open-exchange-rate',
openExchangeRate: {
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
},
},
/**
* Bank Synchronization.
*/
bankSync: {
enabled: parseBoolean(defaultTo(process.env.BANKING_CONNECT, false), false),
provider: 'plaid',
},
/**
* Plaid.
*/
plaid: {
env: process.env.PLAID_ENV || 'sandbox',
clientId: process.env.PLAID_CLIENT_ID,
secretDevelopment: process.env.PLAID_SECRET_DEVELOPMENT,
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
linkWebhook: process.env.PLAID_LINK_WEBHOOK,
},
/**
* Lemon Squeezy.
*/
lemonSqueezy: {
key: process.env.LEMONSQUEEZY_API_KEY,
storeId: process.env.LEMONSQUEEZY_STORE_ID,
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
},
/**
* Bigcapital (Cloud).
* NOTE: DO NOT CHANGE THIS OPTION OR ADD THIS ENV VAR.
*/
hostedOnBigcapitalCloud: parseBoolean(
defaultTo(process.env.HOSTED_ON_BIGCAPITAL_CLOUD, false),
false
),
};

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: {
@@ -118,6 +136,11 @@ export default {
type: 'number',
},
},
inventory: {
cost_compute_running: {
type: 'boolean',
},
},
accounts: {
account_code_required: {
type: 'boolean',
@@ -147,6 +170,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
export default class NotAllowedChangeSubscriptionPlan {
constructor() {
this.name = "NotAllowedChangeSubscriptionPlan";
}
}

View File

@@ -1,3 +1,4 @@
import NotAllowedChangeSubscriptionPlan from './NotAllowedChangeSubscriptionPlan';
import ServiceError from './ServiceError';
import ServiceErrors from './ServiceErrors';
import TenantAlreadyInitialized from './TenantAlreadyInitialized';
@@ -6,6 +7,7 @@ import TenantDBAlreadyExists from './TenantDBAlreadyExists';
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
export {
NotAllowedChangeSubscriptionPlan,
ServiceError,
ServiceErrors,
TenantAlreadyInitialized,

View File

@@ -1,13 +1,12 @@
import {
IAgingPeriod,
IAgingPeriodTotal,
IAgingAmount,
IAgingSummaryQuery,
IAgingSummaryTotal,
IAgingSummaryContact,
IAgingSummaryData,
} from './AgingReport';
import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
vendorsIds: number[];
@@ -25,12 +24,22 @@ export interface IAPAgingSummaryData extends IAgingSummaryData {
export type IAPAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta {
baseCurrency: string;
organizationName: string;
export interface IARAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
}
export interface IAPAgingSummaryMeta {
baseCurrency: string;
organizationName: string;
export interface IAPAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
}
export interface IAPAgingSummaryTable extends IFinancialTable {
query: IAPAgingSummaryQuery;
meta: IAPAgingSummaryMeta;
}
export interface IAPAgingSummarySheet {
data: IAPAgingSummaryData;
meta: IAPAgingSummaryMeta;
query: IAPAgingSummaryQuery;
columns: any;
}

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,16 @@ export interface IARAgingSummaryMeta {
organizationName: string;
baseCurrency: string;
}
export interface IARAgingSummaryTable extends IFinancialTable {
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
}
export interface IARAgingSummarySheet {
data: IARAgingSummaryData;
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
columns: IARAgingSummaryColumns;
}

View File

@@ -6,12 +6,15 @@ export interface IAccountDTO {
code: string;
description: string;
accountType: string;
parentAccountId: number;
parentAccountId?: number;
active: boolean;
bankBalance?: number;
accountMask?: string;
}
export interface IAccountCreateDTO extends IAccountDTO {
currencyCode?: string;
plaidAccountId?: string;
}
export interface IAccountEditDTO extends IAccountDTO {}
@@ -33,6 +36,7 @@ export interface IAccount {
type?: any[];
accountNormal: string;
accountParentType: string;
bankBalance: string;
}
export enum AccountNormal {
@@ -154,10 +158,9 @@ export enum AccountAction {
TransactionsLocking = 'TransactionsLocking',
}
export enum TaxRateAction {
CREATE = 'Create',
EDIT = 'Edit',
DELETE = 'Delete',
VIEW = 'View',
}
}

View File

@@ -1,5 +1,7 @@
import { INumberFormatQuery } from './FinancialStatements';
import {
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
export interface IAgingPeriodTotal extends IAgingPeriod {
total: IAgingAmount;
@@ -42,3 +44,8 @@ export interface IAgingSummaryTotal {
export interface IAgingSummaryData {
total: IAgingSummaryTotal;
}
export interface IAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}

View File

@@ -66,16 +66,27 @@ export interface IAuthResetedPasswordEventPayload {
password: string;
}
export interface IAuthSendingResetPassword {
user: ISystemUser,
user: ISystemUser;
token: string;
}
export interface IAuthSendedResetPassword {
user: ISystemUser,
user: ISystemUser;
token: string;
}
export interface IAuthGetMetaPOJO {
export interface IAuthGetMetaPOJO {
signupDisabled: boolean;
}
}
export interface IAuthSignUpVerifingEventPayload {
email: string;
verifyToken: string;
userId: number;
}
export interface IAuthSignUpVerifiedEventPayload {
email: string;
verifyToken: string;
userId: number;
}

View File

@@ -2,7 +2,9 @@ import {
INumberFormatQuery,
IFormatNumberSettings,
IFinancialSheetBranchesQuery,
IFinancialSheetCommonMeta,
} from './FinancialStatements';
import { IFinancialTable } from './Table';
// Balance sheet schema nodes types.
export enum BALANCE_SHEET_SCHEMA_NODE_TYPE {
@@ -62,10 +64,9 @@ export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
}
// Balance sheet meta.
export interface IBalanceSheetMeta {
isCostComputeRunning: boolean;
organizationName: string;
baseCurrency: string;
export interface IBalanceSheetMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}
export interface IBalanceSheetFormatNumberSettings
@@ -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,10 @@
import { INumberFormatQuery } from './FinancialStatements';
import {
IFinancialSheetCommonMeta,
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;
@@ -79,8 +82,8 @@ export interface ICashFlowStatementAggregateSection
export interface ICashFlowCashBeginningNode
extends ICashFlowStatementCommonSection {
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING;
}
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING;
}
export type ICashFlowStatementSection =
| ICashFlowStatementAccountSection
@@ -89,10 +92,10 @@ export type ICashFlowStatementSection =
| ICashFlowStatementCommonSection;
export interface ICashFlowStatementColumn {}
export interface ICashFlowStatementMeta {
isCostComputeRunning: boolean;
organizationName: string;
baseCurrency: string;
export interface ICashFlowStatementMeta extends IFinancialSheetCommonMeta {
formattedToDate: string;
formattedFromDate: string;
formattedDateRange: string;
}
export interface ICashFlowStatementDOO {
@@ -101,6 +104,11 @@ export interface ICashFlowStatementDOO {
query: ICashFlowStatementQuery;
}
export interface ICashFlowStatementTable extends IFinancialTable {
meta: ICashFlowStatementMeta;
query: ICashFlowStatementQuery;
}
export interface ICashFlowStatementService {
cashFlow(
tenantId: number,
@@ -225,3 +233,38 @@ export interface ICashflowTransactionSchema {
}
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
export interface ICategorizeCashflowTransactioDTO {
creditAccountId: number;
referenceNo: string;
transactionNumber: string;
transactionType: string;
exchangeRate: number;
description: string;
branchId: number;
}
export interface IUncategorizedCashflowTransaction {
id?: number;
amount: number;
date: Date;
currencyCode: string;
accountId: number;
description: string;
referenceNo: string;
categorizeRefType: string;
categorizeRefId: number;
categorized: boolean;
}
export interface CreateUncategorizedTransactionDTO {
date: Date | string;
accountId: number;
amount: number;
currencyCode: string;
payee?: string;
description?: string;
referenceNo?: string | null;
plaidTransactionId?: string | null;
}

View File

@@ -1,5 +1,6 @@
import { Knex } from 'knex';
import { IAccount } from './Account';
import { IUncategorizedCashflowTransaction } from './CashFlow';
export interface ICashflowAccountTransactionsFilter {
page: number;
@@ -45,9 +46,13 @@ export interface ICashflowCommandDTO {
publish: boolean;
branchId?: number;
plaidTransactionId?: string;
}
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {}
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {
plaidAccountId?: string;
uncategorizedTransactionId?: number;
}
export interface ICashflowTransaction {
id?: number;
@@ -79,6 +84,8 @@ export interface ICashflowTransaction {
isCashDebit?: boolean;
isCashCredit?: boolean;
uncategorizedTransactionId?: number;
}
export interface ICashflowTransactionLine {
@@ -121,8 +128,39 @@ export interface ICommandCashflowDeletedPayload {
trx: Knex.Transaction;
}
export interface ICashflowTransactionCategorizedPayload {
tenantId: number;
cashflowTransactionId: number;
cashflowTransaction: ICashflowTransaction;
trx: Knex.Transaction;
}
export interface ICashflowTransactionUncategorizingPayload {
tenantId: number;
uncategorizedTransaction: IUncategorizedCashflowTransaction;
trx: Knex.Transaction;
}
export interface ICashflowTransactionUncategorizedPayload {
tenantId: number;
uncategorizedTransaction: IUncategorizedCashflowTransaction;
oldUncategorizedTransaction: IUncategorizedCashflowTransaction;
trx: Knex.Transaction;
}
export enum CashflowAction {
Create = 'Create',
Delete = 'Delete',
View = 'View',
}
export interface CategorizeTransactionAsExpenseDTO {
expenseAccountId: number;
exchangeRate: number;
referenceNo: string;
description: string;
branchId?: number;
}
export interface IGetUncategorizedTransactionsQuery {
page?: number;
pageSize?: number;
}

View File

@@ -1,11 +1,11 @@
import { INumberFormatQuery } from './FinancialStatements';
import {
IContactBalanceSummaryQuery,
IContactBalanceSummaryAmount,
IContactBalanceSummaryPercentage,
IContactBalanceSummaryTotal,
} from './ContactBalanceSummary';
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface ICustomerBalanceSummaryQuery
extends IContactBalanceSummaryQuery {
@@ -19,7 +19,7 @@ export interface ICustomerBalanceSummaryPercentage
extends IContactBalanceSummaryPercentage {}
export interface ICustomerBalanceSummaryCustomer {
id: number,
id: number;
customerName: string;
total: ICustomerBalanceSummaryAmount;
percentageOfColumn?: ICustomerBalanceSummaryPercentage;
@@ -36,9 +36,15 @@ export interface ICustomerBalanceSummaryData {
total: ICustomerBalanceSummaryTotal;
}
export interface ICustomerBalanceSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}
export interface ICustomerBalanceSummaryStatement {
data: ICustomerBalanceSummaryData;
query: ICustomerBalanceSummaryQuery;
meta: ICustomerBalanceSummaryMeta;
}
export interface ICustomerBalanceSummaryService {
@@ -47,3 +53,8 @@ export interface ICustomerBalanceSummaryService {
query: ICustomerBalanceSummaryQuery
): Promise<ICustomerBalanceSummaryStatement>;
}
export interface ICustomerBalanceSummaryTable extends IFinancialTable {
query: ICustomerBalanceSummaryQuery;
meta: ICustomerBalanceSummaryMeta;
}

View File

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

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