mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cd54653a8 | ||
|
|
6cad929738 | ||
|
|
184648040c | ||
|
|
df9d277e66 | ||
|
|
75ec315de2 | ||
|
|
c89b2367e6 | ||
|
|
bca5b3481c | ||
|
|
59996e7a40 | ||
|
|
af5726c48c | ||
|
|
90f08c5d51 | ||
|
|
a0a9f4a768 | ||
|
|
2649f1c326 | ||
|
|
c5ff1e4d4a | ||
|
|
c74c8e896a | ||
|
|
55fdc47ff0 | ||
|
|
126eb221d0 | ||
|
|
3c7e22be43 | ||
|
|
b23112bc92 | ||
|
|
cbc60b3c73 | ||
|
|
6caa1311fd | ||
|
|
cd0bbd11c3 | ||
|
|
2a944f8507 | ||
|
|
8a2754d9ce | ||
|
|
ace75f2dfa | ||
|
|
7ceb785c1b | ||
|
|
904a52f5a1 | ||
|
|
04fe65b176 | ||
|
|
7ac6e0d349 | ||
|
|
4ec3586173 | ||
|
|
4b6ab7035e | ||
|
|
3fe7babe00 | ||
|
|
f21570982e | ||
|
|
ad8fe52b84 | ||
|
|
15ce6ac710 | ||
|
|
783387dce6 | ||
|
|
863c7ad99f | ||
|
|
776b69475c | ||
|
|
6b6027a588 | ||
|
|
d465ee15bd | ||
|
|
be2049ca6e | ||
|
|
9b63c176cd | ||
|
|
e506a7ba35 | ||
|
|
2191ad0d40 | ||
|
|
ca162206a3 | ||
|
|
c5d7a2bfd8 | ||
|
|
b9506424d1 | ||
|
|
46a145ae58 | ||
|
|
e4044ef563 | ||
|
|
1cc71eb368 | ||
|
|
323b95de7b | ||
|
|
b0658be041 | ||
|
|
b222d56148 | ||
|
|
946872204b | ||
|
|
2f9adfd908 | ||
|
|
1c8e19378f | ||
|
|
37fd4a1fdb | ||
|
|
7aed3d9c8c | ||
|
|
b125e3e58b | ||
|
|
70bba4a6ed | ||
|
|
1570995021 | ||
|
|
8109236e72 | ||
|
|
9ba651decb | ||
|
|
eb5fdbf4ee | ||
|
|
9827a84857 | ||
|
|
3308133736 | ||
|
|
3129c76c30 | ||
|
|
c0a4c965f0 | ||
|
|
d16c57b63b | ||
|
|
e04f5d26a3 | ||
|
|
ad74007d58 | ||
|
|
6271d6c268 | ||
|
|
7756b5b304 | ||
|
|
8de8695b25 | ||
|
|
11c56c75a4 | ||
|
|
f5a1d68c52 | ||
|
|
0ae7a25c27 | ||
|
|
16eaacd4bc | ||
|
|
809973730f | ||
|
|
2ebb4595a8 | ||
|
|
77f628509c | ||
|
|
d2cd32a735 | ||
|
|
4665f529e6 | ||
|
|
df706d2573 | ||
|
|
5270e99de8 | ||
|
|
eb48f66f6e | ||
|
|
5e7cff0eb7 | ||
|
|
34e781b4a2 | ||
|
|
5f40d50852 | ||
|
|
bb0d91a9cb | ||
|
|
2c790427fa | ||
|
|
4f59b27d70 | ||
|
|
94c08f0b9e | ||
|
|
ef4beaa564 | ||
|
|
2b42215381 | ||
|
|
18d6ec7b59 | ||
|
|
430cf19533 | ||
|
|
542e61dbfc | ||
|
|
8566422ce3 | ||
|
|
9517b4e279 | ||
|
|
70551bee30 | ||
|
|
d690c6a3fe | ||
|
|
28319c2cdc | ||
|
|
df0f73f338 | ||
|
|
411ac55986 | ||
|
|
12226d469a | ||
|
|
632c4629de | ||
|
|
a7df23cebc | ||
|
|
ef74e250f1 | ||
|
|
c0769662bd | ||
|
|
5b6270a184 | ||
|
|
4541d28b68 | ||
|
|
716dec799a | ||
|
|
77a1e35ff4 | ||
|
|
317adfa0de | ||
|
|
f0dfc3d1b0 | ||
|
|
67904f52af | ||
|
|
f644ed6708 | ||
|
|
dc18bde6be | ||
|
|
132c1dfdbe | ||
|
|
162b92ce84 | ||
|
|
9247745ab0 | ||
|
|
c5c0342c7b | ||
|
|
f5e9485a12 | ||
|
|
a183666df6 | ||
|
|
e6bad27771 | ||
|
|
6d24474162 | ||
|
|
5962b990c4 | ||
|
|
9f21f649f0 | ||
|
|
d3d2112b8a | ||
|
|
3795322a65 | ||
|
|
fe5cd5a8ea | ||
|
|
c032a5db16 | ||
|
|
3fcb6fefde | ||
|
|
16f5cb713d | ||
|
|
f7a7925028 | ||
|
|
66fb0c9fa3 | ||
|
|
85acc85f17 | ||
|
|
c76ce09191 | ||
|
|
e3532098b2 | ||
|
|
b6783eb22e | ||
|
|
4ef00ab122 | ||
|
|
3dd827417a | ||
|
|
cbacd02aa2 | ||
|
|
3b7e0fb78a | ||
|
|
9add716395 | ||
|
|
083ea28a1f | ||
|
|
1b51742c36 | ||
|
|
0c6f23e770 | ||
|
|
37a8ca4e97 | ||
|
|
795303c3a8 | ||
|
|
63ba3f0898 | ||
|
|
62594efa00 | ||
|
|
9ac0dcbdc3 | ||
|
|
df588dc4ce | ||
|
|
d54f14a87a | ||
|
|
a4d4be54c1 | ||
|
|
ddd17e74b5 | ||
|
|
81c0761fbe | ||
|
|
0812e3087e | ||
|
|
0dd05493b2 | ||
|
|
bfb3909d26 | ||
|
|
266902026e | ||
|
|
791c4a4e9e | ||
|
|
2cfa9123b8 | ||
|
|
bb9614aafb | ||
|
|
302564a56e | ||
|
|
dcfc231d4d | ||
|
|
64080ed678 | ||
|
|
263935d91e | ||
|
|
7f5ffb8da1 | ||
|
|
f07d25edbe | ||
|
|
7c07d6b5ff | ||
|
|
e433f4ad68 | ||
|
|
a79b9caff6 | ||
|
|
2227cead66 | ||
|
|
410c4ea3e2 | ||
|
|
f92acbcbe0 | ||
|
|
c986585cd9 | ||
|
|
250f0a30ef | ||
|
|
84b5e1adc1 | ||
|
|
2ab28370db | ||
|
|
a88a525326 | ||
|
|
fce8e2c5a4 | ||
|
|
095608266c | ||
|
|
0ec8aaa330 | ||
|
|
9fcb3ef77d | ||
|
|
dc61c57daf | ||
|
|
af284f3f6d | ||
|
|
c43123db76 | ||
|
|
ebbcab3926 | ||
|
|
a235f573c0 | ||
|
|
84a0b8f495 | ||
|
|
b87321c897 | ||
|
|
fc6ebfea5c | ||
|
|
c9fe6d9b37 | ||
|
|
161d60393a | ||
|
|
79413fa85e | ||
|
|
58552c6c94 | ||
|
|
2072e35cfa | ||
|
|
1eaac9d691 | ||
|
|
a916e8a0cb | ||
|
|
a56f560036 | ||
|
|
0fb886936c | ||
|
|
670136916f | ||
|
|
768297f137 | ||
|
|
ef505a0a62 | ||
|
|
42d40620ec | ||
|
|
ee2d8d3065 | ||
|
|
959ef7a691 | ||
|
|
60f03f534b | ||
|
|
e44ebb700a | ||
|
|
8e94c7a755 | ||
|
|
3a2ca36c07 | ||
|
|
88ece74c8a | ||
|
|
67a8610328 | ||
|
|
278d61ce61 | ||
|
|
e72d6ad6b8 | ||
|
|
bf66b31679 | ||
|
|
b4d426d2e8 | ||
|
|
7f7dd270e7 | ||
|
|
f6bad8fe30 | ||
|
|
820b363f79 | ||
|
|
07740a51ef | ||
|
|
d15fb6fe19 | ||
|
|
5749ccec81 | ||
|
|
4a99f6c0cf | ||
|
|
59f480f9d5 | ||
|
|
6cb9c919b5 | ||
|
|
1062b65b5b | ||
|
|
bf3a70cabd | ||
|
|
f46cd28f87 | ||
|
|
dffcfe50aa | ||
|
|
fac55efbc7 | ||
|
|
8b90ce5f6c | ||
|
|
705b8da053 | ||
|
|
4a05ccc692 | ||
|
|
3200d65d90 | ||
|
|
3f23038227 | ||
|
|
408c807fc2 | ||
|
|
d29079a8c5 | ||
|
|
cca596b4a9 | ||
|
|
fed620505d | ||
|
|
a008aea3f3 | ||
|
|
df8b68fda6 |
11
.env.example
11
.env.example
@@ -89,3 +89,14 @@ S3_ACCESS_KEY_ID=
|
|||||||
S3_SECRET_ACCESS_KEY=
|
S3_SECRET_ACCESS_KEY=
|
||||||
S3_ENDPOINT=
|
S3_ENDPOINT=
|
||||||
S3_BUCKET=
|
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=
|
||||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -2,7 +2,94 @@
|
|||||||
|
|
||||||
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.19.4] - 18-08-2024
|
# [0.20.0]
|
||||||
|
|
||||||
|
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
||||||
|
* feat: Onboard accounts to Stripe Connect by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/668
|
||||||
|
* feat: Upload company logo to invoice templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/670
|
||||||
|
* fix: Invoice pdf customize by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/672
|
||||||
|
* fix: Invoice customize bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/673
|
||||||
|
* feat: Clean up payment links endpoints by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/674
|
||||||
|
* feat: Hook up company logo to server-side pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/675
|
||||||
|
* feat: Company branding preferences by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/677
|
||||||
|
* feat: Pdf templates customer/company addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/678
|
||||||
|
* fix: Listen to Stripe session completed event by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/679
|
||||||
|
* feat: Track pdf templates Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/680
|
||||||
|
* fix: Branding customize content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/681
|
||||||
|
* feat: Listen to Stripe integration events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/682
|
||||||
|
* feat: Hook up customer/company address to invoice preview of payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/683
|
||||||
|
|
||||||
|
# [0.19.17]
|
||||||
|
|
||||||
|
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
|
||||||
|
|
||||||
|
# [0.19.16]
|
||||||
|
|
||||||
|
* feat: Tracking more Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/653
|
||||||
|
* fix: Expense cannot accept credit card as payment account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/654
|
||||||
|
* fix: Suspense the lazy loaded components in banking pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/657
|
||||||
|
* feat: Add help dropdown menu by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/656
|
||||||
|
* feat: Bank pages layout breaking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/658
|
||||||
|
* feat: Datatable UI improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/655
|
||||||
|
* fix: Array cast of recognize function rule ids by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/660
|
||||||
|
* fix: Payment made filling the form full amount field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/661
|
||||||
|
* feat: Tabular number of all money columns by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/659
|
||||||
|
* refactor: The expense G/L writer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/662
|
||||||
|
|
||||||
|
## [0.19.15] -
|
||||||
|
|
||||||
|
* fix: Bank transactions infinity scrolling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/648
|
||||||
|
* feat: Integrate multiple branches and warehouses to resource importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/645
|
||||||
|
* fix: Integrate multiple branches with expense resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/649
|
||||||
|
* feat: Cover more tracking events. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/650
|
||||||
|
* feat: Track banking service events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/651
|
||||||
|
|
||||||
|
## [0.19.14]
|
||||||
|
|
||||||
|
* fix: Import bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/643
|
||||||
|
* fix: Set default index to transaction entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/644
|
||||||
|
* feat(server): Events tracking using Posthog by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/646
|
||||||
|
|
||||||
|
## [0.19.13]
|
||||||
|
|
||||||
|
* fix: Subscription middleware by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/624
|
||||||
|
* fix: Getting the sheet columns in import sheet by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/641
|
||||||
|
|
||||||
|
## [0.19.12]
|
||||||
|
|
||||||
|
* fix: Typo one-click demo page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/640
|
||||||
|
|
||||||
|
## [0.19.11]
|
||||||
|
|
||||||
|
* fix: Avoid running the cost job in import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/635
|
||||||
|
* fix: Debounce scheduling calculating items cost by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/634
|
||||||
|
* fix: Expand the resources export page size limitation by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/636
|
||||||
|
* feat: Optimize loading perf. by splitting big chunks and lazy loading them by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/632
|
||||||
|
* fix: Use standard ISO 8601 format for exported data by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/638
|
||||||
|
* fix: Add customer type to customers resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/639
|
||||||
|
|
||||||
|
## [0.19.10]
|
||||||
|
|
||||||
|
* fix: Add subscription plans offer text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/629
|
||||||
|
|
||||||
|
## [0.19.9]
|
||||||
|
|
||||||
|
* fix: Make webapp package env variables dynamic by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/628
|
||||||
|
|
||||||
|
## [v0.19.8]
|
||||||
|
|
||||||
|
* fix: Cannot import items income and cost accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/617
|
||||||
|
* fix: Some bank account details hidden by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/618
|
||||||
|
* feat(ee): One-click demo account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/616
|
||||||
|
* feat: change banking service language by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/619
|
||||||
|
* feat(banking): Filter uncategorized bank transactions by date by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/590
|
||||||
|
* Fix: Syntax error caused error by @wolone in https://github.com/bigcapitalhq/bigcapital/pull/622
|
||||||
|
* fix: Listen to payment webhooks by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/623
|
||||||
|
* fix: Add prefix J-00001 to manual journals increments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/625
|
||||||
|
* fix: Disable sms service until Twilo integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/626
|
||||||
|
* fix: Style tweaks in onboarding page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/627
|
||||||
|
|
||||||
|
## [0.19.5] - 18-08-2024
|
||||||
|
|
||||||
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
||||||
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
||||||
@@ -23,6 +110,15 @@ All notable changes to Bigcapital server-side will be in this file.
|
|||||||
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
||||||
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
||||||
|
|
||||||
|
## [v0.19.4]
|
||||||
|
|
||||||
|
* feat: Import and export tax rates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/591
|
||||||
|
* feat: Un-categorize bank transactions in bulk by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/587
|
||||||
|
* feat: Pending bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/589
|
||||||
|
* fix: Update `dev` Script in `package.json` to Use `cross-env` by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/588
|
||||||
|
* fix: Should not load branches on reconcile matching form if the branches not enabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/592
|
||||||
|
* fix: Rounding the total amount the pending and matched transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/593
|
||||||
|
|
||||||
## [v0.18.0] - 10-08-2024
|
## [v0.18.0] - 10-08-2024
|
||||||
|
|
||||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"version": "independent",
|
"version": "independent",
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*",
|
||||||
|
"shared/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -4,11 +4,11 @@
|
|||||||
"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\"",
|
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
|
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
|
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"build:server": "lerna run build --scope \"@bigcapital/server\"",
|
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
|
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
@@ -29,5 +29,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tsup": "^8.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,7 @@ RUN chown node:node /
|
|||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
# Copy application dependency manifests to the container image.
|
# Copy application dependency manifests to the container image.
|
||||||
COPY ./package*.json ./
|
COPY --chown=node:node ./ ./
|
||||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
|
||||||
COPY ./lerna.json ./lerna.json
|
|
||||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
||||||
COPY ./packages/server/package*.json ./packages/server/
|
|
||||||
|
|
||||||
# Install application dependencies
|
# Install application dependencies
|
||||||
RUN apk update
|
RUN apk update
|
||||||
@@ -109,6 +105,6 @@ RUN pnpm install
|
|||||||
COPY --chown=node:node ./packages/server ./packages/server
|
COPY --chown=node:node ./packages/server ./packages/server
|
||||||
|
|
||||||
# # Creates a "dist" folder with the production build
|
# # Creates a "dist" folder with the production build
|
||||||
RUN npm run build:server --skip-nx-cache
|
RUN pnpm run build:server --skip-nx-cache
|
||||||
|
|
||||||
CMD [ "node", "./packages/server/build/index.js" ]
|
CMD [ "node", "./packages/server/build/index.js" ]
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.576.0",
|
"@aws-sdk/client-s3": "^3.576.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
||||||
|
"@bigcapital/utils": "*",
|
||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"agendash": "^3.1.0",
|
"agendash": "^3.1.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
"async": "^3.2.0",
|
"async": "^3.2.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -98,6 +100,7 @@
|
|||||||
"objection-unique": "^1.2.2",
|
"objection-unique": "^1.2.2",
|
||||||
"plaid": "^10.3.0",
|
"plaid": "^10.3.0",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
|
"posthog-node": "^4.2.0",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"puppeteer": "^10.2.0",
|
"puppeteer": "^10.2.0",
|
||||||
"qim": "0.0.52",
|
"qim": "0.0.52",
|
||||||
@@ -107,11 +110,13 @@
|
|||||||
"rtl-detect": "^1.0.4",
|
"rtl-detect": "^1.0.4",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.1",
|
||||||
|
"stripe": "^16.10.0",
|
||||||
"tmp-promise": "^3.0.3",
|
"tmp-promise": "^3.0.3",
|
||||||
"ts-transformer-keys": "^0.4.2",
|
"ts-transformer-keys": "^0.4.2",
|
||||||
"tsyringe": "^4.3.0",
|
"tsyringe": "^4.3.0",
|
||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
"uniqid": "^5.2.0",
|
"uniqid": "^5.2.0",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^0.28.1"
|
"yup": "^0.28.1"
|
||||||
|
|||||||
40
packages/server/resources/scss/base.css
Normal file
40
packages/server/resources/scss/base.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, h1, h2, h3, h4, h5, h6{
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
@@ -1,35 +1 @@
|
|||||||
@import "./normalize.scss";
|
@import "./normalize.scss";
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: inherit; // 2
|
|
||||||
text-align: -webkit-match-parent; // 3
|
|
||||||
}
|
|
||||||
|
|
||||||
thead,
|
|
||||||
tbody,
|
|
||||||
tfoot,
|
|
||||||
tr,
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border-color: inherit;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #212529;
|
|
||||||
background-color: #fff;
|
|
||||||
direction: ltr;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
|
|||||||
379
packages/server/resources/scss/normalize.css
vendored
Normal file
379
packages/server/resources/scss/normalize.css
vendored
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
|
/* Document
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the line height in all browsers.
|
||||||
|
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.15;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the margin in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the `main` element consistently in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the font size and margin on `h1` elements within `section` and
|
||||||
|
* `article` contexts in Chrome, Firefox, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in Firefox.
|
||||||
|
* 2. Show the overflow in Edge and IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
/* 1 */
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-level semantics
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the gray background on active links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the bottom border in Chrome 57-
|
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none;
|
||||||
|
/* 1 */
|
||||||
|
text-decoration: underline;
|
||||||
|
/* 2 */
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the border on images inside links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Change the font styles in all browsers.
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
line-height: 1.15;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the overflow in IE.
|
||||||
|
* 1. Show the overflow in Edge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
/* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
|
* 1. Remove the inheritance of text transform in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
/* 1 */
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner border and padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the focus styles unset by the previous rule.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button:-moz-focusring,
|
||||||
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the text wrapping in Edge and IE.
|
||||||
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||||
|
* 3. Remove the padding so developers are not caught out when they zero out
|
||||||
|
* `fieldset` elements in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
display: table;
|
||||||
|
/* 1 */
|
||||||
|
max-width: 100%;
|
||||||
|
/* 1 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
white-space: normal;
|
||||||
|
/* 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the default vertical scrollbar in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in IE 10.
|
||||||
|
* 2. Remove the padding in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
padding: 0;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
* 2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
details {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Misc
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
html(lang=locale)
|
html(lang=locale)
|
||||||
head
|
head
|
||||||
title My Site - #{title}
|
title My Site - #{title}
|
||||||
|
style
|
||||||
|
include ../scss/normalize.css
|
||||||
|
include ../scss/base.css
|
||||||
block head
|
block head
|
||||||
body
|
body
|
||||||
div.paper-template
|
div.paper-template
|
||||||
|
|||||||
@@ -1,81 +1,217 @@
|
|||||||
extends ../PaperTemplateLayout.pug
|
extends ../PaperTemplateLayout.pug
|
||||||
|
|
||||||
block head
|
block head
|
||||||
style
|
- var prefix = 'bc'
|
||||||
if (isRtl)
|
style.
|
||||||
include ../../css/modules/credit-rtl.css
|
.#{prefix}-root {
|
||||||
else
|
color: #111;
|
||||||
include ../../css/modules/credit.css
|
padding: 24px 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-big-title {
|
||||||
|
font-size: 60px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__label {
|
||||||
|
min-width: 120px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__value {
|
||||||
|
/* Styles for the term value */
|
||||||
|
}
|
||||||
|
.#{prefix}-address-section{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
-webkit-box-align: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-address-section > * {
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
.#{prefix}-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:last-of-type{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell {
|
||||||
|
border-bottom: 1px solid #F6F6F6;
|
||||||
|
padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{prefix}-totals {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-gray {
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-dark {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--font-weight-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-amount {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement__label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement__value {
|
||||||
|
/* Styles for statement value */
|
||||||
|
}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.credit
|
div(class=`${prefix}-root`)
|
||||||
div.credit__header
|
|
||||||
div.paper
|
//- Header (includes big title, details and logo)
|
||||||
h1.title #{__('credit.paper.credit_note')}
|
div(class=`${prefix}-header`)
|
||||||
if creditNote.creditNoteNumber
|
//- Header details (includes big title and details)
|
||||||
span.creditNoteNumber #{creditNote.creditNoteNumber}
|
div(class=`${prefix}-header-details`)
|
||||||
|
div(class=`${prefix}-big-title`) Credit Note
|
||||||
|
|
||||||
div.organization
|
div(class=`${prefix}-terms-list`)
|
||||||
h3.title #{organizationName}
|
if showCreditNoteNumber
|
||||||
if organizationEmail
|
div(class=`${prefix}-terms-item`)
|
||||||
span.email #{organizationEmail}
|
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
|
||||||
|
|
||||||
div.credit__full-amount
|
if showCreditNoteDate
|
||||||
div.label #{__('credit.paper.amount')}
|
div(class=`${prefix}-terms-item`)
|
||||||
div.amount #{creditNote.formattedAmount}
|
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
|
||||||
|
|
||||||
div.credit__meta
|
if showCompanyLogo && companyLogoUri
|
||||||
div.credit__meta-item.credit__meta-item--amount
|
div(class=`${prefix}-logo-wrap`)
|
||||||
span.label #{__('credit.paper.remaining')}
|
img(src=companyLogoUri alt=`Company Logo`)
|
||||||
span.value #{creditNote.formattedCreditsRemaining}
|
|
||||||
|
|
||||||
div.credit__meta-item.credit__meta-item--billed-to
|
div(class=`${prefix}-address-section`)
|
||||||
span.label #{__("credit.paper.billed_to")}
|
if showCompanyAddress
|
||||||
span.value #{creditNote.customer.displayName}
|
div(class=`${prefix}-address-from`)
|
||||||
|
div !{companyAddress}
|
||||||
|
|
||||||
div.credit__meta-item.credit__meta-item--credit-date
|
if showCustomerAddress
|
||||||
span.label #{__("credit.paper.credit_date")}
|
div(class=`${prefix}-address-to`)
|
||||||
span.value #{creditNote.formattedCreditNoteDate}
|
strong #{billedToLabel}
|
||||||
|
div !{customerAddress}
|
||||||
|
|
||||||
div.credit__table
|
table(class=`${prefix}-table`)
|
||||||
table
|
thead
|
||||||
thead
|
tr
|
||||||
tr
|
th(class=`${prefix}-table__header`) #{'Item'}
|
||||||
th.item #{__("item_entry.paper.item_name")}
|
th(class=`${prefix}-table__header`) #{'Description'}
|
||||||
th.rate #{__("item_entry.paper.rate")}
|
th(class=`${prefix}-table__header`) #{'Rate'}
|
||||||
th.quantity #{__("item_entry.paper.quantity")}
|
th(class=`${prefix}-table__header`) #{'Total'}
|
||||||
th.total #{__("item_entry.paper.total")}
|
|
||||||
tbody
|
tbody
|
||||||
each entry in creditNote.entries
|
each line in lines
|
||||||
tr
|
tr(class=`${prefix}-table__row`)
|
||||||
td.item
|
td(class=`${prefix}-table__cell`) #{line.item}
|
||||||
div.title=entry.item.name
|
td(class=`${prefix}-table__cell`) #{line.description}
|
||||||
span.description=entry.description
|
td(class=`${prefix}-table__cell--right`) #{line.rate}
|
||||||
td.rate=entry.rate
|
td(class=`${prefix}-table__cell--right`) #{line.total}
|
||||||
td.quantity=entry.quantity
|
|
||||||
td.total=entry.amount
|
|
||||||
|
|
||||||
div.credit__table-after
|
div(class=`${prefix}-totals`)
|
||||||
div.credit__table-total
|
if showSubtotal
|
||||||
table
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
|
||||||
tbody
|
div(class=`${prefix}-totals__item-label`) #{subtotallabel}
|
||||||
tr.total
|
div(class=`${prefix}-totals__item-amount`) #{subtotal}
|
||||||
td #{__('credit.paper.total')}
|
|
||||||
td #{creditNote.formattedAmount}
|
|
||||||
tr.payment-amount
|
|
||||||
td #{__('credit.paper.credits_used')}
|
|
||||||
td #{creditNote.formattedCreditsUsed}
|
|
||||||
tr.blanace-due
|
|
||||||
td #{__('credit.paper.credits_remaining')}
|
|
||||||
td #{creditNote.formattedCreditsRemaining}
|
|
||||||
|
|
||||||
div.credit__footer
|
if showTotal
|
||||||
if creditNote.termsConditions
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark`)
|
||||||
div.credit__conditions
|
div(class=`${prefix}-totals__item-amount`) #{totalLabel}:
|
||||||
h3 #{__("credit.paper.terms_conditions")}
|
div(class=`${prefix}-totals__item-label`) #{total}
|
||||||
p #{creditNote.termsConditions}
|
|
||||||
|
|
||||||
if creditNote.note
|
if showCustomerNote && customerNote
|
||||||
div.credit__notes
|
div(class=`${prefix}-statement`)
|
||||||
h3 #{__("credit.paper.notes")}
|
div(class=`${prefix}-statement__label`) #{customerNoteLabel}:
|
||||||
p #{creditNote.note}
|
div(class=`${prefix}-statement__value`) #{customerNote}
|
||||||
|
|
||||||
|
if showTermsConditions && termsConditions
|
||||||
|
div(class=`${prefix}-statement`)
|
||||||
|
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
|
||||||
|
div(class=`${prefix}-statement__value`) #{termsConditions}
|
||||||
|
|||||||
@@ -1,82 +1,227 @@
|
|||||||
extends ../PaperTemplateLayout.pug
|
extends ../PaperTemplateLayout.pug
|
||||||
|
|
||||||
block head
|
block head
|
||||||
style
|
- var prefix = 'bc'
|
||||||
if (isRtl)
|
style.
|
||||||
include ../../css/modules/estimate-rtl.css
|
.#{prefix}-root {
|
||||||
else
|
color: #111;
|
||||||
include ../../css/modules/estimate.css
|
padding: 24px 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-big-title {
|
||||||
|
font-size: 60px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__label {
|
||||||
|
min-width: 120px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__value {
|
||||||
|
}
|
||||||
|
.#{prefix}-addresses{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-addresses > * {
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
.#{prefix}-address {
|
||||||
|
}
|
||||||
|
.#{prefix}-address__item {
|
||||||
|
}
|
||||||
|
.#{prefix}-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:last-of-type{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell {
|
||||||
|
border-bottom: 1px solid #F6F6F6;
|
||||||
|
padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell--right{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-gray {
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-dark {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--font-weight-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-amount {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement__label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement__value {
|
||||||
|
}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.estimate
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
div.estimate__header
|
|
||||||
div.paper
|
|
||||||
h1.title #{__("estimate.paper.estimate")}
|
|
||||||
span.email #{saleEstimate.estimateNumber}
|
|
||||||
|
|
||||||
div.organization
|
//- Header (invluces big title, details and logo)
|
||||||
h3.title #{organizationName}
|
div(class=`${prefix}-header`)
|
||||||
if organizationEmail
|
|
||||||
span.email #{organizationEmail}
|
|
||||||
|
|
||||||
div.estimate__estimate-amount
|
//- Header details (includes big title and details )
|
||||||
div.label #{__('estimate.paper.estimate_amount')}
|
div(class=`${prefix}-header-details`)
|
||||||
div.amount #{saleEstimate.formattedAmount}
|
h1(class=`${prefix}-big-title`) Estimate
|
||||||
|
|
||||||
div.estimate__meta
|
//- Terms List
|
||||||
if saleEstimate.estimateNumber
|
div(class=`${prefix}-terms`)
|
||||||
div.estimate__meta-item.estimate__meta-item--estimate-number
|
if showEstimateNumber
|
||||||
span.label #{__("estimate.paper.estimate_number")}
|
div(class=`${prefix}-terms-item`)
|
||||||
span.value #{saleEstimate.estimateNumber}
|
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
||||||
|
|
||||||
div.estimate__meta-item.estimate__meta-item--billed-to
|
if showEstimateDate
|
||||||
span.label #{__("estimate.paper.billed_to")}
|
div(class=`${prefix}-terms-item`)
|
||||||
span.value #{saleEstimate.customer.displayName}
|
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
||||||
|
|
||||||
div.estimate__meta-item.estimate__meta-item--estimate-date
|
if showExpirationDate
|
||||||
span.label #{__("estimate.paper.estimate_date")}
|
div(class=`${prefix}-terms-item`)
|
||||||
span.value #{saleEstimate.formattedEstimateDate}
|
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{expirationDate}
|
||||||
|
|
||||||
div.estimate__meta-item.estimate__meta-item--due-date
|
//- Company logo
|
||||||
span.label #{__("estimate.paper.expiration_date")}
|
if showCompanyLogo && companyLogoUri
|
||||||
span.value #{saleEstimate.formattedExpirationDate}
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(alt="Company logo", src=companyLogoUri)
|
||||||
|
|
||||||
div.estimate__table
|
//- Addresses (Group section)
|
||||||
table
|
div(class=`${prefix}-addresses`)
|
||||||
thead
|
if showCompanyAddress
|
||||||
tr
|
div(class=`${prefix}-address-from`)
|
||||||
th.item #{__("item_entry.paper.item_name")}
|
div !{companyAddress}
|
||||||
th.rate #{__("item_entry.paper.rate")}
|
|
||||||
th.quantity #{__("item_entry.paper.quantity")}
|
if showCustomerAddress
|
||||||
th.total #{__("item_entry.paper.total")}
|
div(class=`${prefix}-address-to`)
|
||||||
|
strong #{billedToLabel}
|
||||||
|
div !{customerAddress}
|
||||||
|
|
||||||
|
//- Table section (Line items)
|
||||||
|
table(class=`${prefix}-table`)
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th(class=`${prefix}-table__header`) Item
|
||||||
|
th(class=`${prefix}-table__header`) Description
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
|
||||||
tbody
|
tbody
|
||||||
each entry in saleEstimate.entries
|
each line in lines
|
||||||
tr
|
tr
|
||||||
td.item
|
td(class=`${prefix}-table__cell`) #{line.item}
|
||||||
div.title=entry.item.name
|
td(class=`${prefix}-table__cell`) #{line.description}
|
||||||
span.description=entry.description
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
|
||||||
td.rate=entry.rate
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
||||||
td.quantity=entry.quantity
|
|
||||||
td.total=entry.amount
|
|
||||||
|
|
||||||
div.estimate__table-after
|
//- Totals section
|
||||||
div.estimate__table-total
|
div(class=`${prefix}-totals`)
|
||||||
table
|
if showSubtotal
|
||||||
tbody
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
|
||||||
tr.subtotal
|
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
|
||||||
td #{__('estimate.paper.subtotal')}
|
div(class=`${prefix}-totals__item-amount`) #{subtotal}
|
||||||
td #{saleEstimate.formattedAmount}
|
if showTotal
|
||||||
tr.total
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
|
||||||
td #{__('estimate.paper.total')}
|
div(class=`${prefix}-totals__item-label`) #{totalLabel}
|
||||||
td #{saleEstimate.formattedAmount}
|
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||||
|
|
||||||
div.estimate__footer
|
//- Statements section
|
||||||
if saleEstimate.termsConditions
|
if showCustomerNote && customerNote
|
||||||
div.estimate__conditions
|
div(class=`${prefix}-statement`)
|
||||||
h3 #{__("estimate.paper.conditions_title")}
|
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
|
||||||
p #{saleEstimate.termsConditions}
|
div(class=`${prefix}-statement__value`) #{customerNote}
|
||||||
|
|
||||||
if saleEstimate.note
|
if showTermsConditions && termsConditions
|
||||||
div.estimate__notes
|
div(class=`${prefix}-statement`)
|
||||||
h3 #{__("estimate.paper.notes_title")}
|
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
|
||||||
p #{saleEstimate.note}
|
div(class=`${prefix}-statement__value`) #{termsConditions}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
extends ../PaperTemplateLayout.pug
|
|
||||||
|
|
||||||
block head
|
|
||||||
style
|
|
||||||
if (isRtl)
|
|
||||||
include ../../css/modules/invoice-rtl.css
|
|
||||||
else
|
|
||||||
include ../../css/modules/invoice.css
|
|
||||||
|
|
||||||
block content
|
|
||||||
div.invoice
|
|
||||||
div.invoice__header
|
|
||||||
div.paper
|
|
||||||
h1.title #{__("invoice.paper.invoice")}
|
|
||||||
if saleInvoice.invoiceNo
|
|
||||||
span.invoiceNo #{saleInvoice.invoiceNo}
|
|
||||||
|
|
||||||
div.organization
|
|
||||||
h3.title #{organizationName}
|
|
||||||
if organizationEmail
|
|
||||||
span.email #{organizationEmail}
|
|
||||||
|
|
||||||
div.invoice__due-amount
|
|
||||||
div.label #{__('invoice.paper.invoice_amount')}
|
|
||||||
div.amount #{saleInvoice.totalFormatted}
|
|
||||||
|
|
||||||
div.invoice__meta
|
|
||||||
div.invoice__meta-item.invoice__meta-item--amount
|
|
||||||
span.label #{__('invoice.paper.due_amount')}
|
|
||||||
span.value #{saleInvoice.dueAmountFormatted}
|
|
||||||
|
|
||||||
div.invoice__meta-item.invoice__meta-item--billed-to
|
|
||||||
span.label #{__("invoice.paper.billed_to")}
|
|
||||||
span.value #{saleInvoice.customer.displayName}
|
|
||||||
|
|
||||||
div.invoice__meta-item.invoice__meta-item--invoice-date
|
|
||||||
span.label #{__("invoice.paper.invoice_date")}
|
|
||||||
span.value #{saleInvoice.invoiceDateFormatted}
|
|
||||||
|
|
||||||
div.invoice__meta-item.invoice__meta-item--due-date
|
|
||||||
span.label #{__("invoice.paper.due_date")}
|
|
||||||
span.value #{saleInvoice.dueDateFormatted}
|
|
||||||
|
|
||||||
div.invoice__table
|
|
||||||
table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
th.item #{__("item_entry.paper.item_name")}
|
|
||||||
th.rate #{__("item_entry.paper.rate")}
|
|
||||||
th.quantity #{__("item_entry.paper.quantity")}
|
|
||||||
th.total #{__("item_entry.paper.total")}
|
|
||||||
tbody
|
|
||||||
each entry in saleInvoice.entries
|
|
||||||
tr
|
|
||||||
td.item
|
|
||||||
div.title=entry.item.name
|
|
||||||
span.description=entry.description
|
|
||||||
td.rate=entry.rate
|
|
||||||
td.quantity=entry.quantity
|
|
||||||
td.total=entry.amount
|
|
||||||
|
|
||||||
div.invoice__table-after
|
|
||||||
div.invoice__table-total
|
|
||||||
table
|
|
||||||
tbody
|
|
||||||
tr.subtotal
|
|
||||||
td #{__('invoice.paper.subtotal')}
|
|
||||||
td #{saleInvoice.subtotalFormatted}
|
|
||||||
each tax in saleInvoice.taxes
|
|
||||||
tr.tax_line
|
|
||||||
td #{tax.name} [#{tax.taxRate}%]
|
|
||||||
td #{tax.taxRateAmountFormatted}
|
|
||||||
tr.total
|
|
||||||
td #{__('invoice.paper.total')}
|
|
||||||
td #{saleInvoice.totalFormatted}
|
|
||||||
tr.payment-amount
|
|
||||||
td #{__('invoice.paper.payment_amount')}
|
|
||||||
td #{saleInvoice.paymentAmountFormatted}
|
|
||||||
tr.blanace-due
|
|
||||||
td #{__('invoice.paper.balance_due')}
|
|
||||||
td #{saleInvoice.dueAmountFormatted}
|
|
||||||
|
|
||||||
div.invoice__footer
|
|
||||||
if saleInvoice.termsConditions
|
|
||||||
div.invoice__conditions
|
|
||||||
h3 #{__("invoice.paper.conditions_title")}
|
|
||||||
p #{saleInvoice.termsConditions}
|
|
||||||
|
|
||||||
if saleInvoice.invoiceMessage
|
|
||||||
div.invoice__notes
|
|
||||||
h3 #{__("invoice.paper.notes_title")}
|
|
||||||
p #{saleInvoice.invoiceMessage}
|
|
||||||
258
packages/server/resources/views/modules/invoice-standard.pug
Normal file
258
packages/server/resources/views/modules/invoice-standard.pug
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
extends ../PaperTemplateLayout.pug
|
||||||
|
|
||||||
|
block head
|
||||||
|
- var prefix = 'bc'
|
||||||
|
style.
|
||||||
|
.#{prefix}-root {
|
||||||
|
color: #111;
|
||||||
|
padding: 24px 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-big-title {
|
||||||
|
font-size: 60px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.#{prefix}-detail__label {
|
||||||
|
min-width: 120px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-detail__value {
|
||||||
|
/* Styles for detail values */
|
||||||
|
}
|
||||||
|
.#{prefix}-address-root {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-address-from {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.#{prefix}-address-from__item {
|
||||||
|
/* Styles for items in the billed-from address */
|
||||||
|
}
|
||||||
|
.#{prefix}-address-to {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.#{prefix}-address-to__item {
|
||||||
|
/* Styles for items in the billed-to address */
|
||||||
|
}
|
||||||
|
.#{prefix}-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:last-of-type{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell {
|
||||||
|
border-bottom: 1px solid #F6F6F6;
|
||||||
|
padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-gray {
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--border-dark {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--font-weight-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-amount {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-paragraph {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.#{prefix}-paragraph__label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.#{prefix}-paragraph__value {
|
||||||
|
/* Styles for values within the paragraph section */
|
||||||
|
}
|
||||||
|
block content
|
||||||
|
//- block head
|
||||||
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
|
|
||||||
|
//- Header (includes big title, details and logo )
|
||||||
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
//- Title and company logo
|
||||||
|
h1(class=`${prefix}-big-title`) Invoice
|
||||||
|
|
||||||
|
//- Invoice details
|
||||||
|
div(class=`${prefix}-details`)
|
||||||
|
if showInvoiceNumber
|
||||||
|
div(class=`${prefix}-detail`)
|
||||||
|
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
|
||||||
|
div(class=`${prefix}-detail__value`) #{invoiceNumber}
|
||||||
|
|
||||||
|
if showDateIssue
|
||||||
|
div(class=`${prefix}-detail`)
|
||||||
|
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
|
||||||
|
div(class=`${prefix}-detail__value`) #{dateIssue}
|
||||||
|
|
||||||
|
if showDueDate
|
||||||
|
div(class=`${prefix}-detail`)
|
||||||
|
div(class=`${prefix}-detail__label`) #{dueDateLabel}
|
||||||
|
div(class=`${prefix}-detail__value`) #{dueDate}
|
||||||
|
|
||||||
|
//- Company logo
|
||||||
|
if showCompanyLogo && companyLogoUri
|
||||||
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(alt="Company logo", src=companyLogoUri)
|
||||||
|
|
||||||
|
//- Address section
|
||||||
|
div(class=`${prefix}-address-root`)
|
||||||
|
if showCompanyAddress
|
||||||
|
div(class=`${prefix}-address-from`)
|
||||||
|
div !{companyAddress}
|
||||||
|
|
||||||
|
if showCustomerAddress
|
||||||
|
div(class=`${prefix}-address-to`)
|
||||||
|
strong #{billedToLabel}
|
||||||
|
div !{customerAddress}
|
||||||
|
|
||||||
|
//- Invoice table
|
||||||
|
table(class=`${prefix}-table`)
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th(class=`${prefix}-table__header`) #{lineItemLabel}
|
||||||
|
th(class=`${prefix}-table__header`) #{lineDescriptionLabel}
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineRateLabel}
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineTotalLabel}
|
||||||
|
tbody
|
||||||
|
each line in lines
|
||||||
|
tr
|
||||||
|
td(class=`${prefix}-table__cell`) #{line.item}
|
||||||
|
td(class=`${prefix}-table__cell`) #{line.description}
|
||||||
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
|
||||||
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
||||||
|
|
||||||
|
//- Totals section
|
||||||
|
div(class=`${prefix}-totals`)
|
||||||
|
if showSubtotal
|
||||||
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{subtotal}
|
||||||
|
|
||||||
|
if showDiscount
|
||||||
|
div(class=`${prefix}-totals__item`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{discountLabel}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{discount}
|
||||||
|
|
||||||
|
if showTaxes
|
||||||
|
each tax in taxes
|
||||||
|
div(class=`${prefix}-totals__item`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{tax.label}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{tax.amount}
|
||||||
|
|
||||||
|
if showTotal
|
||||||
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{totalLabel}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||||
|
|
||||||
|
if showPaymentMade
|
||||||
|
div(class=`${prefix}-totals__item`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{paymentMadeLabel}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{paymentMade}
|
||||||
|
|
||||||
|
if showBalanceDue
|
||||||
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
|
||||||
|
div(class=`${prefix}-totals__item-label`) #{balanceDueLabel}
|
||||||
|
div(class=`${prefix}-totals__item-amount`) #{balanceDue}
|
||||||
|
|
||||||
|
//- Footer section
|
||||||
|
if showTermsConditions && termsConditions
|
||||||
|
div(class=`${prefix}-paragraph`)
|
||||||
|
if termsConditionsLabel
|
||||||
|
div(class=`${prefix}-paragraph__label`) #{termsConditionsLabel}
|
||||||
|
div(class=`${prefix}-paragraph__value`) #{termsConditions}
|
||||||
|
|
||||||
|
if showStatement && statement
|
||||||
|
div(class=`${prefix}-paragraph`)
|
||||||
|
if statementLabel
|
||||||
|
div(class=`${prefix}-paragraph__label`) #{statementLabel}
|
||||||
|
div(class=`${prefix}-paragraph__value`) #{statement}
|
||||||
@@ -1,67 +1,192 @@
|
|||||||
extends ../PaperTemplateLayout.pug
|
extends ../PaperTemplateLayout.pug
|
||||||
|
|
||||||
block head
|
block head
|
||||||
style
|
- var prefix = 'bp3';
|
||||||
if (isRtl)
|
|
||||||
include ../../css/modules/payment-rtl.css
|
style.
|
||||||
else
|
.#{prefix}-root{
|
||||||
include ../../css/modules/payment.css
|
color: #111;
|
||||||
|
padding: 24px 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-big-title{
|
||||||
|
font-size: 60px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-list{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__label{
|
||||||
|
min-width: 120px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-addresses{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-addresses > * {
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
.#{prefix}-address__label{
|
||||||
|
|
||||||
|
}
|
||||||
|
.#{prefix}-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:last-of-type{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell {
|
||||||
|
border-bottom: 1px solid #F6F6F6;
|
||||||
|
padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--gray-border {
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--dark-border {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item--bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__item-amount {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
block content
|
block content
|
||||||
div.payment
|
div(class=`${prefix}-root`)
|
||||||
div.payment__header
|
//- Header (includes big title, details and logo )
|
||||||
div.paper
|
div(class=`${prefix}-header`)
|
||||||
h1.title #{__("payment.paper.payment_receipt")}
|
//- Header details (includes big title and details )
|
||||||
if paymentReceive.paymentReceiveNo
|
div(class=`${prefix}-header-details`)
|
||||||
span.paymentNumber #{paymentReceive.paymentReceiveNo}
|
div(class=`${prefix}-big-title`) Payment
|
||||||
|
div(class=`${prefix}-terms-list`)
|
||||||
|
if showPaymentReceivedNumber
|
||||||
|
div(class=`${prefix}-terms-item`)
|
||||||
|
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
|
||||||
|
|
||||||
div.organization
|
if showPaymentReceivedDate
|
||||||
h3.title #{organizationName}
|
div(class=`${prefix}-terms-item`)
|
||||||
if organizationEmail
|
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
||||||
span.email #{organizationEmail}
|
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
||||||
|
|
||||||
div.payment__received-amount
|
if showCompanyLogo && companyLogoUri
|
||||||
div.label #{__('payment.paper.amount_received')}
|
div(class=`${prefix}-logo-wrap`)
|
||||||
div.amount #{paymentReceive.formattedAmount}
|
img(src=companyLogoUri alt="Company Logo")
|
||||||
|
|
||||||
|
div(class=`${prefix}-addresses`)
|
||||||
|
if showCompanyAddress
|
||||||
|
div(class=`${prefix}-address-from`)
|
||||||
|
div !{companyAddress}
|
||||||
|
|
||||||
div.payment__meta
|
if showCustomerAddress
|
||||||
div.payment__meta-item.payment__meta-item--billed-to
|
div(class=`${prefix}-address-to`)
|
||||||
span.label #{__("payment.paper.billed_to")}
|
strong #{billedToLabel}
|
||||||
span.value #{paymentReceive.customer.displayName}
|
div !{customerAddress}
|
||||||
|
|
||||||
div.payment__meta-item.payment__meta-item--payment-date
|
table(class=`${prefix}-table`)
|
||||||
span.label #{__("payment.paper.payment_date")}
|
thead
|
||||||
span.value #{paymentReceive.formattedPaymentDate}
|
tr
|
||||||
|
th(class=`${prefix}-table__header`) Invoice #
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Invoice Amount
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Paid Amount
|
||||||
|
|
||||||
div.payment__table
|
tbody
|
||||||
table
|
each line in lines
|
||||||
thead
|
tr
|
||||||
tr
|
td(class=`${prefix}-table__cell`) #{line.invoiceNumber}
|
||||||
th.item #{__("payment.paper.invoice_number")}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.invoiceAmount}
|
||||||
th.date #{__("payment.paper.invoice_date")}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.paidAmount}
|
||||||
th.invoiceAmount #{__("payment.paper.invoice_amount")}
|
|
||||||
th.paymentAmount #{__("payment.paper.payment_amount")}
|
|
||||||
tbody
|
|
||||||
each entry in paymentReceive.entries
|
|
||||||
tr
|
|
||||||
td.item=entry.invoice.invoiceNo
|
|
||||||
td.date=entry.invoice.invoiceDateFormatted
|
|
||||||
td.invoiceAmount=entry.invoice.totalFormatted
|
|
||||||
td.paymentAmount=entry.invoice.paymentAmountFormatted
|
|
||||||
|
|
||||||
div.payment__table-after
|
div(class=`${prefix}-totals`)
|
||||||
div.payment__table-total
|
if showSubtotal
|
||||||
table
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--gray-border`)
|
||||||
tbody
|
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
|
||||||
tr.payment-amount
|
div(class=`${prefix}-totals__item-amount`) #{subtotal}
|
||||||
td #{__('payment.paper.payment_amount')}
|
|
||||||
td #{paymentReceive.formattedAmount}
|
|
||||||
tr.blanace-due
|
|
||||||
td #{__('payment.paper.balance_due')}
|
|
||||||
td #{paymentReceive.customer.closingBalance}
|
|
||||||
|
|
||||||
div.payment__footer
|
if showTotal
|
||||||
if paymentReceive.statement
|
div(class=`${prefix}-totals__item ${prefix}-totals__item--dark-border`)
|
||||||
div.payment__notes
|
div(class=`${prefix}-totals__item-label`) #{totalLabel}
|
||||||
h3 #{__("payment.paper.statement")}
|
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||||
p #{paymentReceive.statement}
|
|
||||||
|
|||||||
@@ -1,77 +1,215 @@
|
|||||||
extends ../PaperTemplateLayout.pug
|
extends ../PaperTemplateLayout.pug
|
||||||
|
|
||||||
block head
|
block head
|
||||||
style
|
- var prefix = 'bc'
|
||||||
if (isRtl)
|
style.
|
||||||
include ../../css/modules/receipt-rtl.css
|
.#{prefix}-root {
|
||||||
else
|
color: #000;
|
||||||
include ../../css/modules/receipt.css
|
padding: 24px 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-big-title {
|
||||||
|
font-size: 60px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__label {
|
||||||
|
min-width: 120px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-terms-item__value {}
|
||||||
|
.#{prefix}-address-section {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
-webkit-box-align: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-address-section > * {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.#{prefix}-address {}
|
||||||
|
.#{prefix}-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header:last-of-type{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__header--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell {
|
||||||
|
border-bottom: 1px solid #F6F6F6;
|
||||||
|
padding: 12px 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:first-of-type{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-table__cell--right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__line {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__line--gray-border {
|
||||||
|
border-bottom: 1px solid #DADADA;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__line--dark-border {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__line__label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.#{prefix}-totals__line__amount {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.#{prefix}-statement__label {}
|
||||||
|
.#{prefix}-statement__value {}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div.receipt
|
//- block head
|
||||||
div.receipt__header
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
div.paper
|
|
||||||
h1.title #{__("receipt.paper.receipt")}
|
|
||||||
span.receiptNumber #{saleReceipt.receiptNumber}
|
|
||||||
|
|
||||||
div.organization
|
//- Header (includes big title, details and logo )
|
||||||
h3.title #{organizationName}
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
//- Title and company logo
|
||||||
|
h1(class=`${prefix}-big-title`) Receipt
|
||||||
|
|
||||||
div.receipt__receipt-amount
|
//- Terms List
|
||||||
div.label #{__('receipt.paper.receipt_amount')}
|
div(class=`${prefix}-terms-list`)
|
||||||
div.amount #{saleReceipt.formattedAmount}
|
if showReceiptNumber
|
||||||
|
div(class=`${prefix}-terms-item`)
|
||||||
|
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
|
||||||
|
span(class=`${prefix}-terms-item__value`)= receiptNumber
|
||||||
|
|
||||||
div.receipt__meta
|
if showReceiptDate
|
||||||
div.receipt__meta-item.receipt__meta-item--billed-to
|
div(class=`${prefix}-terms-item`)
|
||||||
span.label #{__("receipt.paper.billed_to")}
|
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
||||||
span.value #{saleReceipt.customer.displayName}
|
span(class=`${prefix}-terms-item__value`)= receiptDate
|
||||||
|
|
||||||
div.receipt__meta-item.receipt__meta-item--invoice-date
|
//- Company logo
|
||||||
span.label #{__("receipt.paper.receipt_date")}
|
if showCompanyLogo && companyLogoUri
|
||||||
span.value #{saleReceipt.formattedReceiptDate}
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(src=companyLogoUri alt=`Company Logo`)
|
||||||
|
|
||||||
if saleReceipt.receiptNumber
|
//- Address Section
|
||||||
div.receipt__meta-item.receipt__meta-item--invoice-number
|
div(class=`${prefix}-address-section`)
|
||||||
span.label #{__("receipt.paper.receipt_number")}
|
if showCompanyAddress
|
||||||
span.value #{saleReceipt.receiptNumber}
|
div(class=`${prefix}-address-from`)
|
||||||
|
div !{companyAddress}
|
||||||
|
|
||||||
div.receipt__table
|
if showCustomerAddress
|
||||||
table
|
div(class=`${prefix}-address-to`)
|
||||||
thead
|
strong #{billedToLabel}
|
||||||
tr
|
div !{customerAddress}
|
||||||
th.item #{__("item_entry.paper.item_name")}
|
|
||||||
th.rate #{__("item_entry.paper.rate")}
|
|
||||||
th.quantity #{__("item_entry.paper.quantity")}
|
|
||||||
th.total #{__("item_entry.paper.total")}
|
|
||||||
tbody
|
|
||||||
each entry in saleReceipt.entries
|
|
||||||
tr
|
|
||||||
td.item=entry.item.name
|
|
||||||
td.rate=entry.rate
|
|
||||||
td.quantity=entry.quantity
|
|
||||||
td.total=entry.amount
|
|
||||||
|
|
||||||
div.receipt__table-after
|
|
||||||
div.receipt__table-total
|
|
||||||
table
|
|
||||||
tbody
|
|
||||||
tr.total
|
|
||||||
td #{__('receipt.paper.total')}
|
|
||||||
td #{saleReceipt.formattedAmount}
|
|
||||||
tr.payment-amount
|
|
||||||
td #{__('receipt.paper.payment_amount')}
|
|
||||||
td #{saleReceipt.formattedAmount}
|
|
||||||
tr.blanace-due
|
|
||||||
td #{__('receipt.paper.balance_due')}
|
|
||||||
td #{'$0'}
|
|
||||||
|
|
||||||
div.receipt__footer
|
//- Table Section
|
||||||
if saleReceipt.statement
|
table(class=`${prefix}-table`)
|
||||||
div.receipt__conditions
|
thead(class=`${prefix}-table__header`)
|
||||||
h3 #{__("receipt.paper.statement")}
|
tr
|
||||||
p #{saleReceipt.statement}
|
th(class=`${prefix}-table__header`) Item
|
||||||
|
th(class=`${prefix}-table__header`) Description
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
|
||||||
|
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
|
||||||
|
tbody
|
||||||
|
each line in lines
|
||||||
|
tr(class=`${prefix}-table__row`)
|
||||||
|
td(class=`${prefix}-table__cell`)= line.item
|
||||||
|
td(class=`${prefix}-table__cell`)= line.description
|
||||||
|
td(class=`${prefix}-table__cell${prefix}-table__cell--right`)= line.rate
|
||||||
|
td(class=`${prefix}-table__cell${prefix}-table__cell--right`)= line.total
|
||||||
|
|
||||||
if saleReceipt.receiptMessage
|
//- Totals Section
|
||||||
div.receipt__notes
|
div(class=`${prefix}-totals`)
|
||||||
h3 #{__("receipt.paper.notes")}
|
if showSubtotal
|
||||||
p #{saleReceipt.receiptMessage}
|
div(class=`${prefix}-totals__line ${prefix}-totals__line--gray-border`)
|
||||||
|
span(class=`${prefix}-totals__line__label`)= subtotalLabel
|
||||||
|
span(class=`${prefix}-totals__line__amount`)= subtotal
|
||||||
|
|
||||||
|
if showTotal
|
||||||
|
div(class=`${prefix}-totals__line ${prefix}-totals__line--dark-border`)
|
||||||
|
span(class=`${prefix}-totals__line__label`)= totalLabel
|
||||||
|
span(class=`${prefix}-totals__line__amount`)= total
|
||||||
|
|
||||||
|
//- Customer Note Section
|
||||||
|
if showCustomerNote && customerNote
|
||||||
|
div(class=`${prefix}-statement`)
|
||||||
|
div(class=`${prefix}-statement__label`)= customerNoteLabel
|
||||||
|
div(class=`${prefix}-statement__value`)= customerNote
|
||||||
|
|
||||||
|
//- Terms & Conditions Section
|
||||||
|
if showTermsConditions && termsConditions
|
||||||
|
div(class=`${prefix}-statement`)
|
||||||
|
div(class=`${prefix}-statement__label`)= termsConditionsLabel
|
||||||
|
div(class=`${prefix}-statement__value`)= termsConditions
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getExcludedBankTransactions.bind(this)
|
this.getExcludedBankTransactions.bind(this)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export class RecognizedTransactionsController extends BaseController {
|
|||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getRecognizedTransactions.bind(this)
|
this.getRecognizedTransactions.bind(this)
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
param('id').exists().isNumeric().toInt(),
|
param('id').exists().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
||||||
|
import config from '@/config';
|
||||||
|
@Service()
|
||||||
|
export class OneClickDemoController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private oneClickDemoApp: OneClickDemoApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor method.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
// Protects the endpoints if the feature is not enabled.
|
||||||
|
const protectMiddleware = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
// Add your protection logic here
|
||||||
|
if (config.oneClickDemoAccounts) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.status(403).send({ message: 'Forbidden' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
router.post(
|
||||||
|
'/one_click',
|
||||||
|
protectMiddleware,
|
||||||
|
asyncMiddleware(this.oneClickDemo.bind(this))
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/one_click_signin',
|
||||||
|
[body('demo_id').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
protectMiddleware,
|
||||||
|
asyncMiddleware(this.oneClickSignIn.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-click demo application.
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
* @param {NextFunction} next -
|
||||||
|
*/
|
||||||
|
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.oneClickDemoApp.createOneClick();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
data,
|
||||||
|
message: 'The one-click demo has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign-in to one-click demo account.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private async oneClickSignIn(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { demoId } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import BaseController from '@/api/controllers/BaseController';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class OrganizationController extends BaseController {
|
export default class OrganizationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
organizationService: OrganizationService;
|
private organizationService: OrganizationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -56,10 +56,10 @@ export default class OrganizationController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization setup schema.
|
* Build organization validation schema.
|
||||||
* @return {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
private get commonOrganizationValidationSchema(): ValidationChain[] {
|
private get buildOrganizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('name').exists().trim(),
|
check('name').exists().trim(),
|
||||||
check('industry').optional({ nullable: true }).isString().trim(),
|
check('industry').optional({ nullable: true }).isString().trim(),
|
||||||
@@ -72,21 +72,34 @@ export default class OrganizationController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build organization validation schema.
|
|
||||||
* @returns {ValidationChain[]}
|
|
||||||
*/
|
|
||||||
private get buildOrganizationValidationSchema(): ValidationChain[] {
|
|
||||||
return [...this.commonOrganizationValidationSchema];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update organization validation schema.
|
* Update organization validation schema.
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
...this.commonOrganizationValidationSchema,
|
// # Profile
|
||||||
|
check('name').optional().trim(),
|
||||||
|
check('industry').optional({ nullable: true }).isString().trim(),
|
||||||
|
check('location').optional().isString().isISO31661Alpha2(),
|
||||||
|
check('base_currency').optional().isISO4217(),
|
||||||
|
check('timezone').optional().isIn(moment.tz.names()),
|
||||||
|
check('fiscal_year').optional().isIn(MONTHS),
|
||||||
|
check('language').optional().isString().isIn(ACCEPTED_LOCALES),
|
||||||
|
check('date_format').optional().isIn(DATE_FORMATS),
|
||||||
|
|
||||||
|
// # Address
|
||||||
|
check('address.address_1').optional().isString().trim(),
|
||||||
|
check('address.address_2').optional().isString().trim(),
|
||||||
|
check('address.postal_code').optional().isString().trim(),
|
||||||
|
check('address.city').optional().isString().trim(),
|
||||||
|
check('address.state_province').optional().isString().trim(),
|
||||||
|
check('address.phone').optional().isString().trim(),
|
||||||
|
|
||||||
|
// # Branding
|
||||||
|
check('primary_color').optional({ nullable: true }).isHexColor().trim(),
|
||||||
|
check('logo_key').optional({ nullable: true }).isString().trim(),
|
||||||
|
|
||||||
check('tax_number').optional({ nullable: true }).isString().trim(),
|
check('tax_number').optional({ nullable: true }).isString().trim(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -156,7 +169,7 @@ export default class OrganizationController extends BaseController {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const tenantDTO = this.matchedBodyData(req);
|
const tenantDTO = this.matchedBodyData(req, { includeOptionals: false });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.organizationService.updateOrganization(tenantId, tenantDTO);
|
await this.organizationService.updateOrganization(tenantId, tenantDTO);
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { Request, Response, Router, NextFunction } from 'express';
|
||||||
|
import { body, param } from 'express-validator';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { PaymentServicesApplication } from '@/services/PaymentServices/PaymentServicesApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentServicesController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private paymentServicesApp: PaymentServicesApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
asyncMiddleware(this.getPaymentServicesSpecificInvoice.bind(this))
|
||||||
|
);
|
||||||
|
router.get('/state', this.getPaymentMethodsState.bind(this));
|
||||||
|
router.get('/:paymentServiceId', this.getPaymentService.bind(this));
|
||||||
|
router.post(
|
||||||
|
'/:paymentMethodId',
|
||||||
|
[
|
||||||
|
param('paymentMethodId').exists(),
|
||||||
|
|
||||||
|
body('name').optional().isString(),
|
||||||
|
body('options.bank_account_id').optional().isNumeric(),
|
||||||
|
body('options.clearing_account_id').optional().isNumeric(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.updatePaymentMethod.bind(this))
|
||||||
|
);
|
||||||
|
router.delete(
|
||||||
|
'/:paymentMethodId',
|
||||||
|
[param('paymentMethodId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.deletePaymentMethod.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types list.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentServicesSpecificInvoice(
|
||||||
|
req: Request<{ invoiceId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentServices =
|
||||||
|
await this.paymentServicesApp.getPaymentServicesForInvoice(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ paymentServices });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a specific payment service.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentService(
|
||||||
|
req: Request<{ paymentServiceId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentServiceId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentService = await this.paymentServicesApp.getPaymentService(
|
||||||
|
tenantId,
|
||||||
|
paymentServiceId
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({ data: paymentService });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given payment method settings.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async updatePaymentMethod(
|
||||||
|
req: Request<{ paymentMethodId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentMethodId } = req.params;
|
||||||
|
const updatePaymentMethodDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.paymentServicesApp.editPaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentMethodId,
|
||||||
|
updatePaymentMethodDTO
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
id: paymentMethodId,
|
||||||
|
message: 'The given payment method has been updated.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment state providing state.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentMethodsState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentMethodsState =
|
||||||
|
await this.paymentServicesApp.getPaymentMethodsState(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ data: paymentMethodsState });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given payment method.
|
||||||
|
* @param {Request<{ paymentMethodId: number }>} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async deletePaymentMethod(
|
||||||
|
req: Request<{ paymentMethodId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentMethodId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.paymentServicesApp.deletePaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentMethodId
|
||||||
|
);
|
||||||
|
return res.status(204).send({
|
||||||
|
id: paymentMethodId,
|
||||||
|
message: 'The payment method has been deleted.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { check, param, query } from 'express-validator';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { PdfTemplateApplication } from '@/services/PdfTemplate/PdfTemplateApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PdfTemplatesController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
public pdfTemplateApplication: PdfTemplateApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor method.
|
||||||
|
*/
|
||||||
|
public router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:template_id',
|
||||||
|
[param('template_id').exists().isInt().toInt()],
|
||||||
|
this.validationResult,
|
||||||
|
this.deletePdfTemplate.bind(this)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/:template_id',
|
||||||
|
[
|
||||||
|
param('template_id').exists().isInt().toInt(),
|
||||||
|
check('template_name').exists(),
|
||||||
|
check('attributes').exists(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
this.editPdfTemplate.bind(this)
|
||||||
|
);
|
||||||
|
router.get('/state', this.getOrganizationBrandingState.bind(this));
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
[query('resource').optional()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPdfTemplates.bind(this)
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/:template_id',
|
||||||
|
[param('template_id').exists().isInt().toInt()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPdfTemplate.bind(this)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
[
|
||||||
|
check('template_name').exists(),
|
||||||
|
check('resource').exists(),
|
||||||
|
check('attributes').exists(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
this.createPdfInvoiceTemplate.bind(this)
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/:template_id/assign_default',
|
||||||
|
[param('template_id').exists().isInt().toInt()],
|
||||||
|
this.validationResult,
|
||||||
|
this.assginPdfTemplateAsDefault.bind(this)
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPdfInvoiceTemplate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { templateName, resource, attributes } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.pdfTemplateApplication.createPdfTemplate(
|
||||||
|
tenantId,
|
||||||
|
templateName,
|
||||||
|
resource,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
return res.status(201).send({
|
||||||
|
id: result.id,
|
||||||
|
message: 'The PDF template has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async editPdfTemplate(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { template_id: templateId } = req.params;
|
||||||
|
const editTemplateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.pdfTemplateApplication.editPdfTemplate(
|
||||||
|
tenantId,
|
||||||
|
Number(templateId),
|
||||||
|
editTemplateDTO
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
id: result.id,
|
||||||
|
message: 'The PDF template has been updated successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePdfTemplate(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { template_id: templateId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.pdfTemplateApplication.deletePdfTemplate(
|
||||||
|
tenantId,
|
||||||
|
Number(templateId)
|
||||||
|
);
|
||||||
|
return res.status(204).send({
|
||||||
|
id: templateId,
|
||||||
|
message: 'The PDF template has been deleted successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPdfTemplate(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { template_id: templateId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const template = await this.pdfTemplateApplication.getPdfTemplate(
|
||||||
|
tenantId,
|
||||||
|
Number(templateId)
|
||||||
|
);
|
||||||
|
return res.status(200).send(template);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPdfTemplates(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const query = this.matchedQueryData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const templates = await this.pdfTemplateApplication.getPdfTemplates(
|
||||||
|
tenantId,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
return res.status(200).send(templates);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async assginPdfTemplateAsDefault(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { template_id: templateId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.pdfTemplateApplication.assignPdfTemplateAsDefault(
|
||||||
|
tenantId,
|
||||||
|
Number(templateId)
|
||||||
|
);
|
||||||
|
return res.status(204).send({
|
||||||
|
id: templateId,
|
||||||
|
message: 'The given pdf template has been assigned as default template',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getOrganizationBrandingState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data =
|
||||||
|
await this.pdfTemplateApplication.getPdfTemplateBrandingState(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCr
|
|||||||
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
||||||
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
||||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
import { GetCreditNoteState } from '@/services/CreditNotes/GetCreditNoteState';
|
||||||
/**
|
/**
|
||||||
* Credit notes controller.
|
* Credit notes controller.
|
||||||
* @service
|
* @service
|
||||||
@@ -81,6 +82,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
creditNotePdf: GetCreditNotePdf;
|
creditNotePdf: GetCreditNotePdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
getCreditNoteStateService: GetCreditNoteState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -105,6 +109,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.newCreditNote),
|
this.asyncMiddleware(this.newCreditNote),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(CreditNoteAction.View, AbilitySubject.CreditNote),
|
||||||
|
this.asyncMiddleware(this.getCreditNoteState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
// Get specific credit note.
|
// Get specific credit note.
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
@@ -236,6 +246,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,6 +746,23 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getCreditNoteState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.getCreditNoteStateService.getCreditNoteState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
|
this.getPaymentReceivedState.bind(this),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
@@ -167,6 +173,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +397,29 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
*/
|
||||||
|
private async getPaymentReceivedState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.paymentReceiveApplication.getPaymentReceivedState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the given payment receive details.
|
* Retrieve the given payment receive details.
|
||||||
* @async
|
* @async
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
router.post(
|
router.post(
|
||||||
'/:id/approve',
|
'/:id/approve',
|
||||||
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
||||||
[this.validateSpecificEstimateSchema],
|
[...this.validateSpecificEstimateSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
@@ -59,7 +59,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
router.post(
|
router.post(
|
||||||
'/:id/reject',
|
'/:id/reject',
|
||||||
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
||||||
[this.validateSpecificEstimateSchema],
|
[...this.validateSpecificEstimateSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
@@ -105,6 +105,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
||||||
|
this.getSaleEstimateState.bind(this),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
||||||
@@ -168,9 +174,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
@@ -186,6 +190,9 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +552,23 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getSaleEstimateState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleEstimatesApplication.getSaleEstimateState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
|
asyncMiddleware(this.getSaleInvoiceState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -138,6 +144,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -224,9 +231,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.tax_code')
|
check('entries.*.tax_code')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.trim()
|
.trim()
|
||||||
@@ -257,6 +262,14 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
|
// Payment methods.
|
||||||
|
check('payment_methods').optional({ nullable: true }).isArray(),
|
||||||
|
check('payment_methods.*.payment_integration_id').exists().toInt(),
|
||||||
|
check('payment_methods.*.enable').exists().isBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +460,24 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
return res.status(200).send({ saleInvoice });
|
return res.status(200).send({ saleInvoice });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getSaleInvoiceState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleInvoiceApplication.getSaleInvoiceState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve paginated sales invoices with custom view metadata.
|
* Retrieve paginated sales invoices with custom view metadata.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
|
asyncMiddleware(this.getSaleReceiptState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
@@ -148,17 +154,20 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toInt(),
|
.toInt(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.warehouse_id')
|
check('entries.*.warehouse_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toInt(),
|
.toInt(),
|
||||||
|
|
||||||
check('receipt_message').optional().trim(),
|
check('receipt_message').optional().trim(),
|
||||||
|
|
||||||
check('statement').optional().trim(),
|
check('statement').optional().trim(),
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +375,30 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getSaleReceiptState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
// Retrieves receipt in pdf format.
|
||||||
|
try {
|
||||||
|
const data = await this.saleReceiptsApplication.getSaleReceiptState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sale receipt notification via SMS.
|
* Sale receipt notification via SMS.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { param } from 'express-validator';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { PaymentLinksApplication } from '@/services/PaymentLinks/PaymentLinksApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PublicSharableLinkController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private paymentLinkApp: PaymentLinksApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkPublicMeta.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice/pdf',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkInvoicePdf.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/:paymentLinkId/stripe_checkout_session',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.createInvoicePaymentLinkCheckoutSession.bind(this)
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment link public meta.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkPublicMeta(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.paymentLinkApp.getInvoicePaymentLink(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Stripe checkout session for the given payment link id.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response|void>}
|
||||||
|
*/
|
||||||
|
public async createInvoicePaymentLinkCheckoutSession(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session =
|
||||||
|
await this.paymentLinkApp.createInvoicePaymentCheckoutSession(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
return res.status(200).send(session);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkInvoicePdf(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
res.send(pdfContent);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import { AbilitySubject, PaymentReceiveAction } from '@/interfaces';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
import { GenerateShareLink } from '@/services/Sales/Invoices/GenerateeInvoicePaymentLink';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ShareLinkController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private generateShareLinkService: GenerateShareLink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/payment-links/generate',
|
||||||
|
CheckPolicies(PaymentReceiveAction.Edit, AbilitySubject.PaymentReceive),
|
||||||
|
[
|
||||||
|
body('transaction_type').exists(),
|
||||||
|
body('transaction_id').exists().isNumeric().toInt(),
|
||||||
|
body('publicity').optional(),
|
||||||
|
body('expiry_date').optional({ nullable: true }),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.generateShareLink.bind(this))
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates sharable link for the given transaction.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async generateShareLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { transactionType, transactionId, publicity, expiryDate } =
|
||||||
|
this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const link = await this.generateShareLinkService.generatePaymentLink(
|
||||||
|
tenantId,
|
||||||
|
transactionId,
|
||||||
|
transactionType,
|
||||||
|
publicity,
|
||||||
|
expiryDate
|
||||||
|
);
|
||||||
|
res.status(200).json({ link });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import { StripePaymentApplication } from '@/services/StripePayment/StripePaymentApplication';
|
||||||
|
import BaseController from '../BaseController';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeIntegrationController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentApp: StripePaymentApplication;
|
||||||
|
|
||||||
|
public router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get('/link', this.getStripeConnectLink.bind(this));
|
||||||
|
router.post(
|
||||||
|
'/callback',
|
||||||
|
[body('code').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.exchangeOAuth.bind(this)
|
||||||
|
);
|
||||||
|
router.post('/account', asyncMiddleware(this.createAccount.bind(this)));
|
||||||
|
router.post(
|
||||||
|
'/account_link',
|
||||||
|
[body('stripe_account_id').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.createAccountLink.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves Stripe OAuth2 connect link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response|void>}
|
||||||
|
*/
|
||||||
|
public async getStripeConnectLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const authorizationUri = this.stripePaymentApp.getStripeConnectLink();
|
||||||
|
|
||||||
|
return res.status(200).send({ url: authorizationUri });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges the given Stripe authorization code to Stripe user id and access token.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async exchangeOAuth(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { code } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.stripePaymentApp.exchangeStripeOAuthToken(tenantId, code);
|
||||||
|
|
||||||
|
return res.status(200).send({});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe account.
|
||||||
|
* @param {Request} req - The Express request object.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async createAccount(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const accountId = await this.stripePaymentApp.createStripeAccount(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(201).json({
|
||||||
|
accountId,
|
||||||
|
message: 'The Stripe account has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe account session.
|
||||||
|
* @param {Request} req - The Express request object.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async createAccountLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { stripeAccountId } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clientSecret = await this.stripePaymentApp.createAccountLink(
|
||||||
|
tenantId,
|
||||||
|
stripeAccountId
|
||||||
|
);
|
||||||
|
return res.status(200).json({ clientSecret });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { StripeWebhookEventPayload } from '@/interfaces/StripePayment';
|
||||||
|
import { StripePaymentService } from '@/services/StripePayment/StripePaymentService';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeWebhooksController {
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentService: StripePaymentService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
public router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/stripe',
|
||||||
|
bodyParser.raw({ type: 'application/json' }),
|
||||||
|
this.handleWebhook.bind(this)
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming Stripe webhook events.
|
||||||
|
* Verifies the webhook signature, processes the event based on its type,
|
||||||
|
* and triggers appropriate actions or events in the system.
|
||||||
|
*
|
||||||
|
* @param {Request} req - The Express request object containing the webhook payload.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
*/
|
||||||
|
private async handleWebhook(req: Request, res: Response, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
let event = req.body;
|
||||||
|
const sig = req.headers['stripe-signature'];
|
||||||
|
|
||||||
|
// Verify webhook signature and extract the event.
|
||||||
|
// See https://stripe.com/docs/webhooks#verify-events for more information.
|
||||||
|
try {
|
||||||
|
event = this.stripePaymentService.stripe.webhooks.constructEvent(
|
||||||
|
req.rawBody,
|
||||||
|
sig,
|
||||||
|
config.stripePayment.webhooksSecret
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||||
|
}
|
||||||
|
// Handle the event based on its type
|
||||||
|
switch (event.type) {
|
||||||
|
case 'checkout.session.completed':
|
||||||
|
// Triggers `onStripeCheckoutSessionCompleted` event.
|
||||||
|
this.eventPublisher.emitAsync(
|
||||||
|
events.stripeWebhooks.onCheckoutSessionCompleted,
|
||||||
|
{
|
||||||
|
event,
|
||||||
|
} as StripeWebhookEventPayload
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'account.updated':
|
||||||
|
this.eventPublisher.emitAsync(
|
||||||
|
events.stripeWebhooks.onAccountUpdated,
|
||||||
|
{
|
||||||
|
event,
|
||||||
|
} as StripeWebhookEventPayload
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// Add more cases as needed
|
||||||
|
default:
|
||||||
|
console.log(`Unhandled event type ${event.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ received: true });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,7 +113,7 @@ export class SubscriptionController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
|
await this.subscriptionApp.cancelSubscription(tenantId);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { NextFunction, Router, Request, Response } from 'express';
|
import { NextFunction, Router, Request, Response } from 'express';
|
||||||
import { Inject, Service } from 'typedi';
|
import Container, { Inject, Service } from 'typedi';
|
||||||
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
||||||
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
||||||
|
import { StripeWebhooksController } from '../StripeIntegration/StripeWebhooksController';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class Webhooks extends BaseController {
|
export class Webhooks extends BaseController {
|
||||||
@@ -24,6 +25,8 @@ export class Webhooks extends BaseController {
|
|||||||
|
|
||||||
router.post('/lemon', this.lemonWebhooks.bind(this));
|
router.post('/lemon', this.lemonWebhooks.bind(this));
|
||||||
|
|
||||||
|
router.use(Container.get(StripeWebhooksController).router());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ export class Webhooks extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] ?? '';
|
const signature = req.headers['x-signature'] as string ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ import { BankingController } from './controllers/Banking/BankingController';
|
|||||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||||
import { ExportController } from './controllers/Export/ExportController';
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||||
|
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||||
|
import { StripeIntegrationController } from './controllers/StripeIntegration/StripeIntegrationController';
|
||||||
|
import { ShareLinkController } from './controllers/ShareLink/ShareLinkController';
|
||||||
|
import { PublicSharableLinkController } from './controllers/ShareLink/PublicSharableLinkController';
|
||||||
|
import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController';
|
||||||
|
import { PaymentServicesController } from './controllers/PaymentServices/PaymentServicesController';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -80,6 +86,11 @@ export default () => {
|
|||||||
app.use('/jobs', Container.get(Jobs).router());
|
app.use('/jobs', Container.get(Jobs).router());
|
||||||
app.use('/account', Container.get(Account).router());
|
app.use('/account', Container.get(Account).router());
|
||||||
app.use('/webhooks', Container.get(Webhooks).router());
|
app.use('/webhooks', Container.get(Webhooks).router());
|
||||||
|
app.use('/demo', Container.get(OneClickDemoController).router());
|
||||||
|
app.use(
|
||||||
|
'/payment-links',
|
||||||
|
Container.get(PublicSharableLinkController).router()
|
||||||
|
);
|
||||||
|
|
||||||
// - Dashboard routes.
|
// - Dashboard routes.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -145,10 +156,22 @@ export default () => {
|
|||||||
dashboard.use('/import', Container.get(ImportController).router());
|
dashboard.use('/import', Container.get(ImportController).router());
|
||||||
dashboard.use('/export', Container.get(ExportController).router());
|
dashboard.use('/export', Container.get(ExportController).router());
|
||||||
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
||||||
|
dashboard.use(
|
||||||
|
'/stripe_integration',
|
||||||
|
Container.get(StripeIntegrationController).router()
|
||||||
|
);
|
||||||
|
dashboard.use(
|
||||||
|
'/pdf-templates',
|
||||||
|
Container.get(PdfTemplatesController).router()
|
||||||
|
);
|
||||||
|
dashboard.use(
|
||||||
|
'/payment-services',
|
||||||
|
Container.get(PaymentServicesController).router()
|
||||||
|
);
|
||||||
dashboard.use('/', Container.get(ProjectTasksController).router());
|
dashboard.use('/', Container.get(ProjectTasksController).router());
|
||||||
dashboard.use('/', Container.get(ProjectTimesController).router());
|
dashboard.use('/', Container.get(ProjectTimesController).router());
|
||||||
dashboard.use('/', Container.get(WarehousesItemController).router());
|
dashboard.use('/', Container.get(WarehousesItemController).router());
|
||||||
|
dashboard.use('/', Container.get(ShareLinkController).router());
|
||||||
|
|
||||||
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
||||||
dashboard.use('/', Container.get(Miscellaneous).router());
|
dashboard.use('/', Container.get(Miscellaneous).router());
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||||
|
|
||||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { tenantId } = req.user;
|
const { tenantId } = req.user;
|
||||||
|
|
||||||
const Logger = Container.get('logger');
|
const settings = await initializeTenantSettings(tenantId);
|
||||||
|
req.settings = settings;
|
||||||
|
|
||||||
|
res.on('finish', async () => {
|
||||||
|
await settings.save();
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const initializeTenantSettings = async (tenantId: number) => {
|
||||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||||
|
|
||||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||||
@@ -18,10 +28,5 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
|||||||
|
|
||||||
await settings.load();
|
await settings.load();
|
||||||
|
|
||||||
req.settings = settings;
|
return settings;
|
||||||
|
|
||||||
res.on('finish', async () => {
|
|
||||||
await settings.save();
|
|
||||||
});
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
const SupportedMethods = ['POST', 'PUT'];
|
||||||
|
|
||||||
export default (subscriptionSlug = 'main') =>
|
export default (subscriptionSlug = 'main') =>
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { tenant, tenantId } = req;
|
const { tenant, tenantId } = req;
|
||||||
@@ -19,8 +21,10 @@ export default (subscriptionSlug = 'main') =>
|
|||||||
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Validate in case the subscription is inactive.
|
const isMethodSupported = SupportedMethods.includes(req.method);
|
||||||
else if (subscription.inactive()) {
|
const isSubscriptionInactive = subscription.inactive();
|
||||||
|
|
||||||
|
if (isMethodSupported && isSubscriptionInactive) {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Request } from 'express';
|
|||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||||
import rtlDetect from 'rtl-detect';
|
import rtlDetect from 'rtl-detect';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
|
|
||||||
export default (req: Request, tenant: ITenant) => {
|
export default (req: Request, tenant: ITenant) => {
|
||||||
const { id: tenantId, organizationId } = tenant;
|
const { id: tenantId, organizationId } = tenant;
|
||||||
@@ -16,7 +17,7 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
|
|
||||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||||
|
|
||||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
tenantContainer.set('i18n', injectI18nUtils());
|
||||||
|
|
||||||
const knexInstance = tenantServices.knex(tenantId);
|
const knexInstance = tenantServices.knex(tenantId);
|
||||||
const models = tenantServices.models(tenantId);
|
const models = tenantServices.models(tenantId);
|
||||||
@@ -33,14 +34,36 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const injectI18nUtils = (req) => {
|
export const injectI18nUtils = (req) => {
|
||||||
const locale = req.getLocale();
|
const globalI18n = Container.get('i18n');
|
||||||
|
const locale = globalI18n.getLocale();
|
||||||
const direction = rtlDetect.getLangDir(locale);
|
const direction = rtlDetect.getLangDir(locale);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
__: req.__,
|
__: globalI18n.__,
|
||||||
direction,
|
direction,
|
||||||
isRtl: direction === 'rtl',
|
isRtl: direction === 'rtl',
|
||||||
isLtr: direction === 'ltr',
|
isLtr: direction === 'ltr',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initalizeTenantServices = async (tenantId: number) => {
|
||||||
|
const tenant = await Tenant.query()
|
||||||
|
.findById(tenantId)
|
||||||
|
.withGraphFetched('metadata')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const tenantServices = Container.get(TenancyService);
|
||||||
|
const tenantsManager = Container.get(TenantsManagerService);
|
||||||
|
|
||||||
|
// Initialize the knex instance.
|
||||||
|
tenantsManager.setupKnexInstance(tenant);
|
||||||
|
|
||||||
|
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||||
|
tenantContainer.set('i18n', injectI18nUtils());
|
||||||
|
|
||||||
|
tenantServices.knex(tenantId);
|
||||||
|
tenantServices.models(tenantId);
|
||||||
|
tenantServices.repositories(tenantId);
|
||||||
|
tenantServices.cache(tenantId);
|
||||||
|
};
|
||||||
|
|||||||
@@ -245,4 +245,31 @@ module.exports = {
|
|||||||
loops: {
|
loops: {
|
||||||
apiKey: process.env.LOOPS_API_KEY,
|
apiKey: process.env.LOOPS_API_KEY,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-click demo accounts.
|
||||||
|
*/
|
||||||
|
oneClickDemoAccounts: {
|
||||||
|
enable: parseBoolean(process.env.ONE_CLICK_DEMO_ACCOUNTS, false),
|
||||||
|
demoUrl: process.env.ONE_CLICK_DEMO_ACCOUNTS_URL || '',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostHog
|
||||||
|
*/
|
||||||
|
posthog: {
|
||||||
|
apiKey: process.env.POSTHOG_API_KEY,
|
||||||
|
host: process.env.POSTHOG_HOST,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stripe Payment Integration.
|
||||||
|
*/
|
||||||
|
stripePayment: {
|
||||||
|
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY || '',
|
||||||
|
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY || '',
|
||||||
|
clientId: process.env.STRIPE_PAYMENT_CLIENT_ID || '',
|
||||||
|
redirectTo: process.env.STRIPE_PAYMENT_REDIRECT_URL || '',
|
||||||
|
webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET || '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
91
packages/server/src/constants/event-tracker.ts
Normal file
91
packages/server/src/constants/event-tracker.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
export const SALE_INVOICE_CREATED = 'Sale invoice created';
|
||||||
|
export const SALE_INVOICE_EDITED = 'Sale invoice edited';
|
||||||
|
export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
|
||||||
|
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
|
||||||
|
|
||||||
|
export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
|
||||||
|
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
|
||||||
|
export const SALE_ESTIMATE_DELETED = 'Sale estimate deleted';
|
||||||
|
|
||||||
|
export const PAYMENT_RECEIVED_CREATED = 'Payment received created';
|
||||||
|
export const PAYMENT_RECEIVED_EDITED = 'payment received edited';
|
||||||
|
export const PAYMENT_RECEIVED_DELETED = 'Payment received deleted';
|
||||||
|
|
||||||
|
export const BILL_CREATED = 'Bill created';
|
||||||
|
export const BILL_EDITED = 'Bill edited';
|
||||||
|
export const BILL_DELETED = 'Bill deleted';
|
||||||
|
|
||||||
|
export const PAYMENT_MADE_CREATED = 'Payment made created';
|
||||||
|
export const PAYMENT_MADE_EDITED = 'Payment made edited';
|
||||||
|
export const PAYMENT_MADE_DELETED = 'Payment made deleted';
|
||||||
|
|
||||||
|
export const EXPENSE_CREATED = 'Expense created';
|
||||||
|
export const EXPENSE_EDITED = 'Expense edited';
|
||||||
|
export const EXPENSE_DELETED = 'Expense deleted';
|
||||||
|
|
||||||
|
export const ACCOUNT_CREATED = 'Account created';
|
||||||
|
export const ACCOUNT_EDITED = 'Account Edited';
|
||||||
|
export const ACCOUNT_DELETED = 'Account deleted';
|
||||||
|
|
||||||
|
export const ITEM_EVENT_CREATED = 'Item created';
|
||||||
|
export const ITEM_EVENT_EDITED = 'Item edited';
|
||||||
|
export const ITEM_EVENT_DELETED = 'Item deleted';
|
||||||
|
|
||||||
|
export const AUTH_SIGNED_UP = 'Auth Signed-up';
|
||||||
|
export const AUTH_RESET_PASSWORD = 'Auth reset password';
|
||||||
|
|
||||||
|
export const SUBSCRIPTION_CANCELLED = 'Subscription cancelled';
|
||||||
|
export const SUBSCRIPTION_RESUMED = 'Subscription resumed';
|
||||||
|
export const SUBSCRIPTION_PLAN_CHANGED = 'Subscription plan changed';
|
||||||
|
|
||||||
|
export const CUSTOMER_CREATED = 'Customer created';
|
||||||
|
export const CUSTOMER_EDITED = 'Customer edited';
|
||||||
|
export const CUSTOMER_DELETED = 'Customer deleted';
|
||||||
|
|
||||||
|
export const VENDOR_CREATED = 'Vendor created';
|
||||||
|
export const VENDOR_EDITED = 'Vendor edited';
|
||||||
|
export const VENDOR_DELETED = 'Vendor deleted';
|
||||||
|
|
||||||
|
export const TRANSACTIONS_LOCKING_LOCKED = 'Transactions locking locked';
|
||||||
|
export const TRANSACTIONS_LOCKING_LOCKING_CANCELLED =
|
||||||
|
'Transactions locking cancelled';
|
||||||
|
export const TRANSACTIONS_LOCKING_PARTIALLY_UNLOCKED =
|
||||||
|
'Transactions locking partially unlocked';
|
||||||
|
export const TRANSACTIONS_LOCKING_PARTIALLY_UNLOCK_CANCELLED =
|
||||||
|
'Transactions locking partially unlock cancelled';
|
||||||
|
|
||||||
|
export const BANK_TRANSACTION_MATCHED = 'Bank transaction matching deleted';
|
||||||
|
export const BANK_TRANSACTION_EXCLUDED = 'Bank transaction excluded';
|
||||||
|
export const BANK_TRANSACTION_CATEGORIZED = 'Bank transaction categorized';
|
||||||
|
export const BANK_TRANSACTION_UNCATEGORIZED = 'Bank transaction uncategorized';
|
||||||
|
export const BANK_ACCOUNT_DISCONNECTED = 'Bank account disconnected';
|
||||||
|
|
||||||
|
export const MANUAL_JOURNAL_CREATED = 'Manual journal created';
|
||||||
|
export const MANUAL_JOURNAL_EDITED = 'Manual journal edited';
|
||||||
|
export const MANUAL_JOURNAL_DELETED = 'Manual journal deleted';
|
||||||
|
export const MANUAL_JOURNAL_PUBLISHED = 'Manual journal published';
|
||||||
|
|
||||||
|
export const BANK_RULE_CREATED = 'Bank rule created';
|
||||||
|
export const BANK_RULE_EDITED = 'Bank rule edited';
|
||||||
|
export const BANK_RULE_DELETED = 'Bank rule deleted';
|
||||||
|
|
||||||
|
export const PDF_TEMPLATE_CREATED = 'PDF template created';
|
||||||
|
export const PDF_TEMPLATE_EDITED = 'PDF template edited';
|
||||||
|
export const PDF_TEMPLATE_DELETED = 'PDF template deleted';
|
||||||
|
export const PDF_TEMPLATE_ASSIGNED_DEFAULT = 'PDF template assigned as default';
|
||||||
|
|
||||||
|
export const PAYMENT_METHOD_EDITED = 'Payment method edited';
|
||||||
|
export const PAYMENT_METHOD_DELETED = 'Payment method deleted';
|
||||||
|
|
||||||
|
export const INVOICE_PAYMENT_LINK_GENERATED = 'Invoice payment link generated';
|
||||||
|
|
||||||
|
export const STRIPE_INTEGRAION_CONNECTED = 'Stripe integration oauth2 connected';
|
||||||
|
|
||||||
|
// # Event Groups
|
||||||
|
export const ACCOUNT_GROUP = 'Account';
|
||||||
|
export const ITEM_GROUP = 'Item';
|
||||||
|
export const AUTH_GROUP = 'Auth';
|
||||||
|
export const SALE_GROUP = 'Sale';
|
||||||
|
export const PAYMENT_GROUP = 'Payment';
|
||||||
|
export const BILL_GROUP = 'Bill';
|
||||||
|
export const EXPENSE_GROUP = 'Expense';
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.string('stripe_pintent_id').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.dropColumn('stripe_pintent_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable('pdf_templates', (table) => {
|
||||||
|
table.increments('id').primary();
|
||||||
|
table.text('resource');
|
||||||
|
table.text('template_name');
|
||||||
|
table.json('attributes');
|
||||||
|
table.boolean('predefined').defaultTo(false);
|
||||||
|
table.boolean('default').defaultTo(false);
|
||||||
|
table.timestamps();
|
||||||
|
})
|
||||||
|
.table('sales_invoices', (table) => {
|
||||||
|
table
|
||||||
|
.integer('pdf_template_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('pdf_templates');
|
||||||
|
})
|
||||||
|
.table('sales_estimates', (table) => {
|
||||||
|
table
|
||||||
|
.integer('pdf_template_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('pdf_templates');
|
||||||
|
})
|
||||||
|
.table('sales_receipts', (table) => {
|
||||||
|
table
|
||||||
|
.integer('pdf_template_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('pdf_templates');
|
||||||
|
})
|
||||||
|
.table('credit_notes', (table) => {
|
||||||
|
table
|
||||||
|
.integer('pdf_template_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('pdf_templates');
|
||||||
|
})
|
||||||
|
.table('payment_receives', (table) => {
|
||||||
|
table
|
||||||
|
.integer('pdf_template_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('pdf_templates');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.table('payment_receives', (table) => {
|
||||||
|
table.dropColumn('pdf_template_id');
|
||||||
|
})
|
||||||
|
.table('credit_notes', (table) => {
|
||||||
|
table.dropColumn('pdf_template_id');
|
||||||
|
})
|
||||||
|
.table('sales_receipts', (table) => {
|
||||||
|
table.dropColumn('pdf_template_id');
|
||||||
|
})
|
||||||
|
.table('sales_estimates', (table) => {
|
||||||
|
table.dropColumn('pdf_template_id');
|
||||||
|
})
|
||||||
|
.table('sales_invoices', (table) => {
|
||||||
|
table.dropColumn('pdf_template_id');
|
||||||
|
})
|
||||||
|
.dropTableIfExists('pdf_templates');
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('payment_integrations', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('service');
|
||||||
|
table.string('name');
|
||||||
|
table.string('slug');
|
||||||
|
table.boolean('payment_enabled').defaultTo(false);
|
||||||
|
table.boolean('payout_enabled').defaultTo(false);
|
||||||
|
table.string('account_id');
|
||||||
|
table.json('options');
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('payment_integrations');
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('transactions_payment_methods', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
table.integer('reference_id').unsigned();
|
||||||
|
table.string('reference_type');
|
||||||
|
table
|
||||||
|
.integer('payment_integration_id')
|
||||||
|
.unsigned()
|
||||||
|
.index()
|
||||||
|
.references('id')
|
||||||
|
.inTable('payment_integrations');
|
||||||
|
table.boolean('enable').defaultTo(false);
|
||||||
|
table.json('options').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('transactions_payment_methods');
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex('pdf_templates').insert([
|
||||||
|
{
|
||||||
|
resource: 'SaleInvoice',
|
||||||
|
templateName: 'Standard Template',
|
||||||
|
predefined: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: 'SaleEstimate',
|
||||||
|
templateName: 'Standard Template',
|
||||||
|
predefined: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: 'SaleReceipt',
|
||||||
|
templateName: 'Standard Template',
|
||||||
|
predefined: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: 'CreditNote',
|
||||||
|
templateName: 'Standard Template',
|
||||||
|
predefined: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: 'PaymentReceive',
|
||||||
|
templateName: 'Standard Template',
|
||||||
|
predefined: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {};
|
||||||
@@ -15,6 +15,7 @@ export default class SeedSettings extends TenantSeeder {
|
|||||||
|
|
||||||
// Manual journals settings.
|
// Manual journals settings.
|
||||||
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
||||||
|
{ group: 'manual_journals', key: 'number_prefix', value: 'J-' },
|
||||||
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
||||||
|
|
||||||
// Sale invoices settings.
|
// Sale invoices settings.
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ export const PrepardExpenses = {
|
|||||||
predefined: true,
|
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 default [
|
export default [
|
||||||
{
|
{
|
||||||
name: 'Bank Account',
|
name: 'Bank Account',
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export interface IRegisterDTO {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
organizationName: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILoginDTO {
|
export interface ILoginDTO {
|
||||||
@@ -77,6 +76,10 @@ export interface IAuthSendedResetPassword {
|
|||||||
|
|
||||||
export interface IAuthGetMetaPOJO {
|
export interface IAuthGetMetaPOJO {
|
||||||
signupDisabled: boolean;
|
signupDisabled: boolean;
|
||||||
|
oneClickDemo: {
|
||||||
|
enable: boolean;
|
||||||
|
demoUrl: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthSignUpVerifingEventPayload {
|
export interface IAuthSignUpVerifingEventPayload {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export interface IBillsFilter extends IDynamicListFilterDTO {
|
|||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
filterQuery?: (q: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBillsService {
|
export interface IBillsService {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export interface ICashflowTransactionUncategorizedPayload {
|
|||||||
tenantId: number;
|
tenantId: number;
|
||||||
uncategorizedTransactionId: number;
|
uncategorizedTransactionId: number;
|
||||||
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
|
oldMainUncategorizedTransaction: IUncategorizedCashflowTransaction;
|
||||||
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
@@ -167,11 +168,18 @@ export interface CategorizeTransactionAsExpenseDTO {
|
|||||||
export interface IGetUncategorizedTransactionsQuery {
|
export interface IGetUncategorizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
minAmount?: number;
|
||||||
|
maxAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IGetRecognizedTransactionsQuery {
|
export interface IGetRecognizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
}
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
minAmount?: number;
|
||||||
|
maxAmount?: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ export interface ICustomerEventCreatingPayload {
|
|||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
export interface ICustomerEventEditedPayload {
|
export interface ICustomerEventEditedPayload {
|
||||||
|
tenantId: number
|
||||||
customerId: number;
|
customerId: number;
|
||||||
customer: ICustomer;
|
customer: ICustomer;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { IDynamicListFilter, IItemEntry, IVendorCredit } from '@/interfaces';
|
import { IDynamicListFilter, IItemEntry } from '@/interfaces';
|
||||||
import { ILedgerEntry } from './Ledger';
|
import { ILedgerEntry } from './Ledger';
|
||||||
import { AttachmentLinkDTO } from './Attachments';
|
import { AttachmentLinkDTO } from './Attachments';
|
||||||
|
|
||||||
export interface ICreditNoteEntryNewDTO {
|
export interface ICreditNoteEntryNewDTO {
|
||||||
index: number;
|
index?: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
rate: number;
|
rate: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -22,7 +22,7 @@ export interface ICreditNoteNewDTO {
|
|||||||
entries: ICreditNoteEntryNewDTO[];
|
entries: ICreditNoteEntryNewDTO[];
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
warehouseId?: number;
|
warehouseId?: number;
|
||||||
attachments?: AttachmentLinkDTO[]
|
attachments?: AttachmentLinkDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICreditNoteEditDTO {
|
export interface ICreditNoteEditDTO {
|
||||||
@@ -35,7 +35,7 @@ export interface ICreditNoteEditDTO {
|
|||||||
entries: ICreditNoteEntryNewDTO[];
|
entries: ICreditNoteEntryNewDTO[];
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
warehouseId?: number;
|
warehouseId?: number;
|
||||||
attachments?: AttachmentLinkDTO[]
|
attachments?: AttachmentLinkDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICreditNoteEntry extends IItemEntry {}
|
export interface ICreditNoteEntry extends IItemEntry {}
|
||||||
@@ -61,7 +61,9 @@ export interface ICreditNote {
|
|||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
warehouseId: number;
|
warehouseId: number;
|
||||||
createdAt?: Date,
|
createdAt?: Date;
|
||||||
|
termsConditions: string;
|
||||||
|
note: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CreditNoteAction {
|
export enum CreditNoteAction {
|
||||||
@@ -130,7 +132,9 @@ export interface ICreditNoteOpenedPayload {
|
|||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICreditNotesQueryDTO {}
|
export interface ICreditNotesQueryDTO {
|
||||||
|
filterQuery?: (query: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICreditNotesQueryDTO extends IDynamicListFilter {
|
export interface ICreditNotesQueryDTO extends IDynamicListFilter {
|
||||||
page: number;
|
page: number;
|
||||||
@@ -256,3 +260,61 @@ export type ICreditNoteGLCommonEntry = Pick<
|
|||||||
| 'debit'
|
| 'debit'
|
||||||
| 'branchId'
|
| 'branchId'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface CreditNotePdfTemplateAttributes {
|
||||||
|
// # Primary color
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
|
||||||
|
// # Company logo
|
||||||
|
showCompanyLogo: boolean;
|
||||||
|
companyLogo: string;
|
||||||
|
|
||||||
|
// # Company name
|
||||||
|
companyName: string;
|
||||||
|
|
||||||
|
// # Customer Address
|
||||||
|
showCustomerAddress: boolean;
|
||||||
|
customerAddress: string;
|
||||||
|
|
||||||
|
// # Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
|
billedToLabel: string;
|
||||||
|
|
||||||
|
total: string;
|
||||||
|
totalLabel: string;
|
||||||
|
showTotal: boolean;
|
||||||
|
|
||||||
|
subtotal: string;
|
||||||
|
subtotalLabel: string;
|
||||||
|
showSubtotal: boolean;
|
||||||
|
|
||||||
|
showCustomerNote: boolean;
|
||||||
|
customerNote: string;
|
||||||
|
customerNoteLabel: string;
|
||||||
|
|
||||||
|
showTermsConditions: boolean;
|
||||||
|
termsConditions: string;
|
||||||
|
termsConditionsLabel: string;
|
||||||
|
|
||||||
|
lines: Array<{
|
||||||
|
item: string;
|
||||||
|
description: string;
|
||||||
|
rate: string;
|
||||||
|
quantity: string;
|
||||||
|
total: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
showCreditNoteNumber: boolean;
|
||||||
|
creditNoteNumberLabel: string;
|
||||||
|
creditNoteNumebr: string;
|
||||||
|
|
||||||
|
creditNoteDate: string;
|
||||||
|
showCreditNoteDate: boolean;
|
||||||
|
creditNoteDateLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICreditNoteState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ export interface IExpensesFilter {
|
|||||||
columnSortBy: string;
|
columnSortBy: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
viewSlug?: string;
|
viewSlug?: string;
|
||||||
|
filterQuery?: (query: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExpense {
|
export interface IExpense {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
|||||||
|
|
||||||
export interface IImportFileCommitedEventPayload {
|
export interface IImportFileCommitedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
importId: number;
|
importId: string;
|
||||||
meta: ImportFilePreviewPOJO;
|
meta: ImportFilePreviewPOJO;
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,7 @@ export interface IItemEntry {
|
|||||||
|
|
||||||
export interface IItemEntryDTO {
|
export interface IItemEntryDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
index?: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
landedCost?: boolean;
|
landedCost?: boolean;
|
||||||
warehouseId?: number;
|
warehouseId?: number;
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export interface IModelMetaFieldCommon2 {
|
|||||||
importHint?: string;
|
importHint?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
unique?: number;
|
unique?: number;
|
||||||
|
features?: Array<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelMetaRelationField2 {
|
export interface IModelMetaRelationField2 {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface IPaymentReceived {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
pdfTemplateId?: number;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceivedCreateDTO {
|
export interface IPaymentReceivedCreateDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
@@ -66,7 +67,7 @@ export interface IPaymentReceivedEntry {
|
|||||||
|
|
||||||
export interface IPaymentReceivedEntryDTO {
|
export interface IPaymentReceivedEntryDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
index: number;
|
index?: number;
|
||||||
paymentReceiveId?: number;
|
paymentReceiveId?: number;
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
paymentAmount: number;
|
paymentAmount: number;
|
||||||
@@ -74,6 +75,7 @@ export interface IPaymentReceivedEntryDTO {
|
|||||||
|
|
||||||
export interface IPaymentsReceivedFilter extends IDynamicListFilterDTO {
|
export interface IPaymentsReceivedFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
|
filterQuery?: (trx: Knex.Transaction) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceivePageEntry {
|
export interface IPaymentReceivePageEntry {
|
||||||
@@ -184,3 +186,60 @@ export interface PaymentReceiveMailPresendEvent {
|
|||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
messageOptions: PaymentReceiveMailOptsDTO;
|
messageOptions: PaymentReceiveMailOptsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaymentReceivedPdfLineItem {
|
||||||
|
item: string;
|
||||||
|
description: string;
|
||||||
|
rate: string;
|
||||||
|
quantity: string;
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentReceivedPdfTax {
|
||||||
|
label: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentReceivedPdfTemplateAttributes {
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
showCompanyLogo: boolean;
|
||||||
|
companyLogo: string;
|
||||||
|
companyName: string;
|
||||||
|
|
||||||
|
// Customer Address
|
||||||
|
showCustomerAddress: boolean;
|
||||||
|
customerAddress: string;
|
||||||
|
|
||||||
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
|
billedToLabel: string;
|
||||||
|
|
||||||
|
total: string;
|
||||||
|
totalLabel: string;
|
||||||
|
showTotal: boolean;
|
||||||
|
|
||||||
|
subtotal: string;
|
||||||
|
subtotalLabel: string;
|
||||||
|
showSubtotal: boolean;
|
||||||
|
|
||||||
|
lines: Array<{
|
||||||
|
invoiceNumber: string;
|
||||||
|
invoiceAmount: string;
|
||||||
|
paidAmount: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
showPaymentReceivedNumber: boolean;
|
||||||
|
paymentReceivedNumberLabel: string;
|
||||||
|
paymentReceivedNumebr: string;
|
||||||
|
|
||||||
|
paymentReceivedDate: string;
|
||||||
|
showPaymentReceivedDate: boolean;
|
||||||
|
paymentReceivedDateLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IPaymentReceivedState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ export interface ISaleEstimateDTO {
|
|||||||
|
|
||||||
export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
|
export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
|
filterQuery?: (q: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISalesEstimatesService {
|
export interface ISalesEstimatesService {
|
||||||
@@ -142,3 +143,7 @@ export interface ISaleEstimateMailPresendEvent {
|
|||||||
saleEstimateId: number;
|
saleEstimateId: number;
|
||||||
messageOptions: SaleEstimateMailOptionsDTO;
|
messageOptions: SaleEstimateMailOptionsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaleEstimateState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,34 @@ import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
|||||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||||
import { AttachmentLinkDTO } from './Attachments';
|
import { AttachmentLinkDTO } from './Attachments';
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLink {
|
||||||
|
id: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLinkEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
oldSaleInvoiceId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISaleInvoice {
|
export interface ISaleInvoice {
|
||||||
id: number;
|
id: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -45,6 +73,13 @@ export interface ISaleInvoice {
|
|||||||
subtotal: number;
|
subtotal: number;
|
||||||
subtotalLocal: number;
|
subtotalLocal: number;
|
||||||
subtotalExludingTax: number;
|
subtotalExludingTax: number;
|
||||||
|
|
||||||
|
termsConditions: string;
|
||||||
|
invoiceMessage: string;
|
||||||
|
|
||||||
|
pdfTemplateId?: number;
|
||||||
|
|
||||||
|
paymentMethods?: Array<PaymentIntegrationTransactionLink>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceDTO {
|
export interface ISaleInvoiceDTO {
|
||||||
@@ -79,6 +114,7 @@ export interface ISalesInvoicesFilter extends IDynamicListFilter {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
searchKeyword?: string;
|
searchKeyword?: string;
|
||||||
|
filterQuery?: (q: Knex.QueryBuilder) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISalesInvoicesService {
|
export interface ISalesInvoicesService {
|
||||||
@@ -130,7 +166,13 @@ export interface ISaleInvoiceEditingPayload {
|
|||||||
|
|
||||||
export interface ISaleInvoiceDeletePayload {
|
export interface ISaleInvoiceDeletePayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
saleInvoice: ISaleInvoice;
|
oldSaleInvoice: ISaleInvoice;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISaleInvoiceDeletingPayload {
|
||||||
|
tenantId: number;
|
||||||
|
oldSaleInvoice: ISaleInvoice;
|
||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
@@ -216,3 +258,88 @@ export interface ISaleInvoiceMailSent {
|
|||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
messageOptions: SendInvoiceMailDTO;
|
messageOptions: SendInvoiceMailDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoice Pdf Document
|
||||||
|
export interface InvoicePdfLine {
|
||||||
|
item: string;
|
||||||
|
description: string;
|
||||||
|
rate: string;
|
||||||
|
quantity: string;
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoicePdfTax {
|
||||||
|
label: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoicePdfTemplateAttributes {
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
|
||||||
|
companyName: string;
|
||||||
|
|
||||||
|
showCompanyLogo: boolean;
|
||||||
|
companyLogo: string;
|
||||||
|
|
||||||
|
dueDate: string;
|
||||||
|
dueDateLabel: string;
|
||||||
|
showDueDate: boolean;
|
||||||
|
|
||||||
|
dateIssue: string;
|
||||||
|
dateIssueLabel: string;
|
||||||
|
showDateIssue: boolean;
|
||||||
|
|
||||||
|
invoiceNumberLabel: string;
|
||||||
|
invoiceNumber: string;
|
||||||
|
showInvoiceNumber: boolean;
|
||||||
|
|
||||||
|
// Customer Address
|
||||||
|
showCustomerAddress: boolean;
|
||||||
|
customerAddress: string;
|
||||||
|
|
||||||
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
|
billedToLabel: string;
|
||||||
|
|
||||||
|
lineItemLabel: string;
|
||||||
|
lineDescriptionLabel: string;
|
||||||
|
lineRateLabel: string;
|
||||||
|
lineTotalLabel: string;
|
||||||
|
|
||||||
|
totalLabel: string;
|
||||||
|
subtotalLabel: string;
|
||||||
|
discountLabel: string;
|
||||||
|
paymentMadeLabel: string;
|
||||||
|
balanceDueLabel: string;
|
||||||
|
|
||||||
|
showTotal: boolean;
|
||||||
|
showSubtotal: boolean;
|
||||||
|
showDiscount: boolean;
|
||||||
|
showTaxes: boolean;
|
||||||
|
showPaymentMade: boolean;
|
||||||
|
showDueAmount: boolean;
|
||||||
|
showBalanceDue: boolean;
|
||||||
|
|
||||||
|
total: string;
|
||||||
|
subtotal: string;
|
||||||
|
discount: string;
|
||||||
|
paymentMade: string;
|
||||||
|
balanceDue: string;
|
||||||
|
|
||||||
|
termsConditionsLabel: string;
|
||||||
|
showTermsConditions: boolean;
|
||||||
|
termsConditions: string;
|
||||||
|
|
||||||
|
lines: InvoicePdfLine[];
|
||||||
|
taxes: InvoicePdfTax[];
|
||||||
|
|
||||||
|
statementLabel: string;
|
||||||
|
showStatement: boolean;
|
||||||
|
statement: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISaleInvocieState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ export interface ISaleReceipt {
|
|||||||
entries?: IItemEntry[];
|
entries?: IItemEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISalesReceiptsFilter {}
|
export interface ISalesReceiptsFilter {
|
||||||
|
filterQuery?: (query: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISaleReceiptDTO {
|
export interface ISaleReceiptDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
@@ -153,3 +155,64 @@ export interface ISaleReceiptMailPresend {
|
|||||||
saleReceiptId: number;
|
saleReceiptId: number;
|
||||||
messageOptions: SaleReceiptMailOptsDTO;
|
messageOptions: SaleReceiptMailOptsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaleReceiptBrandingTemplateAttributes {
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
showCompanyLogo: boolean;
|
||||||
|
companyLogo: string;
|
||||||
|
companyName: string;
|
||||||
|
|
||||||
|
// Customer Address
|
||||||
|
showCustomerAddress: boolean;
|
||||||
|
customerAddress: string;
|
||||||
|
|
||||||
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
|
billedToLabel: string;
|
||||||
|
|
||||||
|
// Total
|
||||||
|
total: string;
|
||||||
|
totalLabel: string;
|
||||||
|
showTotal: boolean;
|
||||||
|
|
||||||
|
// Subtotal
|
||||||
|
subtotal: string;
|
||||||
|
subtotalLabel: string;
|
||||||
|
showSubtotal: boolean;
|
||||||
|
|
||||||
|
// Customer Note
|
||||||
|
showCustomerNote: boolean;
|
||||||
|
customerNote: string;
|
||||||
|
customerNoteLabel: string;
|
||||||
|
|
||||||
|
// Terms & Conditions
|
||||||
|
showTermsConditions: boolean;
|
||||||
|
termsConditions: string;
|
||||||
|
termsConditionsLabel: string;
|
||||||
|
|
||||||
|
// Lines
|
||||||
|
lines: Array<{
|
||||||
|
item: string;
|
||||||
|
description: string;
|
||||||
|
rate: string;
|
||||||
|
quantity: string;
|
||||||
|
total: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Receipt Number
|
||||||
|
showReceiptNumber: boolean;
|
||||||
|
receiptNumberLabel: string;
|
||||||
|
receiptNumebr: string;
|
||||||
|
|
||||||
|
// Receipt Date
|
||||||
|
receiptDate: string;
|
||||||
|
showReceiptDate: boolean;
|
||||||
|
receiptDateLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ISaleReceiptState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -18,14 +18,26 @@ export interface IOrganizationBuildDTO {
|
|||||||
dateFormat?: string;
|
dateFormat?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OrganizationAddressDTO {
|
||||||
|
address1: string;
|
||||||
|
address2: string;
|
||||||
|
postalCode: string;
|
||||||
|
city: string;
|
||||||
|
stateProvince: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IOrganizationUpdateDTO {
|
export interface IOrganizationUpdateDTO {
|
||||||
name: string;
|
name: string;
|
||||||
location: string;
|
location?: string;
|
||||||
baseCurrency: string;
|
baseCurrency?: string;
|
||||||
timezone: string;
|
timezone?: string;
|
||||||
fiscalYear: string;
|
fiscalYear?: string;
|
||||||
industry: string;
|
industry?: string;
|
||||||
taxNumber: string;
|
taxNumber?: string;
|
||||||
|
primaryColor?: string;
|
||||||
|
logoKey?: string;
|
||||||
|
address?: OrganizationAddressDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOrganizationBuildEventPayload {
|
export interface IOrganizationBuildEventPayload {
|
||||||
@@ -33,3 +45,7 @@ export interface IOrganizationBuildEventPayload {
|
|||||||
buildDTO: IOrganizationBuildDTO;
|
buildDTO: IOrganizationBuildDTO;
|
||||||
systemUser: ISystemUser;
|
systemUser: ISystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IOrganizationBuiltEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
}
|
||||||
|
|||||||
20
packages/server/src/interfaces/StripePayment.ts
Normal file
20
packages/server/src/interfaces/StripePayment.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export interface StripePaymentLinkCreatedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
paymentLinkId: string;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
stripeIntegrationId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeCheckoutSessionCompletedEventPayload {
|
||||||
|
event: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeInvoiceCheckoutSessionPOJO {
|
||||||
|
sessionId: string;
|
||||||
|
publishableKey: string;
|
||||||
|
redirectTo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeWebhookEventPayload {
|
||||||
|
event: any;
|
||||||
|
}
|
||||||
8
packages/server/src/interfaces/Subscription.ts
Normal file
8
packages/server/src/interfaces/Subscription.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SubscriptionPayload {
|
||||||
|
lemonSqueezyId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubscriptionPaymentStatus {
|
||||||
|
Succeed = 'succeed',
|
||||||
|
Failed = 'failed',
|
||||||
|
}
|
||||||
@@ -106,6 +106,7 @@ export interface IVendorCreditsQueryDTO extends IDynamicListFilter {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
searchKeyword?: string;
|
searchKeyword?: string;
|
||||||
|
filterQuery?: (q: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVendorCreditEditingPayload {
|
export interface IVendorCreditEditingPayload {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export * from './Times';
|
|||||||
export * from './ProjectProfitabilitySummary';
|
export * from './ProjectProfitabilitySummary';
|
||||||
export * from './TaxRate';
|
export * from './TaxRate';
|
||||||
export * from './Plaid';
|
export * from './Plaid';
|
||||||
|
export * from './Subscription';
|
||||||
|
|
||||||
export interface I18nService {
|
export interface I18nService {
|
||||||
__: (input: string) => string;
|
__: (input: string) => string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||||
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
||||||
|
import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||||
|
|
||||||
export class Transformer {
|
export class Transformer {
|
||||||
public context: any;
|
public context: any;
|
||||||
@@ -155,19 +156,35 @@ export class Transformer {
|
|||||||
this.dateFormat = format;
|
this.dateFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date.
|
||||||
|
* @param {} date
|
||||||
|
* @param {string} format -
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected formatDate(date, format?: string) {
|
||||||
|
// Use the export date format if the async operation is in exporting,
|
||||||
|
// otherwise use the given or default format.
|
||||||
|
const _format = this.context.exportAls.isExport
|
||||||
|
? EXPORT_DTE_FORMAT
|
||||||
|
: format || this.dateFormat;
|
||||||
|
|
||||||
|
return date ? moment(date).format(_format) : '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param date
|
* @param date
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatDate(date) {
|
protected formatDateFromNow(date) {
|
||||||
return date ? moment(date).format(this.dateFormat) : '';
|
return date ? moment(date).fromNow(true) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param number
|
* @param number
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatNumber(number, props?) {
|
protected formatNumber(number, props?) {
|
||||||
return formatNumber(number, { money: false, ...props });
|
return formatNumber(number, { money: false, ...props });
|
||||||
@@ -177,7 +194,7 @@ export class Transformer {
|
|||||||
*
|
*
|
||||||
* @param money
|
* @param money
|
||||||
* @param options
|
* @param options
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatMoney(money, options?) {
|
protected formatMoney(money, options?) {
|
||||||
return formatNumber(money, {
|
return formatNumber(money, {
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import { isNull } from 'lodash';
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { TenantMetadata } from '@/system/models';
|
import { TenantMetadata } from '@/system/models';
|
||||||
import { Transformer } from './Transformer';
|
import { Transformer } from './Transformer';
|
||||||
|
import { ImportAls } from '@/services/Import/ImportALS';
|
||||||
|
import { ExportAls } from '@/services/Export/ExportAls';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class TransformerInjectable {
|
export class TransformerInjectable {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private exportAls: ExportAls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the application context of all tenant transformers.
|
* Retrieves the application context of all tenant transformers.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -17,10 +22,12 @@ export class TransformerInjectable {
|
|||||||
async getApplicationContext(tenantId: number) {
|
async getApplicationContext(tenantId: number) {
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
const organization = await TenantMetadata.query().findOne({ tenantId });
|
const organization = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
const exportAls = this.exportAls;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
organization,
|
organization,
|
||||||
i18n,
|
i18n,
|
||||||
|
exportAls,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashfl
|
|||||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||||
|
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
||||||
|
import { EventsTrackerListeners } from '@/services/EventsTracker/events/events';
|
||||||
|
import { InvoicePaymentIntegrationSubscriber } from '@/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber';
|
||||||
|
import { StripeWebhooksSubscriber } from '@/services/StripePayment/events/StripeWebhooksSubscriber';
|
||||||
|
import { SeedStripeAccountsOnOAuthGrantedSubscriber } from '@/services/StripePayment/events/SeedStripeAccounts';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -246,6 +251,7 @@ export const susbcribers = () => {
|
|||||||
DeleteCashflowTransactionOnUncategorize,
|
DeleteCashflowTransactionOnUncategorize,
|
||||||
PreventDeleteTransactionOnDelete,
|
PreventDeleteTransactionOnDelete,
|
||||||
|
|
||||||
|
// Subscription
|
||||||
SubscribeFreeOnSignupCommunity,
|
SubscribeFreeOnSignupCommunity,
|
||||||
SendVerfiyMailOnSignUp,
|
SendVerfiyMailOnSignUp,
|
||||||
|
|
||||||
@@ -281,6 +287,16 @@ export const susbcribers = () => {
|
|||||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||||
|
|
||||||
// Loops
|
// Loops
|
||||||
LoopsEventsSubscriber
|
LoopsEventsSubscriber,
|
||||||
|
|
||||||
|
// Demo Account
|
||||||
|
SeedInitialDemoAccountDataOnOrgBuild,
|
||||||
|
|
||||||
|
// Stripe Payment
|
||||||
|
InvoicePaymentIntegrationSubscriber,
|
||||||
|
StripeWebhooksSubscriber,
|
||||||
|
SeedStripeAccountsOnOAuthGrantedSubscriber,
|
||||||
|
|
||||||
|
...EventsTrackerListeners
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { I18n } from 'i18n';
|
|||||||
|
|
||||||
export default () => new I18n({
|
export default () => new I18n({
|
||||||
locales: ['en', 'ar'],
|
locales: ['en', 'ar'],
|
||||||
|
defaultLocale: 'en',
|
||||||
register: global,
|
register: global,
|
||||||
directory: global.__locales_dir,
|
directory: global.__locales_dir,
|
||||||
updateFiles: false,
|
updateFiles: false,
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ import { BankRule } from '@/models/BankRule';
|
|||||||
import { BankRuleCondition } from '@/models/BankRuleCondition';
|
import { BankRuleCondition } from '@/models/BankRuleCondition';
|
||||||
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
|
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
|
||||||
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
|
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
|
||||||
|
import { PdfTemplate } from '@/models/PdfTemplate';
|
||||||
|
import { PaymentIntegration } from '@/models/PaymentIntegration';
|
||||||
|
import { TransactionPaymentServiceEntry } from '@/models/TransactionPaymentServiceEntry';
|
||||||
|
|
||||||
export default (knex) => {
|
export default (knex) => {
|
||||||
const models = {
|
const models = {
|
||||||
@@ -139,6 +142,9 @@ export default (knex) => {
|
|||||||
BankRuleCondition,
|
BankRuleCondition,
|
||||||
RecognizedBankTransaction,
|
RecognizedBankTransaction,
|
||||||
MatchedBankTransaction,
|
MatchedBankTransaction,
|
||||||
|
PdfTemplate,
|
||||||
|
PaymentIntegration,
|
||||||
|
TransactionPaymentServiceEntry,
|
||||||
};
|
};
|
||||||
return mapValues(models, (model) => model.bindKnex(knex));
|
return mapValues(models, (model) => model.bindKnex(knex));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'vendor',
|
defaultFilterField: 'vendor',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
@@ -167,6 +169,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
billNumber: {
|
billNumber: {
|
||||||
@@ -238,6 +252,22 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -402,6 +402,7 @@ export default class Bill extends mixin(TenantModel, [
|
|||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const BillLandedCost = require('models/BillLandedCost');
|
const BillLandedCost = require('models/BillLandedCost');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||||
@@ -453,6 +454,18 @@ export default class Bill extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'bills.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bill may has associated tax rate transactions.
|
* Bill may has associated tax rate transactions.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'vendor',
|
defaultFilterField: 'vendor',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
@@ -5,6 +7,8 @@ export default {
|
|||||||
sortField: 'bill_date',
|
sortField: 'bill_date',
|
||||||
},
|
},
|
||||||
exportable: true,
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
importable: true,
|
importable: true,
|
||||||
importAggregator: 'group',
|
importAggregator: 'group',
|
||||||
importAggregateOn: 'entries',
|
importAggregateOn: 'entries',
|
||||||
@@ -77,7 +81,7 @@ export default {
|
|||||||
paymentDate: {
|
paymentDate: {
|
||||||
name: 'bill_payment.field.payment_date',
|
name: 'bill_payment.field.payment_date',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
accessor: 'formattedPaymentDate'
|
accessor: 'formattedPaymentDate',
|
||||||
},
|
},
|
||||||
paymentNumber: {
|
paymentNumber: {
|
||||||
name: 'bill_payment.field.payment_number',
|
name: 'bill_payment.field.payment_number',
|
||||||
@@ -111,6 +115,40 @@ export default {
|
|||||||
name: 'bill_payment.field.reference',
|
name: 'bill_payment.field.reference',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
date: {
|
||||||
|
name: 'Bill date',
|
||||||
|
accessor: 'bill.formattedBillDate',
|
||||||
|
},
|
||||||
|
billNo: {
|
||||||
|
name: 'Bill No.',
|
||||||
|
accessor: 'bill.billNo',
|
||||||
|
},
|
||||||
|
billRefNo: {
|
||||||
|
name: 'Bill Reference No.',
|
||||||
|
accessor: 'bill.referenceNo',
|
||||||
|
},
|
||||||
|
billAmount: {
|
||||||
|
name: 'Bill Amount',
|
||||||
|
accessor: 'bill.totalFormatted',
|
||||||
|
},
|
||||||
|
paidAmount: {
|
||||||
|
name: 'Paid Amount',
|
||||||
|
accessor: 'paymentAmountFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
vendorId: {
|
vendorId: {
|
||||||
@@ -174,5 +212,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
11
packages/server/src/models/Branch.settings.ts
Normal file
11
packages/server/src/models/Branch.settings.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'Name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {},
|
||||||
|
fields: {}
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import BranchMetadata from './Branch.settings';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class Branch extends TenantModel {
|
export default class Branch extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -169,4 +171,11 @@ export default class Branch extends TenantModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model settings.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return BranchMetadata;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
query.modify('filterByStatus', role.value);
|
query.modify('filterByStatus', role.value);
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ export default {
|
|||||||
},
|
},
|
||||||
creditNoteDate: {
|
creditNoteDate: {
|
||||||
name: 'Credit Note Date',
|
name: 'Credit Note Date',
|
||||||
accessor: 'formattedCreditNoteDate'
|
accessor: 'formattedCreditNoteDate',
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'Reference No.',
|
name: 'Reference No.',
|
||||||
@@ -147,6 +149,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
customerId: {
|
customerId: {
|
||||||
@@ -215,5 +229,21 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ export default class CreditNote extends mixin(TenantModel, [
|
|||||||
const Customer = require('models/Customer');
|
const Customer = require('models/Customer');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -235,6 +236,18 @@ export default class CreditNote extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credit note may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'credit_notes.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credit note may has many attached attachments.
|
* Credit note may has many attached attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
customerType: {
|
||||||
|
name: 'Customer Type',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'formattedCustomerType',
|
||||||
|
},
|
||||||
firstName: {
|
firstName: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -135,116 +140,117 @@ export default {
|
|||||||
openingBalance: {
|
openingBalance: {
|
||||||
name: 'vendor.field.opening_balance',
|
name: 'vendor.field.opening_balance',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
openingBalanceAt: {
|
||||||
name: 'vendor.field.opening_balance_at',
|
name: 'vendor.field.opening_balance_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false
|
printable: false,
|
||||||
|
accessor: 'formattedOpeningBalanceAt'
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currencyCode: {
|
||||||
name: 'vendor.field.currency',
|
name: 'vendor.field.currency',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'vendor.field.status',
|
name: 'vendor.field.status',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'vendor.field.note',
|
name: 'vendor.field.note',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
name: 'vendor.field.created_at',
|
name: 'vendor.field.created_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expense - Settings.
|
* Expense - Settings.
|
||||||
*/
|
*/
|
||||||
@@ -119,6 +121,12 @@ export default {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
paymentAccountId: {
|
paymentAccountId: {
|
||||||
@@ -178,6 +186,14 @@ export default {
|
|||||||
name: 'expense.field.publish',
|
name: 'expense.field.publish',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,17 +41,17 @@ export default {
|
|||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
sell_price: {
|
sell_price: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.sell_price',
|
||||||
column: 'sell_price',
|
column: 'sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_price: {
|
cost_price: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_price',
|
||||||
column: 'cost_price',
|
column: 'cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_account: {
|
cost_account: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.cost_account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export default {
|
|||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
sell_account: {
|
sell_account: {
|
||||||
name: 'item.field.sell_description',
|
name: 'item.field.sell_account',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
|
|
||||||
@@ -155,24 +155,24 @@ export default {
|
|||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
sellPrice: {
|
sellPrice: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.sell_price',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
},
|
},
|
||||||
costPrice: {
|
costPrice: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_price',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
},
|
},
|
||||||
costAccount: {
|
costAccount: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.cost_account',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
accessor: 'costAccount.name',
|
accessor: 'costAccount.name',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
sellAccount: {
|
sellAccount: {
|
||||||
name: 'item.field.sell_description',
|
name: 'item.field.sell_account',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
accessor: 'sellAccount.name',
|
accessor: 'sellAccount.name',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
@@ -257,25 +257,25 @@ export default {
|
|||||||
name: 'item.field.sell_price',
|
name: 'item.field.sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_price: {
|
costPrice: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
costAccount: {
|
costAccountId: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
sellAccount: {
|
sellAccountId: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
inventoryAccount: {
|
inventoryAccountId: {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
|
|||||||
61
packages/server/src/models/PaymentIntegration.ts
Normal file
61
packages/server/src/models/PaymentIntegration.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Model } from 'objection';
|
||||||
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
|
export class PaymentIntegration extends Model {
|
||||||
|
paymentEnabled!: boolean;
|
||||||
|
payoutEnabled!: boolean;
|
||||||
|
|
||||||
|
static get tableName() {
|
||||||
|
return 'payment_integrations';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get idColumn() {
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['fullEnabled'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get jsonAttributes() {
|
||||||
|
return ['options'];
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullEnabled() {
|
||||||
|
return this.paymentEnabled && this.payoutEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Query to filter enabled payment and payout.
|
||||||
|
*/
|
||||||
|
fullEnabled(query) {
|
||||||
|
query.where('paymentEnabled', true).andWhere('payoutEnabled', true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get jsonSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'service'],
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
service: { type: 'string' },
|
||||||
|
paymentEnabled: { type: 'boolean' },
|
||||||
|
payoutEnabled: { type: 'boolean' },
|
||||||
|
accountId: { type: 'string' },
|
||||||
|
options: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
bankAccountId: { type: 'number' },
|
||||||
|
clearingAccountId: { type: 'number' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: { type: 'string', format: 'date-time' },
|
||||||
|
updatedAt: { type: 'string', format: 'date-time' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
importable: true,
|
importable: true,
|
||||||
|
|
||||||
exportable: true,
|
exportable: true,
|
||||||
|
exportFlattenOn: 'entries',
|
||||||
|
|
||||||
importAggregator: 'group',
|
importAggregator: 'group',
|
||||||
importAggregateOn: 'entries',
|
importAggregateOn: 'entries',
|
||||||
importAggregateBy: 'paymentReceiveNo',
|
importAggregateBy: 'paymentReceiveNo',
|
||||||
@@ -72,7 +77,7 @@ export default {
|
|||||||
amount: {
|
amount: {
|
||||||
name: 'payment_receive.field.amount',
|
name: 'payment_receive.field.amount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
accessor: 'formattedAmount'
|
accessor: 'formattedAmount',
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'payment_receive.field.reference_no',
|
name: 'payment_receive.field.reference_no',
|
||||||
@@ -92,11 +97,45 @@ export default {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
accessor: 'entries',
|
||||||
|
type: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
columns: {
|
||||||
|
date: {
|
||||||
|
name: 'Invoice date',
|
||||||
|
accessor: 'invoice.invoiceDateFormatted',
|
||||||
|
},
|
||||||
|
invoiceNo: {
|
||||||
|
name: 'Invoice No.',
|
||||||
|
accessor: 'invoice.invoiceNo',
|
||||||
|
},
|
||||||
|
invoiceRefNo: {
|
||||||
|
name: 'Invoice Reference No.',
|
||||||
|
accessor: 'invoice.referenceNo',
|
||||||
|
},
|
||||||
|
invoiceAmount: {
|
||||||
|
name: 'Invoice Amount',
|
||||||
|
accessor: 'invoice.totalFormatted',
|
||||||
|
},
|
||||||
|
paidAmount: {
|
||||||
|
name: 'Paid Amount',
|
||||||
|
accessor: 'paymentAmountFormatted',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
name: 'payment_receive.field.created_at',
|
name: 'payment_receive.field.created_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
customerId: {
|
customerId: {
|
||||||
@@ -158,5 +197,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
59
packages/server/src/models/PdfTemplate.ts
Normal file
59
packages/server/src/models/PdfTemplate.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
|
export class PdfTemplate extends TenantModel {
|
||||||
|
/**
|
||||||
|
* Table name.
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'pdf_templates';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamps columns.
|
||||||
|
*/
|
||||||
|
get timestamps() {
|
||||||
|
return ['createdAt', 'updatedAt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json schema.
|
||||||
|
*/
|
||||||
|
static get jsonSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
templateName: { type: 'string' },
|
||||||
|
attributes: { type: 'object' }, // JSON field definition
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Filters the due invoices.
|
||||||
|
*/
|
||||||
|
default(query) {
|
||||||
|
query.where('default', true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'estimate_date',
|
defaultFilterField: 'estimate_date',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
@@ -13,7 +15,7 @@ export default {
|
|||||||
importAggregateBy: 'estimateNumber',
|
importAggregateBy: 'estimateNumber',
|
||||||
|
|
||||||
print: {
|
print: {
|
||||||
pageTitle: 'Sale Estimates'
|
pageTitle: 'Sale Estimates',
|
||||||
},
|
},
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
@@ -142,6 +144,7 @@ export default {
|
|||||||
delivered: {
|
delivered: {
|
||||||
name: 'Delivered',
|
name: 'Delivered',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
accessor: 'isDelivered',
|
||||||
exportable: true,
|
exportable: true,
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
@@ -173,6 +176,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
customerId: {
|
customerId: {
|
||||||
@@ -251,6 +266,22 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
|||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const Customer = require('models/Customer');
|
const Customer = require('models/Customer');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -221,6 +222,18 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale estimate may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'sales_estimates.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sale estimate transaction may has many attached attachments.
|
* Sale estimate transaction may has many attached attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'customer',
|
defaultFilterField: 'customer',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
@@ -155,6 +157,7 @@ export default {
|
|||||||
name: 'invoice.field.delivered',
|
name: 'invoice.field.delivered',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
printable: false,
|
printable: false,
|
||||||
|
accessor: 'isDelivered',
|
||||||
},
|
},
|
||||||
entries: {
|
entries: {
|
||||||
name: 'Entries',
|
name: 'Entries',
|
||||||
@@ -184,6 +187,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
invoiceDate: {
|
invoiceDate: {
|
||||||
@@ -267,6 +282,22 @@ export default {
|
|||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -408,10 +408,15 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
const Account = require('models/Account');
|
const Account = require('models/Account');
|
||||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||||
|
const {
|
||||||
|
TransactionPaymentServiceEntry,
|
||||||
|
} = require('models/TransactionPaymentServiceEntry');
|
||||||
|
const { PdfTemplate } = require('models/PdfTemplate');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -499,6 +504,18 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'sales_invoices.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice may has associated written-off expense account.
|
* Invoice may has associated written-off expense account.
|
||||||
*/
|
*/
|
||||||
@@ -553,12 +570,42 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
modelClass: MatchedBankTransaction,
|
modelClass: MatchedBankTransaction,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_invoices.id',
|
from: 'sales_invoices.id',
|
||||||
to: "matched_bank_transactions.referenceId",
|
to: 'matched_bank_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('reference_type', 'SaleInvoice');
|
query.where('reference_type', 'SaleInvoice');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale invoice may belongs to payment methods entries.
|
||||||
|
*/
|
||||||
|
paymentMethods: {
|
||||||
|
relation: Model.HasManyRelation,
|
||||||
|
modelClass: TransactionPaymentServiceEntry,
|
||||||
|
join: {
|
||||||
|
from: 'sales_invoices.id',
|
||||||
|
to: 'transactions_payment_methods.referenceId',
|
||||||
|
},
|
||||||
|
beforeInsert: (model) => {
|
||||||
|
model.referenceType = 'SaleInvoice';
|
||||||
|
},
|
||||||
|
filter: (query) => {
|
||||||
|
query.where('reference_type', 'SaleInvoice');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale invoice may belongs to pdf branding template.
|
||||||
|
*/
|
||||||
|
pdfTemplate: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: PdfTemplate,
|
||||||
|
join: {
|
||||||
|
from: 'sales_invoices.pdfTemplateId',
|
||||||
|
to: 'pdf_templates.id',
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'receipt_date',
|
defaultFilterField: 'receipt_date',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
@@ -169,6 +171,18 @@ export default {
|
|||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false,
|
printable: false,
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
receiptDate: {
|
receiptDate: {
|
||||||
@@ -245,6 +259,22 @@ export default {
|
|||||||
name: 'Receipt Message',
|
name: 'Receipt Message',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export default class SaleReceipt extends mixin(TenantModel, [
|
|||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customer: {
|
customer: {
|
||||||
@@ -169,6 +170,18 @@ export default class SaleReceipt extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale receipt may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'sales_receipts.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sale receipt transaction may has many attached attachments.
|
* Sale receipt transaction may has many attached attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
46
packages/server/src/models/TransactionPaymentServiceEntry.ts
Normal file
46
packages/server/src/models/TransactionPaymentServiceEntry.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
|
export class TransactionPaymentServiceEntry extends TenantModel {
|
||||||
|
/**
|
||||||
|
* Table name
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'transactions_payment_methods';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json schema of the model.
|
||||||
|
*/
|
||||||
|
static get jsonSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
required: ['paymentIntegrationId'],
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
referenceId: { type: 'integer' },
|
||||||
|
referenceType: { type: 'string' },
|
||||||
|
paymentIntegrationId: { type: 'integer' },
|
||||||
|
enable: { type: 'boolean' },
|
||||||
|
options: { type: 'object' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
const { PaymentIntegration } = require('./PaymentIntegration');
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentIntegration: {
|
||||||
|
relation: TenantModel.BelongsToOneRelation,
|
||||||
|
modelClass: PaymentIntegration,
|
||||||
|
join: {
|
||||||
|
from: 'transactions_payment_methods.paymentIntegrationId',
|
||||||
|
to: 'payment_integrations.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
|
import moment from 'moment';
|
||||||
import { Model, mixin } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSettings from './ModelSetting';
|
import ModelSettings from './ModelSetting';
|
||||||
import Account from './Account';
|
|
||||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||||
|
|
||||||
export default class UncategorizedCashflowTransaction extends mixin(
|
export default class UncategorizedCashflowTransaction extends mixin(
|
||||||
@@ -166,6 +166,28 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
pending(query) {
|
pending(query) {
|
||||||
query.where('pending', true);
|
query.where('pending', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
minAmount(query, minAmount) {
|
||||||
|
query.where('amount', '>=', minAmount);
|
||||||
|
},
|
||||||
|
|
||||||
|
maxAmount(query, maxAmount) {
|
||||||
|
query.where('amount', '<=', maxAmount);
|
||||||
|
},
|
||||||
|
|
||||||
|
toDate(query, toDate) {
|
||||||
|
const dateFormat = 'YYYY-MM-DD';
|
||||||
|
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||||
|
|
||||||
|
query.where('date', '<=', _toDate);
|
||||||
|
},
|
||||||
|
|
||||||
|
fromDate(query, fromDate) {
|
||||||
|
const dateFormat = 'YYYY-MM-DD';
|
||||||
|
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||||
|
|
||||||
|
query.where('date', '>=', _fromDate);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Features } from '@/interfaces';
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
query.modify('filterByStatus', role.value);
|
query.modify('filterByStatus', role.value);
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
name: 'Branch',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'branch.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
|
warehouse: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'warehouse.name',
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
vendorId: {
|
vendorId: {
|
||||||
@@ -225,5 +239,21 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branchId: {
|
||||||
|
name: 'Branch',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Branch',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.BRANCHES],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
warehouseId: {
|
||||||
|
name: 'Warehouse',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Warehouse',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
features: [Features.WAREHOUSES],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ export default class VendorCredit extends mixin(TenantModel, [
|
|||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const Branch = require('models/Branch');
|
const Branch = require('models/Branch');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
|
const Warehouse = require('models/Warehouse');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vendor: {
|
vendor: {
|
||||||
@@ -217,6 +218,18 @@ export default class VendorCredit extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor credit may has associated warehouse.
|
||||||
|
*/
|
||||||
|
warehouse: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Warehouse.default,
|
||||||
|
join: {
|
||||||
|
from: 'vendor_credits.warehouseId',
|
||||||
|
to: 'warehouses.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor credit may has many attached attachments.
|
* Vendor credit may has many attached attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
11
packages/server/src/models/Warehouse.settings.ts
Normal file
11
packages/server/src/models/Warehouse.settings.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'Name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {},
|
||||||
|
fields: {}
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { IAccount } from '@/interfaces';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
PrepardExpenses,
|
PrepardExpenses,
|
||||||
|
StripeClearingAccount,
|
||||||
TaxPayableAccount,
|
TaxPayableAccount,
|
||||||
UnearnedRevenueAccount,
|
UnearnedRevenueAccount,
|
||||||
} from '@/database/seeds/data/accounts';
|
} from '@/database/seeds/data/accounts';
|
||||||
@@ -247,4 +248,37 @@ export default class AccountRepository extends TenantRepository {
|
|||||||
}
|
}
|
||||||
return result;
|
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
|
||||||
|
) {
|
||||||
|
// Retrieves the given tenant metadata.
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({
|
||||||
|
tenantId: this.tenantId,
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { AccountsApplication } from './AccountsApplication';
|
import { AccountsApplication } from './AccountsApplication';
|
||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||||
|
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AccountsExportable extends Exportable {
|
export class AccountsExportable extends Exportable {
|
||||||
@@ -20,7 +21,7 @@ export class AccountsExportable extends Exportable {
|
|||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
...query,
|
...query,
|
||||||
structure: IAccountsStructureType.Flat,
|
structure: IAccountsStructureType.Flat,
|
||||||
pageSize: 12000,
|
pageSize: EXPORT_SIZE_LIMIT,
|
||||||
page: 1,
|
page: 1,
|
||||||
} as IAccountsFilter;
|
} as IAccountsFilter;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import type { Multer } from 'multer';
|
import type { Multer } from 'multer';
|
||||||
import multerS3 from 'multer-s3';
|
import multerS3 from 'multer-s3';
|
||||||
import { s3 } from '@/lib/S3/S3';
|
import { s3 } from '@/lib/S3/S3';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AttachmentUploadPipeline {
|
export class AttachmentUploadPipeline {
|
||||||
/**
|
/**
|
||||||
* Middleware to ensure that S3 configuration is properly set before proceeding.
|
* Middleware to ensure that S3 configuration is properly set before proceeding.
|
||||||
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
|
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
|
||||||
*
|
|
||||||
* @param req The HTTP request object.
|
* @param req The HTTP request object.
|
||||||
* @param res The HTTP response object.
|
* @param res The HTTP response object.
|
||||||
* @param next The callback to pass control to the next middleware function.
|
* @param next The callback to pass control to the next middleware function.
|
||||||
@@ -49,6 +48,11 @@ export class AttachmentUploadPipeline {
|
|||||||
key: function (req, file, cb) {
|
key: function (req, file, cb) {
|
||||||
cb(null, Date.now().toString());
|
cb(null, Date.now().toString());
|
||||||
},
|
},
|
||||||
|
acl: function(req, file, cb) {
|
||||||
|
// Conditionally set file to public or private based on isPublic flag
|
||||||
|
const aclValue = true ? 'public-read' : 'private';
|
||||||
|
cb(null, aclValue); // Set ACL based on the isPublic flag
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user