mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
Compare commits
202 Commits
v0.20.6
...
migrate-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a10b58a7c6 | ||
|
|
72818759a5 | ||
|
|
270b421a6c | ||
|
|
ddaea20d16 | ||
|
|
7e82080cb7 | ||
|
|
3bf5f4be86 | ||
|
|
6f870ea1e1 | ||
|
|
ee284196eb | ||
|
|
52362a43ab | ||
|
|
fdfb766587 | ||
|
|
1773df1858 | ||
|
|
abf92ac83f | ||
|
|
385d84d654 | ||
|
|
2bf58d9cb4 | ||
|
|
ba176394c8 | ||
|
|
1869ba216f | ||
|
|
b72f85b394 | ||
|
|
8bacf3a001 | ||
|
|
505c4b28a5 | ||
|
|
3ad34ba56f | ||
|
|
a819d6c1ba | ||
|
|
1b15261adb | ||
|
|
4938db704e | ||
|
|
3191076762 | ||
|
|
b046edf337 | ||
|
|
515a984714 | ||
|
|
77bbf6828d | ||
|
|
caf235e2b5 | ||
|
|
9f9b75cd31 | ||
|
|
736cedd63d | ||
|
|
cd84872a61 | ||
|
|
a6932d76f3 | ||
|
|
336171081e | ||
|
|
8a12caf48d | ||
|
|
cb8fd68d46 | ||
|
|
dc52f784b6 | ||
|
|
330192c042 | ||
|
|
1f32a7c59a | ||
|
|
83dfaa00fd | ||
|
|
93bf6d9d3d | ||
|
|
bfff56c470 | ||
|
|
87e9cd64e8 | ||
|
|
0a112c5655 | ||
|
|
70211980aa | ||
|
|
2ba31148ca | ||
|
|
1906d9f3f5 | ||
|
|
d640dc1f40 | ||
|
|
8cd1b36a02 | ||
|
|
5a8d9cc7e8 | ||
|
|
6323e2ffec | ||
|
|
7af2e7ccbc | ||
|
|
baf4c691d6 | ||
|
|
c633fa8522 | ||
|
|
1d54947764 | ||
|
|
477da0e7c0 | ||
|
|
b9963aa241 | ||
|
|
994c441bb8 | ||
|
|
0a5115fc20 | ||
|
|
11d7a40326 | ||
|
|
46719ef361 | ||
|
|
14ae978bde | ||
|
|
beec09788e | ||
|
|
391dc77071 | ||
|
|
38f2004e56 | ||
|
|
5a5eac246b | ||
|
|
a7bafd4f62 | ||
|
|
a25ab39647 | ||
|
|
7dd09e2903 | ||
|
|
6ab461a212 | ||
|
|
fabc88c81a | ||
|
|
3a19518440 | ||
|
|
56b5e3469e | ||
|
|
542763ddf5 | ||
|
|
1010d97a92 | ||
|
|
d5dacaa988 | ||
|
|
154ade9647 | ||
|
|
5b75fa9286 | ||
|
|
05cf94940e | ||
|
|
03b0d2519b | ||
|
|
000c3e40e1 | ||
|
|
ffb06f5194 | ||
|
|
73ab92e693 | ||
|
|
dd1392cdc8 | ||
|
|
17b3bbe1d8 | ||
|
|
e02ad1e795 | ||
|
|
df8391201f | ||
|
|
aa4aaeb612 | ||
|
|
09b98664c5 | ||
|
|
be46147d9a | ||
|
|
1fe7d58c8c | ||
|
|
4f57782be4 | ||
|
|
7b5f0d3930 | ||
|
|
831fb9180c | ||
|
|
2594e37dc7 | ||
|
|
ca44d6346d | ||
|
|
df41de7239 | ||
|
|
459bf4cd55 | ||
|
|
3537a05ea2 | ||
|
|
da47418f17 | ||
|
|
d5b0546301 | ||
|
|
b6f3c0145f | ||
|
|
c5c85bdfbe | ||
|
|
63a95df534 | ||
|
|
6103f1e4c7 | ||
|
|
3e591beb03 | ||
|
|
c6db54175f | ||
|
|
f7bf24acb3 | ||
|
|
ddffe630ff | ||
|
|
2c54092591 | ||
|
|
7e65f3f642 | ||
|
|
7df6aa4110 | ||
|
|
7df316aa56 | ||
|
|
53ab40a075 | ||
|
|
d115ebde12 | ||
|
|
0e99ac88d3 | ||
|
|
05f4b49b58 | ||
|
|
5d6f901d33 | ||
|
|
908bbb9fa6 | ||
|
|
6c1870be8f | ||
|
|
19080a67ab | ||
|
|
f5834c72c6 | ||
|
|
7ee3392d3e | ||
|
|
c58822fd6c | ||
|
|
ba8091d697 | ||
|
|
d668d60ed5 | ||
|
|
a34b7a2106 | ||
|
|
6f12127095 | ||
|
|
b4e5bbf376 | ||
|
|
a11530d190 | ||
|
|
43d4425da5 | ||
|
|
4c1909cb73 | ||
|
|
a7b6b7a03e | ||
|
|
1f46275bde | ||
|
|
aa7e5d4ae9 | ||
|
|
bb482df3ce | ||
|
|
f878786646 | ||
|
|
652851a1a9 | ||
|
|
850f4956cb | ||
|
|
94223b6ebf | ||
|
|
e9d34e19ad | ||
|
|
107532fe26 | ||
|
|
c32aff82ee | ||
|
|
de8a867d33 | ||
|
|
17a8aba23f | ||
|
|
04b601626b | ||
|
|
802775c118 | ||
|
|
b6baa80134 | ||
|
|
b2d0f2ed3c | ||
|
|
d23f33bae4 | ||
|
|
22ea557337 | ||
|
|
b3ebbb429c | ||
|
|
51218797af | ||
|
|
2d18a6573e | ||
|
|
2646ad5bc4 | ||
|
|
51aec8d8b3 | ||
|
|
638bd95d6f | ||
|
|
f2fcc3a649 | ||
|
|
48795748d8 | ||
|
|
6ba54a994a | ||
|
|
6687db4085 | ||
|
|
ba1d9b3f28 | ||
|
|
51905825fd | ||
|
|
bd5e33855a | ||
|
|
f7fbc0e31c | ||
|
|
cb06fa342c | ||
|
|
581229053a | ||
|
|
209da69b8f | ||
|
|
d09aebcebb | ||
|
|
0cc80bc179 | ||
|
|
79dcc592bc | ||
|
|
687111851a | ||
|
|
dbbaa387bd | ||
|
|
470bfd32f7 | ||
|
|
5fddd080fd | ||
|
|
e10c530b4b | ||
|
|
12189f018d | ||
|
|
0111b0e6ff | ||
|
|
0930b0428d | ||
|
|
289f40014e | ||
|
|
01cc0568f9 | ||
|
|
42ee8ed9fa | ||
|
|
1dae65cb74 | ||
|
|
ce40d67ea2 | ||
|
|
26088a71ee | ||
|
|
cadf6b81a0 | ||
|
|
728b4cacd9 | ||
|
|
b4d3ac2f96 | ||
|
|
bc8e440814 | ||
|
|
4c0f9a0aef | ||
|
|
c321d90575 | ||
|
|
03e6372f14 | ||
|
|
c0481f67ad | ||
|
|
b7f316d25a | ||
|
|
dffd818396 | ||
|
|
bbc19df6b4 | ||
|
|
c8c2786893 | ||
|
|
d79f26f1b5 | ||
|
|
32ba6f9a6c | ||
|
|
65788e344a | ||
|
|
abc242d117 | ||
|
|
6dd4968327 | ||
|
|
d805703c08 |
@@ -159,6 +159,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "nklmantey",
|
||||||
|
"name": "Mantey",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/90279429?v=4",
|
||||||
|
"profile": "https://nklmantey.com/",
|
||||||
|
"contributions": [
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -2,6 +2,66 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
|
# [0.22.0]
|
||||||
|
|
||||||
|
* feat: estimate, receipt, credit note mail preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/757
|
||||||
|
* feat: Add discount to transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/758
|
||||||
|
* fix: update financial forms to use new formatted amount utilities and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/760
|
||||||
|
* fix: total lines style by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/761
|
||||||
|
* fix: discount & adjustment sale transactions bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/762
|
||||||
|
* fix: discount transactions GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/763
|
||||||
|
|
||||||
|
# [0.21.2]
|
||||||
|
|
||||||
|
* hotbug: upload attachments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/755
|
||||||
|
|
||||||
|
# [0.21.1]
|
||||||
|
|
||||||
|
* fix: download invoice document on payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/750
|
||||||
|
* fix: attach branding template attrs to payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/751
|
||||||
|
* fix: make manual entries adjust decimal credit/debit amounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/754
|
||||||
|
* feat: allow quantity of entries accept decimal value by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/753
|
||||||
|
|
||||||
|
# [0.21.0]
|
||||||
|
|
||||||
|
* fix: Credit and debit totals not balancing when decimal values are used by @nklmantey in https://github.com/bigcapitalhq/bigcapital/pull/722
|
||||||
|
* docs: add nklmantey as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/725
|
||||||
|
* feat: track more services events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/721
|
||||||
|
* feat: Invoice mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/723
|
||||||
|
* fix: change the send mail button on invoice drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/730
|
||||||
|
* refactor: notification mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/731
|
||||||
|
* fix: attach payment link in sending invoice mail receipt by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/732
|
||||||
|
* fix: send invoice drawer layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/733
|
||||||
|
* fix: hook up cc and bcc fields to mail sender by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/734
|
||||||
|
* fix: company logo does not show up in mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/736
|
||||||
|
* fix: change default invoice mail message by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/737
|
||||||
|
* fix: typing invoice send mail fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/738
|
||||||
|
* fix: clean up ivnoice mail receipt preview component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/739
|
||||||
|
* feat: add shared package to pdf templates to render in the server and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/735
|
||||||
|
* feat: getting invoice preview on send mail view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/740
|
||||||
|
* fix: style SSR invoice paper template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/741
|
||||||
|
* fix: send invoice receipt addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/742
|
||||||
|
* fix: due invoice server invoice by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/744
|
||||||
|
* fix: `BIG-265` forgot password text by @ibutiti in https://github.com/bigcapitalhq/bigcapital/pull/745
|
||||||
|
* Crims on sv translation by @Crims-on in https://github.com/bigcapitalhq/bigcapital/pull/671
|
||||||
|
* feat: Added Spanish language to the App 🇪🇸 by @angelosorno in https://github.com/bigcapitalhq/bigcapital/pull/530
|
||||||
|
* fix: mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/746
|
||||||
|
* fix: company logo of the template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/747
|
||||||
|
* fix: monorepo dependencies scope by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/748
|
||||||
|
|
||||||
|
# [0.20.6]
|
||||||
|
|
||||||
|
* fix: Import category column of item resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/710
|
||||||
|
* fix: Parse the uppercase values in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/711
|
||||||
|
* chore: Move i18nApply localization to the account transformer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/713
|
||||||
|
* fix: Sync Plaid credit card account type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/714
|
||||||
|
* fix: Sync account normal of cashflow GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/715
|
||||||
|
* feat: Add quantity column to pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/716
|
||||||
|
* feat: Pre-line invoice statements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/717
|
||||||
|
* feat: Invoice number in downloaded pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/718
|
||||||
|
* feat: Track events of pdf documents views by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/719
|
||||||
|
* fix: Customer note does not appear in pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/720
|
||||||
|
|
||||||
# [0.20.5]
|
# [0.20.5]
|
||||||
|
|
||||||
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
|
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ 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/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -4,12 +4,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "lerna run dev",
|
"dev": "lerna run dev",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||||
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"test:e2e": "playwright test",
|
"server2:start": "lerna run start:dev --scope \"@bigcapital/server2\"",
|
||||||
|
"test:watch": "lerna run test:watch",
|
||||||
|
"test:e2e": "lerna run test:e2e",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
102
packages/server-nest/.env.example
Normal file
102
packages/server-nest/.env.example
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Mail
|
||||||
|
MAIL_HOST=
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_PORT=
|
||||||
|
MAIL_SECURE=
|
||||||
|
MAIL_FROM_NAME=
|
||||||
|
MAIL_FROM_ADDRESS=
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_USER=bigcapital
|
||||||
|
DB_PASSWORD=bigcapital
|
||||||
|
DB_ROOT_PASSWORD=root
|
||||||
|
DB_CHARSET=utf8
|
||||||
|
|
||||||
|
# System database
|
||||||
|
SYSTEM_DB_NAME=bigcapital_system
|
||||||
|
# SYSTEM_DB_USER=
|
||||||
|
# SYSTEM_DB_PASSWORD=
|
||||||
|
# SYSTEM_DB_NAME=
|
||||||
|
# SYSTEM_DB_CHARSET=
|
||||||
|
|
||||||
|
# Tenant databases
|
||||||
|
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
||||||
|
# TENANT_DB_HOST=
|
||||||
|
# TENANT_DB_USER=
|
||||||
|
# TENANT_DB_PASSWORD=
|
||||||
|
# TENANT_DB_CHARSET=
|
||||||
|
|
||||||
|
# Application
|
||||||
|
BASE_URL=http://example.com
|
||||||
|
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
||||||
|
|
||||||
|
# Jobs MongoDB
|
||||||
|
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
||||||
|
|
||||||
|
# App proxy
|
||||||
|
PUBLIC_PROXY_PORT=80
|
||||||
|
PUBLIC_PROXY_SSL_PORT=443
|
||||||
|
|
||||||
|
# Agendash
|
||||||
|
AGENDASH_AUTH_USER=agendash
|
||||||
|
AGENDASH_AUTH_PASSWORD=123123
|
||||||
|
|
||||||
|
# Sign-up restrictions
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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=
|
||||||
|
PLAID_LINK_WEBHOOK=
|
||||||
|
|
||||||
|
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
||||||
|
LEMONSQUEEZY_API_KEY=
|
||||||
|
LEMONSQUEEZY_STORE_ID=
|
||||||
|
LEMONSQUEEZY_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
# S3 documents and attachments
|
||||||
|
S3_REGION=US
|
||||||
|
S3_ACCESS_KEY_ID=
|
||||||
|
S3_SECRET_ACCESS_KEY=
|
||||||
|
S3_ENDPOINT=
|
||||||
|
S3_BUCKET=
|
||||||
|
|
||||||
|
# PostHog
|
||||||
|
POSTHOG_API_KEY=
|
||||||
|
POSTHOG_HOST=
|
||||||
|
|
||||||
|
# Stripe Payment
|
||||||
|
STRIPE_PAYMENT_SECRET_KEY=
|
||||||
|
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||||
|
STRIPE_PAYMENT_CLIENT_ID=
|
||||||
|
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||||
|
STRIPE_PAYMENT_REDIRECT_URL=
|
||||||
25
packages/server-nest/.eslintrc.js
Normal file
25
packages/server-nest/.eslintrc.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
56
packages/server-nest/.gitignore
vendored
Normal file
56
packages/server-nest/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# temp directory
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
4
packages/server-nest/.prettierrc
Normal file
4
packages/server-nest/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
1
packages/server-nest/.todo
Normal file
1
packages/server-nest/.todo
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- Build authentication services.
|
||||||
1
packages/server-nest/README.md
Normal file
1
packages/server-nest/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
## @bigcapitalhq/server
|
||||||
11
packages/server-nest/nest-cli.json
Normal file
11
packages/server-nest/nest-cli.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true,
|
||||||
|
"assets": [
|
||||||
|
{ "include": "i18n/**/*", "watchAssets": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
127
packages/server-nest/package.json
Normal file
127
packages/server-nest/package.json
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"name": "@bigcapital/server2",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json --watchAll"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bigcapital/email-components": "*",
|
||||||
|
"@bigcapital/pdf-templates": "*",
|
||||||
|
"@bigcapital/utils": "*",
|
||||||
|
"@nestjs/bull": "^10.2.1",
|
||||||
|
"@nestjs/bullmq": "^10.2.1",
|
||||||
|
"@nestjs/cache-manager": "^2.2.2",
|
||||||
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.2.3",
|
||||||
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/event-emitter": "^2.0.4",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
|
"@nestjs/passport": "^10.0.3",
|
||||||
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/swagger": "^7.4.2",
|
||||||
|
"@nestjs/throttler": "^6.2.1",
|
||||||
|
"@supercharge/promise-pool": "^3.2.0",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
"@types/passport-local": "^1.0.38",
|
||||||
|
"@types/ramda": "^0.30.2",
|
||||||
|
"accounting": "^0.4.1",
|
||||||
|
"async": "^3.2.0",
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"bluebird": "^3.7.2",
|
||||||
|
"bull": "^4.16.3",
|
||||||
|
"bullmq": "^5.21.1",
|
||||||
|
"cache-manager": "^6.1.1",
|
||||||
|
"cache-manager-redis-store": "^3.0.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
|
"express-validator": "^7.2.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"fp-ts": "^2.16.9",
|
||||||
|
"is-my-json-valid": "^2.20.5",
|
||||||
|
"js-money": "^0.6.3",
|
||||||
|
"knex": "^3.1.0",
|
||||||
|
"lamda": "^0.4.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"mysql2": "^3.11.3",
|
||||||
|
"nestjs-cls": "^4.4.1",
|
||||||
|
"nestjs-i18n": "^10.4.9",
|
||||||
|
"nodemailer": "^6.3.0",
|
||||||
|
"object-hash": "^2.0.3",
|
||||||
|
"objection": "^3.1.5",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"plaid": "^10.3.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
|
"posthog-node": "^4.3.2",
|
||||||
|
"pug": "^3.0.2",
|
||||||
|
"ramda": "^0.30.1",
|
||||||
|
"redis": "^4.7.0",
|
||||||
|
"reflect-metadata": "^0.2.0",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"serialize-interceptor": "^1.1.7",
|
||||||
|
"strategy": "^1.1.1",
|
||||||
|
"uniqid": "^5.2.0",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
|
"yup": "^0.28.1",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^10.0.0",
|
||||||
|
"@nestjs/schematics": "^10.0.0",
|
||||||
|
"@nestjs/testing": "^10.0.0",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/jest": "^29.5.2",
|
||||||
|
"@types/node": "^20.3.1",
|
||||||
|
"@types/supertest": "^6.0.0",
|
||||||
|
"@types/yup": "^0.29.13",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
|
"ts-loader": "^9.4.3",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.1.3"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/server-nest/src/common/config/gotenberg.ts
Normal file
6
packages/server-nest/src/common/config/gotenberg.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('gotenberg', () => ({
|
||||||
|
url: process.env.GOTENBERG_URL,
|
||||||
|
docsUrl: process.env.GOTENBERG_DOCS_URL,
|
||||||
|
}));
|
||||||
21
packages/server-nest/src/common/config/index.ts
Normal file
21
packages/server-nest/src/common/config/index.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import systemDatabase from './system-database';
|
||||||
|
import tenantDatabase from './tenant-database';
|
||||||
|
import signup from './signup';
|
||||||
|
import gotenberg from './gotenberg';
|
||||||
|
import plaid from './plaid';
|
||||||
|
import lemonsqueezy from './lemonsqueezy';
|
||||||
|
import s3 from './s3';
|
||||||
|
import openExchange from './open-exchange';
|
||||||
|
import posthog from './posthog';
|
||||||
|
|
||||||
|
export const config = [
|
||||||
|
systemDatabase,
|
||||||
|
tenantDatabase,
|
||||||
|
signup,
|
||||||
|
gotenberg,
|
||||||
|
plaid,
|
||||||
|
lemonsqueezy,
|
||||||
|
s3,
|
||||||
|
openExchange,
|
||||||
|
posthog,
|
||||||
|
];
|
||||||
7
packages/server-nest/src/common/config/lemonsqueezy.ts
Normal file
7
packages/server-nest/src/common/config/lemonsqueezy.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('lemonsqueezy', () => ({
|
||||||
|
apiKey: process.env.LEMONSQUEEZY_API_KEY,
|
||||||
|
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
||||||
|
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
|
||||||
|
}));
|
||||||
5
packages/server-nest/src/common/config/open-exchange.ts
Normal file
5
packages/server-nest/src/common/config/open-exchange.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('openExchange', () => ({
|
||||||
|
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
|
||||||
|
}));
|
||||||
8
packages/server-nest/src/common/config/plaid.ts
Normal file
8
packages/server-nest/src/common/config/plaid.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('plaid', () => ({
|
||||||
|
env: process.env.PLAID_ENV || 'sandbox',
|
||||||
|
clientId: process.env.PLAID_CLIENT_ID,
|
||||||
|
secret: process.env.PLAID_SECRET,
|
||||||
|
linkWebhook: process.env.PLAID_LINK_WEBHOOK,
|
||||||
|
}));
|
||||||
6
packages/server-nest/src/common/config/posthog.ts
Normal file
6
packages/server-nest/src/common/config/posthog.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('posthog', () => ({
|
||||||
|
apiKey: process.env.POSTHOG_API_KEY,
|
||||||
|
host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
|
||||||
|
}));
|
||||||
9
packages/server-nest/src/common/config/s3.ts
Normal file
9
packages/server-nest/src/common/config/s3.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('s3', () => ({
|
||||||
|
region: process.env.S3_REGION || 'US',
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
bucket: process.env.S3_BUCKET,
|
||||||
|
}));
|
||||||
12
packages/server-nest/src/common/config/signup.ts
Normal file
12
packages/server-nest/src/common/config/signup.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('signup', () => ({
|
||||||
|
disabled: process.env.SIGNUP_DISABLED === 'true',
|
||||||
|
allowedDomains: process.env.SIGNUP_ALLOWED_DOMAINS
|
||||||
|
? process.env.SIGNUP_ALLOWED_DOMAINS.split(',')
|
||||||
|
: [],
|
||||||
|
allowedEmails: process.env.SIGNUP_ALLOWED_EMAILS
|
||||||
|
? process.env.SIGNUP_ALLOWED_EMAILS.split(',')
|
||||||
|
: [],
|
||||||
|
emailConfirmation: process.env.SIGNUP_EMAIL_CONFIRMATION === 'true',
|
||||||
|
}));
|
||||||
9
packages/server-nest/src/common/config/stripe-payment.ts
Normal file
9
packages/server-nest/src/common/config/stripe-payment.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('stripePayment', () => ({
|
||||||
|
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY,
|
||||||
|
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY,
|
||||||
|
clientId: process.env.STRIPE_PAYMENT_CLIENT_ID,
|
||||||
|
webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET,
|
||||||
|
redirectUrl: process.env.STRIPE_PAYMENT_REDIRECT_URL,
|
||||||
|
}));
|
||||||
10
packages/server-nest/src/common/config/system-database.ts
Normal file
10
packages/server-nest/src/common/config/system-database.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('systemDatabase', () => ({
|
||||||
|
client: 'mysql',
|
||||||
|
host: process.env.SYSTEM_DB_HOST || process.env.DB_HOST,
|
||||||
|
port: process.env.SYSTEM_DB_PORT || process.env.DB_PORT || 5432,
|
||||||
|
user: process.env.SYSTEM_DB_USER || process.env.DB_USER,
|
||||||
|
password: process.env.SYSTEM_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||||
|
databaseName: process.env.SYSTEM_DB_NAME || process.env.DB_NAME,
|
||||||
|
}));
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('tenantDatabase', () => ({
|
||||||
|
client: 'mysql',
|
||||||
|
host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
||||||
|
port: process.env.TENANT_DB_PORT || process.env.DB_PORT || 5432,
|
||||||
|
user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
||||||
|
password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||||
|
}));
|
||||||
756
packages/server-nest/src/common/events/events.ts
Normal file
756
packages/server-nest/src/common/events/events.ts
Normal file
@@ -0,0 +1,756 @@
|
|||||||
|
export const events = {
|
||||||
|
/**
|
||||||
|
* Authentication service.
|
||||||
|
*/
|
||||||
|
auth: {
|
||||||
|
signIn: 'onSignIn',
|
||||||
|
signingIn: 'onSigningIn',
|
||||||
|
|
||||||
|
signUp: 'onSignUp',
|
||||||
|
signingUp: 'onSigningUp',
|
||||||
|
|
||||||
|
signUpConfirming: 'signUpConfirming',
|
||||||
|
signUpConfirmed: 'signUpConfirmed',
|
||||||
|
|
||||||
|
sendingResetPassword: 'onSendingResetPassword',
|
||||||
|
sendResetPassword: 'onSendResetPassword',
|
||||||
|
|
||||||
|
resetPassword: 'onResetPassword',
|
||||||
|
resetingPassword: 'onResetingPassword',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite users service.
|
||||||
|
*/
|
||||||
|
inviteUser: {
|
||||||
|
acceptInvite: 'onUserAcceptInvite',
|
||||||
|
sendInvite: 'onUserSendInvite',
|
||||||
|
resendInvite: 'onUserInviteResend',
|
||||||
|
checkInvite: 'onUserCheckInvite',
|
||||||
|
sendInviteTenantSynced: 'onUserSendInviteTenantSynced',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization managment service.
|
||||||
|
*/
|
||||||
|
organization: {
|
||||||
|
build: 'onOrganizationBuild',
|
||||||
|
built: 'onOrganizationBuilt',
|
||||||
|
|
||||||
|
seeded: 'onOrganizationSeeded',
|
||||||
|
|
||||||
|
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization subscription.
|
||||||
|
*/
|
||||||
|
subscription: {
|
||||||
|
onSubscriptionCancel: 'onSubscriptionCancel',
|
||||||
|
onSubscriptionCancelled: 'onSubscriptionCancelled',
|
||||||
|
|
||||||
|
onSubscriptionResume: 'onSubscriptionResume',
|
||||||
|
onSubscriptionResumed: 'onSubscriptionResumed',
|
||||||
|
|
||||||
|
onSubscriptionPlanChange: 'onSubscriptionPlanChange',
|
||||||
|
onSubscriptionPlanChanged: 'onSubscriptionPlanChanged',
|
||||||
|
|
||||||
|
onSubscriptionSubscribed: 'onSubscriptionSubscribed',
|
||||||
|
|
||||||
|
onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed',
|
||||||
|
onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenants managment service.
|
||||||
|
*/
|
||||||
|
tenantManager: {
|
||||||
|
databaseCreated: 'onDatabaseCreated',
|
||||||
|
tenantMigrated: 'onTenantMigrated',
|
||||||
|
tenantSeeded: 'onTenantSeeded',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts service.
|
||||||
|
*/
|
||||||
|
accounts: {
|
||||||
|
onViewed: 'onAccountViewed',
|
||||||
|
onListViewed: 'onAccountsListViewed',
|
||||||
|
|
||||||
|
onCreating: 'onAccountCreating',
|
||||||
|
onCreated: 'onAccountCreated',
|
||||||
|
|
||||||
|
onEditing: 'onAccountEditing',
|
||||||
|
onEdited: 'onAccountEdited',
|
||||||
|
|
||||||
|
onDelete: 'onAccountDelete',
|
||||||
|
onDeleted: 'onAccountDeleted',
|
||||||
|
|
||||||
|
onBulkDeleted: 'onBulkDeleted',
|
||||||
|
onBulkActivated: 'onAccountBulkActivated',
|
||||||
|
|
||||||
|
onActivated: 'onAccountActivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manual journals service.
|
||||||
|
*/
|
||||||
|
manualJournals: {
|
||||||
|
onCreating: 'onManualJournalCreating',
|
||||||
|
onCreated: 'onManualJournalCreated',
|
||||||
|
|
||||||
|
onEditing: 'onManualJournalEditing',
|
||||||
|
onEdited: 'onManualJournalEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onManualJournalDeleting',
|
||||||
|
onDeleted: 'onManualJournalDeleted',
|
||||||
|
|
||||||
|
onPublished: 'onManualJournalPublished',
|
||||||
|
onPublishing: 'onManualJournalPublishing',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expenses service.
|
||||||
|
*/
|
||||||
|
expenses: {
|
||||||
|
onCreating: 'onExpenseCreating',
|
||||||
|
onCreated: 'onExpenseCreated',
|
||||||
|
|
||||||
|
onEditing: 'onExpenseEditing',
|
||||||
|
onEdited: 'onExpenseEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onExpenseDeleting',
|
||||||
|
onDeleted: 'onExpenseDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onExpensePublishing',
|
||||||
|
onPublished: 'onExpensePublished',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sales invoices service.
|
||||||
|
*/
|
||||||
|
saleInvoice: {
|
||||||
|
onViewed: 'onSaleInvoiceItemViewed',
|
||||||
|
onListViewed: 'onSaleInvoiceListViewed',
|
||||||
|
|
||||||
|
onPdfViewed: 'onSaleInvoicePdfViewed',
|
||||||
|
|
||||||
|
onCreate: 'onSaleInvoiceCreate',
|
||||||
|
onCreating: 'onSaleInvoiceCreating',
|
||||||
|
onCreated: 'onSaleInvoiceCreated',
|
||||||
|
|
||||||
|
onEdit: 'onSaleInvoiceEdit',
|
||||||
|
onEditing: 'onSaleInvoiceEditing',
|
||||||
|
onEdited: 'onSaleInvoiceEdited',
|
||||||
|
|
||||||
|
onDelete: 'onSaleInvoiceDelete',
|
||||||
|
onDeleting: 'onSaleInvoiceDeleting',
|
||||||
|
onDeleted: 'onSaleInvoiceDeleted',
|
||||||
|
|
||||||
|
onDelivering: 'onSaleInvoiceDelivering',
|
||||||
|
onDeliver: 'onSaleInvoiceDeliver',
|
||||||
|
onDelivered: 'onSaleInvoiceDelivered',
|
||||||
|
|
||||||
|
onPublish: 'onSaleInvoicePublish',
|
||||||
|
onPublished: 'onSaleInvoicePublished',
|
||||||
|
|
||||||
|
onWriteoff: 'onSaleInvoiceWriteoff',
|
||||||
|
onWrittenoff: 'onSaleInvoiceWrittenoff',
|
||||||
|
onWrittenoffCancel: 'onSaleInvoiceWrittenoffCancel',
|
||||||
|
onWrittenoffCanceled: 'onSaleInvoiceWrittenoffCanceled',
|
||||||
|
|
||||||
|
onNotifySms: 'onSaleInvoiceNotifySms',
|
||||||
|
onNotifiedSms: 'onSaleInvoiceNotifiedSms',
|
||||||
|
|
||||||
|
onNotifyMail: 'onSaleInvoiceNotifyMail',
|
||||||
|
onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail',
|
||||||
|
|
||||||
|
onPreMailSend: 'onSaleInvoicePreMailSend',
|
||||||
|
onMailSend: 'onSaleInvoiceMailSend',
|
||||||
|
onMailSent: 'onSaleInvoiceMailSent',
|
||||||
|
|
||||||
|
onMailReminderSend: 'onSaleInvoiceMailReminderSend',
|
||||||
|
onMailReminderSent: 'onSaleInvoiceMailReminderSent',
|
||||||
|
|
||||||
|
onPublicLinkGenerating: 'onPublicSharableLinkGenerating',
|
||||||
|
onPublicLinkGenerated: 'onPublicSharableLinkGenerated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sales estimates service.
|
||||||
|
*/
|
||||||
|
saleEstimate: {
|
||||||
|
onPdfViewed: 'onSaleEstimatePdfViewed',
|
||||||
|
|
||||||
|
onCreating: 'onSaleEstimateCreating',
|
||||||
|
onCreated: 'onSaleEstimateCreated',
|
||||||
|
|
||||||
|
onEditing: 'onSaleEstimateEditing',
|
||||||
|
onEdited: 'onSaleEstimateEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onSaleEstimatedDeleting',
|
||||||
|
onDeleted: 'onSaleEstimatedDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onSaleEstimatedPublishing',
|
||||||
|
onPublished: 'onSaleEstimatedPublished',
|
||||||
|
|
||||||
|
onNotifySms: 'onSaleEstimateNotifySms',
|
||||||
|
onNotifiedSms: 'onSaleEstimateNotifiedSms',
|
||||||
|
|
||||||
|
onDelivering: 'onSaleEstimateDelivering',
|
||||||
|
onDelivered: 'onSaleEstimateDelivered',
|
||||||
|
|
||||||
|
onConvertedToInvoice: 'onSaleEstimateConvertedToInvoice',
|
||||||
|
|
||||||
|
onApproving: 'onSaleEstimateApproving',
|
||||||
|
onApproved: 'onSaleEstimateApproved',
|
||||||
|
|
||||||
|
onRejecting: 'onSaleEstimateRejecting',
|
||||||
|
onRejected: 'onSaleEstimateRejected',
|
||||||
|
|
||||||
|
onNotifyMail: 'onSaleEstimateNotifyMail',
|
||||||
|
|
||||||
|
onPreMailSend: 'onSaleEstimatePreMailSend',
|
||||||
|
onMailSend: 'onSaleEstimateMailSend',
|
||||||
|
onMailSent: 'onSaleEstimateMailSend',
|
||||||
|
|
||||||
|
onViewed: 'onSaleEstimateViewed',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sales receipts service.
|
||||||
|
*/
|
||||||
|
saleReceipt: {
|
||||||
|
onPdfViewed: 'onSaleReceiptPdfViewed',
|
||||||
|
|
||||||
|
onCreating: 'onSaleReceiptsCreating',
|
||||||
|
onCreated: 'onSaleReceiptsCreated',
|
||||||
|
|
||||||
|
onEditing: 'onSaleReceiptsEditing',
|
||||||
|
onEdited: 'onSaleReceiptsEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onSaleReceiptsDeleting',
|
||||||
|
onDeleted: 'onSaleReceiptsDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onSaleReceiptPublishing',
|
||||||
|
onPublished: 'onSaleReceiptPublished',
|
||||||
|
|
||||||
|
onClosed: 'onSaleReceiptClosed',
|
||||||
|
onClosing: 'onSaleReceiptClosing',
|
||||||
|
|
||||||
|
onNotifySms: 'onSaleReceiptNotifySms',
|
||||||
|
onNotifiedSms: 'onSaleReceiptNotifiedSms',
|
||||||
|
|
||||||
|
onPreMailSend: 'onSaleReceiptPreMailSend',
|
||||||
|
onMailSend: 'onSaleReceiptMailSend',
|
||||||
|
onMailSent: 'onSaleReceiptMailSent',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment receipts service.
|
||||||
|
*/
|
||||||
|
paymentReceive: {
|
||||||
|
onPdfViewed: 'onPaymentReceivedPdfViewed',
|
||||||
|
|
||||||
|
onCreated: 'onPaymentReceiveCreated',
|
||||||
|
onCreating: 'onPaymentReceiveCreating',
|
||||||
|
|
||||||
|
onEditing: 'onPaymentReceiveEditing',
|
||||||
|
onEdited: 'onPaymentReceiveEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onPaymentReceiveDeleting',
|
||||||
|
onDeleted: 'onPaymentReceiveDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onPaymentReceivePublishing',
|
||||||
|
onPublished: 'onPaymentReceivePublished',
|
||||||
|
|
||||||
|
onNotifySms: 'onPaymentReceiveNotifySms',
|
||||||
|
onNotifiedSms: 'onPaymentReceiveNotifiedSms',
|
||||||
|
|
||||||
|
onPreMailSend: 'onPaymentReceivePreMailSend',
|
||||||
|
onMailSend: 'onPaymentReceiveMailSend',
|
||||||
|
onMailSent: 'onPaymentReceiveMailSent',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bills service.
|
||||||
|
*/
|
||||||
|
bill: {
|
||||||
|
onCreating: 'onBillCreating',
|
||||||
|
onCreated: 'onBillCreated',
|
||||||
|
|
||||||
|
onEditing: 'onBillEditing',
|
||||||
|
onEdited: 'onBillEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onBillDeleting',
|
||||||
|
onDeleted: 'onBillDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onBillPublishing',
|
||||||
|
onPublished: 'onBillPublished',
|
||||||
|
|
||||||
|
onOpening: 'onBillOpening',
|
||||||
|
onOpened: 'onBillOpened',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill payments service.
|
||||||
|
*/
|
||||||
|
billPayment: {
|
||||||
|
onCreating: 'onBillPaymentCreating',
|
||||||
|
onCreated: 'onBillPaymentCreated',
|
||||||
|
|
||||||
|
onEditing: 'onBillPaymentEditing',
|
||||||
|
onEdited: 'onBillPaymentEdited',
|
||||||
|
|
||||||
|
onDeleted: 'onBillPaymentDeleted',
|
||||||
|
onDeleting: 'onBillPaymentDeleting',
|
||||||
|
|
||||||
|
onPublishing: 'onBillPaymentPublishing',
|
||||||
|
onPublished: 'onBillPaymentPublished',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customers services.
|
||||||
|
*/
|
||||||
|
customers: {
|
||||||
|
onCreating: 'onCustomerCreating',
|
||||||
|
onCreated: 'onCustomerCreated',
|
||||||
|
|
||||||
|
onEdited: 'onCustomerEdited',
|
||||||
|
onEditing: 'onCustomerEditing',
|
||||||
|
|
||||||
|
onDeleted: 'onCustomerDeleted',
|
||||||
|
onDeleting: 'onCustomerDeleting',
|
||||||
|
onBulkDeleted: 'onBulkDeleted',
|
||||||
|
|
||||||
|
onOpeningBalanceChanging: 'onCustomerOpeningBalanceChanging',
|
||||||
|
onOpeningBalanceChanged: 'onCustomerOpeingBalanceChanged',
|
||||||
|
|
||||||
|
onActivating: 'onCustomerActivating',
|
||||||
|
onActivated: 'onCustomerActivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendors services.
|
||||||
|
*/
|
||||||
|
vendors: {
|
||||||
|
onCreated: 'onVendorCreated',
|
||||||
|
onCreating: 'onVendorCreating',
|
||||||
|
|
||||||
|
onEdited: 'onVendorEdited',
|
||||||
|
onEditing: 'onVendorEditing',
|
||||||
|
|
||||||
|
onDeleted: 'onVendorDeleted',
|
||||||
|
onDeleting: 'onVendorDeleting',
|
||||||
|
|
||||||
|
onOpeningBalanceChanging: 'onVendorOpeingBalanceChanging',
|
||||||
|
onOpeningBalanceChanged: 'onVendorOpeingBalanceChanged',
|
||||||
|
|
||||||
|
onActivating: 'onVendorActivating',
|
||||||
|
onActivated: 'onVendorActivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items service.
|
||||||
|
*/
|
||||||
|
item: {
|
||||||
|
onViewed: 'onItemViewed',
|
||||||
|
|
||||||
|
onCreated: 'onItemCreated',
|
||||||
|
onCreating: 'onItemCreating',
|
||||||
|
|
||||||
|
onEditing: 'onItemEditing',
|
||||||
|
onEdited: 'onItemEdited',
|
||||||
|
|
||||||
|
onDeleted: 'onItemDeleted',
|
||||||
|
onDeleting: 'onItemDeleting',
|
||||||
|
|
||||||
|
onActivating: 'onItemActivating',
|
||||||
|
onActivated: 'onItemActivated',
|
||||||
|
|
||||||
|
onInactivating: 'onInactivating',
|
||||||
|
onInactivated: 'onItemInactivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item category service.
|
||||||
|
*/
|
||||||
|
itemCategory: {
|
||||||
|
onCreated: 'onItemCategoryCreated',
|
||||||
|
onEdited: 'onItemCategoryEdited',
|
||||||
|
onDeleted: 'onItemCategoryDeleted',
|
||||||
|
onBulkDeleted: 'onItemCategoryBulkDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory service.
|
||||||
|
*/
|
||||||
|
inventory: {
|
||||||
|
onInventoryTransactionsCreated: 'onInventoryTransactionsCreated',
|
||||||
|
onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted',
|
||||||
|
|
||||||
|
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
|
||||||
|
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
|
||||||
|
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted',
|
||||||
|
|
||||||
|
onInventoryCostEntriesWritten: 'onInventoryCostEntriesWritten',
|
||||||
|
|
||||||
|
onCostLotsGLEntriesBeforeWrite: 'onInventoryCostLotsGLEntriesBeforeWrite',
|
||||||
|
onCostLotsGLEntriesWrite: 'onInventoryCostLotsGLEntriesWrite',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory adjustment service.
|
||||||
|
*/
|
||||||
|
inventoryAdjustment: {
|
||||||
|
onQuickCreating: 'onInventoryAdjustmentCreating',
|
||||||
|
onQuickCreated: 'onInventoryAdjustmentQuickCreated',
|
||||||
|
|
||||||
|
onCreated: 'onInventoryAdjustmentCreated',
|
||||||
|
|
||||||
|
onDeleting: 'onInventoryAdjustmentDeleting',
|
||||||
|
onDeleted: 'onInventoryAdjustmentDeleted',
|
||||||
|
|
||||||
|
onPublishing: 'onInventoryAdjustmentPublishing',
|
||||||
|
onPublished: 'onInventoryAdjustmentPublished',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill landed cost.
|
||||||
|
*/
|
||||||
|
billLandedCost: {
|
||||||
|
onCreate: 'onBillLandedCostCreate',
|
||||||
|
onCreated: 'onBillLandedCostCreated',
|
||||||
|
onDelete: 'onBillLandedCostDelete',
|
||||||
|
onDeleted: 'onBillLandedCostDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
cashflow: {
|
||||||
|
onOwnerContributionCreate: 'onCashflowOwnerContributionCreate',
|
||||||
|
onOwnerContributionCreated: 'onCashflowOwnerContributionCreated',
|
||||||
|
|
||||||
|
onOtherIncomeCreate: 'onCashflowOtherIncomeCreate',
|
||||||
|
onOtherIncomeCreated: 'onCashflowOtherIncomeCreated',
|
||||||
|
|
||||||
|
onTransactionCreating: 'onCashflowTransactionCreating',
|
||||||
|
onTransactionCreated: 'onCashflowTransactionCreated',
|
||||||
|
|
||||||
|
onTransactionDeleting: 'onCashflowTransactionDeleting',
|
||||||
|
onTransactionDeleted: 'onCashflowTransactionDeleted',
|
||||||
|
|
||||||
|
onTransactionCategorizing: 'onTransactionCategorizing',
|
||||||
|
onTransactionCategorized: 'onCashflowTransactionCategorized',
|
||||||
|
|
||||||
|
onTransactionUncategorizedCreating: 'onTransactionUncategorizedCreating',
|
||||||
|
onTransactionUncategorizedCreated: 'onTransactionUncategorizedCreated',
|
||||||
|
|
||||||
|
onTransactionUncategorizing: 'onTransactionUncategorizing',
|
||||||
|
onTransactionUncategorized: 'onTransactionUncategorized',
|
||||||
|
|
||||||
|
onTransactionCategorizingAsExpense: 'onTransactionCategorizingAsExpense',
|
||||||
|
onTransactionCategorizedAsExpense: 'onTransactionCategorizedAsExpense',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roles service events.
|
||||||
|
*/
|
||||||
|
roles: {
|
||||||
|
onCreate: 'onRoleCreate',
|
||||||
|
onCreated: 'onRoleCreated',
|
||||||
|
onEdit: 'onRoleEdit',
|
||||||
|
onEdited: 'onRoleEdited',
|
||||||
|
onDelete: 'onRoleDelete',
|
||||||
|
onDeleted: 'onRoleDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
tenantUser: {
|
||||||
|
onEdited: 'onTenantUserEdited',
|
||||||
|
onDeleted: 'onTenantUserDeleted',
|
||||||
|
onActivated: 'onTenantUserActivated',
|
||||||
|
onInactivated: 'onTenantUserInactivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credit note service.
|
||||||
|
*/
|
||||||
|
creditNote: {
|
||||||
|
onPdfViewed: 'onCreditNotePdfViewed',
|
||||||
|
|
||||||
|
onCreate: 'onCreditNoteCreate',
|
||||||
|
onCreating: 'onCreditNoteCreating',
|
||||||
|
onCreated: 'onCreditNoteCreated',
|
||||||
|
|
||||||
|
onEditing: 'onCreditNoteEditing',
|
||||||
|
onEdit: 'onCreditNoteEdit',
|
||||||
|
onEdited: 'onCreditNoteEdited',
|
||||||
|
|
||||||
|
onDelete: 'onCreditNoteDelete',
|
||||||
|
onDeleting: 'onCreditNoteDeleting',
|
||||||
|
onDeleted: 'onCreditNoteDeleted',
|
||||||
|
|
||||||
|
onOpen: 'onCreditNoteOpen',
|
||||||
|
onOpening: 'onCreditNoteOpening',
|
||||||
|
onOpened: 'onCreditNoteOpened',
|
||||||
|
|
||||||
|
onRefundCreate: 'onCreditNoteRefundCreate',
|
||||||
|
onRefundCreating: 'onCreditNoteRefundCreating',
|
||||||
|
onRefundCreated: 'onCreditNoteRefundCreated',
|
||||||
|
|
||||||
|
onRefundDelete: 'onCreditNoteRefundDelete',
|
||||||
|
onRefundDeleting: 'onCreditNoteRefundDeleting',
|
||||||
|
onRefundDeleted: 'onCreditNoteRefundDeleted',
|
||||||
|
|
||||||
|
onApplyToInvoicesCreated: 'onCreditNoteApplyToInvoiceCreated',
|
||||||
|
onApplyToInvoicesCreate: 'onCreditNoteApplyToInvoiceCreate',
|
||||||
|
onApplyToInvoicesDeleted: 'onCreditNoteApplyToInvoiceDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor credit service.
|
||||||
|
*/
|
||||||
|
vendorCredit: {
|
||||||
|
onCreate: 'onVendorCreditCreate',
|
||||||
|
onCreating: 'onVendorCreditCreating',
|
||||||
|
onCreated: 'onVendorCreditCreated',
|
||||||
|
|
||||||
|
onEdit: 'onVendorCreditEdit',
|
||||||
|
onEditing: 'onVendorCreditEditing',
|
||||||
|
onEdited: 'onVendorCreditEdited',
|
||||||
|
|
||||||
|
onDelete: 'onVendorCreditDelete',
|
||||||
|
onDeleting: 'onVendorCreditDeleting',
|
||||||
|
onDeleted: 'onVendorCreditDeleted',
|
||||||
|
|
||||||
|
onOpen: 'onVendorCreditOpen',
|
||||||
|
onOpened: 'onVendorCreditOpened',
|
||||||
|
|
||||||
|
onRefundCreating: 'onVendorCreditRefundCreating',
|
||||||
|
onRefundCreate: 'onVendorCreditRefundCreate',
|
||||||
|
onRefundCreated: 'onVendorCreditRefundCreated',
|
||||||
|
|
||||||
|
onRefundDelete: 'onVendorCreditRefundDelete',
|
||||||
|
onRefundDeleting: 'onVendorCreditRefundDeleting',
|
||||||
|
onRefundDeleted: 'onVendorCreditRefundDeleted',
|
||||||
|
|
||||||
|
onApplyToInvoicesCreated: 'onVendorCreditApplyToInvoiceCreated',
|
||||||
|
onApplyToInvoicesCreate: 'onVendorCreditApplyToInvoiceCreate',
|
||||||
|
onApplyToInvoicesDeleted: 'onVendorCreditApplyToInvoiceDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionsLocking: {
|
||||||
|
locked: 'onTransactionLockingLocked',
|
||||||
|
lockCanceled: 'onTransactionLockingLockCanceled',
|
||||||
|
partialUnlocked: 'onTransactionLockingPartialUnlocked',
|
||||||
|
partialUnlockCanceled: 'onTransactionLockingPartialUnlockCanceled',
|
||||||
|
},
|
||||||
|
|
||||||
|
warehouse: {
|
||||||
|
onCreate: 'onWarehouseCreate',
|
||||||
|
onCreated: 'onWarehouseCreated',
|
||||||
|
|
||||||
|
onEdit: 'onWarehouseEdit',
|
||||||
|
onEdited: 'onWarehouseEdited',
|
||||||
|
|
||||||
|
onDelete: 'onWarehouseDelete',
|
||||||
|
onDeleted: 'onWarehouseDeleted',
|
||||||
|
|
||||||
|
onActivate: 'onWarehouseActivate',
|
||||||
|
onActivated: 'onWarehouseActivated',
|
||||||
|
|
||||||
|
onMarkPrimary: 'onWarehouseMarkPrimary',
|
||||||
|
onMarkedPrimary: 'onWarehouseMarkedPrimary',
|
||||||
|
},
|
||||||
|
|
||||||
|
warehouseTransfer: {
|
||||||
|
onCreate: 'onWarehouseTransferCreate',
|
||||||
|
onCreated: 'onWarehouseTransferCreated',
|
||||||
|
|
||||||
|
onEdit: 'onWarehouseTransferEdit',
|
||||||
|
onEdited: 'onWarehouseTransferEdited',
|
||||||
|
|
||||||
|
onDelete: 'onWarehouseTransferDelete',
|
||||||
|
onDeleted: 'onWarehouseTransferDeleted',
|
||||||
|
|
||||||
|
onInitiate: 'onWarehouseTransferInitiate',
|
||||||
|
onInitiated: 'onWarehouseTransferInitated',
|
||||||
|
|
||||||
|
onTransfer: 'onWarehouseTransferInitiate',
|
||||||
|
onTransferred: 'onWarehouseTransferTransferred',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branches.
|
||||||
|
*/
|
||||||
|
branch: {
|
||||||
|
onActivate: 'onBranchActivate',
|
||||||
|
onActivated: 'onBranchActivated',
|
||||||
|
|
||||||
|
onMarkPrimary: 'onBranchMarkPrimary',
|
||||||
|
onMarkedPrimary: 'onBranchMarkedPrimary',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projects.
|
||||||
|
*/
|
||||||
|
project: {
|
||||||
|
onCreate: 'onProjectCreate',
|
||||||
|
onCreating: 'onProjectCreating',
|
||||||
|
onCreated: 'onProjectCreated',
|
||||||
|
|
||||||
|
onEdit: 'onEditProject',
|
||||||
|
onEditing: 'onEditingProject',
|
||||||
|
onEdited: 'onEditedProject',
|
||||||
|
|
||||||
|
onEditStatus: 'onEditStatusProject',
|
||||||
|
onEditingStatus: 'onEditingStatusProject',
|
||||||
|
onEditedStatus: 'onEditedStatusProject',
|
||||||
|
|
||||||
|
onDelete: 'onDeleteProject',
|
||||||
|
onDeleting: 'onDeletingProject',
|
||||||
|
onDeleted: 'onDeletedProject',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project Tasks.
|
||||||
|
*/
|
||||||
|
projectTask: {
|
||||||
|
onCreate: 'onProjectTaskCreate',
|
||||||
|
onCreating: 'onProjectTaskCreating',
|
||||||
|
onCreated: 'onProjectTaskCreated',
|
||||||
|
|
||||||
|
onEdit: 'onProjectTaskEdit',
|
||||||
|
onEditing: 'onProjectTaskEditing',
|
||||||
|
onEdited: 'onProjectTaskEdited',
|
||||||
|
|
||||||
|
onDelete: 'onProjectTaskDelete',
|
||||||
|
onDeleting: 'onProjectTaskDeleting',
|
||||||
|
onDeleted: 'onProjectTaskDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project Times.
|
||||||
|
*/
|
||||||
|
projectTime: {
|
||||||
|
onCreate: 'onProjectTimeCreate',
|
||||||
|
onCreating: 'onProjectTimeCreating',
|
||||||
|
onCreated: 'onProjectTimeCreated',
|
||||||
|
|
||||||
|
onEdit: 'onProjectTimeEdit',
|
||||||
|
onEditing: 'onProjectTimeEditing',
|
||||||
|
onEdited: 'onProjectTimeEdited',
|
||||||
|
|
||||||
|
onDelete: 'onProjectTimeDelete',
|
||||||
|
onDeleting: 'onProjectTimeDeleting',
|
||||||
|
onDeleted: 'onProjectTimeDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
taxRates: {
|
||||||
|
onCreating: 'onTaxRateCreating',
|
||||||
|
onCreated: 'onTaxRateCreated',
|
||||||
|
|
||||||
|
onEditing: 'onTaxRateEditing',
|
||||||
|
onEdited: 'onTaxRateEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onTaxRateDeleting',
|
||||||
|
onDeleted: 'onTaxRateDeleted',
|
||||||
|
|
||||||
|
onActivating: 'onTaxRateActivating',
|
||||||
|
onActivated: 'onTaxRateActivated',
|
||||||
|
|
||||||
|
onInactivating: 'onTaxRateInactivating',
|
||||||
|
onInactivated: 'onTaxRateInactivated',
|
||||||
|
},
|
||||||
|
|
||||||
|
plaid: {
|
||||||
|
onItemCreated: 'onPlaidItemCreated',
|
||||||
|
onTransactionsSynced: 'onPlaidTransactionsSynced',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bank rules.
|
||||||
|
bankRules: {
|
||||||
|
onCreating: 'onBankRuleCreating',
|
||||||
|
onCreated: 'onBankRuleCreated',
|
||||||
|
|
||||||
|
onEditing: 'onBankRuleEditing',
|
||||||
|
onEdited: 'onBankRuleEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onBankRuleDeleting',
|
||||||
|
onDeleted: 'onBankRuleDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bank matching.
|
||||||
|
bankMatch: {
|
||||||
|
onMatching: 'onBankTransactionMatching',
|
||||||
|
onMatched: 'onBankTransactionMatched',
|
||||||
|
|
||||||
|
onUnmatching: 'onBankTransactionUnmathcing',
|
||||||
|
onUnmatched: 'onBankTransactionUnmathced',
|
||||||
|
},
|
||||||
|
|
||||||
|
bankTransactions: {
|
||||||
|
onExcluding: 'onBankTransactionExclude',
|
||||||
|
onExcluded: 'onBankTransactionExcluded',
|
||||||
|
|
||||||
|
onUnexcluding: 'onBankTransactionUnexcluding',
|
||||||
|
onUnexcluded: 'onBankTransactionUnexcluded',
|
||||||
|
|
||||||
|
onPendingRemoving: 'onBankTransactionPendingRemoving',
|
||||||
|
onPendingRemoved: 'onBankTransactionPendingRemoved',
|
||||||
|
},
|
||||||
|
|
||||||
|
bankAccount: {
|
||||||
|
onDisconnecting: 'onBankAccountDisconnecting',
|
||||||
|
onDisconnected: 'onBankAccountDisconnected',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Import files.
|
||||||
|
import: {
|
||||||
|
onImportCommitted: 'onImportFileCommitted',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Branding templates
|
||||||
|
pdfTemplate: {
|
||||||
|
onCreating: 'onPdfTemplateCreating',
|
||||||
|
onCreated: 'onPdfTemplateCreated',
|
||||||
|
|
||||||
|
onEditing: 'onPdfTemplateEditing',
|
||||||
|
onEdited: 'onPdfTemplatedEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onPdfTemplateDeleting',
|
||||||
|
onDeleted: 'onPdfTemplateDeleted',
|
||||||
|
|
||||||
|
onAssignedDefault: 'onPdfTemplateAssignedDefault',
|
||||||
|
onAssigningDefault: 'onPdfTemplateAssigningDefault',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Payment method.
|
||||||
|
paymentMethod: {
|
||||||
|
onEditing: 'onPaymentMethodEditing',
|
||||||
|
onEdited: 'onPaymentMethodEdited',
|
||||||
|
|
||||||
|
onDeleted: 'onPaymentMethodDeleted',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Payment methods integrations
|
||||||
|
paymentIntegrationLink: {
|
||||||
|
onPaymentIntegrationLink: 'onPaymentIntegrationLink',
|
||||||
|
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stripe Payment Integration
|
||||||
|
stripeIntegration: {
|
||||||
|
onAccountCreated: 'onStripeIntegrationAccountCreated',
|
||||||
|
onAccountDeleted: 'onStripeIntegrationAccountDeleted',
|
||||||
|
|
||||||
|
onPaymentLinkCreated: 'onStripePaymentLinkCreated',
|
||||||
|
onPaymentLinkInactivated: 'onStripePaymentLinkInactivated',
|
||||||
|
|
||||||
|
onOAuthCodeGranted: 'onStripeOAuthCodeGranted',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stripe Payment Webhooks
|
||||||
|
stripeWebhooks: {
|
||||||
|
onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted',
|
||||||
|
onAccountUpdated: 'onStripeAccountUpdated',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export class ModelEntityNotFound extends Error {
|
||||||
|
constructor(entityId, message?) {
|
||||||
|
message = message || `Entity with id ${entityId} does not exist`;
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
ExceptionFilter,
|
||||||
|
Catch,
|
||||||
|
ArgumentsHost,
|
||||||
|
HttpStatus,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||||
|
|
||||||
|
@Catch(ServiceError)
|
||||||
|
export class ServiceErrorFilter implements ExceptionFilter {
|
||||||
|
catch(exception: ServiceError, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
const status = exception.getStatus();
|
||||||
|
|
||||||
|
response.status(status).json({
|
||||||
|
statusCode: status,
|
||||||
|
errorType: exception.errorType,
|
||||||
|
message: exception.message,
|
||||||
|
payload: exception.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
type ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
type NestInterceptor,
|
||||||
|
type CallHandler,
|
||||||
|
Optional,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { type Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
export function camelToSnake<T = any>(value: T) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(camelToSnake);
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && !(value instanceof Date)) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, value]) => [
|
||||||
|
key
|
||||||
|
.split(/(?=[A-Z])/)
|
||||||
|
.join('_')
|
||||||
|
.toLowerCase(),
|
||||||
|
camelToSnake(value),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function snakeToCamel<T = any>(value: T) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(snakeToCamel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const impl = (str: string) => {
|
||||||
|
const converted = str.replace(/([-_]\w)/g, (group) =>
|
||||||
|
group[1].toUpperCase(),
|
||||||
|
);
|
||||||
|
return converted[0].toLowerCase() + converted.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof value === 'object' && !(value instanceof Date)) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, value]) => [
|
||||||
|
impl(key),
|
||||||
|
snakeToCamel(value),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_STRATEGY = {
|
||||||
|
in: snakeToCamel,
|
||||||
|
out: camelToSnake,
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SerializeInterceptor implements NestInterceptor<any, any> {
|
||||||
|
constructor(@Optional() readonly strategy = DEFAULT_STRATEGY) {}
|
||||||
|
|
||||||
|
intercept(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler<any>,
|
||||||
|
): Observable<any> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
request.body = this.strategy.in(request.body);
|
||||||
|
|
||||||
|
// handle returns stream..
|
||||||
|
return next.handle().pipe(map(this.strategy.out));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/server-nest/src/common/pipes/ZodValidation.pipe.ts
Normal file
20
packages/server-nest/src/common/pipes/ZodValidation.pipe.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
PipeTransform,
|
||||||
|
ArgumentMetadata,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ZodSchema } from 'zod';
|
||||||
|
|
||||||
|
export class ZodValidationPipe implements PipeTransform {
|
||||||
|
constructor(private schema: ZodSchema) {}
|
||||||
|
|
||||||
|
transform(value: unknown, metadata: ArgumentMetadata) {
|
||||||
|
try {
|
||||||
|
const parsedValue = this.schema.parse(value);
|
||||||
|
return parsedValue;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw new BadRequestException(error.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// import hashObject from 'object-hash';
|
||||||
|
// import { EntityRepository } from './EntityRepository';
|
||||||
|
|
||||||
|
// export class CachableRepository extends EntityRepository {
|
||||||
|
// repositoryName: string;
|
||||||
|
// cache: any;
|
||||||
|
// i18n: any;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Constructor method.
|
||||||
|
// * @param {Knex} knex
|
||||||
|
// * @param {Cache} cache
|
||||||
|
// */
|
||||||
|
// constructor(knex, cache, i18n) {
|
||||||
|
// super(knex);
|
||||||
|
|
||||||
|
// this.cache = cache;
|
||||||
|
// this.i18n = i18n;
|
||||||
|
// this.repositoryName = this.constructor.name;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getByCache(key, callback) {
|
||||||
|
// return callback();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Retrieve the cache key of the method name and arguments.
|
||||||
|
// * @param {string} method
|
||||||
|
// * @param {...any} args
|
||||||
|
// * @return {string}
|
||||||
|
// */
|
||||||
|
// getCacheKey(method, ...args) {
|
||||||
|
// const hashArgs = hashObject({ ...args });
|
||||||
|
// const repositoryName = this.repositoryName;
|
||||||
|
|
||||||
|
// return `${repositoryName}-${method}-${hashArgs}`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Retrieve all entries with specified relations.
|
||||||
|
// * @param withRelations
|
||||||
|
// */
|
||||||
|
// all(withRelations?, trx?) {
|
||||||
|
// const cacheKey = this.getCacheKey('all', withRelations);
|
||||||
|
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.all(withRelations, trx);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds list of entities with specified attributes
|
||||||
|
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||||
|
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
// */
|
||||||
|
// find(attributeValues = {}, withRelations?) {
|
||||||
|
// const cacheKey = this.getCacheKey('find', attributeValues, withRelations);
|
||||||
|
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.find(attributeValues, withRelations);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds list of entities with attribute values that are different from specified ones
|
||||||
|
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
// */
|
||||||
|
// findWhereNot(attributeValues = {}, withRelations?) {
|
||||||
|
// const cacheKey = this.getCacheKey(
|
||||||
|
// 'findWhereNot',
|
||||||
|
// attributeValues,
|
||||||
|
// withRelations
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.findWhereNot(attributeValues, withRelations);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds list of entities with specified attributes (any of multiple specified values)
|
||||||
|
// * Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||||
|
// *
|
||||||
|
// * @param {string|Object} searchParam - attribute name or search criteria object
|
||||||
|
// * @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||||
|
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
// * @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
// */
|
||||||
|
// findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||||
|
// const cacheKey = this.getCacheKey(
|
||||||
|
// 'findWhereIn',
|
||||||
|
// attributeValues,
|
||||||
|
// withRelations
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.findWhereIn(searchParam, attributeValues, withRelations);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds first entity by given parameters
|
||||||
|
// *
|
||||||
|
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
// * @returns {Promise<Object>}
|
||||||
|
// */
|
||||||
|
// findOne(attributeValues = {}, withRelations?) {
|
||||||
|
// const cacheKey = this.getCacheKey(
|
||||||
|
// 'findOne',
|
||||||
|
// attributeValues,
|
||||||
|
// withRelations
|
||||||
|
// );
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.findOne(attributeValues, withRelations);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds first entity by given parameters
|
||||||
|
// *
|
||||||
|
// * @param {string || number} id - value of id column of the entity
|
||||||
|
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
// * @returns {Promise<Object>}
|
||||||
|
// */
|
||||||
|
// findOneById(id, withRelations?) {
|
||||||
|
// const cacheKey = this.getCacheKey('findOneById', id, withRelations);
|
||||||
|
|
||||||
|
// return this.getByCache(cacheKey, () => {
|
||||||
|
// return super.findOneById(id, withRelations);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Persists new entity or an array of entities.
|
||||||
|
// * This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||||
|
// * Batch insert only works on PostgreSQL
|
||||||
|
// * @param {Object} entity - model instance or parameters for a new entity
|
||||||
|
// * @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
// */
|
||||||
|
// async create(entity, trx?) {
|
||||||
|
// const result = await super.create(entity, trx);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after insert operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||||
|
// *
|
||||||
|
// * @param {Object} entity - single entity instance
|
||||||
|
// * @param {Object} [trx] - knex transaction instance. If not specified, new implicit transaction will be used.
|
||||||
|
// * @returns {Promise<integer>} number of affected rows
|
||||||
|
// */
|
||||||
|
// async update(entity, whereAttributes?, trx?) {
|
||||||
|
// const result = await super.update(entity, whereAttributes, trx);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after update operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @param {Object} attributeValues - values to filter deleted entities by
|
||||||
|
// * @param {Object} [trx]
|
||||||
|
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||||
|
// */
|
||||||
|
// async deleteBy(attributeValues, trx?) {
|
||||||
|
// const result = await super.deleteBy(attributeValues, trx);
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @param {string || number} id - value of id column of the entity
|
||||||
|
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||||
|
// */
|
||||||
|
// deleteById(id: number | string, trx?) {
|
||||||
|
// const result = super.deleteById(id, trx);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after insert operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param {string|number[]} values -
|
||||||
|
// */
|
||||||
|
// async deleteWhereIn(field: string, values: string | number[]) {
|
||||||
|
// const result = await super.deleteWhereIn(field, values);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after delete operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param {string|number[]} values
|
||||||
|
// */
|
||||||
|
// async deleteWhereIdIn(values: string | number[], trx?) {
|
||||||
|
// const result = await super.deleteWhereIdIn(values, trx);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after delete operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param graph
|
||||||
|
// * @param options
|
||||||
|
// */
|
||||||
|
// async upsertGraph(graph, options) {
|
||||||
|
// const result = await super.upsertGraph(graph, options);
|
||||||
|
|
||||||
|
// // Flushes the repository cache after insert operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param {} whereAttributes
|
||||||
|
// * @param {string} field
|
||||||
|
// * @param {number} amount
|
||||||
|
// */
|
||||||
|
// async changeNumber(whereAttributes, field: string, amount: number, trx?) {
|
||||||
|
// const result = await super.changeNumber(
|
||||||
|
// whereAttributes,
|
||||||
|
// field,
|
||||||
|
// amount,
|
||||||
|
// trx
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // Flushes the repository cache after update operation.
|
||||||
|
// this.flushCache();
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Flush repository cache.
|
||||||
|
// */
|
||||||
|
// flushCache(): void {
|
||||||
|
// this.cache.delStartWith(this.repositoryName);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import { cloneDeep, forOwn, isString } from 'lodash';
|
||||||
|
import { ModelEntityNotFound } from '../exceptions/ModelEntityNotFound';
|
||||||
|
import { Model } from 'objection';
|
||||||
|
|
||||||
|
function applyGraphFetched(withRelations, builder) {
|
||||||
|
const relations = Array.isArray(withRelations)
|
||||||
|
? withRelations
|
||||||
|
: typeof withRelations === 'string'
|
||||||
|
? withRelations.split(',').map((relation) => relation.trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
relations.forEach((relation) => {
|
||||||
|
builder.withGraphFetched(relation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EntityRepository {
|
||||||
|
idColumn: string = 'id';
|
||||||
|
knex: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the repository model binded it to knex instance.
|
||||||
|
*/
|
||||||
|
get model(): typeof Model {
|
||||||
|
throw new Error("The repository's model is not defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all entries with specified relations.
|
||||||
|
* @param withRelations
|
||||||
|
*/
|
||||||
|
all(withRelations?, trx?) {
|
||||||
|
const builder = this.model.query(trx);
|
||||||
|
applyGraphFetched(withRelations, builder);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds list of entities with specified attributes
|
||||||
|
*
|
||||||
|
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||||
|
* @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
*/
|
||||||
|
find(attributeValues = {}, withRelations?) {
|
||||||
|
const builder = this.model.query().where(attributeValues);
|
||||||
|
|
||||||
|
applyGraphFetched(withRelations, builder);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds list of entities with attribute values that are different from specified ones
|
||||||
|
*
|
||||||
|
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
*/
|
||||||
|
findWhereNot(attributeValues = {}, withRelations?) {
|
||||||
|
const builder = this.model.query().whereNot(attributeValues);
|
||||||
|
|
||||||
|
applyGraphFetched(withRelations, builder);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds list of entities with specified attributes (any of multiple specified values)
|
||||||
|
* Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||||
|
*
|
||||||
|
* @param {string|Object} searchParam - attribute name or search criteria object
|
||||||
|
* @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||||
|
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
*/
|
||||||
|
findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||||
|
const commonBuilder = (builder) => {
|
||||||
|
applyGraphFetched(withRelations, builder);
|
||||||
|
};
|
||||||
|
if (isString(searchParam)) {
|
||||||
|
return this.model
|
||||||
|
.query()
|
||||||
|
.whereIn(searchParam, attributeValues)
|
||||||
|
.onBuild(commonBuilder);
|
||||||
|
} else {
|
||||||
|
const builder = this.model.query(this.knex).onBuild(commonBuilder);
|
||||||
|
|
||||||
|
forOwn(searchParam, (value, key) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
builder.whereIn(key, value);
|
||||||
|
} else {
|
||||||
|
builder.where(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds first entity by given parameters
|
||||||
|
*
|
||||||
|
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||||
|
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
async findOne(attributeValues = {}, withRelations?) {
|
||||||
|
const results = await this.find(attributeValues, withRelations);
|
||||||
|
return results[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds first entity by given parameters
|
||||||
|
*
|
||||||
|
* @param {string || number} id - value of id column of the entity
|
||||||
|
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
findOneById(id, withRelations?) {
|
||||||
|
return this.findOne({ [this.idColumn]: id }, withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists new entity or an array of entities.
|
||||||
|
* This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||||
|
* Batch insert only works on PostgreSQL
|
||||||
|
*
|
||||||
|
* @param {Object} entity - model instance or parameters for a new entity
|
||||||
|
* @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||||
|
*/
|
||||||
|
create(entity, trx?) {
|
||||||
|
// Keep the input parameter immutable
|
||||||
|
const instanceDTO = cloneDeep(entity);
|
||||||
|
|
||||||
|
return this.model.query(trx).insert(instanceDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||||
|
*
|
||||||
|
* @param {Object} entity - single entity instance
|
||||||
|
* @returns {Promise<integer>} number of affected rows
|
||||||
|
*/
|
||||||
|
async update(entity, whereAttributes?, trx?) {
|
||||||
|
const entityDto = cloneDeep(entity);
|
||||||
|
const identityClause = {};
|
||||||
|
|
||||||
|
if (Array.isArray(this.idColumn)) {
|
||||||
|
this.idColumn.forEach(
|
||||||
|
(idColumn) => (identityClause[idColumn] = entityDto[idColumn]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
identityClause[this.idColumn] = entityDto[this.idColumn];
|
||||||
|
}
|
||||||
|
const whereConditions = whereAttributes || identityClause;
|
||||||
|
const modifiedEntitiesCount = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.where(whereConditions)
|
||||||
|
.update(entityDto);
|
||||||
|
|
||||||
|
if (modifiedEntitiesCount === 0) {
|
||||||
|
throw new ModelEntityNotFound(entityDto[this.idColumn]);
|
||||||
|
}
|
||||||
|
return modifiedEntitiesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} attributeValues - values to filter deleted entities by
|
||||||
|
* @param {Object} [trx]
|
||||||
|
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||||
|
*/
|
||||||
|
deleteBy(attributeValues, trx?) {
|
||||||
|
return this.model.query(trx).delete().where(attributeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string || number} id - value of id column of the entity
|
||||||
|
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||||
|
*/
|
||||||
|
deleteById(id: number | string, trx?) {
|
||||||
|
return this.deleteBy(
|
||||||
|
{
|
||||||
|
[this.idColumn]: id,
|
||||||
|
},
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given entries in the array on the specific field.
|
||||||
|
* @param {string} field -
|
||||||
|
* @param {number|string} values -
|
||||||
|
*/
|
||||||
|
deleteWhereIn(field: string, values: (string | number)[], trx) {
|
||||||
|
return this.model.query(trx).whereIn(field, values).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string|number[]} values
|
||||||
|
*/
|
||||||
|
deleteWhereIdIn(values: (string | number)[], trx?) {
|
||||||
|
return this.deleteWhereIn(this.idColumn, values, trx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arbitrary relation graphs can be upserted (insert + update + delete)
|
||||||
|
* using the upsertGraph method.
|
||||||
|
* @param graph
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
upsertGraph(graph, options) {
|
||||||
|
// Keep the input grpah immutable
|
||||||
|
const graphCloned = cloneDeep(graph);
|
||||||
|
return this.model.query().upsertGraph(graphCloned, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} whereAttributes
|
||||||
|
* @param {string} field
|
||||||
|
* @param amount
|
||||||
|
*/
|
||||||
|
changeNumber(whereAttributes, field: string, amount: number, trx) {
|
||||||
|
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||||
|
|
||||||
|
return this.model
|
||||||
|
.query(trx)
|
||||||
|
.where(whereAttributes)
|
||||||
|
[changeMethod](field, Math.abs(amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// import { CachableRepository } from './CachableRepository';
|
||||||
|
import { EntityRepository } from './EntityRepository';
|
||||||
|
|
||||||
|
export class TenantRepository extends EntityRepository {
|
||||||
|
|
||||||
|
}
|
||||||
4
packages/server-nest/src/common/types/Discount.ts
Normal file
4
packages/server-nest/src/common/types/Discount.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum DiscountType {
|
||||||
|
Percentage = 'percentage',
|
||||||
|
Amount = 'amount',
|
||||||
|
}
|
||||||
16
packages/server-nest/src/common/types/Features.ts
Normal file
16
packages/server-nest/src/common/types/Features.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export enum Features {
|
||||||
|
WAREHOUSES = 'warehouses',
|
||||||
|
BRANCHES = 'branches',
|
||||||
|
BankSyncing = 'BankSyncing',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFeatureAllItem {
|
||||||
|
name: string;
|
||||||
|
isAccessible: boolean;
|
||||||
|
defaultAccessible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFeatureConfiugration {
|
||||||
|
name: string;
|
||||||
|
defaultValue?: boolean;
|
||||||
|
}
|
||||||
230
packages/server-nest/src/constants/accounts.ts
Normal file
230
packages/server-nest/src/constants/accounts.ts
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
export const ACCOUNT_TYPE = {
|
||||||
|
CASH: 'cash',
|
||||||
|
BANK: 'bank',
|
||||||
|
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||||
|
INVENTORY: 'inventory',
|
||||||
|
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'none-current-asset',
|
||||||
|
|
||||||
|
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||||
|
CREDIT_CARD: 'credit-card',
|
||||||
|
TAX_PAYABLE: 'tax-payable',
|
||||||
|
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
INCOME: 'income',
|
||||||
|
OTHER_INCOME: 'other-income',
|
||||||
|
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
OTHER_EXPENSE: 'other-expense',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_PARENT_TYPE = {
|
||||||
|
CURRENT_ASSET: 'current-asset',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'non-current-asset',
|
||||||
|
|
||||||
|
CURRENT_LIABILITY: 'current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_ROOT_TYPE = {
|
||||||
|
ASSET: 'asset',
|
||||||
|
LIABILITY: 'liability',
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_NORMAL = {
|
||||||
|
CREDIT: 'credit',
|
||||||
|
DEBIT: 'debit',
|
||||||
|
};
|
||||||
|
export const ACCOUNT_TYPES = [
|
||||||
|
{
|
||||||
|
label: 'Cash',
|
||||||
|
key: ACCOUNT_TYPE.CASH,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bank',
|
||||||
|
key: ACCOUNT_TYPE.BANK,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Receivable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Inventory',
|
||||||
|
key: ACCOUNT_TYPE.INVENTORY,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fixed Asset',
|
||||||
|
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Payable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Credit Card',
|
||||||
|
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tax Payable',
|
||||||
|
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Long Term Liability',
|
||||||
|
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Equity',
|
||||||
|
key: ACCOUNT_TYPE.EQUITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Income',
|
||||||
|
key: ACCOUNT_TYPE.INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Income',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cost of Goods Sold',
|
||||||
|
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Expense',
|
||||||
|
key: ACCOUNT_TYPE.EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Expense',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getAccountsSupportsMultiCurrency = () => {
|
||||||
|
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
||||||
|
};
|
||||||
7
packages/server-nest/src/constants/data-types.ts
Normal file
7
packages/server-nest/src/constants/data-types.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const DATATYPES_LENGTH = {
|
||||||
|
STRING: 255,
|
||||||
|
TEXT: 65535,
|
||||||
|
INT_10: 4294967295,
|
||||||
|
DECIMAL_13_3: 9999999999.999,
|
||||||
|
DECIMAL_15_5: 999999999999.999,
|
||||||
|
};
|
||||||
241
packages/server-nest/src/constants/metable-options.ts
Normal file
241
packages/server-nest/src/constants/metable-options.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// import { getTransactionsLockingSettingsSchema } from '@/api/controllers/TransactionsLocking/utils';
|
||||||
|
|
||||||
|
export const SettingsOptions = {
|
||||||
|
organization: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
base_currency: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
industry: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
fiscal_year: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
financial_date_start: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
time_zone: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
date_format: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
accounting_basis: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manual_journals: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bill_payments: {
|
||||||
|
withdrawal_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sales_estimates: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
customer_notes: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
terms_conditions: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sales_receipts: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
preferred_deposit_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
receipt_message: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
terms_conditions: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sales_invoices: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
customer_notes: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
terms_conditions: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payment_receives: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
preferred_deposit_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
preferred_advance_deposit: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
preferred_sell_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
preferred_cost_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
preferred_inventory_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expenses: {
|
||||||
|
preferred_payment_account: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inventory: {
|
||||||
|
cost_compute_running: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
account_code_required: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
account_code_unique: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cashflow: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credit_note: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
customer_notes: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
terms_conditions: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vendor_credit: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warehouse_transfers: {
|
||||||
|
next_number: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
number_prefix: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
auto_increment: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'sms-notification': {
|
||||||
|
'sms-notification-enable.sale-invoice-details': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'sms-notification-enable.sale-invoice-reminder': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'sms-notification-enable.sale-estimate-details': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'sms-notification-enable.sale-receipt-details': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'sms-notification-enable.payment-receive-details': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'sms-notification-enable.customer-balance': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'transactions-locking': {
|
||||||
|
'locking-type': {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
// ...getTransactionsLockingSettingsSchema([
|
||||||
|
// 'all',
|
||||||
|
// 'sales',
|
||||||
|
// 'purchases',
|
||||||
|
// 'financial',
|
||||||
|
// ]),
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
'multi-warehouses': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
'multi-branches': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
21
packages/server-nest/src/i18n/en/test.json
Normal file
21
packages/server-nest/src/i18n/en/test.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"HELLO": "Hello",
|
||||||
|
"PRODUCT": {
|
||||||
|
"NEW": "New Product: {name}"
|
||||||
|
},
|
||||||
|
"ENGLISH": "English",
|
||||||
|
"ARRAY": ["ONE", "TWO", "THREE"],
|
||||||
|
"cat": "Cat",
|
||||||
|
"cat_name": "Cat: {name}",
|
||||||
|
"set-up-password": {
|
||||||
|
"heading": "Hello, {username}",
|
||||||
|
"title": "Forgot password",
|
||||||
|
"followLink": "Please follow the link to set up your password"
|
||||||
|
},
|
||||||
|
"day_interval": {
|
||||||
|
"one": "Every day",
|
||||||
|
"other": "Every {count} days",
|
||||||
|
"zero": "Never"
|
||||||
|
},
|
||||||
|
"nested": "We go shopping: $t(test.day_interval, {{\"count\": {count} }})"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExcludeNullInterceptor implements NestInterceptor {
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
return next.handle().pipe(map((value) => (value === null ? '' : value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GlobalPrefixInterceptor implements NestInterceptor {
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
request.url = `/api${request.url}`;
|
||||||
|
return next.handle().pipe(
|
||||||
|
map((data) => {
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
packages/server-nest/src/interceptors/user-ip.interceptor.ts
Normal file
21
packages/server-nest/src/interceptors/user-ip.interceptor.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
CallHandler,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserIpInterceptor implements NestInterceptor {
|
||||||
|
constructor(private readonly cls: ClsService) {}
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const userIp = request.connection.remoteAddress;
|
||||||
|
this.cls.set('ip', userIp);
|
||||||
|
|
||||||
|
return next.handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
174
packages/server-nest/src/interfaces/Account.ts
Normal file
174
packages/server-nest/src/interfaces/Account.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export interface IAccountDTO {
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
accountType: string;
|
||||||
|
parentAccountId?: number;
|
||||||
|
active: boolean;
|
||||||
|
bankBalance?: number;
|
||||||
|
accountMask?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountCreateDTO extends IAccountDTO {
|
||||||
|
currencyCode?: string;
|
||||||
|
plaidAccountId?: string;
|
||||||
|
plaidItemId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEditDTO extends IAccountDTO {}
|
||||||
|
|
||||||
|
export interface IAccount {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
code: string;
|
||||||
|
index: number;
|
||||||
|
description: string;
|
||||||
|
accountType: string;
|
||||||
|
parentAccountId: number;
|
||||||
|
active: boolean;
|
||||||
|
predefined: boolean;
|
||||||
|
amount: number;
|
||||||
|
currencyCode: string;
|
||||||
|
transactions?: any[];
|
||||||
|
type?: any[];
|
||||||
|
accountNormal: string;
|
||||||
|
accountParentType: string;
|
||||||
|
bankBalance: string;
|
||||||
|
plaidItemId: number | null;
|
||||||
|
lastFeedsUpdatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccountNormal {
|
||||||
|
DEBIT = 'debit',
|
||||||
|
CREDIT = 'credit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountsTransactionsFilter {
|
||||||
|
accountId?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountTransaction {
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
credit: number;
|
||||||
|
debit: number;
|
||||||
|
currencyCode: string;
|
||||||
|
exchangeRate: number;
|
||||||
|
|
||||||
|
accountId: number;
|
||||||
|
contactId?: number | null;
|
||||||
|
date: string | Date;
|
||||||
|
|
||||||
|
referenceType: string;
|
||||||
|
referenceTypeFormatted: string;
|
||||||
|
referenceId: number;
|
||||||
|
|
||||||
|
referenceNumber?: string;
|
||||||
|
|
||||||
|
transactionNumber?: string;
|
||||||
|
transactionType?: string;
|
||||||
|
|
||||||
|
note?: string;
|
||||||
|
|
||||||
|
index: number;
|
||||||
|
indexGroup?: number;
|
||||||
|
|
||||||
|
costable?: boolean;
|
||||||
|
|
||||||
|
userId?: number;
|
||||||
|
itemId?: number;
|
||||||
|
branchId?: number;
|
||||||
|
projectId?: number;
|
||||||
|
|
||||||
|
account?: IAccount;
|
||||||
|
|
||||||
|
taxRateId?: number;
|
||||||
|
taxRate?: number;
|
||||||
|
}
|
||||||
|
export interface IAccountResponse extends IAccount {}
|
||||||
|
|
||||||
|
export enum IAccountsStructureType {
|
||||||
|
Tree = 'tree',
|
||||||
|
Flat = 'flat',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountsFilter {
|
||||||
|
stringifiedFilterRoles?: string;
|
||||||
|
onlyInactive: boolean;
|
||||||
|
structure?: IAccountsStructureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountType {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
normal: string;
|
||||||
|
rootType: string;
|
||||||
|
childType: string;
|
||||||
|
balanceSheet: boolean;
|
||||||
|
incomeSheet: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountsTypesService {
|
||||||
|
getAccountsTypes(tenantId: number): Promise<IAccountType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventCreatingPayload {
|
||||||
|
tenantId: number;
|
||||||
|
accountDTO: any;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
export interface IAccountEventCreatedPayload {
|
||||||
|
tenantId: number;
|
||||||
|
account: IAccount;
|
||||||
|
accountId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventEditedPayload {
|
||||||
|
tenantId: number;
|
||||||
|
account: IAccount;
|
||||||
|
oldAccount: IAccount;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventDeletedPayload {
|
||||||
|
tenantId: number;
|
||||||
|
accountId: number;
|
||||||
|
oldAccount: IAccount;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventDeletePayload {
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
oldAccount: IAccount;
|
||||||
|
tenantId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventActivatedPayload {
|
||||||
|
tenantId: number;
|
||||||
|
accountId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccountAction {
|
||||||
|
CREATE = 'Create',
|
||||||
|
EDIT = 'Edit',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
VIEW = 'View',
|
||||||
|
TransactionsLocking = 'TransactionsLocking',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TaxRateAction {
|
||||||
|
CREATE = 'Create',
|
||||||
|
EDIT = 'Edit',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
VIEW = 'View',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAccountParams {
|
||||||
|
ignoreUniqueName: boolean;
|
||||||
|
}
|
||||||
165
packages/server-nest/src/interfaces/Item.ts
Normal file
165
packages/server-nest/src/interfaces/Item.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Item } from '@/modules/Items/models/Item';
|
||||||
|
// import { AbilitySubject } from '@/interfaces';
|
||||||
|
// import { IFilterRole } from '@/interfaces/DynamicFilter';
|
||||||
|
|
||||||
|
export interface IItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
sellable: boolean;
|
||||||
|
purchasable: boolean;
|
||||||
|
|
||||||
|
costPrice: number;
|
||||||
|
sellPrice: number;
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
costAccountId: number;
|
||||||
|
sellAccountId: number;
|
||||||
|
inventoryAccountId: number;
|
||||||
|
|
||||||
|
sellDescription: string;
|
||||||
|
purchaseDescription: string;
|
||||||
|
|
||||||
|
sellTaxRateId: number;
|
||||||
|
purchaseTaxRateId: number;
|
||||||
|
|
||||||
|
quantityOnHand: number;
|
||||||
|
|
||||||
|
note: string;
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
categoryId: number;
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IItemDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sellable: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
purchasable: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
costPrice: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sellPrice: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
costAccountId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sellAccountId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
inventoryAccountId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sellDescription: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
purchaseDescription: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sellTaxRateId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
purchaseTaxRateId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
quantityOnHand: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
note: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
categoryId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemCreateDTO extends IItemDTO {}
|
||||||
|
export interface IItemEditDTO extends IItemDTO {}
|
||||||
|
|
||||||
|
// export interface IItemsService {
|
||||||
|
// getItem(tenantId: number, itemId: number): Promise<IItem>;
|
||||||
|
// deleteItem(tenantId: number, itemId: number): Promise<void>;
|
||||||
|
// editItem(tenantId: number, itemId: number, itemDTO: IItemDTO): Promise<IItem>;
|
||||||
|
// newItem(tenantId: number, itemDTO: IItemDTO): Promise<IItem>;
|
||||||
|
// itemsList(
|
||||||
|
// tenantId: number,
|
||||||
|
// itemsFilter: IItemsFilter,
|
||||||
|
// ): Promise<{ items: IItem[] }>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface IItemsFilter extends IDynamicListFilterDTO {
|
||||||
|
// stringifiedFilterRoles?: string;
|
||||||
|
// page: number;
|
||||||
|
// pageSize: number;
|
||||||
|
// inactiveMode: boolean;
|
||||||
|
// viewSlug?: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface IItemsAutoCompleteFilter {
|
||||||
|
// limit: number;
|
||||||
|
// keyword: string;
|
||||||
|
// filterRoles?: IFilterRole[];
|
||||||
|
// columnSortBy: string;
|
||||||
|
// sortOrder: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface IItemEventCreatedPayload {
|
||||||
|
// tenantId: number;
|
||||||
|
item: Item;
|
||||||
|
itemId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemEventEditedPayload {
|
||||||
|
item: Item;
|
||||||
|
oldItem: Item;
|
||||||
|
itemId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemEventDeletingPayload {
|
||||||
|
// tenantId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
oldItem: Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemEventDeletedPayload {
|
||||||
|
// tenantId: number;
|
||||||
|
itemId: number;
|
||||||
|
oldItem: Item;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ItemAction {
|
||||||
|
CREATE = 'Create',
|
||||||
|
EDIT = 'Edit',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
VIEW = 'View',
|
||||||
|
}
|
||||||
|
|
||||||
|
// export type ItemAbility = [ItemAction, AbilitySubject.Item];
|
||||||
193
packages/server-nest/src/interfaces/Model.ts
Normal file
193
packages/server-nest/src/interfaces/Model.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
export interface IModel {
|
||||||
|
name: string;
|
||||||
|
tableName: string;
|
||||||
|
fields: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFilterMeta {
|
||||||
|
sortOrder: string;
|
||||||
|
sortBy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaginationMeta {
|
||||||
|
pageSize: number;
|
||||||
|
page: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaDefaultSort {
|
||||||
|
sortOrder: ISortOrder;
|
||||||
|
sortField: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelColumnType =
|
||||||
|
| 'text'
|
||||||
|
| 'number'
|
||||||
|
| 'enumeration'
|
||||||
|
| 'boolean'
|
||||||
|
| 'relation';
|
||||||
|
|
||||||
|
export type ISortOrder = 'DESC' | 'ASC';
|
||||||
|
|
||||||
|
export interface IModelMetaFieldCommon {
|
||||||
|
name: string;
|
||||||
|
column: string;
|
||||||
|
columnable?: boolean;
|
||||||
|
customQuery?: Function;
|
||||||
|
required?: boolean;
|
||||||
|
importHint?: string;
|
||||||
|
importableRelationLabel?: string;
|
||||||
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
dataTransferObjectKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaFieldText {
|
||||||
|
fieldType: 'text';
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
}
|
||||||
|
export interface IModelMetaFieldBoolean {
|
||||||
|
fieldType: 'boolean';
|
||||||
|
}
|
||||||
|
export interface IModelMetaFieldNumber {
|
||||||
|
fieldType: 'number';
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
export interface IModelMetaFieldDate {
|
||||||
|
fieldType: 'date';
|
||||||
|
}
|
||||||
|
export interface IModelMetaFieldUrl {
|
||||||
|
fieldType: 'url';
|
||||||
|
}
|
||||||
|
export type IModelMetaField = IModelMetaFieldCommon &
|
||||||
|
(
|
||||||
|
| IModelMetaFieldText
|
||||||
|
| IModelMetaFieldNumber
|
||||||
|
| IModelMetaFieldBoolean
|
||||||
|
| IModelMetaFieldDate
|
||||||
|
| IModelMetaFieldUrl
|
||||||
|
| IModelMetaEnumerationField
|
||||||
|
| IModelMetaRelationField
|
||||||
|
| IModelMetaCollectionField
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface IModelMetaEnumerationOption {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaEnumerationField {
|
||||||
|
fieldType: 'enumeration';
|
||||||
|
options: IModelMetaEnumerationOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaRelationFieldCommon {
|
||||||
|
fieldType: 'relation';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaRelationEnumerationField {
|
||||||
|
relationType: 'enumeration';
|
||||||
|
relationKey: string;
|
||||||
|
relationEntityLabel: string;
|
||||||
|
relationEntityKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaFieldWithFields {
|
||||||
|
fields: IModelMetaFieldCommon2 &
|
||||||
|
(
|
||||||
|
| IModelMetaFieldText
|
||||||
|
| IModelMetaFieldNumber
|
||||||
|
| IModelMetaFieldBoolean
|
||||||
|
| IModelMetaFieldDate
|
||||||
|
| IModelMetaFieldUrl
|
||||||
|
| IModelMetaEnumerationField
|
||||||
|
| IModelMetaRelationField
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IModelMetaCollectionObjectField extends IModelMetaFieldWithFields {
|
||||||
|
collectionOf: 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaCollectionFieldCommon {
|
||||||
|
fieldType: 'collection';
|
||||||
|
collectionMinLength?: number;
|
||||||
|
collectionMaxLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaCollectionField = IModelMetaCollectionFieldCommon &
|
||||||
|
IModelMetaCollectionObjectField;
|
||||||
|
|
||||||
|
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
||||||
|
IModelMetaRelationEnumerationField;
|
||||||
|
|
||||||
|
interface IModelPrintMeta {
|
||||||
|
pageTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMeta {
|
||||||
|
defaultFilterField: string;
|
||||||
|
defaultSort: IModelMetaDefaultSort;
|
||||||
|
|
||||||
|
exportable?: boolean;
|
||||||
|
exportFlattenOn?: string;
|
||||||
|
|
||||||
|
importable?: boolean;
|
||||||
|
importAggregator?: string;
|
||||||
|
importAggregateOn?: string;
|
||||||
|
importAggregateBy?: string;
|
||||||
|
|
||||||
|
print?: IModelPrintMeta;
|
||||||
|
|
||||||
|
fields: Record<string, IModelMetaField>;
|
||||||
|
fields2: Record<string, IModelMetaField2>;
|
||||||
|
columns: Record<string, IModelMetaColumn>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
export interface IModelMetaFieldCommon2 {
|
||||||
|
name: string;
|
||||||
|
required?: boolean;
|
||||||
|
importHint?: string;
|
||||||
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
features?: Array<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaRelationField2 {
|
||||||
|
fieldType: 'relation';
|
||||||
|
relationModel: string;
|
||||||
|
importableRelationLabel: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaField2 = IModelMetaFieldCommon2 &
|
||||||
|
(
|
||||||
|
| IModelMetaFieldText
|
||||||
|
| IModelMetaFieldNumber
|
||||||
|
| IModelMetaFieldBoolean
|
||||||
|
| IModelMetaFieldDate
|
||||||
|
| IModelMetaFieldUrl
|
||||||
|
| IModelMetaEnumerationField
|
||||||
|
| IModelMetaRelationField2
|
||||||
|
| IModelMetaCollectionField
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ImodelMetaColumnMeta {
|
||||||
|
name: string;
|
||||||
|
accessor?: string;
|
||||||
|
exportable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IModelMetaColumnText {
|
||||||
|
type: 'text;';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IModelMetaColumnCollection {
|
||||||
|
type: 'collection';
|
||||||
|
collectionOf: 'object';
|
||||||
|
columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaColumn = ImodelMetaColumnMeta &
|
||||||
|
(IModelMetaColumnText | IModelMetaColumnCollection);
|
||||||
8
packages/server-nest/src/interfaces/SubscriptionPlan.ts
Normal file
8
packages/server-nest/src/interfaces/SubscriptionPlan.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SubscriptionPayload {
|
||||||
|
lemonSqueezyId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubscriptionPaymentStatus {
|
||||||
|
Succeed = 'succeed',
|
||||||
|
Failed = 'failed',
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { get } from 'lodash';
|
||||||
|
import { ACCOUNT_TYPES } from '@/constants/accounts';
|
||||||
|
|
||||||
|
export class AccountTypesUtils {
|
||||||
|
/**
|
||||||
|
* Retrieve account types list.
|
||||||
|
*/
|
||||||
|
static getList() {
|
||||||
|
return ACCOUNT_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the given root type.
|
||||||
|
* @param {string} rootType -
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
static getTypesByRootType(rootType: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account type by the given account type key.
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} accessor
|
||||||
|
*/
|
||||||
|
static getType(key: string, accessor?: string) {
|
||||||
|
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
||||||
|
|
||||||
|
if (accessor) {
|
||||||
|
return get(type, accessor);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the parent account type.
|
||||||
|
* @param {string} parentType
|
||||||
|
*/
|
||||||
|
static getTypesByParentType(parentType: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the given account normal.
|
||||||
|
* @param {string} normal
|
||||||
|
*/
|
||||||
|
static getTypesByNormal(normal: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the root type equals the account type.
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} rootType
|
||||||
|
*/
|
||||||
|
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
const isRootType = type.rootType === rootType;
|
||||||
|
|
||||||
|
return isType && isRootType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the parent account type equals the account type key.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
* @param {string} parentType - Account parent type.
|
||||||
|
*/
|
||||||
|
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
const isParentType = type.parentType === parentType;
|
||||||
|
|
||||||
|
return isType && isParentType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether account type has balance sheet.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static isTypeBalanceSheet(key: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
return isType && type.balanceSheet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether account type has profit/loss sheet.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
*/
|
||||||
|
static isTypePLSheet(key: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
return isType && type.incomeSheet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
26
packages/server-nest/src/libs/chromiumly/Chromiumly.ts
Normal file
26
packages/server-nest/src/libs/chromiumly/Chromiumly.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { ChromiumRoute, LibreOfficeRoute, PdfEngineRoute } from './_types';
|
||||||
|
|
||||||
|
export class Chromiumly {
|
||||||
|
public static readonly GOTENBERG_ENDPOINT = process.env.GOTENBERG_URL || '';
|
||||||
|
|
||||||
|
public static readonly CHROMIUM_PATH = 'forms/chromium/convert';
|
||||||
|
public static readonly PDF_ENGINES_PATH = 'forms/pdfengines';
|
||||||
|
public static readonly LIBRE_OFFICE_PATH = 'forms/libreoffice';
|
||||||
|
|
||||||
|
public static readonly GOTENBERG_DOCS_ENDPOINT =
|
||||||
|
process.env.GOTENBERG_DOCS_URL || '';
|
||||||
|
|
||||||
|
public static readonly CHROMIUM_ROUTES = {
|
||||||
|
url: ChromiumRoute.URL,
|
||||||
|
html: ChromiumRoute.HTML,
|
||||||
|
markdown: ChromiumRoute.MARKDOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly PDF_ENGINE_ROUTES = {
|
||||||
|
merge: PdfEngineRoute.MERGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly LIBRE_OFFICE_ROUTES = {
|
||||||
|
convert: LibreOfficeRoute.CONVERT,
|
||||||
|
};
|
||||||
|
}
|
||||||
66
packages/server-nest/src/libs/chromiumly/ConvertUtils.ts
Normal file
66
packages/server-nest/src/libs/chromiumly/ConvertUtils.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import FormData from 'form-data';
|
||||||
|
import { GotenbergUtils } from './GotenbergUtils';
|
||||||
|
import { PageProperties } from './_types';
|
||||||
|
|
||||||
|
export class ConverterUtils {
|
||||||
|
public static injectPageProperties(
|
||||||
|
data: FormData,
|
||||||
|
pageProperties: PageProperties
|
||||||
|
): void {
|
||||||
|
if (pageProperties.size) {
|
||||||
|
GotenbergUtils.assert(
|
||||||
|
pageProperties.size.width >= 1.0 && pageProperties.size.height >= 1.5,
|
||||||
|
'size is smaller than the minimum printing requirements (i.e. 1.0 x 1.5 in)'
|
||||||
|
);
|
||||||
|
|
||||||
|
data.append('paperWidth', pageProperties.size.width);
|
||||||
|
data.append('paperHeight', pageProperties.size.height);
|
||||||
|
}
|
||||||
|
if (pageProperties.margins) {
|
||||||
|
GotenbergUtils.assert(
|
||||||
|
pageProperties.margins.top >= 0 &&
|
||||||
|
pageProperties.margins.bottom >= 0 &&
|
||||||
|
pageProperties.margins.left >= 0 &&
|
||||||
|
pageProperties.margins.left >= 0,
|
||||||
|
'negative margins are not allowed'
|
||||||
|
);
|
||||||
|
data.append('marginTop', pageProperties.margins.top);
|
||||||
|
data.append('marginBottom', pageProperties.margins.bottom);
|
||||||
|
data.append('marginLeft', pageProperties.margins.left);
|
||||||
|
data.append('marginRight', pageProperties.margins.right);
|
||||||
|
}
|
||||||
|
if (pageProperties.preferCssPageSize) {
|
||||||
|
data.append(
|
||||||
|
'preferCssPageSize',
|
||||||
|
String(pageProperties.preferCssPageSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pageProperties.printBackground) {
|
||||||
|
data.append('printBackground', String(pageProperties.printBackground));
|
||||||
|
}
|
||||||
|
if (pageProperties.landscape) {
|
||||||
|
data.append('landscape', String(pageProperties.landscape));
|
||||||
|
}
|
||||||
|
if (pageProperties.scale) {
|
||||||
|
GotenbergUtils.assert(
|
||||||
|
pageProperties.scale >= 0.1 && pageProperties.scale <= 2.0,
|
||||||
|
'scale is outside of [0.1 - 2] range'
|
||||||
|
);
|
||||||
|
data.append('scale', pageProperties.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageProperties.nativePageRanges) {
|
||||||
|
GotenbergUtils.assert(
|
||||||
|
pageProperties.nativePageRanges.from > 0 &&
|
||||||
|
pageProperties.nativePageRanges.to > 0 &&
|
||||||
|
pageProperties.nativePageRanges.to >=
|
||||||
|
pageProperties.nativePageRanges.from,
|
||||||
|
'page ranges syntax error'
|
||||||
|
);
|
||||||
|
data.append(
|
||||||
|
'nativePageRanges',
|
||||||
|
`${pageProperties.nativePageRanges.from}-${pageProperties.nativePageRanges.to}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/server-nest/src/libs/chromiumly/Converter.ts
Normal file
10
packages/server-nest/src/libs/chromiumly/Converter.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Chromiumly } from './Chromiumly';
|
||||||
|
import { ChromiumRoute } from './_types';
|
||||||
|
|
||||||
|
export abstract class Converter {
|
||||||
|
readonly endpoint: string;
|
||||||
|
|
||||||
|
constructor(route: ChromiumRoute) {
|
||||||
|
this.endpoint = `${Chromiumly.GOTENBERG_ENDPOINT}/${Chromiumly.CHROMIUM_PATH}/${Chromiumly.CHROMIUM_ROUTES[route]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
packages/server-nest/src/libs/chromiumly/GotenbergUtils.ts
Normal file
24
packages/server-nest/src/libs/chromiumly/GotenbergUtils.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import FormData from 'form-data';
|
||||||
|
import Axios from 'axios';
|
||||||
|
|
||||||
|
export class GotenbergUtils {
|
||||||
|
public static assert(condition: boolean, message: string): asserts condition {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
const response = await Axios.post(endpoint, data, {
|
||||||
|
headers: {
|
||||||
|
...data.getHeaders(),
|
||||||
|
},
|
||||||
|
responseType: 'arraybuffer', // This ensures you get a Buffer bac
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/server-nest/src/libs/chromiumly/HTMLConvert.ts
Normal file
38
packages/server-nest/src/libs/chromiumly/HTMLConvert.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { constants, createReadStream, PathLike, promises } from 'fs';
|
||||||
|
import FormData from 'form-data';
|
||||||
|
import { GotenbergUtils } from './GotenbergUtils';
|
||||||
|
import { IConverter, PageProperties } from './_types';
|
||||||
|
import { PdfFormat, ChromiumRoute } from './_types';
|
||||||
|
import { ConverterUtils } from './ConvertUtils';
|
||||||
|
import { Converter } from './Converter';
|
||||||
|
|
||||||
|
export class HtmlConverter extends Converter implements IConverter {
|
||||||
|
constructor() {
|
||||||
|
super(ChromiumRoute.HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
async convert({
|
||||||
|
html,
|
||||||
|
properties,
|
||||||
|
pdfFormat,
|
||||||
|
}: {
|
||||||
|
html: PathLike;
|
||||||
|
properties?: PageProperties;
|
||||||
|
pdfFormat?: PdfFormat;
|
||||||
|
}): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
await promises.access(html, constants.R_OK);
|
||||||
|
const data = new FormData();
|
||||||
|
if (pdfFormat) {
|
||||||
|
data.append('pdfFormat', pdfFormat);
|
||||||
|
}
|
||||||
|
data.append('index.html', createReadStream(html));
|
||||||
|
if (properties) {
|
||||||
|
ConverterUtils.injectPageProperties(data, properties);
|
||||||
|
}
|
||||||
|
return GotenbergUtils.fetch(this.endpoint, data);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/server-nest/src/libs/chromiumly/UrlConvert.ts
Normal file
38
packages/server-nest/src/libs/chromiumly/UrlConvert.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import FormData from 'form-data';
|
||||||
|
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
|
||||||
|
import { ConverterUtils } from './ConvertUtils';
|
||||||
|
import { Converter } from './Converter';
|
||||||
|
import { GotenbergUtils } from './GotenbergUtils';
|
||||||
|
|
||||||
|
export class UrlConverter extends Converter implements IConverter {
|
||||||
|
constructor() {
|
||||||
|
super(ChromiumRoute.URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
async convert({
|
||||||
|
url,
|
||||||
|
properties,
|
||||||
|
pdfFormat,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
properties?: PageProperties;
|
||||||
|
pdfFormat?: PdfFormat;
|
||||||
|
}): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
const _url = new URL(url);
|
||||||
|
const data = new FormData();
|
||||||
|
|
||||||
|
if (pdfFormat) {
|
||||||
|
data.append('pdfFormat', pdfFormat);
|
||||||
|
}
|
||||||
|
data.append('url', _url.href);
|
||||||
|
|
||||||
|
if (properties) {
|
||||||
|
ConverterUtils.injectPageProperties(data, properties);
|
||||||
|
}
|
||||||
|
return GotenbergUtils.fetch(this.endpoint, data);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
packages/server-nest/src/libs/chromiumly/_types.ts
Normal file
51
packages/server-nest/src/libs/chromiumly/_types.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { PathLike } from 'fs';
|
||||||
|
|
||||||
|
export type PageSize = {
|
||||||
|
width: number; // Paper width, in inches (default 8.5)
|
||||||
|
height: number; //Paper height, in inches (default 11)
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageMargins = {
|
||||||
|
top: number; // Top margin, in inches (default 0.39)
|
||||||
|
bottom: number; // Bottom margin, in inches (default 0.39)
|
||||||
|
left: number; // Left margin, in inches (default 0.39)
|
||||||
|
right: number; // Right margin, in inches (default 0.39)
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageProperties = {
|
||||||
|
size?: PageSize;
|
||||||
|
margins?: PageMargins;
|
||||||
|
preferCssPageSize?: boolean; // Define whether to prefer page size as defined by CSS (default false)
|
||||||
|
printBackground?: boolean; // Print the background graphics (default false)
|
||||||
|
landscape?: boolean; // Set the paper orientation to landscape (default false)
|
||||||
|
scale?: number; // The scale of the page rendering (default 1.0)
|
||||||
|
nativePageRanges?: { from: number; to: number }; // Page ranges to print
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IConverter {
|
||||||
|
convert({
|
||||||
|
...args
|
||||||
|
}: {
|
||||||
|
[x: string]: string | PathLike | PageProperties | PdfFormat;
|
||||||
|
}): Promise<Buffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PdfFormat {
|
||||||
|
A_1a = 'PDF/A-1a',
|
||||||
|
A_2b = 'PDF/A-2b',
|
||||||
|
A_3b = 'PDF/A-3b',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChromiumRoute {
|
||||||
|
URL = 'url',
|
||||||
|
HTML = 'html',
|
||||||
|
MARKDOWN = 'markdown',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PdfEngineRoute {
|
||||||
|
MERGE = 'merge',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LibreOfficeRoute {
|
||||||
|
CONVERT = 'convert',
|
||||||
|
}
|
||||||
350
packages/server-nest/src/libs/dependency-graph/index.ts
Normal file
350
packages/server-nest/src/libs/dependency-graph/index.ts
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
/**
|
||||||
|
* A simple dependency graph
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
||||||
|
*
|
||||||
|
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
||||||
|
* parameter is "true" in which case it ignores them).
|
||||||
|
*
|
||||||
|
* @param edges The set of edges to DFS through
|
||||||
|
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
|
||||||
|
* @param result An array in which the results will be populated
|
||||||
|
* @param circular A boolean to allow circular dependencies
|
||||||
|
*/
|
||||||
|
function createDFS(edges, leavesOnly, result, circular) {
|
||||||
|
var visited = {};
|
||||||
|
return function (start) {
|
||||||
|
if (visited[start]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var inCurrentPath = {};
|
||||||
|
var currentPath = [];
|
||||||
|
var todo = []; // used as a stack
|
||||||
|
todo.push({ node: start, processed: false });
|
||||||
|
while (todo.length > 0) {
|
||||||
|
var current = todo[todo.length - 1]; // peek at the todo stack
|
||||||
|
var processed = current.processed;
|
||||||
|
var node = current.node;
|
||||||
|
if (!processed) {
|
||||||
|
// Haven't visited edges yet (visiting phase)
|
||||||
|
if (visited[node]) {
|
||||||
|
todo.pop();
|
||||||
|
continue;
|
||||||
|
} else if (inCurrentPath[node]) {
|
||||||
|
// It's not a DAG
|
||||||
|
if (circular) {
|
||||||
|
todo.pop();
|
||||||
|
// If we're tolerating cycles, don't revisit the node
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentPath.push(node);
|
||||||
|
throw new DepGraphCycleError(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
inCurrentPath[node] = true;
|
||||||
|
currentPath.push(node);
|
||||||
|
var nodeEdges = edges[node];
|
||||||
|
// (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
|
||||||
|
for (var i = nodeEdges.length - 1; i >= 0; i--) {
|
||||||
|
todo.push({ node: nodeEdges[i], processed: false });
|
||||||
|
}
|
||||||
|
current.processed = true;
|
||||||
|
} else {
|
||||||
|
// Have visited edges (stack unrolling phase)
|
||||||
|
todo.pop();
|
||||||
|
currentPath.pop();
|
||||||
|
inCurrentPath[node] = false;
|
||||||
|
visited[node] = true;
|
||||||
|
if (!leavesOnly || edges[node].length === 0) {
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Dependency Graph
|
||||||
|
*/
|
||||||
|
var DepGraph = (DepGraph = function DepGraph(opts) {
|
||||||
|
this.nodes = {}; // Node -> Node/Data (treated like a Set)
|
||||||
|
this.outgoingEdges = {}; // Node -> [Dependency Node]
|
||||||
|
this.incomingEdges = {}; // Node -> [Dependant Node]
|
||||||
|
this.circular = opts && !!opts.circular; // Allows circular deps
|
||||||
|
});
|
||||||
|
|
||||||
|
DepGraph.fromArray = (
|
||||||
|
items,
|
||||||
|
options = { itemId: 'id', parentItemId: 'parent_id' }
|
||||||
|
) => {
|
||||||
|
const depGraph = new DepGraph();
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
depGraph.addNode(item[options.itemId], item);
|
||||||
|
});
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item[options.parentItemId]) {
|
||||||
|
depGraph.addDependency(item[options.parentItemId], item[options.itemId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return depGraph;
|
||||||
|
};
|
||||||
|
|
||||||
|
DepGraph.prototype = {
|
||||||
|
/**
|
||||||
|
* The number of nodes in the graph.
|
||||||
|
*/
|
||||||
|
size: function () {
|
||||||
|
return Object.keys(this.nodes).length;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Add a node to the dependency graph. If a node already exists, this method will do nothing.
|
||||||
|
*/
|
||||||
|
addNode: function (node, data) {
|
||||||
|
if (!this.hasNode(node)) {
|
||||||
|
// Checking the arguments length allows the user to add a node with undefined data
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
this.nodes[node] = data;
|
||||||
|
} else {
|
||||||
|
this.nodes[node] = node;
|
||||||
|
}
|
||||||
|
this.outgoingEdges[node] = [];
|
||||||
|
this.incomingEdges[node] = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
|
||||||
|
*/
|
||||||
|
removeNode: function (node) {
|
||||||
|
if (this.hasNode(node)) {
|
||||||
|
delete this.nodes[node];
|
||||||
|
delete this.outgoingEdges[node];
|
||||||
|
delete this.incomingEdges[node];
|
||||||
|
[this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) {
|
||||||
|
Object.keys(edgeList).forEach(function (key) {
|
||||||
|
var idx = edgeList[key].indexOf(node);
|
||||||
|
if (idx >= 0) {
|
||||||
|
edgeList[key].splice(idx, 1);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Check if a node exists in the graph
|
||||||
|
*/
|
||||||
|
hasNode: function (node) {
|
||||||
|
return this.nodes.hasOwnProperty(node);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the data associated with a node name
|
||||||
|
*/
|
||||||
|
getNodeData: function (node) {
|
||||||
|
if (this.hasNode(node)) {
|
||||||
|
return this.nodes[node];
|
||||||
|
} else {
|
||||||
|
throw new Error('Node does not exist: ' + node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
|
||||||
|
*/
|
||||||
|
setNodeData: function (node, data) {
|
||||||
|
if (this.hasNode(node)) {
|
||||||
|
this.nodes[node] = data;
|
||||||
|
} else {
|
||||||
|
throw new Error('Node does not exist: ' + node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Add a dependency between two nodes. If either of the nodes does not exist,
|
||||||
|
* an Error will be thrown.
|
||||||
|
*/
|
||||||
|
addDependency: function (from, to) {
|
||||||
|
if (!this.hasNode(from)) {
|
||||||
|
throw new Error('Node does not exist: ' + from);
|
||||||
|
}
|
||||||
|
if (!this.hasNode(to)) {
|
||||||
|
throw new Error('Node does not exist: ' + to);
|
||||||
|
}
|
||||||
|
if (this.outgoingEdges[from].indexOf(to) === -1) {
|
||||||
|
this.outgoingEdges[from].push(to);
|
||||||
|
}
|
||||||
|
if (this.incomingEdges[to].indexOf(from) === -1) {
|
||||||
|
this.incomingEdges[to].push(from);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Remove a dependency between two nodes.
|
||||||
|
*/
|
||||||
|
removeDependency: function (from, to) {
|
||||||
|
var idx;
|
||||||
|
if (this.hasNode(from)) {
|
||||||
|
idx = this.outgoingEdges[from].indexOf(to);
|
||||||
|
if (idx >= 0) {
|
||||||
|
this.outgoingEdges[from].splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasNode(to)) {
|
||||||
|
idx = this.incomingEdges[to].indexOf(from);
|
||||||
|
if (idx >= 0) {
|
||||||
|
this.incomingEdges[to].splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Return a clone of the dependency graph. If any custom data is attached
|
||||||
|
* to the nodes, it will only be shallow copied.
|
||||||
|
*/
|
||||||
|
clone: function () {
|
||||||
|
var source = this;
|
||||||
|
var result = new DepGraph();
|
||||||
|
var keys = Object.keys(source.nodes);
|
||||||
|
keys.forEach(function (n) {
|
||||||
|
result.nodes[n] = source.nodes[n];
|
||||||
|
result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
|
||||||
|
result.incomingEdges[n] = source.incomingEdges[n].slice(0);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get an array containing the nodes that the specified node depends on (transitively).
|
||||||
|
*
|
||||||
|
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
||||||
|
*
|
||||||
|
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
|
||||||
|
* in the array.
|
||||||
|
*/
|
||||||
|
dependenciesOf: function (node, leavesOnly) {
|
||||||
|
if (this.hasNode(node)) {
|
||||||
|
var result = [];
|
||||||
|
var DFS = createDFS(
|
||||||
|
this.outgoingEdges,
|
||||||
|
leavesOnly,
|
||||||
|
result,
|
||||||
|
this.circular
|
||||||
|
);
|
||||||
|
DFS(node);
|
||||||
|
var idx = result.indexOf(node);
|
||||||
|
if (idx >= 0) {
|
||||||
|
result.splice(idx, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new Error('Node does not exist: ' + node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* get an array containing the nodes that depend on the specified node (transitively).
|
||||||
|
*
|
||||||
|
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
||||||
|
*
|
||||||
|
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
||||||
|
*/
|
||||||
|
dependantsOf: function (node, leavesOnly) {
|
||||||
|
if (this.hasNode(node)) {
|
||||||
|
var result = [];
|
||||||
|
var DFS = createDFS(
|
||||||
|
this.incomingEdges,
|
||||||
|
leavesOnly,
|
||||||
|
result,
|
||||||
|
this.circular
|
||||||
|
);
|
||||||
|
DFS(node);
|
||||||
|
var idx = result.indexOf(node);
|
||||||
|
if (idx >= 0) {
|
||||||
|
result.splice(idx, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new Error('Node does not exist: ' + node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Construct the overall processing order for the dependency graph.
|
||||||
|
*
|
||||||
|
* Throws an Error if the graph has a cycle.
|
||||||
|
*
|
||||||
|
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
|
||||||
|
*/
|
||||||
|
overallOrder: function (leavesOnly) {
|
||||||
|
var self = this;
|
||||||
|
var result = [];
|
||||||
|
var keys = Object.keys(this.nodes);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return result; // Empty graph
|
||||||
|
} else {
|
||||||
|
if (!this.circular) {
|
||||||
|
// Look for cycles - we run the DFS starting at all the nodes in case there
|
||||||
|
// are several disconnected subgraphs inside this dependency graph.
|
||||||
|
var CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular);
|
||||||
|
keys.forEach(function (n) {
|
||||||
|
CycleDFS(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var DFS = createDFS(
|
||||||
|
this.outgoingEdges,
|
||||||
|
leavesOnly,
|
||||||
|
result,
|
||||||
|
this.circular
|
||||||
|
);
|
||||||
|
// Find all potential starting points (nodes with nothing depending on them) an
|
||||||
|
// run a DFS starting at these points to get the order
|
||||||
|
keys
|
||||||
|
.filter(function (node) {
|
||||||
|
return self.incomingEdges[node].length === 0;
|
||||||
|
})
|
||||||
|
.forEach(function (n) {
|
||||||
|
DFS(n);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we're allowing cycles - we need to run the DFS against any remaining
|
||||||
|
// nodes that did not end up in the initial result (as they are part of a
|
||||||
|
// subgraph that does not have a clear starting point)
|
||||||
|
if (this.circular) {
|
||||||
|
keys
|
||||||
|
.filter(function (node) {
|
||||||
|
return result.indexOf(node) === -1;
|
||||||
|
})
|
||||||
|
.forEach(function (n) {
|
||||||
|
DFS(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mapNodes(mapper) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycle error, including the path of the cycle.
|
||||||
|
*/
|
||||||
|
var DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
|
||||||
|
var message = 'Dependency Cycle Found: ' + cyclePath.join(' -> ');
|
||||||
|
var instance = new Error(message);
|
||||||
|
instance.cyclePath = cyclePath;
|
||||||
|
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
Error.captureStackTrace(instance, DepGraphCycleError);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
DepGraphCycleError.prototype = Object.create(Error.prototype, {
|
||||||
|
constructor: {
|
||||||
|
value: Error,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(DepGraphCycleError, Error);
|
||||||
|
|
||||||
|
export default DepGraph;
|
||||||
172
packages/server-nest/src/libs/logic-evaluation/Lexer.js
Normal file
172
packages/server-nest/src/libs/logic-evaluation/Lexer.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
|
||||||
|
const OperationType = {
|
||||||
|
LOGIC: 'LOGIC',
|
||||||
|
STRING: 'STRING',
|
||||||
|
COMPARISON: 'COMPARISON',
|
||||||
|
MATH: 'MATH',
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Lexer {
|
||||||
|
// operation table
|
||||||
|
static get optable() {
|
||||||
|
return {
|
||||||
|
'=': OperationType.LOGIC,
|
||||||
|
'&': OperationType.LOGIC,
|
||||||
|
'|': OperationType.LOGIC,
|
||||||
|
'?': OperationType.LOGIC,
|
||||||
|
':': OperationType.LOGIC,
|
||||||
|
|
||||||
|
'\'': OperationType.STRING,
|
||||||
|
'"': OperationType.STRING,
|
||||||
|
|
||||||
|
'!': OperationType.COMPARISON,
|
||||||
|
'>': OperationType.COMPARISON,
|
||||||
|
'<': OperationType.COMPARISON,
|
||||||
|
|
||||||
|
'(': OperationType.MATH,
|
||||||
|
')': OperationType.MATH,
|
||||||
|
'+': OperationType.MATH,
|
||||||
|
'-': OperationType.MATH,
|
||||||
|
'*': OperationType.MATH,
|
||||||
|
'/': OperationType.MATH,
|
||||||
|
'%': OperationType.MATH,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {*} expression -
|
||||||
|
*/
|
||||||
|
constructor(expression) {
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.input = expression;
|
||||||
|
this.tokenList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getTokens() {
|
||||||
|
let tok;
|
||||||
|
do {
|
||||||
|
// read current token, so step should be -1
|
||||||
|
tok = this.pickNext(-1);
|
||||||
|
const pos = this.currentIndex;
|
||||||
|
switch (Lexer.optable[tok]) {
|
||||||
|
case OperationType.LOGIC:
|
||||||
|
// == && || ===
|
||||||
|
this.readLogicOpt(tok);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OperationType.STRING:
|
||||||
|
this.readString(tok);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OperationType.COMPARISON:
|
||||||
|
this.readCompare(tok);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OperationType.MATH:
|
||||||
|
this.receiveToken();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.readValue(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the pos not changed, this loop will go into a infinite loop, every step of while loop,
|
||||||
|
// we must move the pos forward
|
||||||
|
// so here we should throw error, for example `1 & 2`
|
||||||
|
if (pos === this.currentIndex && tok !== undefined) {
|
||||||
|
const err = new Error(`unkonw token ${tok} from input string ${this.input}`);
|
||||||
|
err.name = 'UnknowToken';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} while (tok !== undefined)
|
||||||
|
|
||||||
|
return this.tokenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read next token, the index param can set next step, default go foward 1 step
|
||||||
|
*
|
||||||
|
* @param index next postion
|
||||||
|
*/
|
||||||
|
pickNext(index = 0) {
|
||||||
|
return this.input[index + this.currentIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store token into result tokenList, and move the pos index
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
receiveToken(index = 1) {
|
||||||
|
const tok = this.input.slice(this.currentIndex, this.currentIndex + index).trim();
|
||||||
|
// skip empty string
|
||||||
|
if (tok) {
|
||||||
|
this.tokenList.push(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentIndex += index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ' or "
|
||||||
|
readString(tok) {
|
||||||
|
let next;
|
||||||
|
let index = 0;
|
||||||
|
do {
|
||||||
|
next = this.pickNext(index);
|
||||||
|
index += 1;
|
||||||
|
} while (next !== tok && next !== undefined);
|
||||||
|
this.receiveToken(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// > or < or >= or <= or !==
|
||||||
|
// tok in (>, <, !)
|
||||||
|
readCompare(tok) {
|
||||||
|
if (this.pickNext() !== '=') {
|
||||||
|
this.receiveToken(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// !==
|
||||||
|
if (tok === '!' && this.pickNext(1) === '=') {
|
||||||
|
this.receiveToken(3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.receiveToken(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === or ==
|
||||||
|
// && ||
|
||||||
|
readLogicOpt(tok) {
|
||||||
|
if (this.pickNext() === tok) {
|
||||||
|
// ===
|
||||||
|
if (tok === '=' && this.pickNext(1) === tok) {
|
||||||
|
return this.receiveToken(3);
|
||||||
|
}
|
||||||
|
// == && ||
|
||||||
|
return this.receiveToken(2);
|
||||||
|
}
|
||||||
|
// handle as &&
|
||||||
|
// a ? b : c is equal to a && b || c
|
||||||
|
if (tok === '?' || tok === ':') {
|
||||||
|
return this.receiveToken(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readValue(tok) {
|
||||||
|
if (!tok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
while (!Lexer.optable[tok] && tok !== undefined) {
|
||||||
|
tok = this.pickNext(index);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
this.receiveToken(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function token(expression) {
|
||||||
|
const lexer = new Lexer(expression);
|
||||||
|
return lexer.getTokens();
|
||||||
|
}
|
||||||
159
packages/server-nest/src/libs/logic-evaluation/Parser.js
Normal file
159
packages/server-nest/src/libs/logic-evaluation/Parser.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
export const OPERATION = {
|
||||||
|
'!': 5,
|
||||||
|
'*': 4,
|
||||||
|
'/': 4,
|
||||||
|
'%': 4,
|
||||||
|
'+': 3,
|
||||||
|
'-': 3,
|
||||||
|
'>': 2,
|
||||||
|
'<': 2,
|
||||||
|
'>=': 2,
|
||||||
|
'<=': 2,
|
||||||
|
'===': 2,
|
||||||
|
'!==': 2,
|
||||||
|
'==': 2,
|
||||||
|
'!=': 2,
|
||||||
|
'&&': 1,
|
||||||
|
'||': 1,
|
||||||
|
'?': 1,
|
||||||
|
':': 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// export interface Node {
|
||||||
|
// left: Node | string | null;
|
||||||
|
// right: Node | string | null;
|
||||||
|
// operation: string;
|
||||||
|
// grouped?: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
export default class Parser {
|
||||||
|
|
||||||
|
constructor(token) {
|
||||||
|
this.index = -1;
|
||||||
|
this.blockLevel = 0;
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return {Node | string} =-
|
||||||
|
*/
|
||||||
|
parse() {
|
||||||
|
let tok;
|
||||||
|
let root = {
|
||||||
|
left: null,
|
||||||
|
right: null,
|
||||||
|
operation: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
tok = this.parseStatement();
|
||||||
|
|
||||||
|
if (tok === null || tok === undefined) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.left === null) {
|
||||||
|
root.left = tok;
|
||||||
|
root.operation = this.nextToken();
|
||||||
|
|
||||||
|
if (!root.operation) {
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.right = this.parseStatement();
|
||||||
|
} else {
|
||||||
|
if (typeof tok !== 'string') {
|
||||||
|
throw new Error('operation must be string, but get ' + JSON.stringify(tok));
|
||||||
|
}
|
||||||
|
root = this.addNode(tok, this.parseStatement(), root);
|
||||||
|
}
|
||||||
|
} while (tok);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextToken() {
|
||||||
|
this.index += 1;
|
||||||
|
return this.token[this.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
prevToken() {
|
||||||
|
return this.token[this.index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} operation
|
||||||
|
* @param {Node|String|null} right
|
||||||
|
* @param {Node} root
|
||||||
|
*/
|
||||||
|
addNode(operation, right, root) {
|
||||||
|
let pre = root;
|
||||||
|
|
||||||
|
if (this.compare(pre.operation, operation) < 0 && !pre.grouped) {
|
||||||
|
|
||||||
|
while (pre.right !== null &&
|
||||||
|
typeof pre.right !== 'string' &&
|
||||||
|
this.compare(pre.right.operation, operation) < 0 && !pre.right.grouped) {
|
||||||
|
pre = pre.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.right = {
|
||||||
|
operation,
|
||||||
|
left: pre.right,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: pre,
|
||||||
|
right,
|
||||||
|
operation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} a
|
||||||
|
* @param {String} b
|
||||||
|
*/
|
||||||
|
compare(a, b) {
|
||||||
|
if (!OPERATION.hasOwnProperty(a) || !OPERATION.hasOwnProperty(b)) {
|
||||||
|
throw new Error(`unknow operation ${a} or ${b}`);
|
||||||
|
}
|
||||||
|
return OPERATION[a] - OPERATION[b];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string | Node | null
|
||||||
|
*/
|
||||||
|
parseStatement() {
|
||||||
|
const token = this.nextToken();
|
||||||
|
if (token === '(') {
|
||||||
|
this.blockLevel += 1;
|
||||||
|
const node = this.parse();
|
||||||
|
this.blockLevel -= 1;
|
||||||
|
|
||||||
|
if (typeof node !== 'string') {
|
||||||
|
node.grouped = true;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === ')') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '!') {
|
||||||
|
return { left: null, operation: token, right: this.parseStatement() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 > -12 or -12 + 10
|
||||||
|
if (token === '-' && (OPERATION[this.prevToken()] > 0 || this.prevToken() === undefined)) {
|
||||||
|
return { left: '0', operation: token, right: this.parseStatement(), grouped: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { OPERATION } from './Parser';
|
||||||
|
|
||||||
|
export default class QueryParser {
|
||||||
|
|
||||||
|
constructor(tree, queries) {
|
||||||
|
this.tree = tree;
|
||||||
|
this.queries = queries;
|
||||||
|
this.query = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setQuery(query) {
|
||||||
|
this.query = query.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
parse() {
|
||||||
|
return this.parseNode(this.tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseNode(node) {
|
||||||
|
if (typeof node === 'string') {
|
||||||
|
const nodeQuery = this.getQuery(node);
|
||||||
|
return (query) => { nodeQuery(query); };
|
||||||
|
}
|
||||||
|
if (OPERATION[node.operation] === undefined) {
|
||||||
|
throw new Error(`unknow expression ${node.operation}`);
|
||||||
|
}
|
||||||
|
const leftQuery = this.getQuery(node.left);
|
||||||
|
const rightQuery = this.getQuery(node.right);
|
||||||
|
|
||||||
|
switch (node.operation) {
|
||||||
|
case '&&':
|
||||||
|
case 'AND':
|
||||||
|
default:
|
||||||
|
return (nodeQuery) => nodeQuery.where((query) => {
|
||||||
|
query.where((q) => { leftQuery(q); });
|
||||||
|
query.andWhere((q) => { rightQuery(q); });
|
||||||
|
});
|
||||||
|
case '||':
|
||||||
|
case 'OR':
|
||||||
|
return (nodeQuery) => nodeQuery.where((query) => {
|
||||||
|
query.where((q) => { leftQuery(q); });
|
||||||
|
query.orWhere((q) => { rightQuery(q); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuery(node) {
|
||||||
|
if (typeof node !== 'string' && node !== null) {
|
||||||
|
return this.parseNode(node);
|
||||||
|
}
|
||||||
|
const value = parseFloat(node);
|
||||||
|
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
if (typeof this.queries[node] === 'undefined') {
|
||||||
|
throw new Error(`unknow query under index ${node}`);
|
||||||
|
}
|
||||||
|
return this.queries[node];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/server-nest/src/main.ts
Normal file
29
packages/server-nest/src/main.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { ClsMiddleware } from 'nestjs-cls';
|
||||||
|
import './utils/moment-mysql';
|
||||||
|
import { AppModule } from './modules/App/App.module';
|
||||||
|
import { ServiceErrorFilter } from './common/filters/service-error.filter';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.setGlobalPrefix('/api');
|
||||||
|
|
||||||
|
// create and mount the middleware manually here
|
||||||
|
app.use(new ClsMiddleware({}).use);
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('Bigcapital')
|
||||||
|
.setDescription('Financial accounting software')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addTag('cats')
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('swagger', app, documentFactory);
|
||||||
|
|
||||||
|
app.useGlobalFilters(new ServiceErrorFilter());
|
||||||
|
|
||||||
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
}
|
||||||
|
bootstrap();
|
||||||
11
packages/server-nest/src/middleware/logger.middleware.ts
Normal file
11
packages/server-nest/src/middleware/logger.middleware.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggerMiddleware implements NestMiddleware {
|
||||||
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
|
// @ts-expect-error
|
||||||
|
req.test = 'test';
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
packages/server-nest/src/models/Model.ts
Normal file
43
packages/server-nest/src/models/Model.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { QueryBuilder, Model } from 'objection';
|
||||||
|
|
||||||
|
interface PaginationResult<M extends Model> {
|
||||||
|
results: M[];
|
||||||
|
pagination: {
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaginationQueryBuilderType<M extends Model> = QueryBuilder<
|
||||||
|
M,
|
||||||
|
PaginationResult<M>
|
||||||
|
>;
|
||||||
|
|
||||||
|
class PaginationQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
|
||||||
|
M,
|
||||||
|
R
|
||||||
|
> {
|
||||||
|
pagination(page: number, pageSize: number): PaginationQueryBuilderType<M> {
|
||||||
|
const query = super.page(page, pageSize);
|
||||||
|
|
||||||
|
return query.runAfter(({ results, total }) => {
|
||||||
|
return {
|
||||||
|
results,
|
||||||
|
pagination: {
|
||||||
|
total,
|
||||||
|
page: page + 1,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) as unknown as PaginationQueryBuilderType<M>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseModel extends Model {
|
||||||
|
public readonly id: number;
|
||||||
|
public readonly tableName: string;
|
||||||
|
|
||||||
|
QueryBuilderType!: PaginationQueryBuilder<this>;
|
||||||
|
static QueryBuilder = PaginationQueryBuilder;
|
||||||
|
}
|
||||||
130
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
130
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { Transformer } from '../Transformer/Transformer';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
|
||||||
|
import { assocDepthLevelToObjectTree } from '@/utils/assoc-depth-level-to-object-tree';
|
||||||
|
import { nestedArrayToFlatten } from '@/utils/nested-array-to-flatten';
|
||||||
|
import { IAccountsStructureType } from './Accounts.types';
|
||||||
|
|
||||||
|
export class AccountTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to sale invoice object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'accountTypeLabel',
|
||||||
|
'accountNormalFormatted',
|
||||||
|
'formattedAmount',
|
||||||
|
'flattenName',
|
||||||
|
'bankBalanceFormatted',
|
||||||
|
'lastFeedsUpdatedAtFormatted',
|
||||||
|
'isFeedsPaused',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['plaidItem'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the flatten name with all dependants accounts names.
|
||||||
|
* @param {IAccount} account -
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public flattenName = (account: Account): string => {
|
||||||
|
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
|
||||||
|
account.id,
|
||||||
|
);
|
||||||
|
const prefixAccounts = parentDependantsIds.map((dependId) => {
|
||||||
|
const node = this.options.accountsGraph.getNodeData(dependId);
|
||||||
|
return `${node.name}: `;
|
||||||
|
});
|
||||||
|
return `${prefixAccounts}${account.name}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted account amount.
|
||||||
|
* @param {IAccount} invoice
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedAmount = (account: Account): string => {
|
||||||
|
return this.formatNumber(account.amount, {
|
||||||
|
currencyCode: account.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted bank balance.
|
||||||
|
* @param {Account} account
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected bankBalanceFormatted = (account: Account): string => {
|
||||||
|
return this.formatNumber(account.bankBalance, {
|
||||||
|
currencyCode: account.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted last feeds update at.
|
||||||
|
* @param {IAccount} account
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected lastFeedsUpdatedAtFormatted = (account: Account): string => {
|
||||||
|
return account.lastFeedsUpdatedAt
|
||||||
|
? this.formatDate(account.lastFeedsUpdatedAt)
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the bank account connection is paused.
|
||||||
|
* @param account
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
protected isFeedsPaused = (account: Account): boolean => {
|
||||||
|
// return account.plaidItem?.isPaused || false;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves formatted account type label.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected accountTypeLabel = (account: Account): string => {
|
||||||
|
return this.context.i18n.t(account.accountTypeLabel);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves formatted account normal.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected accountNormalFormatted = (account: Account): string => {
|
||||||
|
return this.context.i18n.t(account.accountNormalFormatted);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the accounts collection to flat or nested array.
|
||||||
|
* @param {IAccount[]}
|
||||||
|
* @returns {IAccount[]}
|
||||||
|
*/
|
||||||
|
protected postCollectionTransform = (accounts: Account[]) => {
|
||||||
|
// Transfom the flatten to accounts tree.
|
||||||
|
const transformed = flatToNestedArray(accounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
|
// Associate `accountLevel` attr to indicate object depth.
|
||||||
|
const transformed2 = assocDepthLevelToObjectTree(
|
||||||
|
transformed,
|
||||||
|
1,
|
||||||
|
'accountLevel',
|
||||||
|
);
|
||||||
|
return this.options.structure === IAccountsStructureType.Flat
|
||||||
|
? nestedArrayToFlatten(transformed2)
|
||||||
|
: transformed2;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Transformer } from '../Transformer/Transformer';
|
||||||
|
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||||
|
|
||||||
|
export class AccountTransactionTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to sale invoice object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'date',
|
||||||
|
'formattedDate',
|
||||||
|
'transactionType',
|
||||||
|
'transactionId',
|
||||||
|
'transactionTypeFormatted',
|
||||||
|
'credit',
|
||||||
|
'debit',
|
||||||
|
'formattedCredit',
|
||||||
|
'formattedDebit',
|
||||||
|
'fcCredit',
|
||||||
|
'fcDebit',
|
||||||
|
'formattedFcCredit',
|
||||||
|
'formattedFcDebit',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude all attributes of the model.
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted date.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public formattedDate(transaction: AccountTransaction) {
|
||||||
|
return this.formatDate(transaction.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted transaction type.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public transactionTypeFormatted(transaction: AccountTransaction) {
|
||||||
|
return this.context.i18n.t(transaction.referenceTypeFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tranasction type.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public transactionType(transaction: AccountTransaction) {
|
||||||
|
return transaction.referenceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transaction id.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public transactionId(transaction: AccountTransaction) {
|
||||||
|
return transaction.referenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the credit amount.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedCredit(transaction: AccountTransaction) {
|
||||||
|
return this.formatMoney(transaction.credit, {
|
||||||
|
excerptZero: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the credit amount.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedDebit(transaction: AccountTransaction) {
|
||||||
|
return this.formatMoney(transaction.debit, {
|
||||||
|
excerptZero: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the foreign credit amount.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected fcCredit(transaction: AccountTransaction) {
|
||||||
|
return transaction.credit * transaction.exchangeRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the foreign debit amount.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected fcDebit(transaction: AccountTransaction) {
|
||||||
|
return transaction.debit * transaction.exchangeRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted foreign credit amount.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedFcCredit(transaction: AccountTransaction) {
|
||||||
|
return this.formatMoney(this.fcCredit(transaction), {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
excerptZero: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted foreign debit amount.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedFcDebit(transaction: AccountTransaction) {
|
||||||
|
return this.formatMoney(this.fcDebit(transaction), {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
excerptZero: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
637
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
637
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
export const OtherExpensesAccount = {
|
||||||
|
name: 'Other Expenses',
|
||||||
|
slug: 'other-expenses',
|
||||||
|
account_type: 'other-expense',
|
||||||
|
code: '40011',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TaxPayableAccount = {
|
||||||
|
name: 'Tax Payable',
|
||||||
|
slug: 'tax-payable',
|
||||||
|
account_type: 'tax-payable',
|
||||||
|
code: '20006',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UnearnedRevenueAccount = {
|
||||||
|
name: 'Unearned Revenue',
|
||||||
|
slug: 'unearned-revenue',
|
||||||
|
account_type: 'other-current-liability',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '50005',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrepardExpenses = {
|
||||||
|
name: 'Prepaid Expenses',
|
||||||
|
slug: 'prepaid-expenses',
|
||||||
|
account_type: 'other-current-asset',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '100010',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StripeClearingAccount = {
|
||||||
|
name: 'Stripe Clearing',
|
||||||
|
slug: 'stripe-clearing',
|
||||||
|
account_type: 'other-current-asset',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '100020',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DiscountExpenseAccount = {
|
||||||
|
name: 'Discount',
|
||||||
|
slug: 'discount',
|
||||||
|
account_type: 'other-income',
|
||||||
|
code: '40008',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PurchaseDiscountAccount = {
|
||||||
|
name: 'Purchase Discount',
|
||||||
|
slug: 'purchase-discount',
|
||||||
|
account_type: 'other-expense',
|
||||||
|
code: '40009',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OtherChargesAccount = {
|
||||||
|
name: 'Other Charges',
|
||||||
|
slug: 'other-charges',
|
||||||
|
account_type: 'other-income',
|
||||||
|
code: '40010',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SeedAccounts = [
|
||||||
|
{
|
||||||
|
name: 'Bank Account',
|
||||||
|
slug: 'bank-account',
|
||||||
|
account_type: 'bank',
|
||||||
|
code: '10001',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Saving Bank Account',
|
||||||
|
slug: 'saving-bank-account',
|
||||||
|
account_type: 'bank',
|
||||||
|
code: '10002',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Undeposited Funds',
|
||||||
|
slug: 'undeposited-funds',
|
||||||
|
account_type: 'cash',
|
||||||
|
code: '10003',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Petty Cash',
|
||||||
|
slug: 'petty-cash',
|
||||||
|
account_type: 'cash',
|
||||||
|
code: '10004',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Computer Equipment',
|
||||||
|
slug: 'computer-equipment',
|
||||||
|
code: '10005',
|
||||||
|
account_type: 'fixed-asset',
|
||||||
|
predefined: 0,
|
||||||
|
parent_account_id: null,
|
||||||
|
index: 1,
|
||||||
|
active: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Office Equipment',
|
||||||
|
slug: 'office-equipment',
|
||||||
|
code: '10006',
|
||||||
|
account_type: 'fixed-asset',
|
||||||
|
predefined: 0,
|
||||||
|
parent_account_id: null,
|
||||||
|
index: 1,
|
||||||
|
active: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Accounts Receivable (A/R)',
|
||||||
|
slug: 'accounts-receivable',
|
||||||
|
account_type: 'accounts-receivable',
|
||||||
|
code: '10007',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Inventory Asset',
|
||||||
|
slug: 'inventory-asset',
|
||||||
|
code: '10008',
|
||||||
|
account_type: 'inventory',
|
||||||
|
predefined: 1,
|
||||||
|
parent_account_id: null,
|
||||||
|
index: 1,
|
||||||
|
active: 1,
|
||||||
|
description:
|
||||||
|
'An account that holds valuation of products or goods that available for sale.',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Libilities
|
||||||
|
{
|
||||||
|
name: 'Accounts Payable (A/P)',
|
||||||
|
slug: 'accounts-payable',
|
||||||
|
account_type: 'accounts-payable',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '20001',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Owner A Drawings',
|
||||||
|
slug: 'owner-drawings',
|
||||||
|
account_type: 'other-current-liability',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '20002',
|
||||||
|
description: 'Withdrawals by the owners.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Loan',
|
||||||
|
slug: 'owner-drawings',
|
||||||
|
account_type: 'other-current-liability',
|
||||||
|
code: '20003',
|
||||||
|
description: 'Money that has been borrowed from a creditor.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Opening Balance Liabilities',
|
||||||
|
slug: 'opening-balance-liabilities',
|
||||||
|
account_type: 'other-current-liability',
|
||||||
|
code: '20004',
|
||||||
|
description:
|
||||||
|
'This account will hold the difference in the debits and credits entered during the opening balance..',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Revenue Received in Advance',
|
||||||
|
slug: 'revenue-received-in-advance',
|
||||||
|
account_type: 'other-current-liability',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '20005',
|
||||||
|
description: 'When customers pay in advance for products/services.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
TaxPayableAccount,
|
||||||
|
|
||||||
|
// Equity
|
||||||
|
{
|
||||||
|
name: 'Retained Earnings',
|
||||||
|
slug: 'retained-earnings',
|
||||||
|
account_type: 'equity',
|
||||||
|
code: '30001',
|
||||||
|
description:
|
||||||
|
'Retained earnings tracks net income from previous fiscal years.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Opening Balance Equity',
|
||||||
|
slug: 'opening-balance-equity',
|
||||||
|
account_type: 'equity',
|
||||||
|
code: '30002',
|
||||||
|
description:
|
||||||
|
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Owner's Equity",
|
||||||
|
slug: 'owner-equity',
|
||||||
|
account_type: 'equity',
|
||||||
|
code: '30003',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Drawings`,
|
||||||
|
slug: 'drawings',
|
||||||
|
account_type: 'equity',
|
||||||
|
code: '30003',
|
||||||
|
description:
|
||||||
|
'Goods purchased with the intention of selling these to customers',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Expenses
|
||||||
|
OtherExpensesAccount,
|
||||||
|
{
|
||||||
|
name: 'Other Expenses',
|
||||||
|
slug: 'other-expenses',
|
||||||
|
account_type: 'other-expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40001',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cost of Goods Sold',
|
||||||
|
slug: 'cost-of-goods-sold',
|
||||||
|
account_type: 'cost-of-goods-sold',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40002',
|
||||||
|
description: 'Tracks the direct cost of the goods sold.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Office expenses',
|
||||||
|
slug: 'office-expenses',
|
||||||
|
account_type: 'expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40003',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rent',
|
||||||
|
slug: 'rent',
|
||||||
|
account_type: 'expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40004',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Exchange Gain or Loss',
|
||||||
|
slug: 'exchange-grain-loss',
|
||||||
|
account_type: 'other-expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40005',
|
||||||
|
description: 'Tracks the gain and losses of the exchange differences.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bank Fees and Charges',
|
||||||
|
slug: 'bank-fees-and-charges',
|
||||||
|
account_type: 'expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40006',
|
||||||
|
description:
|
||||||
|
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Depreciation Expense',
|
||||||
|
slug: 'depreciation-expense',
|
||||||
|
account_type: 'expense',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '40007',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Income
|
||||||
|
{
|
||||||
|
name: 'Sales of Product Income',
|
||||||
|
slug: 'sales-of-product-income',
|
||||||
|
account_type: 'income',
|
||||||
|
predefined: 1,
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '50001',
|
||||||
|
index: 1,
|
||||||
|
active: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sales of Service Income',
|
||||||
|
slug: 'sales-of-service-income',
|
||||||
|
account_type: 'income',
|
||||||
|
predefined: 0,
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '50002',
|
||||||
|
index: 1,
|
||||||
|
active: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Uncategorized Income',
|
||||||
|
slug: 'uncategorized-income',
|
||||||
|
account_type: 'income',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '50003',
|
||||||
|
description: '',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Other Income',
|
||||||
|
slug: 'other-income',
|
||||||
|
account_type: 'other-income',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '50004',
|
||||||
|
description:
|
||||||
|
'The income activities are not associated to the core business.',
|
||||||
|
active: 1,
|
||||||
|
index: 1,
|
||||||
|
predefined: 0,
|
||||||
|
},
|
||||||
|
UnearnedRevenueAccount,
|
||||||
|
PrepardExpenses,
|
||||||
|
DiscountExpenseAccount,
|
||||||
|
PurchaseDiscountAccount,
|
||||||
|
OtherChargesAccount,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ACCOUNT_TYPE = {
|
||||||
|
CASH: 'cash',
|
||||||
|
BANK: 'bank',
|
||||||
|
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||||
|
INVENTORY: 'inventory',
|
||||||
|
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'none-current-asset',
|
||||||
|
|
||||||
|
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||||
|
CREDIT_CARD: 'credit-card',
|
||||||
|
TAX_PAYABLE: 'tax-payable',
|
||||||
|
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
INCOME: 'income',
|
||||||
|
OTHER_INCOME: 'other-income',
|
||||||
|
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
OTHER_EXPENSE: 'other-expense',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_PARENT_TYPE = {
|
||||||
|
CURRENT_ASSET: 'current-asset',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'non-current-asset',
|
||||||
|
|
||||||
|
CURRENT_LIABILITY: 'current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_ROOT_TYPE = {
|
||||||
|
ASSET: 'asset',
|
||||||
|
LIABILITY: 'liability',
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_NORMAL = {
|
||||||
|
CREDIT: 'credit',
|
||||||
|
DEBIT: 'debit',
|
||||||
|
};
|
||||||
|
export const ACCOUNT_TYPES = [
|
||||||
|
{
|
||||||
|
label: 'Cash',
|
||||||
|
key: ACCOUNT_TYPE.CASH,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bank',
|
||||||
|
key: ACCOUNT_TYPE.BANK,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Receivable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Inventory',
|
||||||
|
key: ACCOUNT_TYPE.INVENTORY,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fixed Asset',
|
||||||
|
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Payable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Credit Card',
|
||||||
|
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
multiCurrency: true,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tax Payable',
|
||||||
|
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Long Term Liability',
|
||||||
|
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Equity',
|
||||||
|
key: ACCOUNT_TYPE.EQUITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Income',
|
||||||
|
key: ACCOUNT_TYPE.INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Income',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cost of Goods Sold',
|
||||||
|
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Expense',
|
||||||
|
key: ACCOUNT_TYPE.EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Expense',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getAccountsSupportsMultiCurrency = () => {
|
||||||
|
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
||||||
|
};
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AccountsApplication } from './AccountsApplication.service';
|
||||||
|
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||||
|
import { EditAccountDTO } from './EditAccount.dto';
|
||||||
|
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||||
|
import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||||
|
// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||||
|
// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||||
|
|
||||||
|
@Controller('accounts')
|
||||||
|
@PublicRoute()
|
||||||
|
export class AccountsController {
|
||||||
|
constructor(private readonly accountsApplication: AccountsApplication) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async createAccount(@Body() accountDTO: CreateAccountDTO) {
|
||||||
|
return this.accountsApplication.createAccount(accountDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id')
|
||||||
|
async editAccount(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() accountDTO: EditAccountDTO,
|
||||||
|
) {
|
||||||
|
return this.accountsApplication.editAccount(id, accountDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async deleteAccount(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.accountsApplication.deleteAccount(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/activate')
|
||||||
|
async activateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.accountsApplication.activateAccount(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/inactivate')
|
||||||
|
async inactivateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.accountsApplication.inactivateAccount(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('types')
|
||||||
|
async getAccountTypes() {
|
||||||
|
return this.accountsApplication.getAccountTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('transactions')
|
||||||
|
async getAccountTransactions(@Query() filter: IAccountsTransactionsFilter) {
|
||||||
|
return this.accountsApplication.getAccountsTransactions(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async getAccount(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.accountsApplication.getAccount(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getAccounts(@Query() filter: IAccountsFilter) {
|
||||||
|
return this.accountsApplication.getAccounts(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
42
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
42
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||||
|
import { AccountsController } from './Accounts.controller';
|
||||||
|
import { AccountsApplication } from './AccountsApplication.service';
|
||||||
|
import { CreateAccountService } from './CreateAccount.service';
|
||||||
|
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||||
|
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||||
|
import { AccountRepository } from './repositories/Account.repository';
|
||||||
|
import { EditAccount } from './EditAccount.service';
|
||||||
|
import { DeleteAccount } from './DeleteAccount.service';
|
||||||
|
import { GetAccount } from './GetAccount.service';
|
||||||
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
|
import { ActivateAccount } from './ActivateAccount.service';
|
||||||
|
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||||
|
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||||
|
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||||
|
import { BankAccount } from '../BankingTransactions/models/BankAccount';
|
||||||
|
// import { GetAccountsService } from './GetAccounts.service';
|
||||||
|
|
||||||
|
const models = [RegisterTenancyModel(BankAccount)];
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TenancyDatabaseModule],
|
||||||
|
controllers: [AccountsController],
|
||||||
|
providers: [
|
||||||
|
AccountsApplication,
|
||||||
|
CreateAccountService,
|
||||||
|
TenancyContext,
|
||||||
|
CommandAccountValidators,
|
||||||
|
AccountRepository,
|
||||||
|
EditAccount,
|
||||||
|
DeleteAccount,
|
||||||
|
GetAccount,
|
||||||
|
TransformerInjectable,
|
||||||
|
ActivateAccount,
|
||||||
|
GetAccountTypesService,
|
||||||
|
GetAccountTransactionsService,
|
||||||
|
...models,
|
||||||
|
],
|
||||||
|
exports: [AccountRepository, CreateAccountService, ...models],
|
||||||
|
})
|
||||||
|
export class AccountsModule {}
|
||||||
91
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
91
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
// import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
||||||
|
|
||||||
|
export enum AccountNormal {
|
||||||
|
DEBIT = 'debit',
|
||||||
|
CREDIT = 'credit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountsTransactionsFilter {
|
||||||
|
accountId?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IAccountsStructureType {
|
||||||
|
Tree = 'tree',
|
||||||
|
Flat = 'flat',
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||||
|
// }
|
||||||
|
export interface IAccountsFilter {
|
||||||
|
onlyInactive: boolean;
|
||||||
|
structure?: IAccountsStructureType;
|
||||||
|
}
|
||||||
|
export interface IAccountType {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
normal: string;
|
||||||
|
rootType: string;
|
||||||
|
childType: string;
|
||||||
|
balanceSheet: boolean;
|
||||||
|
incomeSheet: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountsTypesService {
|
||||||
|
getAccountsTypes(): Promise<IAccountType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventCreatingPayload {
|
||||||
|
accountDTO: any;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
export interface IAccountEventCreatedPayload {
|
||||||
|
account: Account;
|
||||||
|
accountId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventEditedPayload {
|
||||||
|
account: Account;
|
||||||
|
oldAccount: Account;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventDeletedPayload {
|
||||||
|
accountId: number;
|
||||||
|
oldAccount: Account;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventDeletePayload {
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
oldAccount: Account;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccountEventActivatedPayload {
|
||||||
|
accountId: number;
|
||||||
|
trx: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccountAction {
|
||||||
|
CREATE = 'Create',
|
||||||
|
EDIT = 'Edit',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
VIEW = 'View',
|
||||||
|
TransactionsLocking = 'TransactionsLocking',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TaxRateAction {
|
||||||
|
CREATE = 'Create',
|
||||||
|
EDIT = 'Edit',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
VIEW = 'View',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAccountParams {
|
||||||
|
ignoreUniqueName: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetAccountTransactionPOJO {}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateAccountService } from './CreateAccount.service';
|
||||||
|
import { DeleteAccount } from './DeleteAccount.service';
|
||||||
|
import { EditAccount } from './EditAccount.service';
|
||||||
|
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { EditAccountDTO } from './EditAccount.dto';
|
||||||
|
import { GetAccount } from './GetAccount.service';
|
||||||
|
import { ActivateAccount } from './ActivateAccount.service';
|
||||||
|
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||||
|
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||||
|
import {
|
||||||
|
IAccountsFilter,
|
||||||
|
IAccountsTransactionsFilter,
|
||||||
|
IGetAccountTransactionPOJO,
|
||||||
|
} from './Accounts.types';
|
||||||
|
import { GetAccountsService } from './GetAccounts.service';
|
||||||
|
import { IFilterMeta } from '@/interfaces/Model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AccountsApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly createAccountService: CreateAccountService,
|
||||||
|
private readonly editAccountService: EditAccount,
|
||||||
|
private readonly deleteAccountService: DeleteAccount,
|
||||||
|
private readonly activateAccountService: ActivateAccount,
|
||||||
|
private readonly getAccountTypesService: GetAccountTypesService,
|
||||||
|
private readonly getAccountService: GetAccount,
|
||||||
|
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||||
|
private readonly getAccountsService: GetAccountsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
|
* @returns {Promise<IAccount>}
|
||||||
|
*/
|
||||||
|
public createAccount = (
|
||||||
|
accountDTO: CreateAccountDTO,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<Account> => {
|
||||||
|
return this.createAccountService.createAccount(accountDTO, trx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public deleteAccount = (accountId: number) => {
|
||||||
|
return this.deleteAccountService.deleteAccount(accountId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} accountId
|
||||||
|
* @param {IAccountEditDTO} accountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public editAccount = (accountId: number, accountDTO: EditAccountDTO) => {
|
||||||
|
return this.editAccountService.editAccount(accountId, accountDTO);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given account.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
*/
|
||||||
|
public activateAccount = (accountId: number) => {
|
||||||
|
return this.activateAccountService.activateAccount(accountId, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactivate the given account.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
*/
|
||||||
|
public inactivateAccount = (accountId: number) => {
|
||||||
|
return this.activateAccountService.activateAccount(accountId, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the account details.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
* @returns {Promise<IAccount>}
|
||||||
|
*/
|
||||||
|
public getAccount = (accountId: number) => {
|
||||||
|
return this.getAccountService.getAccount(accountId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all account types.
|
||||||
|
* @returns {Promise<IAccountType[]>}
|
||||||
|
*/
|
||||||
|
public getAccountTypes = () => {
|
||||||
|
return this.getAccountTypesService.getAccountsTypes();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts list.
|
||||||
|
* @param {IAccountsFilter} filterDTO - Filter DTO.
|
||||||
|
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||||
|
*/
|
||||||
|
public getAccounts = (
|
||||||
|
filterDTO: IAccountsFilter,
|
||||||
|
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> => {
|
||||||
|
return this.getAccountsService.getAccountsList(filterDTO);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the given account transactions.
|
||||||
|
* @param {IAccountsTransactionsFilter} filter
|
||||||
|
* @returns {Promise<IGetAccountTransactionPOJO[]>}
|
||||||
|
*/
|
||||||
|
public getAccountsTransactions = (
|
||||||
|
filter: IAccountsTransactionsFilter,
|
||||||
|
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||||
|
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// import { Inject, Service } from 'typedi';
|
||||||
|
// import { AccountsApplication } from './AccountsApplication.service';
|
||||||
|
// import { Exportable } from '../Export/Exportable';
|
||||||
|
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||||
|
// import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||||
|
|
||||||
|
// @Service()
|
||||||
|
// export class AccountsExportable extends Exportable {
|
||||||
|
// @Inject()
|
||||||
|
// private accountsApplication: AccountsApplication;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Retrieves the accounts data to exportable sheet.
|
||||||
|
// * @param {number} tenantId
|
||||||
|
// * @returns
|
||||||
|
// */
|
||||||
|
// public exportable(tenantId: number, query: IAccountsFilter) {
|
||||||
|
// const parsedQuery = {
|
||||||
|
// sortOrder: 'desc',
|
||||||
|
// columnSortBy: 'created_at',
|
||||||
|
// inactiveMode: false,
|
||||||
|
// ...query,
|
||||||
|
// structure: IAccountsStructureType.Flat,
|
||||||
|
// pageSize: EXPORT_SIZE_LIMIT,
|
||||||
|
// page: 1,
|
||||||
|
// } as IAccountsFilter;
|
||||||
|
|
||||||
|
// return this.accountsApplication
|
||||||
|
// .getAccounts(tenantId, parsedQuery)
|
||||||
|
// .then((output) => output.accounts);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
export const AccountsSampleData = [
|
||||||
|
{
|
||||||
|
'Account Name': 'Utilities Expense',
|
||||||
|
'Account Code': 9000,
|
||||||
|
Type: 'Expense',
|
||||||
|
Description: 'Omnis voluptatum consequatur.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Account Name': 'Unearned Revenue',
|
||||||
|
'Account Code': 9010,
|
||||||
|
Type: 'Long Term Liability',
|
||||||
|
Description: 'Autem odit voluptas nihil unde.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Account Name': 'Long-Term Debt',
|
||||||
|
'Account Code': 9020,
|
||||||
|
Type: 'Long Term Liability',
|
||||||
|
Description: 'In voluptas cumque exercitationem.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Account Name': 'Salaries and Wages Expense',
|
||||||
|
'Account Code': 9030,
|
||||||
|
Type: 'Expense',
|
||||||
|
Description: 'Assumenda aspernatur soluta aliquid perspiciatis quasi.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Account Name': 'Rental Income',
|
||||||
|
'Account Code': 9040,
|
||||||
|
Type: 'Income',
|
||||||
|
Description: 'Omnis possimus amet occaecati inventore.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Account Name': 'Paypal',
|
||||||
|
'Account Code': 9050,
|
||||||
|
Type: 'Bank',
|
||||||
|
Description: 'In voluptas cumque exercitationem.',
|
||||||
|
Active: 'T',
|
||||||
|
'Currency Code': '',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// import { Inject, Service } from 'typedi';
|
||||||
|
// import { Knex } from 'knex';
|
||||||
|
// import { IAccountCreateDTO } from '@/interfaces';
|
||||||
|
// import { CreateAccount } from './CreateAccount.service';
|
||||||
|
// import { Importable } from '../Import/Importable';
|
||||||
|
// import { AccountsSampleData } from './AccountsImportable.SampleData';
|
||||||
|
|
||||||
|
// @Service()
|
||||||
|
// export class AccountsImportable extends Importable {
|
||||||
|
// @Inject()
|
||||||
|
// private createAccountService: CreateAccount;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Importing to account service.
|
||||||
|
// * @param {number} tenantId
|
||||||
|
// * @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
// * @returns
|
||||||
|
// */
|
||||||
|
// public importable(
|
||||||
|
// tenantId: number,
|
||||||
|
// createAccountDTO: IAccountCreateDTO,
|
||||||
|
// trx?: Knex.Transaction
|
||||||
|
// ) {
|
||||||
|
// return this.createAccountService.createAccount(
|
||||||
|
// tenantId,
|
||||||
|
// createAccountDTO,
|
||||||
|
// trx
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Concurrrency controlling of the importing process.
|
||||||
|
// * @returns {number}
|
||||||
|
// */
|
||||||
|
// public get concurrency() {
|
||||||
|
// return 1;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
// */
|
||||||
|
// public sampleData(): any[] {
|
||||||
|
// return AccountsSampleData;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IAccountEventActivatedPayload } from './Accounts.types';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { AccountRepository } from './repositories/Account.repository';
|
||||||
|
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ActivateAccount {
|
||||||
|
constructor(
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
private readonly accountRepository: AccountRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates/Inactivates the given account.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @param {boolean} activate
|
||||||
|
*/
|
||||||
|
public activateAccount = async (accountId: number, activate?: boolean) => {
|
||||||
|
// Retrieve the given account or throw not found error.
|
||||||
|
const oldAccount = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Get all children accounts.
|
||||||
|
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
const dependenciesAccounts = accountsGraph.dependenciesOf(accountId);
|
||||||
|
|
||||||
|
const patchAccountsIds = [...dependenciesAccounts, accountId];
|
||||||
|
|
||||||
|
// Activate account and associated transactions under unit-of-work environment.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Activate and inactivate the given accounts ids.
|
||||||
|
activate
|
||||||
|
? await this.accountRepository.activateByIds(patchAccountsIds, trx)
|
||||||
|
: await this.accountRepository.inactivateByIds(patchAccountsIds, trx);
|
||||||
|
|
||||||
|
// Triggers `onAccountActivated` event.
|
||||||
|
this.eventEmitter.emitAsync(events.accounts.onActivated, {
|
||||||
|
accountId,
|
||||||
|
trx,
|
||||||
|
} as IAccountEventActivatedPayload);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
|
// import { IAccountDTO, IAccount, IAccountCreateDTO } from './Accounts.types';
|
||||||
|
// import AccountTypesUtils from '@/lib/AccountTypes';
|
||||||
|
import { ServiceError } from '../Items/ServiceError';
|
||||||
|
import { ERRORS, MAX_ACCOUNTS_CHART_DEPTH } from './constants';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { AccountRepository } from './repositories/Account.repository';
|
||||||
|
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||||
|
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||||
|
import { EditAccountDTO } from './EditAccount.dto';
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
|
export class CommandAccountValidators {
|
||||||
|
constructor(
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
private readonly accountRepository: AccountRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws error if the account was prefined.
|
||||||
|
* @param {Account} account
|
||||||
|
*/
|
||||||
|
public throwErrorIfAccountPredefined(account: Account) {
|
||||||
|
if (account.predefined) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_PREDEFINED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diff account type between new and old account, throw service error
|
||||||
|
* if they have different account type.
|
||||||
|
* @param {Account|CreateAccountDTO|EditAccountDTO} oldAccount
|
||||||
|
* @param {Account|CreateAccountDTO|EditAccountDTO} newAccount
|
||||||
|
*/
|
||||||
|
public async isAccountTypeChangedOrThrowError(
|
||||||
|
oldAccount: Account | CreateAccountDTO | EditAccountDTO,
|
||||||
|
newAccount: Account | CreateAccountDTO | EditAccountDTO,
|
||||||
|
) {
|
||||||
|
if (oldAccount.accountType !== newAccount.accountType) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account type or throws service error.
|
||||||
|
* @param {string} accountTypeKey -
|
||||||
|
* @return {IAccountType}
|
||||||
|
*/
|
||||||
|
public getAccountTypeOrThrowError(accountTypeKey: string) {
|
||||||
|
const accountType = AccountTypesUtils.getType(accountTypeKey);
|
||||||
|
|
||||||
|
if (!accountType) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return accountType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve parent account or throw service error.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
* @param {number} notAccountId - Ignore the account id.
|
||||||
|
*/
|
||||||
|
public async getParentAccountOrThrowError(
|
||||||
|
accountId: number,
|
||||||
|
notAccountId?: number,
|
||||||
|
) {
|
||||||
|
const parentAccount = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (notAccountId) {
|
||||||
|
query.whereNot('id', notAccountId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!parentAccount) {
|
||||||
|
throw new ServiceError(ERRORS.PARENT_ACCOUNT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return parentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws error if the account type was not unique on the storage.
|
||||||
|
* @param {string} accountCode - Account code.
|
||||||
|
* @param {number} notAccountId - Ignore the account id.
|
||||||
|
*/
|
||||||
|
public async isAccountCodeUniqueOrThrowError(
|
||||||
|
accountCode: string,
|
||||||
|
notAccountId?: number,
|
||||||
|
) {
|
||||||
|
const account = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.where('code', accountCode)
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (notAccountId) {
|
||||||
|
query.whereNot('id', notAccountId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (account.length > 0) {
|
||||||
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
||||||
|
'Account code is not unique.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the account name uniquiness.
|
||||||
|
* @param {string} accountName - Account name.
|
||||||
|
* @param {number} notAccountId - Ignore the account id.
|
||||||
|
*/
|
||||||
|
public async validateAccountNameUniquiness(
|
||||||
|
accountName: string,
|
||||||
|
notAccountId?: number,
|
||||||
|
) {
|
||||||
|
const foundAccount = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.findOne('name', accountName)
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (notAccountId) {
|
||||||
|
query.whereNot('id', notAccountId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (foundAccount) {
|
||||||
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
||||||
|
'Account name is not unique.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given account type supports multi-currency.
|
||||||
|
* @param {CreateAccountDTO | EditAccountDTO} accountDTO -
|
||||||
|
*/
|
||||||
|
public validateAccountTypeSupportCurrency = (
|
||||||
|
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||||
|
baseCurrency: string,
|
||||||
|
) => {
|
||||||
|
// Can't continue to validate the type has multi-currency feature
|
||||||
|
// if the given currency equals the base currency or not assigned.
|
||||||
|
if (accountDTO.currencyCode === baseCurrency || !accountDTO.currencyCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const meta = AccountTypesUtils.getType(accountDTO.accountType);
|
||||||
|
|
||||||
|
// Throw error if the account type does not support multi-currency.
|
||||||
|
if (!meta?.multiCurrency) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the account DTO currency code whether equals the currency code of
|
||||||
|
* parent account.
|
||||||
|
* @param {CreateAccountDTO | EditAccountDTO} accountDTO
|
||||||
|
* @param {Account} parentAccount
|
||||||
|
* @param {string} baseCurrency -
|
||||||
|
* @throws {ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT)}
|
||||||
|
*/
|
||||||
|
public validateCurrentSameParentAccount = (
|
||||||
|
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||||
|
parentAccount: Account,
|
||||||
|
baseCurrency: string,
|
||||||
|
) => {
|
||||||
|
// If the account DTO currency not assigned and the parent account has no base currency.
|
||||||
|
if (
|
||||||
|
!accountDTO.currencyCode &&
|
||||||
|
parentAccount.currencyCode !== baseCurrency
|
||||||
|
) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||||
|
}
|
||||||
|
// If the account DTO is assigned and not equals the currency code of parent account.
|
||||||
|
if (
|
||||||
|
accountDTO.currencyCode &&
|
||||||
|
parentAccount.currencyCode !== accountDTO.currencyCode
|
||||||
|
) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws service error if parent account has different type.
|
||||||
|
* @param {IAccountDTO} accountDTO
|
||||||
|
* @param {IAccount} parentAccount
|
||||||
|
*/
|
||||||
|
public throwErrorIfParentHasDiffType(
|
||||||
|
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||||
|
parentAccount: Account,
|
||||||
|
) {
|
||||||
|
if (accountDTO.accountType !== parentAccount.accountType) {
|
||||||
|
throw new ServiceError(ERRORS.PARENT_ACCOUNT_HAS_DIFFERENT_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account of throw service error in case account not found.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @return {IAccount}
|
||||||
|
*/
|
||||||
|
public async getAccountOrThrowError(accountId: number) {
|
||||||
|
const account = await this.accountRepository.findOneById(accountId);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the max depth level of accounts chart.
|
||||||
|
* @param {number} parentAccountId - Parent account id.
|
||||||
|
*/
|
||||||
|
public async validateMaxParentAccountDepthLevels(parentAccountId: number) {
|
||||||
|
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
const parentDependantsIds = accountsGraph.dependantsOf(parentAccountId);
|
||||||
|
|
||||||
|
if (parentDependantsIds.length >= MAX_ACCOUNTS_CHART_DEPTH) {
|
||||||
|
throw new ServiceError(ERRORS.PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
IsString,
|
||||||
|
IsOptional,
|
||||||
|
IsInt,
|
||||||
|
MinLength,
|
||||||
|
MaxLength,
|
||||||
|
IsBoolean,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateAccountDTO {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(6)
|
||||||
|
code?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
currencyCode?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||||
|
accountType: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
parentAccountId?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
active?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
plaidAccountId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
plaidItemId?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { kebabCase } from 'lodash';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import {
|
||||||
|
// IAccount,
|
||||||
|
// IAccountEventCreatedPayload,
|
||||||
|
// IAccountCreateDTO,
|
||||||
|
IAccountEventCreatingPayload,
|
||||||
|
CreateAccountParams,
|
||||||
|
} from './Accounts.types';
|
||||||
|
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateAccountService {
|
||||||
|
constructor(
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
private readonly validator: CommandAccountValidators,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize the account creation.
|
||||||
|
* @param {CreateAccountDTO} accountDTO
|
||||||
|
*/
|
||||||
|
private authorize = async (
|
||||||
|
accountDTO: CreateAccountDTO,
|
||||||
|
baseCurrency: string,
|
||||||
|
params?: CreateAccountParams,
|
||||||
|
) => {
|
||||||
|
// Validate account name uniquiness.
|
||||||
|
if (!params.ignoreUniqueName) {
|
||||||
|
await this.validator.validateAccountNameUniquiness(accountDTO.name);
|
||||||
|
}
|
||||||
|
// Validate the account code uniquiness.
|
||||||
|
if (accountDTO.code) {
|
||||||
|
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
|
||||||
|
}
|
||||||
|
// Retrieve the account type meta or throw service error if not found.
|
||||||
|
this.validator.getAccountTypeOrThrowError(accountDTO.accountType);
|
||||||
|
|
||||||
|
// Ingore the parent account validation if not presented.
|
||||||
|
if (accountDTO.parentAccountId) {
|
||||||
|
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||||
|
accountDTO.parentAccountId,
|
||||||
|
);
|
||||||
|
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||||
|
|
||||||
|
// Inherit active status from parent account.
|
||||||
|
accountDTO.active = parentAccount.active;
|
||||||
|
|
||||||
|
// Validate should currency code be the same currency of parent account.
|
||||||
|
this.validator.validateCurrentSameParentAccount(
|
||||||
|
accountDTO,
|
||||||
|
parentAccount,
|
||||||
|
baseCurrency,
|
||||||
|
);
|
||||||
|
// Validates the max depth level of accounts chart.
|
||||||
|
await this.validator.validateMaxParentAccountDepthLevels(
|
||||||
|
accountDTO.parentAccountId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Validates the given account type supports the multi-currency.
|
||||||
|
this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the create account DTO to input model.
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
*/
|
||||||
|
private transformDTOToModel = (
|
||||||
|
createAccountDTO: CreateAccountDTO,
|
||||||
|
baseCurrency: string,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...createAccountDTO,
|
||||||
|
slug: kebabCase(createAccountDTO.name),
|
||||||
|
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
||||||
|
|
||||||
|
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
||||||
|
isSyncingOwner: Boolean(
|
||||||
|
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new account on the storage.
|
||||||
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
|
* @returns {Promise<IAccount>}
|
||||||
|
*/
|
||||||
|
public createAccount = async (
|
||||||
|
accountDTO: CreateAccountDTO,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
params: CreateAccountParams = { ignoreUniqueName: false },
|
||||||
|
): Promise<Account> => {
|
||||||
|
// Retrieves the given tenant metadata.
|
||||||
|
const tenant = await this.tenancyContext.getTenant(true);
|
||||||
|
|
||||||
|
// Authorize the account creation.
|
||||||
|
await this.authorize(accountDTO, tenant.metadata.baseCurrency, params);
|
||||||
|
|
||||||
|
// Transformes the DTO to model.
|
||||||
|
const accountInputModel = this.transformDTOToModel(
|
||||||
|
accountDTO,
|
||||||
|
tenant.metadata.baseCurrency,
|
||||||
|
);
|
||||||
|
// Creates a new account with associated transactions under unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onAccountCreating` event.
|
||||||
|
await this.eventEmitter.emitAsync(events.accounts.onCreating, {
|
||||||
|
accountDTO,
|
||||||
|
trx,
|
||||||
|
} as IAccountEventCreatingPayload);
|
||||||
|
|
||||||
|
// Inserts account to the storage.
|
||||||
|
const account = await this.accountModel.query().insert({
|
||||||
|
...accountInputModel,
|
||||||
|
});
|
||||||
|
// Triggers `onAccountCreated` event.
|
||||||
|
// await this.eventEmitter.emitAsync(events.accounts.onCreated, {
|
||||||
|
// account,
|
||||||
|
// accountId: account.id,
|
||||||
|
// trx,
|
||||||
|
// } as IAccountEventCreatedPayload);
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}, trx);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
// import { IAccountEventDeletedPayload } from '@/interfaces';
|
||||||
|
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { IAccountEventDeletedPayload } from './Accounts.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DeleteAccount {
|
||||||
|
constructor(
|
||||||
|
@Inject(Account.name) private accountModel: typeof Account,
|
||||||
|
private eventEmitter: EventEmitter2,
|
||||||
|
private uow: UnitOfWork,
|
||||||
|
private validator: CommandAccountValidators,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize account delete.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
*/
|
||||||
|
private authorize = async (accountId: number, oldAccount: Account) => {
|
||||||
|
// Throw error if the account was predefined.
|
||||||
|
this.validator.throwErrorIfAccountPredefined(oldAccount);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink the given parent account with children accounts.
|
||||||
|
* @param {number|number[]} parentAccountId -
|
||||||
|
*/
|
||||||
|
private async unassociateChildrenAccountsFromParent(
|
||||||
|
parentAccountId: number | number[],
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const accountsIds = Array.isArray(parentAccountId)
|
||||||
|
? parentAccountId
|
||||||
|
: [parentAccountId];
|
||||||
|
|
||||||
|
await this.accountModel
|
||||||
|
.query(trx)
|
||||||
|
.whereIn('parent_account_id', accountsIds)
|
||||||
|
.patch({ parent_account_id: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the account from the storage.
|
||||||
|
* @param {number} accountId
|
||||||
|
*/
|
||||||
|
public deleteAccount = async (accountId: number): Promise<void> => {
|
||||||
|
// Retrieve account or not found service error.
|
||||||
|
const oldAccount = await this.accountModel.query().findById(accountId);
|
||||||
|
|
||||||
|
// Authorize before delete account.
|
||||||
|
await this.authorize(accountId, oldAccount);
|
||||||
|
|
||||||
|
// Deletes the account and associated transactions under UOW environment.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onAccountDelete` event.
|
||||||
|
await this.eventEmitter.emitAsync(events.accounts.onDelete, {
|
||||||
|
trx,
|
||||||
|
oldAccount,
|
||||||
|
} as IAccountEventDeletedPayload);
|
||||||
|
|
||||||
|
// Unlink the parent account from children accounts.
|
||||||
|
await this.unassociateChildrenAccountsFromParent(accountId, trx);
|
||||||
|
|
||||||
|
// Deletes account by the given id.
|
||||||
|
await this.accountModel.query(trx).deleteById(accountId);
|
||||||
|
|
||||||
|
// Triggers `onAccountDeleted` event.
|
||||||
|
await this.eventEmitter.emitAsync(events.accounts.onDeleted, {
|
||||||
|
accountId,
|
||||||
|
oldAccount,
|
||||||
|
trx,
|
||||||
|
} as IAccountEventDeletedPayload);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
IsString,
|
||||||
|
IsOptional,
|
||||||
|
IsInt,
|
||||||
|
MinLength,
|
||||||
|
MaxLength,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export class EditAccountDTO {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(6)
|
||||||
|
code?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||||
|
accountType: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
parentAccountId?: number;
|
||||||
|
}
|
||||||
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { EditAccountDTO } from './EditAccount.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EditAccount {
|
||||||
|
constructor(
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
private readonly validator: CommandAccountValidators,
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize the account editing.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @param {IAccountEditDTO} accountDTO
|
||||||
|
* @param {IAccount} oldAccount -
|
||||||
|
*/
|
||||||
|
private authorize = async (
|
||||||
|
accountId: number,
|
||||||
|
accountDTO: EditAccountDTO,
|
||||||
|
oldAccount: Account,
|
||||||
|
) => {
|
||||||
|
// Validate account name uniquiness.
|
||||||
|
await this.validator.validateAccountNameUniquiness(
|
||||||
|
accountDTO.name,
|
||||||
|
accountId,
|
||||||
|
);
|
||||||
|
// Validate the account type should be not mutated.
|
||||||
|
await this.validator.isAccountTypeChangedOrThrowError(
|
||||||
|
oldAccount,
|
||||||
|
accountDTO,
|
||||||
|
);
|
||||||
|
// Validate the account code not exists on the storage.
|
||||||
|
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
|
||||||
|
await this.validator.isAccountCodeUniqueOrThrowError(
|
||||||
|
accountDTO.code,
|
||||||
|
oldAccount.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Retrieve the parent account of throw not found service error.
|
||||||
|
if (accountDTO.parentAccountId) {
|
||||||
|
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||||
|
accountDTO.parentAccountId,
|
||||||
|
oldAccount.id,
|
||||||
|
);
|
||||||
|
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits details of the given account.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @param {IAccountDTO} accountDTO
|
||||||
|
*/
|
||||||
|
public async editAccount(
|
||||||
|
accountId: number,
|
||||||
|
accountDTO: EditAccountDTO,
|
||||||
|
): Promise<Account> {
|
||||||
|
// Retrieve the old account or throw not found service error.
|
||||||
|
const oldAccount = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Authorize the account editing.
|
||||||
|
await this.authorize(accountId, accountDTO, oldAccount);
|
||||||
|
|
||||||
|
// Edits account and associated transactions under unit-of-work environment.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onAccountEditing` event.
|
||||||
|
await this.eventEmitter.emitAsync(events.accounts.onEditing, {
|
||||||
|
oldAccount,
|
||||||
|
accountDTO,
|
||||||
|
});
|
||||||
|
// Update the account on the storage.
|
||||||
|
const account = await this.accountModel
|
||||||
|
.query(trx)
|
||||||
|
.findById(accountId)
|
||||||
|
.updateAndFetch({ ...accountDTO });
|
||||||
|
|
||||||
|
// Triggers `onAccountEdited` event.
|
||||||
|
// await this.eventEmitter.emitAsync(events.accounts.onEdited, {
|
||||||
|
// account,
|
||||||
|
// oldAccount,
|
||||||
|
// trx,
|
||||||
|
// } as IAccountEventEditedPayload);
|
||||||
|
|
||||||
|
return account;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { AccountTransformer } from './Account.transformer';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { AccountRepository } from './repositories/Account.repository';
|
||||||
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetAccount {
|
||||||
|
constructor(
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
private readonly accountRepository: AccountRepository,
|
||||||
|
private readonly transformer: TransformerInjectable,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given account details.
|
||||||
|
* @param {number} accountId
|
||||||
|
*/
|
||||||
|
public getAccount = async (accountId: number) => {
|
||||||
|
// Find the given account or throw not found error.
|
||||||
|
const account = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.withGraphFetched('plaidItem')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
// Transforms the account model to POJO.
|
||||||
|
const transformed = await this.transformer.transform(
|
||||||
|
account,
|
||||||
|
new AccountTransformer(),
|
||||||
|
{ accountsGraph },
|
||||||
|
);
|
||||||
|
const eventPayload = { accountId };
|
||||||
|
|
||||||
|
// Triggers `onAccountViewed` event.
|
||||||
|
await this.eventEmitter.emitAsync(events.accounts.onViewed, eventPayload);
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
IAccountsTransactionsFilter,
|
||||||
|
IGetAccountTransactionPOJO,
|
||||||
|
} from './Accounts.types';
|
||||||
|
import { AccountTransactionTransformer } from './AccountTransaction.transformer';
|
||||||
|
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetAccountTransactionsService {
|
||||||
|
constructor(
|
||||||
|
private readonly transformer: TransformerInjectable,
|
||||||
|
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
private readonly accountTransaction: typeof AccountTransaction,
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly account: typeof Account,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts transactions.
|
||||||
|
* @param {IAccountsTransactionsFilter} filter -
|
||||||
|
*/
|
||||||
|
public getAccountsTransactions = async (
|
||||||
|
filter: IAccountsTransactionsFilter,
|
||||||
|
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||||
|
// Retrieve the given account or throw not found error.
|
||||||
|
if (filter.accountId) {
|
||||||
|
await this.account.query().findById(filter.accountId).throwIfNotFound();
|
||||||
|
}
|
||||||
|
const transactions = await this.accountTransaction
|
||||||
|
.query()
|
||||||
|
.onBuild((query) => {
|
||||||
|
query.orderBy('date', 'DESC');
|
||||||
|
|
||||||
|
if (filter.accountId) {
|
||||||
|
query.where('account_id', filter.accountId);
|
||||||
|
}
|
||||||
|
query.withGraphFetched('account');
|
||||||
|
query.withGraphFetched('contact');
|
||||||
|
query.limit(filter.limit || 50);
|
||||||
|
});
|
||||||
|
// Transform the account transaction.
|
||||||
|
return this.transformer.transform(
|
||||||
|
transactions,
|
||||||
|
new AccountTransactionTransformer(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// import { IAccountType } from './Accounts.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetAccountTypesService {
|
||||||
|
/**
|
||||||
|
* Retrieve all accounts types.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @return {IAccountType}
|
||||||
|
*/
|
||||||
|
public getAccountsTypes() {
|
||||||
|
const accountTypes = AccountTypesUtils.getList();
|
||||||
|
|
||||||
|
return accountTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { IAccountsFilter } from './Accounts.types';
|
||||||
|
import { DynamicListService } from '../DynamicListing/DynamicList.service';
|
||||||
|
import { AccountTransformer } from './Account.transformer';
|
||||||
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
|
import { Account } from './models/Account.model';
|
||||||
|
import { AccountRepository } from './repositories/Account.repository';
|
||||||
|
import { IFilterMeta } from '@/interfaces/Model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetAccountsService {
|
||||||
|
constructor(
|
||||||
|
private readonly dynamicListService: DynamicListService,
|
||||||
|
private readonly transformerService: TransformerInjectable,
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account,
|
||||||
|
private readonly accountRepository: AccountRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts datatable list.
|
||||||
|
* @param {IAccountsFilter} accountsFilter
|
||||||
|
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||||
|
*/
|
||||||
|
public async getAccountsList(
|
||||||
|
filterDTO: IAccountsFilter,
|
||||||
|
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> {
|
||||||
|
// Parses the stringified filter roles.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
|
this.accountModel,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
// Retrieve accounts model based on the given query.
|
||||||
|
const accounts = await this.accountModel.query().onBuild((builder) => {
|
||||||
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
|
});
|
||||||
|
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
// Retrieves the transformed accounts collection.
|
||||||
|
const transformedAccounts = await this.transformerService.transform(
|
||||||
|
accounts,
|
||||||
|
new AccountTransformer(),
|
||||||
|
{ accountsGraph, structure: filterDTO.structure },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accounts: transformedAccounts,
|
||||||
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsees accounts list filter DTO.
|
||||||
|
* @param filterDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private parseListFilterDTO(filterDTO) {
|
||||||
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// import { Inject, Service } from 'typedi';
|
||||||
|
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
// @Service()
|
||||||
|
// export class MutateBaseCurrencyAccounts {
|
||||||
|
// @Inject()
|
||||||
|
// tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Mutates the all accounts or the organziation.
|
||||||
|
// * @param {number} tenantId
|
||||||
|
// * @param {string} currencyCode
|
||||||
|
// */
|
||||||
|
// public mutateAllAccountsCurrency = async (
|
||||||
|
// tenantId: number,
|
||||||
|
// currencyCode: string
|
||||||
|
// ) => {
|
||||||
|
// const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// await Account.query().update({ currencyCode });
|
||||||
|
// };
|
||||||
|
// }
|
||||||
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
ACCOUNT_NOT_FOUND: 'account_not_found',
|
||||||
|
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
||||||
|
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
||||||
|
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
||||||
|
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
||||||
|
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
||||||
|
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
||||||
|
ACCOUNT_PREDEFINED: 'account_predefined',
|
||||||
|
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
||||||
|
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
||||||
|
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
||||||
|
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
|
||||||
|
'close_account_and_to_account_not_same_type',
|
||||||
|
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
||||||
|
ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY:
|
||||||
|
'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY',
|
||||||
|
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT:
|
||||||
|
'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT',
|
||||||
|
PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL:
|
||||||
|
'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default views columns.
|
||||||
|
export const DEFAULT_VIEW_COLUMNS = [
|
||||||
|
{ key: 'name', label: 'Account name' },
|
||||||
|
{ key: 'code', label: 'Account code' },
|
||||||
|
{ key: 'account_type_label', label: 'Account type' },
|
||||||
|
{ key: 'account_normal', label: 'Account normal' },
|
||||||
|
{ key: 'amount', label: 'Balance' },
|
||||||
|
{ key: 'currencyCode', label: 'Currency' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MAX_ACCOUNTS_CHART_DEPTH = 5;
|
||||||
|
|
||||||
|
// Accounts default views.
|
||||||
|
export const DEFAULT_VIEWS = [
|
||||||
|
{
|
||||||
|
name: 'Assets',
|
||||||
|
slug: 'assets',
|
||||||
|
rolesLogicExpression: '1',
|
||||||
|
roles: [
|
||||||
|
{ index: 1, fieldKey: 'root_type', comparator: 'equals', value: 'asset' },
|
||||||
|
],
|
||||||
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Liabilities',
|
||||||
|
slug: 'liabilities',
|
||||||
|
rolesLogicExpression: '1',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
fieldKey: 'root_type',
|
||||||
|
index: 1,
|
||||||
|
comparator: 'equals',
|
||||||
|
value: 'liability',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Equity',
|
||||||
|
slug: 'equity',
|
||||||
|
rolesLogicExpression: '1',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
fieldKey: 'root_type',
|
||||||
|
index: 1,
|
||||||
|
comparator: 'equals',
|
||||||
|
value: 'equity',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Income',
|
||||||
|
slug: 'income',
|
||||||
|
rolesLogicExpression: '1',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
fieldKey: 'root_type',
|
||||||
|
index: 1,
|
||||||
|
comparator: 'equals',
|
||||||
|
value: 'income',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Expenses',
|
||||||
|
slug: 'expenses',
|
||||||
|
rolesLogicExpression: '1',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
fieldKey: 'root_type',
|
||||||
|
index: 1,
|
||||||
|
comparator: 'equals',
|
||||||
|
value: 'expense',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
// import { mixin, Model } from 'objection';
|
||||||
|
import { castArray } from 'lodash';
|
||||||
|
import DependencyGraph from '@/libs/dependency-graph';
|
||||||
|
import {
|
||||||
|
ACCOUNT_TYPES,
|
||||||
|
getAccountsSupportsMultiCurrency,
|
||||||
|
} from '@/constants/accounts';
|
||||||
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
|
// import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||||
|
// import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||||
|
// import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
||||||
|
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||||
|
import { Model } from 'objection';
|
||||||
|
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||||
|
// import AccountSettings from './Account.Settings';
|
||||||
|
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
|
||||||
|
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
|
||||||
|
// import { flatToNestedArray } from 'utils';
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
// export class Account extends mixin(TenantModel, [
|
||||||
|
// ModelSettings,
|
||||||
|
// CustomViewBaseModel,
|
||||||
|
// SearchableModel,
|
||||||
|
// ]) {
|
||||||
|
|
||||||
|
export class Account extends TenantModel {
|
||||||
|
public name!: string;
|
||||||
|
public slug!: string;
|
||||||
|
public code!: string;
|
||||||
|
public index!: number;
|
||||||
|
public accountType!: string;
|
||||||
|
public predefined!: boolean;
|
||||||
|
public currencyCode!: string;
|
||||||
|
public active!: boolean;
|
||||||
|
public bankBalance!: number;
|
||||||
|
public lastFeedsUpdatedAt!: string | null;
|
||||||
|
public amount!: number;
|
||||||
|
public plaidItemId!: number;
|
||||||
|
|
||||||
|
public plaidItem!: PlaidItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table name.
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'accounts';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamps columns.
|
||||||
|
*/
|
||||||
|
static get timestamps() {
|
||||||
|
return ['createdAt', 'updatedAt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return [
|
||||||
|
'accountTypeLabel',
|
||||||
|
'accountParentType',
|
||||||
|
'accountRootType',
|
||||||
|
'accountNormal',
|
||||||
|
'accountNormalFormatted',
|
||||||
|
'isBalanceSheetAccount',
|
||||||
|
'isPLSheet',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account normal.
|
||||||
|
*/
|
||||||
|
get accountNormal() {
|
||||||
|
return AccountTypesUtils.getType(this.accountType, 'normal');
|
||||||
|
}
|
||||||
|
|
||||||
|
get accountNormalFormatted() {
|
||||||
|
const paris = {
|
||||||
|
credit: 'Credit',
|
||||||
|
debit: 'Debit',
|
||||||
|
};
|
||||||
|
return paris[this.accountNormal] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account type label.
|
||||||
|
*/
|
||||||
|
get accountTypeLabel() {
|
||||||
|
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account parent type.
|
||||||
|
*/
|
||||||
|
get accountParentType() {
|
||||||
|
return AccountTypesUtils.getType(this.accountType, 'parentType');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account root type.
|
||||||
|
*/
|
||||||
|
get accountRootType() {
|
||||||
|
return AccountTypesUtils.getType(this.accountType, 'rootType');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether the account is balance sheet account.
|
||||||
|
*/
|
||||||
|
get isBalanceSheetAccount() {
|
||||||
|
return this.isBalanceSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether the account is profit/loss sheet account.
|
||||||
|
*/
|
||||||
|
get isPLSheet() {
|
||||||
|
return this.isProfitLossSheet();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Allows to mark model as resourceable to viewable and filterable.
|
||||||
|
*/
|
||||||
|
static get resourceable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
|
static get modifiers() {
|
||||||
|
const TABLE_NAME = Account.tableName;
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('accounts.active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterAccounts(query, accountIds) {
|
||||||
|
if (accountIds.length > 0) {
|
||||||
|
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filterAccountTypes(query, typesIds) {
|
||||||
|
if (typesIds.length > 0) {
|
||||||
|
query.whereIn('account_types.account_type_id', typesIds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
viewRolesBuilder(query, conditionals, expression) {
|
||||||
|
// buildFilterQuery(Account.tableName, conditionals, expression)(query);
|
||||||
|
},
|
||||||
|
sortColumnBuilder(query, columnKey, direction) {
|
||||||
|
// buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by root type.
|
||||||
|
*/
|
||||||
|
filterByRootType(query, rootType) {
|
||||||
|
const filterTypes = ACCOUNT_TYPES.filter(
|
||||||
|
(accountType) => accountType.rootType === rootType,
|
||||||
|
).map((accountType) => accountType.key);
|
||||||
|
|
||||||
|
query.whereIn('account_type', filterTypes);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by account normal
|
||||||
|
*/
|
||||||
|
filterByAccountNormal(query, accountNormal) {
|
||||||
|
const filterTypes = ACCOUNT_TYPES.filter(
|
||||||
|
(accountType) => accountType.normal === accountNormal,
|
||||||
|
).map((accountType) => accountType.key);
|
||||||
|
|
||||||
|
query.whereIn('account_type', filterTypes);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds account by the given slug.
|
||||||
|
* @param {*} query
|
||||||
|
* @param {*} slug
|
||||||
|
*/
|
||||||
|
findBySlug(query, slug) {
|
||||||
|
query.where('slug', slug).first();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} query
|
||||||
|
* @param {*} baseCyrrency
|
||||||
|
*/
|
||||||
|
preventMutateBaseCurrency(query) {
|
||||||
|
const accountsTypes = getAccountsSupportsMultiCurrency();
|
||||||
|
const accountsTypesKeys = accountsTypes.map((type) => type.key);
|
||||||
|
|
||||||
|
query
|
||||||
|
.whereIn('accountType', accountsTypesKeys)
|
||||||
|
.where('seededAt', null)
|
||||||
|
.first();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
const { AccountTransaction } = require('./AccountTransaction.model');
|
||||||
|
const { Item } = require('../../Items/models/Item');
|
||||||
|
// const InventoryAdjustment = require('models/InventoryAdjustment');
|
||||||
|
// const ManualJournalEntry = require('models/ManualJournalEntry');
|
||||||
|
// const Expense = require('models/Expense');
|
||||||
|
// const ExpenseEntry = require('models/ExpenseCategory');
|
||||||
|
// const ItemEntry = require('models/ItemEntry');
|
||||||
|
// const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
||||||
|
const { PlaidItem } = require('../../BankingPlaid/models/PlaidItem');
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Account model may has many transactions.
|
||||||
|
*/
|
||||||
|
transactions: {
|
||||||
|
relation: Model.HasManyRelation,
|
||||||
|
modelClass: AccountTransaction,
|
||||||
|
join: {
|
||||||
|
from: 'accounts.id',
|
||||||
|
to: 'accounts_transactions.accountId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Account may has many items as cost account.
|
||||||
|
*/
|
||||||
|
itemsCostAccount: {
|
||||||
|
relation: Model.HasManyRelation,
|
||||||
|
modelClass: Item,
|
||||||
|
join: {
|
||||||
|
from: 'accounts.id',
|
||||||
|
to: 'items.costAccountId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Account may has many items as sell account.
|
||||||
|
*/
|
||||||
|
itemsSellAccount: {
|
||||||
|
relation: Model.HasManyRelation,
|
||||||
|
modelClass: Item,
|
||||||
|
join: {
|
||||||
|
from: 'accounts.id',
|
||||||
|
to: 'items.sellAccountId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// inventoryAdjustments: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: InventoryAdjustment.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'inventory_adjustments.adjustmentAccountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// manualJournalEntries: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: ManualJournalEntry.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'manual_journals_entries.accountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// expensePayments: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: Expense.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'expenses_transactions.paymentAccountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// expenseEntries: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: ExpenseEntry.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'expense_transaction_categories.expenseAccountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// entriesCostAccount: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: ItemEntry.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'items_entries.costAccountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
// entriesSellAccount: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: ItemEntry.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'items_entries.sellAccountId',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// * Associated uncategorized transactions.
|
||||||
|
// */
|
||||||
|
// uncategorizedTransactions: {
|
||||||
|
// relation: Model.HasManyRelation,
|
||||||
|
// modelClass: UncategorizedTransaction.default,
|
||||||
|
// join: {
|
||||||
|
// from: 'accounts.id',
|
||||||
|
// to: 'uncategorized_cashflow_transactions.accountId',
|
||||||
|
// },
|
||||||
|
// filter: (query) => {
|
||||||
|
// query.where('categorized', false);
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Account model may belongs to a Plaid item.
|
||||||
|
*/
|
||||||
|
plaidItem: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: PlaidItem,
|
||||||
|
join: {
|
||||||
|
from: 'accounts.plaidItemId',
|
||||||
|
to: 'plaid_items.plaidItemId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the given type equals the account type.
|
||||||
|
* @param {string} accountType
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isAccountType(accountType) {
|
||||||
|
const types = castArray(accountType);
|
||||||
|
return types.indexOf(this.accountType) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the given root type equals the account type.
|
||||||
|
* @param {string} rootType
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isRootType(rootType) {
|
||||||
|
return AccountTypesUtils.isRootTypeEqualsKey(this.accountType, rootType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmine whether the given parent type equals the account type.
|
||||||
|
* @param {string} parentType
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isParentType(parentType) {
|
||||||
|
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||||
|
this.accountType,
|
||||||
|
parentType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the account is balance sheet account.
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isBalanceSheet() {
|
||||||
|
return AccountTypesUtils.isTypeBalanceSheet(this.accountType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the account is profit/loss account.
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isProfitLossSheet() {
|
||||||
|
return AccountTypesUtils.isTypePLSheet(this.accountType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the account is income statement account
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isIncomeSheet() {
|
||||||
|
return this.isProfitLossSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts flatten accounts list to nested array.
|
||||||
|
* @param {Array} accounts
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||||
|
// return flatToNestedArray(accounts, {
|
||||||
|
// id: 'id',
|
||||||
|
// parentId: 'parentAccountId',
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the accounts list to depenedency graph structure.
|
||||||
|
* @param {IAccount[]} accounts
|
||||||
|
*/
|
||||||
|
static toDependencyGraph(accounts) {
|
||||||
|
return DependencyGraph.fromArray(accounts, {
|
||||||
|
itemId: 'id',
|
||||||
|
parentItemId: 'parentAccountId',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model settings.
|
||||||
|
*/
|
||||||
|
// static get meta() {
|
||||||
|
// return AccountSettings;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the default custom views, roles and columns.
|
||||||
|
*/
|
||||||
|
// static get defaultViews() {
|
||||||
|
// return DEFAULT_VIEWS;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model search roles.
|
||||||
|
*/
|
||||||
|
static get searchRoles() {
|
||||||
|
return [
|
||||||
|
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||||
|
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents mutate base currency since the model is not empty.
|
||||||
|
*/
|
||||||
|
static get preventMutateBaseCurrency() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
import { Model, raw } from 'objection';
|
||||||
|
import moment, { unitOfTime } from 'moment';
|
||||||
|
import { isEmpty, castArray } from 'lodash';
|
||||||
|
import { BaseModel } from '@/models/Model';
|
||||||
|
import { Account } from './Account.model';
|
||||||
|
// import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||||
|
|
||||||
|
export class AccountTransaction extends BaseModel {
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
accountId: number;
|
||||||
|
contactId: number;
|
||||||
|
credit: number;
|
||||||
|
debit: number;
|
||||||
|
exchangeRate: number;
|
||||||
|
taxRate: number;
|
||||||
|
date: Date | string;
|
||||||
|
transactionType: string;
|
||||||
|
currencyCode: string;
|
||||||
|
referenceTypeFormatted: string;
|
||||||
|
transactionNumber!: string;
|
||||||
|
referenceNumber!: string;
|
||||||
|
note!: string;
|
||||||
|
|
||||||
|
index!: number;
|
||||||
|
indexGroup!: number;
|
||||||
|
|
||||||
|
taxRateId!: number;
|
||||||
|
|
||||||
|
branchId!: number;
|
||||||
|
userId!: number;
|
||||||
|
itemId!: number;
|
||||||
|
projectId!: number;
|
||||||
|
account: Account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table name
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'accounts_transactions';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamps columns.
|
||||||
|
*/
|
||||||
|
get timestamps() {
|
||||||
|
return ['createdAt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['referenceTypeFormatted', 'creditLocal', 'debitLocal'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the credit amount in base currency.
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
get creditLocal() {
|
||||||
|
return this.credit * this.exchangeRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the debit amount in base currency.
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
get debitLocal() {
|
||||||
|
return this.debit * this.exchangeRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Retrieve formatted reference type.
|
||||||
|
// * @return {string}
|
||||||
|
// */
|
||||||
|
// get referenceTypeFormatted() {
|
||||||
|
// return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Filters accounts by the given ids.
|
||||||
|
* @param {Query} query
|
||||||
|
* @param {number[]} accountsIds
|
||||||
|
*/
|
||||||
|
filterAccounts(query, accountsIds) {
|
||||||
|
if (Array.isArray(accountsIds) && accountsIds.length > 0) {
|
||||||
|
query.whereIn('account_id', accountsIds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the transaction types.
|
||||||
|
* @param {Query} query
|
||||||
|
* @param {string[]} types
|
||||||
|
*/
|
||||||
|
filterTransactionTypes(query, types) {
|
||||||
|
if (Array.isArray(types) && types.length > 0) {
|
||||||
|
query.whereIn('reference_type', types);
|
||||||
|
} else if (typeof types === 'string') {
|
||||||
|
query.where('reference_type', types);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the date range.
|
||||||
|
* @param {Query} query
|
||||||
|
* @param {moment.MomentInput} startDate
|
||||||
|
* @param {moment.MomentInput} endDate
|
||||||
|
* @param {unitOfTime.StartOf} type
|
||||||
|
*/
|
||||||
|
filterDateRange(
|
||||||
|
query,
|
||||||
|
startDate: moment.MomentInput,
|
||||||
|
endDate: moment.MomentInput,
|
||||||
|
type: unitOfTime.StartOf = 'day',
|
||||||
|
) {
|
||||||
|
const dateFormat = 'YYYY-MM-DD';
|
||||||
|
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||||
|
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
query.where('date', '>=', fromDate);
|
||||||
|
}
|
||||||
|
if (endDate) {
|
||||||
|
query.where('date', '<=', toDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the amount range.
|
||||||
|
* @param {Query} query
|
||||||
|
* @param {number} fromAmount
|
||||||
|
* @param {number} toAmount
|
||||||
|
*/
|
||||||
|
filterAmountRange(query, fromAmount, toAmount) {
|
||||||
|
if (fromAmount) {
|
||||||
|
query.andWhere((q) => {
|
||||||
|
q.where('credit', '>=', fromAmount);
|
||||||
|
q.orWhere('debit', '>=', fromAmount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (toAmount) {
|
||||||
|
query.andWhere((q) => {
|
||||||
|
q.where('credit', '<=', toAmount);
|
||||||
|
q.orWhere('debit', '<=', toAmount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sumationCreditDebit(query) {
|
||||||
|
query.select(['accountId']);
|
||||||
|
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
query.groupBy('account_id');
|
||||||
|
},
|
||||||
|
filterContactType(query, contactType) {
|
||||||
|
query.where('contact_type', contactType);
|
||||||
|
},
|
||||||
|
filterContactIds(query, contactIds) {
|
||||||
|
query.whereIn('contact_id', contactIds);
|
||||||
|
},
|
||||||
|
openingBalance(query, fromDate) {
|
||||||
|
query.modify('filterDateRange', null, fromDate);
|
||||||
|
query.modify('sumationCreditDebit');
|
||||||
|
},
|
||||||
|
closingBalance(query, toDate) {
|
||||||
|
query.modify('filterDateRange', null, toDate);
|
||||||
|
query.modify('sumationCreditDebit');
|
||||||
|
},
|
||||||
|
contactsOpeningBalance(
|
||||||
|
query,
|
||||||
|
openingDate,
|
||||||
|
receivableAccounts,
|
||||||
|
customersIds,
|
||||||
|
) {
|
||||||
|
// Filter by date.
|
||||||
|
query.modify('filterDateRange', null, openingDate);
|
||||||
|
|
||||||
|
// Filter by customers.
|
||||||
|
query.whereNot('contactId', null);
|
||||||
|
query.whereIn('accountId', castArray(receivableAccounts));
|
||||||
|
|
||||||
|
if (!isEmpty(customersIds)) {
|
||||||
|
query.whereIn('contactId', castArray(customersIds));
|
||||||
|
}
|
||||||
|
// Group by the contact transactions.
|
||||||
|
query.groupBy('contactId');
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
query.select('contactId');
|
||||||
|
},
|
||||||
|
creditDebitSummation(query) {
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
},
|
||||||
|
groupByDateFormat(query, groupType = 'month') {
|
||||||
|
const groupBy = {
|
||||||
|
day: '%Y-%m-%d',
|
||||||
|
month: '%Y-%m',
|
||||||
|
year: '%Y',
|
||||||
|
};
|
||||||
|
const dateFormat = groupBy[groupType];
|
||||||
|
|
||||||
|
query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
|
||||||
|
query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterByBranches(query, branchesIds) {
|
||||||
|
const formattedBranchesIds = castArray(branchesIds);
|
||||||
|
|
||||||
|
query.whereIn('branchId', formattedBranchesIds);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterByProjects(query, projectsIds) {
|
||||||
|
const formattedProjectsIds = castArray(projectsIds);
|
||||||
|
|
||||||
|
query.whereIn('projectId', formattedProjectsIds);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterByReference(query, referenceId: number, referenceType: string) {
|
||||||
|
query.where('reference_id', referenceId);
|
||||||
|
query.where('reference_type', referenceType);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
const { Account } = require('./Account.model');
|
||||||
|
const { Contact } = require('../../Contacts/models/Contact');
|
||||||
|
|
||||||
|
return {
|
||||||
|
account: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Account,
|
||||||
|
join: {
|
||||||
|
from: 'accounts_transactions.accountId',
|
||||||
|
to: 'accounts.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Contact,
|
||||||
|
join: {
|
||||||
|
from: 'accounts_transactions.contactId',
|
||||||
|
to: 'contacts.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents mutate base currency since the model is not empty.
|
||||||
|
*/
|
||||||
|
static get preventMutateBaseCurrency() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
|
import { TenantRepository } from '@/common/repository/TenantRepository';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { Account } from '../models/Account.model';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import {
|
||||||
|
DiscountExpenseAccount,
|
||||||
|
OtherChargesAccount,
|
||||||
|
OtherExpensesAccount,
|
||||||
|
PrepardExpenses,
|
||||||
|
PurchaseDiscountAccount,
|
||||||
|
StripeClearingAccount,
|
||||||
|
TaxPayableAccount,
|
||||||
|
UnearnedRevenueAccount,
|
||||||
|
} from '../Accounts.constants';
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
|
export class AccountRepository extends TenantRepository {
|
||||||
|
constructor(
|
||||||
|
private readonly i18n: I18nService,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantDBKnex: Knex,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the repository's model.
|
||||||
|
*/
|
||||||
|
get model(): typeof Account {
|
||||||
|
return Account.bindKnex(this.tenantDBKnex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts dependency graph.
|
||||||
|
* @param {string} withRelation
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
public async getDependencyGraph(
|
||||||
|
withRelation?: string,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const accounts = await this.all(withRelation, trx);
|
||||||
|
|
||||||
|
return this.model.toDependencyGraph(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account by slug.
|
||||||
|
* @param {string} slug
|
||||||
|
* @return {Promise<IAccount>}
|
||||||
|
*/
|
||||||
|
public findBySlug(slug: string) {
|
||||||
|
return this.findOne({ slug });
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Changes account balance.
|
||||||
|
// * @param {number} accountId
|
||||||
|
// * @param {number} amount
|
||||||
|
// * @return {Promise<void>}
|
||||||
|
// */
|
||||||
|
// async balanceChange(accountId: number, amount: number): Promise<void> {
|
||||||
|
// const method: string = amount < 0 ? 'decrement' : 'increment';
|
||||||
|
|
||||||
|
// await this.model.query().where('id', accountId)[method]('amount', amount);
|
||||||
|
// this.flushCache();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate user by the given id.
|
||||||
|
* @param {number} userId - User id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
activateById(userId: number): Promise<number> {
|
||||||
|
return super.update({ active: 1 }, { id: userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactivate user by the given id.
|
||||||
|
* @param {number} userId - User id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
inactivateById(userId: number): Promise<number> {
|
||||||
|
return super.update({ active: 0 }, { id: userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate user by the given id.
|
||||||
|
* @param {number} userId - User id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async activateByIds(userIds: number[], trx): Promise<number> {
|
||||||
|
const results = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.whereIn('id', userIds)
|
||||||
|
.patch({ active: true });
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactivate user by the given id.
|
||||||
|
* @param {number} userId - User id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async inactivateByIds(userIds: number[], trx): Promise<number> {
|
||||||
|
const results = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.whereIn('id', userIds)
|
||||||
|
.patch({ active: false });
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} currencyCode
|
||||||
|
* @param extraAttrs
|
||||||
|
* @param trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
findOrCreateAccountReceivable = async (
|
||||||
|
currencyCode: string = '',
|
||||||
|
extraAttrs = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (currencyCode) {
|
||||||
|
query.where('currencyCode', currencyCode);
|
||||||
|
}
|
||||||
|
query.where('accountType', 'accounts-receivable');
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
name: this.i18n.t('account.accounts_receivable.currency', {
|
||||||
|
args: { currency: currencyCode },
|
||||||
|
}),
|
||||||
|
accountType: 'accounts-receivable',
|
||||||
|
currencyCode,
|
||||||
|
active: 1,
|
||||||
|
...extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find or create tax payable account.
|
||||||
|
* @param {Record<string, string>}extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async findOrCreateTaxPayable(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...TaxPayableAccount,
|
||||||
|
...extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOrCreateAccountsPayable = async (
|
||||||
|
currencyCode: string = '',
|
||||||
|
extraAttrs = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (currencyCode) {
|
||||||
|
query.where('currencyCode', currencyCode);
|
||||||
|
}
|
||||||
|
query.where('accountType', 'accounts-payable');
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
name: this.i18n.t('account.accounts_payable.currency', {
|
||||||
|
args: { currency: currencyCode },
|
||||||
|
}),
|
||||||
|
accountType: 'accounts-payable',
|
||||||
|
currencyCode,
|
||||||
|
active: 1,
|
||||||
|
...extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds or creates the unearned revenue.
|
||||||
|
* @param {Record<string, string>} extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async findOrCreateUnearnedRevenue(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...UnearnedRevenueAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds or creates the prepard expenses account.
|
||||||
|
* @param {Record<string, string>} extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async findOrCreatePrepardExpenses(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...PrepardExpenses,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds or creates the stripe clearing account.
|
||||||
|
* @param {Record<string, string>} extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async findOrCreateStripeClearing(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...StripeClearingAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds or creates the discount expense account.
|
||||||
|
* @param {Record<string, string>} extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async findOrCreateDiscountAccount(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: DiscountExpenseAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...DiscountExpenseAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOrCreatePurchaseDiscountAccount(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: PurchaseDiscountAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...PurchaseDiscountAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOrCreateOtherChargesAccount(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: OtherChargesAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...OtherChargesAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOrCreateOtherExpensesAccount(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: OtherExpensesAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...OtherExpensesAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// import { Service, Inject } from 'typedi';
|
||||||
|
// import events from '@/subscribers/events';
|
||||||
|
// import { MutateBaseCurrencyAccounts } from '../MutateBaseCurrencyAccounts';
|
||||||
|
|
||||||
|
// @Service()
|
||||||
|
// export class MutateBaseCurrencyAccountsSubscriber {
|
||||||
|
// @Inject()
|
||||||
|
// public mutateBaseCurrencyAccounts: MutateBaseCurrencyAccounts;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Attaches the events with handles.
|
||||||
|
// * @param bus
|
||||||
|
// */
|
||||||
|
// attach(bus) {
|
||||||
|
// bus.subscribe(
|
||||||
|
// events.organization.baseCurrencyUpdated,
|
||||||
|
// this.updateAccountsCurrencyOnBaseCurrencyMutated
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Updates the all accounts currency once the base currency
|
||||||
|
// * of the organization is mutated.
|
||||||
|
// */
|
||||||
|
// private updateAccountsCurrencyOnBaseCurrencyMutated = async ({
|
||||||
|
// tenantId,
|
||||||
|
// organizationDTO,
|
||||||
|
// }) => {
|
||||||
|
// await this.mutateBaseCurrencyAccounts.mutateAllAccountsCurrency(
|
||||||
|
// tenantId,
|
||||||
|
// organizationDTO.baseCurrency
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
// }
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { get } from 'lodash';
|
||||||
|
import { ACCOUNT_TYPES } from '../Accounts.constants';
|
||||||
|
|
||||||
|
export class AccountTypesUtils {
|
||||||
|
/**
|
||||||
|
* Retrieve account types list.
|
||||||
|
*/
|
||||||
|
static getList() {
|
||||||
|
return ACCOUNT_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the given root type.
|
||||||
|
* @param {string} rootType -
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
static getTypesByRootType(rootType: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account type by the given account type key.
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} accessor
|
||||||
|
*/
|
||||||
|
static getType(key: string, accessor?: string) {
|
||||||
|
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
||||||
|
|
||||||
|
if (accessor) {
|
||||||
|
return get(type, accessor);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the parent account type.
|
||||||
|
* @param {string} parentType
|
||||||
|
*/
|
||||||
|
static getTypesByParentType(parentType: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types by the given account normal.
|
||||||
|
* @param {string} normal
|
||||||
|
*/
|
||||||
|
static getTypesByNormal(normal: string) {
|
||||||
|
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the root type equals the account type.
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} rootType
|
||||||
|
*/
|
||||||
|
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
const isRootType = type.rootType === rootType;
|
||||||
|
|
||||||
|
return isType && isRootType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the parent account type equals the account type key.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
* @param {string} parentType - Account parent type.
|
||||||
|
*/
|
||||||
|
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
const isParentType = type.parentType === parentType;
|
||||||
|
|
||||||
|
return isType && isParentType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether account type has balance sheet.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static isTypeBalanceSheet(key: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
return isType && type.balanceSheet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether account type has profit/loss sheet.
|
||||||
|
* @param {string} key - Account type key.
|
||||||
|
*/
|
||||||
|
static isTypePLSheet(key: string): boolean {
|
||||||
|
return ACCOUNT_TYPES.some((type) => {
|
||||||
|
const isType = type.key === key;
|
||||||
|
return isType && type.incomeSheet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/server-nest/src/modules/App/App.controller.spec.ts
Normal file
22
packages/server-nest/src/modules/App/App.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AppController } from './App.controller';
|
||||||
|
import { AppService } from './App.service';
|
||||||
|
|
||||||
|
describe('AppController', () => {
|
||||||
|
let appController: AppController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
appController = app.get<AppController>(AppController);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('root', () => {
|
||||||
|
it('should return "Hello World!"', () => {
|
||||||
|
expect(appController.getHello()).toBe('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
12
packages/server-nest/src/modules/App/App.controller.ts
Normal file
12
packages/server-nest/src/modules/App/App.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { AppService } from './App.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AppController {
|
||||||
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getHello(): string {
|
||||||
|
return this.appService.getHello();
|
||||||
|
}
|
||||||
|
}
|
||||||
192
packages/server-nest/src/modules/App/App.module.ts
Normal file
192
packages/server-nest/src/modules/App/App.module.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { join } from 'path';
|
||||||
|
import {
|
||||||
|
AcceptLanguageResolver,
|
||||||
|
CookieResolver,
|
||||||
|
HeaderResolver,
|
||||||
|
I18nModule,
|
||||||
|
QueryResolver,
|
||||||
|
} from 'nestjs-i18n';
|
||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import { ClsModule } from 'nestjs-cls';
|
||||||
|
import { AppController } from './App.controller';
|
||||||
|
import { AppService } from './App.service';
|
||||||
|
import { ItemsModule } from '../Items/items.module';
|
||||||
|
import { config } from '../../common/config';
|
||||||
|
import { SystemDatabaseModule } from '../System/SystemDB/SystemDB.module';
|
||||||
|
import { SystemModelsModule } from '../System/SystemModels/SystemModels.module';
|
||||||
|
import { JwtStrategy } from '../Auth/Jwt.strategy';
|
||||||
|
import { jwtConstants } from '../Auth/Auth.constants';
|
||||||
|
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||||
|
import { TenancyModelsModule } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||||
|
import { LoggerMiddleware } from '@/middleware/logger.middleware';
|
||||||
|
import { ExcludeNullInterceptor } from '@/interceptors/ExcludeNull.interceptor';
|
||||||
|
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { JwtAuthGuard } from '../Auth/Jwt.guard';
|
||||||
|
import { UserIpInterceptor } from '@/interceptors/user-ip.interceptor';
|
||||||
|
import { TenancyGlobalMiddleware } from '../Tenancy/TenancyGlobal.middleware';
|
||||||
|
import { TransformerModule } from '../Transformer/Transformer.module';
|
||||||
|
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||||
|
import { ExpensesModule } from '../Expenses/Expenses.module';
|
||||||
|
import { ItemCategoryModule } from '../ItemCategories/ItemCategory.module';
|
||||||
|
import { TaxRatesModule } from '../TaxRates/TaxRate.module';
|
||||||
|
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
|
||||||
|
import { BranchesModule } from '../Branches/Branches.module';
|
||||||
|
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
||||||
|
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor';
|
||||||
|
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
|
||||||
|
import { CustomersModule } from '../Customers/Customers.module';
|
||||||
|
import { VendorsModule } from '../Vendors/Vendors.module';
|
||||||
|
import { SaleEstimatesModule } from '../SaleEstimates/SaleEstimates.module';
|
||||||
|
import { BillsModule } from '../Bills/Bills.module';
|
||||||
|
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||||
|
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
||||||
|
import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module';
|
||||||
|
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
|
||||||
|
import { VendorCreditsModule } from '../VendorCredit/VendorCredits.module';
|
||||||
|
import { VendorCreditApplyBillsModule } from '../VendorCreditsApplyBills/VendorCreditApplyBills.module';
|
||||||
|
import { VendorCreditsRefundModule } from '../VendorCreditsRefund/VendorCreditsRefund.module';
|
||||||
|
import { CreditNoteRefundsModule } from '../CreditNoteRefunds/CreditNoteRefunds.module';
|
||||||
|
import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
|
||||||
|
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
|
||||||
|
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||||
|
import { BankRulesModule } from '../BankRules/BankRules.module';
|
||||||
|
import { BankAccountsModule } from '../BankingAccounts/BankAccounts.module';
|
||||||
|
import { BankingTransactionsExcludeModule } from '../BankingTransactionsExclude/BankingTransactionsExclude.module';
|
||||||
|
import { BankingTransactionsRegonizeModule } from '../BankingTranasctionsRegonize/BankingTransactionsRegonize.module';
|
||||||
|
import { BankingMatchingModule } from '../BankingMatching/BankingMatching.module';
|
||||||
|
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||||
|
import { TransactionsLockingModule } from '../TransactionsLocking/TransactionsLocking.module';
|
||||||
|
import { SettingsModule } from '../Settings/Settings.module';
|
||||||
|
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
|
||||||
|
import { PostHogModule } from '../EventsTracker/postHog.module';
|
||||||
|
import { EventTrackerModule } from '../EventsTracker/EventTracker.module';
|
||||||
|
import { MailModule } from '../Mail/Mail.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
envFilePath: '.env',
|
||||||
|
load: config,
|
||||||
|
isGlobal: true,
|
||||||
|
}),
|
||||||
|
SystemDatabaseModule,
|
||||||
|
SystemModelsModule,
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
I18nModule.forRootAsync({
|
||||||
|
useFactory: () => ({
|
||||||
|
fallbackLanguage: 'en',
|
||||||
|
loaderOptions: {
|
||||||
|
path: join(__dirname, '/../../i18n/'),
|
||||||
|
watch: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resolvers: [
|
||||||
|
new QueryResolver(),
|
||||||
|
new HeaderResolver(),
|
||||||
|
new CookieResolver(),
|
||||||
|
AcceptLanguageResolver,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
PassportModule,
|
||||||
|
JwtModule.register({
|
||||||
|
secret: jwtConstants.secret,
|
||||||
|
signOptions: { expiresIn: '60s' },
|
||||||
|
}),
|
||||||
|
BullModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
connection: {
|
||||||
|
host: configService.get('QUEUE_HOST'),
|
||||||
|
port: configService.get('QUEUE_PORT'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
ClsModule.forRoot({
|
||||||
|
global: true,
|
||||||
|
middleware: {
|
||||||
|
mount: true,
|
||||||
|
setup: (cls, req: Request, res: Response) => {
|
||||||
|
cls.set('organizationId', req.headers['organization-id']);
|
||||||
|
cls.set('userId', 1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
TenancyDatabaseModule,
|
||||||
|
TenancyModelsModule,
|
||||||
|
ChromiumlyTenancyModule,
|
||||||
|
TransformerModule,
|
||||||
|
MailModule,
|
||||||
|
ItemsModule,
|
||||||
|
ItemCategoryModule,
|
||||||
|
AccountsModule,
|
||||||
|
ExpensesModule,
|
||||||
|
TaxRatesModule,
|
||||||
|
PdfTemplatesModule,
|
||||||
|
BranchesModule,
|
||||||
|
WarehousesModule,
|
||||||
|
CustomersModule,
|
||||||
|
VendorsModule,
|
||||||
|
SaleInvoicesModule,
|
||||||
|
SaleEstimatesModule,
|
||||||
|
SaleReceiptsModule,
|
||||||
|
BillsModule,
|
||||||
|
ManualJournalsModule,
|
||||||
|
CreditNotesModule,
|
||||||
|
VendorCreditsModule,
|
||||||
|
VendorCreditApplyBillsModule,
|
||||||
|
VendorCreditsRefundModule,
|
||||||
|
CreditNoteRefundsModule,
|
||||||
|
BillPaymentsModule,
|
||||||
|
PaymentsReceivedModule,
|
||||||
|
LedgerModule,
|
||||||
|
BankAccountsModule,
|
||||||
|
BankRulesModule,
|
||||||
|
BankingTransactionsModule,
|
||||||
|
BankingTransactionsExcludeModule,
|
||||||
|
BankingTransactionsRegonizeModule,
|
||||||
|
BankingMatchingModule,
|
||||||
|
TransactionsLockingModule,
|
||||||
|
SettingsModule,
|
||||||
|
InventoryAdjustmentsModule,
|
||||||
|
PostHogModule,
|
||||||
|
EventTrackerModule,
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: SerializeInterceptor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: JwtAuthGuard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: UserIpInterceptor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ExcludeNullInterceptor,
|
||||||
|
},
|
||||||
|
AppService,
|
||||||
|
JwtStrategy,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer
|
||||||
|
.apply(LoggerMiddleware)
|
||||||
|
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||||
|
|
||||||
|
consumer
|
||||||
|
.apply(TenancyGlobalMiddleware)
|
||||||
|
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||||
|
}
|
||||||
|
}
|
||||||
24
packages/server-nest/src/modules/App/App.service.ts
Normal file
24
packages/server-nest/src/modules/App/App.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
// configService: ConfigService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private configService: ConfigService,
|
||||||
|
private jwtService: JwtService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getHello(): string {
|
||||||
|
console.log(this.configService.get('DATABASE_PORT'));
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
const accessToken = this.jwtService.sign(payload);
|
||||||
|
|
||||||
|
console.log(accessToken);
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Transformer } from "../Transformer/Transformer";
|
||||||
|
|
||||||
|
export class AttachmentTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Exclude attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['id', 'createdAt'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includeded attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface AttachmentLinkDTO {
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
4
packages/server-nest/src/modules/Auth/Auth.constants.ts
Normal file
4
packages/server-nest/src/modules/Auth/Auth.constants.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const jwtConstants = {
|
||||||
|
secret:
|
||||||
|
'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
|
||||||
|
};
|
||||||
5
packages/server-nest/src/modules/Auth/Auth.interfaces.ts
Normal file
5
packages/server-nest/src/modules/Auth/Auth.interfaces.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface IAuthSignedInEventPayload {}
|
||||||
|
|
||||||
|
export interface IAuthSigningInEventPayload {}
|
||||||
|
|
||||||
|
export interface IAuthSignInPOJO {}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export class AuthApplication {
|
||||||
|
|
||||||
|
}
|
||||||
32
packages/server-nest/src/modules/Auth/Jwt.guard.ts
Normal file
32
packages/server-nest/src/modules/Auth/Jwt.guard.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
Scope,
|
||||||
|
SetMetadata,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
|
||||||
|
export const IS_PUBLIC_KEY = 'isPublic';
|
||||||
|
export const PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
private readonly cls: ClsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
canActivate(context: ExecutionContext) {
|
||||||
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass(),
|
||||||
|
]);
|
||||||
|
if (isPublic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.canActivate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
packages/server-nest/src/modules/Auth/Jwt.strategy.ts
Normal file
19
packages/server-nest/src/modules/Auth/Jwt.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { jwtConstants } from './Auth.constants';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: jwtConstants.secret,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: any) {
|
||||||
|
return { userId: payload.sub, username: payload.username };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||||
|
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||||
|
import { AutoIncrementOrdersService } from './AutoIncrementOrders.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TenancyDatabaseModule],
|
||||||
|
controllers: [],
|
||||||
|
providers: [AutoIncrementOrdersService],
|
||||||
|
exports: [AutoIncrementOrdersService],
|
||||||
|
})
|
||||||
|
export class AutoIncrementOrdersModule {}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto increment orders service.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AutoIncrementOrdersService {
|
||||||
|
/**
|
||||||
|
* Check if the auto increment is enabled for the given settings group.
|
||||||
|
* @param {string} settingsGroup - Settings group.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public autoIncrementEnabled = (settingsGroup: string): boolean => {
|
||||||
|
// const settings = this.tenancy.settings(tenantId);
|
||||||
|
// const group = settingsGroup;
|
||||||
|
|
||||||
|
// // Settings service transaction number and prefix.
|
||||||
|
// return settings.get({ group, key: 'auto_increment' }, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the next service transaction number.
|
||||||
|
* @param {string} settingsGroup
|
||||||
|
* @param {Function} getMaxTransactionNo
|
||||||
|
* @return {Promise<string>}
|
||||||
|
*/
|
||||||
|
getNextTransactionNumber(group: string): string {
|
||||||
|
// const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
// // Settings service transaction number and prefix.
|
||||||
|
// const autoIncrement = this.autoIncrementEnabled(tenantId, group);
|
||||||
|
|
||||||
|
// const settingNo = settings.get({ group, key: 'next_number' }, '');
|
||||||
|
// const settingPrefix = settings.get({ group, key: 'number_prefix' }, '');
|
||||||
|
|
||||||
|
// return autoIncrement ? `${settingPrefix}${settingNo}` : '';
|
||||||
|
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment setting next number.
|
||||||
|
* @param {string} orderGroup - Order group.
|
||||||
|
* @param {string} orderNumber -Order number.
|
||||||
|
*/
|
||||||
|
async incrementSettingsNextNumber(group: string) {
|
||||||
|
// const settings = this.tenancy.settings(tenantId);
|
||||||
|
// const settingNo = settings.get({ group, key: 'next_number' });
|
||||||
|
// const autoIncrement = settings.get({ group, key: 'auto_increment' });
|
||||||
|
// // Can't continue if the auto-increment of the service was disabled.
|
||||||
|
// if (!autoIncrement) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// settings.set(
|
||||||
|
// { group, key: 'next_number' },
|
||||||
|
// transactionIncrement(settingNo)
|
||||||
|
// );
|
||||||
|
// await settings.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user