mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-24 16:49:48 +00:00
Compare commits
119 Commits
upload-com
...
v0.20.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
cbc60b3c73 | ||
|
|
6caa1311fd | ||
|
|
cd0bbd11c3 | ||
|
|
2a944f8507 | ||
|
|
8a2754d9ce | ||
|
|
ace75f2dfa | ||
|
|
7ceb785c1b | ||
|
|
904a52f5a1 | ||
|
|
04fe65b176 | ||
|
|
7ac6e0d349 | ||
|
|
4ec3586173 | ||
|
|
4b6ab7035e | ||
|
|
3fe7babe00 | ||
|
|
f21570982e | ||
|
|
ad8fe52b84 | ||
|
|
15ce6ac710 | ||
|
|
783387dce6 | ||
|
|
863c7ad99f | ||
|
|
776b69475c | ||
|
|
6b6027a588 | ||
|
|
d465ee15bd | ||
|
|
be2049ca6e | ||
|
|
9b63c176cd | ||
|
|
e506a7ba35 | ||
|
|
2191ad0d40 | ||
|
|
ca162206a3 | ||
|
|
c5d7a2bfd8 | ||
|
|
b9506424d1 | ||
|
|
46a145ae58 | ||
|
|
e4044ef563 | ||
|
|
1cc71eb368 | ||
|
|
323b95de7b | ||
|
|
b0658be041 | ||
|
|
b222d56148 | ||
|
|
946872204b | ||
|
|
2f9adfd908 | ||
|
|
1c8e19378f | ||
|
|
7aed3d9c8c | ||
|
|
b125e3e58b | ||
|
|
70bba4a6ed | ||
|
|
1570995021 | ||
|
|
8109236e72 | ||
|
|
9ba651decb | ||
|
|
eb5fdbf4ee | ||
|
|
9827a84857 | ||
|
|
3308133736 | ||
|
|
3129c76c30 | ||
|
|
c0a4c965f0 | ||
|
|
e04f5d26a3 | ||
|
|
ad74007d58 | ||
|
|
6271d6c268 | ||
|
|
7756b5b304 | ||
|
|
8de8695b25 | ||
|
|
11c56c75a4 | ||
|
|
f5a1d68c52 | ||
|
|
0ae7a25c27 | ||
|
|
16eaacd4bc | ||
|
|
809973730f | ||
|
|
2ebb4595a8 | ||
|
|
77f628509c | ||
|
|
d2cd32a735 | ||
|
|
4665f529e6 | ||
|
|
df706d2573 | ||
|
|
5270e99de8 | ||
|
|
eb48f66f6e | ||
|
|
2b42215381 | ||
|
|
18d6ec7b59 | ||
|
|
430cf19533 | ||
|
|
542e61dbfc | ||
|
|
9517b4e279 | ||
|
|
162b92ce84 | ||
|
|
a183666df6 |
@@ -93,3 +93,10 @@ S3_BUCKET=
|
|||||||
# PostHog
|
# PostHog
|
||||||
POSTHOG_API_KEY=
|
POSTHOG_API_KEY=
|
||||||
POSTHOG_HOST=
|
POSTHOG_HOST=
|
||||||
|
|
||||||
|
# Stripe Payment
|
||||||
|
STRIPE_PAYMENT_SECRET_KEY=
|
||||||
|
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||||
|
STRIPE_PAYMENT_CLIENT_ID=
|
||||||
|
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||||
|
STRIPE_PAYMENT_REDIRECT_URL=
|
||||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -2,6 +2,23 @@
|
|||||||
|
|
||||||
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.0]
|
||||||
|
|
||||||
|
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
||||||
|
* feat: Onboard accounts to Stripe Connect by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/668
|
||||||
|
* feat: Upload company logo to invoice templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/670
|
||||||
|
* fix: Invoice pdf customize by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/672
|
||||||
|
* fix: Invoice customize bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/673
|
||||||
|
* feat: Clean up payment links endpoints by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/674
|
||||||
|
* feat: Hook up company logo to server-side pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/675
|
||||||
|
* feat: Company branding preferences by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/677
|
||||||
|
* feat: Pdf templates customer/company addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/678
|
||||||
|
* fix: Listen to Stripe session completed event by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/679
|
||||||
|
* feat: Track pdf templates Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/680
|
||||||
|
* fix: Branding customize content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/681
|
||||||
|
* feat: Listen to Stripe integration events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/682
|
||||||
|
* feat: Hook up customer/company address to invoice preview of payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/683
|
||||||
|
|
||||||
# [0.19.17]
|
# [0.19.17]
|
||||||
|
|
||||||
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
|
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -109,11 +110,13 @@
|
|||||||
"rtl-detect": "^1.0.4",
|
"rtl-detect": "^1.0.4",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.1",
|
||||||
|
"stripe": "^16.10.0",
|
||||||
"tmp-promise": "^3.0.3",
|
"tmp-promise": "^3.0.3",
|
||||||
"ts-transformer-keys": "^0.4.2",
|
"ts-transformer-keys": "^0.4.2",
|
||||||
"tsyringe": "^4.3.0",
|
"tsyringe": "^4.3.0",
|
||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
"uniqid": "^5.2.0",
|
"uniqid": "^5.2.0",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^0.28.1"
|
"yup": "^0.28.1"
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -48,8 +64,8 @@ block head
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
-webkit-box-align: center;
|
-webkit-box-align: flex-start;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
-webkit-box-pack: start;
|
-webkit-box-pack: start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -132,11 +148,12 @@ block head
|
|||||||
|
|
||||||
block content
|
block content
|
||||||
div(class=`${prefix}-root`)
|
div(class=`${prefix}-root`)
|
||||||
div(class=`${prefix}-big-title`) Credit Note
|
|
||||||
|
|
||||||
if showCompanyLogo
|
//- Header (includes big title, details and logo)
|
||||||
div(class=`${prefix}-logo-wrap`)
|
div(class=`${prefix}-header`)
|
||||||
img(src=companyLogo alt=`Company Logo`)
|
//- Header details (includes big title and details)
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
div(class=`${prefix}-big-title`) Credit Note
|
||||||
|
|
||||||
div(class=`${prefix}-terms-list`)
|
div(class=`${prefix}-terms-list`)
|
||||||
if showCreditNoteNumber
|
if showCreditNoteNumber
|
||||||
@@ -149,17 +166,19 @@ block content
|
|||||||
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
|
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
|
||||||
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
|
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 showBilledFromAddress
|
if showCompanyAddress
|
||||||
div(class=`${prefix}-address`)
|
div(class=`${prefix}-address-from`)
|
||||||
strong #{companyName}
|
div !{companyAddress}
|
||||||
each address in billedFromAddress
|
|
||||||
div #{address}
|
if showCustomerAddress
|
||||||
if showBilledToAddress
|
div(class=`${prefix}-address-to`)
|
||||||
div(class=`${prefix}-address`)
|
|
||||||
strong #{billedToLabel}
|
strong #{billedToLabel}
|
||||||
each address in billedToAddress
|
div !{customerAddress}
|
||||||
div #{address}
|
|
||||||
|
|
||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead
|
thead
|
||||||
@@ -187,12 +206,12 @@ block content
|
|||||||
div(class=`${prefix}-totals__item-amount`) #{totalLabel}:
|
div(class=`${prefix}-totals__item-amount`) #{totalLabel}:
|
||||||
div(class=`${prefix}-totals__item-label`) #{total}
|
div(class=`${prefix}-totals__item-label`) #{total}
|
||||||
|
|
||||||
if showCustomerNote
|
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
|
if showTermsConditions && termsConditions
|
||||||
div(class=`${prefix}-statement`)
|
div(class=`${prefix}-statement`)
|
||||||
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
|
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
|
||||||
div(class=`${prefix}-statement__value`) #{termsConditions}
|
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}-terms {
|
.#{prefix}-terms {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -47,9 +63,7 @@ block head
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
-webkit-box-align: center;
|
align-items: flex-start;
|
||||||
align-items: center;
|
|
||||||
-webkit-box-pack: start;
|
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@@ -133,11 +147,13 @@ block head
|
|||||||
|
|
||||||
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
|
//- Header (invluces big title, details and logo)
|
||||||
div(class=`${prefix}-logo-wrap`)
|
div(class=`${prefix}-header`)
|
||||||
img(alt="", src=companyLogo)
|
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
|
h1(class=`${prefix}-big-title`) Estimate
|
||||||
|
|
||||||
//- Terms List
|
//- Terms List
|
||||||
div(class=`${prefix}-terms`)
|
div(class=`${prefix}-terms`)
|
||||||
@@ -145,28 +161,32 @@ block content
|
|||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
|
||||||
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
|
||||||
|
|
||||||
if showEstimateDate
|
if showEstimateDate
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
|
||||||
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
div(class=`${prefix}-terms-item__value`) #{estimateDate}
|
||||||
|
|
||||||
if showExpirationDate
|
if showExpirationDate
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
|
||||||
div(class=`${prefix}-terms-item__value`) #{expirationDate}
|
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`)
|
||||||
if showBilledFromAddress
|
if showCompanyAddress
|
||||||
div(class=`${prefix}-address`)
|
div(class=`${prefix}-address-from`)
|
||||||
strong #{companyName}
|
div !{companyAddress}
|
||||||
each item in billedFromAddress
|
|
||||||
div(class=`${prefix}-address__item`) #{item}
|
|
||||||
|
|
||||||
if showBilledToAddress
|
if showCustomerAddress
|
||||||
div(class=`${prefix}-address`)
|
div(class=`${prefix}-address-to`)
|
||||||
strong #{billedToLabel}
|
strong #{billedToLabel}
|
||||||
each item in billedToAddress
|
div !{customerAddress}
|
||||||
div(class=`${prefix}-address__item`) #{item}
|
|
||||||
|
|
||||||
//- Table section (Line items)
|
//- Table section (Line items)
|
||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -48,9 +64,7 @@ block head
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
-webkit-box-align: center;
|
align-items: flex-start;
|
||||||
align-items: center;
|
|
||||||
-webkit-box-pack: start;
|
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@@ -141,13 +155,13 @@ 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};`)
|
||||||
|
|
||||||
|
//- Header (includes big title, details and logo )
|
||||||
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
//- Title and company logo
|
//- Title and company logo
|
||||||
h1(class=`${prefix}-big-title`) Invoice
|
h1(class=`${prefix}-big-title`) Invoice
|
||||||
|
|
||||||
if showCompanyLogo
|
|
||||||
div(class=`${prefix}-logo-wrap`)
|
|
||||||
img(alt="", src=companyLogo)
|
|
||||||
|
|
||||||
//- Invoice details
|
//- Invoice details
|
||||||
div(class=`${prefix}-details`)
|
div(class=`${prefix}-details`)
|
||||||
if showInvoiceNumber
|
if showInvoiceNumber
|
||||||
@@ -165,19 +179,21 @@ block content
|
|||||||
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`)
|
||||||
if showBilledFromAddress
|
if showCompanyAddress
|
||||||
div(class=`${prefix}-address-from`)
|
div(class=`${prefix}-address-from`)
|
||||||
strong #{companyName}
|
div !{companyAddress}
|
||||||
each item in billedFromAddres
|
|
||||||
div(class=`${prefix}-address-from__item`) #{item}
|
|
||||||
|
|
||||||
if showBillingToAddress
|
if showCustomerAddress
|
||||||
div(class=`${prefix}-address-to`)
|
div(class=`${prefix}-address-to`)
|
||||||
strong #{billedToLabel}
|
strong #{billedToLabel}
|
||||||
each item in billedToAddress
|
div !{customerAddress}
|
||||||
div(class=`${prefix}-address-to__item`) #{item}
|
|
||||||
|
|
||||||
//- Invoice table
|
//- Invoice table
|
||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -46,9 +62,7 @@ block head
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
-webkit-box-align: center;
|
align-items: flex-start;
|
||||||
align-items: center;
|
|
||||||
-webkit-box-pack: start;
|
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@@ -122,12 +136,11 @@ block head
|
|||||||
}
|
}
|
||||||
block content
|
block content
|
||||||
div(class=`${prefix}-root`)
|
div(class=`${prefix}-root`)
|
||||||
|
//- Header (includes big title, details and logo )
|
||||||
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
div(class=`${prefix}-big-title`) Payment
|
div(class=`${prefix}-big-title`) Payment
|
||||||
|
|
||||||
if showCompanyLogo
|
|
||||||
div(class=`${prefix}-logo-wrap`)
|
|
||||||
img(src=companyLogo alt="Company Logo")
|
|
||||||
|
|
||||||
div(class=`${prefix}-terms-list`)
|
div(class=`${prefix}-terms-list`)
|
||||||
if showPaymentReceivedNumber
|
if showPaymentReceivedNumber
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
@@ -139,18 +152,19 @@ block content
|
|||||||
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
|
||||||
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
|
||||||
|
|
||||||
div(class=`${prefix}-addresses`)
|
if showCompanyLogo && companyLogoUri
|
||||||
if showBilledFromAddress
|
div(class=`${prefix}-logo-wrap`)
|
||||||
div(class=`${prefix}-address`)
|
img(src=companyLogoUri alt="Company Logo")
|
||||||
strong(class=`${prefix}-address__item`) #{companyName}
|
|
||||||
each addressLine in billedFromAddress
|
|
||||||
div(class=`${prefix}-address__item`) #{addressLine}
|
|
||||||
|
|
||||||
if showBillingToAddress
|
div(class=`${prefix}-addresses`)
|
||||||
div(class=`${prefix}-address`)
|
if showCompanyAddress
|
||||||
strong(class=`${prefix}-address__item`) #{billedToLabel}
|
div(class=`${prefix}-address-from`)
|
||||||
each addressLine in billedToAddress
|
div !{companyAddress}
|
||||||
div(class=`${prefix}-address__item`) #{addressLine}
|
|
||||||
|
if showCustomerAddress
|
||||||
|
div(class=`${prefix}-address-to`)
|
||||||
|
strong #{billedToLabel}
|
||||||
|
div !{customerAddress}
|
||||||
|
|
||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
thead
|
thead
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -46,8 +60,8 @@ block head
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
-webkit-box-align: center;
|
-webkit-box-align: flex-start;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
-webkit-box-pack: start;
|
-webkit-box-pack: start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -125,37 +139,40 @@ 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};`)
|
||||||
|
|
||||||
|
//- Header (includes big title, details and logo )
|
||||||
|
div(class=`${prefix}-header`)
|
||||||
|
//- Header details (includes big title and details )
|
||||||
|
div(class=`${prefix}-header-details`)
|
||||||
//- Title and company logo
|
//- Title and company logo
|
||||||
h1(class=`${prefix}-big-title`) Receipt
|
h1(class=`${prefix}-big-title`) Receipt
|
||||||
|
|
||||||
if showCompanyLogo
|
|
||||||
div(class=`${prefix}-logo-wrap`)
|
|
||||||
img(src=companyLogo alt=`Company Logo`)
|
|
||||||
|
|
||||||
//- 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
|
if showReceiptDate
|
||||||
div(class=`${prefix}-terms-item`)
|
div(class=`${prefix}-terms-item`)
|
||||||
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
|
||||||
span(class=`${prefix}-terms-item__value`)= receiptDate
|
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`)
|
||||||
if showBilledFromAddress
|
if showCompanyAddress
|
||||||
div(class=`${prefix}-address`)
|
div(class=`${prefix}-address-from`)
|
||||||
strong= companyName
|
div !{companyAddress}
|
||||||
each addressLine in billedFromAddress
|
|
||||||
div= addressLine
|
|
||||||
|
|
||||||
if showBilledToAddress
|
if showCustomerAddress
|
||||||
div(class=`${prefix}-address`)
|
div(class=`${prefix}-address-to`)
|
||||||
strong= billedToLabel
|
strong #{billedToLabel}
|
||||||
each addressLine in billedToAddress
|
div !{customerAddress}
|
||||||
div= addressLine
|
|
||||||
|
|
||||||
//- Table Section
|
//- Table Section
|
||||||
table(class=`${prefix}-table`)
|
table(class=`${prefix}-table`)
|
||||||
@@ -186,13 +203,13 @@ block content
|
|||||||
span(class=`${prefix}-totals__line__amount`)= total
|
span(class=`${prefix}-totals__line__amount`)= total
|
||||||
|
|
||||||
//- Customer Note Section
|
//- Customer Note Section
|
||||||
if showCustomerNote
|
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
|
||||||
|
|
||||||
//- Terms & Conditions Section
|
//- Terms & Conditions Section
|
||||||
if showTermsConditions
|
if showTermsConditions && termsConditions
|
||||||
div(class=`${prefix}-statement`)
|
div(class=`${prefix}-statement`)
|
||||||
div(class=`${prefix}-statement__label`)= termsConditionsLabel
|
div(class=`${prefix}-statement__label`)= termsConditionsLabel
|
||||||
div(class=`${prefix}-statement__value`)= termsConditions
|
div(class=`${prefix}-statement__value`)= termsConditions
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import BaseController from '@/api/controllers/BaseController';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class OrganizationController extends BaseController {
|
export default class OrganizationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
organizationService: OrganizationService;
|
private organizationService: OrganizationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -56,10 +56,10 @@ export default class OrganizationController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization setup schema.
|
* Build organization validation schema.
|
||||||
* @return {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
private get commonOrganizationValidationSchema(): ValidationChain[] {
|
private get buildOrganizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('name').exists().trim(),
|
check('name').exists().trim(),
|
||||||
check('industry').optional({ nullable: true }).isString().trim(),
|
check('industry').optional({ nullable: true }).isString().trim(),
|
||||||
@@ -72,21 +72,34 @@ export default class OrganizationController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build organization validation schema.
|
|
||||||
* @returns {ValidationChain[]}
|
|
||||||
*/
|
|
||||||
private get buildOrganizationValidationSchema(): ValidationChain[] {
|
|
||||||
return [...this.commonOrganizationValidationSchema];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update organization validation schema.
|
* Update organization validation schema.
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
...this.commonOrganizationValidationSchema,
|
// # Profile
|
||||||
|
check('name').optional().trim(),
|
||||||
|
check('industry').optional({ nullable: true }).isString().trim(),
|
||||||
|
check('location').optional().isString().isISO31661Alpha2(),
|
||||||
|
check('base_currency').optional().isISO4217(),
|
||||||
|
check('timezone').optional().isIn(moment.tz.names()),
|
||||||
|
check('fiscal_year').optional().isIn(MONTHS),
|
||||||
|
check('language').optional().isString().isIn(ACCEPTED_LOCALES),
|
||||||
|
check('date_format').optional().isIn(DATE_FORMATS),
|
||||||
|
|
||||||
|
// # Address
|
||||||
|
check('address.address_1').optional().isString().trim(),
|
||||||
|
check('address.address_2').optional().isString().trim(),
|
||||||
|
check('address.postal_code').optional().isString().trim(),
|
||||||
|
check('address.city').optional().isString().trim(),
|
||||||
|
check('address.state_province').optional().isString().trim(),
|
||||||
|
check('address.phone').optional().isString().trim(),
|
||||||
|
|
||||||
|
// # Branding
|
||||||
|
check('primary_color').optional({ nullable: true }).isHexColor().trim(),
|
||||||
|
check('logo_key').optional({ nullable: true }).isString().trim(),
|
||||||
|
|
||||||
check('tax_number').optional({ nullable: true }).isString().trim(),
|
check('tax_number').optional({ nullable: true }).isString().trim(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -156,7 +169,7 @@ export default class OrganizationController extends BaseController {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const tenantDTO = this.matchedBodyData(req);
|
const tenantDTO = this.matchedBodyData(req, { includeOptionals: false });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.organizationService.updateOrganization(tenantId, tenantDTO);
|
await this.organizationService.updateOrganization(tenantId, tenantDTO);
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { Request, Response, Router, NextFunction } from 'express';
|
||||||
|
import { body, param } from 'express-validator';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { PaymentServicesApplication } from '@/services/PaymentServices/PaymentServicesApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentServicesController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private paymentServicesApp: PaymentServicesApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
asyncMiddleware(this.getPaymentServicesSpecificInvoice.bind(this))
|
||||||
|
);
|
||||||
|
router.get('/state', this.getPaymentMethodsState.bind(this));
|
||||||
|
router.get('/:paymentServiceId', this.getPaymentService.bind(this));
|
||||||
|
router.post(
|
||||||
|
'/:paymentMethodId',
|
||||||
|
[
|
||||||
|
param('paymentMethodId').exists(),
|
||||||
|
|
||||||
|
body('name').optional().isString(),
|
||||||
|
body('options.bank_account_id').optional().isNumeric(),
|
||||||
|
body('options.clearing_account_id').optional().isNumeric(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.updatePaymentMethod.bind(this))
|
||||||
|
);
|
||||||
|
router.delete(
|
||||||
|
'/:paymentMethodId',
|
||||||
|
[param('paymentMethodId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.deletePaymentMethod.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts types list.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentServicesSpecificInvoice(
|
||||||
|
req: Request<{ invoiceId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentServices =
|
||||||
|
await this.paymentServicesApp.getPaymentServicesForInvoice(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ paymentServices });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a specific payment service.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentService(
|
||||||
|
req: Request<{ paymentServiceId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentServiceId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentService = await this.paymentServicesApp.getPaymentService(
|
||||||
|
tenantId,
|
||||||
|
paymentServiceId
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({ data: paymentService });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given payment method settings.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async updatePaymentMethod(
|
||||||
|
req: Request<{ paymentMethodId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentMethodId } = req.params;
|
||||||
|
const updatePaymentMethodDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.paymentServicesApp.editPaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentMethodId,
|
||||||
|
updatePaymentMethodDTO
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
id: paymentMethodId,
|
||||||
|
message: 'The given payment method has been updated.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment state providing state.
|
||||||
|
* @param {Request} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async getPaymentMethodsState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentMethodsState =
|
||||||
|
await this.paymentServicesApp.getPaymentMethodsState(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ data: paymentMethodsState });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given payment method.
|
||||||
|
* @param {Request<{ paymentMethodId: number }>} req - Request.
|
||||||
|
* @param {Response} res - Response.
|
||||||
|
* @param {NextFunction} next - Next function.
|
||||||
|
* @return {Promise<Response | void>}
|
||||||
|
*/
|
||||||
|
private async deletePaymentMethod(
|
||||||
|
req: Request<{ paymentMethodId: number }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { paymentMethodId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.paymentServicesApp.deletePaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentMethodId
|
||||||
|
);
|
||||||
|
return res.status(204).send({
|
||||||
|
id: paymentMethodId,
|
||||||
|
message: 'The payment method has been deleted.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ export class PdfTemplatesController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.editPdfTemplate.bind(this)
|
this.editPdfTemplate.bind(this)
|
||||||
);
|
);
|
||||||
|
router.get('/state', this.getOrganizationBrandingState.bind(this));
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
[query('resource').optional()],
|
[query('resource').optional()],
|
||||||
@@ -175,4 +176,20 @@ export class PdfTemplatesController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getOrganizationBrandingState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data =
|
||||||
|
await this.pdfTemplateApplication.getPdfTemplateBrandingState(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCr
|
|||||||
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
|
||||||
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
|
||||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
import { GetCreditNoteState } from '@/services/CreditNotes/GetCreditNoteState';
|
||||||
/**
|
/**
|
||||||
* Credit notes controller.
|
* Credit notes controller.
|
||||||
* @service
|
* @service
|
||||||
@@ -81,6 +82,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
creditNotePdf: GetCreditNotePdf;
|
creditNotePdf: GetCreditNotePdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
getCreditNoteStateService: GetCreditNoteState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -105,6 +109,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.newCreditNote),
|
this.asyncMiddleware(this.newCreditNote),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(CreditNoteAction.View, AbilitySubject.CreditNote),
|
||||||
|
this.asyncMiddleware(this.getCreditNoteState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
// Get specific credit note.
|
// Get specific credit note.
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
@@ -736,6 +746,23 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getCreditNoteState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.getCreditNoteStateService.getCreditNoteState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
|
this.getPaymentReceivedState.bind(this),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
@@ -546,6 +552,23 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getSaleEstimateState = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleEstimatesApplication.getSaleEstimateState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
this.asyncMiddleware(this.getInvoicePaymentTransactions),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
|
asyncMiddleware(this.getSaleInvoiceState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -138,6 +144,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
|
||||||
@@ -258,6 +265,11 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
|
|
||||||
// Pdf template id.
|
// Pdf template id.
|
||||||
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
|
// Payment methods.
|
||||||
|
check('payment_methods').optional({ nullable: true }).isArray(),
|
||||||
|
check('payment_methods.*.payment_integration_id').exists().toInt(),
|
||||||
|
check('payment_methods.*.enable').exists().isBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,6 +460,24 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
return res.status(200).send({ saleInvoice });
|
return res.status(200).send({ saleInvoice });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getSaleInvoiceState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.saleInvoiceApplication.getSaleInvoiceState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve paginated sales invoices with custom view metadata.
|
* Retrieve paginated sales invoices with custom view metadata.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/state',
|
||||||
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
|
asyncMiddleware(this.getSaleReceiptState.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
|
||||||
@@ -369,6 +375,30 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getSaleReceiptState(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
// Retrieves receipt in pdf format.
|
||||||
|
try {
|
||||||
|
const data = await this.saleReceiptsApplication.getSaleReceiptState(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sale receipt notification via SMS.
|
* Sale receipt notification via SMS.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { param } from 'express-validator';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { PaymentLinksApplication } from '@/services/PaymentLinks/PaymentLinksApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PublicSharableLinkController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private paymentLinkApp: PaymentLinksApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkPublicMeta.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice/pdf',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkInvoicePdf.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/:paymentLinkId/stripe_checkout_session',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.createInvoicePaymentLinkCheckoutSession.bind(this)
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment link public meta.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkPublicMeta(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.paymentLinkApp.getInvoicePaymentLink(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Stripe checkout session for the given payment link id.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response|void>}
|
||||||
|
*/
|
||||||
|
public async createInvoicePaymentLinkCheckoutSession(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session =
|
||||||
|
await this.paymentLinkApp.createInvoicePaymentCheckoutSession(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
return res.status(200).send(session);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkInvoicePdf(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
res.send(pdfContent);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import { AbilitySubject, PaymentReceiveAction } from '@/interfaces';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
import { GenerateShareLink } from '@/services/Sales/Invoices/GenerateeInvoicePaymentLink';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ShareLinkController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private generateShareLinkService: GenerateShareLink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/payment-links/generate',
|
||||||
|
CheckPolicies(PaymentReceiveAction.Edit, AbilitySubject.PaymentReceive),
|
||||||
|
[
|
||||||
|
body('transaction_type').exists(),
|
||||||
|
body('transaction_id').exists().isNumeric().toInt(),
|
||||||
|
body('publicity').optional(),
|
||||||
|
body('expiry_date').optional({ nullable: true }),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.generateShareLink.bind(this))
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates sharable link for the given transaction.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async generateShareLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { transactionType, transactionId, publicity, expiryDate } =
|
||||||
|
this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const link = await this.generateShareLinkService.generatePaymentLink(
|
||||||
|
tenantId,
|
||||||
|
transactionId,
|
||||||
|
transactionType,
|
||||||
|
publicity,
|
||||||
|
expiryDate
|
||||||
|
);
|
||||||
|
res.status(200).json({ link });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import { StripePaymentApplication } from '@/services/StripePayment/StripePaymentApplication';
|
||||||
|
import BaseController from '../BaseController';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeIntegrationController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentApp: StripePaymentApplication;
|
||||||
|
|
||||||
|
public router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get('/link', this.getStripeConnectLink.bind(this));
|
||||||
|
router.post(
|
||||||
|
'/callback',
|
||||||
|
[body('code').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.exchangeOAuth.bind(this)
|
||||||
|
);
|
||||||
|
router.post('/account', asyncMiddleware(this.createAccount.bind(this)));
|
||||||
|
router.post(
|
||||||
|
'/account_link',
|
||||||
|
[body('stripe_account_id').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.createAccountLink.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves Stripe OAuth2 connect link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response|void>}
|
||||||
|
*/
|
||||||
|
public async getStripeConnectLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const authorizationUri = this.stripePaymentApp.getStripeConnectLink();
|
||||||
|
|
||||||
|
return res.status(200).send({ url: authorizationUri });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges the given Stripe authorization code to Stripe user id and access token.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async exchangeOAuth(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { code } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.stripePaymentApp.exchangeStripeOAuthToken(tenantId, code);
|
||||||
|
|
||||||
|
return res.status(200).send({});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe account.
|
||||||
|
* @param {Request} req - The Express request object.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async createAccount(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const accountId = await this.stripePaymentApp.createStripeAccount(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(201).json({
|
||||||
|
accountId,
|
||||||
|
message: 'The Stripe account has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe account session.
|
||||||
|
* @param {Request} req - The Express request object.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async createAccountLink(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { stripeAccountId } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clientSecret = await this.stripePaymentApp.createAccountLink(
|
||||||
|
tenantId,
|
||||||
|
stripeAccountId
|
||||||
|
);
|
||||||
|
return res.status(200).json({ clientSecret });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { StripeWebhookEventPayload } from '@/interfaces/StripePayment';
|
||||||
|
import { StripePaymentService } from '@/services/StripePayment/StripePaymentService';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeWebhooksController {
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentService: StripePaymentService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
public router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/stripe',
|
||||||
|
bodyParser.raw({ type: 'application/json' }),
|
||||||
|
this.handleWebhook.bind(this)
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming Stripe webhook events.
|
||||||
|
* Verifies the webhook signature, processes the event based on its type,
|
||||||
|
* and triggers appropriate actions or events in the system.
|
||||||
|
*
|
||||||
|
* @param {Request} req - The Express request object containing the webhook payload.
|
||||||
|
* @param {Response} res - The Express response object.
|
||||||
|
* @param {NextFunction} next - The Express next middleware function.
|
||||||
|
*/
|
||||||
|
private async handleWebhook(req: Request, res: Response, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
let event = req.body;
|
||||||
|
const sig = req.headers['stripe-signature'];
|
||||||
|
|
||||||
|
// Verify webhook signature and extract the event.
|
||||||
|
// See https://stripe.com/docs/webhooks#verify-events for more information.
|
||||||
|
try {
|
||||||
|
event = this.stripePaymentService.stripe.webhooks.constructEvent(
|
||||||
|
req.rawBody,
|
||||||
|
sig,
|
||||||
|
config.stripePayment.webhooksSecret
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||||
|
}
|
||||||
|
// Handle the event based on its type
|
||||||
|
switch (event.type) {
|
||||||
|
case 'checkout.session.completed':
|
||||||
|
// Triggers `onStripeCheckoutSessionCompleted` event.
|
||||||
|
this.eventPublisher.emitAsync(
|
||||||
|
events.stripeWebhooks.onCheckoutSessionCompleted,
|
||||||
|
{
|
||||||
|
event,
|
||||||
|
} as StripeWebhookEventPayload
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'account.updated':
|
||||||
|
this.eventPublisher.emitAsync(
|
||||||
|
events.stripeWebhooks.onAccountUpdated,
|
||||||
|
{
|
||||||
|
event,
|
||||||
|
} as StripeWebhookEventPayload
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// Add more cases as needed
|
||||||
|
default:
|
||||||
|
console.log(`Unhandled event type ${event.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ received: true });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { NextFunction, Router, Request, Response } from 'express';
|
import { NextFunction, Router, Request, Response } from 'express';
|
||||||
import { Inject, Service } from 'typedi';
|
import Container, { Inject, Service } from 'typedi';
|
||||||
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
||||||
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
||||||
|
import { StripeWebhooksController } from '../StripeIntegration/StripeWebhooksController';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class Webhooks extends BaseController {
|
export class Webhooks extends BaseController {
|
||||||
@@ -24,6 +25,8 @@ export class Webhooks extends BaseController {
|
|||||||
|
|
||||||
router.post('/lemon', this.lemonWebhooks.bind(this));
|
router.post('/lemon', this.lemonWebhooks.bind(this));
|
||||||
|
|
||||||
|
router.use(Container.get(StripeWebhooksController).router());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,11 @@ import { Webhooks } from './controllers/Webhooks/Webhooks';
|
|||||||
import { ExportController } from './controllers/Export/ExportController';
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||||
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||||
|
import { StripeIntegrationController } from './controllers/StripeIntegration/StripeIntegrationController';
|
||||||
|
import { ShareLinkController } from './controllers/ShareLink/ShareLinkController';
|
||||||
|
import { PublicSharableLinkController } from './controllers/ShareLink/PublicSharableLinkController';
|
||||||
import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController';
|
import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController';
|
||||||
|
import { PaymentServicesController } from './controllers/PaymentServices/PaymentServicesController';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -83,6 +87,10 @@ export default () => {
|
|||||||
app.use('/account', Container.get(Account).router());
|
app.use('/account', Container.get(Account).router());
|
||||||
app.use('/webhooks', Container.get(Webhooks).router());
|
app.use('/webhooks', Container.get(Webhooks).router());
|
||||||
app.use('/demo', Container.get(OneClickDemoController).router());
|
app.use('/demo', Container.get(OneClickDemoController).router());
|
||||||
|
app.use(
|
||||||
|
'/payment-links',
|
||||||
|
Container.get(PublicSharableLinkController).router()
|
||||||
|
);
|
||||||
|
|
||||||
// - Dashboard routes.
|
// - Dashboard routes.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -148,14 +156,22 @@ export default () => {
|
|||||||
dashboard.use('/import', Container.get(ImportController).router());
|
dashboard.use('/import', Container.get(ImportController).router());
|
||||||
dashboard.use('/export', Container.get(ExportController).router());
|
dashboard.use('/export', Container.get(ExportController).router());
|
||||||
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
||||||
|
dashboard.use(
|
||||||
|
'/stripe_integration',
|
||||||
|
Container.get(StripeIntegrationController).router()
|
||||||
|
);
|
||||||
dashboard.use(
|
dashboard.use(
|
||||||
'/pdf-templates',
|
'/pdf-templates',
|
||||||
Container.get(PdfTemplatesController).router()
|
Container.get(PdfTemplatesController).router()
|
||||||
);
|
);
|
||||||
|
dashboard.use(
|
||||||
|
'/payment-services',
|
||||||
|
Container.get(PaymentServicesController).router()
|
||||||
|
);
|
||||||
dashboard.use('/', Container.get(ProjectTasksController).router());
|
dashboard.use('/', Container.get(ProjectTasksController).router());
|
||||||
dashboard.use('/', Container.get(ProjectTimesController).router());
|
dashboard.use('/', Container.get(ProjectTimesController).router());
|
||||||
dashboard.use('/', Container.get(WarehousesItemController).router());
|
dashboard.use('/', Container.get(WarehousesItemController).router());
|
||||||
|
dashboard.use('/', Container.get(ShareLinkController).router());
|
||||||
|
|
||||||
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
dashboard.use('/dashboard', Container.get(DashboardController).router());
|
||||||
dashboard.use('/', Container.get(Miscellaneous).router());
|
dashboard.use('/', Container.get(Miscellaneous).router());
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export const injectI18nUtils = (req) => {
|
|||||||
export const initalizeTenantServices = async (tenantId: number) => {
|
export const initalizeTenantServices = async (tenantId: number) => {
|
||||||
const tenant = await Tenant.query()
|
const tenant = await Tenant.query()
|
||||||
.findById(tenantId)
|
.findById(tenantId)
|
||||||
.withGraphFetched('metadata');
|
.withGraphFetched('metadata')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
const tenantServices = Container.get(TenancyService);
|
const tenantServices = Container.get(TenancyService);
|
||||||
const tenantsManager = Container.get(TenantsManagerService);
|
const tenantsManager = Container.get(TenantsManagerService);
|
||||||
|
|||||||
@@ -259,6 +259,17 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
posthog: {
|
posthog: {
|
||||||
apiKey: process.env.POSTHOG_API_KEY,
|
apiKey: process.env.POSTHOG_API_KEY,
|
||||||
host: process.env.POSTHOG_HOST
|
host: process.env.POSTHOG_HOST,
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stripe Payment Integration.
|
||||||
|
*/
|
||||||
|
stripePayment: {
|
||||||
|
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY || '',
|
||||||
|
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY || '',
|
||||||
|
clientId: process.env.STRIPE_PAYMENT_CLIENT_ID || '',
|
||||||
|
redirectTo: process.env.STRIPE_PAYMENT_REDIRECT_URL || '',
|
||||||
|
webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET || '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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_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';
|
||||||
@@ -26,10 +27,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';
|
||||||
@@ -69,6 +72,18 @@ export const BANK_RULE_CREATED = 'Bank rule created';
|
|||||||
export const BANK_RULE_EDITED = 'Bank rule edited';
|
export const BANK_RULE_EDITED = 'Bank rule edited';
|
||||||
export const BANK_RULE_DELETED = 'Bank rule deleted';
|
export const BANK_RULE_DELETED = 'Bank rule deleted';
|
||||||
|
|
||||||
|
export const PDF_TEMPLATE_CREATED = 'PDF template created';
|
||||||
|
export const PDF_TEMPLATE_EDITED = 'PDF template edited';
|
||||||
|
export const PDF_TEMPLATE_DELETED = 'PDF template deleted';
|
||||||
|
export const PDF_TEMPLATE_ASSIGNED_DEFAULT = 'PDF template assigned as default';
|
||||||
|
|
||||||
|
export const PAYMENT_METHOD_EDITED = 'Payment method edited';
|
||||||
|
export const PAYMENT_METHOD_DELETED = 'Payment method deleted';
|
||||||
|
|
||||||
|
export const INVOICE_PAYMENT_LINK_GENERATED = 'Invoice payment link generated';
|
||||||
|
|
||||||
|
export const STRIPE_INTEGRAION_CONNECTED = 'Stripe integration oauth2 connected';
|
||||||
|
|
||||||
// # Event Groups
|
// # Event Groups
|
||||||
export const ACCOUNT_GROUP = 'Account';
|
export const ACCOUNT_GROUP = 'Account';
|
||||||
export const ITEM_GROUP = 'Item';
|
export const ITEM_GROUP = 'Item';
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.string('stripe_pintent_id').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.dropColumn('stripe_pintent_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('payment_integrations', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('service');
|
||||||
|
table.string('name');
|
||||||
|
table.string('slug');
|
||||||
|
table.boolean('payment_enabled').defaultTo(false);
|
||||||
|
table.boolean('payout_enabled').defaultTo(false);
|
||||||
|
table.string('account_id');
|
||||||
|
table.json('options');
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('payment_integrations');
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('transactions_payment_methods', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
table.integer('reference_id').unsigned();
|
||||||
|
table.string('reference_type');
|
||||||
|
table
|
||||||
|
.integer('payment_integration_id')
|
||||||
|
.unsigned()
|
||||||
|
.index()
|
||||||
|
.references('id')
|
||||||
|
.inTable('payment_integrations');
|
||||||
|
table.boolean('enable').defaultTo(false);
|
||||||
|
table.json('options').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('transactions_payment_methods');
|
||||||
|
};
|
||||||
@@ -31,6 +31,17 @@ export const PrepardExpenses = {
|
|||||||
predefined: true,
|
predefined: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const StripeClearingAccount = {
|
||||||
|
name: 'Stripe Clearing',
|
||||||
|
slug: 'stripe-clearing',
|
||||||
|
account_type: 'other-current-asset',
|
||||||
|
parent_account_id: null,
|
||||||
|
code: '100020',
|
||||||
|
active: true,
|
||||||
|
index: 1,
|
||||||
|
predefined: true,
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: 'Bank Account',
|
name: 'Bank Account',
|
||||||
|
|||||||
@@ -262,16 +262,24 @@ export type ICreditNoteGLCommonEntry = Pick<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export interface CreditNotePdfTemplateAttributes {
|
export interface CreditNotePdfTemplateAttributes {
|
||||||
|
// # Primary color
|
||||||
primaryColor: string;
|
primaryColor: string;
|
||||||
secondaryColor: string;
|
secondaryColor: string;
|
||||||
|
|
||||||
|
// # Company logo
|
||||||
showCompanyLogo: boolean;
|
showCompanyLogo: boolean;
|
||||||
companyLogo: string;
|
companyLogo: string;
|
||||||
|
|
||||||
|
// # Company name
|
||||||
companyName: string;
|
companyName: string;
|
||||||
|
|
||||||
billedToAddress: string[];
|
// # Customer Address
|
||||||
billedFromAddress: string[];
|
showCustomerAddress: boolean;
|
||||||
showBilledToAddress: boolean;
|
customerAddress: string;
|
||||||
showBilledFromAddress: boolean;
|
|
||||||
|
// # Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
billedToLabel: string;
|
billedToLabel: string;
|
||||||
|
|
||||||
total: string;
|
total: string;
|
||||||
@@ -306,3 +314,7 @@ export interface CreditNotePdfTemplateAttributes {
|
|||||||
showCreditNoteDate: boolean;
|
showCreditNoteDate: boolean;
|
||||||
creditNoteDateLabel: string;
|
creditNoteDateLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICreditNoteState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -207,10 +207,13 @@ export interface PaymentReceivedPdfTemplateAttributes {
|
|||||||
companyLogo: string;
|
companyLogo: string;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
|
|
||||||
billedToAddress: string[];
|
// Customer Address
|
||||||
billedFromAddress: string[];
|
showCustomerAddress: boolean;
|
||||||
showBilledFromAddress: boolean;
|
customerAddress: string;
|
||||||
showBillingToAddress: boolean;
|
|
||||||
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
billedToLabel: string;
|
billedToLabel: string;
|
||||||
|
|
||||||
total: string;
|
total: string;
|
||||||
@@ -235,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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,34 @@ import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
|||||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||||
import { AttachmentLinkDTO } from './Attachments';
|
import { AttachmentLinkDTO } from './Attachments';
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLink {
|
||||||
|
id: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLinkEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
enable: true;
|
||||||
|
paymentIntegrationId: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: number;
|
||||||
|
oldSaleInvoiceId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISaleInvoice {
|
export interface ISaleInvoice {
|
||||||
id: number;
|
id: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -50,6 +78,8 @@ export interface ISaleInvoice {
|
|||||||
invoiceMessage: string;
|
invoiceMessage: string;
|
||||||
|
|
||||||
pdfTemplateId?: number;
|
pdfTemplateId?: number;
|
||||||
|
|
||||||
|
paymentMethods?: Array<PaymentIntegrationTransactionLink>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceDTO {
|
export interface ISaleInvoiceDTO {
|
||||||
@@ -136,7 +166,13 @@ export interface ISaleInvoiceEditingPayload {
|
|||||||
|
|
||||||
export interface ISaleInvoiceDeletePayload {
|
export interface ISaleInvoiceDeletePayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
saleInvoice: ISaleInvoice;
|
oldSaleInvoice: ISaleInvoice;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISaleInvoiceDeletingPayload {
|
||||||
|
tenantId: number;
|
||||||
|
oldSaleInvoice: ISaleInvoice;
|
||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
@@ -223,7 +259,6 @@ export interface ISaleInvoiceMailSent {
|
|||||||
messageOptions: SendInvoiceMailDTO;
|
messageOptions: SendInvoiceMailDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Invoice Pdf Document
|
// Invoice Pdf Document
|
||||||
export interface InvoicePdfLine {
|
export interface InvoicePdfLine {
|
||||||
item: string;
|
item: string;
|
||||||
@@ -259,8 +294,13 @@ export interface InvoicePdfTemplateAttributes {
|
|||||||
invoiceNumber: string;
|
invoiceNumber: string;
|
||||||
showInvoiceNumber: boolean;
|
showInvoiceNumber: boolean;
|
||||||
|
|
||||||
showBillingToAddress: boolean;
|
// Customer Address
|
||||||
showBilledFromAddress: boolean;
|
showCustomerAddress: boolean;
|
||||||
|
customerAddress: string;
|
||||||
|
|
||||||
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
billedToLabel: string;
|
billedToLabel: string;
|
||||||
|
|
||||||
lineItemLabel: string;
|
lineItemLabel: string;
|
||||||
@@ -298,7 +338,8 @@ export interface InvoicePdfTemplateAttributes {
|
|||||||
statementLabel: string;
|
statementLabel: string;
|
||||||
showStatement: boolean;
|
showStatement: boolean;
|
||||||
statement: string;
|
statement: string;
|
||||||
|
}
|
||||||
billedToAddress: string[];
|
|
||||||
billedFromAddres: string[];
|
export interface ISaleInvocieState {
|
||||||
|
defaultTemplateId: number;
|
||||||
}
|
}
|
||||||
@@ -163,11 +163,13 @@ export interface ISaleReceiptBrandingTemplateAttributes {
|
|||||||
companyLogo: string;
|
companyLogo: string;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
|
|
||||||
// Address
|
// Customer Address
|
||||||
billedToAddress: string[];
|
showCustomerAddress: boolean;
|
||||||
billedFromAddress: string[];
|
customerAddress: string;
|
||||||
showBilledFromAddress: boolean;
|
|
||||||
showBilledToAddress: boolean;
|
// Company address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
billedToLabel: string;
|
billedToLabel: string;
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
@@ -209,3 +211,8 @@ export interface ISaleReceiptBrandingTemplateAttributes {
|
|||||||
showReceiptDate: boolean;
|
showReceiptDate: boolean;
|
||||||
receiptDateLabel: string;
|
receiptDateLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ISaleReceiptState {
|
||||||
|
defaultTemplateId: number;
|
||||||
|
}
|
||||||
@@ -18,14 +18,26 @@ export interface IOrganizationBuildDTO {
|
|||||||
dateFormat?: string;
|
dateFormat?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OrganizationAddressDTO {
|
||||||
|
address1: string;
|
||||||
|
address2: string;
|
||||||
|
postalCode: string;
|
||||||
|
city: string;
|
||||||
|
stateProvince: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IOrganizationUpdateDTO {
|
export interface IOrganizationUpdateDTO {
|
||||||
name: string;
|
name: string;
|
||||||
location: string;
|
location?: string;
|
||||||
baseCurrency: string;
|
baseCurrency?: string;
|
||||||
timezone: string;
|
timezone?: string;
|
||||||
fiscalYear: string;
|
fiscalYear?: string;
|
||||||
industry: string;
|
industry?: string;
|
||||||
taxNumber: string;
|
taxNumber?: string;
|
||||||
|
primaryColor?: string;
|
||||||
|
logoKey?: string;
|
||||||
|
address?: OrganizationAddressDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOrganizationBuildEventPayload {
|
export interface IOrganizationBuildEventPayload {
|
||||||
|
|||||||
20
packages/server/src/interfaces/StripePayment.ts
Normal file
20
packages/server/src/interfaces/StripePayment.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export interface StripePaymentLinkCreatedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
paymentLinkId: string;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
stripeIntegrationId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeCheckoutSessionCompletedEventPayload {
|
||||||
|
event: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeInvoiceCheckoutSessionPOJO {
|
||||||
|
sessionId: string;
|
||||||
|
publishableKey: string;
|
||||||
|
redirectTo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StripeWebhookEventPayload {
|
||||||
|
event: any;
|
||||||
|
}
|
||||||
@@ -117,8 +117,10 @@ import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAcco
|
|||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||||
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
||||||
import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscription/events/TriggerInvalidateCacheOnSubscriptionChange';
|
|
||||||
import { EventsTrackerListeners } from '@/services/EventsTracker/events/events';
|
import { EventsTrackerListeners } from '@/services/EventsTracker/events/events';
|
||||||
|
import { InvoicePaymentIntegrationSubscriber } from '@/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber';
|
||||||
|
import { StripeWebhooksSubscriber } from '@/services/StripePayment/events/StripeWebhooksSubscriber';
|
||||||
|
import { SeedStripeAccountsOnOAuthGrantedSubscriber } from '@/services/StripePayment/events/SeedStripeAccounts';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -252,7 +254,6 @@ export const susbcribers = () => {
|
|||||||
// Subscription
|
// Subscription
|
||||||
SubscribeFreeOnSignupCommunity,
|
SubscribeFreeOnSignupCommunity,
|
||||||
SendVerfiyMailOnSignUp,
|
SendVerfiyMailOnSignUp,
|
||||||
TriggerInvalidateCacheOnSubscriptionChange,
|
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
AttachmentsOnSaleInvoiceCreated,
|
AttachmentsOnSaleInvoiceCreated,
|
||||||
@@ -291,6 +292,11 @@ export const susbcribers = () => {
|
|||||||
// Demo Account
|
// Demo Account
|
||||||
SeedInitialDemoAccountDataOnOrgBuild,
|
SeedInitialDemoAccountDataOnOrgBuild,
|
||||||
|
|
||||||
|
// Stripe Payment
|
||||||
|
InvoicePaymentIntegrationSubscriber,
|
||||||
|
StripeWebhooksSubscriber,
|
||||||
|
SeedStripeAccountsOnOAuthGrantedSubscriber,
|
||||||
|
|
||||||
...EventsTrackerListeners
|
...EventsTrackerListeners
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ import { BankRuleCondition } from '@/models/BankRuleCondition';
|
|||||||
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
|
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
|
||||||
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
|
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
|
||||||
import { PdfTemplate } from '@/models/PdfTemplate';
|
import { PdfTemplate } from '@/models/PdfTemplate';
|
||||||
|
import { PaymentIntegration } from '@/models/PaymentIntegration';
|
||||||
|
import { TransactionPaymentServiceEntry } from '@/models/TransactionPaymentServiceEntry';
|
||||||
|
|
||||||
export default (knex) => {
|
export default (knex) => {
|
||||||
const models = {
|
const models = {
|
||||||
@@ -140,7 +142,9 @@ export default (knex) => {
|
|||||||
BankRuleCondition,
|
BankRuleCondition,
|
||||||
RecognizedBankTransaction,
|
RecognizedBankTransaction,
|
||||||
MatchedBankTransaction,
|
MatchedBankTransaction,
|
||||||
PdfTemplate
|
PdfTemplate,
|
||||||
|
PaymentIntegration,
|
||||||
|
TransactionPaymentServiceEntry,
|
||||||
};
|
};
|
||||||
return mapValues(models, (model) => model.bindKnex(knex));
|
return mapValues(models, (model) => model.bindKnex(knex));
|
||||||
};
|
};
|
||||||
|
|||||||
61
packages/server/src/models/PaymentIntegration.ts
Normal file
61
packages/server/src/models/PaymentIntegration.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Model } from 'objection';
|
||||||
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
|
export class PaymentIntegration extends Model {
|
||||||
|
paymentEnabled!: boolean;
|
||||||
|
payoutEnabled!: boolean;
|
||||||
|
|
||||||
|
static get tableName() {
|
||||||
|
return 'payment_integrations';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get idColumn() {
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['fullEnabled'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get jsonAttributes() {
|
||||||
|
return ['options'];
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullEnabled() {
|
||||||
|
return this.paymentEnabled && this.payoutEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Query to filter enabled payment and payout.
|
||||||
|
*/
|
||||||
|
fullEnabled(query) {
|
||||||
|
query.where('paymentEnabled', true).andWhere('payoutEnabled', true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get jsonSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'service'],
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
service: { type: 'string' },
|
||||||
|
paymentEnabled: { type: 'boolean' },
|
||||||
|
payoutEnabled: { type: 'boolean' },
|
||||||
|
accountId: { type: 'string' },
|
||||||
|
options: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
bankAccountId: { type: 'number' },
|
||||||
|
clearingAccountId: { type: 'number' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: { type: 'string', format: 'date-time' },
|
||||||
|
updatedAt: { type: 'string', format: 'date-time' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -413,6 +413,10 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||||
const Document = require('models/Document');
|
const Document = require('models/Document');
|
||||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||||
|
const {
|
||||||
|
TransactionPaymentServiceEntry,
|
||||||
|
} = require('models/TransactionPaymentServiceEntry');
|
||||||
|
const { PdfTemplate } = require('models/PdfTemplate');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -509,7 +513,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
join: {
|
join: {
|
||||||
from: 'sales_invoices.warehouseId',
|
from: 'sales_invoices.warehouseId',
|
||||||
to: 'warehouses.id',
|
to: 'warehouses.id',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -566,12 +570,42 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
modelClass: MatchedBankTransaction,
|
modelClass: MatchedBankTransaction,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_invoices.id',
|
from: 'sales_invoices.id',
|
||||||
to: "matched_bank_transactions.referenceId",
|
to: 'matched_bank_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('reference_type', 'SaleInvoice');
|
query.where('reference_type', 'SaleInvoice');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale invoice may belongs to payment methods entries.
|
||||||
|
*/
|
||||||
|
paymentMethods: {
|
||||||
|
relation: Model.HasManyRelation,
|
||||||
|
modelClass: TransactionPaymentServiceEntry,
|
||||||
|
join: {
|
||||||
|
from: 'sales_invoices.id',
|
||||||
|
to: 'transactions_payment_methods.referenceId',
|
||||||
|
},
|
||||||
|
beforeInsert: (model) => {
|
||||||
|
model.referenceType = 'SaleInvoice';
|
||||||
|
},
|
||||||
|
filter: (query) => {
|
||||||
|
query.where('reference_type', 'SaleInvoice');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale invoice may belongs to pdf branding template.
|
||||||
|
*/
|
||||||
|
pdfTemplate: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: PdfTemplate,
|
||||||
|
join: {
|
||||||
|
from: 'sales_invoices.pdfTemplateId',
|
||||||
|
to: 'pdf_templates.id',
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
packages/server/src/models/TransactionPaymentServiceEntry.ts
Normal file
46
packages/server/src/models/TransactionPaymentServiceEntry.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
|
export class TransactionPaymentServiceEntry extends TenantModel {
|
||||||
|
/**
|
||||||
|
* Table name
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'transactions_payment_methods';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json schema of the model.
|
||||||
|
*/
|
||||||
|
static get jsonSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
required: ['paymentIntegrationId'],
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
referenceId: { type: 'integer' },
|
||||||
|
referenceType: { type: 'string' },
|
||||||
|
paymentIntegrationId: { type: 'integer' },
|
||||||
|
enable: { type: 'boolean' },
|
||||||
|
options: { type: 'object' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
const { PaymentIntegration } = require('./PaymentIntegration');
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentIntegration: {
|
||||||
|
relation: TenantModel.BelongsToOneRelation,
|
||||||
|
modelClass: PaymentIntegration,
|
||||||
|
join: {
|
||||||
|
from: 'transactions_payment_methods.paymentIntegrationId',
|
||||||
|
to: 'payment_integrations.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { IAccount } from '@/interfaces';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
PrepardExpenses,
|
PrepardExpenses,
|
||||||
|
StripeClearingAccount,
|
||||||
TaxPayableAccount,
|
TaxPayableAccount,
|
||||||
UnearnedRevenueAccount,
|
UnearnedRevenueAccount,
|
||||||
} from '@/database/seeds/data/accounts';
|
} from '@/database/seeds/data/accounts';
|
||||||
@@ -247,4 +248,37 @@ export default class AccountRepository extends TenantRepository {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds or creates the stripe clearing account.
|
||||||
|
* @param {Record<string, string>} extraAttrs
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async findOrCreateStripeClearing(
|
||||||
|
extraAttrs: Record<string, string> = {},
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
// Retrieves the given tenant metadata.
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({
|
||||||
|
tenantId: this.tenantId,
|
||||||
|
});
|
||||||
|
const _extraAttrs = {
|
||||||
|
currencyCode: tenantMeta.baseCurrency,
|
||||||
|
...extraAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await this.model
|
||||||
|
.query(trx)
|
||||||
|
.findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await this.model.query(trx).insertAndFetch({
|
||||||
|
...StripeClearingAccount,
|
||||||
|
..._extraAttrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +44,13 @@ export class GetAccount {
|
|||||||
new AccountTransformer(),
|
new AccountTransformer(),
|
||||||
{ accountsGraph }
|
{ accountsGraph }
|
||||||
);
|
);
|
||||||
|
const eventPayload = {
|
||||||
|
tenantId,
|
||||||
|
accountId,
|
||||||
|
};
|
||||||
|
// Triggers `onAccountViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.accounts.onViewed, eventPayload);
|
||||||
|
|
||||||
return this.i18nService.i18nApply(
|
return this.i18nService.i18nApply(
|
||||||
[['accountTypeLabel'], ['accountNormalFormatted']],
|
[['accountTypeLabel'], ['accountNormalFormatted']],
|
||||||
transformed,
|
transformed,
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import type { Multer } from 'multer';
|
import type { Multer } from 'multer';
|
||||||
import multerS3 from 'multer-s3';
|
import multerS3 from 'multer-s3';
|
||||||
import { s3 } from '@/lib/S3/S3';
|
import { s3 } from '@/lib/S3/S3';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AttachmentUploadPipeline {
|
export class AttachmentUploadPipeline {
|
||||||
/**
|
/**
|
||||||
* Middleware to ensure that S3 configuration is properly set before proceeding.
|
* Middleware to ensure that S3 configuration is properly set before proceeding.
|
||||||
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
|
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
|
||||||
*
|
|
||||||
* @param req The HTTP request object.
|
* @param req The HTTP request object.
|
||||||
* @param res The HTTP response object.
|
* @param res The HTTP response object.
|
||||||
* @param next The callback to pass control to the next middleware function.
|
* @param next The callback to pass control to the next middleware function.
|
||||||
@@ -49,6 +48,11 @@ export class AttachmentUploadPipeline {
|
|||||||
key: function (req, file, cb) {
|
key: function (req, file, cb) {
|
||||||
cb(null, Date.now().toString());
|
cb(null, Date.now().toString());
|
||||||
},
|
},
|
||||||
|
acl: function(req, file, cb) {
|
||||||
|
// Conditionally set file to public or private based on isPublic flag
|
||||||
|
const aclValue = true ? 'public-read' : 'private';
|
||||||
|
cb(null, aclValue); // Set ACL based on the isPublic flag
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { isEmpty } from 'lodash';
|
|||||||
import {
|
import {
|
||||||
ISaleInvoiceCreatedPayload,
|
ISaleInvoiceCreatedPayload,
|
||||||
ISaleInvoiceCreatingPaylaod,
|
ISaleInvoiceCreatingPaylaod,
|
||||||
ISaleInvoiceDeletePayload,
|
ISaleInvoiceDeletingPayload,
|
||||||
ISaleInvoiceEditedPayload,
|
ISaleInvoiceEditedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
@@ -146,13 +146,13 @@ export class AttachmentsOnSaleInvoiceCreated {
|
|||||||
*/
|
*/
|
||||||
private async handleUnlinkAttachmentsOnInvoiceDeleted({
|
private async handleUnlinkAttachmentsOnInvoiceDeleted({
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice,
|
oldSaleInvoice,
|
||||||
trx,
|
trx,
|
||||||
}: ISaleInvoiceDeletePayload) {
|
}: ISaleInvoiceDeletingPayload) {
|
||||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||||
tenantId,
|
tenantId,
|
||||||
'SaleInvoice',
|
'SaleInvoice',
|
||||||
saleInvoice.id,
|
oldSaleInvoice.id,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
9
packages/server/src/services/Attachments/utils.ts
Normal file
9
packages/server/src/services/Attachments/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
export const getUploadedObjectUri = (objectKey: string) => {
|
||||||
|
return new URL(
|
||||||
|
path.join(config.s3.bucket, objectKey),
|
||||||
|
config.s3.endpoint
|
||||||
|
).toString();
|
||||||
|
};
|
||||||
@@ -1,26 +1,44 @@
|
|||||||
import { Inject } from "typedi";
|
import { Inject } from 'typedi';
|
||||||
import { GetPdfTemplate } from "../PdfTemplate/GetPdfTemplate";
|
import { GetPdfTemplate } from '../PdfTemplate/GetPdfTemplate';
|
||||||
import { defaultCreditNoteBrandingAttributes } from "./constants";
|
import { defaultCreditNoteBrandingAttributes } from './constants';
|
||||||
import { mergePdfTemplateWithDefaultAttributes } from "../Sales/Invoices/utils";
|
import { mergePdfTemplateWithDefaultAttributes } from '../Sales/Invoices/utils';
|
||||||
|
import { GetOrganizationBrandingAttributes } from '../PdfTemplate/GetOrganizationBrandingAttributes';
|
||||||
|
|
||||||
export class CreditNoteBrandingTemplate {
|
export class CreditNoteBrandingTemplate {
|
||||||
@Inject()
|
@Inject()
|
||||||
private getPdfTemplateService: GetPdfTemplate;
|
private getPdfTemplateService: GetPdfTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getOrgBrandingAttributes: GetOrganizationBrandingAttributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the credit note branding template.
|
* Retrieves the credit note branding template.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} templateId
|
* @param {number} templateId
|
||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
public async getCreditNoteBrandingTemplate(tenantId: number, templateId: number) {
|
public async getCreditNoteBrandingTemplate(
|
||||||
|
tenantId: number,
|
||||||
|
templateId: number
|
||||||
|
) {
|
||||||
const template = await this.getPdfTemplateService.getPdfTemplate(
|
const template = await this.getPdfTemplateService.getPdfTemplate(
|
||||||
tenantId,
|
tenantId,
|
||||||
templateId
|
templateId
|
||||||
);
|
);
|
||||||
|
// Retrieves the organization branding attributes.
|
||||||
|
const commonOrgBrandingAttrs =
|
||||||
|
await this.getOrgBrandingAttributes.getOrganizationBrandingAttributes(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
// Merges the default branding attributes with common organization branding attrs.
|
||||||
|
const organizationBrandingAttrs = {
|
||||||
|
...defaultCreditNoteBrandingAttributes,
|
||||||
|
...commonOrgBrandingAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||||
template.attributes,
|
template.attributes,
|
||||||
defaultCreditNoteBrandingAttributes
|
organizationBrandingAttrs
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...template,
|
...template,
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ export default class GetCreditNotePdf {
|
|||||||
tenantId,
|
tenantId,
|
||||||
creditNoteId
|
creditNoteId
|
||||||
);
|
);
|
||||||
console.log(brandingAttributes, 'brandingAttributes');
|
|
||||||
|
|
||||||
const htmlContent = await this.templateInjectable.render(
|
const htmlContent = await this.templateInjectable.render(
|
||||||
tenantId,
|
tenantId,
|
||||||
'modules/credit-note-standard',
|
'modules/credit-note-standard',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,30 +68,25 @@ export const DEFAULT_VIEWS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const defaultCreditNoteBrandingAttributes = {
|
export const defaultCreditNoteBrandingAttributes = {
|
||||||
|
// # Colors
|
||||||
primaryColor: '',
|
primaryColor: '',
|
||||||
secondaryColor: '',
|
secondaryColor: '',
|
||||||
|
|
||||||
|
// # Company logo
|
||||||
showCompanyLogo: true,
|
showCompanyLogo: true,
|
||||||
companyLogo: '',
|
companyLogoKey: '',
|
||||||
|
companyLogoUri: '',
|
||||||
|
|
||||||
|
// # Company name
|
||||||
companyName: 'Bigcapital Technology, Inc.',
|
companyName: 'Bigcapital Technology, Inc.',
|
||||||
|
|
||||||
// Address
|
// # Customer address
|
||||||
billedToAddress: [
|
showCustomerAddress: true,
|
||||||
'Bigcapital Technology, Inc.',
|
customerAddress: '',
|
||||||
'131 Continental Dr Suite 305 Newark,',
|
|
||||||
'Delaware 19713',
|
// # Company address
|
||||||
'United States',
|
showCompanyAddress: true,
|
||||||
'+1 762-339-5634',
|
companyAddress: '',
|
||||||
'ahmed@bigcapital.app',
|
|
||||||
],
|
|
||||||
billedFromAddress: [
|
|
||||||
'131 Continental Dr Suite 305 Newark,',
|
|
||||||
'Delaware 19713',
|
|
||||||
'United States',
|
|
||||||
'+1 762-339-5634',
|
|
||||||
'ahmed@bigcapital.app',
|
|
||||||
],
|
|
||||||
showBilledToAddress: true,
|
|
||||||
showBilledFromAddress: true,
|
|
||||||
billedToLabel: 'Billed To',
|
billedToLabel: 'Billed To',
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CreditNotePdfTemplateAttributes, ICreditNote } from '@/interfaces';
|
import { CreditNotePdfTemplateAttributes, ICreditNote } from '@/interfaces';
|
||||||
|
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||||
|
|
||||||
export const transformCreditNoteToPdfTemplate = (
|
export const transformCreditNoteToPdfTemplate = (
|
||||||
creditNote: ICreditNote
|
creditNote: ICreditNote
|
||||||
@@ -19,5 +20,6 @@ export const transformCreditNoteToPdfTemplate = (
|
|||||||
})),
|
})),
|
||||||
customerNote: creditNote.note,
|
customerNote: creditNote.note,
|
||||||
termsConditions: creditNote.termsConditions,
|
termsConditions: creditNote.termsConditions,
|
||||||
|
customerAddress: contactAddressTextFormat(creditNote.customer),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { PosthogService } from '../PostHog';
|
||||||
|
import { INVOICE_PAYMENT_LINK_GENERATED } from '@/constants/event-tracker';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentLinkEventsTracker extends EventSubscriber {
|
||||||
|
@Inject()
|
||||||
|
private posthog: PosthogService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onPublicLinkGenerated,
|
||||||
|
this.handleTrackInvoicePublicLinkGeneratedEvent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleTrackInvoicePublicLinkGeneratedEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: INVOICE_PAYMENT_LINK_GENERATED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { PosthogService } from '../PostHog';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_EDITED,
|
||||||
|
PAYMENT_METHOD_DELETED,
|
||||||
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentMethodEventsTracker extends EventSubscriber {
|
||||||
|
@Inject()
|
||||||
|
private posthog: PosthogService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.paymentMethod.onEdited,
|
||||||
|
this.handleTrackPaymentMethodEditedEvent
|
||||||
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.paymentMethod.onDeleted,
|
||||||
|
this.handleTrackPaymentMethodDeletedEvent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleTrackPaymentMethodEditedEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PAYMENT_METHOD_EDITED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleTrackPaymentMethodDeletedEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PAYMENT_METHOD_DELETED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import {
|
||||||
|
PDF_TEMPLATE_CREATED,
|
||||||
|
PDF_TEMPLATE_EDITED,
|
||||||
|
PDF_TEMPLATE_DELETED,
|
||||||
|
PDF_TEMPLATE_ASSIGNED_DEFAULT,
|
||||||
|
} from '@/constants/event-tracker';
|
||||||
|
import { PosthogService } from '../PostHog';
|
||||||
|
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PdfTemplateEventsTracker extends EventSubscriber {
|
||||||
|
@Inject()
|
||||||
|
private posthog: PosthogService;
|
||||||
|
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.pdfTemplate.onCreated,
|
||||||
|
this.handleTrackPdfTemplateCreatedEvent
|
||||||
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.pdfTemplate.onEdited,
|
||||||
|
this.handleTrackEditedPdfTemplateEvent
|
||||||
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.pdfTemplate.onDeleted,
|
||||||
|
this.handleTrackDeletedPdfTemplateEvent
|
||||||
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.pdfTemplate.onAssignedDefault,
|
||||||
|
this.handleTrackAssignedAsDefaultPdfTemplateEvent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleTrackPdfTemplateCreatedEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PDF_TEMPLATE_CREATED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleTrackEditedPdfTemplateEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PDF_TEMPLATE_EDITED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleTrackDeletedPdfTemplateEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PDF_TEMPLATE_DELETED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleTrackAssignedAsDefaultPdfTemplateEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: PDF_TEMPLATE_ASSIGNED_DEFAULT,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SALE_INVOICE_CREATED,
|
SALE_INVOICE_CREATED,
|
||||||
SALE_INVOICE_DELETED,
|
SALE_INVOICE_DELETED,
|
||||||
SALE_INVOICE_EDITED,
|
SALE_INVOICE_EDITED,
|
||||||
|
SALE_INVOICE_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -33,6 +34,10 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
events.saleInvoice.onDeleted,
|
events.saleInvoice.onDeleted,
|
||||||
this.handleTrackDeletedInvoiceEvent
|
this.handleTrackDeletedInvoiceEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onViewed,
|
||||||
|
this.handleTrackViewedInvoiceEvent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackInvoiceCreatedEvent = ({
|
private handleTrackInvoiceCreatedEvent = ({
|
||||||
@@ -64,4 +69,12 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackViewedInvoiceEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: SALE_INVOICE_VIEWED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { ISaleInvoiceCreatedPayload } from '@/interfaces';
|
||||||
|
import { PosthogService } from '../PostHog';
|
||||||
|
import { STRIPE_INTEGRAION_CONNECTED } from '@/constants/event-tracker';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeIntegrationEventsTracker extends EventSubscriber {
|
||||||
|
@Inject()
|
||||||
|
private posthog: PosthogService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.stripeIntegration.onOAuthCodeGranted,
|
||||||
|
this.handleTrackOAuthCodeGrantedTrackEvent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleTrackOAuthCodeGrantedTrackEvent = ({
|
||||||
|
tenantId,
|
||||||
|
}: ISaleInvoiceCreatedPayload) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: STRIPE_INTEGRAION_CONNECTED,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,6 +12,10 @@ import { CustomerEventsTracker } from './CustomerEventsTracker';
|
|||||||
import { VendorEventsTracker } from './VendorEventsTracker';
|
import { VendorEventsTracker } from './VendorEventsTracker';
|
||||||
import { ManualJournalEventsTracker } from './ManualJournalEventsTracker';
|
import { ManualJournalEventsTracker } from './ManualJournalEventsTracker';
|
||||||
import { BankRuleEventsTracker } from './BankRuleEventsTracker';
|
import { BankRuleEventsTracker } from './BankRuleEventsTracker';
|
||||||
|
import { PdfTemplateEventsTracker } from './PdfTemplateEventsTracker';
|
||||||
|
import { PaymentMethodEventsTracker } from './PaymentMethodEventsTracker';
|
||||||
|
import { PaymentLinkEventsTracker } from './PaymentLinkEventsTracker';
|
||||||
|
import { StripeIntegrationEventsTracker } from './StripeIntegrationEventsTracker';
|
||||||
|
|
||||||
export const EventsTrackerListeners = [
|
export const EventsTrackerListeners = [
|
||||||
SaleInvoiceEventsTracker,
|
SaleInvoiceEventsTracker,
|
||||||
@@ -28,4 +32,8 @@ export const EventsTrackerListeners = [
|
|||||||
VendorEventsTracker,
|
VendorEventsTracker,
|
||||||
ManualJournalEventsTracker,
|
ManualJournalEventsTracker,
|
||||||
BankRuleEventsTracker,
|
BankRuleEventsTracker,
|
||||||
|
PdfTemplateEventsTracker,
|
||||||
|
PaymentMethodEventsTracker,
|
||||||
|
PaymentLinkEventsTracker,
|
||||||
|
StripeIntegrationEventsTracker,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default class OrganizationService {
|
|||||||
// Triggers the organization built event.
|
// Triggers the organization built event.
|
||||||
await this.eventPublisher.emitAsync(events.organization.built, {
|
await this.eventPublisher.emitAsync(events.organization.built, {
|
||||||
tenantId: tenant.id,
|
tenantId: tenant.id,
|
||||||
} as IOrganizationBuiltEventPayload)
|
} as IOrganizationBuiltEventPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,11 +190,13 @@ export default class OrganizationService {
|
|||||||
this.throwIfTenantNotExists(tenant);
|
this.throwIfTenantNotExists(tenant);
|
||||||
|
|
||||||
// Validate organization transactions before mutate base currency.
|
// Validate organization transactions before mutate base currency.
|
||||||
|
if (organizationDTO.baseCurrency) {
|
||||||
await this.validateMutateBaseCurrency(
|
await this.validateMutateBaseCurrency(
|
||||||
tenant,
|
tenant,
|
||||||
organizationDTO.baseCurrency,
|
organizationDTO.baseCurrency,
|
||||||
tenant.metadata?.baseCurrency
|
tenant.metadata?.baseCurrency
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await tenant.saveMetadata(organizationDTO);
|
await tenant.saveMetadata(organizationDTO);
|
||||||
|
|
||||||
if (organizationDTO.baseCurrency !== tenant.metadata?.baseCurrency) {
|
if (organizationDTO.baseCurrency !== tenant.metadata?.baseCurrency) {
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class OrganizationUpgrade {
|
export default class OrganizationUpgrade {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private organizationService: OrganizationService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
organizationService: OrganizationService;
|
private tenantsManager: TenantsManagerService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
tenantsManager: TenantsManagerService;
|
|
||||||
|
|
||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
agenda: any;
|
private agenda: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upgrades the given organization database.
|
* Upgrades the given organization database.
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { StripePaymentService } from '../StripePayment/StripePaymentService';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { ISaleInvoice } from '@/interfaces';
|
||||||
|
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||||
|
import { PaymentLink } from '@/system/models';
|
||||||
|
import { initializeTenantSettings } from '@/api/middleware/SettingsMiddleware';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
const origin = 'http://localhost';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreateInvoiceCheckoutSession {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentService: StripePaymentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe checkout session from the given sale invoice.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} saleInvoiceId - Sale invoice id.
|
||||||
|
* @returns {Promise<StripeInvoiceCheckoutSessionPOJO>}
|
||||||
|
*/
|
||||||
|
async createInvoiceCheckoutSession(
|
||||||
|
publicPaymentLinkId: string
|
||||||
|
): Promise<StripeInvoiceCheckoutSessionPOJO> {
|
||||||
|
// Retrieves the payment link from the given id.
|
||||||
|
const paymentLink = await PaymentLink.query()
|
||||||
|
.findOne('linkId', publicPaymentLinkId)
|
||||||
|
.where('resourceType', 'SaleInvoice')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const tenantId = paymentLink.tenantId;
|
||||||
|
await initializeTenantSettings(tenantId);
|
||||||
|
|
||||||
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
// Retrieves the invoice from associated payment link.
|
||||||
|
const invoice = await SaleInvoice.query()
|
||||||
|
.findById(paymentLink.resourceId)
|
||||||
|
.withGraphFetched('paymentMethods')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// It will be only one Stripe payment method associated to the invoice.
|
||||||
|
const stripePaymentMethod = invoice.paymentMethods?.find(
|
||||||
|
(method) => method.paymentIntegration?.service === 'Stripe'
|
||||||
|
);
|
||||||
|
const stripeAccountId = stripePaymentMethod?.paymentIntegration?.accountId;
|
||||||
|
const paymentIntegrationId = stripePaymentMethod?.paymentIntegration?.id;
|
||||||
|
|
||||||
|
// Creates checkout session for the given invoice.
|
||||||
|
const session = await this.createCheckoutSession(invoice, stripeAccountId, {
|
||||||
|
tenantId,
|
||||||
|
paymentLinkId: paymentLink.id,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
publishableKey: config.stripePayment.publishableKey,
|
||||||
|
redirectTo: session.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Stripe checkout session for the given sale invoice.
|
||||||
|
* @param {ISaleInvoice} invoice - The sale invoice for which the checkout session is created.
|
||||||
|
* @param {string} stripeAccountId - The Stripe account ID associated with the payment method.
|
||||||
|
* @returns {Promise<any>} - The created Stripe checkout session.
|
||||||
|
*/
|
||||||
|
private createCheckoutSession(
|
||||||
|
invoice: ISaleInvoice,
|
||||||
|
stripeAccountId: string,
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
) {
|
||||||
|
return this.stripePaymentService.stripe.checkout.sessions.create(
|
||||||
|
{
|
||||||
|
payment_method_types: ['card'],
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price_data: {
|
||||||
|
currency: invoice.currencyCode,
|
||||||
|
product_data: {
|
||||||
|
name: invoice.invoiceNo,
|
||||||
|
},
|
||||||
|
unit_amount: invoice.total * 100, // Amount in cents
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mode: 'payment',
|
||||||
|
success_url: `${origin}/success`,
|
||||||
|
cancel_url: `${origin}/cancel`,
|
||||||
|
metadata: {
|
||||||
|
saleInvoiceId: invoice.id,
|
||||||
|
resource: 'SaleInvoice',
|
||||||
|
...metadata,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ stripeAccount: stripeAccountId }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { PaymentLink } from '@/system/models';
|
||||||
|
import { GetInvoicePaymentLinkMetaTransformer } from '../Sales/Invoices/GetInvoicePaymentLinkTransformer';
|
||||||
|
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetInvoicePaymentLinkMetadata {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the invoice sharable link meta of the link id.
|
||||||
|
* @param {number}
|
||||||
|
* @param {string} linkId
|
||||||
|
*/
|
||||||
|
async getInvoicePaymentLinkMeta(linkId: string) {
|
||||||
|
const paymentLink = await PaymentLink.query()
|
||||||
|
.findOne('linkId', linkId)
|
||||||
|
.where('resourceType', 'SaleInvoice')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Validate the expiry at date.
|
||||||
|
if (paymentLink.expiryAt) {
|
||||||
|
const currentDate = moment();
|
||||||
|
const expiryDate = moment(paymentLink.expiryAt);
|
||||||
|
|
||||||
|
if (expiryDate.isBefore(currentDate)) {
|
||||||
|
throw new ServiceError('PAYMENT_LINK_EXPIRED');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tenantId = paymentLink.tenantId;
|
||||||
|
await initalizeTenantServices(tenantId);
|
||||||
|
|
||||||
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const invoice = await SaleInvoice.query()
|
||||||
|
.findById(paymentLink.resourceId)
|
||||||
|
.withGraphFetched('entries.item')
|
||||||
|
.withGraphFetched('customer')
|
||||||
|
.withGraphFetched('taxes.taxRate')
|
||||||
|
.withGraphFetched('paymentMethods.paymentIntegration')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
invoice,
|
||||||
|
new GetInvoicePaymentLinkMetaTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
||||||
|
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||||
|
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||||
|
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentLinksApplication {
|
||||||
|
@Inject()
|
||||||
|
private getInvoicePaymentLinkMetadataService: GetInvoicePaymentLinkMetadata;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the invoice payment link.
|
||||||
|
* @param {string} paymentLinkId
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
public getInvoicePaymentLink(paymentLinkId: string) {
|
||||||
|
return this.getInvoicePaymentLinkMetadataService.getInvoicePaymentLinkMeta(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the invoice payment checkout session from the given payment link id.
|
||||||
|
* @param {string} paymentLinkId - Payment link id.
|
||||||
|
* @returns {Promise<StripeInvoiceCheckoutSessionPOJO>}
|
||||||
|
*/
|
||||||
|
public createInvoicePaymentCheckoutSession(
|
||||||
|
paymentLinkId: string
|
||||||
|
): Promise<StripeInvoiceCheckoutSessionPOJO> {
|
||||||
|
return this.createInvoiceCheckoutSessionService.createInvoiceCheckoutSession(
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '../UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeletePaymentMethodService {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given payment integration.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentIntegrationId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async deletePaymentMethod(
|
||||||
|
tenantId: number,
|
||||||
|
paymentIntegrationId: number
|
||||||
|
): Promise<void> {
|
||||||
|
const { PaymentIntegration, TransactionPaymentServiceEntry } =
|
||||||
|
this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const paymentIntegration = await PaymentIntegration.query()
|
||||||
|
.findById(paymentIntegrationId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Delete payment methods links.
|
||||||
|
await TransactionPaymentServiceEntry.query(trx)
|
||||||
|
.where('paymentIntegrationId', paymentIntegrationId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete the payment integration.
|
||||||
|
await PaymentIntegration.query(trx)
|
||||||
|
.findById(paymentIntegrationId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Triggers `onPaymentMethodDeleted` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.paymentMethod.onDeleted, {
|
||||||
|
tenantId,
|
||||||
|
paymentIntegrationId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '../UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { EditPaymentMethodDTO } from './types';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class EditPaymentMethodService {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given payment method.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentIntegrationId
|
||||||
|
* @param {EditPaymentMethodDTO} editPaymentMethodDTO
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async editPaymentMethod(
|
||||||
|
tenantId: number,
|
||||||
|
paymentIntegrationId: number,
|
||||||
|
editPaymentMethodDTO: EditPaymentMethodDTO
|
||||||
|
): Promise<void> {
|
||||||
|
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const paymentMethod = await PaymentIntegration.query()
|
||||||
|
.findById(paymentIntegrationId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onPaymentMethodEditing` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.paymentMethod.onEditing, {
|
||||||
|
tenantId,
|
||||||
|
paymentIntegrationId,
|
||||||
|
editPaymentMethodDTO,
|
||||||
|
trx,
|
||||||
|
});
|
||||||
|
await PaymentIntegration.query(trx)
|
||||||
|
.findById(paymentIntegrationId)
|
||||||
|
.patch({
|
||||||
|
...editPaymentMethodDTO,
|
||||||
|
});
|
||||||
|
// Triggers `onPaymentMethodEdited` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.paymentMethod.onEdited, {
|
||||||
|
tenantId,
|
||||||
|
paymentIntegrationId,
|
||||||
|
editPaymentMethodDTO,
|
||||||
|
trx,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { GetPaymentMethodsPOJO } from './types';
|
||||||
|
import config from '@/config';
|
||||||
|
import { isStripePaymentConfigured } from './utils';
|
||||||
|
import { GetStripeAuthorizationLinkService } from '../StripePayment/GetStripeAuthorizationLink';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentMethodsStateService {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getStripeAuthorizationLinkService: GetStripeAuthorizationLinkService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment state provising state.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<GetPaymentMethodsPOJO>}
|
||||||
|
*/
|
||||||
|
public async getPaymentMethodsState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<GetPaymentMethodsPOJO> {
|
||||||
|
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const stripePayment = await PaymentIntegration.query()
|
||||||
|
.orderBy('createdAt', 'ASC')
|
||||||
|
.findOne({
|
||||||
|
service: 'Stripe',
|
||||||
|
});
|
||||||
|
const isStripeAccountCreated = !!stripePayment;
|
||||||
|
const isStripePaymentEnabled = stripePayment?.paymentEnabled;
|
||||||
|
const isStripePayoutEnabled = stripePayment?.payoutEnabled;
|
||||||
|
const isStripeEnabled = stripePayment?.fullEnabled;
|
||||||
|
|
||||||
|
const stripePaymentMethodId = stripePayment?.id || null;
|
||||||
|
const stripeAccountId = stripePayment?.accountId || null;
|
||||||
|
const stripePublishableKey = config.stripePayment.publishableKey;
|
||||||
|
const stripeCurrencies = ['USD', 'EUR'];
|
||||||
|
const stripeRedirectUrl = 'https://your-stripe-redirect-url.com';
|
||||||
|
const isStripeServerConfigured = isStripePaymentConfigured();
|
||||||
|
const stripeAuthLink =
|
||||||
|
this.getStripeAuthorizationLinkService.getStripeAuthLink();
|
||||||
|
|
||||||
|
const paymentMethodPOJO: GetPaymentMethodsPOJO = {
|
||||||
|
stripe: {
|
||||||
|
isStripeAccountCreated,
|
||||||
|
isStripePaymentEnabled,
|
||||||
|
isStripePayoutEnabled,
|
||||||
|
isStripeEnabled,
|
||||||
|
isStripeServerConfigured,
|
||||||
|
stripeAccountId,
|
||||||
|
stripePaymentMethodId,
|
||||||
|
stripePublishableKey,
|
||||||
|
stripeCurrencies,
|
||||||
|
stripeAuthLink,
|
||||||
|
stripeRedirectUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return paymentMethodPOJO;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { GetPaymentMethodsPOJO } from './types';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentMethodService {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment state provising state.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<GetPaymentMethodsPOJO>}
|
||||||
|
*/
|
||||||
|
public async getPaymentMethod(
|
||||||
|
tenantId: number,
|
||||||
|
paymentServiceId: number
|
||||||
|
): Promise<GetPaymentMethodsPOJO> {
|
||||||
|
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const stripePayment = await PaymentIntegration.query()
|
||||||
|
.findById(paymentServiceId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return stripePayment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { GetPaymentServicesSpecificInvoiceTransformer } from './GetPaymentServicesSpecificInvoiceTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentServicesSpecificInvoice {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transform: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment services of the given invoice.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} invoiceId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getPaymentServicesInvoice(tenantId: number) {
|
||||||
|
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const paymentGateways = await PaymentIntegration.query()
|
||||||
|
.modify('fullEnabled')
|
||||||
|
.orderBy('name', 'ASC');
|
||||||
|
|
||||||
|
return this.transform.transform(
|
||||||
|
tenantId,
|
||||||
|
paymentGateways,
|
||||||
|
new GetPaymentServicesSpecificInvoiceTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
|
|
||||||
|
export class GetPaymentServicesSpecificInvoiceTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Exclude attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['accountId'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return ['serviceFormatted'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public serviceFormatted(method) {
|
||||||
|
return 'Stripe';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { GetPaymentServicesSpecificInvoice } from './GetPaymentServicesSpecificInvoice';
|
||||||
|
import { DeletePaymentMethodService } from './DeletePaymentMethodService';
|
||||||
|
import { EditPaymentMethodService } from './EditPaymentMethodService';
|
||||||
|
import { EditPaymentMethodDTO, GetPaymentMethodsPOJO } from './types';
|
||||||
|
import { GetPaymentMethodsStateService } from './GetPaymentMethodsState';
|
||||||
|
import { GetPaymentMethodService } from './GetPaymentService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentServicesApplication {
|
||||||
|
@Inject()
|
||||||
|
private getPaymentServicesSpecificInvoice: GetPaymentServicesSpecificInvoice;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private deletePaymentMethodService: DeletePaymentMethodService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private editPaymentMethodService: EditPaymentMethodService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentMethodsStateService: GetPaymentMethodsStateService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentMethodService: GetPaymentMethodService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment services for a specific invoice.
|
||||||
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
|
* @param {number} invoiceId - The ID of the invoice.
|
||||||
|
* @returns {Promise<any>} The payment services for the specified invoice.
|
||||||
|
*/
|
||||||
|
public async getPaymentServicesForInvoice(tenantId: number): Promise<any> {
|
||||||
|
return this.getPaymentServicesSpecificInvoice.getPaymentServicesInvoice(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves specific payment service details.
|
||||||
|
* @param {number} tenantId - Tennat id.
|
||||||
|
* @param {number} paymentServiceId - Payment service id.
|
||||||
|
*/
|
||||||
|
public async getPaymentService(tenantId: number, paymentServiceId: number) {
|
||||||
|
return this.getPaymentMethodService.getPaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentServiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given payment method.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentIntegrationId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async deletePaymentMethod(
|
||||||
|
tenantId: number,
|
||||||
|
paymentIntegrationId: number
|
||||||
|
): Promise<void> {
|
||||||
|
return this.deletePaymentMethodService.deletePaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentIntegrationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given payment method.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentIntegrationId
|
||||||
|
* @param {EditPaymentMethodDTO} editPaymentMethodDTO
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async editPaymentMethod(
|
||||||
|
tenantId: number,
|
||||||
|
paymentIntegrationId: number,
|
||||||
|
editPaymentMethodDTO: EditPaymentMethodDTO
|
||||||
|
): Promise<void> {
|
||||||
|
return this.editPaymentMethodService.editPaymentMethod(
|
||||||
|
tenantId,
|
||||||
|
paymentIntegrationId,
|
||||||
|
editPaymentMethodDTO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment state providing state.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<GetPaymentMethodsPOJO>}
|
||||||
|
*/
|
||||||
|
public async getPaymentMethodsState(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<GetPaymentMethodsPOJO> {
|
||||||
|
return this.getPaymentMethodsStateService.getPaymentMethodsState(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/server/src/services/PaymentServices/types.ts
Normal file
33
packages/server/src/services/PaymentServices/types.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export interface EditPaymentMethodDTO {
|
||||||
|
name?: string;
|
||||||
|
options?: {
|
||||||
|
bankAccountId?: number; // bank account.
|
||||||
|
clearningAccountId?: number; // current liability.
|
||||||
|
|
||||||
|
showVisa?: boolean;
|
||||||
|
showMasterCard?: boolean;
|
||||||
|
showDiscover?: boolean;
|
||||||
|
showAmer?: boolean;
|
||||||
|
showJcb?: boolean;
|
||||||
|
showDiners?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetPaymentMethodsPOJO {
|
||||||
|
stripe: {
|
||||||
|
isStripeAccountCreated: boolean;
|
||||||
|
|
||||||
|
isStripePaymentEnabled: boolean;
|
||||||
|
isStripePayoutEnabled: boolean;
|
||||||
|
isStripeEnabled: boolean;
|
||||||
|
|
||||||
|
isStripeServerConfigured: boolean;
|
||||||
|
|
||||||
|
stripeAccountId: string | null;
|
||||||
|
stripePaymentMethodId: number | null;
|
||||||
|
stripePublishableKey: string | null;
|
||||||
|
stripeAuthLink: string;
|
||||||
|
stripeCurrencies: Array<string>;
|
||||||
|
stripeRedirectUrl: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
9
packages/server/src/services/PaymentServices/utils.ts
Normal file
9
packages/server/src/services/PaymentServices/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
export const isStripePaymentConfigured = () => {
|
||||||
|
return (
|
||||||
|
config.stripePayment.secretKey &&
|
||||||
|
config.stripePayment.publishableKey &&
|
||||||
|
config.stripePayment.webhooksSecret
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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,31 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { CommonOrganizationBrandingAttributes } from './types';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetOrganizationBrandingAttributes {
|
||||||
|
/**
|
||||||
|
* Retrieves the given organization branding attributes initial state.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<CommonOrganizationBrandingAttributes>}
|
||||||
|
*/
|
||||||
|
async getOrganizationBrandingAttributes(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<CommonOrganizationBrandingAttributes> {
|
||||||
|
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
|
const companyName = tenantMetadata?.name;
|
||||||
|
const primaryColor = tenantMetadata?.primaryColor;
|
||||||
|
const companyLogoKey = tenantMetadata?.logoKey;
|
||||||
|
const companyLogoUri = tenantMetadata?.logoUri;
|
||||||
|
const companyAddress = tenantMetadata?.addressTextFormatted;
|
||||||
|
|
||||||
|
return {
|
||||||
|
companyName,
|
||||||
|
companyAddress,
|
||||||
|
companyLogoUri,
|
||||||
|
companyLogoKey,
|
||||||
|
primaryColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { GetOrganizationBrandingAttributes } from './GetOrganizationBrandingAttributes';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPdfTemplateBrandingState {
|
||||||
|
@Inject()
|
||||||
|
private getOrgBrandingAttributes: GetOrganizationBrandingAttributes;
|
||||||
|
|
||||||
|
getBrandingState(tenantId: number) {
|
||||||
|
const brandingAttributes =
|
||||||
|
this.getOrgBrandingAttributes.getOrganizationBrandingAttributes(tenantId);
|
||||||
|
|
||||||
|
return brandingAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||||
|
import { getUploadedObjectUri } from '../Attachments/utils';
|
||||||
|
|
||||||
export class GetPdfTemplateTransformer extends Transformer {
|
export class GetPdfTemplateTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +57,7 @@ class GetPdfTemplateAttributesTransformer extends Transformer {
|
|||||||
*/
|
*/
|
||||||
protected companyLogoUri(template) {
|
protected companyLogoUri(template) {
|
||||||
return template.companyLogoKey
|
return template.companyLogoKey
|
||||||
? `https://bigcapital.sfo3.digitaloceanspaces.com/${template.companyLogoKey}`
|
? getUploadedObjectUri(template.companyLogoKey)
|
||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { GetPdfTemplate } from './GetPdfTemplate';
|
|||||||
import { GetPdfTemplates } from './GetPdfTemplates';
|
import { GetPdfTemplates } from './GetPdfTemplates';
|
||||||
import { EditPdfTemplate } from './EditPdfTemplate';
|
import { EditPdfTemplate } from './EditPdfTemplate';
|
||||||
import { AssignPdfTemplateDefault } from './AssignPdfTemplateDefault';
|
import { AssignPdfTemplateDefault } from './AssignPdfTemplateDefault';
|
||||||
|
import { GetPdfTemplateBrandingState } from './GetPdfTemplateBrandingState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PdfTemplateApplication {
|
export class PdfTemplateApplication {
|
||||||
@@ -27,6 +28,9 @@ export class PdfTemplateApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private assignPdfTemplateDefaultService: AssignPdfTemplateDefault;
|
private assignPdfTemplateDefaultService: AssignPdfTemplateDefault;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPdfTemplateBrandingStateService: GetPdfTemplateBrandingState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new PDF template.
|
* Creates a new PDF template.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -120,4 +124,12 @@ export class PdfTemplateApplication {
|
|||||||
templateId
|
templateId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} tenantId
|
||||||
|
*/
|
||||||
|
public async getPdfTemplateBrandingState(tenantId: number) {
|
||||||
|
return this.getPdfTemplateBrandingStateService.getBrandingState(tenantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,3 +65,12 @@ export interface ICreateInvoicePdfTemplateDTO {
|
|||||||
statementLabel?: string;
|
statementLabel?: string;
|
||||||
showStatement?: boolean;
|
showStatement?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface CommonOrganizationBrandingAttributes {
|
||||||
|
companyName?: string;
|
||||||
|
primaryColor?: string;
|
||||||
|
companyLogoKey?: string;
|
||||||
|
companyLogoUri?: string;
|
||||||
|
companyAddress?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,27 +177,18 @@ export const SaleEstimatesSampleData = [
|
|||||||
export const defaultEstimatePdfBrandingAttributes = {
|
export const defaultEstimatePdfBrandingAttributes = {
|
||||||
primaryColor: '#000',
|
primaryColor: '#000',
|
||||||
secondaryColor: '#000',
|
secondaryColor: '#000',
|
||||||
|
|
||||||
|
// # Company logo
|
||||||
showCompanyLogo: true,
|
showCompanyLogo: true,
|
||||||
companyLogo: '',
|
companyLogoUri: '',
|
||||||
|
companyLogoKey: '',
|
||||||
|
|
||||||
companyName: '',
|
companyName: '',
|
||||||
|
|
||||||
billedToAddress: [
|
customerAddress: '',
|
||||||
'Bigcapital Technology, Inc.',
|
companyAddress: '',
|
||||||
'131 Continental Dr Suite 305 Newark,',
|
showCustomerAddress: true,
|
||||||
'Delaware 19713',
|
showCompanyAddress: true,
|
||||||
'United States',
|
|
||||||
'+1 762-339-5634',
|
|
||||||
'ahmed@bigcapital.app',
|
|
||||||
],
|
|
||||||
billedFromAddress: [
|
|
||||||
'131 Continental Dr Suite 305 Newark,',
|
|
||||||
'Delaware 19713',
|
|
||||||
'United States',
|
|
||||||
'+1 762-339-5634',
|
|
||||||
'ahmed@bigcapital.app',
|
|
||||||
],
|
|
||||||
showBilledFromAddress: true,
|
|
||||||
showBilledToAddress: true,
|
|
||||||
billedToLabel: 'Billed To',
|
billedToLabel: 'Billed To',
|
||||||
|
|
||||||
total: '$1000.00',
|
total: '$1000.00',
|
||||||
@@ -240,7 +231,6 @@ export const defaultEstimatePdfBrandingAttributes = {
|
|||||||
expirationDate: 'September 3, 2024',
|
expirationDate: 'September 3, 2024',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
interface EstimatePdfBrandingLineItem {
|
interface EstimatePdfBrandingLineItem {
|
||||||
item: string;
|
item: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -256,10 +246,13 @@ export interface EstimatePdfBrandingAttributes {
|
|||||||
companyLogo: string;
|
companyLogo: string;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
|
|
||||||
billedToAddress: string[];
|
// Customer Address
|
||||||
billedFromAddress: string[];
|
showCustomerAddress: boolean;
|
||||||
showBilledFromAddress: boolean;
|
customerAddress: string;
|
||||||
showBilledToAddress: boolean;
|
|
||||||
|
// Company Address
|
||||||
|
showCompanyAddress: boolean;
|
||||||
|
companyAddress: string;
|
||||||
billedToLabel: string;
|
billedToLabel: string;
|
||||||
|
|
||||||
total: string;
|
total: string;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||||
import { EstimatePdfBrandingAttributes } from './constants';
|
import { EstimatePdfBrandingAttributes } from './constants';
|
||||||
|
|
||||||
export const transformEstimateToPdfTemplate = (
|
export const transformEstimateToPdfTemplate = (
|
||||||
@@ -18,5 +19,6 @@ export const transformEstimateToPdfTemplate = (
|
|||||||
subtotal: estimate.formattedSubtotal,
|
subtotal: estimate.formattedSubtotal,
|
||||||
customerNote: estimate.customerNote,
|
customerNote: estimate.customerNote,
|
||||||
termsConditions: estimate.termsConditions,
|
termsConditions: estimate.termsConditions,
|
||||||
|
customerAddress: contactAddressTextFormat(estimate.customer),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ISystemUser,
|
ISystemUser,
|
||||||
ISaleInvoiceDeletePayload,
|
ISaleInvoiceDeletePayload,
|
||||||
ISaleInvoiceDeletedPayload,
|
ISaleInvoiceDeletedPayload,
|
||||||
|
ISaleInvoiceDeletingPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
@@ -82,10 +83,10 @@ export class DeleteSaleInvoice {
|
|||||||
) {
|
) {
|
||||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
const saleInvoice = await saleInvoiceRepository.findOneById(
|
const saleInvoice = await saleInvoiceRepository.findOneById(saleInvoiceId, [
|
||||||
saleInvoiceId,
|
'entries',
|
||||||
'entries'
|
'paymentMethods',
|
||||||
);
|
]);
|
||||||
if (!saleInvoice) {
|
if (!saleInvoice) {
|
||||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
@@ -118,15 +119,22 @@ export class DeleteSaleInvoice {
|
|||||||
// Validate the sale invoice has applied to credit note transaction.
|
// Validate the sale invoice has applied to credit note transaction.
|
||||||
await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId);
|
await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId);
|
||||||
|
|
||||||
|
// Triggers `onSaleInvoiceDelete` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleInvoice.onDelete, {
|
||||||
|
tenantId,
|
||||||
|
oldSaleInvoice,
|
||||||
|
saleInvoiceId,
|
||||||
|
} as ISaleInvoiceDeletePayload);
|
||||||
|
|
||||||
// Deletes sale invoice transaction and associate transactions with UOW env.
|
// Deletes sale invoice transaction and associate transactions with UOW env.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
// Triggers `onSaleInvoiceDelete` event.
|
// Triggers `onSaleInvoiceDeleting` event.
|
||||||
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, {
|
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice: oldSaleInvoice,
|
oldSaleInvoice,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
trx,
|
trx,
|
||||||
} as ISaleInvoiceDeletePayload);
|
} as ISaleInvoiceDeletingPayload);
|
||||||
|
|
||||||
// Unlink the converted sale estimates from the given sale invoice.
|
// Unlink the converted sale estimates from the given sale invoice.
|
||||||
await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice(
|
await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice(
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
|
import { PUBLIC_PAYMENT_LINK } from './constants';
|
||||||
|
|
||||||
|
export class GeneratePaymentLinkTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Exclude these attributes from payment link object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['linkId'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Included attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return ['link'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the public/private payment linl
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public link(link) {
|
||||||
|
return PUBLIC_PAYMENT_LINK?.replace('{PAYMENT_LINK_ID}', link.linkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { PaymentLink } from '@/system/models';
|
||||||
|
import { GeneratePaymentLinkTransformer } from './GeneratePaymentLinkTransformer';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GenerateShareLink {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates private or public payment link for the given sale invoice.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} invoiceId - Sale invoice id.
|
||||||
|
* @param {string} publicOrPrivate - Public or private.
|
||||||
|
* @param {string} expiryTime - Expiry time.
|
||||||
|
*/
|
||||||
|
async generatePaymentLink(
|
||||||
|
tenantId: number,
|
||||||
|
transactionId: number,
|
||||||
|
transactionType: string,
|
||||||
|
publicity: string = 'private',
|
||||||
|
expiryTime: string = ''
|
||||||
|
) {
|
||||||
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const foundInvoice = await SaleInvoice.query()
|
||||||
|
.findById(transactionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Generate unique uuid for sharable link.
|
||||||
|
const linkId = uuidv4() as string;
|
||||||
|
|
||||||
|
const commonEventPayload = {
|
||||||
|
tenantId,
|
||||||
|
transactionId,
|
||||||
|
transactionType,
|
||||||
|
publicity,
|
||||||
|
expiryTime,
|
||||||
|
};
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onPublicSharableLinkGenerating` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleInvoice.onPublicLinkGenerating,
|
||||||
|
{ ...commonEventPayload, trx }
|
||||||
|
);
|
||||||
|
const paymentLink = await PaymentLink.query().insert({
|
||||||
|
linkId,
|
||||||
|
tenantId,
|
||||||
|
publicity,
|
||||||
|
resourceId: foundInvoice.id,
|
||||||
|
resourceType: 'SaleInvoice',
|
||||||
|
});
|
||||||
|
// Triggers `onPublicSharableLinkGenerated` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleInvoice.onPublicLinkGenerated,
|
||||||
|
{
|
||||||
|
...commonEventPayload,
|
||||||
|
paymentLink,
|
||||||
|
trx,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
paymentLink,
|
||||||
|
new GeneratePaymentLinkTransformer()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
import { Transform } from 'form-data';
|
||||||
|
import { ItemEntryTransformer } from './ItemEntryTransformer';
|
||||||
|
import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer';
|
||||||
|
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||||
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
|
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||||
|
|
||||||
|
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
|
||||||
|
/**
|
||||||
|
* Exclude these attributes from payment link object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Included attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'customerName',
|
||||||
|
'dueAmount',
|
||||||
|
'dueDateFormatted',
|
||||||
|
'invoiceDateFormatted',
|
||||||
|
'total',
|
||||||
|
'totalFormatted',
|
||||||
|
'totalLocalFormatted',
|
||||||
|
'subtotal',
|
||||||
|
'subtotalFormatted',
|
||||||
|
'subtotalLocalFormatted',
|
||||||
|
'dueAmount',
|
||||||
|
'dueAmountFormatted',
|
||||||
|
'paymentAmount',
|
||||||
|
'paymentAmountFormatted',
|
||||||
|
'dueDate',
|
||||||
|
'dueDateFormatted',
|
||||||
|
'invoiceNo',
|
||||||
|
'invoiceMessage',
|
||||||
|
'termsConditions',
|
||||||
|
'entries',
|
||||||
|
'taxes',
|
||||||
|
'organization',
|
||||||
|
'isReceivable',
|
||||||
|
'hasStripePaymentMethod',
|
||||||
|
'formattedCustomerAddress',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
public customerName(invoice) {
|
||||||
|
return invoice.customer.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the organization metadata for the payment link.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public organization(invoice) {
|
||||||
|
return this.item(
|
||||||
|
this.context.organization,
|
||||||
|
new GetPaymentLinkOrganizationMetaTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the entries of the sale invoice.
|
||||||
|
* @param {ISaleInvoice} invoice
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected entries = (invoice) => {
|
||||||
|
return this.item(
|
||||||
|
invoice.entries,
|
||||||
|
new GetInvoicePaymentLinkEntryMetaTransformer(),
|
||||||
|
{
|
||||||
|
currencyCode: invoice.currencyCode,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice entries.
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected taxes = (invoice) => {
|
||||||
|
return this.item(
|
||||||
|
invoice.taxes,
|
||||||
|
new GetInvoicePaymentLinkTaxEntryTransformer(),
|
||||||
|
{
|
||||||
|
subtotal: invoice.subtotal,
|
||||||
|
isInclusiveTax: invoice.isInclusiveTax,
|
||||||
|
currencyCode: invoice.currencyCode,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
protected isReceivable(invoice) {
|
||||||
|
return invoice.dueAmount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hasStripePaymentMethod(invoice) {
|
||||||
|
return invoice.paymentMethods.some(
|
||||||
|
(paymentMethod) => paymentMethod.paymentIntegration.service === 'Stripe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get customerAddressFormat() {
|
||||||
|
return `{ADDRESS_1}
|
||||||
|
{ADDRESS_2}
|
||||||
|
{CITY} {STATE} {POSTAL_CODE}
|
||||||
|
{COUNTRY}
|
||||||
|
{PHONE}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted customer address.
|
||||||
|
* @param invoice
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedCustomerAddress(invoice) {
|
||||||
|
return contactAddressTextFormat(
|
||||||
|
invoice.customer,
|
||||||
|
this.customerAddressFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetPaymentLinkOrganizationMetaTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to item entry object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'primaryColor',
|
||||||
|
'name',
|
||||||
|
'address',
|
||||||
|
'logoUri',
|
||||||
|
'addressTextFormatted',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted text of organization address.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public addressTextFormatted() {
|
||||||
|
return this.context.organization.addressTextFormatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetInvoicePaymentLinkEntryMetaTransformer extends ItemEntryTransformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to item entry object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'quantity',
|
||||||
|
'quantityFormatted',
|
||||||
|
'rate',
|
||||||
|
'rateFormatted',
|
||||||
|
'total',
|
||||||
|
'totalFormatted',
|
||||||
|
'itemName',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
public itemName(entry) {
|
||||||
|
return entry.item.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude these attributes from payment link object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetInvoicePaymentLinkTaxEntryTransformer extends SaleInvoiceTaxEntryTransformer {
|
||||||
|
/**
|
||||||
|
* Included attributes.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return ['name', 'taxRateCode', 'taxRateAmount', 'taxRateAmountFormatted'];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 -
|
||||||
@@ -35,15 +40,26 @@ export class GetSaleInvoice {
|
|||||||
.withGraphFetched('customer')
|
.withGraphFetched('customer')
|
||||||
.withGraphFetched('branch')
|
.withGraphFetched('branch')
|
||||||
.withGraphFetched('taxes.taxRate')
|
.withGraphFetched('taxes.taxRate')
|
||||||
.withGraphFetched('attachments');
|
.withGraphFetched('attachments')
|
||||||
|
.withGraphFetched('paymentMethods');
|
||||||
|
|
||||||
// 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,16 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { mergePdfTemplateWithDefaultAttributes } from './utils';
|
import { mergePdfTemplateWithDefaultAttributes } from './utils';
|
||||||
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
|
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
|
||||||
import { defaultEstimatePdfBrandingAttributes } from '../Estimates/constants';
|
import { defaultEstimatePdfBrandingAttributes } from '../Estimates/constants';
|
||||||
|
import { GetOrganizationBrandingAttributes } from '@/services/PdfTemplate/GetOrganizationBrandingAttributes';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleEstimatePdfTemplate {
|
export class SaleEstimatePdfTemplate {
|
||||||
@Inject()
|
@Inject()
|
||||||
private getPdfTemplateService: GetPdfTemplate;
|
private getPdfTemplateService: GetPdfTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getOrgBrandingAttrs: GetOrganizationBrandingAttributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the estimate pdf template.
|
* Retrieves the estimate pdf template.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -19,9 +23,19 @@ export class SaleEstimatePdfTemplate {
|
|||||||
tenantId,
|
tenantId,
|
||||||
estimateTemplateId
|
estimateTemplateId
|
||||||
);
|
);
|
||||||
|
// Retreives the organization branding attributes.
|
||||||
|
const commonOrgBrandingAttrs =
|
||||||
|
await this.getOrgBrandingAttrs.getOrganizationBrandingAttributes(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
// Merge the default branding attributes with organization attrs.
|
||||||
|
const orgainizationBrandingAttrs = {
|
||||||
|
...defaultEstimatePdfBrandingAttributes,
|
||||||
|
...commonOrgBrandingAttrs,
|
||||||
|
};
|
||||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||||
template.attributes,
|
template.attributes,
|
||||||
defaultEstimatePdfBrandingAttributes
|
orgainizationBrandingAttrs
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...template,
|
...template,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user