Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
cf78255220 | ||
|
|
f9aa6abdd7 | ||
|
|
0a5e40a0d8 | ||
|
|
4aa445fe35 | ||
|
|
1a1095c99b | ||
|
|
d92b46aa7b | ||
|
|
682d40cbf8 | ||
|
|
b62f3b3fa6 | ||
|
|
e76d3b15ce | ||
|
|
9edfb83221 | ||
|
|
bbdfe00c7a | ||
|
|
e3942551cd | ||
|
|
a0c1a21983 | ||
|
|
3358ce58bc | ||
|
|
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,
|
||||
|
||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -2,6 +2,43 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
# [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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -30,17 +30,17 @@ block head
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.#{prefix}-big-title {
|
||||
font-size: 60px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
max-height: 160px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-terms-list {
|
||||
display: flex;
|
||||
@@ -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
|
||||
|
||||
@@ -30,17 +30,17 @@ block head
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.#{prefix}-big-title {
|
||||
font-size: 60px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
max-height: 160px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-terms {
|
||||
display: flex;
|
||||
@@ -96,6 +96,9 @@ block head
|
||||
.#{prefix}-table__header--right {
|
||||
text-align: right;
|
||||
}
|
||||
.#{prefix}-table__header--item{
|
||||
width: 50%;
|
||||
}
|
||||
.#{prefix}-table__cell {
|
||||
border-bottom: 1px solid #F6F6F6;
|
||||
padding: 12px 10px;
|
||||
@@ -109,6 +112,14 @@ block head
|
||||
.#{prefix}-table__cell:last-of-type {
|
||||
padding-right: 0;
|
||||
}
|
||||
.#{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,6 +154,7 @@ block head
|
||||
color: #666;
|
||||
}
|
||||
.#{prefix}-statement__value {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
block content
|
||||
@@ -192,17 +204,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 ${prefix}-table__header--right`) Rate
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
|
||||
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
|
||||
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}
|
||||
td(class=`${prefix}-table__cell ${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}
|
||||
|
||||
//- Totals section
|
||||
div(class=`${prefix}-totals`)
|
||||
@@ -216,12 +231,12 @@ block content
|
||||
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||
|
||||
//- Statements section
|
||||
if showTermsConditions && termsConditions
|
||||
div(class=`${prefix}-statement`)
|
||||
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
|
||||
div(class=`${prefix}-statement__value`) #{termsConditions}
|
||||
|
||||
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}
|
||||
@@ -30,17 +30,17 @@ block head
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.#{prefix}-big-title {
|
||||
font-size: 60px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
max-height: 160px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-details {
|
||||
display: flex;
|
||||
@@ -102,6 +102,9 @@ block head
|
||||
.#{prefix}-table__header--right {
|
||||
text-align: right;
|
||||
}
|
||||
.#{prefix}-table__header--item {
|
||||
width: 50%;
|
||||
}
|
||||
.#{prefix}-table__cell {
|
||||
border-bottom: 1px solid #F6F6F6;
|
||||
padding: 12px 10px;
|
||||
@@ -115,6 +118,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__description {
|
||||
color: #5f6b7c;
|
||||
}
|
||||
.#{prefix}-totals {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -149,7 +160,7 @@ block head
|
||||
color: #666;
|
||||
}
|
||||
.#{prefix}-paragraph__value {
|
||||
/* Styles for values within the paragraph section */
|
||||
white-space: pre-line;
|
||||
}
|
||||
block content
|
||||
//- block head
|
||||
@@ -199,15 +210,18 @@ block content
|
||||
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}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{lineItemLabel}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{lineQuantityLabel}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{lineRateLabel}
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--total ${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--item`)
|
||||
div.item
|
||||
div.item__label #{line.item}
|
||||
div.item__description #{line.description}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.quantity}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
||||
|
||||
|
||||
@@ -31,17 +31,17 @@ block head
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.#{prefix}-big-title{
|
||||
font-size: 60px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
max-height: 160px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-terms-list{
|
||||
display: flex;
|
||||
|
||||
@@ -28,13 +28,13 @@ block head
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.#{prefix}-logo-wrap img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
max-height: 160px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 260px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.#{prefix}-big-title {
|
||||
font-size: 60px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
font-weight: 500;
|
||||
@@ -92,6 +92,9 @@ block head
|
||||
.#{prefix}-table__header--right {
|
||||
text-align: right;
|
||||
}
|
||||
.#{prefix}-table__header--item{
|
||||
width: 50%;
|
||||
}
|
||||
.#{prefix}-table__cell {
|
||||
border-bottom: 1px solid #F6F6F6;
|
||||
padding: 12px 10px;
|
||||
@@ -105,6 +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;
|
||||
@@ -133,7 +144,9 @@ block head
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.#{prefix}-statement__label {}
|
||||
.#{prefix}-statement__value {}
|
||||
.#{prefix}-statement__value {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
block content
|
||||
//- block head
|
||||
@@ -178,17 +191,20 @@ block content
|
||||
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
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
|
||||
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
|
||||
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${prefix}-table__cell--right`)= line.rate
|
||||
td(class=`${prefix}-table__cell${prefix}-table__cell--right`)= line.total
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
|
||||
div.item
|
||||
div.item__label #{line.item}
|
||||
div.item__description #{line.description}
|
||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.quantity}
|
||||
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`)
|
||||
|
||||
@@ -121,7 +121,7 @@ export default class BillsController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
|
||||
@@ -170,7 +170,7 @@ export default class VendorCreditController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
@@ -209,7 +209,7 @@ export default class VendorCreditController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
|
||||
@@ -233,7 +233,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
@@ -471,13 +471,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 {
|
||||
@@ -754,9 +755,8 @@ export default class PaymentReceivesController extends BaseController {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data = await this.getCreditNoteStateService.getCreditNoteState(
|
||||
tenantId
|
||||
);
|
||||
const data =
|
||||
await this.getCreditNoteStateService.getCreditNoteState(tenantId);
|
||||
return res.status(200).send({ data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -408,7 +408,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data = await this.paymentReceiveApplication.getPaymentReceivedState(
|
||||
@@ -473,7 +473,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
]);
|
||||
// Response in pdf format.
|
||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||
const pdfContent =
|
||||
const [pdfContent, filename] =
|
||||
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
@@ -481,6 +481,7 @@ 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.
|
||||
|
||||
@@ -172,7 +172,7 @@ 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')
|
||||
@@ -398,13 +398,15 @@ export default class SalesEstimatesController extends BaseController {
|
||||
]);
|
||||
// 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.
|
||||
@@ -560,9 +562,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data = await this.saleEstimatesApplication.getSaleEstimateState(
|
||||
tenantId
|
||||
);
|
||||
const data =
|
||||
await this.saleEstimatesApplication.getSaleEstimateState(tenantId);
|
||||
return res.status(200).send({ data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -179,10 +179,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 +201,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)),
|
||||
@@ -438,19 +449,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 +796,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 +810,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
|
||||
);
|
||||
|
||||
@@ -113,7 +113,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,7 +148,7 @@ 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 })
|
||||
@@ -356,13 +356,15 @@ export default class SalesReceiptsController extends BaseController {
|
||||
]);
|
||||
// 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.
|
||||
@@ -390,9 +392,8 @@ export default class SalesReceiptsController extends BaseController {
|
||||
|
||||
// Retrieves receipt in pdf format.
|
||||
try {
|
||||
const data = await this.saleReceiptsApplication.getSaleReceiptState(
|
||||
tenantId
|
||||
);
|
||||
const data =
|
||||
await this.saleReceiptsApplication.getSaleReceiptState(tenantId);
|
||||
return res.status(200).send({ data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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> {}
|
||||
|
||||
@@ -234,7 +234,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 +276,7 @@ export interface ISaleInvoiceMailSend {
|
||||
tenantId: number;
|
||||
saleInvoiceId: number;
|
||||
messageOptions: SendInvoiceMailDTO;
|
||||
formattedMessageOptions: SaleInvoiceMailOptions;
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceMailSent {
|
||||
@@ -312,21 +338,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;
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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,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)
|
||||
: '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as R from 'ramda';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { isNil } from 'lodash';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class BrandingTemplateDTOTransformer {
|
||||
@@ -22,11 +21,12 @@ export class BrandingTemplateDTOTransformer {
|
||||
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||
const attributeName = 'pdfTemplateId';
|
||||
|
||||
const defaultTemplate = await PdfTemplate.query().findOne({
|
||||
resource,
|
||||
default: true,
|
||||
});
|
||||
if (!defaultTemplate || !isEmpty(object[attributeName])) {
|
||||
const defaultTemplate = await PdfTemplate.query()
|
||||
.modify('default')
|
||||
.findOne({ resource });
|
||||
|
||||
// If the default template is not found OR the given object has no defined template id.
|
||||
if (!defaultTemplate || !isNil(object[attributeName])) {
|
||||
return object;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
import { getUploadedObjectUri } from '../Attachments/utils';
|
||||
|
||||
export class GetPdfTemplateTransformer extends Transformer {
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* Included attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
@@ -44,20 +43,10 @@ export class GetPdfTemplateTransformer extends Transformer {
|
||||
|
||||
class GetPdfTemplateAttributesTransformer extends Transformer {
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* Included attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['companyLogoUri'];
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the company logo uri.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected companyLogoUri(template) {
|
||||
return template.companyLogoKey
|
||||
? getUploadedObjectUri(template.companyLogoKey)
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ export interface ICreateInvoicePdfTemplateDTO {
|
||||
showStatement?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface CommonOrganizationBrandingAttributes {
|
||||
companyName?: string;
|
||||
primaryColor?: string;
|
||||
|
||||
@@ -3,6 +3,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';
|
||||
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class GetSaleEstimate {
|
||||
@@ -15,6 +17,9 @@ export class GetSaleEstimate {
|
||||
@Inject()
|
||||
private validators: SaleEstimateValidators;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve the estimate details with associated entries.
|
||||
* @async
|
||||
@@ -35,10 +40,18 @@ export class GetSaleEstimate {
|
||||
this.validators.validateEstimateExistance(estimate);
|
||||
|
||||
// Transformes sale estimate model to POJO.
|
||||
return this.transformer.transform(
|
||||
const transformed = await this.transformer.transform(
|
||||
tenantId,
|
||||
estimate,
|
||||
new SaleEstimateTransfromer()
|
||||
);
|
||||
const eventPayload = { tenantId, saleEstimateId: estimateId };
|
||||
|
||||
// Triggers `onSaleEstimateViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleEstimate.onViewed,
|
||||
eventPayload
|
||||
);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate';
|
||||
import { transformEstimateToPdfTemplate } from './utils';
|
||||
import { EstimatePdfBrandingAttributes } from './constants';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimatesPdf {
|
||||
@@ -24,12 +26,22 @@ export class SaleEstimatesPdf {
|
||||
@Inject()
|
||||
private estimatePdfTemplate: SaleEstimatePdfTemplate;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {number} tenantId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
*/
|
||||
public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
|
||||
public async getSaleEstimatePdf(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<[Buffer, string]> {
|
||||
const filename = await this.getSaleEstimateFilename(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
const brandingAttributes = await this.getEstimateBrandingAttributes(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
@@ -39,7 +51,32 @@ export class SaleEstimatesPdf {
|
||||
'modules/estimate-regular',
|
||||
brandingAttributes
|
||||
);
|
||||
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
|
||||
const content = await this.chromiumlyTenancy.convertHtmlContent(
|
||||
tenantId,
|
||||
htmlContent
|
||||
);
|
||||
const eventPayload = { tenantId, saleEstimateId };
|
||||
|
||||
// Triggers the `onSaleEstimatePdfViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleEstimate.onPdfViewed,
|
||||
eventPayload
|
||||
);
|
||||
return [content, filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename file document of the given estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} estimateId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getSaleEstimateFilename(tenantId: number, estimateId: number) {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
const estimate = await SaleEstimate.query().findById(estimateId);
|
||||
|
||||
return `Estimate-${estimate.estimateNumber}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,9 +13,10 @@ import {
|
||||
SaleEstimateMailOptionsDTO,
|
||||
} from '@/interfaces';
|
||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { transformEstimateToMailDataArgs } from './utils';
|
||||
|
||||
@Service()
|
||||
export class SendSaleEstimateMail {
|
||||
@@ -65,23 +66,17 @@ export class SendSaleEstimateMail {
|
||||
}
|
||||
|
||||
/**
|
||||
* Formates the text of the mail.
|
||||
* Formate the text of the mail.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} estimateId - Estimate id.
|
||||
* @returns {Promise<Record<string, any>>}
|
||||
*/
|
||||
public formatterData = async (tenantId: number, estimateId: number) => {
|
||||
public formatterArgs = async (tenantId: number, estimateId: number) => {
|
||||
const estimate = await this.getSaleEstimateService.getEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return {
|
||||
CustomerName: estimate.customer.displayName,
|
||||
EstimateNumber: estimate.estimateNumber,
|
||||
EstimateDate: estimate.formattedEstimateDate,
|
||||
EstimateAmount: estimate.formattedAmount,
|
||||
EstimateExpirationDate: estimate.formattedExpirationDate,
|
||||
};
|
||||
return transformEstimateToMailDataArgs(estimate);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -92,7 +87,9 @@ export class SendSaleEstimateMail {
|
||||
*/
|
||||
public getMailOptions = async (
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
saleEstimateId: number,
|
||||
defaultSubject: string = DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
|
||||
defaultMessage: string = DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT
|
||||
): Promise<SaleEstimateMailOptions> => {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -100,22 +97,44 @@ export class SendSaleEstimateMail {
|
||||
.findById(saleEstimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const formatterData = await this.formatterData(tenantId, saleEstimateId);
|
||||
const formatArgs = await this.formatterArgs(tenantId, saleEstimateId);
|
||||
|
||||
const mailOptions = await this.contactMailNotification.getMailOptions(
|
||||
tenantId,
|
||||
saleEstimate.customerId,
|
||||
DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
|
||||
DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT,
|
||||
formatterData
|
||||
);
|
||||
const mailOptions =
|
||||
await this.contactMailNotification.getDefaultMailOptions(
|
||||
tenantId,
|
||||
saleEstimate.customerId
|
||||
);
|
||||
return {
|
||||
...mailOptions,
|
||||
data: formatterData,
|
||||
message: defaultMessage,
|
||||
subject: defaultSubject,
|
||||
attachEstimate: true,
|
||||
formatArgs,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the given mail options.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @param {SaleEstimateMailOptions} mailOptions
|
||||
* @returns {Promise<SaleEstimateMailOptions>}
|
||||
*/
|
||||
public formatMailOptions = async (
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
mailOptions: SaleEstimateMailOptions
|
||||
): Promise<SaleEstimateMailOptions> => {
|
||||
const formatterArgs = await this.formatterArgs(tenantId, saleEstimateId);
|
||||
const formattedOptions =
|
||||
await this.contactMailNotification.formatMailOptions(
|
||||
tenantId,
|
||||
mailOptions,
|
||||
formatterArgs
|
||||
);
|
||||
return { ...formattedOptions };
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends the mail notification of the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
@@ -133,27 +152,54 @@ export class SendSaleEstimateMail {
|
||||
saleEstimateId
|
||||
);
|
||||
// Overrides and validates the given mail options.
|
||||
const messageOpts = parseAndValidateMailOptions(
|
||||
const parsedMessageOptions = mergeAndValidateMailOptions(
|
||||
localMessageOpts,
|
||||
messageOptions
|
||||
) as SaleEstimateMailOptions;
|
||||
|
||||
const formattedOptions = await this.formatMailOptions(
|
||||
tenantId,
|
||||
saleEstimateId,
|
||||
parsedMessageOptions
|
||||
);
|
||||
const mail = new Mail()
|
||||
.setSubject(messageOpts.subject)
|
||||
.setTo(messageOpts.to)
|
||||
.setContent(messageOpts.body);
|
||||
.setSubject(formattedOptions.subject)
|
||||
.setTo(formattedOptions.to)
|
||||
.setCC(formattedOptions.cc)
|
||||
.setBCC(formattedOptions.bcc)
|
||||
.setContent(formattedOptions.message);
|
||||
|
||||
// Attaches the estimate pdf to the mail.
|
||||
if (formattedOptions.attachEstimate) {
|
||||
// Retrieves the estimate pdf and attaches it to the mail.
|
||||
const [estimatePdfBuffer, estimateFilename] =
|
||||
await this.estimatePdf.getSaleEstimatePdf(tenantId, saleEstimateId);
|
||||
|
||||
if (messageOpts.attachEstimate) {
|
||||
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
mail.setAttachments([
|
||||
{
|
||||
filename: messageOpts.data?.EstimateNumber || 'estimate.pdf',
|
||||
filename: `${estimateFilename}.pdf`,
|
||||
content: estimatePdfBuffer,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
saleEstimateId,
|
||||
messageOptions,
|
||||
formattedOptions,
|
||||
};
|
||||
// Triggers `onSaleEstimateMailSend` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleEstimate.onMailSend,
|
||||
eventPayload as ISaleEstimateMailPresendEvent
|
||||
);
|
||||
await mail.send();
|
||||
|
||||
// Triggers `onSaleEstimateMailSent` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleEstimate.onMailSent,
|
||||
eventPayload as ISaleEstimateMailPresendEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export const DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT =
|
||||
'Estimate {EstimateNumber} is awaiting your approval';
|
||||
export const DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT = `<p>Dear {CustomerName}</p>
|
||||
'Estimate {Estimate Number} is awaiting your approval';
|
||||
export const DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT = `<p>Dear {Customer Name}</p>
|
||||
<p>Thank you for your business, You can view or print your estimate from attachements.</p>
|
||||
<p>
|
||||
Estimate <strong>#{EstimateNumber}</strong><br />
|
||||
Expiration Date : <strong>{EstimateExpirationDate}</strong><br />
|
||||
Amount : <strong>{EstimateAmount}</strong></br />
|
||||
Estimate <strong>#{Estimate Number}</strong><br />
|
||||
Expiration Date : <strong>{Estimate Expiration Date}</strong><br />
|
||||
Amount : <strong>{Estimate Amount}</strong></br />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>Regards</i><br />
|
||||
<i>{CompanyName}</i>
|
||||
<i>{Company Name}</i>
|
||||
</p>
|
||||
`;
|
||||
|
||||
|
||||
@@ -17,8 +17,18 @@ export const transformEstimateToPdfTemplate = (
|
||||
})),
|
||||
total: estimate.formattedSubtotal,
|
||||
subtotal: estimate.formattedSubtotal,
|
||||
customerNote: estimate.customerNote,
|
||||
customerNote: estimate.note,
|
||||
termsConditions: estimate.termsConditions,
|
||||
customerAddress: contactAddressTextFormat(estimate.customer),
|
||||
};
|
||||
};
|
||||
|
||||
export const transformEstimateToMailDataArgs = (estimate: any) => {
|
||||
return {
|
||||
'Customer Name': estimate.customer.displayName,
|
||||
'Estimate Number': estimate.estimateNumber,
|
||||
'Estimate Date': estimate.formattedEstimateDate,
|
||||
'Estimate Amount': estimate.formattedAmount,
|
||||
'Estimate Expiration Date': estimate.formattedExpirationDate,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -32,15 +32,14 @@ export class GenerateShareLink {
|
||||
*/
|
||||
async generatePaymentLink(
|
||||
tenantId: number,
|
||||
transactionId: number,
|
||||
transactionType: string,
|
||||
saleInvoiceId: number,
|
||||
publicity: string = 'private',
|
||||
expiryTime: string = ''
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundInvoice = await SaleInvoice.query()
|
||||
.findById(transactionId)
|
||||
.findById(saleInvoiceId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Generate unique uuid for sharable link.
|
||||
@@ -48,8 +47,7 @@ export class GenerateShareLink {
|
||||
|
||||
const commonEventPayload = {
|
||||
tenantId,
|
||||
transactionId,
|
||||
transactionType,
|
||||
saleInvoiceId,
|
||||
publicity,
|
||||
expiryTime,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer
|
||||
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||
import { GetPdfTemplateTransformer } from '@/services/PdfTemplate/GetPdfTemplateTransformer';
|
||||
|
||||
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
|
||||
/**
|
||||
@@ -45,6 +46,7 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
||||
'isReceivable',
|
||||
'hasStripePaymentMethod',
|
||||
'formattedCustomerAddress',
|
||||
'brandingTemplate',
|
||||
];
|
||||
};
|
||||
|
||||
@@ -63,6 +65,18 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the branding template for the payment link.
|
||||
* @param {} invoice
|
||||
* @returns
|
||||
*/
|
||||
public brandingTemplate(invoice) {
|
||||
return this.item(
|
||||
invoice.pdfTemplate,
|
||||
new GetInvoicePaymentLinkBrandingTemplate()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entries of the sale invoice.
|
||||
* @param {ISaleInvoice} invoice
|
||||
@@ -114,7 +128,7 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
||||
|
||||
/**
|
||||
* Retrieves the formatted customer address.
|
||||
* @param invoice
|
||||
* @param invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCustomerAddress(invoice) {
|
||||
@@ -193,3 +207,17 @@ class GetInvoicePaymentLinkTaxEntryTransformer extends SaleInvoiceTaxEntryTransf
|
||||
return ['name', 'taxRateCode', 'taxRateAmount', 'taxRateAmountFormatted'];
|
||||
};
|
||||
}
|
||||
|
||||
class GetInvoicePaymentLinkBrandingTemplate extends GetPdfTemplateTransformer {
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['companyLogoUri', 'primaryColor'];
|
||||
};
|
||||
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
primaryColor = (template) => {
|
||||
return template.attributes?.primaryColor;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
|
||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import {
|
||||
InvoicePaymentEmailProps,
|
||||
renderInvoicePaymentEmail,
|
||||
} from '@bigcapital/email-components';
|
||||
import { GetInvoiceMailTemplateAttributesTransformer } from './GetInvoicePaymentMailAttributesTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetInvoicePaymentMail {
|
||||
@Inject()
|
||||
private getSaleInvoiceService: GetSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private getBrandingTemplate: GetPdfTemplate;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the mail template attributes of the given invoice.
|
||||
* Invoice template attributes are composed of the invoice and branding template attributes.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Invoice id.
|
||||
*/
|
||||
public async getMailTemplateAttributes(tenantId: number, invoiceId: number) {
|
||||
const invoice = await this.getSaleInvoiceService.getSaleInvoice(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
const brandingTemplate = await this.getBrandingTemplate.getPdfTemplate(
|
||||
tenantId,
|
||||
invoice.pdfTemplateId
|
||||
);
|
||||
const mailTemplateAttributes = await this.transformer.transform(
|
||||
tenantId,
|
||||
invoice,
|
||||
new GetInvoiceMailTemplateAttributesTransformer(),
|
||||
{
|
||||
invoice,
|
||||
brandingTemplate,
|
||||
}
|
||||
);
|
||||
return mailTemplateAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mail template html content.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Invoice id.
|
||||
*/
|
||||
public async getMailTemplate(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
overrideAttributes?: Partial<InvoicePaymentEmailProps>
|
||||
): Promise<string> {
|
||||
const attributes = await this.getMailTemplateAttributes(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
const mergedAttributes = { ...attributes, ...overrideAttributes };
|
||||
|
||||
return renderInvoicePaymentEmail(mergedAttributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
|
||||
export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to item entry object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'companyLogoUri',
|
||||
'companyName',
|
||||
|
||||
'invoiceAmount',
|
||||
|
||||
'primaryColor',
|
||||
|
||||
'invoiceAmount',
|
||||
'invoiceMessage',
|
||||
|
||||
'dueDate',
|
||||
'dueDateLabel',
|
||||
|
||||
'invoiceNumber',
|
||||
'invoiceNumberLabel',
|
||||
|
||||
'total',
|
||||
'totalLabel',
|
||||
|
||||
'dueAmount',
|
||||
'dueAmountLabel',
|
||||
|
||||
'viewInvoiceButtonLabel',
|
||||
'viewInvoiceButtonUrl',
|
||||
|
||||
'items',
|
||||
];
|
||||
};
|
||||
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
public companyLogoUri(): string {
|
||||
return this.options.brandingTemplate?.companyLogoUri;
|
||||
}
|
||||
|
||||
public companyName(): string {
|
||||
return this.context.organization.name;
|
||||
}
|
||||
|
||||
public invoiceAmount(): string {
|
||||
return this.options.invoice.totalFormatted;
|
||||
}
|
||||
|
||||
public primaryColor(): string {
|
||||
return this.options?.brandingTemplate?.attributes?.primaryColor;
|
||||
}
|
||||
|
||||
public invoiceMessage(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public dueDate(): string {
|
||||
return this.options?.invoice?.dueDateFormatted;
|
||||
}
|
||||
|
||||
public dueDateLabel(): string {
|
||||
return 'Due {dueDate}';
|
||||
}
|
||||
|
||||
public invoiceNumber(): string {
|
||||
return this.options?.invoice?.invoiceNo;
|
||||
}
|
||||
|
||||
public invoiceNumberLabel(): string {
|
||||
return 'Invoice # {invoiceNumber}';
|
||||
}
|
||||
|
||||
public total(): string {
|
||||
return this.options.invoice?.totalFormatted;
|
||||
}
|
||||
|
||||
public totalLabel(): string {
|
||||
return 'Total';
|
||||
}
|
||||
|
||||
public dueAmount(): string {
|
||||
return this.options?.invoice.dueAmountFormatted;
|
||||
}
|
||||
|
||||
public dueAmountLabel(): string {
|
||||
return 'Due Amount';
|
||||
}
|
||||
|
||||
public viewInvoiceButtonLabel(): string {
|
||||
return 'View Invoice';
|
||||
}
|
||||
|
||||
public viewInvoiceButtonUrl(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public items(): Array<any> {
|
||||
return this.item(
|
||||
this.options.invoice?.entries,
|
||||
new GetInvoiceMailTemplateItemAttrsTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GetInvoiceMailTemplateItemAttrsTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to item entry object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['quantity', 'label', 'rate'];
|
||||
};
|
||||
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
public quantity(entry): string {
|
||||
return entry?.quantity;
|
||||
}
|
||||
|
||||
public label(entry): string {
|
||||
console.log(entry);
|
||||
return entry?.item?.name;
|
||||
}
|
||||
|
||||
public rate(entry): string {
|
||||
return entry?.rateFormatted;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export class GetSaleInvoice {
|
||||
@@ -16,6 +18,9 @@ export class GetSaleInvoice {
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice with associated entries.
|
||||
* @param {Number} saleInvoiceId -
|
||||
@@ -41,10 +46,20 @@ export class GetSaleInvoice {
|
||||
// Validates the given sale invoice existance.
|
||||
this.validators.validateInvoiceExistance(saleInvoice);
|
||||
|
||||
return this.transformer.transform(
|
||||
const transformed = await this.transformer.transform(
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
new SaleInvoiceTransformer()
|
||||
);
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
};
|
||||
// Triggers the `onSaleInvoiceItemViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onViewed,
|
||||
eventPayload
|
||||
);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { SaleInvoiceMailOptions, SaleInvoiceMailState } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject } from 'typedi';
|
||||
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { GetSaleInvoiceMailStateTransformer } from './GetSaleInvoiceMailStateTransformer';
|
||||
|
||||
export class GetSaleInvoiceMailState {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private invoiceMail: SendSaleInvoiceMailCommon;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the invoice mail state of the given sale invoice.
|
||||
* Invoice mail state includes the mail options, branding attributes and the invoice details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @returns {Promise<SaleInvoiceMailState>}
|
||||
*/
|
||||
async getInvoiceMailState(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<SaleInvoiceMailState> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('pdfTemplate')
|
||||
.throwIfNotFound();
|
||||
|
||||
const mailOptions = await this.invoiceMail.getInvoiceMailOptions(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Transforms the sale invoice mail state.
|
||||
const transformed = await this.transformer.transform(
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
new GetSaleInvoiceMailStateTransformer(),
|
||||
{
|
||||
mailOptions,
|
||||
}
|
||||
);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||
import { ItemEntryTransformer } from './ItemEntryTransformer';
|
||||
|
||||
export class GetSaleInvoiceMailStateTransformer extends SaleInvoiceTransformer {
|
||||
/**
|
||||
* Exclude these attributes from user object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Included attributes.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'invoiceDate',
|
||||
'invoiceDateFormatted',
|
||||
|
||||
'dueDate',
|
||||
'dueDateFormatted',
|
||||
|
||||
'dueAmount',
|
||||
'dueAmountFormatted',
|
||||
|
||||
'total',
|
||||
'totalFormatted',
|
||||
|
||||
'subtotal',
|
||||
'subtotalFormatted',
|
||||
|
||||
'invoiceNo',
|
||||
|
||||
'entries',
|
||||
|
||||
'companyName',
|
||||
'companyLogoUri',
|
||||
|
||||
'primaryColor',
|
||||
|
||||
'customerName',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the customer name of the invoice.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected customerName = (invoice) => {
|
||||
return invoice.customer.displayName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the company name.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected companyName = () => {
|
||||
return this.context.organization.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the company logo uri.
|
||||
* @returns {string | null}
|
||||
*/
|
||||
protected companyLogoUri = (invoice) => {
|
||||
return invoice.pdfTemplate?.companyLogoUri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the primary color.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected primaryColor = (invoice) => {
|
||||
return invoice.pdfTemplate?.attributes?.primaryColor;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param invoice
|
||||
* @returns
|
||||
*/
|
||||
protected entries = (invoice) => {
|
||||
return this.item(
|
||||
invoice.entries,
|
||||
new GetSaleInvoiceMailStateEntryTransformer(),
|
||||
{
|
||||
currencyCode: invoice.currencyCode,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the mail options with the invoice object.
|
||||
*/
|
||||
public transform = (object: any) => {
|
||||
return {
|
||||
...this.options.mailOptions,
|
||||
...object,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class GetSaleInvoiceMailStateEntryTransformer extends ItemEntryTransformer {
|
||||
/**
|
||||
* Exclude these attributes from user object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
public name = (entry) => {
|
||||
return entry.item.name;
|
||||
};
|
||||
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'name',
|
||||
'quantity',
|
||||
'quantityFormatted',
|
||||
'rate',
|
||||
'rateFormatted',
|
||||
'total',
|
||||
'totalFormatted',
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -33,8 +33,12 @@ export class SaleEstimatePdfTemplate {
|
||||
...defaultEstimatePdfBrandingAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
brandingTemplateAttrs,
|
||||
orgainizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { renderInvoicePaperTemplateHtml } from '@bigcapital/pdf-templates';
|
||||
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
||||
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||
@@ -6,6 +7,9 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { transformInvoiceToPdfTemplate } from './utils';
|
||||
import { InvoicePdfTemplateAttributes } from '@/interfaces';
|
||||
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { renderInvoicePaymentEmail } from '@bigcapital/email-components';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoicePdf {
|
||||
@@ -15,36 +19,78 @@ export class SaleInvoicePdf {
|
||||
@Inject()
|
||||
private chromiumlyTenancy: ChromiumlyTenancy;
|
||||
|
||||
@Inject()
|
||||
private templateInjectable: TemplateInjectable;
|
||||
|
||||
@Inject()
|
||||
private getInvoiceService: GetSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private invoiceBrandingTemplateService: SaleInvoicePdfTemplate;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* Retrieve sale invoice html content.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
* @returns {Promise<Buffer>}
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async saleInvoicePdf(
|
||||
public async saleInvoiceHtml(
|
||||
tenantId: number,
|
||||
invoiceId: number
|
||||
): Promise<Buffer> {
|
||||
): Promise<string> {
|
||||
const brandingAttributes = await this.getInvoiceBrandingAttributes(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
const htmlContent = await this.templateInjectable.render(
|
||||
tenantId,
|
||||
'modules/invoice-standard',
|
||||
brandingAttributes
|
||||
);
|
||||
return renderInvoicePaperTemplateHtml({
|
||||
...brandingAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
* @returns {Promise<[Buffer, string]>}
|
||||
*/
|
||||
public async saleInvoicePdf(
|
||||
tenantId: number,
|
||||
invoiceId: number
|
||||
): Promise<[Buffer, string]> {
|
||||
const filename = await this.getInvoicePdfFilename(tenantId, invoiceId);
|
||||
|
||||
const htmlContent = await this.saleInvoiceHtml(tenantId, invoiceId);
|
||||
|
||||
// Converts the given html content to pdf document.
|
||||
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
|
||||
const buffer = await this.chromiumlyTenancy.convertHtmlContent(
|
||||
tenantId,
|
||||
htmlContent
|
||||
);
|
||||
const eventPayload = { tenantId, saleInvoiceId: invoiceId };
|
||||
|
||||
// Triggers the `onSaleInvoicePdfViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onPdfViewed,
|
||||
eventPayload
|
||||
);
|
||||
return [buffer, filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename pdf document of the given invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getInvoicePdfFilename(
|
||||
tenantId: number,
|
||||
invoiceId: number
|
||||
): Promise<string> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const invoice = await SaleInvoice.query().findById(invoiceId);
|
||||
|
||||
return `Invoice-${invoice.invoiceNo}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,8 +32,12 @@ export class SaleInvoicePdfTemplate {
|
||||
...defaultInvoicePdfTemplateAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
brandingTemplateAttrs,
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ISystemUser,
|
||||
ITenantUser,
|
||||
InvoiceNotificationType,
|
||||
SaleInvoiceMailState,
|
||||
SendInvoiceMailDTO,
|
||||
} from '@/interfaces';
|
||||
import { Inject, Service } from 'typedi';
|
||||
@@ -27,8 +28,8 @@ import { GetInvoicePaymentsService } from './GetInvoicePaymentsService';
|
||||
import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
|
||||
import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
|
||||
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
|
||||
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
|
||||
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
|
||||
import { GetSaleInvoiceMailState } from './GetSaleInvoiceMailState';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoiceApplication {
|
||||
@@ -72,7 +73,7 @@ export class SaleInvoiceApplication {
|
||||
private sendSaleInvoiceMailService: SendSaleInvoiceMail;
|
||||
|
||||
@Inject()
|
||||
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
|
||||
private getSaleInvoiceMailStateService: GetSaleInvoiceMailState;
|
||||
|
||||
@Inject()
|
||||
private getSaleInvoiceStateService: GetSaleInvoiceState;
|
||||
@@ -272,6 +273,19 @@ export class SaleInvoiceApplication {
|
||||
return this.pdfSaleInvoiceService.saleInvoicePdf(tenantId, saleInvoiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the html content of the given sale invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public saleInvoiceHtml(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<string> {
|
||||
return this.pdfSaleInvoiceService.saleInvoiceHtml(tenantId, saleInvoiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
@@ -361,10 +375,13 @@ export class SaleInvoiceApplication {
|
||||
* Retrieves the default mail options of the given sale invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceid
|
||||
* @returns {Promise<SendInvoiceMailDTO>}
|
||||
* @returns {Promise<SaleInvoiceMailState>}
|
||||
*/
|
||||
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
||||
return this.sendSaleInvoiceMailService.getMailOption(
|
||||
public getSaleInvoiceMailState(
|
||||
tenantId: number,
|
||||
saleInvoiceid: number
|
||||
): Promise<SaleInvoiceMailState> {
|
||||
return this.getSaleInvoiceMailStateService.getInvoiceMailState(
|
||||
tenantId,
|
||||
saleInvoiceid
|
||||
);
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||
} from './constants';
|
||||
import { GetInvoicePaymentMail } from './GetInvoicePaymentMail';
|
||||
import { GenerateShareLink } from './GenerateeInvoicePaymentLink';
|
||||
|
||||
@Service()
|
||||
export class SendSaleInvoiceMailCommon {
|
||||
@@ -19,6 +21,12 @@ export class SendSaleInvoiceMailCommon {
|
||||
@Inject()
|
||||
private contactMailNotification: ContactMailNotification;
|
||||
|
||||
@Inject()
|
||||
private getInvoicePaymentMail: GetInvoicePaymentMail;
|
||||
|
||||
@Inject()
|
||||
private generatePaymentLinkService: GenerateShareLink;
|
||||
|
||||
/**
|
||||
* Retrieves the mail options.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
@@ -27,11 +35,11 @@ export class SendSaleInvoiceMailCommon {
|
||||
* @param {string} defaultBody - Subject body.
|
||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||
*/
|
||||
public async getMailOption(
|
||||
public async getInvoiceMailOptions(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||
defaultBody: string = DEFAULT_INVOICE_MAIL_CONTENT
|
||||
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
|
||||
): Promise<SaleInvoiceMailOptions> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -39,21 +47,66 @@ export class SendSaleInvoiceMailCommon {
|
||||
.findById(invoiceId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const formatterData = await this.formatText(tenantId, invoiceId);
|
||||
const contactMailDefaultOptions =
|
||||
await this.contactMailNotification.getDefaultMailOptions(
|
||||
tenantId,
|
||||
saleInvoice.customerId
|
||||
);
|
||||
const formatArgs = await this.getInvoiceFormatterArgs(tenantId, invoiceId);
|
||||
|
||||
const mailOptions = await this.contactMailNotification.getMailOptions(
|
||||
tenantId,
|
||||
saleInvoice.customerId,
|
||||
defaultSubject,
|
||||
defaultBody,
|
||||
formatterData
|
||||
);
|
||||
return {
|
||||
...mailOptions,
|
||||
...contactMailDefaultOptions,
|
||||
subject: defaultSubject,
|
||||
message: defaultMessage,
|
||||
attachInvoice: true,
|
||||
formatArgs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given invoice mail options.
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceId
|
||||
* @param {SaleInvoiceMailOptions} mailOptions
|
||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||
*/
|
||||
public async formatInvoiceMailOptions(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
mailOptions: SaleInvoiceMailOptions
|
||||
): Promise<SaleInvoiceMailOptions> {
|
||||
const formatterArgs = await this.getInvoiceFormatterArgs(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
const formattedOptions =
|
||||
await this.contactMailNotification.formatMailOptions(
|
||||
tenantId,
|
||||
mailOptions,
|
||||
formatterArgs
|
||||
);
|
||||
// Generates the a new payment link for the given invoice.
|
||||
const paymentLink =
|
||||
await this.generatePaymentLinkService.generatePaymentLink(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
'public'
|
||||
);
|
||||
const message = await this.getInvoicePaymentMail.getMailTemplate(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
{
|
||||
// # Invoice message
|
||||
invoiceMessage: formattedOptions.message,
|
||||
preview: formattedOptions.message,
|
||||
|
||||
// # Payment link
|
||||
viewInvoiceButtonUrl: paymentLink.link,
|
||||
}
|
||||
);
|
||||
return { ...formattedOptions, message };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted text of the given sale invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
@@ -61,7 +114,7 @@ export class SendSaleInvoiceMailCommon {
|
||||
* @param {string} text - The given text.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public formatText = async (
|
||||
public getInvoiceFormatterArgs = async (
|
||||
tenantId: number,
|
||||
invoiceId: number
|
||||
): Promise<Record<string, string | number>> => {
|
||||
@@ -69,15 +122,18 @@ export class SendSaleInvoiceMailCommon {
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
|
||||
const commonArgs = await this.contactMailNotification.getCommonFormatArgs(
|
||||
tenantId
|
||||
);
|
||||
return {
|
||||
CustomerName: invoice.customer.displayName,
|
||||
InvoiceNumber: invoice.invoiceNo,
|
||||
InvoiceDueAmount: invoice.dueAmountFormatted,
|
||||
InvoiceDueDate: invoice.dueDateFormatted,
|
||||
InvoiceDate: invoice.invoiceDateFormatted,
|
||||
InvoiceAmount: invoice.totalFormatted,
|
||||
OverdueDays: invoice.overdueDays,
|
||||
...commonArgs,
|
||||
'Customer Name': invoice.customer.displayName,
|
||||
'Invoice Number': invoice.invoiceNo,
|
||||
'Invoice Due Amount': invoice.dueAmountFormatted,
|
||||
'Invoice Due Date': invoice.dueDateFormatted,
|
||||
'Invoice Date': invoice.invoiceDateFormatted,
|
||||
'Invoice Amount': invoice.totalFormatted,
|
||||
'Overdue Days': invoice.overdueDays,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import Mail from '@/lib/Mail';
|
||||
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
|
||||
import {
|
||||
ISaleInvoiceMailSend,
|
||||
SaleInvoiceMailOptions,
|
||||
SendInvoiceMailDTO,
|
||||
} from '@/interfaces';
|
||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||
import {
|
||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||
} from './constants';
|
||||
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import events from '@/subscribers/events';
|
||||
import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class SendSaleInvoiceMail {
|
||||
@@ -19,12 +19,12 @@ export class SendSaleInvoiceMail {
|
||||
@Inject()
|
||||
private invoiceMail: SendSaleInvoiceMailCommon;
|
||||
|
||||
@Inject('agenda')
|
||||
private agenda: any;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject('agenda')
|
||||
private agenda: any;
|
||||
|
||||
/**
|
||||
* Sends the invoice mail of the given sale invoice.
|
||||
* @param {number} tenantId
|
||||
@@ -52,17 +52,30 @@ export class SendSaleInvoiceMail {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mail options of the given sale invoice.
|
||||
* Retrieves the formatted mail options.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {SendInvoiceMailDTO} messageOptions
|
||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||
*/
|
||||
public async getMailOption(tenantId: number, saleInvoiceId: number) {
|
||||
return this.invoiceMail.getMailOption(
|
||||
async getFormattedMailOptions(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
messageOptions: SendInvoiceMailDTO
|
||||
): Promise<SaleInvoiceMailOptions> {
|
||||
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Merges message options with default options and parses the options values.
|
||||
const parsedMessageOptions = mergeAndValidateMailOptions(
|
||||
defaultMessageOptions,
|
||||
messageOptions
|
||||
);
|
||||
return this.invoiceMail.formatInvoiceMailOptions(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||
DEFAULT_INVOICE_MAIL_CONTENT
|
||||
parsedMessageOptions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,44 +91,46 @@ export class SendSaleInvoiceMail {
|
||||
saleInvoiceId: number,
|
||||
messageOptions: SendInvoiceMailDTO
|
||||
) {
|
||||
const defaultMessageOpts = await this.getMailOption(
|
||||
const formattedMessageOptions = await this.getFormattedMailOptions(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Merge message opts with default options and validate the incoming options.
|
||||
const messageOpts = parseAndValidateMailOptions(
|
||||
defaultMessageOpts,
|
||||
saleInvoiceId,
|
||||
messageOptions
|
||||
);
|
||||
const mail = new Mail()
|
||||
.setSubject(messageOpts.subject)
|
||||
.setTo(messageOpts.to)
|
||||
.setContent(messageOpts.body);
|
||||
.setSubject(formattedMessageOptions.subject)
|
||||
.setTo(formattedMessageOptions.to)
|
||||
.setCC(formattedMessageOptions.cc)
|
||||
.setBCC(formattedMessageOptions.bcc)
|
||||
.setContent(formattedMessageOptions.message);
|
||||
|
||||
if (messageOpts.attachInvoice) {
|
||||
// Attach invoice document.
|
||||
if (formattedMessageOptions.attachInvoice) {
|
||||
// Retrieves document buffer of the invoice pdf document.
|
||||
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
const [invoicePdfBuffer, invoiceFilename] =
|
||||
await this.invoicePdf.saleInvoicePdf(tenantId, saleInvoiceId);
|
||||
|
||||
mail.setAttachments([
|
||||
{ filename: 'invoice.pdf', content: invoicePdfBuffer },
|
||||
{ filename: `${invoiceFilename}.pdf`, content: invoicePdfBuffer },
|
||||
]);
|
||||
}
|
||||
// Triggers the event `onSaleInvoiceSend`.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSend, {
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
messageOptions,
|
||||
} as ISaleInvoiceMailSend);
|
||||
formattedMessageOptions,
|
||||
} as ISaleInvoiceMailSend;
|
||||
|
||||
// Triggers the event `onSaleInvoiceSend`.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onMailSend,
|
||||
eventPayload
|
||||
);
|
||||
await mail.send();
|
||||
|
||||
// Triggers the event `onSaleInvoiceSend`.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSent, {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
messageOptions,
|
||||
} as ISaleInvoiceMailSend);
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onMailSent,
|
||||
eventPayload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import config from '@/config';
|
||||
|
||||
export const DEFAULT_INVOICE_MAIL_SUBJECT =
|
||||
'Invoice {InvoiceNumber} from {CompanyName}';
|
||||
export const DEFAULT_INVOICE_MAIL_CONTENT = `
|
||||
<p>Dear {CustomerName}</p>
|
||||
<p>Thank you for your business, You can view or print your invoice from attachements.</p>
|
||||
<p>
|
||||
Invoice <strong>#{InvoiceNumber}</strong><br />
|
||||
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
||||
Amount : <strong>{InvoiceAmount}</strong></br />
|
||||
</p>
|
||||
'Invoice {Invoice Number} from {Company Name} for {Customer Name}';
|
||||
export const DEFAULT_INVOICE_MAIL_CONTENT = `Hi {Customer Name},
|
||||
|
||||
<p>
|
||||
<i>Regards</i><br />
|
||||
<i>{CompanyName}</i>
|
||||
</p>
|
||||
Here's invoice # {Invoice Number} for {Invoice Amount}
|
||||
|
||||
The amount outstanding of {Invoice Due Amount} is due on {Invoice Due Date}.
|
||||
|
||||
From your online payment page you can print a PDF or view your outstanding bills.
|
||||
|
||||
If you have any questions, please let us know.
|
||||
|
||||
Thanks,
|
||||
{Company Name}
|
||||
`;
|
||||
|
||||
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =
|
||||
@@ -194,7 +193,7 @@ export const defaultInvoicePdfTemplateAttributes = {
|
||||
|
||||
// Entries
|
||||
lineItemLabel: 'Item',
|
||||
lineDescriptionLabel: 'Description',
|
||||
lineQuantityLabel: 'Qty',
|
||||
lineRateLabel: 'Rate',
|
||||
lineTotalLabel: 'Total',
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const transformInvoiceToPdfTemplate = (
|
||||
total: invoice.totalFormatted,
|
||||
subtotal: invoice.subtotalFormatted,
|
||||
paymentMade: invoice.paymentAmountFormatted,
|
||||
balanceDue: invoice.balanceAmountFormatted,
|
||||
dueAmount: invoice.dueAmountFormatted,
|
||||
|
||||
termsConditions: invoice.termsConditions,
|
||||
statement: invoice.invoiceMessage,
|
||||
|
||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate';
|
||||
import { transformPaymentReceivedToPdfTemplate } from './utils';
|
||||
import { PaymentReceivedPdfTemplateAttributes } from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export default class GetPaymentReceivedPdf {
|
||||
@@ -24,6 +26,9 @@ export default class GetPaymentReceivedPdf {
|
||||
@Inject()
|
||||
private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {number} tenantId -
|
||||
@@ -32,19 +37,51 @@ export default class GetPaymentReceivedPdf {
|
||||
*/
|
||||
async getPaymentReceivePdf(
|
||||
tenantId: number,
|
||||
paymentReceiveId: number
|
||||
): Promise<Buffer> {
|
||||
paymentReceivedId: number
|
||||
): Promise<[Buffer, string]> {
|
||||
const brandingAttributes = await this.getPaymentBrandingAttributes(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
paymentReceivedId
|
||||
);
|
||||
const htmlContent = await this.templateInjectable.render(
|
||||
tenantId,
|
||||
'modules/payment-receive-standard',
|
||||
brandingAttributes
|
||||
);
|
||||
const filename = await this.getPaymentReceivedFilename(
|
||||
tenantId,
|
||||
paymentReceivedId
|
||||
);
|
||||
// Converts the given html content to pdf document.
|
||||
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
|
||||
const content = await this.chromiumlyTenancy.convertHtmlContent(
|
||||
tenantId,
|
||||
htmlContent
|
||||
);
|
||||
const eventPayload = { tenantId, paymentReceivedId };
|
||||
|
||||
// Triggers the `onCreditNotePdfViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.paymentReceive.onPdfViewed,
|
||||
eventPayload
|
||||
);
|
||||
return [content, filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename of the given payment.
|
||||
* @param {number} tenantId
|
||||
* @param {number} paymentReceivedId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getPaymentReceivedFilename(
|
||||
tenantId: number,
|
||||
paymentReceivedId: number
|
||||
): Promise<string> {
|
||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||
|
||||
const payment = await PaymentReceive.query().findById(paymentReceivedId);
|
||||
|
||||
return `Payment-${payment.paymentReceiveNo}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,8 +37,12 @@ export class PaymentReceivedBrandingTemplate {
|
||||
...defaultPaymentReceivedPdfTemplateAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
brandingTemplateAttrs,
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -13,9 +13,10 @@ import {
|
||||
} from './constants';
|
||||
import { GetPaymentReceived } from './GetPaymentReceived';
|
||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { transformPaymentReceivedToMailDataArgs } from './utils';
|
||||
|
||||
@Service()
|
||||
export class SendPaymentReceiveMailNotification {
|
||||
@@ -77,15 +78,19 @@ export class SendPaymentReceiveMailNotification {
|
||||
.findById(paymentId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const formatterData = await this.textFormatter(tenantId, paymentId);
|
||||
const formatArgs = await this.textFormatter(tenantId, paymentId);
|
||||
|
||||
return this.contactMailNotification.getMailOptions(
|
||||
tenantId,
|
||||
paymentReceive.customerId,
|
||||
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
||||
DEFAULT_PAYMENT_MAIL_CONTENT,
|
||||
formatterData
|
||||
);
|
||||
const mailOptions =
|
||||
await this.contactMailNotification.getDefaultMailOptions(
|
||||
tenantId,
|
||||
paymentReceive.customerId
|
||||
);
|
||||
return {
|
||||
...mailOptions,
|
||||
subject: DEFAULT_PAYMENT_MAIL_SUBJECT,
|
||||
message: DEFAULT_PAYMENT_MAIL_CONTENT,
|
||||
...formatArgs,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,19 +108,46 @@ export class SendPaymentReceiveMailNotification {
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
return {
|
||||
CustomerName: payment.customer.displayName,
|
||||
PaymentNumber: payment.payment_receive_no,
|
||||
PaymentDate: payment.formattedPaymentDate,
|
||||
PaymentAmount: payment.formattedAmount,
|
||||
};
|
||||
return transformPaymentReceivedToMailDataArgs(payment);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted mail options of the given payment receive.
|
||||
* @param {number} tenantId
|
||||
* @param {number} paymentReceiveId
|
||||
* @param {SendInvoiceMailDTO} messageDTO
|
||||
* @returns {Promise<PaymentReceiveMailOpts>}
|
||||
*/
|
||||
public getFormattedMailOptions = async (
|
||||
tenantId: number,
|
||||
paymentReceiveId: number,
|
||||
messageDTO: SendInvoiceMailDTO
|
||||
) => {
|
||||
const formatterArgs = await this.textFormatter(tenantId, paymentReceiveId);
|
||||
|
||||
// Default message options.
|
||||
const defaultMessageOpts = await this.getMailOptions(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
// Parsed message opts with default options.
|
||||
const parsedMessageOpts = mergeAndValidateMailOptions(
|
||||
defaultMessageOpts,
|
||||
messageDTO
|
||||
);
|
||||
// Formats the message options.
|
||||
return this.contactMailNotification.formatMailOptions(
|
||||
tenantId,
|
||||
parsedMessageOpts,
|
||||
formatterArgs
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers the mail invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {SendInvoiceMailDTO} messageDTO
|
||||
* @param {number} saleInvoiceId - Invoice id.
|
||||
* @param {SendInvoiceMailDTO} messageDTO - Message options.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async sendMail(
|
||||
@@ -123,19 +155,35 @@ export class SendPaymentReceiveMailNotification {
|
||||
paymentReceiveId: number,
|
||||
messageDTO: SendInvoiceMailDTO
|
||||
): Promise<void> {
|
||||
const defaultMessageOpts = await this.getMailOptions(
|
||||
// Retrieves the formatted mail options.
|
||||
const formattedMessageOptions = await this.getFormattedMailOptions(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
// Parsed message opts with default options.
|
||||
const parsedMessageOpts = parseAndValidateMailOptions(
|
||||
defaultMessageOpts,
|
||||
paymentReceiveId,
|
||||
messageDTO
|
||||
);
|
||||
await new Mail()
|
||||
.setSubject(parsedMessageOpts.subject)
|
||||
.setTo(parsedMessageOpts.to)
|
||||
.setContent(parsedMessageOpts.body)
|
||||
.send();
|
||||
const mail = new Mail()
|
||||
.setSubject(formattedMessageOptions.subject)
|
||||
.setTo(formattedMessageOptions.to)
|
||||
.setCC(formattedMessageOptions.cc)
|
||||
.setBCC(formattedMessageOptions.bcc)
|
||||
.setContent(formattedMessageOptions.message);
|
||||
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
paymentReceiveId,
|
||||
messageOptions: formattedMessageOptions,
|
||||
};
|
||||
// Triggers `onPaymentReceiveMailSend` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.paymentReceive.onMailSend,
|
||||
eventPayload
|
||||
);
|
||||
await mail.send();
|
||||
|
||||
// Triggers `onPaymentReceiveMailSent` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.paymentReceive.onMailSent,
|
||||
eventPayload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
export const DEFAULT_PAYMENT_MAIL_SUBJECT = 'Payment Received by {CompanyName}';
|
||||
export const DEFAULT_PAYMENT_MAIL_SUBJECT =
|
||||
'Payment Received for {Customer Name} from {Company Name}';
|
||||
export const DEFAULT_PAYMENT_MAIL_CONTENT = `
|
||||
<p>Dear {CustomerName}</p>
|
||||
<p>Dear {Customer Name}</p>
|
||||
<p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p>
|
||||
<p>
|
||||
Payment Date : <strong>{PaymentDate}</strong><br />
|
||||
Amount : <strong>{PaymentAmount}</strong></br />
|
||||
Payment Date : <strong>{Payment Date}</strong><br />
|
||||
Amount : <strong>{Payment Amount}</strong></br />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>Regards</i><br />
|
||||
<i>{CompanyName}</i>
|
||||
<i>{Company Name}</i>
|
||||
</p>
|
||||
`;
|
||||
|
||||
|
||||
@@ -21,3 +21,12 @@ export const transformPaymentReceivedToPdfTemplate = (
|
||||
customerAddress: contactAddressTextFormat(payment.customer),
|
||||
};
|
||||
};
|
||||
|
||||
export const transformPaymentReceivedToMailDataArgs = (payment: any) => {
|
||||
return {
|
||||
'Customer Name': payment.customer.displayName,
|
||||
'Payment Number': payment.paymentReceiveNo,
|
||||
'Payment Date': payment.formattedPaymentDate,
|
||||
'Payment Amount': payment.formattedAmount,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,8 +37,12 @@ export class SaleReceiptBrandingTemplate {
|
||||
...defaultSaleReceiptBrandingAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
brandingTemplateAttrs,
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -13,9 +13,10 @@ import {
|
||||
SaleReceiptMailOptsDTO,
|
||||
} from '@/interfaces';
|
||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { transformReceiptToMailDataArgs } from './utils';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptMailNotification {
|
||||
@@ -79,18 +80,19 @@ export class SaleReceiptMailNotification {
|
||||
.findById(saleReceiptId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const formattedData = await this.textFormatter(tenantId, saleReceiptId);
|
||||
const formatArgs = await this.textFormatterArgs(tenantId, saleReceiptId);
|
||||
|
||||
const mailOpts = await this.contactMailNotification.getMailOptions(
|
||||
tenantId,
|
||||
saleReceipt.customerId,
|
||||
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||
DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||
formattedData
|
||||
);
|
||||
const mailOptions =
|
||||
await this.contactMailNotification.getDefaultMailOptions(
|
||||
tenantId,
|
||||
saleReceipt.customerId
|
||||
);
|
||||
return {
|
||||
...mailOpts,
|
||||
...mailOptions,
|
||||
message: DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||
subject: DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||
attachReceipt: true,
|
||||
formatArgs,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +103,7 @@ export class SaleReceiptMailNotification {
|
||||
* @param {string} text - The given text.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public textFormatter = async (
|
||||
public textFormatterArgs = async (
|
||||
tenantId: number,
|
||||
receiptId: number
|
||||
): Promise<Record<string, string>> => {
|
||||
@@ -109,19 +111,66 @@ export class SaleReceiptMailNotification {
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
return {
|
||||
CustomerName: receipt.customer.displayName,
|
||||
ReceiptNumber: receipt.receiptNumber,
|
||||
ReceiptDate: receipt.formattedReceiptDate,
|
||||
ReceiptAmount: receipt.formattedAmount,
|
||||
};
|
||||
return transformReceiptToMailDataArgs(receipt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the mail options of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} receiptId
|
||||
* @param {SaleReceiptMailOpts} mailOptions
|
||||
* @returns {Promise<SaleReceiptMailOpts>}
|
||||
*/
|
||||
public async formatEstimateMailOptions(
|
||||
tenantId: number,
|
||||
receiptId: number,
|
||||
mailOptions: SaleReceiptMailOpts
|
||||
): Promise<SaleReceiptMailOpts> {
|
||||
const formatterArgs = await this.textFormatterArgs(tenantId, receiptId);
|
||||
const formattedOptions =
|
||||
(await this.contactMailNotification.formatMailOptions(
|
||||
tenantId,
|
||||
mailOptions,
|
||||
formatterArgs
|
||||
)) as SaleReceiptMailOpts;
|
||||
return formattedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted mail options of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @param {SaleReceiptMailOptsDTO} messageOpts
|
||||
* @returns {Promise<SaleReceiptMailOpts>}
|
||||
*/
|
||||
public getFormatMailOptions = async (
|
||||
tenantId: number,
|
||||
saleReceiptId: number,
|
||||
messageOpts: SaleReceiptMailOptsDTO
|
||||
): Promise<SaleReceiptMailOpts> => {
|
||||
const defaultMessageOptions = await this.getMailOptions(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
// Merges message opts with default options.
|
||||
const parsedMessageOpts = mergeAndValidateMailOptions(
|
||||
defaultMessageOptions,
|
||||
messageOpts
|
||||
) as SaleReceiptMailOpts;
|
||||
|
||||
// Formats the message options.
|
||||
return this.formatEstimateMailOptions(
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
parsedMessageOpts
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers the mail notification of the given sale receipt.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleReceiptId - Sale receipt id.
|
||||
* @param {SaleReceiptMailOpts} messageDTO - Overrided message options.
|
||||
* @param {SaleReceiptMailOpts} messageDTO - message options.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async sendMail(
|
||||
@@ -129,30 +178,43 @@ export class SaleReceiptMailNotification {
|
||||
saleReceiptId: number,
|
||||
messageOpts: SaleReceiptMailOptsDTO
|
||||
) {
|
||||
const defaultMessageOpts = await this.getMailOptions(
|
||||
// Formats the message options.
|
||||
const formattedMessageOptions = await this.getFormatMailOptions(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
// Merges message opts with default options.
|
||||
const parsedMessageOpts = parseAndValidateMailOptions(
|
||||
defaultMessageOpts,
|
||||
saleReceiptId,
|
||||
messageOpts
|
||||
);
|
||||
const mail = new Mail()
|
||||
.setSubject(parsedMessageOpts.subject)
|
||||
.setTo(parsedMessageOpts.to)
|
||||
.setContent(parsedMessageOpts.body);
|
||||
.setSubject(formattedMessageOptions.subject)
|
||||
.setTo(formattedMessageOptions.to)
|
||||
.setCC(formattedMessageOptions.cc)
|
||||
.setBCC(formattedMessageOptions.bcc)
|
||||
.setContent(formattedMessageOptions.message);
|
||||
|
||||
if (parsedMessageOpts.attachReceipt) {
|
||||
// Attaches the receipt pdf document.
|
||||
if (formattedMessageOptions.attachReceipt) {
|
||||
// Retrieves document buffer of the receipt pdf document.
|
||||
const receiptPdfBuffer = await this.receiptPdfService.saleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
const [receiptPdfBuffer, filename] =
|
||||
await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId);
|
||||
|
||||
mail.setAttachments([
|
||||
{ filename: 'receipt.pdf', content: receiptPdfBuffer },
|
||||
{ filename: `${filename}.pdf`, content: receiptPdfBuffer },
|
||||
]);
|
||||
}
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
messageOptions: {},
|
||||
};
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleReceipt.onMailSend,
|
||||
eventPayload
|
||||
);
|
||||
await mail.send();
|
||||
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleReceipt.onMailSent,
|
||||
eventPayload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user