Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
415673656c | ||
|
|
1906d9f3f5 | ||
|
|
d640dc1f40 | ||
|
|
8cd1b36a02 | ||
|
|
5a8d9cc7e8 | ||
|
|
6323e2ffec | ||
|
|
7af2e7ccbc | ||
|
|
baf4c691d6 | ||
|
|
c633fa8522 | ||
|
|
1d54947764 | ||
|
|
477da0e7c0 | ||
|
|
b9963aa241 | ||
|
|
994c441bb8 | ||
|
|
0a5115fc20 | ||
|
|
11d7a40326 | ||
|
|
46719ef361 | ||
|
|
14ae978bde | ||
|
|
beec09788e | ||
|
|
391dc77071 | ||
|
|
38f2004e56 | ||
|
|
5a5eac246b | ||
|
|
a7bafd4f62 | ||
|
|
a25ab39647 | ||
|
|
7dd09e2903 | ||
|
|
6ab461a212 | ||
|
|
fabc88c81a | ||
|
|
3a19518440 | ||
|
|
56b5e3469e | ||
|
|
542763ddf5 | ||
|
|
1010d97a92 | ||
|
|
d5dacaa988 | ||
|
|
154ade9647 | ||
|
|
5b75fa9286 | ||
|
|
05cf94940e | ||
|
|
03b0d2519b | ||
|
|
000c3e40e1 | ||
|
|
ffb06f5194 | ||
|
|
73ab92e693 | ||
|
|
dd1392cdc8 | ||
|
|
17b3bbe1d8 | ||
|
|
e02ad1e795 | ||
|
|
df8391201f | ||
|
|
aa4aaeb612 | ||
|
|
09b98664c5 | ||
|
|
be46147d9a | ||
|
|
1fe7d58c8c | ||
|
|
4f57782be4 | ||
|
|
7b5f0d3930 | ||
|
|
831fb9180c | ||
|
|
2594e37dc7 | ||
|
|
ca44d6346d | ||
|
|
df41de7239 | ||
|
|
459bf4cd55 | ||
|
|
3537a05ea2 | ||
|
|
da47418f17 | ||
|
|
d5b0546301 | ||
|
|
b6f3c0145f | ||
|
|
c5c85bdfbe | ||
|
|
63a95df534 | ||
|
|
6103f1e4c7 | ||
|
|
3e591beb03 | ||
|
|
c6db54175f | ||
|
|
f7bf24acb3 | ||
|
|
ddffe630ff | ||
|
|
2c54092591 | ||
|
|
7e65f3f642 | ||
|
|
7df6aa4110 | ||
|
|
7df316aa56 | ||
|
|
53ab40a075 | ||
|
|
d115ebde12 | ||
|
|
0e99ac88d3 | ||
|
|
5d6f901d33 | ||
|
|
908bbb9fa6 | ||
|
|
6c1870be8f | ||
|
|
f5834c72c6 | ||
|
|
7ee3392d3e | ||
|
|
c58822fd6c | ||
|
|
ba8091d697 | ||
|
|
d668d60ed5 | ||
|
|
a34b7a2106 | ||
|
|
6f12127095 | ||
|
|
b4e5bbf376 | ||
|
|
a11530d190 | ||
|
|
43d4425da5 | ||
|
|
4c1909cb73 | ||
|
|
a7b6b7a03e | ||
|
|
1f46275bde | ||
|
|
aa7e5d4ae9 | ||
|
|
bb482df3ce | ||
|
|
f878786646 | ||
|
|
652851a1a9 | ||
|
|
850f4956cb | ||
|
|
94223b6ebf | ||
|
|
e9d34e19ad | ||
|
|
107532fe26 | ||
|
|
c32aff82ee | ||
|
|
de8a867d33 | ||
|
|
17a8aba23f | ||
|
|
04b601626b | ||
|
|
802775c118 | ||
|
|
b6baa80134 | ||
|
|
b2d0f2ed3c | ||
|
|
d23f33bae4 | ||
|
|
22ea557337 | ||
|
|
b3ebbb429c | ||
|
|
51218797af | ||
|
|
2d18a6573e | ||
|
|
2646ad5bc4 | ||
|
|
51aec8d8b3 | ||
|
|
638bd95d6f | ||
|
|
f2fcc3a649 | ||
|
|
48795748d8 | ||
|
|
6ba54a994a | ||
|
|
6687db4085 | ||
|
|
ba1d9b3f28 | ||
|
|
51905825fd | ||
|
|
bd5e33855a | ||
|
|
f7fbc0e31c | ||
|
|
cb06fa342c | ||
|
|
581229053a | ||
|
|
209da69b8f | ||
|
|
d09aebcebb | ||
|
|
0cc80bc179 | ||
|
|
79dcc592bc | ||
|
|
687111851a | ||
|
|
dbbaa387bd | ||
|
|
470bfd32f7 | ||
|
|
5fddd080fd | ||
|
|
e10c530b4b | ||
|
|
12189f018d | ||
|
|
0111b0e6ff | ||
|
|
0930b0428d | ||
|
|
289f40014e | ||
|
|
01cc0568f9 | ||
|
|
42ee8ed9fa | ||
|
|
1dae65cb74 | ||
|
|
ce40d67ea2 | ||
|
|
26088a71ee | ||
|
|
cadf6b81a0 | ||
|
|
728b4cacd9 | ||
|
|
b4d3ac2f96 | ||
|
|
bc8e440814 | ||
|
|
4c0f9a0aef | ||
|
|
c321d90575 | ||
|
|
03e6372f14 | ||
|
|
c0481f67ad | ||
|
|
b7f316d25a | ||
|
|
dffd818396 | ||
|
|
bbc19df6b4 | ||
|
|
c8c2786893 | ||
|
|
d79f26f1b5 | ||
|
|
32ba6f9a6c | ||
|
|
ccbb399685 | ||
|
|
f381d433ec | ||
|
|
2ee49f7496 | ||
|
|
bb299aa595 | ||
|
|
a66867463e | ||
|
|
de50b89e5c | ||
|
|
c4ee143354 | ||
|
|
a2227016e5 | ||
|
|
4d6f65b179 | ||
|
|
758ebbe261 | ||
|
|
279890e922 | ||
|
|
44fae36b82 | ||
|
|
fc2fac80af | ||
|
|
5ad9a9654b | ||
|
|
8a4034cc5d | ||
|
|
5649657bf0 | ||
|
|
c929a7cb27 | ||
|
|
eeedb789a9 | ||
|
|
321af8c271 | ||
|
|
fd4d86e797 | ||
|
|
49988e27a2 | ||
|
|
8c94ee5982 | ||
|
|
2e73a34fef | ||
|
|
ea7f987fe3 | ||
|
|
d55503f0c7 | ||
|
|
f59b8166b6 | ||
|
|
2c735d7edf | ||
|
|
5b5ab9fe1e | ||
|
|
28ac9b2d90 | ||
|
|
3300a6a499 | ||
|
|
152a22baa0 | ||
|
|
e873198238 | ||
|
|
68a0db91ee | ||
|
|
ddea7be24a | ||
|
|
b7b86bb0c5 | ||
|
|
817ef906dc | ||
|
|
863c7693fa | ||
|
|
65788e344a | ||
|
|
abc242d117 | ||
|
|
6dd4968327 | ||
|
|
d805703c08 |
@@ -159,6 +159,15 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nklmantey",
|
||||
"name": "Mantey",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/90279429?v=4",
|
||||
"profile": "https://nklmantey.com/",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
97
CHANGELOG.md
97
CHANGELOG.md
@@ -2,6 +2,103 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
# [0.22.0]
|
||||
|
||||
* feat: estimate, receipt, credit note mail preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/757
|
||||
* feat: Add discount to transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/758
|
||||
* fix: update financial forms to use new formatted amount utilities and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/760
|
||||
* fix: total lines style by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/761
|
||||
* fix: discount & adjustment sale transactions bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/762
|
||||
* fix: discount transactions GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/763
|
||||
|
||||
# [0.21.2]
|
||||
|
||||
* hotbug: upload attachments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/755
|
||||
|
||||
# [0.21.1]
|
||||
|
||||
* fix: download invoice document on payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/750
|
||||
* fix: attach branding template attrs to payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/751
|
||||
* fix: make manual entries adjust decimal credit/debit amounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/754
|
||||
* feat: allow quantity of entries accept decimal value by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/753
|
||||
|
||||
# [0.21.0]
|
||||
|
||||
* fix: Credit and debit totals not balancing when decimal values are used by @nklmantey in https://github.com/bigcapitalhq/bigcapital/pull/722
|
||||
* docs: add nklmantey as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/725
|
||||
* feat: track more services events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/721
|
||||
* feat: Invoice mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/723
|
||||
* fix: change the send mail button on invoice drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/730
|
||||
* refactor: notification mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/731
|
||||
* fix: attach payment link in sending invoice mail receipt by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/732
|
||||
* fix: send invoice drawer layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/733
|
||||
* fix: hook up cc and bcc fields to mail sender by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/734
|
||||
* fix: company logo does not show up in mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/736
|
||||
* fix: change default invoice mail message by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/737
|
||||
* fix: typing invoice send mail fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/738
|
||||
* fix: clean up ivnoice mail receipt preview component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/739
|
||||
* feat: add shared package to pdf templates to render in the server and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/735
|
||||
* feat: getting invoice preview on send mail view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/740
|
||||
* fix: style SSR invoice paper template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/741
|
||||
* fix: send invoice receipt addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/742
|
||||
* fix: due invoice server invoice by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/744
|
||||
* fix: `BIG-265` forgot password text by @ibutiti in https://github.com/bigcapitalhq/bigcapital/pull/745
|
||||
* Crims on sv translation by @Crims-on in https://github.com/bigcapitalhq/bigcapital/pull/671
|
||||
* feat: Added Spanish language to the App 🇪🇸 by @angelosorno in https://github.com/bigcapitalhq/bigcapital/pull/530
|
||||
* fix: mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/746
|
||||
* fix: company logo of the template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/747
|
||||
* fix: monorepo dependencies scope by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/748
|
||||
|
||||
# [0.20.6]
|
||||
|
||||
* fix: Import category column of item resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/710
|
||||
* fix: Parse the uppercase values in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/711
|
||||
* chore: Move i18nApply localization to the account transformer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/713
|
||||
* fix: Sync Plaid credit card account type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/714
|
||||
* fix: Sync account normal of cashflow GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/715
|
||||
* feat: Add quantity column to pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/716
|
||||
* feat: Pre-line invoice statements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/717
|
||||
* feat: Invoice number in downloaded pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/718
|
||||
* feat: Track events of pdf documents views by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/719
|
||||
* fix: Customer note does not appear in pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/720
|
||||
|
||||
# [0.20.5]
|
||||
|
||||
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
|
||||
* fix: Invoice form layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/705
|
||||
* refactor: Invoice, estimate, receipt, credit note and payment received date input fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/707
|
||||
* feat: Add customize templates button to edit forms by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/708
|
||||
* feat: Track account, invoice and item viewed events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/709
|
||||
|
||||
# [0.20.4]
|
||||
|
||||
* fix: Delete company logo from the PDF template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/699
|
||||
* fix: Set max width/height to company logo of pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/700
|
||||
|
||||
# [0.20.3]
|
||||
|
||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
||||
|
||||
# [0.20.2]
|
||||
|
||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
||||
|
||||
# [0.20.1]
|
||||
|
||||
* fix: Getting uploaded object uri by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/684
|
||||
|
||||
# [0.20.0]
|
||||
|
||||
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/u/bigcapitalhq">
|
||||
<img src="https://img.shields.io/docker/pulls/bigcapitalhq/webapp" />
|
||||
</a>
|
||||
<a href="https://discord.com/invite/c8nPBJafeb">
|
||||
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
||||
</a>
|
||||
@@ -130,6 +133,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"scripts": {
|
||||
"dev": "lerna run dev",
|
||||
"build": "lerna run build",
|
||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||
"test:e2e": "playwright test",
|
||||
"prepare": "husky install"
|
||||
|
||||
4
packages/server/.gitignore
vendored
4
packages/server/.gitignore
vendored
@@ -4,5 +4,5 @@ stdout.log
|
||||
/dist
|
||||
/build
|
||||
/public/imports
|
||||
|
||||
dist
|
||||
dist
|
||||
newrelic_agent.log
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
"bigcapital": "./bin/bigcapital.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bigcapital/utils": "*",
|
||||
"@bigcapital/email-components": "*",
|
||||
"@bigcapital/pdf-templates": "*",
|
||||
"@aws-sdk/client-s3": "^3.576.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
||||
"@bigcapital/utils": "*",
|
||||
"@casl/ability": "^5.4.3",
|
||||
"@hapi/boom": "^7.4.3",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
|
||||
@@ -108,7 +108,14 @@ block head
|
||||
.#{prefix}-table__cell--right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.#{prefix}-table__cell--item .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.#{prefix}-table__cell--item .item .item__description{
|
||||
color: #5f6b7c;
|
||||
}
|
||||
.#{prefix}-totals {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -143,7 +150,7 @@ block head
|
||||
color: #666;
|
||||
}
|
||||
.#{prefix}-statement__value {
|
||||
/* Styles for statement value */
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
block content
|
||||
@@ -183,17 +190,20 @@ block content
|
||||
table(class=`${prefix}-table`)
|
||||
thead
|
||||
tr
|
||||
th(class=`${prefix}-table__header`) #{'Item'}
|
||||
th(class=`${prefix}-table__header`) #{'Description'}
|
||||
th(class=`${prefix}-table__header`) #{'Rate'}
|
||||
th(class=`${prefix}-table__header`) #{'Total'}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{'Item'}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{'Quantity'}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{'Rate'}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--total ${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--right`) #{line.rate}
|
||||
td(class=`${prefix}-table__cell--right`) #{line.total}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--item ${prefix}-table__cell--item`)
|
||||
div.item
|
||||
div.item__label #{line.item}
|
||||
div.item__description #{line.description}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--quantity ${prefix}-table__cell--right`) #{line.quantity}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--rate ${prefix}-table__cell--right`) #{line.rate}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--total ${prefix}-table__cell--right`) #{line.total}
|
||||
|
||||
div(class=`${prefix}-totals`)
|
||||
if showSubtotal
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
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: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{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
|
||||
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||
|
||||
//- Header (invluces big title, details and logo)
|
||||
div(class=`${prefix}-header`)
|
||||
|
||||
//- Header details (includes big title and details )
|
||||
div(class=`${prefix}-header-details`)
|
||||
h1(class=`${prefix}-big-title`) Estimate
|
||||
|
||||
//- Terms List
|
||||
div(class=`${prefix}-terms`)
|
||||
if showEstimateNumber
|
||||
div(class=`${prefix}-terms-item`)
|
||||
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
||||
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
||||
|
||||
if showEstimateDate
|
||||
div(class=`${prefix}-terms-item`)
|
||||
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
||||
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
||||
|
||||
if showExpirationDate
|
||||
div(class=`${prefix}-terms-item`)
|
||||
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
||||
div(class=`${prefix}-terms-item__value`) #{expirationDate}
|
||||
|
||||
//- Company logo
|
||||
if showCompanyLogo && companyLogoUri
|
||||
div(class=`${prefix}-logo-wrap`)
|
||||
img(alt="Company logo", src=companyLogoUri)
|
||||
|
||||
//- Addresses (Group section)
|
||||
div(class=`${prefix}-addresses`)
|
||||
if showCompanyAddress
|
||||
div(class=`${prefix}-address-from`)
|
||||
div !{companyAddress}
|
||||
|
||||
if showCustomerAddress
|
||||
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
|
||||
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 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}
|
||||
|
||||
//- Statements section
|
||||
if showCustomerNote && customerNote
|
||||
div(class=`${prefix}-statement`)
|
||||
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
|
||||
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,258 +0,0 @@
|
||||
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: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{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,192 +0,0 @@
|
||||
extends ../PaperTemplateLayout.pug
|
||||
|
||||
block head
|
||||
- var prefix = 'bp3';
|
||||
|
||||
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: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{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
|
||||
div(class=`${prefix}-root`)
|
||||
//- Header (includes big title, details and logo )
|
||||
div(class=`${prefix}-header`)
|
||||
//- Header details (includes big title and details )
|
||||
div(class=`${prefix}-header-details`)
|
||||
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}
|
||||
|
||||
if showPaymentReceivedDate
|
||||
div(class=`${prefix}-terms-item`)
|
||||
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
||||
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
||||
|
||||
if showCompanyLogo && companyLogoUri
|
||||
div(class=`${prefix}-logo-wrap`)
|
||||
img(src=companyLogoUri alt="Company Logo")
|
||||
|
||||
div(class=`${prefix}-addresses`)
|
||||
if showCompanyAddress
|
||||
div(class=`${prefix}-address-from`)
|
||||
div !{companyAddress}
|
||||
|
||||
if showCustomerAddress
|
||||
div(class=`${prefix}-address-to`)
|
||||
strong #{billedToLabel}
|
||||
div !{customerAddress}
|
||||
|
||||
table(class=`${prefix}-table`)
|
||||
thead
|
||||
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
|
||||
|
||||
tbody
|
||||
each line in lines
|
||||
tr
|
||||
td(class=`${prefix}-table__cell`) #{line.invoiceNumber}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.invoiceAmount}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.paidAmount}
|
||||
|
||||
div(class=`${prefix}-totals`)
|
||||
if showSubtotal
|
||||
div(class=`${prefix}-totals__item ${prefix}-totals__item--gray-border`)
|
||||
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
|
||||
div(class=`${prefix}-totals__item-amount`) #{subtotal}
|
||||
|
||||
if showTotal
|
||||
div(class=`${prefix}-totals__item ${prefix}-totals__item--dark-border`)
|
||||
div(class=`${prefix}-totals__item-label`) #{totalLabel}
|
||||
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||
@@ -1,215 +0,0 @@
|
||||
extends ../PaperTemplateLayout.pug
|
||||
|
||||
block head
|
||||
- var prefix = 'bc'
|
||||
style.
|
||||
.#{prefix}-root {
|
||||
color: #000;
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-big-title {
|
||||
font-size: 30px;
|
||||
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 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`) Receipt
|
||||
|
||||
//- Terms List
|
||||
div(class=`${prefix}-terms-list`)
|
||||
if showReceiptNumber
|
||||
div(class=`${prefix}-terms-item`)
|
||||
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
|
||||
span(class=`${prefix}-terms-item__value`)= receiptNumber
|
||||
|
||||
if showReceiptDate
|
||||
div(class=`${prefix}-terms-item`)
|
||||
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
||||
span(class=`${prefix}-terms-item__value`)= receiptDate
|
||||
|
||||
//- Company logo
|
||||
if showCompanyLogo && companyLogoUri
|
||||
div(class=`${prefix}-logo-wrap`)
|
||||
img(src=companyLogoUri alt=`Company Logo`)
|
||||
|
||||
//- Address Section
|
||||
div(class=`${prefix}-address-section`)
|
||||
if showCompanyAddress
|
||||
div(class=`${prefix}-address-from`)
|
||||
div !{companyAddress}
|
||||
|
||||
if showCustomerAddress
|
||||
div(class=`${prefix}-address-to`)
|
||||
strong #{billedToLabel}
|
||||
div !{customerAddress}
|
||||
|
||||
//- Table Section
|
||||
table(class=`${prefix}-table`)
|
||||
thead(class=`${prefix}-table__header`)
|
||||
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
|
||||
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
|
||||
|
||||
//- Totals Section
|
||||
div(class=`${prefix}-totals`)
|
||||
if showSubtotal
|
||||
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
|
||||
@@ -4,6 +4,7 @@ import { check, param, query } from 'express-validator';
|
||||
import {
|
||||
AbilitySubject,
|
||||
BillAction,
|
||||
DiscountType,
|
||||
IBillDTO,
|
||||
IBillEditDTO,
|
||||
} from '@/interfaces';
|
||||
@@ -121,11 +122,16 @@ export default class BillsController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.landed_cost')
|
||||
.optional({ nullable: true })
|
||||
@@ -144,8 +150,18 @@ export default class BillsController extends BaseController {
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
|
||||
// Attachments
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// # Discount
|
||||
check('discount_type')
|
||||
.default(DiscountType.Amount)
|
||||
.isIn([DiscountType.Amount, DiscountType.Percentage]),
|
||||
check('discount').optional({ nullable: true }).isDecimal().toFloat(),
|
||||
|
||||
// # Adjustment
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -188,6 +204,15 @@ export default class BillsController extends BaseController {
|
||||
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// # Discount
|
||||
check('discount_type')
|
||||
.default(DiscountType.Amount)
|
||||
.isIn([DiscountType.Amount, DiscountType.Percentage]),
|
||||
check('discount').optional({ nullable: true }).isDecimal().toFloat(),
|
||||
|
||||
// # Adjustment
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { check, param, query } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
AbilitySubject,
|
||||
DiscountType,
|
||||
IVendorCreditCreateDTO,
|
||||
IVendorCreditEditDTO,
|
||||
VendorCreditAction,
|
||||
@@ -170,11 +171,15 @@ export default class VendorCreditController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
@@ -183,6 +188,16 @@ export default class VendorCreditController extends BaseController {
|
||||
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// Discount.
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
// Adjustment.
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -209,11 +224,15 @@ export default class VendorCreditController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
@@ -222,6 +241,16 @@ export default class VendorCreditController extends BaseController {
|
||||
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// Discount.
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
// Adjustment.
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
AbilitySubject,
|
||||
CreditNoteAction,
|
||||
DiscountType,
|
||||
ICreditNoteEditDTO,
|
||||
ICreditNoteNewDTO,
|
||||
} from '@/interfaces';
|
||||
@@ -233,22 +234,37 @@ export default class PaymentReceivesController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
|
||||
// Attachments.
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// Pdf template id.
|
||||
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
// Discount.
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
// Adjustment.
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -471,13 +487,14 @@ export default class PaymentReceivesController extends BaseController {
|
||||
ACCEPT_TYPE.APPLICATION_PDF,
|
||||
]);
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
|
||||
const [pdfContent, filename] = await this.creditNotePdf.getCreditNotePdf(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
} else {
|
||||
|
||||
@@ -130,9 +130,18 @@ export default class PaymentReceivesController extends BaseController {
|
||||
[
|
||||
...this.paymentReceiveValidation,
|
||||
body('subject').isString().optional(),
|
||||
|
||||
body('from').isString().optional(),
|
||||
body('to').isString().optional(),
|
||||
body('body').isString().optional(),
|
||||
|
||||
body('to').isArray().exists(),
|
||||
body('to.*').isString().isEmail().optional(),
|
||||
|
||||
body('cc').isArray().optional({ nullable: true }),
|
||||
body('cc.*').isString().isEmail().optional(),
|
||||
|
||||
body('bcc').isArray().optional({ nullable: true }),
|
||||
body('bcc.*').isString().isEmail().optional(),
|
||||
|
||||
body('attach_invoice').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
this.sendPaymentReceiveByMail.bind(this),
|
||||
@@ -408,7 +417,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data = await this.paymentReceiveApplication.getPaymentReceivedState(
|
||||
@@ -470,10 +479,11 @@ export default class PaymentReceivesController extends BaseController {
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_PDF,
|
||||
ACCEPT_TYPE.APPLICATION_TEXT_HTML,
|
||||
]);
|
||||
// Response in pdf format.
|
||||
// Responds pdf format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||
const pdfContent =
|
||||
const [pdfContent, filename] =
|
||||
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
@@ -481,9 +491,18 @@ export default class PaymentReceivesController extends BaseController {
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
// Response in json format.
|
||||
// Responds html format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_TEXT_HTML === acceptType) {
|
||||
const htmlContent =
|
||||
await this.paymentReceiveApplication.getPaymentReceivedHtml(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
return res.status(200).send({ htmlContent });
|
||||
// Responds json format.
|
||||
} else {
|
||||
const paymentReceive =
|
||||
await this.paymentReceiveApplication.getPaymentReceive(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { body, check, param, query } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
AbilitySubject,
|
||||
DiscountType,
|
||||
ISaleEstimateDTO,
|
||||
SaleEstimateAction,
|
||||
SaleEstimateMailOptionsDTO,
|
||||
@@ -13,11 +14,8 @@ import DynamicListingService from '@/services/DynamicListing/DynamicListService'
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { SaleEstimatesApplication } from '@/services/Sales/Estimates/SaleEstimatesApplication';
|
||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||
|
||||
const ACCEPT_TYPE = {
|
||||
APPLICATION_PDF: 'application/pdf',
|
||||
APPLICATION_JSON: 'application/json',
|
||||
};
|
||||
@Service()
|
||||
export default class SalesEstimatesController extends BaseController {
|
||||
@Inject()
|
||||
@@ -133,8 +131,18 @@ export default class SalesEstimatesController extends BaseController {
|
||||
[
|
||||
...this.validateSpecificEstimateSchema,
|
||||
body('subject').isString().optional(),
|
||||
|
||||
body('from').isString().optional(),
|
||||
body('to').isString().optional(),
|
||||
|
||||
body('to').isArray().exists(),
|
||||
body('to.*').isString().isEmail().optional(),
|
||||
|
||||
body('cc').isArray().optional({ nullable: true }),
|
||||
body('cc.*').isString().isEmail().optional(),
|
||||
|
||||
body('bcc').isArray().optional({ nullable: true }),
|
||||
body('bcc.*').isString().isEmail().optional(),
|
||||
|
||||
body('body').isString().optional(),
|
||||
body('attach_invoice').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
@@ -143,10 +151,10 @@ export default class SalesEstimatesController extends BaseController {
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:id/mail',
|
||||
'/:id/mail/state',
|
||||
[...this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getSaleEstimateMail.bind(this)),
|
||||
asyncMiddleware(this.getSaleEstimateMailState.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
return router;
|
||||
@@ -172,13 +180,18 @@ export default class SalesEstimatesController extends BaseController {
|
||||
check('entries').exists().isArray({ min: 1 }),
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
@@ -188,11 +201,21 @@ export default class SalesEstimatesController extends BaseController {
|
||||
check('terms_conditions').optional().trim(),
|
||||
check('send_to_email').optional().trim(),
|
||||
|
||||
// # Attachments
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// Pdf template id.
|
||||
// # Pdf template id.
|
||||
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
// # Discount
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.default(DiscountType.Amount)
|
||||
.isIn([DiscountType.Amount, DiscountType.Percentage]),
|
||||
|
||||
// # Adjustment
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -395,20 +418,30 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_PDF,
|
||||
ACCEPT_TYPE.APPLICATION_TEXT_HTML,
|
||||
]);
|
||||
// Retrieves estimate in pdf format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
const [pdfContent, filename] =
|
||||
await this.saleEstimatesApplication.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
// Retrieves estimates in json format.
|
||||
} else {
|
||||
} else if (ACCEPT_TYPE.APPLICATION_TEXT_HTML === acceptType) {
|
||||
const htmlContent =
|
||||
await this.saleEstimatesApplication.getSaleEstimateHtml(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({ htmlContent });
|
||||
} else if (ACCEPT_TYPE.APPLICATION_JSON) {
|
||||
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
@@ -533,18 +566,18 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private getSaleEstimateMail = async (
|
||||
req: Request,
|
||||
private getSaleEstimateMailState = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const { tenantId } = req;
|
||||
const { id: invoiceId } = req.params;
|
||||
const { id: estimateId } = req.params;
|
||||
|
||||
try {
|
||||
const data = await this.saleEstimatesApplication.getSaleEstimateMail(
|
||||
const data = await this.saleEstimatesApplication.getSaleEstimateMailState(
|
||||
tenantId,
|
||||
invoiceId
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({ data });
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
SaleInvoiceAction,
|
||||
AbilitySubject,
|
||||
SendInvoiceMailDTO,
|
||||
DiscountType,
|
||||
} from '@/interfaces';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
|
||||
@@ -179,10 +180,21 @@ export default class SaleInvoicesController extends BaseController {
|
||||
'/:id/mail',
|
||||
[
|
||||
...this.specificSaleInvoiceValidation,
|
||||
body('subject').isString().optional(),
|
||||
|
||||
body('subject').isString().optional({ nullable: true }),
|
||||
body('message').isString().optional({ nullable: true }),
|
||||
|
||||
body('from').isString().optional(),
|
||||
body('to').isString().optional(),
|
||||
body('body').isString().optional(),
|
||||
|
||||
body('to').isArray().exists(),
|
||||
body('to.*').isString().isEmail().optional(),
|
||||
|
||||
body('cc').isArray().optional({ nullable: true }),
|
||||
body('cc.*').isString().isEmail().optional(),
|
||||
|
||||
body('bcc').isArray().optional({ nullable: true }),
|
||||
body('bcc.*').isString().isEmail().optional(),
|
||||
|
||||
body('attach_invoice').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
this.validationResult,
|
||||
@@ -190,7 +202,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:id/mail',
|
||||
'/:id/mail/state',
|
||||
[...this.specificSaleInvoiceValidation],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
|
||||
@@ -231,6 +243,10 @@ export default class SaleInvoicesController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.tax_code')
|
||||
.optional({ nullable: true })
|
||||
@@ -270,6 +286,16 @@ export default class SaleInvoicesController extends BaseController {
|
||||
check('payment_methods').optional({ nullable: true }).isArray(),
|
||||
check('payment_methods.*.payment_integration_id').exists().toInt(),
|
||||
check('payment_methods.*.enable').exists().isBoolean(),
|
||||
|
||||
// Discount
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
// Adjustments
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -438,19 +464,28 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_PDF,
|
||||
ACCEPT_TYPE.APPLICATION_TEXT_HTML,
|
||||
]);
|
||||
// Retrieves invoice in pdf format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Retrieves invoice in PDF format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||
const [pdfContent, filename] =
|
||||
await this.saleInvoiceApplication.saleInvoicePdf(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
// Retrieves invoice in json format.
|
||||
// Retrieves invoice in html json format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_TEXT_HTML === acceptType) {
|
||||
const htmlContent = await this.saleInvoiceApplication.saleInvoiceHtml(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
return res.status(200).send({ htmlContent });
|
||||
} else {
|
||||
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
|
||||
tenantId,
|
||||
@@ -776,7 +811,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default mail options of the given sale invoice.
|
||||
* Retrieves the mail state of the given sale invoice.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
@@ -790,7 +825,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const { id: invoiceId } = req.params;
|
||||
|
||||
try {
|
||||
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
|
||||
const data = await this.saleInvoiceApplication.getSaleInvoiceMailState(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
|
||||
import { AbilitySubject, DiscountType, SaleReceiptAction } from '@/interfaces';
|
||||
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
|
||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||
|
||||
@@ -56,8 +56,18 @@ export default class SalesReceiptsController extends BaseController {
|
||||
[
|
||||
...this.specificReceiptValidationSchema,
|
||||
body('subject').isString().optional(),
|
||||
|
||||
body('from').isString().optional(),
|
||||
body('to').isString().optional(),
|
||||
|
||||
body('to').isArray().exists(),
|
||||
body('to.*').isString().isEmail().optional(),
|
||||
|
||||
body('cc').isArray().optional({ nullable: true }),
|
||||
body('cc.*').isString().isEmail().optional(),
|
||||
|
||||
body('bcc').isArray().optional({ nullable: true }),
|
||||
body('bcc.*').isString().isEmail().optional(),
|
||||
|
||||
body('body').isString().optional(),
|
||||
body('attach_receipt').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
@@ -113,7 +123,7 @@ export default class SalesReceiptsController extends BaseController {
|
||||
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||
asyncMiddleware(this.getSaleReceiptState.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
);
|
||||
router.get(
|
||||
'/:id',
|
||||
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||
@@ -148,12 +158,17 @@ export default class SalesReceiptsController extends BaseController {
|
||||
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('entries.*.discount_type')
|
||||
.default(DiscountType.Percentage)
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
@@ -166,8 +181,18 @@ export default class SalesReceiptsController extends BaseController {
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
// Pdf template id.
|
||||
// # Pdf template
|
||||
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
// # Discount
|
||||
check('discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('discount_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isIn([DiscountType.Percentage, DiscountType.Amount]),
|
||||
|
||||
// # Adjustment
|
||||
check('adjustment').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -353,19 +378,28 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const acceptType = accept.types([
|
||||
ACCEPT_TYPE.APPLICATION_JSON,
|
||||
ACCEPT_TYPE.APPLICATION_PDF,
|
||||
ACCEPT_TYPE.APPLICATION_TEXT_HTML,
|
||||
]);
|
||||
// Retrieves receipt in pdf format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
const [pdfContent, filename] =
|
||||
await this.saleReceiptsApplication.getSaleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
// Retrieves receipt in json format.
|
||||
} else if (ACCEPT_TYPE.APPLICATION_TEXT_HTML === acceptType) {
|
||||
const htmlContent = await this.saleReceiptsApplication.getSaleReceiptHtml(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
res.send({ htmlContent });
|
||||
} else {
|
||||
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
|
||||
tenantId,
|
||||
@@ -505,7 +539,7 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const { id: receiptId } = req.params;
|
||||
|
||||
try {
|
||||
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
|
||||
const data = await this.saleReceiptsApplication.getSaleReceiptMailState(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
|
||||
@@ -102,12 +102,13 @@ export class PublicSharableLinkController extends BaseController {
|
||||
const { paymentLinkId } = req.params;
|
||||
|
||||
try {
|
||||
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
||||
paymentLinkId
|
||||
);
|
||||
const [pdfContent, filename] =
|
||||
await this.paymentLinkApp.getPaymentLinkInvoicePdf(paymentLinkId);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
} catch (error) {
|
||||
|
||||
@@ -30,7 +30,6 @@ export class ShareLinkController extends BaseController {
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.generateShareLink.bind(this))
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -53,7 +52,6 @@ export class ShareLinkController extends BaseController {
|
||||
const link = await this.generateShareLinkService.generatePaymentLink(
|
||||
tenantId,
|
||||
transactionId,
|
||||
transactionType,
|
||||
publicity,
|
||||
expiryDate
|
||||
);
|
||||
|
||||
@@ -2,14 +2,30 @@ 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_INVOICE_VIEWED = 'Sale invoice viewed';
|
||||
export const SALE_INVOICE_PDF_VIEWED = 'Sale invoice PDF viewed';
|
||||
export const SALE_INVOICE_MAIL_SENT = 'Sale invoice mail sent';
|
||||
export const SALE_INVOICE_MAIL_REMINDER_SENT =
|
||||
'Sale invoice reminder mail sent';
|
||||
|
||||
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 SALE_ESTIMATE_PDF_VIEWED = 'Sale estimate PDF viewed';
|
||||
export const SALE_ESTIMATE_VIEWED = 'Sale estimate viewed';
|
||||
export const SALE_ESTIMATE_MAIL_SENT = 'Sale estimate mail sent';
|
||||
|
||||
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 PAYMENT_RECEIVED_PDF_VIEWED = 'Payment received PDF viewed';
|
||||
export const PAYMENT_RECEIVED_MAIL_SENT = 'Payment received mail sent';
|
||||
|
||||
export const SALE_RECEIPT_PDF_VIEWED = 'Sale credit PDF viewed';
|
||||
export const SALE_RECEIPT_MAIL_SENT = 'Sale credit mail sent';
|
||||
|
||||
export const CREDIT_NOTE_PDF_VIEWED = 'Credit note PDF viewed';
|
||||
export const CREDIT_NOTE_MAIL_SENT = 'Credit note mail sent';
|
||||
|
||||
export const BILL_CREATED = 'Bill created';
|
||||
export const BILL_EDITED = 'Bill edited';
|
||||
@@ -26,10 +42,12 @@ 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 ACCOUNT_VIEWED = 'Account viewed';
|
||||
|
||||
export const ITEM_EVENT_CREATED = 'Item created';
|
||||
export const ITEM_EVENT_EDITED = 'Item edited';
|
||||
export const ITEM_EVENT_DELETED = 'Item deleted';
|
||||
export const ITEM_EVENT_VIEWED = 'Item viewed';
|
||||
|
||||
export const AUTH_SIGNED_UP = 'Auth Signed-up';
|
||||
export const AUTH_RESET_PASSWORD = 'Auth reset password';
|
||||
@@ -37,6 +55,8 @@ 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 SUBSCRIPTION_PAYMENT_SUCCEED = 'Subscription payment succeed';
|
||||
export const SUBSCRIPTION_PAYMENT_FAILED = 'Subscription payment failed';
|
||||
|
||||
export const CUSTOMER_CREATED = 'Customer created';
|
||||
export const CUSTOMER_EDITED = 'Customer edited';
|
||||
@@ -79,7 +99,8 @@ 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';
|
||||
export const STRIPE_INTEGRAION_CONNECTED =
|
||||
'Stripe integration oauth2 connected';
|
||||
|
||||
// # Event Groups
|
||||
export const ACCOUNT_GROUP = 'Account';
|
||||
@@ -89,3 +110,21 @@ export const SALE_GROUP = 'Sale';
|
||||
export const PAYMENT_GROUP = 'Payment';
|
||||
export const BILL_GROUP = 'Bill';
|
||||
export const EXPENSE_GROUP = 'Expense';
|
||||
|
||||
// # Reports
|
||||
export const BALANCE_SHEET_VIEWED = 'Balance sheet viewed';
|
||||
export const TRIAL_BALANCE_SHEET_VIEWED = 'Trial balance sheet viewed';
|
||||
export const PROFIT_LOSS_SHEET_VIEWED = 'Profit loss sheet viewed';
|
||||
export const CASHFLOW_STATEMENT_VIEWED = 'Cashflow statement viewed';
|
||||
export const GENERAL_LEDGER_VIEWED = 'General ledger viewed';
|
||||
export const JOURNAL_VIEWED = 'Journal viewed';
|
||||
export const RECEIVABLE_AGING_VIEWED = 'Receivable aging viewed';
|
||||
export const PAYABLE_AGING_VIEWED = 'Payable aging viewed';
|
||||
export const CUSTOMER_BALANCE_SUMMARY_VIEWED =
|
||||
'Customer balance summary viewed';
|
||||
export const VENDOR_BALANCE_SUMMARY_VIEWED = 'Vendor balance summary viewed';
|
||||
export const INVENTORY_VALUATION_VIEWED = 'Inventory valuation viewed';
|
||||
export const CUSTOMER_TRANSACTIONS_VIEWED = 'Customer transactions viewed';
|
||||
export const VENDOR_TRANSACTIONS_VIEWED = 'Vendor transactions viewed';
|
||||
export const SALES_BY_ITEM_VIEWED = 'Sales by item viewed';
|
||||
export const PURCHASES_BY_ITEM_VIEWED = 'Purchases by item viewed';
|
||||
|
||||
@@ -129,6 +129,7 @@ export const ACCOUNT_TYPES = [
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.table('items_entries', (table) => {
|
||||
table.decimal('quantity', 13, 3).alter();
|
||||
})
|
||||
.table('inventory_transactions', (table) => {
|
||||
table.decimal('quantity', 13, 3).alter();
|
||||
})
|
||||
.table('inventory_cost_lot_tracker', (table) => {
|
||||
table.decimal('quantity', 13, 3).alter();
|
||||
})
|
||||
.table('items', (table) => {
|
||||
table.decimal('quantityOnHand', 13, 3).alter();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function (knex) {
|
||||
return knex.schema
|
||||
.table('items_entries', (table) => {
|
||||
table.integer('quantity').alter();
|
||||
})
|
||||
.table('inventory_transactions', (table) => {
|
||||
table.integer('quantity').alter();
|
||||
})
|
||||
.table('inventory_cost_lot_tracker', (table) => {
|
||||
table.integer('quantity').alter();
|
||||
})
|
||||
.table('items', (table) => {
|
||||
table.integer('quantityOnHand').alter();
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.alterTable('sales_invoices', (table) => {
|
||||
table.decimal('discount', 10, 2).nullable().after('credited_amount');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable('sale_invoices', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('sales_estimates', (table) => {
|
||||
table.decimal('discount', 10, 2).nullable().after('amount');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('sales_estimates', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('sales_receipts', (table) => {
|
||||
table.decimal('discount', 10, 2).nullable().after('amount');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('sales_receipts', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('bills', (table) => {
|
||||
// Discount.
|
||||
table.decimal('discount', 10, 2).nullable().after('amount');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
|
||||
// Adjustment.
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('bills', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('credit_notes', (table) => {
|
||||
table.decimal('discount', 10, 2).nullable().after('exchange_rate');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('credit_notes', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.alterTable('vendor_credits', (table) => {
|
||||
table.decimal('discount', 10, 2).nullable().after('amount');
|
||||
table.string('discount_type').nullable().after('discount');
|
||||
table.decimal('adjustment', 10, 2).nullable().after('discount_type');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.alterTable('vendor_credits', (table) => {
|
||||
table.dropColumn('discount');
|
||||
table.dropColumn('discount_type');
|
||||
table.dropColumn('adjustment');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.alterTable('items_entries', (table) => {
|
||||
table.string('discount_type').defaultTo('percentage').after('discount');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable('items_entries', (table) => {
|
||||
table.dropColumn('discount_type');
|
||||
});
|
||||
};
|
||||
@@ -1,3 +1,14 @@
|
||||
export const OtherExpensesAccount = {
|
||||
name: 'Other Expenses',
|
||||
slug: 'other-expenses',
|
||||
account_type: 'other-expense',
|
||||
code: '40011',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
};
|
||||
|
||||
export const TaxPayableAccount = {
|
||||
name: 'Tax Payable',
|
||||
slug: 'tax-payable',
|
||||
@@ -39,8 +50,38 @@ export const StripeClearingAccount = {
|
||||
code: '100020',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
}
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const DiscountExpenseAccount = {
|
||||
name: 'Discount',
|
||||
slug: 'discount',
|
||||
account_type: 'other-income',
|
||||
code: '40008',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const PurchaseDiscountAccount = {
|
||||
name: 'Purchase Discount',
|
||||
slug: 'purchase-discount',
|
||||
account_type: 'other-expense',
|
||||
code: '40009',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const OtherChargesAccount = {
|
||||
name: 'Other Charges',
|
||||
slug: 'other-charges',
|
||||
account_type: 'other-income',
|
||||
code: '40010',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -231,17 +272,7 @@ export default [
|
||||
},
|
||||
|
||||
// Expenses
|
||||
{
|
||||
name: 'Other Expenses',
|
||||
slug: 'other-expenses',
|
||||
account_type: 'other-expense',
|
||||
parent_account_id: null,
|
||||
code: '40001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
OtherExpensesAccount,
|
||||
{
|
||||
name: 'Cost of Goods Sold',
|
||||
slug: 'cost-of-goods-sold',
|
||||
@@ -358,4 +389,7 @@ export default [
|
||||
},
|
||||
UnearnedRevenueAccount,
|
||||
PrepardExpenses,
|
||||
DiscountExpenseAccount,
|
||||
PurchaseDiscountAccount,
|
||||
OtherChargesAccount,
|
||||
];
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IDynamicListFilterDTO } from './DynamicFilter';
|
||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||
import { IBillLandedCost } from './LandedCost';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
import { DiscountType } from './SaleInvoice';
|
||||
|
||||
export interface IBillDTO {
|
||||
vendorId: number;
|
||||
@@ -22,6 +23,13 @@ export interface IBillDTO {
|
||||
projectId?: number;
|
||||
isInclusiveTax?: boolean;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
// # Discount
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
|
||||
// # Adjustment
|
||||
adjustment?: number;
|
||||
}
|
||||
|
||||
export interface IBillEditDTO {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import { IDynamicListFilter, IItemEntry } from '@/interfaces';
|
||||
import { DiscountType, IDynamicListFilter, IItemEntry } from '@/interfaces';
|
||||
import { ILedgerEntry } from './Ledger';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
|
||||
@@ -23,6 +23,9 @@ export interface ICreditNoteNewDTO {
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
adjustment?: number;
|
||||
}
|
||||
|
||||
export interface ICreditNoteEditDTO {
|
||||
|
||||
@@ -4,4 +4,5 @@ export const ACCEPT_TYPE = {
|
||||
APPLICATION_JSON_TABLE: 'application/json+table',
|
||||
APPLICATION_XLSX: 'application/xlsx',
|
||||
APPLICATION_CSV: 'application/csv',
|
||||
APPLICATION_TEXT_HTML: 'application/json+html',
|
||||
};
|
||||
|
||||
@@ -13,14 +13,17 @@ export interface IItemEntry {
|
||||
|
||||
itemId: number;
|
||||
description: string;
|
||||
discountType?: string;
|
||||
discount: number;
|
||||
quantity: number;
|
||||
rate: number;
|
||||
amount: number;
|
||||
|
||||
total: number;
|
||||
amountInclusingTax: number;
|
||||
amountExludingTax: number;
|
||||
totalExcludingTax?: number;
|
||||
|
||||
subtotalInclusingTax: number;
|
||||
subtotalExcludingTax: number;
|
||||
discountAmount: number;
|
||||
|
||||
landedCost: number;
|
||||
|
||||
@@ -30,18 +30,15 @@ export interface AddressItem {
|
||||
}
|
||||
|
||||
export interface CommonMailOptions {
|
||||
toAddresses: AddressItem[];
|
||||
fromAddresses: AddressItem[];
|
||||
from: string;
|
||||
to: string | string[];
|
||||
from: Array<string>;
|
||||
subject: string;
|
||||
body: string;
|
||||
data?: Record<string, any>;
|
||||
message: string;
|
||||
to: Array<string>;
|
||||
cc?: Array<string>;
|
||||
bcc?: Array<string>;
|
||||
formatArgs?: Record<string, any>;
|
||||
toOptions: Array<AddressItem>;
|
||||
fromOptions: Array<AddressItem>;
|
||||
}
|
||||
|
||||
export interface CommonMailOptionsDTO {
|
||||
to?: string | string[];
|
||||
from?: string;
|
||||
subject?: string;
|
||||
body?: string;
|
||||
}
|
||||
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {}
|
||||
|
||||
@@ -177,7 +177,9 @@ export type IPaymentReceiveGLCommonEntry = Pick<
|
||||
| 'branchId'
|
||||
>;
|
||||
|
||||
export interface PaymentReceiveMailOpts extends CommonMailOptions {}
|
||||
export interface PaymentReceiveMailOpts extends CommonMailOptions {
|
||||
attachPdf?: boolean;
|
||||
}
|
||||
|
||||
export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {}
|
||||
|
||||
@@ -239,7 +241,6 @@ export interface PaymentReceivedPdfTemplateAttributes {
|
||||
paymentReceivedDateLabel: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IPaymentReceivedState {
|
||||
defaultTemplateId: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
||||
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
import { DiscountType } from './SaleInvoice';
|
||||
|
||||
export interface ISaleEstimate {
|
||||
id?: number;
|
||||
@@ -24,6 +25,14 @@ export interface ISaleEstimate {
|
||||
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
|
||||
total?: number;
|
||||
totalLocal?: number;
|
||||
|
||||
discountAmount?: number;
|
||||
discountPercentage?: number | null;
|
||||
|
||||
adjustment?: number;
|
||||
}
|
||||
export interface ISaleEstimateDTO {
|
||||
customerId: number;
|
||||
@@ -40,6 +49,13 @@ export interface ISaleEstimateDTO {
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
// # Discount
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
|
||||
// # Adjustment
|
||||
adjustment?: number;
|
||||
}
|
||||
|
||||
export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -80,6 +80,18 @@ export interface ISaleInvoice {
|
||||
pdfTemplateId?: number;
|
||||
|
||||
paymentMethods?: Array<PaymentIntegrationTransactionLink>;
|
||||
|
||||
adjustment?: number;
|
||||
adjustmentLocal?: number | null;
|
||||
|
||||
discount?: number;
|
||||
discountAmount?: number;
|
||||
discountAmountLocal?: number | null;
|
||||
}
|
||||
|
||||
export enum DiscountType {
|
||||
Percentage = 'percentage',
|
||||
Amount = 'amount',
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceDTO {
|
||||
@@ -102,6 +114,13 @@ export interface ISaleInvoiceDTO {
|
||||
isInclusiveTax?: boolean;
|
||||
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
// # Discount
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
|
||||
// # Adjustments
|
||||
adjustments?: string;
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO {
|
||||
@@ -234,7 +253,32 @@ export enum SaleInvoiceAction {
|
||||
}
|
||||
|
||||
export interface SaleInvoiceMailOptions extends CommonMailOptions {
|
||||
attachInvoice: boolean;
|
||||
attachInvoice?: boolean;
|
||||
formatArgs?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SaleInvoiceMailState extends SaleInvoiceMailOptions {
|
||||
invoiceNo: string;
|
||||
|
||||
invoiceDate: string;
|
||||
invoiceDateFormatted: string;
|
||||
|
||||
dueDate: string;
|
||||
dueDateFormatted: string;
|
||||
|
||||
total: number;
|
||||
totalFormatted: string;
|
||||
|
||||
subtotal: number;
|
||||
subtotalFormatted: number;
|
||||
|
||||
companyName: string;
|
||||
companyLogoUri: string;
|
||||
|
||||
customerName: string;
|
||||
|
||||
// # Invoice entries
|
||||
entries?: Array<{ label: string; total: string; quantity: string | number }>;
|
||||
}
|
||||
|
||||
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
|
||||
@@ -251,6 +295,7 @@ export interface ISaleInvoiceMailSend {
|
||||
tenantId: number;
|
||||
saleInvoiceId: number;
|
||||
messageOptions: SendInvoiceMailDTO;
|
||||
formattedMessageOptions: SaleInvoiceMailOptions;
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceMailSent {
|
||||
@@ -312,21 +357,22 @@ export interface InvoicePdfTemplateAttributes {
|
||||
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;
|
||||
|
||||
// Due Amount
|
||||
dueAmount: string;
|
||||
showDueAmount: boolean;
|
||||
dueAmountLabel: string;
|
||||
|
||||
termsConditionsLabel: string;
|
||||
showTermsConditions: boolean;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
import { IItemEntry } from './ItemEntry';
|
||||
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
import { DiscountType } from './SaleInvoice';
|
||||
|
||||
export interface ISaleReceipt {
|
||||
id?: number;
|
||||
@@ -27,6 +28,20 @@ export interface ISaleReceipt {
|
||||
|
||||
localAmount?: number;
|
||||
entries?: IItemEntry[];
|
||||
|
||||
subtotal?: number;
|
||||
subtotalLocal?: number;
|
||||
|
||||
total?: number;
|
||||
totalLocal?: number;
|
||||
|
||||
discountAmount: number;
|
||||
discountPercentage?: number | null;
|
||||
|
||||
adjustment?: number;
|
||||
adjustmentLocal?: number | null;
|
||||
|
||||
discountAmountLocal?: number | null;
|
||||
}
|
||||
|
||||
export interface ISalesReceiptsFilter {
|
||||
@@ -47,6 +62,11 @@ export interface ISaleReceiptDTO {
|
||||
entries: any[];
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
|
||||
adjustment?: number;
|
||||
}
|
||||
|
||||
export interface ISalesReceiptsService {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces';
|
||||
import { DiscountType, IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
|
||||
@@ -63,6 +63,11 @@ export interface IVendorCreditDTO {
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
discount?: number;
|
||||
discountType?: DiscountType;
|
||||
|
||||
adjustment?: number;
|
||||
}
|
||||
|
||||
export interface IVendorCreditCreateDTO extends IVendorCreditDTO {}
|
||||
|
||||
@@ -9,6 +9,9 @@ export default class Mail {
|
||||
subject: string = '';
|
||||
content: string = '';
|
||||
to: string | string[];
|
||||
cc: string | string[];
|
||||
bcc: string | string[];
|
||||
replyTo: string | string[];
|
||||
from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`;
|
||||
data: { [key: string]: string | number };
|
||||
attachments: IMailAttachment[];
|
||||
@@ -20,9 +23,12 @@ export default class Mail {
|
||||
return {
|
||||
to: this.to,
|
||||
from: this.from,
|
||||
cc: this.cc,
|
||||
bcc: this.bcc,
|
||||
subject: this.subject,
|
||||
html: this.html,
|
||||
attachments: this.attachments,
|
||||
replyTo: this.replyTo,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +66,21 @@ export default class Mail {
|
||||
return this;
|
||||
}
|
||||
|
||||
setCC(cc: string | string[]) {
|
||||
this.cc = cc;
|
||||
return this;
|
||||
}
|
||||
|
||||
setBCC(bcc: string | string[]) {
|
||||
this.bcc = bcc;
|
||||
return this;
|
||||
}
|
||||
|
||||
setReplyTo(replyTo: string | string[]) {
|
||||
this.replyTo = replyTo;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets from address to the mail.
|
||||
* @param {string} from
|
||||
@@ -72,7 +93,7 @@ export default class Mail {
|
||||
|
||||
/**
|
||||
* Set attachments to the mail.
|
||||
* @param {IMailAttachment[]} attachments
|
||||
* @param {IMailAttachment[]} attachments
|
||||
* @returns {Mail}
|
||||
*/
|
||||
setAttachments(attachments: IMailAttachment[]) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Model, raw, mixin } from 'objection';
|
||||
import { castArray, difference } from 'lodash';
|
||||
import { castArray, defaultTo, difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import BillSettings from './Bill.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class Bill extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
@@ -21,6 +23,11 @@ export default class Bill extends mixin(TenantModel, [
|
||||
public taxAmountWithheld: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
@@ -47,6 +54,13 @@ export default class Bill extends mixin(TenantModel, [
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
'amountLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'adjustmentLocal',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'subtotalExludingTax',
|
||||
@@ -98,14 +112,53 @@ export default class Bill extends mixin(TenantModel, [
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal
|
||||
: this.subtotal + this.taxAmountWithheld;
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return R.compose(
|
||||
R.add(adjustmentAmount),
|
||||
R.subtract(R.__, this.discountAmount),
|
||||
R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld))
|
||||
)(this.subtotal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,12 +5,20 @@ import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/CreditNotes/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import CreditNoteMeta from './CreditNote.Meta';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class CreditNote extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public openedAt: Date;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -35,8 +43,21 @@ export default class CreditNote extends mixin(TenantModel, [
|
||||
'isPublished',
|
||||
'isOpen',
|
||||
'isClosed',
|
||||
|
||||
'creditsRemaining',
|
||||
'creditsUsed',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'adjustmentLocal',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -48,6 +69,72 @@ export default class CreditNote extends mixin(TenantModel, [
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.subtotal * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.subtotal - this.discountAmount + this.adjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is draft.
|
||||
* @returns {boolean}
|
||||
@@ -176,6 +263,7 @@ export default class CreditNote extends mixin(TenantModel, [
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -266,6 +354,18 @@ export default class CreditNote extends mixin(TenantModel, [
|
||||
query.where('model_ref', 'CreditNote');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'credit_notes.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ export default {
|
||||
name: 'item.field.note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
category: {
|
||||
categoryId: {
|
||||
name: 'item.field.category',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'ItemCategory',
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
// Subtotal (qty * rate) (tax inclusive)
|
||||
// Subtotal Tax Exclusive (Subtotal - Tax Amount)
|
||||
// Discount (Is percentage ? amount * discount : discount)
|
||||
// Total (Subtotal - Discount)
|
||||
|
||||
export default class ItemEntry extends TenantModel {
|
||||
public taxRate: number;
|
||||
@@ -8,7 +14,7 @@ export default class ItemEntry extends TenantModel {
|
||||
public quantity: number;
|
||||
public rate: number;
|
||||
public isInclusiveTax: number;
|
||||
|
||||
public discountType: DiscountType;
|
||||
/**
|
||||
* Table name.
|
||||
* @returns {string}
|
||||
@@ -31,10 +37,24 @@ export default class ItemEntry extends TenantModel {
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
// Amount (qty * rate)
|
||||
'amount',
|
||||
|
||||
'taxAmount',
|
||||
'amountExludingTax',
|
||||
'amountInclusingTax',
|
||||
|
||||
// Subtotal (qty * rate) + (tax inclusive)
|
||||
'subtotalInclusingTax',
|
||||
|
||||
// Subtotal Tax Exclusive (Subtotal - Tax Amount)
|
||||
'subtotalExcludingTax',
|
||||
|
||||
// Subtotal (qty * rate) + (tax inclusive)
|
||||
'subtotal',
|
||||
|
||||
// Discount (Is percentage ? amount * discount : discount)
|
||||
'discountAmount',
|
||||
|
||||
// Total (Subtotal - Discount)
|
||||
'total',
|
||||
];
|
||||
}
|
||||
@@ -45,7 +65,15 @@ export default class ItemEntry extends TenantModel {
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.amountInclusingTax;
|
||||
return this.subtotal - this.discountAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total (excluding tax).
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalExcludingTax() {
|
||||
return this.subtotalExcludingTax - this.discountAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,19 +85,27 @@ export default class ItemEntry extends TenantModel {
|
||||
return this.quantity * this.rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal amount (tax inclusive).
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.subtotalInclusingTax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry amount including tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountInclusingTax() {
|
||||
get subtotalInclusingTax() {
|
||||
return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry amount excluding tax.
|
||||
* Subtotal amount (tax exclusive).
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountExludingTax() {
|
||||
get subtotalExcludingTax() {
|
||||
return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount;
|
||||
}
|
||||
|
||||
@@ -78,7 +114,9 @@ export default class ItemEntry extends TenantModel {
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.amount * (this.discount / 100);
|
||||
return this.discountType === DiscountType.Percentage
|
||||
? this.amount * (this.discount / 100)
|
||||
: this.discount;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ export default class PaymentReceive extends mixin(TenantModel, [
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
amount!: number;
|
||||
paymentAmount!: number;
|
||||
exchangeRate!: number;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -29,7 +33,7 @@ export default class PaymentReceive extends mixin(TenantModel, [
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount'];
|
||||
return ['localAmount', 'total'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,6 +44,14 @@ export default class PaymentReceive extends mixin(TenantModel, [
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment receive total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.paymentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resourcable model.
|
||||
*/
|
||||
@@ -57,6 +69,7 @@ export default class PaymentReceive extends mixin(TenantModel, [
|
||||
const Account = require('models/Account');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
@@ -131,6 +144,18 @@ export default class PaymentReceive extends mixin(TenantModel, [
|
||||
query.where('model_ref', 'PaymentReceive');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment received may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'payment_receives.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { getUploadedObjectUri } from '@/services/Attachments/utils';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class PdfTemplate extends TenantModel {
|
||||
public readonly attributes: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -47,7 +50,17 @@ export class PdfTemplate extends TenantModel {
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
return ['companyLogoUri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the company logo uri.
|
||||
* @returns {string}
|
||||
*/
|
||||
get companyLogoUri() {
|
||||
return this.attributes?.companyLogoKey
|
||||
? getUploadedObjectUri(this.attributes.companyLogoKey)
|
||||
: '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,12 +7,30 @@ import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Estimates/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
export default class SaleEstimate extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
public expirationDate!: string;
|
||||
public deliveredAt!: string | null;
|
||||
public approvedAt!: string | null;
|
||||
public rejectedAt!: string | null;
|
||||
|
||||
public convertedToInvoiceId!: number | null;
|
||||
public convertedToInvoiceAt!: string | null;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -33,6 +51,12 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
'discountAmount',
|
||||
'discountPercentage',
|
||||
'total',
|
||||
'totalLocal',
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'isDelivered',
|
||||
'isExpired',
|
||||
'isConvertedToInvoice',
|
||||
@@ -49,6 +73,60 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.localAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage
|
||||
? this.discount
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return this.subtotal - this.discountAmount + adjustmentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale estimate converted to sale invoice.
|
||||
* @return {boolean}
|
||||
@@ -184,6 +262,7 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
||||
const Branch = require('models/Branch');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const Document = require('models/Document');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
@@ -252,6 +331,18 @@ export default class SaleEstimate extends mixin(TenantModel, [
|
||||
query.where('model_ref', 'SaleEstimate');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale estimate may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'sales_estimates.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import { castArray, takeWhile } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { castArray, defaultTo, takeWhile } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
@@ -7,6 +8,7 @@ import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class SaleInvoice extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
@@ -23,6 +25,9 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
||||
public writtenoffAt: Date;
|
||||
public dueDate: Date;
|
||||
public deliveredAt: Date;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number | null;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
@@ -67,10 +72,15 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
||||
'subtotalExludingTax',
|
||||
|
||||
'taxAmountWithheldLocal',
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'writtenoffAmountLocal',
|
||||
'adjustmentLocal',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -125,14 +135,52 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local discount amount.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal(): number | null {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal
|
||||
: this.subtotal + this.taxAmountWithheld;
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return R.compose(
|
||||
R.add(adjustmentAmount),
|
||||
R.subtract(R.__, this.discountAmount),
|
||||
R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld))
|
||||
)(this.subtotal);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -604,7 +652,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
||||
join: {
|
||||
from: 'sales_invoices.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,12 +5,23 @@ import SaleReceiptSettings from './SaleReceipt.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
export default class SaleReceipt extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public closedAt: Date;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -29,7 +40,28 @@ export default class SaleReceipt extends mixin(TenantModel, [
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount', 'isClosed', 'isDraft'];
|
||||
return [
|
||||
'localAmount',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'adjustment',
|
||||
'adjustmentLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'paid',
|
||||
'paidLocal',
|
||||
|
||||
'isClosed',
|
||||
'isDraft',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,6 +72,90 @@ export default class SaleReceipt extends mixin(TenantModel, [
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.localAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return this.subtotal - this.discountAmount + adjustmentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt paid amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get paid() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt paid amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get paidLocal() {
|
||||
return this.paid * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the sale receipt closed.
|
||||
* @return {boolean}
|
||||
|
||||
@@ -11,6 +11,12 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
) {
|
||||
id!: number;
|
||||
date!: Date | string;
|
||||
|
||||
/**
|
||||
* Transaction amount.
|
||||
* Negative represents to spending and positive to deposit/card charge.
|
||||
* @param {number}
|
||||
*/
|
||||
amount!: number;
|
||||
categorized!: boolean;
|
||||
accountId!: number;
|
||||
|
||||
@@ -6,26 +6,26 @@ import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/VendorCredits/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import VendorCreditMeta from './VendorCredit.Meta';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class VendorCredit extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public openedAt: Date;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'vendor_credits';
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit amount in local currency.
|
||||
* @returns {number}
|
||||
@@ -34,6 +34,72 @@ export default class VendorCredit extends mixin(TenantModel, [
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.subtotal * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.subtotal - this.discountAmount + this.adjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
@@ -120,7 +186,24 @@ export default class VendorCredit extends mixin(TenantModel, [
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isDraft', 'isPublished', 'isOpen', 'isClosed', 'creditsRemaining'];
|
||||
return [
|
||||
'isDraft',
|
||||
'isPublished',
|
||||
'isOpen',
|
||||
'isClosed',
|
||||
|
||||
'creditsRemaining',
|
||||
'localAmount',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'adjustmentLocal',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,11 @@ import TenantRepository from '@/repositories/TenantRepository';
|
||||
import { IAccount } from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
DiscountExpenseAccount,
|
||||
OtherChargesAccount,
|
||||
OtherExpensesAccount,
|
||||
PrepardExpenses,
|
||||
PurchaseDiscountAccount,
|
||||
StripeClearingAccount,
|
||||
TaxPayableAccount,
|
||||
UnearnedRevenueAccount,
|
||||
@@ -188,9 +192,9 @@ export default class AccountRepository extends TenantRepository {
|
||||
|
||||
/**
|
||||
* Finds or creates the unearned revenue.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateUnearnedRevenue(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
@@ -219,9 +223,9 @@ export default class AccountRepository extends TenantRepository {
|
||||
|
||||
/**
|
||||
* Finds or creates the prepard expenses account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreatePrepardExpenses(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
@@ -249,12 +253,11 @@ export default class AccountRepository extends TenantRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds or creates the stripe clearing account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateStripeClearing(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
@@ -281,4 +284,114 @@ export default class AccountRepository extends TenantRepository {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the discount expense account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateDiscountAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// 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: DiscountExpenseAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...DiscountExpenseAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreatePurchaseDiscountAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// 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: PurchaseDiscountAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...PurchaseDiscountAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreateOtherChargesAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// 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: OtherChargesAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...OtherChargesAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreateOtherExpensesAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// 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: OtherExpensesAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...OtherExpensesAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ export default class Ledger implements ILedger {
|
||||
return {
|
||||
credit: defaultTo(entry.credit, 0),
|
||||
debit: defaultTo(entry.debit, 0),
|
||||
|
||||
exchangeRate: entry.exchangeRate,
|
||||
currencyCode: entry.currencyCode,
|
||||
|
||||
|
||||
@@ -9,15 +9,18 @@ import {
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { transformLedgerEntryToTransaction } from './utils';
|
||||
|
||||
// Filter the blank entries.
|
||||
const filterBlankEntry = (entry: ILedgerEntry) => Boolean(entry.credit || entry.debit);
|
||||
|
||||
@Service()
|
||||
export class LedgerEntriesStorage {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
/**
|
||||
* Saves entries of the given ledger.
|
||||
* @param {number} tenantId
|
||||
* @param {ILedger} ledger
|
||||
* @param {Knex.Transaction} knex
|
||||
* @param {number} tenantId
|
||||
* @param {ILedger} ledger
|
||||
* @param {Knex.Transaction} knex
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public saveEntries = async (
|
||||
@@ -26,7 +29,7 @@ export class LedgerEntriesStorage {
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const saveEntryQueue = async.queue(this.saveEntryTask, 10);
|
||||
const entries = ledger.getEntries();
|
||||
const entries = ledger.filter(filterBlankEntry).getEntries();
|
||||
|
||||
entries.forEach((entry) => {
|
||||
saveEntryQueue.push({ tenantId, entry, trx });
|
||||
@@ -57,8 +60,8 @@ export class LedgerEntriesStorage {
|
||||
|
||||
/**
|
||||
* Saves the ledger entry to the account transactions repository.
|
||||
* @param {number} tenantId
|
||||
* @param {ILedgerEntry} entry
|
||||
* @param {number} tenantId
|
||||
* @param {ILedgerEntry} entry
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private saveEntry = async (
|
||||
|
||||
@@ -14,6 +14,8 @@ export class AccountTransformer extends Transformer {
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'accountTypeLabel',
|
||||
'accountNormalFormatted',
|
||||
'formattedAmount',
|
||||
'flattenName',
|
||||
'bankBalanceFormatted',
|
||||
@@ -84,6 +86,22 @@ export class AccountTransformer extends Transformer {
|
||||
return account.plaidItem?.isPaused || false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account type label.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountTypeLabel = (account: any): string => {
|
||||
return this.context.i18n.__(account.accountTypeLabel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account normal.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountNormalFormatted = (account: any): string => {
|
||||
return this.context.i18n.__(account.accountNormalFormatted);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the accounts collection to flat or nested array.
|
||||
* @param {IAccount[]}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import I18nService from '@/services/I18n/I18nService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { AccountTransformer } from './AccountTransform';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class GetAccount {
|
||||
@@ -10,10 +11,10 @@ export class GetAccount {
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private i18nService: I18nService;
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve the given account details.
|
||||
@@ -39,10 +40,11 @@ export class GetAccount {
|
||||
new AccountTransformer(),
|
||||
{ accountsGraph }
|
||||
);
|
||||
return this.i18nService.i18nApply(
|
||||
[['accountTypeLabel'], ['accountNormalFormatted']],
|
||||
transformed,
|
||||
tenantId
|
||||
);
|
||||
const eventPayload = { tenantId, accountId };
|
||||
|
||||
// Triggers `onAccountViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.accounts.onViewed, eventPayload);
|
||||
|
||||
return transformed;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,27 @@ import {
|
||||
Institution as PlaidInstitution,
|
||||
AccountBase as PlaidAccount,
|
||||
TransactionBase as PlaidTransactionBase,
|
||||
AccountType as PlaidAccountType,
|
||||
} from 'plaid';
|
||||
import {
|
||||
CreateUncategorizedTransactionDTO,
|
||||
IAccountCreateDTO,
|
||||
} from '@/interfaces';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
|
||||
/**
|
||||
* Retrieves the system account type from the given Plaid account type.
|
||||
* @param {PlaidAccountType} plaidAccountType
|
||||
* @returns {string}
|
||||
*/
|
||||
const getAccountTypeFromPlaidAccountType = (
|
||||
plaidAccountType: PlaidAccountType
|
||||
) => {
|
||||
if (plaidAccountType === PlaidAccountType.Credit) {
|
||||
return ACCOUNT_TYPE.CREDIT_CARD;
|
||||
}
|
||||
return ACCOUNT_TYPE.BANK;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the Plaid account to create cashflow account DTO.
|
||||
@@ -28,7 +44,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
||||
code: '',
|
||||
description: plaidAccount.official_name,
|
||||
currencyCode: plaidAccount.balances.iso_currency_code,
|
||||
accountType: 'cash',
|
||||
accountType: getAccountTypeFromPlaidAccountType(plaidAccount.type),
|
||||
active: true,
|
||||
bankBalance: plaidAccount.balances.current,
|
||||
accountMask: plaidAccount.mask,
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ILedgerEntry,
|
||||
ICashflowTransaction,
|
||||
AccountNormal,
|
||||
} from '../../interfaces';
|
||||
import {
|
||||
transformCashflowTransactionType,
|
||||
getCashflowAccountTransactionsTypes,
|
||||
} from './utils';
|
||||
import { ILedgerEntry, ICashflowTransaction } from '../../interfaces';
|
||||
import { transformCashflowTransactionType } from './utils';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
@@ -70,7 +63,7 @@ export default class CashflowTransactionJournalEntries {
|
||||
debit: cashflowTransaction.isCashDebit
|
||||
? cashflowTransaction.localAmount
|
||||
: 0,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
accountNormal: cashflowTransaction?.cashflowAccount?.accountNormal,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
@@ -143,6 +136,7 @@ export default class CashflowTransactionJournalEntries {
|
||||
// Retrieves the cashflow transactions with associated entries.
|
||||
const transaction = await CashflowTransaction.query(trx)
|
||||
.findById(cashflowTransactionId)
|
||||
.withGraphFetched('cashflowAccount')
|
||||
.withGraphFetched('creditAccount');
|
||||
|
||||
// Retrieves the cashflow transaction ledger.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CashflowAccountTransformer } from './CashflowAccountTransformer';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
|
||||
@Service()
|
||||
export default class GetCashflowAccountsService {
|
||||
@@ -41,14 +42,20 @@ export default class GetCashflowAccountsService {
|
||||
const accounts = await CashflowAccount.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
|
||||
builder.whereIn('account_type', ['bank', 'cash']);
|
||||
builder.whereIn('account_type', [
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.CREDIT_CARD,
|
||||
]);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
// Retrieves the transformed accounts.
|
||||
return this.transformer.transform(
|
||||
const transformed = await this.transformer.transform(
|
||||
tenantId,
|
||||
accounts,
|
||||
new CashflowAccountTransformer()
|
||||
);
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export class GetCashflowTransactionService {
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transfromer: TransformerInjectable;
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the given cashflow transaction.
|
||||
@@ -37,7 +37,7 @@ export class GetCashflowTransactionService {
|
||||
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
|
||||
|
||||
// Transformes the cashflow transaction model to POJO.
|
||||
return this.transfromer.transform(
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
cashflowTransaction,
|
||||
new CashflowTransactionTransformer()
|
||||
|
||||
@@ -122,6 +122,7 @@ export default class NewCashflowTransactionService {
|
||||
* @param {number} tenantId -
|
||||
* @param {ICashflowOwnerContributionDTO} ownerContributionDTO
|
||||
* @param {number} userId - User id.
|
||||
* @returns {Promise<ICashflowTransaction>}
|
||||
*/
|
||||
public newCashflowTransaction = async (
|
||||
tenantId: number,
|
||||
|
||||
@@ -35,9 +35,12 @@ export class CreditNoteBrandingTemplate {
|
||||
...defaultCreditNoteBrandingAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
brandingTemplateAttrs,
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import { SaleReceipt } from '@/models';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteGLEntries {
|
||||
@@ -29,11 +30,15 @@ export default class CreditNoteGLEntries {
|
||||
*/
|
||||
private getCreditNoteGLedger = (
|
||||
creditNote: ICreditNote,
|
||||
receivableAccount: number
|
||||
receivableAccount: number,
|
||||
discountAccount: number,
|
||||
adjustmentAccount: number
|
||||
): Ledger => {
|
||||
const ledgerEntries = this.getCreditNoteGLEntries(
|
||||
creditNote,
|
||||
receivableAccount
|
||||
receivableAccount,
|
||||
discountAccount,
|
||||
adjustmentAccount
|
||||
);
|
||||
return new Ledger(ledgerEntries);
|
||||
};
|
||||
@@ -49,9 +54,16 @@ export default class CreditNoteGLEntries {
|
||||
tenantId: number,
|
||||
creditNote: ICreditNote,
|
||||
payableAccount: number,
|
||||
discountAccount: number,
|
||||
adjustmentAccount: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const ledger = this.getCreditNoteGLedger(creditNote, payableAccount);
|
||||
const ledger = this.getCreditNoteGLedger(
|
||||
creditNote,
|
||||
payableAccount,
|
||||
discountAccount,
|
||||
adjustmentAccount
|
||||
);
|
||||
|
||||
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||
};
|
||||
@@ -98,11 +110,18 @@ export default class CreditNoteGLEntries {
|
||||
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
|
||||
creditNoteWithItems.currencyCode
|
||||
);
|
||||
const discountAccount = await accountRepository.findOrCreateDiscountAccount(
|
||||
{}
|
||||
);
|
||||
const adjustmentAccount =
|
||||
await accountRepository.findOrCreateOtherChargesAccount({});
|
||||
// Saves the credit note GL entries.
|
||||
await this.saveCreditNoteGLEntries(
|
||||
tenantId,
|
||||
creditNoteWithItems,
|
||||
ARAccount.id,
|
||||
discountAccount.id,
|
||||
adjustmentAccount.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
@@ -169,7 +188,7 @@ export default class CreditNoteGLEntries {
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: creditNote.localAmount,
|
||||
credit: creditNote.totalLocal,
|
||||
accountId: ARAccountId,
|
||||
contactId: creditNote.customerId,
|
||||
index: 1,
|
||||
@@ -191,11 +210,11 @@ export default class CreditNoteGLEntries {
|
||||
index: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCreditNoteCommonEntry(creditNote);
|
||||
const localAmount = entry.amount * creditNote.exchangeRate;
|
||||
const totalLocal = entry.totalExcludingTax * creditNote.exchangeRate;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: localAmount,
|
||||
debit: totalLocal,
|
||||
accountId: entry.sellAccountId || entry.item.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
@@ -206,6 +225,50 @@ export default class CreditNoteGLEntries {
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves the credit note discount entry.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} discountAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getDiscountEntry = (
|
||||
creditNote: ICreditNote,
|
||||
discountAccountId: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCreditNoteCommonEntry(creditNote);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: creditNote.discountAmountLocal,
|
||||
accountId: discountAccountId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the credit note adjustment entry.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} adjustmentAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getAdjustmentEntry = (
|
||||
creditNote: ICreditNote,
|
||||
adjustmentAccountId: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCreditNoteCommonEntry(creditNote);
|
||||
const adjustmentAmount = Math.abs(creditNote.adjustmentLocal);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: creditNote.adjustmentLocal < 0 ? adjustmentAmount : 0,
|
||||
debit: creditNote.adjustmentLocal > 0 ? adjustmentAmount : 0,
|
||||
accountId: adjustmentAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the credit note GL entries.
|
||||
* @param {ICreditNote} creditNote - Credit note.
|
||||
@@ -214,13 +277,21 @@ export default class CreditNoteGLEntries {
|
||||
*/
|
||||
public getCreditNoteGLEntries = (
|
||||
creditNote: ICreditNote,
|
||||
ARAccountId: number
|
||||
ARAccountId: number,
|
||||
discountAccountId: number,
|
||||
adjustmentAccountId: number
|
||||
): ILedgerEntry[] => {
|
||||
const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId);
|
||||
|
||||
const getItemEntry = this.getCreditNoteItemEntry(creditNote);
|
||||
const itemsEntries = creditNote.entries.map(getItemEntry);
|
||||
|
||||
return [AREntry, ...itemsEntries];
|
||||
const discountEntry = this.getDiscountEntry(creditNote, discountAccountId);
|
||||
const adjustmentEntry = this.getAdjustmentEntry(
|
||||
creditNote,
|
||||
adjustmentAccountId
|
||||
);
|
||||
|
||||
return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,18 @@ export class CreditNoteTransformer extends Transformer {
|
||||
'formattedAmount',
|
||||
'formattedCreditsUsed',
|
||||
'formattedSubtotal',
|
||||
|
||||
'discountAmountFormatted',
|
||||
'discountAmountLocalFormatted',
|
||||
|
||||
'discountPercentageFormatted',
|
||||
|
||||
'adjustmentFormatted',
|
||||
'adjustmentLocalFormatted',
|
||||
|
||||
'totalFormatted',
|
||||
'totalLocalFormatted',
|
||||
|
||||
'entries',
|
||||
'attachments',
|
||||
];
|
||||
@@ -34,7 +46,7 @@ export class CreditNoteTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted created at date.
|
||||
* @param credit
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCreatedAt = (credit): string => {
|
||||
@@ -83,6 +95,85 @@ export class CreditNoteTransformer extends Transformer {
|
||||
return formatNumber(credit.amount, { money: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted discount amount.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected discountAmountFormatted = (credit): string => {
|
||||
return formatNumber(credit.discountAmount, {
|
||||
currencyCode: credit.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted discount amount in local currency.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected discountAmountLocalFormatted = (credit): string => {
|
||||
return formatNumber(credit.discountAmountLocal, {
|
||||
currencyCode: credit.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted discount percentage.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected discountPercentageFormatted = (credit): string => {
|
||||
return credit.discountPercentage ? `${credit.discountPercentage}%` : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted adjustment amount.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected adjustmentFormatted = (credit): string => {
|
||||
return this.formatMoney(credit.adjustment, {
|
||||
currencyCode: credit.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted adjustment amount in local currency.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected adjustmentLocalFormatted = (credit): string => {
|
||||
return formatNumber(credit.adjustmentLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
excerptZero: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted total.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalFormatted = (credit): string => {
|
||||
return formatNumber(credit.total, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted total in local currency.
|
||||
* @param credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalLocalFormatted = (credit): string => {
|
||||
return formatNumber(credit.totalLocal, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the entries of the credit note.
|
||||
* @param {ICreditNote} credit
|
||||
|
||||
@@ -6,6 +6,8 @@ import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate';
|
||||
import { CreditNotePdfTemplateAttributes } from '@/interfaces';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { transformCreditNoteToPdfTemplate } from './utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class GetCreditNotePdf {
|
||||
@@ -24,12 +26,19 @@ export default class GetCreditNotePdf {
|
||||
@Inject()
|
||||
private creditNoteBrandingTemplate: CreditNoteBrandingTemplate;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieves sale invoice pdf content.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @returns {Promise<[Buffer, string]>}
|
||||
*/
|
||||
public async getCreditNotePdf(tenantId: number, creditNoteId: number) {
|
||||
public async getCreditNotePdf(
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<[Buffer, string]> {
|
||||
const brandingAttributes = await this.getCreditNoteBrandingAttributes(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
@@ -39,7 +48,37 @@ export default class GetCreditNotePdf {
|
||||
'modules/credit-note-standard',
|
||||
brandingAttributes
|
||||
);
|
||||
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
|
||||
const filename = await this.getCreditNoteFilename(tenantId, creditNoteId);
|
||||
|
||||
const document = await this.chromiumlyTenancy.convertHtmlContent(
|
||||
tenantId,
|
||||
htmlContent
|
||||
);
|
||||
const eventPayload = { tenantId, creditNoteId };
|
||||
|
||||
// Triggers the `onCreditNotePdfViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onPdfViewed,
|
||||
eventPayload
|
||||
);
|
||||
return [document, filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename pdf document of the given credit note.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async getCreditNoteFilename(
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<string> {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
const creditNote = await CreditNote.query().findById(creditNoteId);
|
||||
|
||||
return `Credit-${creditNote.creditNoteNumber}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Knex from 'knex';
|
||||
import { IRefundCreditNote } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export default class RefundSyncCreditNoteBalance {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ACCOUNT_CREATED,
|
||||
ACCOUNT_EDITED,
|
||||
ACCOUNT_DELETED,
|
||||
ACCOUNT_VIEWED,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
@@ -31,6 +32,7 @@ export class AccountEventsTracker extends EventSubscriber {
|
||||
events.accounts.onDeleted,
|
||||
this.handleTrackDeletedAccountEvent
|
||||
);
|
||||
bus.subscribe(events.accounts.onViewed, this.handleTrackAccountViewedEvent);
|
||||
}
|
||||
|
||||
private handleTrackAccountCreatedEvent = ({
|
||||
@@ -62,4 +64,12 @@ export class AccountEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackAccountViewedEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: ACCOUNT_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ITEM_EVENT_CREATED,
|
||||
ITEM_EVENT_EDITED,
|
||||
ITEM_EVENT_DELETED,
|
||||
ITEM_EVENT_VIEWED,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
@@ -25,6 +26,7 @@ export class ItemEventsTracker extends EventSubscriber {
|
||||
bus.subscribe(events.item.onCreated, this.handleTrackItemCreatedEvent);
|
||||
bus.subscribe(events.item.onEdited, this.handleTrackEditedItemEvent);
|
||||
bus.subscribe(events.item.onDeleted, this.handleTrackDeletedItemEvent);
|
||||
bus.subscribe(events.item.onViewed, this.handleTrackViewedItemEvent);
|
||||
}
|
||||
|
||||
private handleTrackItemCreatedEvent = ({
|
||||
@@ -56,4 +58,14 @@ export class ItemEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackViewedItemEvent = ({
|
||||
tenantId,
|
||||
}: IItemEventDeletedPayload) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: ITEM_EVENT_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
PAYMENT_RECEIVED_CREATED,
|
||||
PAYMENT_RECEIVED_EDITED,
|
||||
PAYMENT_RECEIVED_DELETED,
|
||||
PAYMENT_RECEIVED_PDF_VIEWED,
|
||||
PAYMENT_RECEIVED_MAIL_SENT,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
@@ -34,6 +36,14 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
|
||||
events.paymentReceive.onDeleted,
|
||||
this.handleTrackDeletedPaymentReceivedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.paymentReceive.onPdfViewed,
|
||||
this.handleTrackPdfViewedPaymentReceivedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.paymentReceive.onMailSent,
|
||||
this.handleTrackMailSentPaymentReceivedEvent
|
||||
);
|
||||
}
|
||||
|
||||
private handleTrackPaymentReceivedCreatedEvent = ({
|
||||
@@ -65,4 +75,24 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackPdfViewedPaymentReceivedEvent = ({
|
||||
tenantId,
|
||||
}: IPaymentReceivedDeletedPayload) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: PAYMENT_RECEIVED_PDF_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackMailSentPaymentReceivedEvent = ({
|
||||
tenantId,
|
||||
}: IPaymentReceivedDeletedPayload) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: PAYMENT_RECEIVED_MAIL_SENT,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { ReportsEvents } from '@/constants/event-tracker';
|
||||
import { PosthogService } from '../PostHog';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
BALANCE_SHEET_VIEWED,
|
||||
TRIAL_BALANCE_SHEET_VIEWED,
|
||||
PROFIT_LOSS_SHEET_VIEWED,
|
||||
CASHFLOW_STATEMENT_VIEWED,
|
||||
GENERAL_LEDGER_VIEWED,
|
||||
JOURNAL_VIEWED,
|
||||
RECEIVABLE_AGING_VIEWED,
|
||||
PAYABLE_AGING_VIEWED,
|
||||
CUSTOMER_BALANCE_SUMMARY_VIEWED,
|
||||
VENDOR_BALANCE_SUMMARY_VIEWED,
|
||||
INVENTORY_VALUATION_VIEWED,
|
||||
CUSTOMER_TRANSACTIONS_VIEWED,
|
||||
VENDOR_TRANSACTIONS_VIEWED,
|
||||
SALES_BY_ITEM_VIEWED,
|
||||
PURCHASES_BY_ITEM_VIEWED,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
export class ReportsEventsTracker extends EventSubscriber {
|
||||
@Inject()
|
||||
private posthog: PosthogService;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.reports.onBalanceSheetViewed,
|
||||
this.handleTrackBalanceSheetViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onTrialBalanceSheetView,
|
||||
this.handleTrackTrialBalanceSheetViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onProfitLossSheetViewed,
|
||||
this.handleTrackProfitLossSheetViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onCashflowStatementViewed,
|
||||
this.handleTrackCashflowStatementViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onGeneralLedgerViewed,
|
||||
this.handleTrackGeneralLedgerViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onJournalViewed,
|
||||
this.handleTrackJournalViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onReceivableAgingViewed,
|
||||
this.handleTrackReceivableAgingViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onPayableAgingViewed,
|
||||
this.handleTrackPayableAgingViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onCustomerBalanceSummaryViewed,
|
||||
this.handleTrackCustomerBalanceSummaryViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onVendorBalanceSummaryViewed,
|
||||
this.handleTrackVendorBalanceSummaryViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onInventoryValuationViewed,
|
||||
this.handleTrackInventoryValuationViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onCustomerTransactionsViewed,
|
||||
this.handleTrackCustomerTransactionsViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onVendorTransactionsViewed,
|
||||
this.handleTrackVendorTransactionsViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onSalesByItemViewed,
|
||||
this.handleTrackSalesByItemViewedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.reports.onPurchasesByItemViewed,
|
||||
this.handleTrackPurchasesByItemViewedEvent
|
||||
);
|
||||
}
|
||||
|
||||
private handleTrackBalanceSheetViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: BALANCE_SHEET_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackTrialBalanceSheetViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: TRIAL_BALANCE_SHEET_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackProfitLossSheetViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: PROFIT_LOSS_SHEET_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackCashflowStatementViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: CASHFLOW_STATEMENT_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackGeneralLedgerViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: GENERAL_LEDGER_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackJournalViewedEvent = ({ tenantId }: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: JOURNAL_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackReceivableAgingViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: RECEIVABLE_AGING_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackPayableAgingViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: PAYABLE_AGING_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackCustomerBalanceSummaryViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: CUSTOMER_BALANCE_SUMMARY_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackVendorBalanceSummaryViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: VENDOR_BALANCE_SUMMARY_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackInventoryValuationViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: INVENTORY_VALUATION_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackCustomerTransactionsViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: CUSTOMER_TRANSACTIONS_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackVendorTransactionsViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: VENDOR_TRANSACTIONS_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackSalesByItemViewedEvent = ({ tenantId }: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALES_BY_ITEM_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackPurchasesByItemViewedEvent = ({
|
||||
tenantId,
|
||||
}: ReportsEvents) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: PURCHASES_BY_ITEM_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
SALE_ESTIMATE_CREATED,
|
||||
SALE_ESTIMATE_EDITED,
|
||||
SALE_ESTIMATE_DELETED,
|
||||
SALE_ESTIMATE_PDF_VIEWED,
|
||||
SALE_ESTIMATE_VIEWED,
|
||||
SALE_ESTIMATE_MAIL_SENT,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
@@ -34,6 +37,18 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
|
||||
events.saleEstimate.onDeleted,
|
||||
this.handleTrackDeletedEstimateEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleEstimate.onPdfViewed,
|
||||
this.handleTrackPdfViewedEstimateEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleEstimate.onViewed,
|
||||
this.handleTrackViewedEstimateEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleEstimate.onMailSent,
|
||||
this.handleTrackMailSentEstimateEvent
|
||||
);
|
||||
}
|
||||
|
||||
private handleTrackEstimateCreatedEvent = ({
|
||||
@@ -65,4 +80,30 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackPdfViewedEstimateEvent = ({
|
||||
tenantId,
|
||||
}: ISaleEstimateDeletedPayload) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_ESTIMATE_PDF_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackViewedEstimateEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_ESTIMATE_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackMailSentEstimateEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_ESTIMATE_MAIL_SENT,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
SALE_INVOICE_CREATED,
|
||||
SALE_INVOICE_DELETED,
|
||||
SALE_INVOICE_EDITED,
|
||||
SALE_INVOICE_MAIL_SENT,
|
||||
SALE_INVOICE_PDF_VIEWED,
|
||||
SALE_INVOICE_VIEWED,
|
||||
} from '@/constants/event-tracker';
|
||||
|
||||
@Service()
|
||||
@@ -33,6 +36,18 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
||||
events.saleInvoice.onDeleted,
|
||||
this.handleTrackDeletedInvoiceEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onViewed,
|
||||
this.handleTrackViewedInvoiceEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onPdfViewed,
|
||||
this.handleTrackPdfViewedInvoiceEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onMailSent,
|
||||
this.handleTrackMailSentInvoiceEvent
|
||||
);
|
||||
}
|
||||
|
||||
private handleTrackInvoiceCreatedEvent = ({
|
||||
@@ -64,4 +79,28 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackViewedInvoiceEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_INVOICE_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackPdfViewedInvoiceEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_INVOICE_PDF_VIEWED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleTrackMailSentInvoiceEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SALE_INVOICE_MAIL_SENT,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ITransactionsLockingPartialUnlocked } from '@/interfaces';
|
||||
import { PosthogService } from '../PostHog';
|
||||
import {
|
||||
SUBSCRIPTION_CANCELLED,
|
||||
SUBSCRIPTION_PAYMENT_FAILED,
|
||||
SUBSCRIPTION_PAYMENT_SUCCEED,
|
||||
SUBSCRIPTION_PLAN_CHANGED,
|
||||
SUBSCRIPTION_RESUMED,
|
||||
} from '@/constants/event-tracker';
|
||||
@@ -27,6 +29,14 @@ export class TransactionsLockingEventsTracker extends EventSubscriber {
|
||||
events.subscription.onSubscriptionPlanChanged,
|
||||
this.handleSubscriptionPlanChangedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.subscription.onSubscriptionPaymentSucceed,
|
||||
this.handleSubscriptionPaymentFailedEvent
|
||||
);
|
||||
bus.subscribe(
|
||||
events.subscription.onSubscriptionPaymentFailed,
|
||||
this.handleSubscriptionPaymentSucceed
|
||||
);
|
||||
}
|
||||
|
||||
private handleSubscriptionResumedEvent = ({ tenantId }) => {
|
||||
@@ -52,4 +62,20 @@ export class TransactionsLockingEventsTracker extends EventSubscriber {
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleSubscriptionPaymentFailedEvent = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SUBSCRIPTION_PAYMENT_FAILED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
|
||||
private handleSubscriptionPaymentSucceed = ({ tenantId }) => {
|
||||
this.posthog.trackEvent({
|
||||
distinctId: `tenant-${tenantId}`,
|
||||
event: SUBSCRIPTION_PAYMENT_SUCCEED,
|
||||
properties: {},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { PdfTemplateEventsTracker } from './PdfTemplateEventsTracker';
|
||||
import { PaymentMethodEventsTracker } from './PaymentMethodEventsTracker';
|
||||
import { PaymentLinkEventsTracker } from './PaymentLinkEventsTracker';
|
||||
import { StripeIntegrationEventsTracker } from './StripeIntegrationEventsTracker';
|
||||
import { ReportsEventsTracker } from './ReportsEventsTracker';
|
||||
|
||||
export const EventsTrackerListeners = [
|
||||
SaleInvoiceEventsTracker,
|
||||
@@ -36,4 +37,5 @@ export const EventsTrackerListeners = [
|
||||
PaymentMethodEventsTracker,
|
||||
PaymentLinkEventsTracker,
|
||||
StripeIntegrationEventsTracker,
|
||||
ReportsEventsTracker,
|
||||
];
|
||||
|
||||
@@ -6,6 +6,8 @@ import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import APAgingSummarySheet from './APAgingSummarySheet';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { APAgingSummaryMeta } from './APAgingSummaryMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class APAgingSummaryService {
|
||||
@@ -15,6 +17,9 @@ export class APAgingSummaryService {
|
||||
@Inject()
|
||||
private APAgingSummaryMeta: APAgingSummaryMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Default report query.
|
||||
*/
|
||||
@@ -96,6 +101,12 @@ export class APAgingSummaryService {
|
||||
// Retrieve the aging summary report meta.
|
||||
const meta = await this.APAgingSummaryMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onPayableAgingViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onPayableAgingViewed, {
|
||||
tenantId,
|
||||
query,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
columns,
|
||||
|
||||
@@ -6,6 +6,8 @@ import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import ARAgingSummarySheet from './ARAgingSummarySheet';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { ARAgingSummaryMeta } from './ARAgingSummaryMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class ARAgingSummaryService {
|
||||
@@ -15,6 +17,9 @@ export default class ARAgingSummaryService {
|
||||
@Inject()
|
||||
private ARAgingSummaryMeta: ARAgingSummaryMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Default report query.
|
||||
*/
|
||||
@@ -91,6 +96,15 @@ export default class ARAgingSummaryService {
|
||||
// Retrieve the aging summary report meta.
|
||||
const meta = await this.ARAgingSummaryMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onReceivableAgingViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onReceivableAgingViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
columns,
|
||||
|
||||
@@ -10,6 +10,8 @@ import BalanceSheetStatement from './BalanceSheet';
|
||||
import { Tenant } from '@/system/models';
|
||||
import BalanceSheetRepository from './BalanceSheetRepository';
|
||||
import { BalanceSheetMetaInjectable } from './BalanceSheetMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class BalanceSheetStatementService
|
||||
@@ -21,6 +23,9 @@ export default class BalanceSheetStatementService
|
||||
@Inject()
|
||||
private balanceSheetMeta: BalanceSheetMetaInjectable;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
@@ -98,6 +103,10 @@ export default class BalanceSheetStatementService
|
||||
// Balance sheet meta.
|
||||
const meta = await this.balanceSheetMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onBalanceSheetViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, {
|
||||
query,
|
||||
});
|
||||
return {
|
||||
query: filter,
|
||||
data,
|
||||
|
||||
@@ -13,6 +13,8 @@ import Ledger from '@/services/Accounting/Ledger';
|
||||
import CustomerBalanceSummaryRepository from './CustomerBalanceSummaryRepository';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { CustomerBalanceSummaryMeta } from './CustomerBalanceSummaryMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class CustomerBalanceSummaryService
|
||||
@@ -24,6 +26,9 @@ export class CustomerBalanceSummaryService
|
||||
@Inject()
|
||||
private customerBalanceSummaryMeta: CustomerBalanceSummaryMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {ICustomerBalanceSummaryQuery}
|
||||
@@ -104,6 +109,15 @@ export class CustomerBalanceSummaryService
|
||||
// Retrieve the customer balance summary meta.
|
||||
const meta = await this.customerBalanceSummaryMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onCustomerBalanceSummaryViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onCustomerBalanceSummaryViewed,
|
||||
{
|
||||
tenant,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: report.reportData(),
|
||||
query: filter,
|
||||
|
||||
@@ -5,6 +5,8 @@ import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class GeneralLedgerService {
|
||||
@@ -14,6 +16,9 @@ export class GeneralLedgerService {
|
||||
@Inject()
|
||||
private generalLedgerMeta: GeneralLedgerMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults general ledger report filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
@@ -72,6 +77,11 @@ export class GeneralLedgerService {
|
||||
// Retrieve general ledger report metadata.
|
||||
const meta = await this.generalLedgerMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onGeneralLedgerViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onGeneralLedgerViewed, {
|
||||
tenantId,
|
||||
});
|
||||
|
||||
return {
|
||||
data: reportData,
|
||||
query: filter,
|
||||
|
||||
@@ -11,6 +11,8 @@ import { InventoryValuationSheet } from './InventoryValuationSheet';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { InventoryValuationMetaInjectable } from './InventoryValuationSheetMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class InventoryValuationSheetService {
|
||||
@@ -26,6 +28,9 @@ export class InventoryValuationSheetService {
|
||||
@Inject()
|
||||
private inventoryValuationMeta: InventoryValuationMetaInjectable;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
@@ -116,6 +121,15 @@ export class InventoryValuationSheetService {
|
||||
// Retrieves the inventorty valuation meta.
|
||||
const meta = await this.inventoryValuationMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onInventoryValuationViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onInventoryValuationViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: inventoryValuationData,
|
||||
query: filter,
|
||||
|
||||
@@ -7,6 +7,8 @@ import Journal from '@/services/Accounting/JournalPoster';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { transformToMap } from 'utils';
|
||||
import { JournalSheetMeta } from './JournalSheetMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class JournalSheetService {
|
||||
@@ -16,6 +18,9 @@ export class JournalSheetService {
|
||||
@Inject()
|
||||
private journalSheetMeta: JournalSheetMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Default journal sheet filter queyr.
|
||||
*/
|
||||
@@ -101,6 +106,12 @@ export class JournalSheetService {
|
||||
// Retrieve the journal sheet meta.
|
||||
const meta = await this.journalSheetMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onJournalViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onJournalViewed, {
|
||||
tenantId,
|
||||
query,
|
||||
});
|
||||
|
||||
return {
|
||||
data: journalSheetData,
|
||||
query: filter,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Tenant } from '@/system/models';
|
||||
import { mergeQueryWithDefaults } from './utils';
|
||||
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||
import { ProfitLossSheetMeta } from './ProfitLossSheetMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
// Profit/Loss sheet service.
|
||||
@Service()
|
||||
@@ -20,6 +22,9 @@ export default class ProfitLossSheetService {
|
||||
@Inject()
|
||||
private profitLossSheetMeta: ProfitLossSheetMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve profit/loss sheet statement.
|
||||
* @param {number} tenantId
|
||||
@@ -62,6 +67,15 @@ export default class ProfitLossSheetService {
|
||||
// Retrieve the profit/loss sheet meta.
|
||||
const meta = await this.profitLossSheetMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onProfitLossSheetViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onProfitLossSheetViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
query: filter,
|
||||
data,
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
IPurchasesByItemsSheet,
|
||||
} from '@/interfaces/PurchasesByItemsSheet';
|
||||
import { PurchasesByItemsMeta } from './PurchasesByItemsMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class PurchasesByItemsService {
|
||||
@@ -17,6 +19,9 @@ export class PurchasesByItemsService {
|
||||
@Inject()
|
||||
private purchasesByItemsMeta: PurchasesByItemsMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults purchases by items filter query.
|
||||
* @return {IPurchasesByItemsReportQuery}
|
||||
@@ -92,6 +97,15 @@ export class PurchasesByItemsService {
|
||||
// Retrieve the purchases by items meta.
|
||||
const meta = await this.purchasesByItemsMeta.meta(tenantId, query);
|
||||
|
||||
// Triggers `onPurchasesByItemViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onPurchasesByItemViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: purchasesByItemsData,
|
||||
query: filter,
|
||||
|
||||
@@ -5,6 +5,8 @@ import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import SalesByItems from './SalesByItems';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { SalesByItemsMeta } from './SalesByItemsMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class SalesByItemsReportService {
|
||||
@@ -14,6 +16,9 @@ export class SalesByItemsReportService {
|
||||
@Inject()
|
||||
private salesByItemsMeta: SalesByItemsMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
@@ -89,6 +94,12 @@ export class SalesByItemsReportService {
|
||||
// Retrieve the sales by items meta.
|
||||
const meta = await this.salesByItemsMeta.meta(tenantId, query);
|
||||
|
||||
// Triggers `onSalesByItemViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onSalesByItemViewed, {
|
||||
tenantId,
|
||||
query,
|
||||
});
|
||||
|
||||
return {
|
||||
data: salesByItemsData,
|
||||
query: filter,
|
||||
|
||||
@@ -13,6 +13,8 @@ import Ledger from '@/services/Accounting/Ledger';
|
||||
import TransactionsByCustomersRepository from './TransactionsByCustomersRepository';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { TransactionsByCustomersMeta } from './TransactionsByCustomersMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
export class TransactionsByCustomersSheet
|
||||
implements ITransactionsByCustomersService
|
||||
@@ -26,6 +28,9 @@ export class TransactionsByCustomersSheet
|
||||
@Inject()
|
||||
private transactionsByCustomersMeta: TransactionsByCustomersMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {ICustomerBalanceSummaryQuery}
|
||||
@@ -166,6 +171,15 @@ export class TransactionsByCustomersSheet
|
||||
|
||||
const meta = await this.transactionsByCustomersMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onCustomerTransactionsViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onCustomerTransactionsViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: reportInstance.reportData(),
|
||||
query: filter,
|
||||
|
||||
@@ -13,6 +13,8 @@ import Ledger from '@/services/Accounting/Ledger';
|
||||
import TransactionsByVendorRepository from './TransactionsByVendorRepository';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { TransactionsByVendorMeta } from './TransactionsByVendorMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
export class TransactionsByVendorsInjectable
|
||||
implements ITransactionsByVendorsService
|
||||
@@ -26,6 +28,9 @@ export class TransactionsByVendorsInjectable
|
||||
@Inject()
|
||||
private transactionsByVendorMeta: TransactionsByVendorMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IVendorBalanceSummaryQuery}
|
||||
@@ -171,6 +176,15 @@ export class TransactionsByVendorsInjectable
|
||||
);
|
||||
const meta = await this.transactionsByVendorMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onVendorTransactionsViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onVendorTransactionsViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: reportInstance.reportData(),
|
||||
query: filter,
|
||||
|
||||
@@ -7,6 +7,8 @@ import FinancialSheet from '../FinancialSheet';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository';
|
||||
import { TrialBalanceSheetMeta } from './TrialBalanceSheetMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class TrialBalanceSheetService extends FinancialSheet {
|
||||
@@ -16,6 +18,9 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
||||
@Inject()
|
||||
private trialBalanceSheetMetaService: TrialBalanceSheetMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults trial balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
@@ -81,6 +86,15 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
||||
// Trial balance sheet meta.
|
||||
const meta = await this.trialBalanceSheetMetaService.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onTrialBalanceSheetViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onTrialBalanceSheetView,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: trialBalanceSheetData,
|
||||
query: filter,
|
||||
|
||||
@@ -15,6 +15,8 @@ import { Tenant } from '@/system/models';
|
||||
import { JournalSheetMeta } from '../JournalSheet/JournalSheetMeta';
|
||||
|
||||
import { VendorBalanceSummaryMeta } from './VendorBalanceSummaryMeta';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
export class VendorBalanceSummaryService
|
||||
implements IVendorBalanceSummaryService
|
||||
@@ -28,6 +30,9 @@ export class VendorBalanceSummaryService
|
||||
@Inject()
|
||||
private vendorBalanceSummaryMeta: VendorBalanceSummaryMeta;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IVendorBalanceSummaryQuery}
|
||||
@@ -49,7 +54,7 @@ export class VendorBalanceSummaryService
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Retrieve the vendors ledger entrjes.
|
||||
* @param {number} tenantId -
|
||||
* @param {Date|string} date -
|
||||
@@ -107,10 +112,19 @@ export class VendorBalanceSummaryService
|
||||
// Retrieve the vendor balance summary meta.
|
||||
const meta = await this.vendorBalanceSummaryMeta.meta(tenantId, filter);
|
||||
|
||||
// Triggers `onVendorBalanceSummaryViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.reports.onVendorBalanceSummaryViewed,
|
||||
{
|
||||
tenantId,
|
||||
query,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data: reportInstance.reportData(),
|
||||
query: filter,
|
||||
meta
|
||||
meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ export const valueParser =
|
||||
// Parses the enumeration value.
|
||||
} else if (field.fieldType === 'enumeration') {
|
||||
const option = get(field, 'options', []).find(
|
||||
(option) => option.label === value
|
||||
(option) => option.label?.toLowerCase() === value?.toLowerCase()
|
||||
);
|
||||
_value = get(option, 'key');
|
||||
// Parses the numeric value.
|
||||
@@ -433,8 +433,8 @@ export const getMapToPath = (to: string, group = '') =>
|
||||
group ? `${group}.${to}` : to;
|
||||
|
||||
export const getImportsStoragePath = () => {
|
||||
return path.join(global.__storage_dir, `/imports`);
|
||||
}
|
||||
return path.join(global.__storage_dir, `/imports`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the imported file from the storage and database.
|
||||
|
||||
@@ -24,7 +24,7 @@ export class CreateItem {
|
||||
|
||||
/**
|
||||
* Authorize the creating item.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @param {IItemDTO} itemDTO
|
||||
*/
|
||||
async authorize(tenantId: number, itemDTO: IItemDTO) {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { IItem } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import ItemTransformer from './ItemTransformer';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Inject()
|
||||
export class GetItem {
|
||||
@@ -12,6 +14,9 @@ export class GetItem {
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve the item details of the given id with associated details.
|
||||
* @param {number} tenantId
|
||||
@@ -31,6 +36,16 @@ export class GetItem {
|
||||
.withGraphFetched('purchaseTaxRate')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(tenantId, item, new ItemTransformer());
|
||||
const transformed = await this.transformer.transform(
|
||||
tenantId,
|
||||
item,
|
||||
new ItemTransformer()
|
||||
);
|
||||
const eventPayload = { tenantId, itemId };
|
||||
|
||||
// Triggers the `onItemViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.item.onViewed, eventPayload);
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ export class ItemsApplication {
|
||||
|
||||
/**
|
||||
* Creates a new item (service/product).
|
||||
* @param {number} tenantId
|
||||
* @param {IItemCreateDTO} itemDTO
|
||||
* @param {number} tenantId
|
||||
* @param {IItemCreateDTO} itemDTO
|
||||
* @returns {Promise<IItem>}
|
||||
*/
|
||||
public async createItem(
|
||||
@@ -52,8 +52,8 @@ export class ItemsApplication {
|
||||
|
||||
/**
|
||||
* Retrieves the given item.
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @returns {Promise<IItem>}
|
||||
*/
|
||||
public getItem(tenantId: number, itemId: number): Promise<IItem> {
|
||||
@@ -62,9 +62,9 @@ export class ItemsApplication {
|
||||
|
||||
/**
|
||||
* Edits the given item (service/product).
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {IItemEditDTO} itemDTO
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {IItemEditDTO} itemDTO
|
||||
* @returns {Promise<IItem>}
|
||||
*/
|
||||
public editItem(tenantId: number, itemId: number, itemDTO: IItemEditDTO) {
|
||||
@@ -73,8 +73,8 @@ export class ItemsApplication {
|
||||
|
||||
/**
|
||||
* Deletes the given item (service/product).
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteItem(tenantId: number, itemId: number) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
|
||||
import { formatSmsMessage } from '@/utils';
|
||||
import { Tenant } from '@/system/models';
|
||||
import { castArray } from 'lodash';
|
||||
|
||||
@Service()
|
||||
export class ContactMailNotification {
|
||||
@@ -14,76 +15,56 @@ export class ContactMailNotification {
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Parses the default message options.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} invoiceId -
|
||||
* @param {string} subject -
|
||||
* @param {string} body -
|
||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||
* Gets the default mail address of the given contact.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Contact id.
|
||||
* @returns {Promise<Pick<CommonMailOptions, 'to' | 'from'>>}
|
||||
*/
|
||||
public async getDefaultMailOptions(
|
||||
tenantId: number,
|
||||
contactId: number,
|
||||
subject: string = '',
|
||||
body: string = ''
|
||||
): Promise<CommonMailOptions> {
|
||||
customerId: number
|
||||
): Promise<
|
||||
Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'>
|
||||
> {
|
||||
const { Customer } = this.tenancy.models(tenantId);
|
||||
const contact = await Customer.query()
|
||||
.findById(contactId)
|
||||
const customer = await Customer.query()
|
||||
.findById(customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const toAddresses = contact.contactAddresses;
|
||||
const fromAddresses = await this.mailTenancy.senders(tenantId);
|
||||
const toOptions = customer.contactAddresses;
|
||||
const fromOptions = await this.mailTenancy.senders(tenantId);
|
||||
|
||||
const toAddress = toAddresses.find((a) => a.primary);
|
||||
const fromAddress = fromAddresses.find((a) => a.primary);
|
||||
const toAddress = toOptions.find((a) => a.primary);
|
||||
const fromAddress = fromOptions.find((a) => a.primary);
|
||||
|
||||
const to = toAddress?.mail || '';
|
||||
const from = fromAddress?.mail || '';
|
||||
const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
|
||||
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
|
||||
|
||||
return {
|
||||
subject,
|
||||
body,
|
||||
to,
|
||||
from,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
};
|
||||
return { to, from, toOptions, fromOptions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mail options of the given contact.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Invoice id.
|
||||
* @param {string} defaultSubject - Default subject text.
|
||||
* @param {string} defaultBody - Default body text.
|
||||
* @returns {Promise<CommonMailOptions>}
|
||||
*/
|
||||
public async getMailOptions(
|
||||
public async formatMailOptions(
|
||||
tenantId: number,
|
||||
contactId: number,
|
||||
defaultSubject?: string,
|
||||
defaultBody?: string,
|
||||
formatterData?: Record<string, any>
|
||||
mailOptions: CommonMailOptions,
|
||||
formatterArgs?: Record<string, any>
|
||||
): Promise<CommonMailOptions> {
|
||||
const mailOpts = await this.getDefaultMailOptions(
|
||||
tenantId,
|
||||
contactId,
|
||||
defaultSubject,
|
||||
defaultBody
|
||||
);
|
||||
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
|
||||
const formatArgs = {
|
||||
...commonFormatArgs,
|
||||
...formatterData,
|
||||
...formatterArgs,
|
||||
};
|
||||
const subject = formatSmsMessage(mailOpts.subject, formatArgs);
|
||||
const body = formatSmsMessage(mailOpts.body, formatArgs);
|
||||
const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs);
|
||||
const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs);
|
||||
|
||||
return {
|
||||
...mailOpts,
|
||||
subject,
|
||||
body,
|
||||
...mailOptions,
|
||||
subject: subjectFormatted,
|
||||
message: messageFormatted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,7 +81,7 @@ export class ContactMailNotification {
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
return {
|
||||
CompanyName: organization.metadata.name,
|
||||
['Company Name']: organization.metadata.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,56 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { CommonMailOptions, CommonMailOptionsDTO } from '@/interfaces';
|
||||
import { CommonMailOptions } from '@/interfaces';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
/**
|
||||
* Merges the mail options with incoming options.
|
||||
* @param {Partial<SaleInvoiceMailOptions>} mailOptions
|
||||
* @param {Partial<SendInvoiceMailDTO>} overridedOptions
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
export function parseAndValidateMailOptions(
|
||||
mailOptions: Partial<CommonMailOptions>,
|
||||
overridedOptions: Partial<CommonMailOptionsDTO>
|
||||
) {
|
||||
export function parseMailOptions(
|
||||
mailOptions: CommonMailOptions,
|
||||
overridedOptions: Partial<CommonMailOptions>
|
||||
): CommonMailOptions {
|
||||
const mergedMessageOptions = {
|
||||
...mailOptions,
|
||||
...overridedOptions,
|
||||
};
|
||||
if (isEmpty(mergedMessageOptions.from)) {
|
||||
const parsedMessageOptions = {
|
||||
...mergedMessageOptions,
|
||||
from: mergedMessageOptions?.from
|
||||
? castArray(mergedMessageOptions?.from)
|
||||
: [],
|
||||
to: mergedMessageOptions?.to ? castArray(mergedMessageOptions?.to) : [],
|
||||
cc: mergedMessageOptions?.cc ? castArray(mergedMessageOptions?.cc) : [],
|
||||
bcc: mergedMessageOptions?.bcc ? castArray(mergedMessageOptions?.bcc) : [],
|
||||
};
|
||||
return parsedMessageOptions;
|
||||
}
|
||||
|
||||
export function validateRequiredMailOptions(
|
||||
mailOptions: Partial<CommonMailOptions>
|
||||
) {
|
||||
if (isEmpty(mailOptions.from)) {
|
||||
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
|
||||
}
|
||||
if (isEmpty(mergedMessageOptions.to)) {
|
||||
if (isEmpty(mailOptions.to)) {
|
||||
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
|
||||
}
|
||||
if (isEmpty(mergedMessageOptions.subject)) {
|
||||
if (isEmpty(mailOptions.subject)) {
|
||||
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
|
||||
}
|
||||
if (isEmpty(mergedMessageOptions.body)) {
|
||||
if (isEmpty(mailOptions.message)) {
|
||||
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
|
||||
}
|
||||
return mergedMessageOptions;
|
||||
}
|
||||
|
||||
export const mergeAndValidateMailOptions = (
|
||||
mailOptions: CommonMailOptions,
|
||||
overridedOptions: Partial<CommonMailOptions>
|
||||
): CommonMailOptions => {
|
||||
const parsedMessageOptions = parseMailOptions(mailOptions, overridedOptions);
|
||||
validateRequiredMailOptions(parsedMessageOptions);
|
||||
|
||||
return parsedMessageOptions;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { difference, isEmpty } from 'lodash';
|
||||
import { difference, isEmpty, round, sumBy } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
@@ -23,20 +23,18 @@ export class CommandManualJournalValidators {
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) {
|
||||
let totalCredit = 0;
|
||||
let totalDebit = 0;
|
||||
|
||||
manualJournalDTO.entries.forEach((entry) => {
|
||||
if (entry.credit > 0) {
|
||||
totalCredit += entry.credit;
|
||||
}
|
||||
if (entry.debit > 0) {
|
||||
totalDebit += entry.debit;
|
||||
}
|
||||
});
|
||||
const totalCredit = round(
|
||||
sumBy(manualJournalDTO.entries, (entry) => entry.credit || 0),
|
||||
2
|
||||
);
|
||||
const totalDebit = round(
|
||||
sumBy(manualJournalDTO.entries, (entry) => entry.debit || 0),
|
||||
2
|
||||
);
|
||||
if (totalCredit <= 0 || totalDebit <= 0) {
|
||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL_ZERO);
|
||||
}
|
||||
|
||||
if (totalCredit !== totalDebit) {
|
||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export class GetInvoicePaymentLinkMetadata {
|
||||
.findOne('linkId', linkId)
|
||||
.where('resourceType', 'SaleInvoice')
|
||||
.throwIfNotFound();
|
||||
|
||||
|
||||
// Validate the expiry at date.
|
||||
if (paymentLink.expiryAt) {
|
||||
const currentDate = moment();
|
||||
@@ -46,6 +46,7 @@ export class GetInvoicePaymentLinkMetadata {
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('taxes.taxRate')
|
||||
.withGraphFetched('paymentMethods.paymentIntegration')
|
||||
.withGraphFetched('pdfTemplate')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(
|
||||
|
||||
@@ -12,9 +12,11 @@ export class GetPaymentLinkInvoicePdf {
|
||||
* Retrieves the sale invoice PDF of the given payment link id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} paymentLinkId
|
||||
* @returns {Promise<Buffer>}
|
||||
* @returns {Promise<Buffer, string>}
|
||||
*/
|
||||
async getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||
async getPaymentLinkInvoicePdf(
|
||||
paymentLinkId: string
|
||||
): Promise<[Buffer, string]> {
|
||||
const paymentLink = await PaymentLink.query()
|
||||
.findOne('linkId', paymentLinkId)
|
||||
.where('resourceType', 'SaleInvoice')
|
||||
|
||||
@@ -11,7 +11,7 @@ export class PaymentLinksApplication {
|
||||
|
||||
@Inject()
|
||||
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||
|
||||
|
||||
@Inject()
|
||||
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
|
||||
|
||||
@@ -45,7 +45,9 @@ export class PaymentLinksApplication {
|
||||
* @param {number} paymentLinkId
|
||||
* @returns {Promise<Buffer> }
|
||||
*/
|
||||
public getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||
public getPaymentLinkInvoicePdf(
|
||||
paymentLinkId: string
|
||||
): Promise<[Buffer, string]> {
|
||||
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
|
||||
paymentLinkId
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user