mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
Compare commits
68 Commits
v0.20.1
...
add-estima
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3cd54653a8 | ||
|
|
6cad929738 | ||
|
|
184648040c | ||
|
|
df9d277e66 | ||
|
|
75ec315de2 | ||
|
|
c89b2367e6 | ||
|
|
bca5b3481c | ||
|
|
59996e7a40 | ||
|
|
af5726c48c | ||
|
|
90f08c5d51 | ||
|
|
a0a9f4a768 | ||
|
|
2649f1c326 | ||
|
|
c5ff1e4d4a | ||
|
|
c74c8e896a | ||
|
|
55fdc47ff0 | ||
|
|
126eb221d0 | ||
|
|
3c7e22be43 | ||
|
|
b23112bc92 |
37
CHANGELOG.md
37
CHANGELOG.md
@@ -2,6 +2,43 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
|
# [0.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]
|
# [0.20.0]
|
||||||
|
|
||||||
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
* 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">
|
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
||||||
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
||||||
</a>
|
</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">
|
<a href="https://discord.com/invite/c8nPBJafeb">
|
||||||
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"version": "independent",
|
"version": "independent",
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*",
|
||||||
|
"shared/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -4,11 +4,11 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "lerna run dev",
|
"dev": "lerna run dev",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
|
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
|
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"",
|
||||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
|
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"build:server": "lerna run build --scope \"@bigcapital/server\"",
|
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
|
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
@@ -29,5 +29,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tsup": "^8.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,7 @@ RUN chown node:node /
|
|||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
# Copy application dependency manifests to the container image.
|
# Copy application dependency manifests to the container image.
|
||||||
COPY ./package*.json ./
|
COPY --chown=node:node ./ ./
|
||||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
|
||||||
COPY ./lerna.json ./lerna.json
|
|
||||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
||||||
COPY ./packages/server/package*.json ./packages/server/
|
|
||||||
|
|
||||||
# Install application dependencies
|
# Install application dependencies
|
||||||
RUN apk update
|
RUN apk update
|
||||||
@@ -109,6 +105,6 @@ RUN pnpm install
|
|||||||
COPY --chown=node:node ./packages/server ./packages/server
|
COPY --chown=node:node ./packages/server ./packages/server
|
||||||
|
|
||||||
# # Creates a "dist" folder with the production build
|
# # Creates a "dist" folder with the production build
|
||||||
RUN npm run build:server --skip-nx-cache
|
RUN pnpm run build:server --skip-nx-cache
|
||||||
|
|
||||||
CMD [ "node", "./packages/server/build/index.js" ]
|
CMD [ "node", "./packages/server/build/index.js" ]
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.576.0",
|
"@aws-sdk/client-s3": "^3.576.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
||||||
|
"@bigcapital/utils": "*",
|
||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
|
|||||||
@@ -10,21 +10,37 @@ block head
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
}
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
.#{prefix}-big-title {
|
.#{prefix}-big-title {
|
||||||
font-size: 60px;
|
font-size: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.#{prefix}-logo-wrap {
|
.#{prefix}-logo-wrap img {
|
||||||
height: 120px;
|
width: 100%;
|
||||||
width: 120px;
|
height: 100%;
|
||||||
position: absolute;
|
max-width: 260px;
|
||||||
right: 26px;
|
max-height: 100px;
|
||||||
top: 26px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.#{prefix}-terms-list {
|
.#{prefix}-terms-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -92,7 +108,14 @@ block head
|
|||||||
.#{prefix}-table__cell--right {
|
.#{prefix}-table__cell--right {
|
||||||
text-align: 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 {
|
.#{prefix}-totals {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -127,27 +150,32 @@ block head
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
.#{prefix}-statement__value {
|
.#{prefix}-statement__value {
|
||||||
/* Styles for statement value */
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div(class=`${prefix}-root`)
|
div(class=`${prefix}-root`)
|
||||||
div(class=`${prefix}-big-title`) Credit Note
|
|
||||||
|
|
||||||
if showCompanyLogo && companyLogoUri
|
|
||||||
div(class=`${prefix}-logo-wrap`)
|
|
||||||
img(src=companyLogoUri alt=`Company Logo`)
|
|
||||||
|
|
||||||
div(class=`${prefix}-terms-list`)
|
//- Header (includes big title, details and logo)
|
||||||
if showCreditNoteNumber
|
div(class=`${prefix}-header`)
|
||||||
div(class=`${prefix}-terms-item`)
|
//- Header details (includes big title and details)
|
||||||
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
|
div(class=`${prefix}-header-details`)
|
||||||
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
|
div(class=`${prefix}-big-title`) Credit Note
|
||||||
|
|
||||||
if showCreditNoteDate
|
div(class=`${prefix}-terms-list`)
|
||||||
div(class=`${prefix}-terms-item`)
|
if showCreditNoteNumber
|
||||||
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
|
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
|
||||||
|
|
||||||
|
if showCreditNoteDate
|
||||||
|
div(class=`${prefix}-terms-item`)
|
||||||
|
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
|
||||||
|
|
||||||
|
if showCompanyLogo && companyLogoUri
|
||||||
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(src=companyLogoUri alt=`Company Logo`)
|
||||||
|
|
||||||
div(class=`${prefix}-address-section`)
|
div(class=`${prefix}-address-section`)
|
||||||
if showCompanyAddress
|
if showCompanyAddress
|
||||||
@@ -162,17 +190,20 @@ block content
|
|||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th(class=`${prefix}-table__header`) #{'Item'}
|
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{'Item'}
|
||||||
th(class=`${prefix}-table__header`) #{'Description'}
|
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{'Quantity'}
|
||||||
th(class=`${prefix}-table__header`) #{'Rate'}
|
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{'Rate'}
|
||||||
th(class=`${prefix}-table__header`) #{'Total'}
|
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) #{'Total'}
|
||||||
tbody
|
tbody
|
||||||
each line in lines
|
each line in lines
|
||||||
tr(class=`${prefix}-table__row`)
|
tr(class=`${prefix}-table__row`)
|
||||||
td(class=`${prefix}-table__cell`) #{line.item}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--item ${prefix}-table__cell--item`)
|
||||||
td(class=`${prefix}-table__cell`) #{line.description}
|
div.item
|
||||||
td(class=`${prefix}-table__cell--right`) #{line.rate}
|
div.item__label #{line.item}
|
||||||
td(class=`${prefix}-table__cell--right`) #{line.total}
|
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`)
|
div(class=`${prefix}-totals`)
|
||||||
if showSubtotal
|
if showSubtotal
|
||||||
|
|||||||
@@ -10,21 +10,37 @@ block head
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
}
|
}
|
||||||
|
.#{prefix}-header {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
.#{prefix}-big-title {
|
.#{prefix}-big-title {
|
||||||
font-size: 60px;
|
font-size: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.#{prefix}-logo-wrap {
|
.#{prefix}-logo-wrap img {
|
||||||
height: 120px;
|
width: 100%;
|
||||||
width: 120px;
|
height: 100%;
|
||||||
position: absolute;
|
max-width: 260px;
|
||||||
right: 26px;
|
max-height: 100px;
|
||||||
top: 26px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.#{prefix}-terms {
|
.#{prefix}-terms {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -80,6 +96,9 @@ block head
|
|||||||
.#{prefix}-table__header--right {
|
.#{prefix}-table__header--right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.#{prefix}-table__header--item{
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
.#{prefix}-table__cell {
|
.#{prefix}-table__cell {
|
||||||
border-bottom: 1px solid #F6F6F6;
|
border-bottom: 1px solid #F6F6F6;
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
@@ -93,6 +112,14 @@ block head
|
|||||||
.#{prefix}-table__cell:last-of-type {
|
.#{prefix}-table__cell:last-of-type {
|
||||||
padding-right: 0;
|
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 {
|
.#{prefix}-totals {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -127,30 +154,40 @@ block head
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
.#{prefix}-statement__value {
|
.#{prefix}-statement__value {
|
||||||
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
h1(class=`${prefix}-big-title`) Estimate
|
|
||||||
|
|
||||||
if showCompanyLogo && companyLogoUri
|
//- Header (invluces big title, details and logo)
|
||||||
div(class=`${prefix}-logo-wrap`)
|
div(class=`${prefix}-header`)
|
||||||
img(alt="Company logo", src=companyLogoUri)
|
|
||||||
|
|
||||||
//- Terms List
|
//- Header details (includes big title and details )
|
||||||
div(class=`${prefix}-terms`)
|
div(class=`${prefix}-header-details`)
|
||||||
if showEstimateNumber
|
h1(class=`${prefix}-big-title`) Estimate
|
||||||
div(class=`${prefix}-terms-item`)
|
|
||||||
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
//- Terms List
|
||||||
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
div(class=`${prefix}-terms`)
|
||||||
if showEstimateDate
|
if showEstimateNumber
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
||||||
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
||||||
if showExpirationDate
|
|
||||||
div(class=`${prefix}-terms-item`)
|
if showEstimateDate
|
||||||
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__value`) #{expirationDate}
|
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
||||||
|
|
||||||
|
if showExpirationDate
|
||||||
|
div(class=`${prefix}-terms-item`)
|
||||||
|
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{expirationDate}
|
||||||
|
|
||||||
|
//- Company logo
|
||||||
|
if showCompanyLogo && companyLogoUri
|
||||||
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(alt="Company logo", src=companyLogoUri)
|
||||||
|
|
||||||
//- Addresses (Group section)
|
//- Addresses (Group section)
|
||||||
div(class=`${prefix}-addresses`)
|
div(class=`${prefix}-addresses`)
|
||||||
@@ -167,17 +204,20 @@ block content
|
|||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th(class=`${prefix}-table__header`) Item
|
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
|
||||||
th(class=`${prefix}-table__header`) Description
|
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
|
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) Rate
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
|
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) Total
|
||||||
tbody
|
tbody
|
||||||
each line in lines
|
each line in lines
|
||||||
tr
|
tr
|
||||||
td(class=`${prefix}-table__cell`) #{line.item}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
|
||||||
td(class=`${prefix}-table__cell`) #{line.description}
|
div.item
|
||||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
|
div.item__label #{line.item}
|
||||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
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
|
//- Totals section
|
||||||
div(class=`${prefix}-totals`)
|
div(class=`${prefix}-totals`)
|
||||||
@@ -191,12 +231,12 @@ block content
|
|||||||
div(class=`${prefix}-totals__item-amount`) #{total}
|
div(class=`${prefix}-totals__item-amount`) #{total}
|
||||||
|
|
||||||
//- Statements section
|
//- Statements section
|
||||||
|
if showTermsConditions && termsConditions
|
||||||
|
div(class=`${prefix}-statement`)
|
||||||
|
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
|
||||||
|
div(class=`${prefix}-statement__value`) #{termsConditions}
|
||||||
|
|
||||||
if showCustomerNote && customerNote
|
if showCustomerNote && customerNote
|
||||||
div(class=`${prefix}-statement`)
|
div(class=`${prefix}-statement`)
|
||||||
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
|
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
|
||||||
div(class=`${prefix}-statement__value`) #{customerNote}
|
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}
|
|
||||||
@@ -10,21 +10,37 @@ block head
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
}
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
.#{prefix}-big-title {
|
.#{prefix}-big-title {
|
||||||
font-size: 60px;
|
font-size: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.#{prefix}-logo-wrap {
|
.#{prefix}-logo-wrap img {
|
||||||
height: 120px;
|
width: 100%;
|
||||||
width: 120px;
|
height: 100%;
|
||||||
position: absolute;
|
max-width: 260px;
|
||||||
right: 26px;
|
max-height: 100px;
|
||||||
top: 26px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.#{prefix}-details {
|
.#{prefix}-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -86,6 +102,9 @@ block head
|
|||||||
.#{prefix}-table__header--right {
|
.#{prefix}-table__header--right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.#{prefix}-table__header--item {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
.#{prefix}-table__cell {
|
.#{prefix}-table__cell {
|
||||||
border-bottom: 1px solid #F6F6F6;
|
border-bottom: 1px solid #F6F6F6;
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
@@ -99,6 +118,14 @@ block head
|
|||||||
.#{prefix}-table__cell--right {
|
.#{prefix}-table__cell--right {
|
||||||
text-align: 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 {
|
.#{prefix}-totals {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -133,35 +160,40 @@ block head
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
.#{prefix}-paragraph__value {
|
.#{prefix}-paragraph__value {
|
||||||
/* Styles for values within the paragraph section */
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
block content
|
block content
|
||||||
//- block head
|
//- block head
|
||||||
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
|
|
||||||
//- Title and company logo
|
|
||||||
h1(class=`${prefix}-big-title`) Invoice
|
|
||||||
|
|
||||||
if showCompanyLogo && companyLogoUri
|
//- Header (includes big title, details and logo )
|
||||||
div(class=`${prefix}-logo-wrap`)
|
div(class=`${prefix}-header`)
|
||||||
img(alt="Company logo", src=companyLogoUri)
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
//- Title and company logo
|
||||||
|
h1(class=`${prefix}-big-title`) Invoice
|
||||||
|
|
||||||
//- Invoice details
|
//- Invoice details
|
||||||
div(class=`${prefix}-details`)
|
div(class=`${prefix}-details`)
|
||||||
if showInvoiceNumber
|
if showInvoiceNumber
|
||||||
div(class=`${prefix}-detail`)
|
div(class=`${prefix}-detail`)
|
||||||
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
|
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
|
||||||
div(class=`${prefix}-detail__value`) #{invoiceNumber}
|
div(class=`${prefix}-detail__value`) #{invoiceNumber}
|
||||||
|
|
||||||
if showDateIssue
|
if showDateIssue
|
||||||
div(class=`${prefix}-detail`)
|
div(class=`${prefix}-detail`)
|
||||||
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
|
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
|
||||||
div(class=`${prefix}-detail__value`) #{dateIssue}
|
div(class=`${prefix}-detail__value`) #{dateIssue}
|
||||||
|
|
||||||
if showDueDate
|
if showDueDate
|
||||||
div(class=`${prefix}-detail`)
|
div(class=`${prefix}-detail`)
|
||||||
div(class=`${prefix}-detail__label`) #{dueDateLabel}
|
div(class=`${prefix}-detail__label`) #{dueDateLabel}
|
||||||
div(class=`${prefix}-detail__value`) #{dueDate}
|
div(class=`${prefix}-detail__value`) #{dueDate}
|
||||||
|
|
||||||
|
//- Company logo
|
||||||
|
if showCompanyLogo && companyLogoUri
|
||||||
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(alt="Company logo", src=companyLogoUri)
|
||||||
|
|
||||||
//- Address section
|
//- Address section
|
||||||
div(class=`${prefix}-address-root`)
|
div(class=`${prefix}-address-root`)
|
||||||
@@ -178,15 +210,18 @@ block content
|
|||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th(class=`${prefix}-table__header`) #{lineItemLabel}
|
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{lineItemLabel}
|
||||||
th(class=`${prefix}-table__header`) #{lineDescriptionLabel}
|
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{lineQuantityLabel}
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineRateLabel}
|
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{lineRateLabel}
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineTotalLabel}
|
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) #{lineTotalLabel}
|
||||||
tbody
|
tbody
|
||||||
each line in lines
|
each line in lines
|
||||||
tr
|
tr
|
||||||
td(class=`${prefix}-table__cell`) #{line.item}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
|
||||||
td(class=`${prefix}-table__cell`) #{line.description}
|
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.rate}
|
||||||
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
|
||||||
|
|
||||||
|
|||||||
@@ -10,22 +10,38 @@ block head
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
|
}
|
||||||
|
.#{prefix}-header{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
-webkit-box-align: start;
|
||||||
|
align-items: start;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
.#{prefix}-big-title{
|
.#{prefix}-big-title{
|
||||||
font-size: 60px;
|
font-size: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.#{prefix}-logo-wrap{
|
.#{prefix}-logo-wrap img {
|
||||||
height: 120px;
|
width: 100%;
|
||||||
width: 120px;
|
height: 100%;
|
||||||
position: absolute;
|
max-width: 260px;
|
||||||
right: 26px;
|
max-height: 100px;
|
||||||
top: 26px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.#{prefix}-terms-list{
|
.#{prefix}-terms-list{
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -120,23 +136,26 @@ block head
|
|||||||
}
|
}
|
||||||
block content
|
block content
|
||||||
div(class=`${prefix}-root`)
|
div(class=`${prefix}-root`)
|
||||||
div(class=`${prefix}-big-title`) Payment
|
//- Header (includes big title, details and logo )
|
||||||
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
div(class=`${prefix}-big-title`) Payment
|
||||||
|
div(class=`${prefix}-terms-list`)
|
||||||
|
if showPaymentReceivedNumber
|
||||||
|
div(class=`${prefix}-terms-item`)
|
||||||
|
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
|
||||||
|
|
||||||
if showCompanyLogo && companyLogoUri
|
if showPaymentReceivedDate
|
||||||
div(class=`${prefix}-logo-wrap`)
|
div(class=`${prefix}-terms-item`)
|
||||||
img(src=companyLogoUri alt="Company Logo")
|
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
||||||
|
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
||||||
div(class=`${prefix}-terms-list`)
|
|
||||||
if showPaymentReceivedNumber
|
|
||||||
div(class=`${prefix}-terms-item`)
|
|
||||||
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
|
|
||||||
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
|
|
||||||
|
|
||||||
if showPaymentReceivedDate
|
if showCompanyLogo && companyLogoUri
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-logo-wrap`)
|
||||||
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
img(src=companyLogoUri alt="Company Logo")
|
||||||
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
|
||||||
|
|
||||||
div(class=`${prefix}-addresses`)
|
div(class=`${prefix}-addresses`)
|
||||||
if showCompanyAddress
|
if showCompanyAddress
|
||||||
div(class=`${prefix}-address-from`)
|
div(class=`${prefix}-address-from`)
|
||||||
|
|||||||
@@ -10,19 +10,33 @@ block head
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
|
||||||
}
|
}
|
||||||
.#{prefix}-logo-wrap {
|
.#{prefix}-header{
|
||||||
height: 120px;
|
box-sizing: border-box;
|
||||||
width: 120px;
|
display: flex;
|
||||||
position: absolute;
|
flex-flow: wrap;
|
||||||
right: 26px;
|
flex: 0 0 auto;
|
||||||
top: 26px;
|
align-items: start;
|
||||||
overflow: hidden;
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.#{prefix}-header-details{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
.#{prefix}-logo-wrap img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
max-height: 100px;
|
||||||
}
|
}
|
||||||
.#{prefix}-big-title {
|
.#{prefix}-big-title {
|
||||||
font-size: 60px;
|
font-size: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@@ -78,6 +92,9 @@ block head
|
|||||||
.#{prefix}-table__header--right {
|
.#{prefix}-table__header--right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.#{prefix}-table__header--item{
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
.#{prefix}-table__cell {
|
.#{prefix}-table__cell {
|
||||||
border-bottom: 1px solid #F6F6F6;
|
border-bottom: 1px solid #F6F6F6;
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
@@ -91,6 +108,14 @@ block head
|
|||||||
.#{prefix}-table__cell--right {
|
.#{prefix}-table__cell--right {
|
||||||
text-align: 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 {
|
.#{prefix}-totals {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -119,30 +144,37 @@ block head
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.#{prefix}-statement__label {}
|
.#{prefix}-statement__label {}
|
||||||
.#{prefix}-statement__value {}
|
.#{prefix}-statement__value {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
block content
|
block content
|
||||||
//- block head
|
//- block head
|
||||||
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
|
||||||
|
|
||||||
//- Title and company logo
|
|
||||||
h1(class=`${prefix}-big-title`) Receipt
|
|
||||||
|
|
||||||
//- Company Logo
|
//- Header (includes big title, details and logo )
|
||||||
if showCompanyLogo && companyLogoUri
|
div(class=`${prefix}-header`)
|
||||||
div(class=`${prefix}-logo-wrap`)
|
//- Header details (includes big title and details )
|
||||||
img(src=companyLogoUri alt=`Company Logo`)
|
div(class=`${prefix}-header-details`)
|
||||||
|
//- Title and company logo
|
||||||
|
h1(class=`${prefix}-big-title`) Receipt
|
||||||
|
|
||||||
//- Terms List
|
//- Terms List
|
||||||
div(class=`${prefix}-terms-list`)
|
div(class=`${prefix}-terms-list`)
|
||||||
if showReceiptNumber
|
if showReceiptNumber
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
|
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
|
||||||
span(class=`${prefix}-terms-item__value`)= receiptNumber
|
span(class=`${prefix}-terms-item__value`)= receiptNumber
|
||||||
if showReceiptDate
|
|
||||||
div(class=`${prefix}-terms-item`)
|
if showReceiptDate
|
||||||
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
div(class=`${prefix}-terms-item`)
|
||||||
span(class=`${prefix}-terms-item__value`)= receiptDate
|
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
||||||
|
span(class=`${prefix}-terms-item__value`)= receiptDate
|
||||||
|
|
||||||
|
//- Company logo
|
||||||
|
if showCompanyLogo && companyLogoUri
|
||||||
|
div(class=`${prefix}-logo-wrap`)
|
||||||
|
img(src=companyLogoUri alt=`Company Logo`)
|
||||||
|
|
||||||
//- Address Section
|
//- Address Section
|
||||||
div(class=`${prefix}-address-section`)
|
div(class=`${prefix}-address-section`)
|
||||||
@@ -159,17 +191,20 @@ block content
|
|||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead(class=`${prefix}-table__header`)
|
thead(class=`${prefix}-table__header`)
|
||||||
tr
|
tr
|
||||||
th(class=`${prefix}-table__header`) Item
|
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
|
||||||
th(class=`${prefix}-table__header`) Description
|
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
|
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) Rate
|
||||||
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
|
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) Total
|
||||||
tbody
|
tbody
|
||||||
each line in lines
|
each line in lines
|
||||||
tr(class=`${prefix}-table__row`)
|
tr(class=`${prefix}-table__row`)
|
||||||
td(class=`${prefix}-table__cell`)= line.item
|
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
|
||||||
td(class=`${prefix}-table__cell`)= line.description
|
div.item
|
||||||
td(class=`${prefix}-table__cell${prefix}-table__cell--right`)= line.rate
|
div.item__label #{line.item}
|
||||||
td(class=`${prefix}-table__cell${prefix}-table__cell--right`)= line.total
|
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
|
//- Totals Section
|
||||||
div(class=`${prefix}-totals`)
|
div(class=`${prefix}-totals`)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCr
|
|||||||
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
||||||
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
||||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
import { GetCreditNoteState } from '@/services/CreditNotes/GetCreditNoteState';
|
||||||
/**
|
/**
|
||||||
* Credit notes controller.
|
* Credit notes controller.
|
||||||
* @service
|
* @service
|
||||||
@@ -81,6 +82,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
creditNotePdf: GetCreditNotePdf;
|
creditNotePdf: GetCreditNotePdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
getCreditNoteStateService: GetCreditNoteState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -105,6 +109,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.newCreditNote),
|
this.asyncMiddleware(this.newCreditNote),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(CreditNoteAction.View, AbilitySubject.CreditNote),
|
||||||
|
this.asyncMiddleware(this.getCreditNoteState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
// Get specific credit note.
|
// Get specific credit note.
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
@@ -461,13 +471,14 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
ACCEPT_TYPE.APPLICATION_PDF,
|
ACCEPT_TYPE.APPLICATION_PDF,
|
||||||
]);
|
]);
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||||
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
|
const [pdfContent, filename] = await this.creditNotePdf.getCreditNotePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNoteId
|
creditNoteId
|
||||||
);
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
} else {
|
} else {
|
||||||
@@ -736,6 +747,23 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getCreditNoteState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.getCreditNoteStateService.getCreditNoteState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
|
this.getPaymentReceivedState.bind(this),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
@@ -391,6 +397,29 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
*/
|
||||||
|
private async getPaymentReceivedState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.paymentReceiveApplication.getPaymentReceivedState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the given payment receive details.
|
* Retrieve the given payment receive details.
|
||||||
* @async
|
* @async
|
||||||
@@ -444,7 +473,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
]);
|
]);
|
||||||
// Response in pdf format.
|
// Response in pdf format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
|
||||||
const pdfContent =
|
const [pdfContent, filename] =
|
||||||
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
@@ -452,6 +481,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
// Response in json format.
|
// Response in json format.
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
router.post(
|
router.post(
|
||||||
'/:id/approve',
|
'/:id/approve',
|
||||||
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
||||||
[this.validateSpecificEstimateSchema],
|
[...this.validateSpecificEstimateSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
@@ -59,7 +59,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
router.post(
|
router.post(
|
||||||
'/:id/reject',
|
'/:id/reject',
|
||||||
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
|
||||||
[this.validateSpecificEstimateSchema],
|
[...this.validateSpecificEstimateSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
@@ -105,6 +105,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
||||||
|
this.getSaleEstimateState.bind(this),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
|
||||||
@@ -392,13 +398,15 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
]);
|
]);
|
||||||
// Retrieves estimate in pdf format.
|
// Retrieves estimate in pdf format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||||
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
|
const [pdfContent, filename] =
|
||||||
tenantId,
|
await this.saleEstimatesApplication.getSaleEstimatePdf(
|
||||||
estimateId
|
tenantId,
|
||||||
);
|
estimateId
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
// Retrieves estimates in json format.
|
// Retrieves estimates in json format.
|
||||||
@@ -546,6 +554,23 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getSaleEstimateState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleEstimatesApplication.getSaleEstimateState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
|
asyncMiddleware(this.getSaleInvoiceState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -138,6 +144,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -434,13 +441,15 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
]);
|
]);
|
||||||
// Retrieves invoice in pdf format.
|
// Retrieves invoice in pdf format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||||
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
|
const [pdfContent, filename] =
|
||||||
tenantId,
|
await this.saleInvoiceApplication.saleInvoicePdf(
|
||||||
saleInvoiceId
|
tenantId,
|
||||||
);
|
saleInvoiceId
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
// Retrieves invoice in json format.
|
// Retrieves invoice in json format.
|
||||||
@@ -453,6 +462,24 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
return res.status(200).send({ saleInvoice });
|
return res.status(200).send({ saleInvoice });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getSaleInvoiceState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleInvoiceApplication.getSaleInvoiceState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve paginated sales invoices with custom view metadata.
|
* Retrieve paginated sales invoices with custom view metadata.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
|
asyncMiddleware(this.getSaleReceiptState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
@@ -350,13 +356,15 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
]);
|
]);
|
||||||
// Retrieves receipt in pdf format.
|
// Retrieves receipt in pdf format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
||||||
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
|
const [pdfContent, filename] =
|
||||||
tenantId,
|
await this.saleReceiptsApplication.getSaleReceiptPdf(
|
||||||
saleReceiptId
|
tenantId,
|
||||||
);
|
saleReceiptId
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
// Retrieves receipt in json format.
|
// Retrieves receipt in json format.
|
||||||
@@ -369,6 +377,30 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getSaleReceiptState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
// Retrieves receipt in pdf format.
|
||||||
|
try {
|
||||||
|
const data = await this.saleReceiptsApplication.getSaleReceiptState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sale receipt notification via SMS.
|
* Sale receipt notification via SMS.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ export class PublicSharableLinkController extends BaseController {
|
|||||||
this.getPaymentLinkPublicMeta.bind(this),
|
this.getPaymentLinkPublicMeta.bind(this),
|
||||||
this.validationResult
|
this.validationResult
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice/pdf',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkInvoicePdf.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:paymentLinkId/stripe_checkout_session',
|
'/:paymentLinkId/stripe_checkout_session',
|
||||||
[param('paymentLinkId').exists()],
|
[param('paymentLinkId').exists()],
|
||||||
@@ -80,4 +87,31 @@ export class PublicSharableLinkController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkInvoicePdf(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
res.send(pdfContent);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,22 @@ export const SALE_INVOICE_CREATED = 'Sale invoice created';
|
|||||||
export const SALE_INVOICE_EDITED = 'Sale invoice edited';
|
export const SALE_INVOICE_EDITED = 'Sale invoice edited';
|
||||||
export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
|
export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
|
||||||
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
|
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_ESTIMATE_CREATED = 'Sale estimate created';
|
export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
|
||||||
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
|
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
|
||||||
export const SALE_ESTIMATE_DELETED = 'Sale estimate deleted';
|
export const SALE_ESTIMATE_DELETED = 'Sale estimate deleted';
|
||||||
|
export const SALE_ESTIMATE_PDF_VIEWED = 'Sale estimate PDF viewed';
|
||||||
|
|
||||||
export const PAYMENT_RECEIVED_CREATED = 'Payment received created';
|
export const PAYMENT_RECEIVED_CREATED = 'Payment received created';
|
||||||
export const PAYMENT_RECEIVED_EDITED = 'payment received edited';
|
export const PAYMENT_RECEIVED_EDITED = 'payment received edited';
|
||||||
export const PAYMENT_RECEIVED_DELETED = 'Payment received deleted';
|
export const PAYMENT_RECEIVED_DELETED = 'Payment received deleted';
|
||||||
|
export const PAYMENT_RECEIVED_PDF_VIEWED = 'Payment received PDF viewed';
|
||||||
|
|
||||||
|
export const SALE_RECEIPT_PDF_VIEWED = 'Sale credit PDF viewed';
|
||||||
|
|
||||||
|
export const CREDIT_NOTE_PDF_VIEWED = 'Credit note PDF viewed';
|
||||||
|
|
||||||
export const BILL_CREATED = 'Bill created';
|
export const BILL_CREATED = 'Bill created';
|
||||||
export const BILL_EDITED = 'Bill edited';
|
export const BILL_EDITED = 'Bill edited';
|
||||||
@@ -26,10 +34,12 @@ export const EXPENSE_DELETED = 'Expense deleted';
|
|||||||
export const ACCOUNT_CREATED = 'Account created';
|
export const ACCOUNT_CREATED = 'Account created';
|
||||||
export const ACCOUNT_EDITED = 'Account Edited';
|
export const ACCOUNT_EDITED = 'Account Edited';
|
||||||
export const ACCOUNT_DELETED = 'Account deleted';
|
export const ACCOUNT_DELETED = 'Account deleted';
|
||||||
|
export const ACCOUNT_VIEWED = 'Account viewed';
|
||||||
|
|
||||||
export const ITEM_EVENT_CREATED = 'Item created';
|
export const ITEM_EVENT_CREATED = 'Item created';
|
||||||
export const ITEM_EVENT_EDITED = 'Item edited';
|
export const ITEM_EVENT_EDITED = 'Item edited';
|
||||||
export const ITEM_EVENT_DELETED = 'Item deleted';
|
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_SIGNED_UP = 'Auth Signed-up';
|
||||||
export const AUTH_RESET_PASSWORD = 'Auth reset password';
|
export const AUTH_RESET_PASSWORD = 'Auth reset password';
|
||||||
@@ -79,7 +89,8 @@ export const PAYMENT_METHOD_DELETED = 'Payment method deleted';
|
|||||||
|
|
||||||
export const INVOICE_PAYMENT_LINK_GENERATED = 'Invoice payment link generated';
|
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
|
// # Event Groups
|
||||||
export const ACCOUNT_GROUP = 'Account';
|
export const ACCOUNT_GROUP = 'Account';
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const ACCOUNT_TYPES = [
|
|||||||
normal: ACCOUNT_NORMAL.CREDIT,
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
multiCurrency: true,
|
||||||
balanceSheet: true,
|
balanceSheet: true,
|
||||||
incomeSheet: false,
|
incomeSheet: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -314,3 +314,7 @@ export interface CreditNotePdfTemplateAttributes {
|
|||||||
showCreditNoteDate: boolean;
|
showCreditNoteDate: boolean;
|
||||||
creditNoteDateLabel: string;
|
creditNoteDateLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICreditNoteState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -238,3 +238,8 @@ export interface PaymentReceivedPdfTemplateAttributes {
|
|||||||
showPaymentReceivedDate: boolean;
|
showPaymentReceivedDate: boolean;
|
||||||
paymentReceivedDateLabel: string;
|
paymentReceivedDateLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IPaymentReceivedState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -144,3 +144,6 @@ export interface ISaleEstimateMailPresendEvent {
|
|||||||
messageOptions: SaleEstimateMailOptionsDTO;
|
messageOptions: SaleEstimateMailOptionsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaleEstimateState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface PaymentIntegrationTransactionLinkEventPayload {
|
|||||||
referenceType: string;
|
referenceType: string;
|
||||||
referenceId: number;
|
referenceId: number;
|
||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
|
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
|
||||||
@@ -30,7 +30,7 @@ export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
|
|||||||
referenceType: string;
|
referenceType: string;
|
||||||
referenceId: number;
|
referenceId: number;
|
||||||
oldSaleInvoiceId: number;
|
oldSaleInvoiceId: number;
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoice {
|
export interface ISaleInvoice {
|
||||||
@@ -174,7 +174,7 @@ export interface ISaleInvoiceDeletingPayload {
|
|||||||
tenantId: number;
|
tenantId: number;
|
||||||
oldSaleInvoice: ISaleInvoice;
|
oldSaleInvoice: ISaleInvoice;
|
||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceDeletedPayload {
|
export interface ISaleInvoiceDeletedPayload {
|
||||||
@@ -339,3 +339,7 @@ export interface InvoicePdfTemplateAttributes {
|
|||||||
showStatement: boolean;
|
showStatement: boolean;
|
||||||
statement: string;
|
statement: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaleInvocieState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -211,3 +211,8 @@ export interface ISaleReceiptBrandingTemplateAttributes {
|
|||||||
showReceiptDate: boolean;
|
showReceiptDate: boolean;
|
||||||
receiptDateLabel: string;
|
receiptDateLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ISaleReceiptState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -294,7 +294,7 @@ export default {
|
|||||||
name: 'item.field.note',
|
name: 'item.field.note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
category: {
|
categoryId: {
|
||||||
name: 'item.field.category',
|
name: 'item.field.category',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'ItemCategory',
|
relationModel: 'ItemCategory',
|
||||||
|
|||||||
@@ -29,6 +29,20 @@ export class PdfTemplate extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Filters the due invoices.
|
||||||
|
*/
|
||||||
|
default(query) {
|
||||||
|
query.where('default', true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
) {
|
) {
|
||||||
id!: number;
|
id!: number;
|
||||||
date!: Date | string;
|
date!: Date | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction amount.
|
||||||
|
* Negative represents to spending and positive to deposit/card charge.
|
||||||
|
* @param {number}
|
||||||
|
*/
|
||||||
amount!: number;
|
amount!: number;
|
||||||
categorized!: boolean;
|
categorized!: boolean;
|
||||||
accountId!: number;
|
accountId!: number;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export class AccountTransformer extends Transformer {
|
|||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return [
|
||||||
|
'accountTypeLabel',
|
||||||
|
'accountNormalFormatted',
|
||||||
'formattedAmount',
|
'formattedAmount',
|
||||||
'flattenName',
|
'flattenName',
|
||||||
'bankBalanceFormatted',
|
'bankBalanceFormatted',
|
||||||
@@ -84,6 +86,22 @@ export class AccountTransformer extends Transformer {
|
|||||||
return account.plaidItem?.isPaused || false;
|
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.
|
* Transformes the accounts collection to flat or nested array.
|
||||||
* @param {IAccount[]}
|
* @param {IAccount[]}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import I18nService from '@/services/I18n/I18nService';
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { AccountTransformer } from './AccountTransform';
|
import { AccountTransformer } from './AccountTransform';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetAccount {
|
export class GetAccount {
|
||||||
@@ -15,6 +17,9 @@ export class GetAccount {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private transformer: TransformerInjectable;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the given account details.
|
* Retrieve the given account details.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -39,10 +44,13 @@ export class GetAccount {
|
|||||||
new AccountTransformer(),
|
new AccountTransformer(),
|
||||||
{ accountsGraph }
|
{ accountsGraph }
|
||||||
);
|
);
|
||||||
return this.i18nService.i18nApply(
|
const eventPayload = {
|
||||||
[['accountTypeLabel'], ['accountNormalFormatted']],
|
tenantId,
|
||||||
transformed,
|
accountId,
|
||||||
tenantId
|
};
|
||||||
);
|
// Triggers `onAccountViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.accounts.onViewed, eventPayload);
|
||||||
|
|
||||||
|
return transformed;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,27 @@ import {
|
|||||||
Institution as PlaidInstitution,
|
Institution as PlaidInstitution,
|
||||||
AccountBase as PlaidAccount,
|
AccountBase as PlaidAccount,
|
||||||
TransactionBase as PlaidTransactionBase,
|
TransactionBase as PlaidTransactionBase,
|
||||||
|
AccountType as PlaidAccountType,
|
||||||
} from 'plaid';
|
} from 'plaid';
|
||||||
import {
|
import {
|
||||||
CreateUncategorizedTransactionDTO,
|
CreateUncategorizedTransactionDTO,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
} from '@/interfaces';
|
} 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.
|
* Transformes the Plaid account to create cashflow account DTO.
|
||||||
@@ -28,7 +44,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
|||||||
code: '',
|
code: '',
|
||||||
description: plaidAccount.official_name,
|
description: plaidAccount.official_name,
|
||||||
currencyCode: plaidAccount.balances.iso_currency_code,
|
currencyCode: plaidAccount.balances.iso_currency_code,
|
||||||
accountType: 'cash',
|
accountType: getAccountTypeFromPlaidAccountType(plaidAccount.type),
|
||||||
active: true,
|
active: true,
|
||||||
bankBalance: plaidAccount.balances.current,
|
bankBalance: plaidAccount.balances.current,
|
||||||
accountMask: plaidAccount.mask,
|
accountMask: plaidAccount.mask,
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import { ILedgerEntry, ICashflowTransaction } from '../../interfaces';
|
||||||
ILedgerEntry,
|
import { transformCashflowTransactionType } from './utils';
|
||||||
ICashflowTransaction,
|
|
||||||
AccountNormal,
|
|
||||||
} from '../../interfaces';
|
|
||||||
import {
|
|
||||||
transformCashflowTransactionType,
|
|
||||||
getCashflowAccountTransactionsTypes,
|
|
||||||
} from './utils';
|
|
||||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
@@ -70,7 +63,7 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
debit: cashflowTransaction.isCashDebit
|
debit: cashflowTransaction.isCashDebit
|
||||||
? cashflowTransaction.localAmount
|
? cashflowTransaction.localAmount
|
||||||
: 0,
|
: 0,
|
||||||
accountNormal: AccountNormal.DEBIT,
|
accountNormal: cashflowTransaction?.cashflowAccount?.accountNormal,
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -143,6 +136,7 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
// Retrieves the cashflow transactions with associated entries.
|
// Retrieves the cashflow transactions with associated entries.
|
||||||
const transaction = await CashflowTransaction.query(trx)
|
const transaction = await CashflowTransaction.query(trx)
|
||||||
.findById(cashflowTransactionId)
|
.findById(cashflowTransactionId)
|
||||||
|
.withGraphFetched('cashflowAccount')
|
||||||
.withGraphFetched('creditAccount');
|
.withGraphFetched('creditAccount');
|
||||||
|
|
||||||
// Retrieves the cashflow transaction ledger.
|
// Retrieves the cashflow transaction ledger.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { CashflowAccountTransformer } from './CashflowAccountTransformer';
|
|||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GetCashflowAccountsService {
|
export default class GetCashflowAccountsService {
|
||||||
@@ -41,14 +42,20 @@ export default class GetCashflowAccountsService {
|
|||||||
const accounts = await CashflowAccount.query().onBuild((builder) => {
|
const accounts = await CashflowAccount.query().onBuild((builder) => {
|
||||||
dynamicList.buildQuery()(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);
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
});
|
});
|
||||||
// Retrieves the transformed accounts.
|
// Retrieves the transformed accounts.
|
||||||
return this.transformer.transform(
|
const transformed = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
accounts,
|
accounts,
|
||||||
new CashflowAccountTransformer()
|
new CashflowAccountTransformer()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return transformed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class GetCashflowTransactionService {
|
|||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private transfromer: TransformerInjectable;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the given cashflow transaction.
|
* Retrieve the given cashflow transaction.
|
||||||
@@ -37,7 +37,7 @@ export class GetCashflowTransactionService {
|
|||||||
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
|
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
|
||||||
|
|
||||||
// Transformes the cashflow transaction model to POJO.
|
// Transformes the cashflow transaction model to POJO.
|
||||||
return this.transfromer.transform(
|
return this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
cashflowTransaction,
|
cashflowTransaction,
|
||||||
new CashflowTransactionTransformer()
|
new CashflowTransactionTransformer()
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate';
|
|||||||
import { CreditNotePdfTemplateAttributes } from '@/interfaces';
|
import { CreditNotePdfTemplateAttributes } from '@/interfaces';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { transformCreditNoteToPdfTemplate } from './utils';
|
import { transformCreditNoteToPdfTemplate } from './utils';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GetCreditNotePdf {
|
export default class GetCreditNotePdf {
|
||||||
@@ -24,12 +26,19 @@ export default class GetCreditNotePdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private creditNoteBrandingTemplate: CreditNoteBrandingTemplate;
|
private creditNoteBrandingTemplate: CreditNoteBrandingTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves sale invoice pdf content.
|
* Retrieves sale invoice pdf content.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} creditNoteId - Credit note 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(
|
const brandingAttributes = await this.getCreditNoteBrandingAttributes(
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNoteId
|
creditNoteId
|
||||||
@@ -39,7 +48,37 @@ export default class GetCreditNotePdf {
|
|||||||
'modules/credit-note-standard',
|
'modules/credit-note-standard',
|
||||||
brandingAttributes
|
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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ICreditNoteState } from '@/interfaces';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetCreditNoteState {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the create/edit initial state of the payment received.
|
||||||
|
* @param {Number} saleInvoiceId -
|
||||||
|
* @return {Promise<ISaleInvoice>}
|
||||||
|
*/
|
||||||
|
public async getCreditNoteState(tenantId: number): Promise<ICreditNoteState> {
|
||||||
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const defaultPdfTemplate = await PdfTemplate.query()
|
||||||
|
.findOne({ resource: 'CreditNote' })
|
||||||
|
.modify('default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTemplateId: defaultPdfTemplate?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ACCOUNT_CREATED,
|
ACCOUNT_CREATED,
|
||||||
ACCOUNT_EDITED,
|
ACCOUNT_EDITED,
|
||||||
ACCOUNT_DELETED,
|
ACCOUNT_DELETED,
|
||||||
|
ACCOUNT_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -31,6 +32,7 @@ export class AccountEventsTracker extends EventSubscriber {
|
|||||||
events.accounts.onDeleted,
|
events.accounts.onDeleted,
|
||||||
this.handleTrackDeletedAccountEvent
|
this.handleTrackDeletedAccountEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(events.accounts.onViewed, this.handleTrackAccountViewedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackAccountCreatedEvent = ({
|
private handleTrackAccountCreatedEvent = ({
|
||||||
@@ -62,4 +64,12 @@ export class AccountEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackAccountViewedEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: ACCOUNT_VIEWED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ITEM_EVENT_CREATED,
|
ITEM_EVENT_CREATED,
|
||||||
ITEM_EVENT_EDITED,
|
ITEM_EVENT_EDITED,
|
||||||
ITEM_EVENT_DELETED,
|
ITEM_EVENT_DELETED,
|
||||||
|
ITEM_EVENT_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -25,6 +26,7 @@ export class ItemEventsTracker extends EventSubscriber {
|
|||||||
bus.subscribe(events.item.onCreated, this.handleTrackItemCreatedEvent);
|
bus.subscribe(events.item.onCreated, this.handleTrackItemCreatedEvent);
|
||||||
bus.subscribe(events.item.onEdited, this.handleTrackEditedItemEvent);
|
bus.subscribe(events.item.onEdited, this.handleTrackEditedItemEvent);
|
||||||
bus.subscribe(events.item.onDeleted, this.handleTrackDeletedItemEvent);
|
bus.subscribe(events.item.onDeleted, this.handleTrackDeletedItemEvent);
|
||||||
|
bus.subscribe(events.item.onViewed, this.handleTrackViewedItemEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackItemCreatedEvent = ({
|
private handleTrackItemCreatedEvent = ({
|
||||||
@@ -56,4 +58,14 @@ export class ItemEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackViewedItemEvent = ({
|
||||||
|
tenantId,
|
||||||
|
}: IItemEventDeletedPayload) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: ITEM_EVENT_VIEWED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
PAYMENT_RECEIVED_CREATED,
|
PAYMENT_RECEIVED_CREATED,
|
||||||
PAYMENT_RECEIVED_EDITED,
|
PAYMENT_RECEIVED_EDITED,
|
||||||
PAYMENT_RECEIVED_DELETED,
|
PAYMENT_RECEIVED_DELETED,
|
||||||
|
PAYMENT_RECEIVED_PDF_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -34,6 +35,10 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
|
|||||||
events.paymentReceive.onDeleted,
|
events.paymentReceive.onDeleted,
|
||||||
this.handleTrackDeletedPaymentReceivedEvent
|
this.handleTrackDeletedPaymentReceivedEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.paymentReceive.onPdfViewed,
|
||||||
|
this.handleTrackPdfViewedPaymentReceivedEvent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackPaymentReceivedCreatedEvent = ({
|
private handleTrackPaymentReceivedCreatedEvent = ({
|
||||||
@@ -65,4 +70,14 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackPdfViewedPaymentReceivedEvent = ({
|
||||||
|
tenantId,
|
||||||
|
}: IPaymentReceivedDeletedPayload) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PAYMENT_RECEIVED_PDF_VIEWED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
SALE_ESTIMATE_CREATED,
|
SALE_ESTIMATE_CREATED,
|
||||||
SALE_ESTIMATE_EDITED,
|
SALE_ESTIMATE_EDITED,
|
||||||
SALE_ESTIMATE_DELETED,
|
SALE_ESTIMATE_DELETED,
|
||||||
|
SALE_ESTIMATE_PDF_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -34,6 +35,10 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
|
|||||||
events.saleEstimate.onDeleted,
|
events.saleEstimate.onDeleted,
|
||||||
this.handleTrackDeletedEstimateEvent
|
this.handleTrackDeletedEstimateEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleEstimate.onPdfViewed,
|
||||||
|
this.handleTrackPdfViewedEstimateEvent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackEstimateCreatedEvent = ({
|
private handleTrackEstimateCreatedEvent = ({
|
||||||
@@ -65,4 +70,14 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackPdfViewedEstimateEvent = ({
|
||||||
|
tenantId,
|
||||||
|
}: ISaleEstimateDeletedPayload) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: SALE_ESTIMATE_PDF_VIEWED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
SALE_INVOICE_CREATED,
|
SALE_INVOICE_CREATED,
|
||||||
SALE_INVOICE_DELETED,
|
SALE_INVOICE_DELETED,
|
||||||
SALE_INVOICE_EDITED,
|
SALE_INVOICE_EDITED,
|
||||||
|
SALE_INVOICE_PDF_VIEWED,
|
||||||
|
SALE_INVOICE_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -33,6 +35,14 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
events.saleInvoice.onDeleted,
|
events.saleInvoice.onDeleted,
|
||||||
this.handleTrackDeletedInvoiceEvent
|
this.handleTrackDeletedInvoiceEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onViewed,
|
||||||
|
this.handleTrackViewedInvoiceEvent
|
||||||
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onPdfViewed,
|
||||||
|
this.handleTrackPdfViewedInvoiceEvent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackInvoiceCreatedEvent = ({
|
private handleTrackInvoiceCreatedEvent = ({
|
||||||
@@ -64,4 +74,20 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
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: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export const valueParser =
|
|||||||
// Parses the enumeration value.
|
// Parses the enumeration value.
|
||||||
} else if (field.fieldType === 'enumeration') {
|
} else if (field.fieldType === 'enumeration') {
|
||||||
const option = get(field, 'options', []).find(
|
const option = get(field, 'options', []).find(
|
||||||
(option) => option.label === value
|
(option) => option.label?.toLowerCase() === value?.toLowerCase()
|
||||||
);
|
);
|
||||||
_value = get(option, 'key');
|
_value = get(option, 'key');
|
||||||
// Parses the numeric value.
|
// Parses the numeric value.
|
||||||
@@ -433,8 +433,8 @@ export const getMapToPath = (to: string, group = '') =>
|
|||||||
group ? `${group}.${to}` : to;
|
group ? `${group}.${to}` : to;
|
||||||
|
|
||||||
export const getImportsStoragePath = () => {
|
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.
|
* Deletes the imported file from the storage and database.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { IItem } from '@/interfaces';
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import ItemTransformer from './ItemTransformer';
|
import ItemTransformer from './ItemTransformer';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
export class GetItem {
|
export class GetItem {
|
||||||
@@ -12,6 +14,9 @@ export class GetItem {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private transformer: TransformerInjectable;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the item details of the given id with associated details.
|
* Retrieve the item details of the given id with associated details.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -31,6 +36,16 @@ export class GetItem {
|
|||||||
.withGraphFetched('purchaseTaxRate')
|
.withGraphFetched('purchaseTaxRate')
|
||||||
.throwIfNotFound();
|
.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
|
||||||
|
import { SaleInvoicePdf } from '../Sales/Invoices/SaleInvoicePdf';
|
||||||
|
import { PaymentLink } from '@/system/models';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentLinkInvoicePdf {
|
||||||
|
@Inject()
|
||||||
|
private getSaleInvoicePdfService: SaleInvoicePdf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice PDF of the given payment link id.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentLinkId
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
async getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||||
|
const paymentLink = await PaymentLink.query()
|
||||||
|
.findOne('linkId', paymentLinkId)
|
||||||
|
.where('resourceType', 'SaleInvoice')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const tenantId = paymentLink.tenantId;
|
||||||
|
await initalizeTenantServices(tenantId);
|
||||||
|
|
||||||
|
const saleInvoiceId = paymentLink.resourceId;
|
||||||
|
|
||||||
|
return this.getSaleInvoicePdfService.saleInvoicePdf(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
||||||
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||||
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||||
|
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentLinksApplication {
|
export class PaymentLinksApplication {
|
||||||
@@ -10,6 +11,9 @@ export class PaymentLinksApplication {
|
|||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the invoice payment link.
|
* Retrieves the invoice payment link.
|
||||||
@@ -34,4 +38,16 @@ export class PaymentLinksApplication {
|
|||||||
paymentLinkId
|
paymentLinkId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link id.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentLinkId
|
||||||
|
* @returns {Promise<Buffer> }
|
||||||
|
*/
|
||||||
|
public getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||||
|
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 { Inject, Service } from 'typedi';
|
||||||
import { isEmpty } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class BrandingTemplateDTOTransformer {
|
export class BrandingTemplateDTOTransformer {
|
||||||
@@ -22,11 +21,12 @@ export class BrandingTemplateDTOTransformer {
|
|||||||
const { PdfTemplate } = this.tenancy.models(tenantId);
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
const attributeName = 'pdfTemplateId';
|
const attributeName = 'pdfTemplateId';
|
||||||
|
|
||||||
const defaultTemplate = await PdfTemplate.query().findOne({
|
const defaultTemplate = await PdfTemplate.query()
|
||||||
resource,
|
.modify('default')
|
||||||
default: true,
|
.findOne({ resource });
|
||||||
});
|
|
||||||
if (!defaultTemplate || !isEmpty(object[attributeName])) {
|
// If the default template is not found OR the given object has no defined template id.
|
||||||
|
if (!defaultTemplate || !isNil(object[attributeName])) {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ISaleEstimateState } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetSaleEstimateState {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the create/edit sale estimate state.
|
||||||
|
* @param {Number} saleEstimateId -
|
||||||
|
* @return {Promise<ISaleEstimateState>}
|
||||||
|
*/
|
||||||
|
public async getSaleEstimateState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<ISaleEstimateState> {
|
||||||
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const defaultPdfTemplate = await PdfTemplate.query()
|
||||||
|
.findOne({ resource: 'SaleEstimate' })
|
||||||
|
.modify('default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTemplateId: defaultPdfTemplate?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import { RejectSaleEstimate } from './RejectSaleEstimate';
|
|||||||
import { SaleEstimateNotifyBySms } from './SaleEstimateSmsNotify';
|
import { SaleEstimateNotifyBySms } from './SaleEstimateSmsNotify';
|
||||||
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
||||||
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
|
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
|
||||||
|
import { GetSaleEstimateState } from './GetSaleEstimateState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleEstimatesApplication {
|
export class SaleEstimatesApplication {
|
||||||
@@ -56,6 +57,9 @@ export class SaleEstimatesApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private sendEstimateMailService: SendSaleEstimateMail;
|
private sendEstimateMailService: SendSaleEstimateMail;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleEstimateStateService: GetSaleEstimateState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sale estimate.
|
* Create a sale estimate.
|
||||||
* @param {number} tenantId - The tenant id.
|
* @param {number} tenantId - The tenant id.
|
||||||
@@ -249,4 +253,13 @@ export class SaleEstimatesApplication {
|
|||||||
saleEstimateId
|
saleEstimateId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current state of the sale estimate.
|
||||||
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
|
* @returns {Promise<ISaleEstimateState>} - A promise resolving to the sale estimate state.
|
||||||
|
*/
|
||||||
|
public getSaleEstimateState(tenantId: number) {
|
||||||
|
return this.getSaleEstimateStateService.getSaleEstimateState(tenantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate';
|
import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate';
|
||||||
import { transformEstimateToPdfTemplate } from './utils';
|
import { transformEstimateToPdfTemplate } from './utils';
|
||||||
import { EstimatePdfBrandingAttributes } from './constants';
|
import { EstimatePdfBrandingAttributes } from './constants';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleEstimatesPdf {
|
export class SaleEstimatesPdf {
|
||||||
@@ -24,12 +26,22 @@ export class SaleEstimatesPdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private estimatePdfTemplate: SaleEstimatePdfTemplate;
|
private estimatePdfTemplate: SaleEstimatePdfTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice pdf content.
|
* Retrieve sale invoice pdf content.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {ISaleInvoice} saleInvoice -
|
* @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(
|
const brandingAttributes = await this.getEstimateBrandingAttributes(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
@@ -39,7 +51,32 @@ export class SaleEstimatesPdf {
|
|||||||
'modules/estimate-regular',
|
'modules/estimate-regular',
|
||||||
brandingAttributes
|
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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const transformEstimateToPdfTemplate = (
|
|||||||
})),
|
})),
|
||||||
total: estimate.formattedSubtotal,
|
total: estimate.formattedSubtotal,
|
||||||
subtotal: estimate.formattedSubtotal,
|
subtotal: estimate.formattedSubtotal,
|
||||||
customerNote: estimate.customerNote,
|
customerNote: estimate.note,
|
||||||
termsConditions: estimate.termsConditions,
|
termsConditions: estimate.termsConditions,
|
||||||
customerAddress: contactAddressTextFormat(estimate.customer),
|
customerAddress: contactAddressTextFormat(estimate.customer),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -104,12 +104,24 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected formattedCustomerAddress(invoice) {
|
get customerAddressFormat() {
|
||||||
return contactAddressTextFormat(invoice.customer, `{ADDRESS_1}
|
return `{ADDRESS_1}
|
||||||
{ADDRESS_2}
|
{ADDRESS_2}
|
||||||
{CITY}, {STATE} {POSTAL_CODE}
|
{CITY} {STATE} {POSTAL_CODE}
|
||||||
{COUNTRY}
|
{COUNTRY}
|
||||||
{PHONE}`);
|
{PHONE}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted customer address.
|
||||||
|
* @param invoice
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedCustomerAddress(invoice) {
|
||||||
|
return contactAddressTextFormat(
|
||||||
|
invoice.customer,
|
||||||
|
this.customerAddressFormat
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
|||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetSaleInvoice {
|
export class GetSaleInvoice {
|
||||||
@@ -16,6 +18,9 @@ export class GetSaleInvoice {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private validators: CommandSaleInvoiceValidators;
|
private validators: CommandSaleInvoiceValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice with associated entries.
|
* Retrieve sale invoice with associated entries.
|
||||||
* @param {Number} saleInvoiceId -
|
* @param {Number} saleInvoiceId -
|
||||||
@@ -41,10 +46,20 @@ export class GetSaleInvoice {
|
|||||||
// Validates the given sale invoice existance.
|
// Validates the given sale invoice existance.
|
||||||
this.validators.validateInvoiceExistance(saleInvoice);
|
this.validators.validateInvoiceExistance(saleInvoice);
|
||||||
|
|
||||||
return this.transformer.transform(
|
const transformed = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice,
|
saleInvoice,
|
||||||
new SaleInvoiceTransformer()
|
new SaleInvoiceTransformer()
|
||||||
);
|
);
|
||||||
|
const eventPayload = {
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId,
|
||||||
|
};
|
||||||
|
// Triggers the `onSaleInvoiceItemViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleInvoice.onViewed,
|
||||||
|
eventPayload
|
||||||
|
);
|
||||||
|
return transformed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ISaleInvocieState } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetSaleInvoiceState {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the create/edit invoice state.
|
||||||
|
* @param {Number} saleInvoiceId -
|
||||||
|
* @return {Promise<ISaleInvoice>}
|
||||||
|
*/
|
||||||
|
public async getSaleInvoiceState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<ISaleInvocieState> {
|
||||||
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const defaultPdfTemplate = await PdfTemplate.query()
|
||||||
|
.findOne({ resource: 'SaleInvoice' })
|
||||||
|
.modify('default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTemplateId: defaultPdfTemplate?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import { transformInvoiceToPdfTemplate } from './utils';
|
import { transformInvoiceToPdfTemplate } from './utils';
|
||||||
import { InvoicePdfTemplateAttributes } from '@/interfaces';
|
import { InvoicePdfTemplateAttributes } from '@/interfaces';
|
||||||
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
|
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoicePdf {
|
export class SaleInvoicePdf {
|
||||||
@@ -24,6 +26,9 @@ export class SaleInvoicePdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private invoiceBrandingTemplateService: SaleInvoicePdfTemplate;
|
private invoiceBrandingTemplateService: SaleInvoicePdfTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice pdf content.
|
* Retrieve sale invoice pdf content.
|
||||||
* @param {number} tenantId - Tenant Id.
|
* @param {number} tenantId - Tenant Id.
|
||||||
@@ -33,7 +38,9 @@ export class SaleInvoicePdf {
|
|||||||
public async saleInvoicePdf(
|
public async saleInvoicePdf(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
invoiceId: number
|
invoiceId: number
|
||||||
): Promise<Buffer> {
|
): Promise<[Buffer, string]> {
|
||||||
|
const filename = await this.getInvoicePdfFilename(tenantId, invoiceId);
|
||||||
|
|
||||||
const brandingAttributes = await this.getInvoiceBrandingAttributes(
|
const brandingAttributes = await this.getInvoiceBrandingAttributes(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
@@ -44,7 +51,35 @@ export class SaleInvoicePdf {
|
|||||||
brandingAttributes
|
brandingAttributes
|
||||||
);
|
);
|
||||||
// Converts the given html content to pdf document.
|
// 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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
|
|||||||
import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
|
import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
|
||||||
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
|
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
|
||||||
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
|
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
|
||||||
|
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoiceApplication {
|
export class SaleInvoiceApplication {
|
||||||
@@ -73,6 +74,9 @@ export class SaleInvoiceApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
|
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleInvoiceStateService: GetSaleInvoiceState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new sale invoice with associated GL entries.
|
* Creates a new sale invoice with associated GL entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -169,6 +173,16 @@ export class SaleInvoiceApplication {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice state.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} saleInvoiceId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getSaleInvoiceState(tenantId: number) {
|
||||||
|
return this.getSaleInvoiceStateService.getSaleInvoiceState(tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the given sale invoice as delivered.
|
* Mark the given sale invoice as delivered.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export const defaultInvoicePdfTemplateAttributes = {
|
|||||||
|
|
||||||
// Entries
|
// Entries
|
||||||
lineItemLabel: 'Item',
|
lineItemLabel: 'Item',
|
||||||
lineDescriptionLabel: 'Description',
|
lineQuantityLabel: 'Qty',
|
||||||
lineRateLabel: 'Rate',
|
lineRateLabel: 'Rate',
|
||||||
lineTotalLabel: 'Total',
|
lineTotalLabel: 'Total',
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate';
|
import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate';
|
||||||
import { transformPaymentReceivedToPdfTemplate } from './utils';
|
import { transformPaymentReceivedToPdfTemplate } from './utils';
|
||||||
import { PaymentReceivedPdfTemplateAttributes } from '@/interfaces';
|
import { PaymentReceivedPdfTemplateAttributes } from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GetPaymentReceivedPdf {
|
export default class GetPaymentReceivedPdf {
|
||||||
@@ -24,6 +26,9 @@ export default class GetPaymentReceivedPdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate;
|
private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice pdf content.
|
* Retrieve sale invoice pdf content.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -32,19 +37,51 @@ export default class GetPaymentReceivedPdf {
|
|||||||
*/
|
*/
|
||||||
async getPaymentReceivePdf(
|
async getPaymentReceivePdf(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number
|
paymentReceivedId: number
|
||||||
): Promise<Buffer> {
|
): Promise<[Buffer, string]> {
|
||||||
const brandingAttributes = await this.getPaymentBrandingAttributes(
|
const brandingAttributes = await this.getPaymentBrandingAttributes(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId
|
paymentReceivedId
|
||||||
);
|
);
|
||||||
const htmlContent = await this.templateInjectable.render(
|
const htmlContent = await this.templateInjectable.render(
|
||||||
tenantId,
|
tenantId,
|
||||||
'modules/payment-receive-standard',
|
'modules/payment-receive-standard',
|
||||||
brandingAttributes
|
brandingAttributes
|
||||||
);
|
);
|
||||||
|
const filename = await this.getPaymentReceivedFilename(
|
||||||
|
tenantId,
|
||||||
|
paymentReceivedId
|
||||||
|
);
|
||||||
// Converts the given html content to pdf document.
|
// 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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { IPaymentReceivedState } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentReceivedState {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the create/edit initial state of the payment received.
|
||||||
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
|
* @returns {Promise<IPaymentReceivedState>} - A promise resolving to the payment received state.
|
||||||
|
*/
|
||||||
|
public async getPaymentReceivedState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<IPaymentReceivedState> {
|
||||||
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const defaultPdfTemplate = await PdfTemplate.query()
|
||||||
|
.findOne({ resource: 'PaymentReceive' })
|
||||||
|
.modify('default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTemplateId: defaultPdfTemplate?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { GetPaymentReceivedInvoices } from './GetPaymentReceivedInvoices';
|
|||||||
import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
|
import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
|
||||||
import GetPaymentReceivedPdf from './GetPaymentReceivedPdf';
|
import GetPaymentReceivedPdf from './GetPaymentReceivedPdf';
|
||||||
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
|
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
|
||||||
|
import { GetPaymentReceivedState } from './GetPaymentReceivedState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentReceivesApplication {
|
export class PaymentReceivesApplication {
|
||||||
@@ -49,6 +50,9 @@ export class PaymentReceivesApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getPaymentReceivePdfService: GetPaymentReceivedPdf;
|
private getPaymentReceivePdfService: GetPaymentReceivedPdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentReceivedStateService: GetPaymentReceivedState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new payment receive.
|
* Creates a new payment receive.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -223,4 +227,15 @@ export class PaymentReceivesApplication {
|
|||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the create/edit initial state of the payment received.
|
||||||
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
|
* @returns {Promise<IPaymentReceivedState>}
|
||||||
|
*/
|
||||||
|
public getPaymentReceivedState = (tenantId: number) => {
|
||||||
|
return this.getPaymentReceivedStateService.getPaymentReceivedState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ISaleReceiptState } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetSaleReceiptState {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retireves the sale receipt state.
|
||||||
|
* @param {Number} tenantId -
|
||||||
|
* @return {Promise<ISaleReceiptState>}
|
||||||
|
*/
|
||||||
|
public async getSaleReceiptState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<ISaleReceiptState> {
|
||||||
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const defaultPdfTemplate = await PdfTemplate.query()
|
||||||
|
.findOne({ resource: 'SaleReceipt' })
|
||||||
|
.modify('default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTemplateId: defaultPdfTemplate?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
IFilterMeta,
|
IFilterMeta,
|
||||||
IPaginationMeta,
|
IPaginationMeta,
|
||||||
ISaleReceipt,
|
ISaleReceipt,
|
||||||
|
ISaleReceiptState,
|
||||||
ISalesReceiptsFilter,
|
ISalesReceiptsFilter,
|
||||||
SaleReceiptMailOpts,
|
SaleReceiptMailOpts,
|
||||||
SaleReceiptMailOptsDTO,
|
SaleReceiptMailOptsDTO,
|
||||||
@@ -16,6 +17,7 @@ import { CloseSaleReceipt } from './CloseSaleReceipt';
|
|||||||
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
||||||
import { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
|
import { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
|
||||||
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
||||||
|
import { GetSaleReceiptState } from './GetSaleReceiptState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptApplication {
|
export class SaleReceiptApplication {
|
||||||
@@ -46,6 +48,9 @@ export class SaleReceiptApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private saleReceiptNotifyByMailService: SaleReceiptMailNotification;
|
private saleReceiptNotifyByMailService: SaleReceiptMailNotification;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleReceiptStateService: GetSaleReceiptState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new sale receipt with associated entries.
|
* Creates a new sale receipt with associated entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -207,4 +212,13 @@ export class SaleReceiptApplication {
|
|||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current state of the sale receipt.
|
||||||
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
|
* @returns {Promise<ISaleReceiptState>} - A promise resolving to the sale receipt state.
|
||||||
|
*/
|
||||||
|
public getSaleReceiptState(tenantId: number): Promise<ISaleReceiptState> {
|
||||||
|
return this.getSaleReceiptStateService.getSaleReceiptState(tenantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import { SaleReceiptBrandingTemplate } from './SaleReceiptBrandingTemplate';
|
import { SaleReceiptBrandingTemplate } from './SaleReceiptBrandingTemplate';
|
||||||
import { transformReceiptToBrandingTemplateAttributes } from './utils';
|
import { transformReceiptToBrandingTemplateAttributes } from './utils';
|
||||||
import { ISaleReceiptBrandingTemplateAttributes } from '@/interfaces';
|
import { ISaleReceiptBrandingTemplateAttributes } from '@/interfaces';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptsPdf {
|
export class SaleReceiptsPdf {
|
||||||
@@ -24,6 +26,9 @@ export class SaleReceiptsPdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private saleReceiptBrandingTemplate: SaleReceiptBrandingTemplate;
|
private saleReceiptBrandingTemplate: SaleReceiptBrandingTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves sale invoice pdf content.
|
* Retrieves sale invoice pdf content.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -31,6 +36,8 @@ export class SaleReceiptsPdf {
|
|||||||
* @returns {Promise<Buffer>}
|
* @returns {Promise<Buffer>}
|
||||||
*/
|
*/
|
||||||
public async saleReceiptPdf(tenantId: number, saleReceiptId: number) {
|
public async saleReceiptPdf(tenantId: number, saleReceiptId: number) {
|
||||||
|
const filename = await this.getSaleReceiptFilename(tenantId, saleReceiptId);
|
||||||
|
|
||||||
const brandingAttributes = await this.getReceiptBrandingAttributes(
|
const brandingAttributes = await this.getReceiptBrandingAttributes(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
@@ -42,7 +49,35 @@ export class SaleReceiptsPdf {
|
|||||||
brandingAttributes
|
brandingAttributes
|
||||||
);
|
);
|
||||||
// Renders the html content to pdf document.
|
// Renders the html content to pdf document.
|
||||||
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
|
const content = await this.chromiumlyTenancy.convertHtmlContent(
|
||||||
|
tenantId,
|
||||||
|
htmlContent
|
||||||
|
);
|
||||||
|
const eventPayload = { tenantId, saleReceiptId };
|
||||||
|
|
||||||
|
// Triggers the `onSaleReceiptPdfViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleReceipt.onPdfViewed,
|
||||||
|
eventPayload
|
||||||
|
);
|
||||||
|
return [content, filename];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the filename file document of the given sale receipt.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} receiptId
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public async getSaleReceiptFilename(
|
||||||
|
tenantId: number,
|
||||||
|
receiptId: number
|
||||||
|
): Promise<string> {
|
||||||
|
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const receipt = await SaleReceipt.query().findById(receiptId);
|
||||||
|
|
||||||
|
return `Receipt-${receipt.receiptNumber}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export default {
|
|||||||
* Accounts service.
|
* Accounts service.
|
||||||
*/
|
*/
|
||||||
accounts: {
|
accounts: {
|
||||||
|
onViewed: 'onAccountViewed',
|
||||||
|
onListViewed: 'onAccountsListViewed',
|
||||||
|
|
||||||
onCreating: 'onAccountCreating',
|
onCreating: 'onAccountCreating',
|
||||||
onCreated: 'onAccountCreated',
|
onCreated: 'onAccountCreated',
|
||||||
|
|
||||||
@@ -127,6 +130,11 @@ export default {
|
|||||||
* Sales invoices service.
|
* Sales invoices service.
|
||||||
*/
|
*/
|
||||||
saleInvoice: {
|
saleInvoice: {
|
||||||
|
onViewed: 'onSaleInvoiceItemViewed',
|
||||||
|
onListViewed: 'onSaleInvoiceListViewed',
|
||||||
|
|
||||||
|
onPdfViewed: 'onSaleInvoicePdfViewed',
|
||||||
|
|
||||||
onCreate: 'onSaleInvoiceCreate',
|
onCreate: 'onSaleInvoiceCreate',
|
||||||
onCreating: 'onSaleInvoiceCreating',
|
onCreating: 'onSaleInvoiceCreating',
|
||||||
onCreated: 'onSaleInvoiceCreated',
|
onCreated: 'onSaleInvoiceCreated',
|
||||||
@@ -172,6 +180,8 @@ export default {
|
|||||||
* Sales estimates service.
|
* Sales estimates service.
|
||||||
*/
|
*/
|
||||||
saleEstimate: {
|
saleEstimate: {
|
||||||
|
onPdfViewed: 'onSaleEstimatePdfViewed',
|
||||||
|
|
||||||
onCreating: 'onSaleEstimateCreating',
|
onCreating: 'onSaleEstimateCreating',
|
||||||
onCreated: 'onSaleEstimateCreated',
|
onCreated: 'onSaleEstimateCreated',
|
||||||
|
|
||||||
@@ -209,6 +219,8 @@ export default {
|
|||||||
* Sales receipts service.
|
* Sales receipts service.
|
||||||
*/
|
*/
|
||||||
saleReceipt: {
|
saleReceipt: {
|
||||||
|
onPdfViewed: 'onSaleReceiptPdfViewed',
|
||||||
|
|
||||||
onCreating: 'onSaleReceiptsCreating',
|
onCreating: 'onSaleReceiptsCreating',
|
||||||
onCreated: 'onSaleReceiptsCreated',
|
onCreated: 'onSaleReceiptsCreated',
|
||||||
|
|
||||||
@@ -236,6 +248,8 @@ export default {
|
|||||||
* Payment receipts service.
|
* Payment receipts service.
|
||||||
*/
|
*/
|
||||||
paymentReceive: {
|
paymentReceive: {
|
||||||
|
onPdfViewed: 'onPaymentReceivedPdfViewed',
|
||||||
|
|
||||||
onCreated: 'onPaymentReceiveCreated',
|
onCreated: 'onPaymentReceiveCreated',
|
||||||
onCreating: 'onPaymentReceiveCreating',
|
onCreating: 'onPaymentReceiveCreating',
|
||||||
|
|
||||||
@@ -338,6 +352,8 @@ export default {
|
|||||||
* Items service.
|
* Items service.
|
||||||
*/
|
*/
|
||||||
item: {
|
item: {
|
||||||
|
onViewed: 'onItemViewed',
|
||||||
|
|
||||||
onCreated: 'onItemCreated',
|
onCreated: 'onItemCreated',
|
||||||
onCreating: 'onItemCreating',
|
onCreating: 'onItemCreating',
|
||||||
|
|
||||||
@@ -456,6 +472,8 @@ export default {
|
|||||||
* Credit note service.
|
* Credit note service.
|
||||||
*/
|
*/
|
||||||
creditNote: {
|
creditNote: {
|
||||||
|
onPdfViewed: 'onCreditNotePdfViewed',
|
||||||
|
|
||||||
onCreate: 'onCreditNoteCreate',
|
onCreate: 'onCreditNoteCreate',
|
||||||
onCreating: 'onCreditNoteCreating',
|
onCreating: 'onCreditNoteCreating',
|
||||||
onCreated: 'onCreditNoteCreated',
|
onCreated: 'onCreditNoteCreated',
|
||||||
@@ -714,7 +732,7 @@ export default {
|
|||||||
// Payment methods integrations
|
// Payment methods integrations
|
||||||
paymentIntegrationLink: {
|
paymentIntegrationLink: {
|
||||||
onPaymentIntegrationLink: 'onPaymentIntegrationLink',
|
onPaymentIntegrationLink: 'onPaymentIntegrationLink',
|
||||||
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink'
|
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Stripe Payment Integration
|
// Stripe Payment Integration
|
||||||
@@ -731,6 +749,6 @@ export default {
|
|||||||
// Stripe Payment Webhooks
|
// Stripe Payment Webhooks
|
||||||
stripeWebhooks: {
|
stripeWebhooks: {
|
||||||
onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted',
|
onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted',
|
||||||
onAccountUpdated: 'onStripeAccountUpdated'
|
onAccountUpdated: 'onStripeAccountUpdated',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { organizationAddressTextFormat } from '@/utils/address-text-format';
|
import {
|
||||||
|
defaultOrganizationAddressFormat,
|
||||||
|
organizationAddressTextFormat,
|
||||||
|
} from '@/utils/address-text-format';
|
||||||
import BaseModel from 'models/Model';
|
import BaseModel from 'models/Model';
|
||||||
|
import { findByIsoCountryCode } from '@bigcapital/utils';
|
||||||
import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
||||||
|
|
||||||
export default class TenantMetadata extends BaseModel {
|
export default class TenantMetadata extends BaseModel {
|
||||||
@@ -67,14 +71,9 @@ export default class TenantMetadata extends BaseModel {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public get addressTextFormatted() {
|
public get addressTextFormatted() {
|
||||||
const defaultMessage = `<strong>{ORGANIZATION_NAME}</strong>
|
const addressCountry = findByIsoCountryCode(this.location);
|
||||||
{ADDRESS_1}
|
|
||||||
{ADDRESS_2}
|
return organizationAddressTextFormat(defaultOrganizationAddressFormat, {
|
||||||
{CITY}, {STATE} {POSTAL_CODE}
|
|
||||||
{COUNTRY}
|
|
||||||
{PHONE}
|
|
||||||
`;
|
|
||||||
return organizationAddressTextFormat(defaultMessage, {
|
|
||||||
organizationName: this.name,
|
organizationName: this.name,
|
||||||
address1: this.address?.address1,
|
address1: this.address?.address1,
|
||||||
address2: this.address?.address2,
|
address2: this.address?.address2,
|
||||||
@@ -82,7 +81,7 @@ export default class TenantMetadata extends BaseModel {
|
|||||||
city: this.address?.city,
|
city: this.address?.city,
|
||||||
postalCode: this.address?.postalCode,
|
postalCode: this.address?.postalCode,
|
||||||
phone: this.address?.phone,
|
phone: this.address?.phone,
|
||||||
country: 'United State',
|
country: addressCountry?.name ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ interface OrganizationAddressFormatArgs {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMessage = `
|
export const defaultOrganizationAddressFormat = `
|
||||||
<strong>{ORGANIZATION_NAME}</strong>
|
<strong>{ORGANIZATION_NAME}</strong>
|
||||||
{ADDRESS_1}
|
{ADDRESS_1}
|
||||||
{ADDRESS_2}
|
{ADDRESS_2}
|
||||||
{CITY}, {STATE} {POSTAL_CODE}
|
{CITY} {STATE} {POSTAL_CODE}
|
||||||
{COUNTRY}
|
{COUNTRY}
|
||||||
{PHONE}
|
{PHONE}
|
||||||
`;
|
`;
|
||||||
/**
|
/**
|
||||||
* Formats the address text based on the provided message and arguments.
|
* Formats the address text based on the provided message and arguments.
|
||||||
@@ -36,7 +36,9 @@ const formatText = (message: string, replacements: Record<string, string>) => {
|
|||||||
},
|
},
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
formattedMessage = formattedMessage.replace(/\n{2,}/g, '\n').trim();
|
// Removes any empty lines.
|
||||||
|
formattedMessage = formattedMessage.replace(/^\s*[\r\n]/gm, '');
|
||||||
|
formattedMessage = formattedMessage.replace(/\n{2,}/g, '\n');
|
||||||
formattedMessage = formattedMessage.replace(/\n/g, '<br />');
|
formattedMessage = formattedMessage.replace(/\n/g, '<br />');
|
||||||
formattedMessage = formattedMessage.trim();
|
formattedMessage = formattedMessage.trim();
|
||||||
|
|
||||||
@@ -72,17 +74,17 @@ interface ContactAddressTextFormatArgs {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contactFormatMessage = `{CONTACT_NAME}
|
export const defaultContactAddressFormat = `{CONTACT_NAME}
|
||||||
{ADDRESS_1}
|
{ADDRESS_1}
|
||||||
{ADDRESS_2}
|
{ADDRESS_2}
|
||||||
{CITY}, {STATE} {POSTAL_CODE}
|
{CITY} {STATE} {POSTAL_CODE}
|
||||||
{COUNTRY}
|
{COUNTRY}
|
||||||
{PHONE}
|
{PHONE}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const contactAddressTextFormat = (
|
export const contactAddressTextFormat = (
|
||||||
contact: IContact,
|
contact: IContact,
|
||||||
message: string = contactFormatMessage
|
message: string = defaultContactAddressFormat
|
||||||
) => {
|
) => {
|
||||||
const args = {
|
const args = {
|
||||||
displayName: contact.displayName,
|
displayName: contact.displayName,
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ USER root
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy application dependency manifests to the container image.
|
# Copy application dependency manifests to the container image.
|
||||||
COPY ./package*.json ./
|
COPY . .
|
||||||
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
|
|
||||||
COPY ./lerna.json ./lerna.json
|
|
||||||
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
||||||
COPY ./packages/webapp/package*.json ./packages/webapp/
|
|
||||||
|
|
||||||
# Install application dependencies
|
# Install application dependencies
|
||||||
RUN apk update
|
RUN apk update
|
||||||
@@ -23,7 +19,6 @@ RUN npm install -g pnpm
|
|||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
|
|
||||||
# Build webapp package
|
# Build webapp package
|
||||||
COPY ./packages/webapp /app/packages/webapp
|
|
||||||
RUN pnpm run build:webapp
|
RUN pnpm run build:webapp
|
||||||
|
|
||||||
FROM nginx
|
FROM nginx
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"version": "0.10.2",
|
"version": "0.10.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bigcapital/utils": "*",
|
||||||
"@blueprintjs-formik/core": "^0.3.6",
|
"@blueprintjs-formik/core": "^0.3.6",
|
||||||
"@blueprintjs-formik/datetime": "^0.3.7",
|
"@blueprintjs-formik/datetime": "^0.3.7",
|
||||||
"@blueprintjs-formik/select": "^0.3.5",
|
"@blueprintjs-formik/select": "^0.3.5",
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@casl/react": "^2.3.0",
|
"@casl/react": "^2.3.0",
|
||||||
"@craco/craco": "^5.9.0",
|
"@craco/craco": "^5.9.0",
|
||||||
|
"@emotion/css": "^11.13.4",
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
"@stripe/connect-js": "^3.3.12",
|
"@stripe/connect-js": "^3.3.12",
|
||||||
"@stripe/react-connect-js": "^3.3.13",
|
"@stripe/react-connect-js": "^3.3.13",
|
||||||
@@ -37,6 +40,7 @@
|
|||||||
"@types/react": "^16.14.28",
|
"@types/react": "^16.14.28",
|
||||||
"@types/react-body-classname": "^1.1.7",
|
"@types/react-body-classname": "^1.1.7",
|
||||||
"@types/react-dom": "^16.9.16",
|
"@types/react-dom": "^16.9.16",
|
||||||
|
"@types/react-helmet": "^6.1.11",
|
||||||
"@types/react-redux": "^7.1.24",
|
"@types/react-redux": "^7.1.24",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-transition-group": "^4.4.5",
|
"@types/react-transition-group": "^4.4.5",
|
||||||
@@ -46,6 +50,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||||
"@typescript-eslint/parser": "^2.10.0",
|
"@typescript-eslint/parser": "^2.10.0",
|
||||||
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
||||||
|
"@xstyled/emotion": "^3.8.1",
|
||||||
"accounting": "^0.4.1",
|
"accounting": "^0.4.1",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"basscss": "^8.0.2",
|
"basscss": "^8.0.2",
|
||||||
@@ -59,6 +64,7 @@
|
|||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"flat": "^5.0.2",
|
"flat": "^5.0.2",
|
||||||
"formik": "^2.2.5",
|
"formik": "^2.2.5",
|
||||||
|
"helmet": "^3.21.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"http-proxy-middleware": "^1.0.0",
|
"http-proxy-middleware": "^1.0.0",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
@@ -87,6 +93,7 @@
|
|||||||
"react-dropzone-esm": "^15.0.1",
|
"react-dropzone-esm": "^15.0.1",
|
||||||
"react-error-boundary": "^3.0.2",
|
"react-error-boundary": "^3.0.2",
|
||||||
"react-error-overlay": "^6.0.9",
|
"react-error-overlay": "^6.0.9",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-hotkeys-hook": "^3.0.3",
|
"react-hotkeys-hook": "^3.0.3",
|
||||||
"react-intl-universal": "^2.4.7",
|
"react-intl-universal": "^2.4.7",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
@@ -120,6 +127,7 @@
|
|||||||
"style-loader": "0.23.1",
|
"style-loader": "0.23.1",
|
||||||
"styled-components": "^5.3.1",
|
"styled-components": "^5.3.1",
|
||||||
"stylis-rtlcss": "^2.1.1",
|
"stylis-rtlcss": "^2.1.1",
|
||||||
|
"theme-ui": "^0.16.2",
|
||||||
"typescript": "^4.8.3",
|
"typescript": "^4.8.3",
|
||||||
"yup": "^0.28.1"
|
"yup": "^0.28.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const OneClickDemoPage = lazy(
|
|||||||
const PaymentPortalPage = lazy(
|
const PaymentPortalPage = lazy(
|
||||||
() => import('@/containers/PaymentPortal/PaymentPortalPage'),
|
() => import('@/containers/PaymentPortal/PaymentPortalPage'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App inner.
|
* App inner.
|
||||||
*/
|
*/
|
||||||
@@ -59,7 +60,10 @@ function AppInsider({ history }) {
|
|||||||
children={<EmailConfirmation />}
|
children={<EmailConfirmation />}
|
||||||
/>
|
/>
|
||||||
<Route path={'/auth'} children={<AuthenticationPage />} />
|
<Route path={'/auth'} children={<AuthenticationPage />} />
|
||||||
<Route path={'/payment/:linkId'} children={<PaymentPortalPage />} />
|
<Route
|
||||||
|
path={'/payment/:linkId'}
|
||||||
|
children={<PaymentPortalPage />}
|
||||||
|
/>
|
||||||
<Route path={'/'} children={<DashboardPrivatePages />} />
|
<Route path={'/'} children={<DashboardPrivatePages />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
const AppIntlContext = createContext();
|
interface AppIntlContextValue {
|
||||||
|
currentLocale: string;
|
||||||
|
direction: 'rtl' | 'ltr';
|
||||||
|
isRTL: boolean;
|
||||||
|
isLTR: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppIntlContext = createContext<AppIntlContextValue>(
|
||||||
|
{} as AppIntlContextValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
interface AppIntlProviderProps {
|
||||||
|
currentLocale: string;
|
||||||
|
isRTL: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application intl provider.
|
* Application intl provider.
|
||||||
*/
|
*/
|
||||||
function AppIntlProvider({ currentLocale, isRTL, children }) {
|
function AppIntlProvider({
|
||||||
|
currentLocale,
|
||||||
|
isRTL,
|
||||||
|
children,
|
||||||
|
}: AppIntlProviderProps) {
|
||||||
const provider = {
|
const provider = {
|
||||||
currentLocale,
|
currentLocale,
|
||||||
isRTL,
|
isRTL,
|
||||||
@@ -21,6 +40,7 @@ function AppIntlProvider({ currentLocale, isRTL, children }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAppIntlContext = () => React.useContext(AppIntlContext);
|
const useAppIntlContext = () =>
|
||||||
|
React.useContext<AppIntlContextValue>(AppIntlContext);
|
||||||
|
|
||||||
export { AppIntlProvider, useAppIntlContext };
|
export { AppIntlProvider, useAppIntlContext };
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { LoadingIndicator } from '../Indicator';
|
import { LoadingIndicator } from '../Indicator';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
export function DashboardInsider({
|
export function DashboardInsider({
|
||||||
loading,
|
loading,
|
||||||
@@ -9,6 +10,7 @@ export function DashboardInsider({
|
|||||||
name,
|
name,
|
||||||
mount = false,
|
mount = false,
|
||||||
className,
|
className,
|
||||||
|
style
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -17,9 +19,11 @@ export function DashboardInsider({
|
|||||||
dashboard__insider: true,
|
dashboard__insider: true,
|
||||||
'dashboard__insider--loading': loading,
|
'dashboard__insider--loading': loading,
|
||||||
[`dashboard__insider--${name}`]: !!name,
|
[`dashboard__insider--${name}`]: !!name,
|
||||||
|
|
||||||
},
|
},
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
<LoadingIndicator loading={loading} mount={mount}>
|
<LoadingIndicator loading={loading} mount={mount}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ThemeProvider, StyleSheetManager } from 'styled-components';
|
import {
|
||||||
|
ThemeProvider as StyleComponentsThemeProvider,
|
||||||
|
StyleSheetManager,
|
||||||
|
} from 'styled-components';
|
||||||
import rtlcss from 'stylis-rtlcss';
|
import rtlcss from 'stylis-rtlcss';
|
||||||
|
import {
|
||||||
|
defaultTheme,
|
||||||
|
ThemeProvider as XStyledEmotionThemeProvider,
|
||||||
|
} from '@xstyled/emotion';
|
||||||
import { useAppIntlContext } from '../AppIntlProvider';
|
import { useAppIntlContext } from '../AppIntlProvider';
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
...defaultTheme,
|
||||||
|
bpPrefix: 'bp4',
|
||||||
|
};
|
||||||
|
|
||||||
interface DashboardThemeProviderProps {
|
interface DashboardThemeProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
@@ -17,7 +28,11 @@ export function DashboardThemeProvider({
|
|||||||
<StyleSheetManager
|
<StyleSheetManager
|
||||||
{...(direction === 'rtl' ? { stylisPlugins: [rtlcss] } : {})}
|
{...(direction === 'rtl' ? { stylisPlugins: [rtlcss] } : {})}
|
||||||
>
|
>
|
||||||
<ThemeProvider theme={{ dir: direction }}>{children}</ThemeProvider>
|
<StyleComponentsThemeProvider theme={{ dir: direction }}>
|
||||||
|
<XStyledEmotionThemeProvider theme={theme}>
|
||||||
|
{children}
|
||||||
|
</XStyledEmotionThemeProvider>
|
||||||
|
</StyleComponentsThemeProvider>
|
||||||
</StyleSheetManager>
|
</StyleSheetManager>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import React, { forwardRef, Ref } from 'react';
|
import React, { forwardRef, Ref } from 'react';
|
||||||
import { HTMLDivProps, Props } from '@blueprintjs/core';
|
import { HTMLDivProps, Props } from '@blueprintjs/core';
|
||||||
|
import { SystemProps, x } from '@xstyled/emotion';
|
||||||
|
|
||||||
export interface BoxProps extends Props, HTMLDivProps {
|
export interface BoxProps
|
||||||
className?: string;
|
extends SystemProps,
|
||||||
}
|
Props,
|
||||||
|
Omit<HTMLDivProps, 'color'> {}
|
||||||
|
|
||||||
export const Box = forwardRef(
|
export const Box = forwardRef(
|
||||||
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
|
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const Element = 'div';
|
const Element = x.div;
|
||||||
|
|
||||||
return <Element className={className} ref={ref} {...rest} />;
|
return <Element className={className} ref={ref} {...rest} />;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import { SystemProps } from '@xstyled/emotion';
|
||||||
import { Box } from '../Box';
|
import { Box } from '../Box';
|
||||||
import { filterFalsyChildren } from './_utils';
|
import { filterFalsyChildren } from './_utils';
|
||||||
|
|
||||||
@@ -12,7 +12,9 @@ export const GROUP_POSITIONS = {
|
|||||||
apart: 'space-between',
|
apart: 'space-between',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
|
export interface GroupProps
|
||||||
|
extends SystemProps,
|
||||||
|
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
|
||||||
/** Defines justify-content property */
|
/** Defines justify-content property */
|
||||||
position?: GroupPosition;
|
position?: GroupPosition;
|
||||||
|
|
||||||
@@ -29,28 +31,28 @@ export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
|
|||||||
align?: React.CSSProperties['alignItems'];
|
align?: React.CSSProperties['alignItems'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: Partial<GroupProps> = {
|
export function Group({
|
||||||
position: 'left',
|
position = 'left',
|
||||||
spacing: 20,
|
spacing = 20,
|
||||||
};
|
align = 'center',
|
||||||
|
noWrap,
|
||||||
export function Group({ children, ...props }: GroupProps) {
|
children,
|
||||||
const groupProps = {
|
...props
|
||||||
...defaultProps,
|
}: GroupProps) {
|
||||||
...props,
|
|
||||||
};
|
|
||||||
const filteredChildren = filterFalsyChildren(children);
|
const filteredChildren = filterFalsyChildren(children);
|
||||||
|
|
||||||
return <GroupStyled {...groupProps}>{filteredChildren}</GroupStyled>;
|
return (
|
||||||
|
<Box
|
||||||
|
boxSizing={'border-box'}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'row'}
|
||||||
|
alignItems={align}
|
||||||
|
flexWrap={noWrap ? 'nowrap' : 'wrap'}
|
||||||
|
justifyContent={GROUP_POSITIONS[position]}
|
||||||
|
gap={`${spacing}px`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{filteredChildren}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupStyled = styled(Box)`
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: ${(props: GroupProps) => (props.align || 'center')};
|
|
||||||
flex-wrap: ${(props: GroupProps) => (props.noWrap ? 'nowrap' : 'wrap')};
|
|
||||||
justify-content: ${(props: GroupProps) =>
|
|
||||||
GROUP_POSITIONS[props.position || 'left']};
|
|
||||||
gap: ${(props: GroupProps) => props.spacing}px;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import { x, SystemProps } from '@xstyled/emotion';
|
||||||
import { Box } from '../Box';
|
|
||||||
|
|
||||||
export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
|
export interface StackProps
|
||||||
|
extends SystemProps,
|
||||||
|
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
|
||||||
/** Key of theme.spacing or number to set gap in px */
|
/** Key of theme.spacing or number to set gap in px */
|
||||||
spacing?: number;
|
spacing?: number;
|
||||||
|
|
||||||
@@ -13,24 +14,20 @@ export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
|
|||||||
justify?: React.CSSProperties['justifyContent'];
|
justify?: React.CSSProperties['justifyContent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: Partial<StackProps> = {
|
export function Stack({
|
||||||
spacing: 20,
|
spacing = 20,
|
||||||
align: 'stretch',
|
align = 'stretch',
|
||||||
justify: 'top',
|
justify = 'top',
|
||||||
};
|
...restProps
|
||||||
|
}: StackProps) {
|
||||||
export function Stack(props: StackProps) {
|
return (
|
||||||
const stackProps = {
|
<x.div
|
||||||
...defaultProps,
|
display={'flex'}
|
||||||
...props,
|
flexDirection="column"
|
||||||
};
|
justifyContent="justify"
|
||||||
return <StackStyled {...stackProps} />;
|
gap={`${spacing}px`}
|
||||||
|
alignItems={align}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StackStyled = styled(Box)`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: ${(props: StackProps) => props.align};
|
|
||||||
justify-content: justify;
|
|
||||||
gap: ${(props: StackProps) => props.spacing}px;
|
|
||||||
`;
|
|
||||||
|
|||||||
91
packages/webapp/src/components/PageForm/PageForm.tsx
Normal file
91
packages/webapp/src/components/PageForm/PageForm.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import { x, SystemProps } from '@xstyled/emotion';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { Group, GroupProps } from '@/components';
|
||||||
|
|
||||||
|
interface PageFormProps extends SystemProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page form layout.
|
||||||
|
* @returns {React.ReactNode}
|
||||||
|
*/
|
||||||
|
export const PageForm = ({ children, ...props }: PageFormProps) => {
|
||||||
|
return (
|
||||||
|
<x.div display="flex" flexDirection={'column'} overflow="hidden" {...props}>
|
||||||
|
{children}
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
PageForm.displayName = 'PageFormBody';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page form body layout, by default the content body is scrollable.
|
||||||
|
* @returns {React.ReactNode}
|
||||||
|
*/
|
||||||
|
const PageFormBody: FC<{ children: React.ReactNode } & SystemProps> = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<x.div flex="1" overflow="auto" {...props}>
|
||||||
|
{children}
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
PageFormBody.displayName = 'PageFormBody';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page form footer.
|
||||||
|
* @returns {React.ReactNode}
|
||||||
|
*/
|
||||||
|
const PageFormFooter: FC<{ children: React.ReactNode } & SystemProps> = ({ children }) => {
|
||||||
|
return <x.div>{children} </x.div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
PageFormFooter.displayName = 'PageFormFooter';
|
||||||
|
|
||||||
|
const footerActionsStyle = `
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-top: 1px solid rgb(210, 221, 226);
|
||||||
|
box-shadow: 0px -1px 4px 0px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.bp4-button-group{
|
||||||
|
.bp4-button{
|
||||||
|
&:not(:last-child),
|
||||||
|
&.bp4-popover-wrapper:not(:last-child) {
|
||||||
|
border-right: 1px solid rgba(92, 112, 127, 0.3);
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
&.bp4-intent-primary{
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PageFormFooterActions: FC<GroupProps> = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...restProps
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
spacing={20}
|
||||||
|
{...restProps}
|
||||||
|
className={clsx(css(footerActionsStyle), className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
PageFormFooterActions.displayName = 'PageFormFooterActions';
|
||||||
|
|
||||||
|
PageForm.Body = PageFormBody;
|
||||||
|
PageForm.Footer = PageFormFooter;
|
||||||
|
PageForm.FooterActions = PageFormFooterActions;
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
export * from './FormTopbar';
|
export * from './FormTopbar';
|
||||||
export * from './FormTopbarSelectInputs';
|
export * from './FormTopbarSelectInputs';
|
||||||
export * from './PageFormBigNumber';
|
export * from './PageFormBigNumber';
|
||||||
|
export * from './PageForm';
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import { x, SystemProps } from '@xstyled/emotion';
|
||||||
|
|
||||||
export function Paper({ children, className }) {
|
interface PaperProps extends SystemProps {
|
||||||
return <PaperRoot className={className}>{children}</PaperRoot>;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaperRoot = styled.div`
|
export const Paper = ({ children, ...props }: PaperProps) => {
|
||||||
border: 1px solid #d2dce2;
|
return (
|
||||||
background: #fff;
|
<x.div
|
||||||
padding: 10px;
|
background={'white'}
|
||||||
`;
|
p={'10px'}
|
||||||
|
border={'1px solid #d2dce2'}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Paper.displayName = 'Paper';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { Row, Col, Paper } from '@/components';
|
import { Row, Col, Paper } from '@/components';
|
||||||
@@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
|
|||||||
export default function MakeJournalFormFooter() {
|
export default function MakeJournalFormFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<MakeJournalFooterPaper>
|
<Paper p={'20px'}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
<MakeJournalFormFooterLeft />
|
<MakeJournalFormFooterLeft />
|
||||||
@@ -23,10 +22,7 @@ export default function MakeJournalFormFooter() {
|
|||||||
<MakeJournalFormFooterRight />
|
<MakeJournalFormFooterRight />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</MakeJournalFooterPaper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const MakeJournalFooterPaper = styled(Paper)`
|
|
||||||
padding: 20px;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { Spinner } from '@blueprintjs/core';
|
||||||
import {
|
import {
|
||||||
GetPdfTemplateBrandingStateResponse,
|
GetPdfTemplateBrandingStateResponse,
|
||||||
GetPdfTemplateResponse,
|
GetPdfTemplateResponse,
|
||||||
useGetPdfTemplate,
|
useGetPdfTemplate,
|
||||||
useGetPdfTemplateBrandingState,
|
useGetPdfTemplateBrandingState,
|
||||||
} from '@/hooks/query/pdf-templates';
|
} from '@/hooks/query/pdf-templates';
|
||||||
import { Spinner } from '@blueprintjs/core';
|
|
||||||
|
|
||||||
interface PdfTemplateContextValue {
|
interface PdfTemplateContextValue {
|
||||||
templateId: number | string;
|
templateId: number | string;
|
||||||
|
|
||||||
|
// Pdf template.
|
||||||
pdfTemplate: GetPdfTemplateResponse | undefined;
|
pdfTemplate: GetPdfTemplateResponse | undefined;
|
||||||
isPdfTemplateLoading: boolean;
|
isPdfTemplateLoading: boolean;
|
||||||
|
|
||||||
// Branding state.
|
// Branding state.
|
||||||
brandingTemplateState: GetPdfTemplateBrandingStateResponse | undefined;
|
brandingTemplateState: GetPdfTemplateBrandingStateResponse;
|
||||||
isBrandingTemplateLoading: boolean;
|
isBrandingTemplateLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +36,17 @@ export const BrandingTemplateBoot = ({
|
|||||||
useGetPdfTemplate(templateId, {
|
useGetPdfTemplate(templateId, {
|
||||||
enabled: !!templateId,
|
enabled: !!templateId,
|
||||||
});
|
});
|
||||||
// Retreives the branding template state.
|
// Retrieves the branding template state.
|
||||||
const { data: brandingTemplateState, isLoading: isBrandingTemplateLoading } =
|
const { data: brandingTemplateState, isLoading: isBrandingTemplateLoading } =
|
||||||
useGetPdfTemplateBrandingState();
|
useGetPdfTemplateBrandingState();
|
||||||
|
|
||||||
|
const isLoading = isPdfTemplateLoading ||
|
||||||
|
isBrandingTemplateLoading ||
|
||||||
|
!brandingTemplateState;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner size={20} />;
|
||||||
|
}
|
||||||
const value = {
|
const value = {
|
||||||
templateId,
|
templateId,
|
||||||
pdfTemplate,
|
pdfTemplate,
|
||||||
@@ -47,11 +56,6 @@ export const BrandingTemplateBoot = ({
|
|||||||
isBrandingTemplateLoading,
|
isBrandingTemplateLoading,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = isPdfTemplateLoading || isBrandingTemplateLoading;
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Spinner size={20} />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<PdfTemplateContext.Provider value={value}>
|
<PdfTemplateContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
transformToEditRequest,
|
transformToEditRequest,
|
||||||
transformToNewRequest,
|
transformToNewRequest,
|
||||||
|
useBrandingState,
|
||||||
useBrandingTemplateFormInitialValues,
|
useBrandingTemplateFormInitialValues,
|
||||||
} from './_utils';
|
} from './_utils';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
@@ -17,31 +18,40 @@ import {
|
|||||||
useEditPdfTemplate,
|
useEditPdfTemplate,
|
||||||
} from '@/hooks/query/pdf-templates';
|
} from '@/hooks/query/pdf-templates';
|
||||||
import { FormikHelpers } from 'formik';
|
import { FormikHelpers } from 'formik';
|
||||||
import { BrandingTemplateValues } from './types';
|
import { BrandingTemplateValues, BrandingState } from './types';
|
||||||
import { useUploadAttachments } from '@/hooks/query/attachments';
|
import { useUploadAttachments } from '@/hooks/query/attachments';
|
||||||
import { excludePrivateProps } from '@/utils';
|
import { excludePrivateProps } from '@/utils';
|
||||||
|
|
||||||
interface BrandingTemplateFormProps<T> extends ElementCustomizeProps<T> {
|
interface BrandingTemplateFormProps<
|
||||||
|
T extends BrandingTemplateValues,
|
||||||
|
Y extends BrandingState
|
||||||
|
> extends ElementCustomizeProps<T, Y> {
|
||||||
resource: string;
|
resource: string;
|
||||||
templateId?: number;
|
templateId?: number;
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
onError?: () => void;
|
onError?: () => void;
|
||||||
|
|
||||||
|
/* The default values hold the initial values of the form and the values being sent to the server. */
|
||||||
defaultValues?: T;
|
defaultValues?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
export function BrandingTemplateForm<
|
||||||
|
T extends BrandingTemplateValues,
|
||||||
|
Y extends BrandingState,
|
||||||
|
>({
|
||||||
templateId,
|
templateId,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resource,
|
resource,
|
||||||
...props
|
...props
|
||||||
}: BrandingTemplateFormProps<T>) {
|
}: BrandingTemplateFormProps<T, Y>) {
|
||||||
|
// Create/edit pdf template mutators.
|
||||||
const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate();
|
const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate();
|
||||||
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
|
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
|
||||||
|
|
||||||
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
|
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
|
||||||
const [isUploading, setIsLoading] = useState<boolean>(false);
|
const [, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
// Uploads the attachments.
|
// Uploads the attachments.
|
||||||
const { mutateAsync: uploadAttachments } = useUploadAttachments({
|
const { mutateAsync: uploadAttachments } = useUploadAttachments({
|
||||||
@@ -122,7 +132,7 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ElementCustomize<T>
|
<ElementCustomize<T, Y>
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Button } from '@blueprintjs/core';
|
import { Button, ButtonProps } from '@blueprintjs/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { FFormGroup } from '@/components';
|
import { FFormGroup, Icon } from '@/components';
|
||||||
|
|
||||||
export const BrandingThemeFormGroup = styled(FFormGroup)`
|
export const BrandingThemeFormGroup = styled(FFormGroup)`
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -14,33 +14,21 @@ export const BrandingThemeFormGroup = styled(FFormGroup)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const BrandingThemeSelectButton = styled(Button)`
|
export const BrandingThemeSelectButton = (props: ButtonProps) => {
|
||||||
position: relative;
|
return (
|
||||||
padding-right: 26px;
|
<Button
|
||||||
|
text={props?.text || 'Brand Theme'}
|
||||||
|
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
||||||
|
minimal
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
&::after {
|
export const convertBrandingTemplatesToOptions = (
|
||||||
content: '';
|
brandingTemplates: Array<any>,
|
||||||
display: inline-block;
|
) => {
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-top: 5px solid #98a1ae;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
right: -2px;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -2px;
|
|
||||||
margin-right: 12px;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
export const convertBrandingTemplatesToOptions = (brandingTemplates: Array<any>) => {
|
|
||||||
return brandingTemplates?.map(
|
return brandingTemplates?.map(
|
||||||
(template) =>
|
(template) => ({ text: template.template_name, value: template.id } || []),
|
||||||
({ text: template.template_name, value: template.id } || []),
|
);
|
||||||
)
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from '@/hooks/query/pdf-templates';
|
} from '@/hooks/query/pdf-templates';
|
||||||
import { useBrandingTemplateBoot } from './BrandingTemplateBoot';
|
import { useBrandingTemplateBoot } from './BrandingTemplateBoot';
|
||||||
import { transformToForm } from '@/utils';
|
import { transformToForm } from '@/utils';
|
||||||
import { BrandingTemplateValues } from './types';
|
import { BrandingState, BrandingTemplateValues } from './types';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
|
||||||
const commonExcludedAttrs = ['templateName', 'companyLogoUri'];
|
const commonExcludedAttrs = ['templateName', 'companyLogoUri'];
|
||||||
@@ -44,11 +44,10 @@ export const useBrandingTemplateFormInitialValues = <
|
|||||||
>(
|
>(
|
||||||
initialValues = {},
|
initialValues = {},
|
||||||
) => {
|
) => {
|
||||||
const { pdfTemplate, brandingTemplateState } = useBrandingTemplateBoot();
|
const { pdfTemplate } = useBrandingTemplateBoot();
|
||||||
|
|
||||||
const brandingAttributes = {
|
const brandingAttributes = {
|
||||||
templateName: pdfTemplate?.templateName,
|
templateName: pdfTemplate?.templateName,
|
||||||
...brandingTemplateState,
|
|
||||||
...pdfTemplate?.attributes,
|
...pdfTemplate?.attributes,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@@ -57,6 +56,15 @@ export const useBrandingTemplateFormInitialValues = <
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useBrandingState = (state?: Partial<BrandingState>): BrandingState => {
|
||||||
|
const { brandingTemplateState } = useBrandingTemplateBoot();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...brandingTemplateState,
|
||||||
|
...state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getCustomizeDrawerNameFromResource = (resource: string) => {
|
export const getCustomizeDrawerNameFromResource = (resource: string) => {
|
||||||
const pairs = {
|
const pairs = {
|
||||||
SaleInvoice: DRAWERS.INVOICE_CUSTOMIZE,
|
SaleInvoice: DRAWERS.INVOICE_CUSTOMIZE,
|
||||||
|
|||||||
@@ -6,4 +6,18 @@ export interface BrandingTemplateValues {
|
|||||||
// Company logo
|
// Company logo
|
||||||
companyLogoKey?: string;
|
companyLogoKey?: string;
|
||||||
companyLogoUri?: string;
|
companyLogoUri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrandingState extends ElementPreviewState {
|
||||||
|
companyName: string;
|
||||||
|
companyAddress: string;
|
||||||
|
|
||||||
|
companyLogoKey: string;
|
||||||
|
companyLogoUri: string;
|
||||||
|
|
||||||
|
primaryColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElementPreviewState {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,8 @@ import { compose } from '@/utils';
|
|||||||
function CreditNotePdfPreviewDialogContent({
|
function CreditNotePdfPreviewDialogContent({
|
||||||
subscriptionForm: { creditNoteId },
|
subscriptionForm: { creditNoteId },
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, pdfUrl } = usePdfCreditNote(creditNoteId);
|
const { isLoading, pdfUrl, filename } = usePdfCreditNote(creditNoteId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div class="dialog__header-actions">
|
<div class="dialog__header-actions">
|
||||||
@@ -27,7 +27,7 @@ function CreditNotePdfPreviewDialogContent({
|
|||||||
|
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
href={pdfUrl}
|
href={pdfUrl}
|
||||||
download={'creditNote.pdf'}
|
download={filename}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function EstimatePdfPreviewDialogContent({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, pdfUrl } = usePdfEstimate(estimateId);
|
const { isLoading, pdfUrl, filename } = usePdfEstimate(estimateId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -30,7 +30,7 @@ function EstimatePdfPreviewDialogContent({
|
|||||||
|
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
href={pdfUrl}
|
href={pdfUrl}
|
||||||
download={'estimate.pdf'}
|
download={filename}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function InvoicePdfPreviewDialogContent({
|
|||||||
// #withDialog
|
// #withDialog
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, pdfUrl } = usePdfInvoice(invoiceId);
|
const { isLoading, pdfUrl, filename } = usePdfInvoice(invoiceId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -29,7 +29,7 @@ function InvoicePdfPreviewDialogContent({
|
|||||||
|
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
href={pdfUrl}
|
href={pdfUrl}
|
||||||
download={'invoice.pdf'}
|
download={filename}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { compose } from '@/utils';
|
|||||||
function PaymentReceivePdfPreviewDialogContent({
|
function PaymentReceivePdfPreviewDialogContent({
|
||||||
subscriptionForm: { paymentReceiveId },
|
subscriptionForm: { paymentReceiveId },
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, pdfUrl } = usePdfPaymentReceive(paymentReceiveId);
|
const { isLoading, pdfUrl, filename } = usePdfPaymentReceive(paymentReceiveId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -27,7 +27,7 @@ function PaymentReceivePdfPreviewDialogContent({
|
|||||||
|
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
href={pdfUrl}
|
href={pdfUrl}
|
||||||
download={'payment.pdf'}
|
download={filename}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function ReceiptPdfPreviewDialogContent({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, pdfUrl } = usePdfReceipt(receiptId);
|
const { isLoading, pdfUrl, filename } = usePdfReceipt(receiptId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -29,7 +29,7 @@ function ReceiptPdfPreviewDialogContent({
|
|||||||
|
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
href={pdfUrl}
|
href={pdfUrl}
|
||||||
download={'receipt.pdf'}
|
download={filename}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,17 +9,23 @@ import { ElementCustomizeTabsControllerProvider } from './ElementCustomizeTabsCo
|
|||||||
import { ElementCustomizeFields } from './ElementCustomizeFields';
|
import { ElementCustomizeFields } from './ElementCustomizeFields';
|
||||||
import { ElementCustomizePreview } from './ElementCustomizePreview';
|
import { ElementCustomizePreview } from './ElementCustomizePreview';
|
||||||
import { extractChildren } from '@/utils/extract-children';
|
import { extractChildren } from '@/utils/extract-children';
|
||||||
|
import { ElementPreviewState } from '../BrandingTemplates/types';
|
||||||
|
import { TabProps } from '@blueprintjs/core';
|
||||||
|
import { useBrandingState } from '../BrandingTemplates/_utils';
|
||||||
|
|
||||||
export interface ElementCustomizeProps<T> extends ElementCustomizeFormProps<T> {
|
export interface ElementCustomizeProps<T, Y>
|
||||||
|
extends ElementCustomizeFormProps<T, Y> {
|
||||||
|
brandingState?: Y;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ElementCustomize<T>({
|
export interface ElementCustomizeContentProps {
|
||||||
initialValues,
|
children?: React.ReactNode;
|
||||||
validationSchema,
|
}
|
||||||
onSubmit,
|
|
||||||
|
export function ElementCustomizeContent({
|
||||||
children,
|
children,
|
||||||
}: ElementCustomizeProps<T>) {
|
}: ElementCustomizeContentProps) {
|
||||||
const PaperTemplate = React.useMemo(
|
const PaperTemplate = React.useMemo(
|
||||||
() => extractChildren(children, ElementCustomize.PaperTemplate),
|
() => extractChildren(children, ElementCustomize.PaperTemplate),
|
||||||
[children],
|
[children],
|
||||||
@@ -28,23 +34,34 @@ export function ElementCustomize<T>({
|
|||||||
() => extractChildren(children, ElementCustomize.FieldsTab),
|
() => extractChildren(children, ElementCustomize.FieldsTab),
|
||||||
[children],
|
[children],
|
||||||
);
|
);
|
||||||
|
const brandingState = useBrandingState();
|
||||||
|
const value = { PaperTemplate, CustomizeTabs, brandingState };
|
||||||
|
|
||||||
const value = { PaperTemplate, CustomizeTabs };
|
return (
|
||||||
|
<ElementCustomizeTabsControllerProvider>
|
||||||
|
<ElementCustomizeProvider value={value}>
|
||||||
|
<Group spacing={0} align="stretch">
|
||||||
|
<ElementCustomizeFields />
|
||||||
|
<ElementCustomizePreview />
|
||||||
|
</Group>
|
||||||
|
</ElementCustomizeProvider>
|
||||||
|
</ElementCustomizeTabsControllerProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ElementCustomize<T, Y extends ElementPreviewState>({
|
||||||
|
initialValues,
|
||||||
|
validationSchema,
|
||||||
|
onSubmit,
|
||||||
|
children,
|
||||||
|
}: ElementCustomizeProps<T, Y>) {
|
||||||
return (
|
return (
|
||||||
<ElementCustomizeForm
|
<ElementCustomizeForm
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<ElementCustomizeTabsControllerProvider>
|
{children}
|
||||||
<ElementCustomizeProvider value={value}>
|
|
||||||
<Group spacing={0} align="stretch">
|
|
||||||
<ElementCustomizeFields />
|
|
||||||
<ElementCustomizePreview />
|
|
||||||
</Group>
|
|
||||||
</ElementCustomizeProvider>
|
|
||||||
</ElementCustomizeTabsControllerProvider>
|
|
||||||
</ElementCustomizeForm>
|
</ElementCustomizeForm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -59,16 +76,17 @@ ElementCustomize.PaperTemplate = ({
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ElementCustomizeContentProps {
|
export interface ElementCustomizeFieldsTabProps {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
tabProps?: Partial<TabProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementCustomize.FieldsTab = ({
|
ElementCustomize.FieldsTab = ({
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
children,
|
children,
|
||||||
}: ElementCustomizeContentProps) => {
|
}: ElementCustomizeFieldsTabProps) => {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { ElementPreviewState } from '../BrandingTemplates/types';
|
||||||
|
|
||||||
interface ElementCustomizeValue {
|
interface ElementCustomizeValue {
|
||||||
PaperTemplate?: React.ReactNode;
|
PaperTemplate?: React.ReactNode;
|
||||||
CustomizeTabs: React.ReactNode;
|
CustomizeTabs: React.ReactNode;
|
||||||
|
brandingState?: ElementPreviewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementCustomizeContext = createContext<ElementCustomizeValue>(
|
const ElementCustomizeContext = createContext<ElementCustomizeValue>(
|
||||||
{} as ElementCustomizeValue,
|
{} as ElementCustomizeValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ElementCustomizeProvider: React.FC<{
|
interface ElementCustomizeProviderProps {
|
||||||
value: ElementCustomizeValue;
|
value: ElementCustomizeValue;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ value, children }) => {
|
}
|
||||||
|
|
||||||
|
export const ElementCustomizeProvider = ({ value, children }: ElementCustomizeProviderProps) => {
|
||||||
return (
|
return (
|
||||||
<ElementCustomizeContext.Provider value={{ ...value }}>
|
<ElementCustomizeContext.Provider value={{ ...value }}>
|
||||||
{children}
|
{children}
|
||||||
@@ -29,4 +33,4 @@ export const useElementCustomizeContext = (): ElementCustomizeValue => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Stack } from '@/components';
|
import { Box, Stack } from '@/components';
|
||||||
import { Tab, Tabs } from '@blueprintjs/core';
|
import { Tab, TabProps, Tabs } from '@blueprintjs/core';
|
||||||
import { ElementCustomizeHeader } from './ElementCustomizeHeader';
|
import { ElementCustomizeHeader } from './ElementCustomizeHeader';
|
||||||
import {
|
import {
|
||||||
ElementCustomizeTabsEnum,
|
ElementCustomizeTabsEnum,
|
||||||
@@ -11,7 +11,6 @@ import styles from './ElementCustomizeTabs.module.scss';
|
|||||||
|
|
||||||
export function ElementCustomizeTabs() {
|
export function ElementCustomizeTabs() {
|
||||||
const { setCurrentTabId } = useElementCustomizeTabsController();
|
const { setCurrentTabId } = useElementCustomizeTabsController();
|
||||||
|
|
||||||
const { CustomizeTabs } = useElementCustomizeContext();
|
const { CustomizeTabs } = useElementCustomizeContext();
|
||||||
|
|
||||||
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
|
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
|
||||||
@@ -32,9 +31,19 @@ export function ElementCustomizeTabs() {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={styles.tabsList}
|
className={styles.tabsList}
|
||||||
>
|
>
|
||||||
{tabItems?.map(({ id, label }: { id: string; label: string }) => (
|
{tabItems?.map(
|
||||||
<Tab id={id} key={id} title={label} />
|
({
|
||||||
))}
|
id,
|
||||||
|
label,
|
||||||
|
tabProps,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
tabProps?: TabProps;
|
||||||
|
}) => (
|
||||||
|
<Tab id={id} key={id} title={label} {...tabProps} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Formik, Form, FormikHelpers } from 'formik';
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
|
|
||||||
export interface ElementCustomizeFormProps<T> {
|
export interface ElementCustomizeFormProps<T, Y> {
|
||||||
initialValues?: T;
|
initialValues?: T;
|
||||||
validationSchema?: any;
|
validationSchema?: any;
|
||||||
onSubmit?: (values: T, formikHelpers: FormikHelpers<T>) => void;
|
onSubmit?: (values: T, formikHelpers: FormikHelpers<T>) => void;
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export function BrandingCompanyLogoUploadField() {
|
|||||||
onChange={(file) => {
|
onChange={(file) => {
|
||||||
const imageUrl = file ? URL.createObjectURL(file) : '';
|
const imageUrl = file ? URL.createObjectURL(file) : '';
|
||||||
|
|
||||||
|
// Reset the logo key since it is changed.
|
||||||
|
setFieldValue('companyLogoKey', '');
|
||||||
|
|
||||||
setFieldValue('_companyLogoFile', file);
|
setFieldValue('_companyLogoFile', file);
|
||||||
setFieldValue('companyLogoUri', imageUrl);
|
setFieldValue('companyLogoUri', imageUrl);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { Row, Col, Paper } from '@/components';
|
import { Row, Col, Paper } from '@/components';
|
||||||
@@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
|
|||||||
export default function ExpenseFormFooter() {
|
export default function ExpenseFormFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<ExpensesFooterPaper>
|
<Paper p={'20px'}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
<ExpenseFormFooterLeft />
|
<ExpenseFormFooterLeft />
|
||||||
@@ -23,11 +22,7 @@ export default function ExpenseFormFooter() {
|
|||||||
<ExpenseFormFooterRight />
|
<ExpenseFormFooterRight />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</ExpensesFooterPaper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpensesFooterPaper = styled(Paper)`
|
|
||||||
padding: 20px;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { Text, Classes, Button, Intent, Tag } from '@blueprintjs/core';
|
import { Text, Classes, Button, Intent } from '@blueprintjs/core';
|
||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
import { AppToaster, Box, Group, Stack } from '@/components';
|
import { AppToaster, Box, Group, Stack } from '@/components';
|
||||||
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
||||||
import { useDrawerActions } from '@/hooks/state';
|
import { useDrawerActions } from '@/hooks/state';
|
||||||
import { useCreateStripeCheckoutSession } from '@/hooks/query/payment-link';
|
import {
|
||||||
|
useCreateStripeCheckoutSession,
|
||||||
|
useGeneratePaymentLinkInvoicePdf,
|
||||||
|
} from '@/hooks/query/payment-link';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import { downloadFile } from '@/hooks/useDownloadFile';
|
||||||
import styles from './PaymentPortal.module.scss';
|
import styles from './PaymentPortal.module.scss';
|
||||||
|
|
||||||
export function PaymentPortal() {
|
export function PaymentPortal() {
|
||||||
@@ -15,10 +19,34 @@ export function PaymentPortal() {
|
|||||||
isLoading: isStripeCheckoutLoading,
|
isLoading: isStripeCheckoutLoading,
|
||||||
} = useCreateStripeCheckoutSession();
|
} = useCreateStripeCheckoutSession();
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: generatePaymentLinkInvoice,
|
||||||
|
isLoading: isInvoiceGenerating,
|
||||||
|
} = useGeneratePaymentLinkInvoicePdf();
|
||||||
|
|
||||||
// Handles invoice preview button click.
|
// Handles invoice preview button click.
|
||||||
const handleInvoicePreviewBtnClick = () => {
|
const handleInvoicePreviewBtnClick = () => {
|
||||||
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
|
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handles invoice download button click.
|
||||||
|
const handleInvoiceDownloadBtnClick = () => {
|
||||||
|
generatePaymentLinkInvoice({ paymentLinkId: linkId })
|
||||||
|
.then((data) => {
|
||||||
|
downloadFile(
|
||||||
|
data,
|
||||||
|
`Invoice ${sharableLinkMeta?.invoiceNo}`,
|
||||||
|
'application/pdf',
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// handles the pay button click.
|
// handles the pay button click.
|
||||||
const handlePayButtonClick = () => {
|
const handlePayButtonClick = () => {
|
||||||
createStripeCheckoutSession({ linkId })
|
createStripeCheckoutSession({ linkId })
|
||||||
@@ -125,6 +153,8 @@ export function PaymentPortal() {
|
|||||||
<Button
|
<Button
|
||||||
minimal
|
minimal
|
||||||
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
|
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
|
||||||
|
onClick={handleInvoiceDownloadBtnClick}
|
||||||
|
loading={isInvoiceGenerating}
|
||||||
>
|
>
|
||||||
Download Invoice
|
Download Invoice
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { PaymentPortal } from './PaymentPortal';
|
import { Helmet } from 'react-helmet';
|
||||||
import { PaymentPortalBoot } from './PaymentPortalBoot';
|
|
||||||
import BodyClassName from 'react-body-classname';
|
import BodyClassName from 'react-body-classname';
|
||||||
import styles from './PaymentPortal.module.scss';
|
import { PaymentPortal } from './PaymentPortal';
|
||||||
|
import { PaymentPortalBoot, usePaymentPortalBoot } from './PaymentPortalBoot';
|
||||||
import { PaymentInvoicePreviewDrawer } from './drawers/PaymentInvoicePreviewDrawer/PaymentInvoicePreviewDrawer';
|
import { PaymentInvoicePreviewDrawer } from './drawers/PaymentInvoicePreviewDrawer/PaymentInvoicePreviewDrawer';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import styles from './PaymentPortal.module.scss';
|
||||||
|
|
||||||
export default function PaymentPortalPage() {
|
export default function PaymentPortalPage() {
|
||||||
const { linkId } = useParams<{ linkId: string }>();
|
const { linkId } = useParams<{ linkId: string }>();
|
||||||
@@ -12,9 +13,26 @@ export default function PaymentPortalPage() {
|
|||||||
return (
|
return (
|
||||||
<BodyClassName className={styles.rootBodyPage}>
|
<BodyClassName className={styles.rootBodyPage}>
|
||||||
<PaymentPortalBoot linkId={linkId}>
|
<PaymentPortalBoot linkId={linkId}>
|
||||||
|
<PaymentPortalHelmet />
|
||||||
<PaymentPortal />
|
<PaymentPortal />
|
||||||
<PaymentInvoicePreviewDrawer name={DRAWERS.PAYMENT_INVOICE_PREVIEW} />
|
<PaymentInvoicePreviewDrawer name={DRAWERS.PAYMENT_INVOICE_PREVIEW} />
|
||||||
</PaymentPortalBoot>
|
</PaymentPortalBoot>
|
||||||
</BodyClassName>
|
</BodyClassName>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the document title of the current payment page.
|
||||||
|
* @returns {React.ReactNode}
|
||||||
|
*/
|
||||||
|
function PaymentPortalHelmet() {
|
||||||
|
const { sharableLinkMeta } = usePaymentPortalBoot();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{sharableLinkMeta?.invoiceNo} | {sharableLinkMeta?.organization?.name}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Button, FormGroup, Intent } from '@blueprintjs/core';
|
|||||||
import { TimezonePicker } from '@blueprintjs/timezone';
|
import { TimezonePicker } from '@blueprintjs/timezone';
|
||||||
import { ErrorMessage, FastField } from 'formik';
|
import { ErrorMessage, FastField } from 'formik';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { getAllCountries } from '@bigcapital/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
@@ -23,7 +24,6 @@ import { getAllCurrenciesOptions } from '@/constants/currencies';
|
|||||||
import { getFiscalYear } from '@/constants/fiscalYearOptions';
|
import { getFiscalYear } from '@/constants/fiscalYearOptions';
|
||||||
import { getLanguages } from '@/constants/languagesOptions';
|
import { getLanguages } from '@/constants/languagesOptions';
|
||||||
import { useGeneralFormContext } from './GeneralFormProvider';
|
import { useGeneralFormContext } from './GeneralFormProvider';
|
||||||
import { getAllCountries } from '@/utils/countries';
|
|
||||||
|
|
||||||
import { shouldBaseCurrencyUpdate } from './utils';
|
import { shouldBaseCurrencyUpdate } from './utils';
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function StripePaymentMethod() {
|
|||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button small icon={<MoreIcon size={16} />} />
|
<Button small icon={<MoreIcon height={10} width={10} />} />
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user