Compare commits

...

782 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
0517b3e430 Merge branch 'develop' into add-mail-invoice-receipt-schema 2024-11-10 14:37:11 +02:00
Ahmed Bouhuolia
d668d60ed5 fix: remove the vite types from pdf-templates package 2024-11-10 11:49:32 +02:00
Ahmed Bouhuolia
a34b7a2106 fix: remove vite from pdf-templates packakge 2024-11-10 11:47:09 +02:00
Ahmed Bouhuolia
6f12127095 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-11-10 11:39:38 +02:00
Ahmed Bouhuolia
b4e5bbf376 fix: invoice payment email storybook 2024-11-10 11:39:03 +02:00
Ahmed Bouhuolia
a11530d190 Merge pull request #748 from bigcapitalhq/fix-monorepo-dependecies
fix: monorepo dependencies scope
2024-11-10 11:35:56 +02:00
Ahmed Bouhuolia
43d4425da5 fix: monorepo dependecoes scope 2024-11-10 11:35:16 +02:00
Ahmed Bouhuolia
4c1909cb73 Merge pull request #747 from bigcapitalhq/fix-template-company-logo
fix: company logo of the template
2024-11-09 23:45:49 +02:00
Ahmed Bouhuolia
a7b6b7a03e fix: company logo of the template 2024-11-09 23:44:40 +02:00
Ahmed Bouhuolia
1f46275bde Merge pull request #746 from bigcapitalhq/fix-mail-services
fix: mail services
2024-11-09 22:25:09 +02:00
Ahmed Bouhuolia
aa7e5d4ae9 fix: mail services 2024-11-09 22:23:52 +02:00
Ahmed Bouhuolia
bb482df3ce Merge pull request #530 from angelosorno/develop 2024-11-08 19:03:10 +02:00
Ahmed Bouhuolia
f878786646 Merge pull request #671 from Crims-on/Crims-on-sv-translation 2024-11-08 19:02:22 +02:00
Ahmed Bouhuolia
652851a1a9 Merge pull request #745 from ibutiti/big-265-fix-forgot-password-text
big-265: Fix forgot password text
2024-11-07 12:35:28 +02:00
Allan Ibutiti
850f4956cb big-265: fixed forgot password text 2024-11-07 12:13:59 +03:00
Ahmed Bouhuolia
94223b6ebf Merge pull request #744 from bigcapitalhq/fix-due-invoice-server-invoice
fix: due invoice server invoice
2024-11-06 17:25:24 +02:00
Ahmed Bouhuolia
e9d34e19ad fix: due invoice server invoice 2024-11-06 17:24:42 +02:00
Ahmed Bouhuolia
4c67d2e321 feat: add invoice mail receipt schema 2024-11-06 14:17:28 +02:00
Ahmed Bouhuolia
107532fe26 Merge pull request #742 from bigcapitalhq/fix-send-invoice-addresses
fix: send invoice receipt addresses
2024-11-06 14:05:22 +02:00
Ahmed Bouhuolia
c32aff82ee fix: send invoice receipt addresses 2024-11-06 14:04:29 +02:00
Ahmed Bouhuolia
de8a867d33 Merge pull request #741 from bigcapitalhq/fix-ssr-invoice-template
fix: style SSR invoice paper template
2024-11-06 11:51:48 +02:00
Ahmed Bouhuolia
17a8aba23f fix: style ssr invoice paper template 2024-11-06 11:51:04 +02:00
Ahmed Bouhuolia
04b601626b Merge pull request #740 from bigcapitalhq/fix-invoice-preview
feat: getting invoice preview on send mail view
2024-11-05 22:31:40 +02:00
Ahmed Bouhuolia
802775c118 feat: getting invoice preview on send mail view 2024-11-05 22:30:54 +02:00
Ahmed Bouhuolia
b6baa80134 Merge pull request #735 from bigcapitalhq/add-pdf-templates-package
feat: add shared package to pdf templates to render in the server and…
2024-11-05 17:20:12 +02:00
Ahmed Bouhuolia
b2d0f2ed3c Merge branch 'develop' into add-pdf-templates-package 2024-11-05 17:19:50 +02:00
Ahmed Bouhuolia
d23f33bae4 feat: add style to SSR invoice paper template 2024-11-05 17:09:47 +02:00
Ahmed Bouhuolia
22ea557337 feat: wip invoice paper template server-side 2024-11-05 13:33:22 +02:00
Ahmed Bouhuolia
b3ebbb429c Merge pull request #739 from bigcapitalhq/clean-up-invoice-mail-receipt-preview-component
fix: clean up ivnoice mail receipt preview component
2024-11-04 16:22:01 +02:00
Ahmed Bouhuolia
51218797af fix: clean up ivnoice mail receipt preview component 2024-11-04 16:21:13 +02:00
Ahmed Bouhuolia
2d18a6573e Merge pull request #738 from bigcapitalhq/fix-ts-typing-invoice-send-mail-fields
fix: typing invoice send mail fields
2024-11-04 14:19:16 +02:00
Ahmed Bouhuolia
2646ad5bc4 fix: typing invoice send mail fields 2024-11-04 14:18:47 +02:00
Ahmed Bouhuolia
51aec8d8b3 feat: render server-side invoice pdf template using React server 2024-11-04 12:55:12 +02:00
Ahmed Bouhuolia
638bd95d6f Merge pull request #737 from bigcapitalhq/change-default-invoice-mail-message
fix: change default invoice mail message
2024-11-03 20:58:53 +02:00
Ahmed Bouhuolia
f2fcc3a649 fix: change default invoice mail message 2024-11-03 20:56:22 +02:00
Ahmed Bouhuolia
48795748d8 Merge pull request #736 from bigcapitalhq/fix-company-logo-mail-receipt
fix: company logo does not show up in mail receipt preview
2024-11-03 20:23:56 +02:00
Ahmed Bouhuolia
6ba54a994a fix: company logo does not show up in mail receipt preview 2024-11-03 20:22:59 +02:00
Ahmed Bouhuolia
6687db4085 feat: add shared package to pdf templates to render in the server and client side 2024-11-03 17:31:17 +02:00
Ahmed Bouhuolia
ba1d9b3f28 Merge pull request #734 from bigcapitalhq/hook-up-cc-bcc-to-mail-sender
fix: hook up cc and bcc fields to mail sender
2024-11-02 19:33:58 +02:00
Ahmed Bouhuolia
51905825fd fix: hook up cc and bcc fields to mail sender 2024-11-02 19:33:29 +02:00
Ahmed Bouhuolia
bd5e33855a Merge pull request #733 from bigcapitalhq/fix-send-invoice-drawer-layout
fix: send invoice drawer layout
2024-11-02 17:11:06 +02:00
Ahmed Bouhuolia
f7fbc0e31c fix: send invoice drawer layout 2024-11-02 17:10:32 +02:00
Ahmed Bouhuolia
cb06fa342c Merge pull request #732 from bigcapitalhq/attach-payment-link-invoice
fix: attach payment link in sending invoice mail receipt
2024-11-02 16:02:53 +02:00
Ahmed Bouhuolia
581229053a fix: attach payment link in sending invoice mail receipt 2024-11-02 16:02:17 +02:00
Ahmed Bouhuolia
209da69b8f Merge pull request #731 from bigcapitalhq/refactor-mail-services
refactor: notification mail services
2024-11-02 15:00:54 +02:00
Ahmed Bouhuolia
d09aebcebb refactor: notification mail services 2024-11-02 14:59:57 +02:00
Ahmed Bouhuolia
0cc80bc179 Merge pull request #730 from bigcapitalhq/change-send-mail-button-invoice
fix: change the send mail button on invoice drawer
2024-11-02 11:51:34 +02:00
Ahmed Bouhuolia
79dcc592bc fix: change the send mail button on invoice drawer 2024-11-02 11:51:10 +02:00
Ahmed Bouhuolia
687111851a Merge pull request #723 from bigcapitalhq/invoice-mail-receipt
feat: wip Invoice mail receipt preview
2024-10-31 12:43:44 +02:00
Ahmed Bouhuolia
dbbaa387bd feat: send invoice receipt preview 2024-10-31 12:40:48 +02:00
Ahmed Bouhuolia
470bfd32f7 feat: wip send invoice receipt preview 2024-10-30 14:22:54 +02:00
Ahmed Bouhuolia
5fddd080fd feat: wip send invoice mail receipt 2024-10-30 13:10:56 +02:00
Ahmed Bouhuolia
e10c530b4b feat: wip preview invoice payment mail 2024-10-29 21:14:46 +02:00
Ahmed Bouhuolia
12189f018d feat: wip send invoice mail payment template 2024-10-28 18:33:16 +02:00
Ahmed Bouhuolia
0111b0e6ff feat: hook up the request to the send mail form 2024-10-28 12:00:17 +02:00
Ahmed Bouhuolia
0930b0428d fix: tsconfig email-components package 2024-10-28 00:28:52 +02:00
Ahmed Bouhuolia
289f40014e feat: invoice payment email template 2024-10-27 17:20:48 +02:00
Ahmed Bouhuolia
01cc0568f9 feat: wip invoice payment email template 2024-10-27 15:09:08 +02:00
Ahmed Bouhuolia
42ee8ed9fa feat: initialize email-components vite package 2024-10-27 10:16:04 +02:00
Ahmed Bouhuolia
1dae65cb74 feat: Style the send email right buttons 2024-10-26 19:01:37 +02:00
Ahmed Bouhuolia
ce40d67ea2 feat: wip send invoice preview 2024-10-26 18:39:36 +02:00
Ahmed Bouhuolia
26088a71ee Merge pull request #721 from bigcapitalhq/track-move-events
feat: track more services events
2024-10-26 12:40:30 +02:00
Ahmed Bouhuolia
cadf6b81a0 feat: Track reports view events 2024-10-26 12:39:48 +02:00
Ahmed Bouhuolia
728b4cacd9 feat: wip send invoice mail preview 2024-10-24 20:48:16 +02:00
allcontributors[bot]
b4d3ac2f96 docs: add nklmantey as a contributor for bug (#725)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-10-24 16:51:21 +02:00
Ahmed Bouhuolia
bc8e440814 Merge pull request #722 from nklmantey/BIG-257-not-balanced-if-decimal
Fix: Credit and debit totals not balancing when decimal values are used
2024-10-24 16:49:52 +02:00
Ahmed Bouhuolia
4c0f9a0aef fix: using lodash utils for decimal rounding 2024-10-24 16:47:29 +02:00
Ahmed Bouhuolia
c321d90575 feat: send invoice mail receipt drawer 2024-10-23 16:30:39 +02:00
Ahmed Bouhuolia
03e6372f14 feat: style the invoice payment preview 2024-10-22 14:36:53 +02:00
Ahmed Bouhuolia
c0481f67ad feat: wip invoice mail receipt preview 2024-10-22 14:00:36 +02:00
Ahmed Bouhuolia
b7f316d25a feat: wip invoice mail receipt preview 2024-10-22 11:59:15 +02:00
Ahmed Bouhuolia
dffd818396 feat: Invoice mail receipt preview 2024-10-21 15:42:12 +02:00
Nana Kofi Larbi Mantey
bbc19df6b4 adds CREDIT_DEBIT_NOT_EQUAL error 2024-10-20 19:04:10 +00:00
Nana Kofi Larbi Mantey
c8c2786893 refactors getTotal function loginc 2024-10-20 17:45:44 +00:00
Nana Kofi Larbi Mantey
d79f26f1b5 refactors backend validator for credit-debit equals 2024-10-20 17:44:39 +00:00
Ahmed Bouhuolia
32ba6f9a6c feat: track more services events 2024-10-19 23:47:14 +02:00
Ahmed Bouhuolia
ccbb399685 Merge pull request #720 from bigcapitalhq/add-estimate-customer-note
fix Customer note does not appear in pdf document
2024-10-19 16:28:33 +02:00
Ahmed Bouhuolia
f381d433ec fix Customer note does not appear in pdf document 2024-10-19 16:28:01 +02:00
Ahmed Bouhuolia
2ee49f7496 Merge pull request #719 from bigcapitalhq/track-pdf-documents-views-events
feat: Track events of pdf documents views
2024-10-19 13:40:18 +02:00
Ahmed Bouhuolia
bb299aa595 feat: Track events of pdf documents views 2024-10-19 13:38:28 +02:00
Ahmed Bouhuolia
a66867463e Merge pull request #718 from bigcapitalhq/invoice-number-filename-document
feat: Invoice number in downloaded pdf document
2024-10-19 13:16:48 +02:00
Ahmed Bouhuolia
de50b89e5c feat: Invoice number in downloaded pdf document 2024-10-19 13:16:06 +02:00
Ahmed Bouhuolia
c4ee143354 Merge pull request #717 from bigcapitalhq/preline-statements
ffeat: Pre-line invoice statements
2024-10-19 11:00:31 +02:00
Ahmed Bouhuolia
a2227016e5 feat: Pre-line invoice statements 2024-10-19 10:59:46 +02:00
Ahmed Bouhuolia
4d6f65b179 Merge pull request #716 from bigcapitalhq/add-quantity-column-pdf-templates
feat: Add quantity column to pdf templates
2024-10-17 16:01:02 +02:00
Ahmed Bouhuolia
758ebbe261 feat: Add qty column to server-side pdf templates 2024-10-17 16:00:19 +02:00
Ahmed Bouhuolia
279890e922 feat: Add qty column to preview pdf templates: 2024-10-17 15:58:19 +02:00
Ahmed Bouhuolia
44fae36b82 Merge pull request #715 from bigcapitalhq/sync-account-norma-cashflow
fix: Sync account normal of cashflow GL entries
2024-10-16 20:12:51 +02:00
Ahmed Bouhuolia
fc2fac80af fix: Sync account normal of cashflow GL entries 2024-10-16 20:12:25 +02:00
Ahmed Bouhuolia
5ad9a9654b Merge pull request #714 from bigcapitalhq/sync-plaid-credit-card-account-type
fix: Sync Plaid credit card account type
2024-10-16 19:47:13 +02:00
Ahmed Bouhuolia
8a4034cc5d fix: Sync Plaid credit card account type 2024-10-16 19:46:39 +02:00
Ahmed Bouhuolia
5649657bf0 Merge pull request #713 from bigcapitalhq/i18napply
chore: Move i18nApply localization to the account transformer
2024-10-15 19:27:43 +02:00
Ahmed Bouhuolia
c929a7cb27 chore: Move i18nApply localization to the account transformer 2024-10-15 19:27:15 +02:00
Ahmed Bouhuolia
eeedb789a9 Merge pull request #711 from bigcapitalhq/fix-parse-non-lowercase-import
fix: Parse the uppercase values in importing
2024-10-14 20:02:31 +02:00
Ahmed Bouhuolia
321af8c271 fix: Parse the uppercase values in importing 2024-10-14 20:01:52 +02:00
Ahmed Bouhuolia
fd4d86e797 Merge pull request #710 from bigcapitalhq/fix-import-category-on-items
fix: Import category column of item resource
2024-10-14 19:49:54 +02:00
Ahmed Bouhuolia
49988e27a2 fix: Import category column of item resource 2024-10-14 19:49:32 +02:00
Ahmed Bouhuolia
8c94ee5982 Dump CHANGELOG 2024-10-14 13:58:57 +02:00
Ahmed Bouhuolia
2e73a34fef Merge pull request #709 from bigcapitalhq/track-viewed-events
feat: Track account, invoice and item viewed events
2024-10-14 12:16:40 +02:00
Ahmed Bouhuolia
ea7f987fe3 feat: Track account, invoice and item viewed events 2024-10-14 12:15:21 +02:00
Ahmed Bouhuolia
d55503f0c7 Update README.md 2024-10-13 23:10:43 +02:00
Ahmed Bouhuolia
f59b8166b6 Merge pull request #708 from bigcapitalhq/add-customize-templates-btn-to-edit-forms
feat: Add customize templates button to edit forms
2024-10-13 21:15:08 +02:00
Ahmed Bouhuolia
2c735d7edf feat: Add customize templates button to edit forms 2024-10-13 21:14:18 +02:00
Ahmed Bouhuolia
5b5ab9fe1e Merge pull request #707 from bigcapitalhq/refactor-date-field
refactor: invoice, estimate, receipt, credit note and payment received date input fields
2024-10-13 18:03:36 +02:00
Ahmed Bouhuolia
28ac9b2d90 refactor: invoice, estimate, receipt, credit note and payment received date input fields 2024-10-13 18:01:43 +02:00
Ahmed Bouhuolia
3300a6a499 Merge pull request #705 from bigcapitalhq/fix-invoice-form-layout
fix: Invoice form layout
2024-10-13 17:23:54 +02:00
Ahmed Bouhuolia
152a22baa0 fix: Remove unused scss files 2024-10-13 17:22:14 +02:00
Ahmed Bouhuolia
e873198238 feat: typeing AppIntlProvider 2024-10-13 16:51:04 +02:00
Ahmed Bouhuolia
68a0db91ee feat: form header fields 2024-10-13 13:56:13 +02:00
Ahmed Bouhuolia
ddea7be24a feat: Add css utilities to Box, Stack and Group components 2024-10-13 01:06:17 +02:00
Ahmed Bouhuolia
b7b86bb0c5 fix: Invoice form layout 2024-10-12 20:49:56 +02:00
Ahmed Bouhuolia
817ef906dc Merge pull request #701 from bigcapitalhq/fix-disable-tabs-customize
fix: Disable tabs of the pdf customization if the first field not filed up
2024-10-12 12:52:01 +02:00
Ahmed Bouhuolia
863c7693fa fix: Disable tabs of the pdf customization if the first field not filled up 2024-10-10 16:41:21 +02:00
Ahmed Bouhuolia
cf78255220 Merge pull request #700 from bigcapitalhq/fix-company-logo-dimenstion-pdf-template
fix: Set max width/height to company logo of pdf templates
2024-10-08 10:12:03 +02:00
Ahmed Bouhuolia
f9aa6abdd7 fix: Set max width/height to company logo of pdf templates 2024-10-08 10:11:40 +02:00
Ahmed Bouhuolia
0a5e40a0d8 Merge pull request #699 from bigcapitalhq/fix-remove-logo-pdf-template
fix: Delete company logo from the PDF template
2024-10-08 08:21:03 +02:00
Ahmed Bouhuolia
4aa445fe35 fix: Delete company logo from the pdf template 2024-10-08 08:20:35 +02:00
Ahmed Bouhuolia
1a1095c99b Merge pull request #698 from bigcapitalhq/fix-estimate-initial-value-template
fix: Estimate customize values
2024-10-07 17:21:35 +02:00
Ahmed Bouhuolia
d92b46aa7b fix: Estimate customize values 2024-10-07 17:20:45 +02:00
Ahmed Bouhuolia
682d40cbf8 Merge pull request #697 from bigcapitalhq/fix-pdf-branding-templates-request-data
fix: Pdf branding templates request data
2024-10-07 16:11:09 +02:00
Ahmed Bouhuolia
b62f3b3fa6 chore: remove commented line 2024-10-07 16:10:59 +02:00
Ahmed Bouhuolia
e76d3b15ce fix: Pdf branding template initial values 2024-10-07 16:08:25 +02:00
Ahmed Bouhuolia
9edfb83221 fix: Pdf branding templates request data 2024-10-07 16:03:56 +02:00
Ahmed Bouhuolia
bbdfe00c7a Merge pull request #696 from bigcapitalhq/fix-changing-pdf-template
fix: Changing the pdf template of the invoice
2024-10-07 09:51:17 +02:00
Ahmed Bouhuolia
e3942551cd fix: Changing the pdf template of the invoice 2024-10-07 09:50:46 +02:00
Ahmed Bouhuolia
a0c1a21983 Merge pull request #695 from bigcapitalhq/feat-change-document-title-of-payment-page
feat: Change the document title of the payment page
2024-10-06 22:21:53 +02:00
Ahmed Bouhuolia
3358ce58bc feat: Change the document title of the payment page 2024-10-06 22:21:12 +02:00
Ahmed Bouhuolia
3cd54653a8 Merge pull request #694 from bigcapitalhq/lerna-shared
feat: Add shared packages to Docker container
2024-10-06 17:26:39 +02:00
Ahmed Bouhuolia
6cad929738 feat: Lerna shared 2024-10-06 17:20:28 +02:00
Ahmed Bouhuolia
184648040c Merge pull request #693 from bigcapitalhq/fix-display-country-name
fix: Display country name
2024-10-06 13:19:17 +02:00
Ahmed Bouhuolia
df9d277e66 fix: Display country name 2024-10-06 13:18:31 +02:00
Ahmed Bouhuolia
75ec315de2 Merge pull request #689 from bigcapitalhq/download-payment-link-invoice-pdf
feat: Download invoice pdf of the payment link
2024-10-05 21:48:07 +02:00
Ahmed Bouhuolia
c89b2367e6 fix: Download invoice pdf of the payment link page 2024-10-05 21:46:48 +02:00
Ahmed Bouhuolia
bca5b3481c Merge pull request #691 from bigcapitalhq/pdf-templates-layout
fix: Pdf templates layout
2024-10-05 21:26:37 +02:00
Ahmed Bouhuolia
59996e7a40 feat: re-layout server-side pdf template 2024-10-05 21:24:07 +02:00
Ahmed Bouhuolia
af5726c48c fix: Pdf templates layout 2024-10-05 19:01:34 +02:00
Ahmed Bouhuolia
90f08c5d51 Merge pull request #690 from bigcapitalhq/fix-remove-empty-lines-from-address
fix: Remove empty lines from address formats
2024-10-05 16:09:03 +02:00
Ahmed Bouhuolia
a0a9f4a768 fix: Remove empty lines from address formats 2024-10-05 16:08:09 +02:00
Ahmed Bouhuolia
2649f1c326 feat: Download invoice pdf of the payment link 2024-10-05 13:56:25 +02:00
Ahmed Bouhuolia
c5ff1e4d4a Merge pull request #688 from bigcapitalhq/fix-pdf-template-addresses-controlling
fix: pdf template addresses controlling
2024-10-03 17:13:07 +02:00
Ahmed Bouhuolia
c74c8e896a fix: pdf template addresses controlling 2024-10-03 17:12:12 +02:00
Ahmed Bouhuolia
55fdc47ff0 Merge pull request #687 from bigcapitalhq/assign-default-pdf-template
feat: Assign default PDF template automatically
2024-10-03 17:02:17 +02:00
Ahmed Bouhuolia
126eb221d0 feat: invalidate invoice state once change default template 2024-10-03 17:01:35 +02:00
Ahmed Bouhuolia
3c7e22be43 feat: Assign default pdf template automatically 2024-10-03 16:36:44 +02:00
Ahmed Bouhuolia
b23112bc92 feat: Assign default PDF template automatically 2024-10-02 18:18:57 +02:00
Ahmed Bouhuolia
cbc60b3c73 Merge pull request #684 from bigcapitalhq/getting-uploaded-object-uri
fix: Getting uploaded object uri
2024-10-01 15:09:53 +02:00
Ahmed Bouhuolia
6caa1311fd fix: Getting uploaded object uri 2024-10-01 15:09:21 +02:00
Ahmed Bouhuolia
cd0bbd11c3 chore: change CHANGELOG.md 2024-10-01 12:56:33 +02:00
Ahmed Bouhuolia
2a944f8507 feat: Add Stripe payment env variables examples 2024-10-01 12:53:27 +02:00
Ahmed Bouhuolia
8a2754d9ce Merge pull request #683 from bigcapitalhq/feat-hook-up-customer-address
feat: Hook up customer/company address to invoice preview of payment page
2024-10-01 09:49:14 +02:00
Ahmed Bouhuolia
ace75f2dfa feat: Hook up customer/company address to invoice preview of payment page 2024-10-01 09:48:07 +02:00
Ahmed Bouhuolia
7ceb785c1b Merge pull request #682 from bigcapitalhq/listen-stripe-integration-events
feat: Listen to Stripe integration events
2024-09-30 23:13:14 +02:00
Ahmed Bouhuolia
904a52f5a1 feat: listen to Stripe integration events 2024-09-30 23:12:42 +02:00
Ahmed Bouhuolia
04fe65b176 fix: payment link events tracker 2024-09-30 18:09:57 +02:00
Ahmed Bouhuolia
7ac6e0d349 Merge pull request #681 from bigcapitalhq/fix-pdf-template-customize-content
fix: Branding customize content
2024-09-30 14:52:36 +02:00
Ahmed Bouhuolia
4ec3586173 fix: branding customize content 2024-09-30 14:51:03 +02:00
Ahmed Bouhuolia
4b6ab7035e Merge pull request #680 from bigcapitalhq/add-posthog-events-tracking-to-pdf-templates
feat: Track pdf templates Posthog events
2024-09-30 12:44:27 +02:00
Ahmed Bouhuolia
3fe7babe00 feat: Track pdf templates Posthog events 2024-09-30 12:43:51 +02:00
Ahmed Bouhuolia
f21570982e Merge pull request #679 from bigcapitalhq/fix-listen-to-stripe-session-completed
fix: Listen to Stripe session completed event
2024-09-30 11:49:48 +02:00
Ahmed Bouhuolia
ad8fe52b84 fix: Listen to Stripe session completed event 2024-09-30 11:49:19 +02:00
Ahmed Bouhuolia
15ce6ac710 Merge pull request #678 from bigcapitalhq/pdf-templates-company-customer-address
feat: Pdf templates customer/company addresses
2024-09-30 11:21:14 +02:00
Ahmed Bouhuolia
783387dce6 fix: pdf templates server-side rendered 2024-09-30 11:15:05 +02:00
Ahmed Bouhuolia
863c7ad99f feat: Hook up customer/company address to pdf templates 2024-09-29 22:59:14 +02:00
Ahmed Bouhuolia
776b69475c feat: PDF templates company/customer address 2024-09-29 19:31:00 +02:00
Ahmed Bouhuolia
6b6027a588 feat: Pdf templates customer/company addresses 2024-09-29 18:04:56 +02:00
Ahmed Bouhuolia
d465ee15bd Merge pull request #677 from bigcapitalhq/preferences-company-branding
feat: Company branding preferences
2024-09-29 13:44:29 +02:00
Ahmed Bouhuolia
be2049ca6e feat: Pdf template address 2024-09-29 13:43:09 +02:00
Ahmed Bouhuolia
9b63c176cd feat: Hook up company address to payment page 2024-09-28 20:19:05 +02:00
Ahmed Bouhuolia
e506a7ba35 feat: Hook orgnization name and logo to payment page 2024-09-28 19:20:01 +02:00
Ahmed Bouhuolia
2191ad0d40 feat: hook up preferences branding form 2024-09-28 18:44:08 +02:00
Ahmed Bouhuolia
ca162206a3 feat: Organization address and branding patch endpoint 2024-09-28 17:43:47 +02:00
Ahmed Bouhuolia
c5d7a2bfd8 feat: Company branding preferences 2024-09-28 14:47:59 +02:00
Ahmed Bouhuolia
b9506424d1 Merge pull request #675 from bigcapitalhq/hook-up-company-logo-to-pdf-templates
feat: Hook up company logo to server-side pdf templates
2024-09-26 18:33:45 +02:00
Ahmed Bouhuolia
46a145ae58 feat: Hook up company logo to server-side pdf templates 2024-09-26 18:33:21 +02:00
Ahmed Bouhuolia
e4044ef563 Merge pull request #674 from bigcapitalhq/clean-up-payment-links-endpoints
feat: Clean up payment links endpoints
2024-09-25 19:38:28 +02:00
Ahmed Bouhuolia
1cc71eb368 feat: Clean up payment links endpoints 2024-09-25 19:37:40 +02:00
Ahmed Bouhuolia
323b95de7b Merge pull request #673 from bigcapitalhq/fix-invoice-customize-bugs
fix: Invoice customize bugs
2024-09-25 15:21:12 +02:00
Ahmed Bouhuolia
b0658be041 fix: Invoice customize bugs 2024-09-25 15:20:24 +02:00
Ahmed Bouhuolia
b222d56148 Merge pull request #672 from bigcapitalhq/fix-invoice-brand-customize
fix: Invoice pdf customize
2024-09-25 12:22:07 +02:00
Ahmed Bouhuolia
946872204b fix: payment page 2024-09-25 12:21:26 +02:00
Ahmed Bouhuolia
2f9adfd908 fix: Invoice pdf customize 2024-09-25 11:04:17 +02:00
Ahmed Bouhuolia
1c8e19378f Merge pull request #670 from bigcapitalhq/upload-company-logo
feat: Upload company logo to invoice templates
2024-09-24 20:31:12 +02:00
Ahmed Bouhuolia
37fd4a1fdb feat: Uploading company logo 2024-09-24 20:28:19 +02:00
Ahmed Bouhuolia
7aed3d9c8c Merge pull request #668 from bigcapitalhq/stripe-integrate
feat: Onboard accounts to Stripe Connect
2024-09-24 14:12:39 +02:00
Ahmed Bouhuolia
b125e3e58b feat: Stripe connect using OAuth 2024-09-24 14:10:53 +02:00
Crims-on
65788e344a Create authentication.tsx 2024-09-23 18:07:47 +02:00
Crims-on
abc242d117 Create locale.tsx 2024-09-23 18:07:14 +02:00
Crims-on
6dd4968327 deepl translation 2024-09-23 17:59:28 +02:00
Ahmed Bouhuolia
70bba4a6ed fix: Stripe integration content 2024-09-23 17:34:27 +02:00
Ahmed Bouhuolia
1570995021 feat: Add Stripe pre-setup dialog 2024-09-23 14:44:07 +02:00
Ahmed Bouhuolia
8109236e72 fix: Stripe payment integration 2024-09-23 13:21:54 +02:00
Ahmed Bouhuolia
9ba651decb feat: Delete Stripe pamyent connection 2024-09-22 21:57:46 +02:00
Ahmed Bouhuolia
eb5fdbf4ee feat: Control the payment method from invoice form 2024-09-22 21:23:02 +02:00
Ahmed Bouhuolia
9827a84857 feat: Hook up edit Stripe settings form 2024-09-22 17:25:27 +02:00
Ahmed Bouhuolia
3308133736 feat: Edit Stripe payment settings 2024-09-22 14:55:48 +02:00
Ahmed Bouhuolia
3129c76c30 feat: Delete Stripe payment method 2024-09-22 14:30:47 +02:00
Ahmed Bouhuolia
c0a4c965f0 feat: Edit stripe payment integation drawer 2024-09-22 12:52:59 +02:00
Ahmed Bouhuolia
d16c57b63b feat: Upload company logo to invoice templates 2024-09-22 00:01:12 +02:00
Ahmed Bouhuolia
e04f5d26a3 feat: listen to stripe account updated webhook 2024-09-21 23:59:54 +02:00
Ahmed Bouhuolia
ad74007d58 fix: Style of paper template address 2024-09-21 20:04:23 +02:00
Ahmed Bouhuolia
6271d6c268 chore: remove newrelic logs file 2024-09-21 19:13:20 +02:00
Ahmed Bouhuolia
7756b5b304 feat: Stripe payment integration 2024-09-21 16:50:22 +02:00
Ahmed Bouhuolia
8de8695b25 feat: clean up the style of public payment page. 2024-09-21 09:53:00 +02:00
Ahmed Bouhuolia
11c56c75a4 feat: clean up the stripe payment integration 2024-09-21 09:18:39 +02:00
Ahmed Bouhuolia
f5a1d68c52 feat: Stripe payment checkout session 2024-09-19 22:24:07 +02:00
Ahmed Bouhuolia
0ae7a25c27 feat: Map the invoice preview data 2024-09-19 14:32:14 +02:00
Ahmed Bouhuolia
16eaacd4bc feat: Payment invoice preview drawer 2024-09-19 12:45:06 +02:00
Ahmed Bouhuolia
809973730f feat: style tweaks in the public payment page 2024-09-19 11:28:40 +02:00
Ahmed Bouhuolia
2ebb4595a8 feat: Emit Stripe webhooks to events in the system 2024-09-19 10:25:13 +02:00
Ahmed Bouhuolia
77f628509c fix: make the base url of payment link configurable 2024-09-18 23:53:46 +02:00
Ahmed Bouhuolia
d2cd32a735 feat: inactive associated Stripe payment link on invoice deleting 2024-09-18 23:41:59 +02:00
Ahmed Bouhuolia
4665f529e6 feat: integrate Stripe payment to invoices 2024-09-18 19:24:01 +02:00
Ahmed Bouhuolia
df706d2573 feat: payment methods preferences page 2024-09-18 11:19:59 +02:00
Ahmed Bouhuolia
5270e99de8 feat: select payment methods dialog 2024-09-18 10:43:21 +02:00
Ahmed Bouhuolia
eb48f66f6e Merge branch 'develop' into stripe-integrate 2024-09-17 19:26:13 +02:00
Ahmed Bouhuolia
5e7cff0eb7 Merge pull request #667 from bigcapitalhq/invoice-customize
feat: customize pdf templates
2024-09-17 19:21:26 +02:00
Ahmed Bouhuolia
34e781b4a2 fix: typo in invoice customize drawer 2024-09-17 19:18:22 +02:00
Ahmed Bouhuolia
5f40d50852 fix: pdf template customization 2024-09-17 18:19:28 +02:00
Ahmed Bouhuolia
bb0d91a9cb fix: pdf templates 2024-09-17 17:46:56 +02:00
Ahmed Bouhuolia
2c790427fa feat: rendering pdf templates on the server-side 2024-09-17 13:53:57 +02:00
Ahmed Bouhuolia
4f59b27d70 feat: hook up branding templates to invoices 2024-09-16 20:02:17 +02:00
Ahmed Bouhuolia
94c08f0b9e chore: clean pdf templates code 2024-09-15 22:55:39 +02:00
Ahmed Bouhuolia
ef4beaa564 feat: seed initial standard branding templates 2024-09-15 22:01:11 +02:00
Ahmed Bouhuolia
2b42215381 feat: add loading state to generate payment link dialog 2024-09-15 21:08:41 +02:00
Ahmed Bouhuolia
18d6ec7b59 feat: style the generate payment link dialog 2024-09-15 21:03:36 +02:00
Ahmed Bouhuolia
430cf19533 feat: Link transations with payment methods 2024-09-15 19:42:43 +02:00
Ahmed Bouhuolia
542e61dbfc feat: sharable payment link dialog 2024-09-15 19:28:43 +02:00
Ahmed Bouhuolia
8566422ce3 fix: Add mising address in branding templates customize 2024-09-14 22:52:37 +02:00
Ahmed Bouhuolia
9517b4e279 feat: wip public payment page 2024-09-14 22:10:27 +02:00
Ahmed Bouhuolia
70551bee30 feat: the element customize submit button 2024-09-14 20:18:03 +02:00
Ahmed Bouhuolia
d690c6a3fe feat: optimize branding templates customiing 2024-09-14 19:32:16 +02:00
Ahmed Bouhuolia
28319c2cdc feat: cannot delete a predefined branding template 2024-09-14 16:26:34 +02:00
Ahmed Bouhuolia
df0f73f338 feat: mark specific template as default 2024-09-14 16:19:06 +02:00
Ahmed Bouhuolia
411ac55986 feat: templates customize 2024-09-12 17:49:00 +02:00
Ahmed Bouhuolia
12226d469a feat: pdf template customize 2024-09-12 16:50:44 +02:00
Ahmed Bouhuolia
632c4629de feat: hook up the invice customize api 2024-09-12 14:16:07 +02:00
Ahmed Bouhuolia
a7df23cebc feat: branding templates table 2024-09-11 21:16:21 +02:00
Ahmed Bouhuolia
ef74e250f1 feat: link pdf template to sales transactions 2024-09-11 16:49:44 +02:00
Ahmed Bouhuolia
c0769662bd feat(server): add pdf template crud endpoints 2024-09-11 14:54:13 +02:00
Ahmed Bouhuolia
5b6270a184 feat: invoice pdf customize 2024-09-10 23:32:34 +02:00
Ahmed Bouhuolia
4541d28b68 chore: dump CHANGELOG 2024-09-10 22:25:16 +02:00
Ahmed Bouhuolia
716dec799a feat: paper template customize 2024-09-10 21:54:37 +02:00
Ahmed Bouhuolia
77a1e35ff4 feat: Paper template reusable 2024-09-10 18:37:38 +02:00
Ahmed Bouhuolia
317adfa0de feat: wip estimate, receipt, payment received customize 2024-09-10 17:06:17 +02:00
Ahmed Bouhuolia
f0dfc3d1b0 feat: invoice customize paper preview 2024-09-10 13:29:25 +02:00
Ahmed Bouhuolia
67904f52af feat: add more customize drawers 2024-09-09 21:31:14 +02:00
Ahmed Bouhuolia
f644ed6708 feat: element customize component 2024-09-09 21:07:22 +02:00
Ahmed Bouhuolia
dc18bde6be feat: wip invoice customizer 2024-09-09 19:40:23 +02:00
Ahmed Bouhuolia
132c1dfdbe feat: craft the paper template style 2024-09-09 16:24:09 +02:00
Ahmed Bouhuolia
162b92ce84 feat: wip Stripe connect integration 2024-09-09 14:18:04 +02:00
Ahmed Bouhuolia
9247745ab0 feat: wip invoice customize 2024-09-08 21:01:54 +02:00
Ahmed Bouhuolia
c5c0342c7b feat: wip styling invoice customize 2024-09-08 20:13:27 +02:00
Ahmed Bouhuolia
f5e9485a12 feat: wip invoice customize 2024-09-08 17:34:19 +02:00
Ahmed Bouhuolia
a183666df6 feat: Onboard accounts to Stripe Connect 2024-09-08 11:42:26 +02:00
Ahmed Bouhuolia
e6bad27771 feat: wip invoice customizer 2024-09-07 21:39:05 +02:00
Ahmed Bouhuolia
6d24474162 Merge pull request #663 from bigcapitalhq/fix-uncategorize-bank-transaction
fix: Un-categorize bank transactions
2024-09-07 13:27:11 +02:00
Ahmed Bouhuolia
5962b990c4 fix: Uncategorize bank transactions 2024-09-07 13:26:02 +02:00
Ahmed Bouhuolia
9f21f649f0 Merge pull request #662 from bigcapitalhq/refactor-expense-gl
refactor: The expense G/L writer
2024-09-04 21:10:51 +02:00
Ahmed Bouhuolia
d3d2112b8a refactor: The expense G/L writer 2024-09-04 21:10:13 +02:00
Ahmed Bouhuolia
3795322a65 Merge pull request #659 from bigcapitalhq/format-money-table-columns
feat: Tabular number of all money columns
2024-09-04 18:48:38 +02:00
Ahmed Bouhuolia
fe5cd5a8ea feat: Mark more columns as money columns 2024-09-04 18:46:42 +02:00
Ahmed Bouhuolia
c032a5db16 Merge pull request #661 from bigcapitalhq/fix-payment-made-full-amount
fix: Payment made filling the form full amount field
2024-09-04 17:35:54 +02:00
Ahmed Bouhuolia
3fcb6fefde fix: Payment made filling the form full amount field 2024-09-04 17:34:19 +02:00
Ahmed Bouhuolia
16f5cb713d Merge pull request #660 from bigcapitalhq/fix-cast-array-rule-ids
fix: Array cast of recognize function rule ids
2024-09-04 16:49:32 +02:00
Ahmed Bouhuolia
f7a7925028 fix: Array cast of recognize function rule ids 2024-09-04 16:48:34 +02:00
Ahmed Bouhuolia
66fb0c9fa3 feat: Tabular number of all money columns 2024-09-04 14:58:31 +02:00
Ahmed Bouhuolia
85acc85f17 Merge pull request #655 from bigcapitalhq/ui-tweaks
feat: Datatable UI improvements
2024-09-04 14:02:43 +02:00
Ahmed Bouhuolia
c76ce09191 feat: optimize the style of bank account transactions tables 2024-09-04 13:59:13 +02:00
Ahmed Bouhuolia
e3532098b2 Merge branch 'develop' into ui-tweaks 2024-09-04 09:53:55 +02:00
Ahmed Bouhuolia
b6783eb22e Merge pull request #658 from bigcapitalhq/banking-layout-breaking
feat: Bank pages layout breaking
2024-09-04 09:52:35 +02:00
Ahmed Bouhuolia
4ef00ab122 feat: Bank pages layout breaking 2024-09-04 09:51:35 +02:00
Ahmed Bouhuolia
3dd827417a fix: remove duplicated event types 2024-09-03 17:31:00 +02:00
Ahmed Bouhuolia
cbacd02aa2 Merge pull request #656 from bigcapitalhq/add-help-dropdown-menu
feat: Add help dropdown menu
2024-09-03 17:28:12 +02:00
Ahmed Bouhuolia
3b7e0fb78a Merge pull request #657 from bigcapitalhq/suspense-lazy-banking-pages
fix: Suspense the lazy loaded components in banking pages
2024-09-03 17:26:02 +02:00
Ahmed Bouhuolia
9add716395 fix: Suspense the lazy loaded components in banking pages 2024-09-03 17:24:11 +02:00
Ahmed Bouhuolia
083ea28a1f feat: Add help dropdown menu 2024-09-03 16:41:20 +02:00
Ahmed Bouhuolia
1b51742c36 feat: Datatable UI improvements 2024-09-03 16:39:13 +02:00
Ahmed Bouhuolia
0c6f23e770 Merge pull request #654 from bigcapitalhq/expense-credit-card
fix: Expense cannot accept credit card as payment account
2024-09-03 12:23:03 +02:00
Ahmed Bouhuolia
37a8ca4e97 fix: Expense cannot accept credit card as payment account 2024-09-03 12:22:07 +02:00
Ahmed Bouhuolia
795303c3a8 Merge pull request #653 from bigcapitalhq/tracking-more-events
feat: Tracking more Posthog events
2024-09-03 11:28:27 +02:00
Ahmed Bouhuolia
63ba3f0898 Merge branch 'develop' into tracking-more-events 2024-09-03 11:28:05 +02:00
Ahmed Bouhuolia
62594efa00 feat: Tracking more Posthog events 2024-09-03 11:26:24 +02:00
Ahmed Bouhuolia
9ac0dcbdc3 Merge pull request #651 from bigcapitalhq/bank-transactions-events
feat: Track banking service events
2024-09-02 17:13:49 +02:00
Ahmed Bouhuolia
df588dc4ce Merge branch 'develop' into bank-transactions-events 2024-09-02 17:13:43 +02:00
Ahmed Bouhuolia
d54f14a87a feat: Track banking service events 2024-09-02 17:12:37 +02:00
Ahmed Bouhuolia
a4d4be54c1 Merge pull request #650 from bigcapitalhq/cover-more-tracking-events
feat: Cover more tracking events.
2024-09-02 15:20:11 +02:00
Ahmed Bouhuolia
ddd17e74b5 feat: Cover more tracking events. 2024-09-02 15:19:01 +02:00
Ahmed Bouhuolia
81c0761fbe Merge pull request #649 from bigcapitalhq/import-multi-branches-expenses
fix: Integrate multiple branches with expense resource
2024-09-02 15:02:53 +02:00
Ahmed Bouhuolia
0812e3087e fix: Integrate multiple branches with expense resource 2024-09-02 15:02:02 +02:00
Ahmed Bouhuolia
0dd05493b2 Merge pull request #645 from bigcapitalhq/multi-branches-warehoues-in-importing
feat: integrate multiple branches and warehouses to resource importing
2024-09-02 14:46:08 +02:00
Ahmed Bouhuolia
bfb3909d26 feat: integrate multiple branches and warehouses with import 2024-09-02 14:42:05 +02:00
Ahmed Bouhuolia
266902026e Merge pull request #648 from bigcapitalhq/fix-bank-transactions-infinity-scrolling
fix: Bank transactions infinity scrolling
2024-09-02 10:41:54 +02:00
Ahmed Bouhuolia
791c4a4e9e fix: Bank transactions infinity scrolling 2024-09-02 10:41:16 +02:00
Ahmed Bouhuolia
2cfa9123b8 fix: Syntax error 2024-09-01 23:32:54 +02:00
Ahmed Bouhuolia
bb9614aafb Merge pull request #646 from bigcapitalhq/events-tracking
feat(server): Events tracking using Posthog
2024-09-01 23:06:38 +02:00
Ahmed Bouhuolia
302564a56e fix: remove the un-used file 2024-09-01 23:02:41 +02:00
Ahmed Bouhuolia
dcfc231d4d feat(server): Events tracking using Posthog 2024-09-01 23:01:25 +02:00
Ahmed Bouhuolia
64080ed678 Merge pull request #644 from bigcapitalhq/set-default-index-entries
fix: Set default index to transaction entries
2024-09-01 15:10:40 +02:00
Ahmed Bouhuolia
263935d91e Merge pull request #643 from bigcapitalhq/import-bugs
fix: Import bugs
2024-09-01 15:02:31 +02:00
Ahmed Bouhuolia
7f5ffb8da1 fix: Add the missing columns to the payment received and made models 2024-09-01 15:00:51 +02:00
Ahmed Bouhuolia
f07d25edbe fix: Set default index to transaction entries 2024-09-01 13:32:47 +02:00
Ahmed Bouhuolia
7c07d6b5ff fix: The unimported functions 2024-09-01 11:16:50 +02:00
Ahmed Bouhuolia
e433f4ad68 fix: Item resource columns labels 2024-09-01 11:07:55 +02:00
Ahmed Bouhuolia
a79b9caff6 Merge pull request #641 from bigcapitalhq/fix-getting-sheet-columns
fix: Getting the sheet columns in import sheet
2024-08-30 17:56:53 +02:00
Ahmed Bouhuolia
2227cead66 Merge pull request #624 from bigcapitalhq/subscription-middleware
fix: Subscription middleware
2024-08-30 17:56:02 +02:00
Ahmed Bouhuolia
410c4ea3e2 fix: Subscription active detarminer 2024-08-30 17:52:53 +02:00
Ahmed Bouhuolia
f92acbcbe0 fix: Getting the sheet columns in import sheet 2024-08-30 17:03:16 +02:00
Ahmed Bouhuolia
c986585cd9 Merge pull request #640 from bigcapitalhq/fix-typo-one-click-demo
fix: Typo one-click demo page
2024-08-30 00:15:29 +02:00
Ahmed Bouhuolia
250f0a30ef fix: Typo one-click demo page 2024-08-30 00:14:59 +02:00
Ahmed Bouhuolia
84b5e1adc1 Merge pull request #639 from bigcapitalhq/add-customer-type-to-customers
fix: Add customer type to customers resource
2024-08-29 22:50:20 +02:00
Ahmed Bouhuolia
2ab28370db fix: Add customer type to customers resource 2024-08-29 22:49:49 +02:00
Ahmed Bouhuolia
a88a525326 Merge pull request #638 from bigcapitalhq/use-standard-date-format-export
fix: use standard ISO 8601 format for exported data
2024-08-29 22:40:59 +02:00
Ahmed Bouhuolia
fce8e2c5a4 fix: use standard ISO 8601 format for exported data 2024-08-29 22:39:51 +02:00
Ahmed Bouhuolia
095608266c Merge pull request #632 from bigcapitalhq/split-lazy-loading
feat: Optimize loading perf. by splitting big chunks and lazy loading them
2024-08-29 21:27:40 +02:00
Ahmed Bouhuolia
0ec8aaa330 fix: delete unwanted files 2024-08-29 21:27:20 +02:00
Ahmed Bouhuolia
9fcb3ef77d feat: Add fallback spinner to authentication lazy-loaded pages 2024-08-29 21:19:36 +02:00
Ahmed Bouhuolia
dc61c57daf fix: Add spinner to preferences lazy loaded pages 2024-08-29 21:03:27 +02:00
Ahmed Bouhuolia
af284f3f6d feat: split the preferences pages 2024-08-29 20:49:08 +02:00
Ahmed Bouhuolia
c43123db76 Merge pull request #636 from bigcapitalhq/expand-export-page-size
fix: Expand the resources export page size limitation
2024-08-29 14:23:48 +02:00
Ahmed Bouhuolia
ebbcab3926 fix: Expand the resources export page size limitation 2024-08-29 14:22:45 +02:00
Ahmed Bouhuolia
a235f573c0 Merge pull request #635 from bigcapitalhq/fix-avoid-cost-job-import-preview
fix: Avoid running the cost job in import preview
2024-08-29 10:09:13 +02:00
Ahmed Bouhuolia
84a0b8f495 fix: re-schedule the jobs have date from the current moment 2024-08-29 10:05:38 +02:00
Ahmed Bouhuolia
b87321c897 fix: Avoid running the cost job in import preview 2024-08-28 22:15:15 +02:00
Ahmed Bouhuolia
fc6ebfea5c Debounce scheduling calculating items cost 2024-08-28 21:25:47 +02:00
Ahmed Bouhuolia
c9fe6d9b37 feat: Optimize loading perf. by spliting big chunks and lazy loading them 2024-08-26 22:51:40 +02:00
Ahmed Bouhuolia
161d60393a Merge pull request #629 from bigcapitalhq/details-subscription
fix: Add subscription plans offer text
2024-08-25 19:44:34 +02:00
Ahmed Bouhuolia
79413fa85e fix: Add subscription plans offer text 2024-08-25 19:43:54 +02:00
Ahmed Bouhuolia
58552c6c94 Merge pull request #628 from bigcapitalhq/fix-webapp-env-variables
fix: Make webapp package env variables dynamic
2024-08-25 18:21:55 +02:00
Ahmed Bouhuolia
2072e35cfa fix: Make webapp package env variables dynamic 2024-08-25 18:21:08 +02:00
Ahmed Bouhuolia
1eaac9d691 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-08-25 14:18:04 +02:00
Ahmed Bouhuolia
a916e8a0cb fix: syntax error 2024-08-25 14:17:23 +02:00
Ahmed Bouhuolia
a56f560036 Merge pull request #627 from bigcapitalhq/fix-style-tweeks-in-onboarding-page
fix: Style tweaks in onboarding page
2024-08-25 13:20:39 +02:00
Ahmed Bouhuolia
0fb886936c Merge pull request #626 from bigcapitalhq/disable-sms-service
fix: Disable sms service until Twilo integration
2024-08-25 13:20:14 +02:00
Ahmed Bouhuolia
670136916f fix: Style tweaks in onboarding page 2024-08-25 13:19:16 +02:00
Ahmed Bouhuolia
768297f137 fix: Disable sms service until Twilo integration 2024-08-25 13:07:07 +02:00
Ahmed Bouhuolia
ef505a0a62 Merge pull request #625 from bigcapitalhq/manual-journal-number-prefix
fix: Add prefix J-00001 to manual journals increments
2024-08-25 13:05:52 +02:00
Ahmed Bouhuolia
42d40620ec fix: add prefix J-00001 to manual journals increments 2024-08-25 13:05:28 +02:00
Ahmed Bouhuolia
ee2d8d3065 fix: subscription middleare 2024-08-25 12:42:42 +02:00
Ahmed Bouhuolia
959ef7a691 Merge branch 'listen-payment-webhooks' into develop 2024-08-24 21:52:04 +02:00
Ahmed Bouhuolia
60f03f534b fix(subscription): event TS types 2024-08-24 21:51:15 +02:00
Ahmed Bouhuolia
e44ebb700a Merge pull request #623 from bigcapitalhq/listen-payment-webhooks
fix: Listen to payment webhooks
2024-08-24 21:49:36 +02:00
Ahmed Bouhuolia
8e94c7a755 feat(subscription): invalidate subscription cache 2024-08-24 21:40:28 +02:00
Ahmed Bouhuolia
3a2ca36c07 Merge branch 'develop' into listen-payment-webhooks 2024-08-24 21:08:47 +02:00
Ahmed Bouhuolia
88ece74c8a Merge pull request #622 from wolone/develop
Fix: Syntax error caused error
2024-08-24 20:48:14 +02:00
Ahmed Bouhuolia
67a8610328 feat: cancel/resume LS subscriptions 2024-08-24 20:46:30 +02:00
Ahmed Bouhuolia
278d61ce61 fix: Listen to payment webhooks 2024-08-24 18:50:12 +02:00
wolone
e72d6ad6b8 Fix: Syntax error caused error 2024-08-24 23:33:53 +08:00
Ahmed Bouhuolia
bf66b31679 Merge pull request #590 from bigcapitalhq/filter-uncategorized-bank-transactions
feat(banking): Filter uncategorized bank transactions by date
2024-08-23 17:42:59 +02:00
Ahmed Bouhuolia
b4d426d2e8 feat: Filter uncategorized transactions by date 2024-08-23 17:40:05 +02:00
Ahmed Bouhuolia
7f7dd270e7 Merge pull request #619 from bigcapitalhq/change-banking-service-language
feat: change banking service language
2024-08-23 02:00:47 +02:00
Ahmed Bouhuolia
f6bad8fe30 fix: wip filter uncategorized transactions by date 2024-08-23 01:57:52 +02:00
Ahmed Bouhuolia
820b363f79 feat: change banking service language 2024-08-22 20:49:15 +02:00
Ahmed Bouhuolia
07740a51ef Merge pull request #616 from bigcapitalhq/one-click-demo-account
feat(ee): One-click demo account
2024-08-22 20:33:06 +02:00
Ahmed Bouhuolia
d15fb6fe19 chore: document http query 2024-08-22 20:32:48 +02:00
Ahmed Bouhuolia
5749ccec81 feat: seed more demo bank transactions 2024-08-22 19:50:31 +02:00
Ahmed Bouhuolia
4a99f6c0cf feat: one-click demo account 2024-08-22 19:21:23 +02:00
Ahmed Bouhuolia
59f480f9d5 feat: add more demo account seeders 2024-08-22 13:04:51 +02:00
Ahmed Bouhuolia
6cb9c919b5 Merge branch 'develop' into one-click-demo-account 2024-08-22 10:48:24 +02:00
Ahmed Bouhuolia
1062b65b5b feat: wip bank account transactions date filter 2024-08-22 01:03:03 +02:00
Ahmed Bouhuolia
bf3a70cabd Merge branch 'develop' into filter-uncategorized-bank-transactions 2024-08-22 00:12:37 +02:00
Ahmed Bouhuolia
f46cd28f87 Merge pull request #618 from bigcapitalhq/display-details-bank-account
fix: Some bank account details hidden
2024-08-21 21:22:15 +02:00
Ahmed Bouhuolia
dffcfe50aa fix: some bank account details hidden 2024-08-21 21:19:59 +02:00
Ahmed Bouhuolia
fac55efbc7 Merge pull request #617 from bigcapitalhq/fix-impoort-itemns
fix: Cannot import items income and cost accounts
2024-08-21 19:33:50 +02:00
Ahmed Bouhuolia
8b90ce5f6c fix: cannot import items income and cost accounts 2024-08-21 19:32:59 +02:00
Ahmed Bouhuolia
705b8da053 fix: protect the one-click demo accounts endpoints 2024-08-21 01:04:18 +02:00
Ahmed Bouhuolia
4a05ccc692 feat: add more seedders 2024-08-20 23:40:23 +02:00
Ahmed Bouhuolia
3200d65d90 feat: add demo account button on onboarding 2024-08-20 22:01:36 +02:00
Ahmed Bouhuolia
3f23038227 chore: comment 2024-08-20 18:49:39 +02:00
Ahmed Bouhuolia
408c807fc2 feat: import sheet files on initializing demo account 2024-08-20 18:30:21 +02:00
Ahmed Bouhuolia
d29079a8c5 fix: one-click demo account 2024-08-20 12:51:23 +02:00
Ahmed Bouhuolia
cca596b4a9 fix: one click demo 2024-08-19 21:21:39 +02:00
Ahmed Bouhuolia
b768f18294 chore: change subscription prices 2024-08-19 13:08:09 +02:00
Ahmed Bouhuolia
fed620505d feat: add one click endpoint 2024-08-19 12:10:38 +02:00
Ahmed Bouhuolia
a008aea3f3 feat(ee): one-click demo account 2024-08-19 12:08:58 +02:00
Ahmed Bouhuolia
25297bc191 chore: dump CHANGELOG 2024-08-18 20:56:10 +02:00
Ahmed Bouhuolia
1989887b25 Merge pull request #615 from bigcapitalhq/activate-account-from-drawer
feat: activate/inactivate account from drawer details
2024-08-18 20:24:07 +02:00
Ahmed Bouhuolia
e4fb126d39 feat: activate/inactivate account from drawer details 2024-08-18 20:23:47 +02:00
Ahmed Bouhuolia
5fcb2d9cc9 Merge pull request #614 from bigcapitalhq/delete-bank-account-with-uncategorized-transactions
fix: Delete bank account with uncategorized transactions
2024-08-18 19:55:15 +02:00
Ahmed Bouhuolia
06ea631732 fix: making pagination more readable 2024-08-18 19:38:15 +02:00
Ahmed Bouhuolia
2f21107a43 feat: delete uncategorized transactions before deleting bank account 2024-08-18 19:30:09 +02:00
Ahmed Bouhuolia
fb8118bea8 fix: Delete bank account with uncategorized transactions 2024-08-18 14:20:23 +02:00
Ahmed Bouhuolia
4ba1c0aa22 Merge pull request #612 from Champetaman/fix-manual-journal-date-expense-drawer
Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader
2024-08-18 11:20:25 +02:00
Ahmed Bouhuolia
169f115fa0 fix: remove the default value from date and createdAt because always required 2024-08-18 11:19:06 +02:00
Ahmed Bouhuolia
61ab2b78d9 Merge pull request #613 from bigcapitalhq/language-typos
fix: Language typos
2024-08-18 11:12:49 +02:00
Ahmed Bouhuolia
93732430fc fix: Language typos 2024-08-18 11:11:17 +02:00
Camilo Oviedo
0215206220 add: Created attribute formattedPublishedAt to display on Expense Drawer 2024-08-17 10:11:05 +10:00
Camilo Oviedo
3c8956fedf fix: Correctly display Date, Published At, and Created At fields 2024-08-17 10:09:44 +10:00
Ahmed Bouhuolia
4477ada1ad Merge pull request #611 from bigcapitalhq/fix-connection-lost
fix: Database connection lost error
2024-08-15 23:48:28 +02:00
Ahmed Bouhuolia
fde9ccc5ca fix: Database connection lost error 2024-08-15 23:47:21 +02:00
Ahmed Bouhuolia
bbbd96f159 Merge pull request #604 from bigcapitalhq/inconsistance-pagination-page-size
fix: inconsistance page size of paginated data tables
2024-08-14 22:14:03 +02:00
Ahmed Bouhuolia
9f4de8115f fix: initial page size to the data tables from store state 2024-08-14 22:12:21 +02:00
Ahmed Bouhuolia
d9f241a2f8 Merge branch 'develop' into inconsistance-pagination-page-size 2024-08-14 22:01:04 +02:00
Ahmed Bouhuolia
ee96dc68cc fix: Change Dropzone title and subtitle (#607) 2024-08-14 20:13:45 +02:00
Ahmed Bouhuolia
b12f090d13 fix: matching bank transactions should create associate payment transactions for bills and invoicese. (#606) 2024-08-14 19:23:15 +02:00
allcontributors[bot]
f6ce761a27 docs: add Champetaman as a contributor for code (#605)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-08-14 16:20:50 +02:00
Ahmed Bouhuolia
8c89e04f54 Merge pull request #603 from Champetaman/fix-import-drag-area-colors-blue
Fix: Enhance Dropzone visual feedback for dropzone
2024-08-14 16:17:44 +02:00
Ahmed Bouhuolia
f97b127a69 fix: style tweaks in dropzoen accept/reject modes 2024-08-14 16:12:13 +02:00
Ahmed Bouhuolia
5c1fa8f5cd fix: inconsistance page size of paginated data tables 2024-08-14 15:23:14 +02:00
Ahmed Bouhuolia
64c4d7b5a4 Merge pull request #599 from bigcapitalhq/rename-payment-receives-to-payment-received
fix: Typo payment receive messages
2024-08-14 14:54:54 +02:00
Ahmed Bouhuolia
ea2fad648b Merge branch 'develop' into rename-payment-receives-to-payment-received 2024-08-14 14:54:48 +02:00
Camilo Oviedo
9b8b51cb91 fix: Enhance visual feedback on file drag-and-drop 2024-08-14 16:29:38 +10:00
Camilo Oviedo
09b7e74d65 fix: Enhance visual feedback on file drag-and-drop 2024-08-14 16:21:48 +10:00
Ahmed Bouhuolia
7137e06d99 Merge pull request #602 from bigcapitalhq/remove-views-tabs-from-receipts-list
fix: Remove views tabs from receipts list
2024-08-14 00:06:14 +02:00
Ahmed Bouhuolia
fc29b765f7 fix: remove views tabs from receipts list 2024-08-14 00:04:11 +02:00
Ahmed Bouhuolia
2946475f89 Merge pull request #601 from bigcapitalhq/autofill-quick-customer-vendor
fix: Autofill the quick created customer/vendor
2024-08-13 23:59:26 +02:00
Ahmed Bouhuolia
d2193fdac0 fix: autofill the quick created customer/vendor 2024-08-13 23:55:53 +02:00
Ahmed Bouhuolia
ec2b7e332e Merge pull request #600 from bigcapitalhq/fix-typo-categories-list
fix: Typo categories list
2024-08-13 19:39:17 +02:00
Ahmed Bouhuolia
a3704df6dd fix: Typo categories list 2024-08-13 19:38:07 +02:00
Ahmed Bouhuolia
c3c784e52c Merge pull request #598 from bigcapitalhq/move-payment-mades-to-payments-made
fix: Typo payments made
2024-08-13 19:33:02 +02:00
Ahmed Bouhuolia
bbcf695a6c fix: Typo payments made 2024-08-13 19:10:09 +02:00
Ahmed Bouhuolia
038d4dd5a7 chore: renmame payment receive term to payment received 2024-08-13 15:15:07 +02:00
Ahmed Bouhuolia
961e4b99e8 fix: rename interfaces to PaymentReceived 2024-08-13 14:17:37 +02:00
Ahmed Bouhuolia
9991eebaaf fix(server): rename term to 2024-08-13 13:41:09 +02:00
Ahmed Bouhuolia
cd90fede54 Merge pull request #597 from bigcapitalhq/refresh-acccounts-bank-transactions
fix: Refresh accounts and account transactions.
2024-08-13 11:39:11 +02:00
Ahmed Bouhuolia
a2d28648bd fix: refresh accounts and account transactions. 2024-08-13 11:37:59 +02:00
Ahmed Bouhuolia
3097d05eda Merge pull request #596 from bigcapitalhq/transaction-type-description-general-ledger
fix: Transaction type and description do not show in general ledger.
2024-08-12 21:11:02 +02:00
Ahmed Bouhuolia
ff94d8d9b2 fix: Transaction type and description do not show in general ledger. 2024-08-12 21:08:02 +02:00
Ahmed Bouhuolia
79cc09fad9 Merge pull request #595 from bigcapitalhq/add-comparators-to-amount-bank-rule
feat: Add amount comparators to amount bank rule field
2024-08-12 20:17:20 +02:00
Ahmed Bouhuolia
c1b29c3f23 fix: add equal condition to number fields on bank rule 2024-08-12 20:16:18 +02:00
Ahmed Bouhuolia
cf4bb3007e feat: run re-recognizing bank transactions on edit bank rule 2024-08-12 20:07:01 +02:00
Ahmed Bouhuolia
193a86cf30 feat: add amount comparators to amount bank rule field 2024-08-12 17:53:57 +02:00
Ahmed Bouhuolia
7a81f14eb2 Merge pull request #594 from bigcapitalhq/multi-lines-transactions-statements
fix: Multi-lines transactions statements
2024-08-12 16:33:01 +02:00
Ahmed Bouhuolia
14d1f0bd1d fix: Multi-lines transactions statements 2024-08-12 16:31:36 +02:00
Ahmed Bouhuolia
82f8648c59 Merge pull request #593 from bigcapitalhq/fix-round-pending-matching
fix: Rounding the total amount the pending and matched transactions
2024-08-12 13:01:27 +02:00
Ahmed Bouhuolia
c928940d32 fix: rounding the total amount the pending and matched transactions 2024-08-12 13:01:00 +02:00
Ahmed Bouhuolia
0a78d56015 Merge pull request #592 from bigcapitalhq/matching-reconcile-branches
fix: Should not load branches on reconcile matching form if the branches not enabled
2024-08-12 11:29:48 +02:00
Ahmed Bouhuolia
1a5716873e fix: should not load branches on reconcile matching form if the branches not enabled 2024-08-12 11:28:34 +02:00
Ahmed Bouhuolia
01b7c86ab9 Merge pull request #588 from Champetaman/fix-dev-variable-setting-error
Update `dev` Script in `package.json` to Use `cross-env`
2024-08-12 10:55:45 +02:00
Ahmed Bouhuolia
0ca209b195 Merge pull request #589 from bigcapitalhq/bank-pending-transactions
feat: Pending bank transactions
2024-08-12 10:54:32 +02:00
Ahmed Bouhuolia
be6f6e3c73 fix: function description 2024-08-12 10:54:16 +02:00
Ahmed Bouhuolia
cb016be78c fix: avoid decrement/increment for pending bank transactions 2024-08-12 10:48:36 +02:00
Ahmed Bouhuolia
fc085f2328 Merge pull request #587 from bigcapitalhq/big-244-uncategorize-bank-transactions-in-bulk
feat: Uncategorize bank transactions in bulk
2024-08-12 10:11:55 +02:00
Ahmed Bouhuolia
9a34f3e283 fix: invalidate account cache on bulk uncategorizing 2024-08-12 10:11:40 +02:00
Ahmed Bouhuolia
7054e862d5 feat: pending transactions table 2024-08-11 22:51:58 +02:00
Ahmed Bouhuolia
faa81abee4 feat(banking): uncategorize bank transactions in bulk 2024-08-11 21:26:02 +02:00
Ahmed Bouhuolia
6d01f2a323 Merge pull request #591 from bigcapitalhq/import-export-tax-rates
feat: import and export tax rates
2024-08-11 19:51:50 +02:00
Ahmed Bouhuolia
72678bb936 feat: import and export tax rates 2024-08-11 19:51:16 +02:00
Ahmed Bouhuolia
df8b68fda6 feat(banking): Filter uncategorized bank transactions 2024-08-11 18:34:45 +02:00
Ahmed Bouhuolia
9ae5644af9 feat: Pending bank transactions 2024-08-11 16:14:13 +02:00
Camilo Oviedo
e8830c5911 Fix dev variable setting causing error on windows for craco start command 2024-08-11 22:14:54 +10:00
Camilo Oviedo
7699889bd6 Fix dev variable setting causing error on windows for craco start command 2024-08-11 21:57:50 +10:00
Ahmed Bouhuolia
35a061d188 feat: Uncategorize bank transactions in bulk 2024-08-11 13:02:38 +02:00
Ahmed Bouhuolia
c7c021c969 fix(banking): detarmine if Plaid item is disabled (#585) 2024-08-11 12:10:15 +02:00
Ahmed Bouhuolia
be8352654e Merge pull request #559 from oleynikd/tax-precisions
Increased tax_amount_withheld decimal precision
2024-08-08 16:31:43 +02:00
Ahmed Bouhuolia
fb58ab8cc1 Merge pull request #571 from bigcapitalhq/remove-controller-escape
fix: Remove the request body escape.
2024-08-08 16:12:31 +02:00
Ahmed Bouhuolia
8da89ebe8b fix: remove the request body escape. 2024-08-08 16:10:42 +02:00
Ahmed Bouhuolia
d43d46ebec Merge pull request #570 from bigcapitalhq/popover2-version
fix: Update @blueprintjs/popover2 version
2024-08-08 12:57:48 +02:00
Ahmed Bouhuolia
ac3a514795 fix: update @blueprintjs/popover2 version 2024-08-08 12:57:08 +02:00
Ahmed Bouhuolia
f67c63a4fa Merge pull request #569 from bigcapitalhq/fix-edit-bank-rule-recognized
fix: Recognize transactions on editing bank rule
2024-08-08 00:26:18 +02:00
Ahmed Bouhuolia
0025dcf8d4 fix: add recognize jobs 2024-08-08 00:23:47 +02:00
Ahmed Bouhuolia
81995dc94f fix: recognize transactions on editing bank rule 2024-08-08 00:20:17 +02:00
Ahmed Bouhuolia
3fcc70c1d8 Merge pull request #568 from bigcapitalhq/fix-banking-api-query
fix: Banking API account and page query.
2024-08-07 20:17:49 +02:00
Ahmed Bouhuolia
a986c7a250 fix: Banking api account and page query. 2024-08-07 20:08:59 +02:00
allcontributors[bot]
37e25a8061 docs: add mittalsam98 as a contributor for bug (#567)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-08-07 18:58:37 +02:00
Ahmed Bouhuolia
cfba628465 Merge pull request #562 from mittalsam98/fix/wrong-due-amount
fix: the wrong invoice due amouynt.
2024-08-07 18:56:28 +02:00
Ahmed Bouhuolia
3d200f4d7d fix: wrong invoice due amount 2024-08-07 18:52:36 +02:00
Ahmed Bouhuolia
f21b01b1d6 feat: Move billing to preferences (#566)
* feat: move billing to preferences

* chore: remove the commented lines
2024-08-06 12:12:41 +02:00
Ahmed Bouhuolia
3cbcfac333 Merge pull request #565 from bigcapitalhq/fix-edit-bank-rule
fix: Edit bank rule
2024-08-06 00:50:05 +02:00
Ahmed Bouhuolia
cc21e1856f fix: Edit bank rule 2024-08-06 00:48:58 +02:00
Ahmed Bouhuolia
efd0e1e225 Merge pull request #564 from bigcapitalhq/fix-banking-bugs
fix: Banking service bugs
2024-08-05 23:06:03 +02:00
Ahmed Bouhuolia
521b083ed7 fix: retrieve the excluded transactions count 2024-08-05 22:50:58 +02:00
Ahmed Bouhuolia
a09fe26df7 fix: group query key constants in seperate file 2024-08-05 21:36:34 +02:00
Ahmed Bouhuolia
c7a85c4cf8 fix: categorize transactions on recognized transactions table 2024-08-05 21:20:11 +02:00
Ahmed Bouhuolia
f6350d3d61 fix: Should not show the excluded transactions in recognized transactions 2024-08-05 21:11:15 +02:00
Ahmed Bouhuolia
64c0732e5f fix: infinity scrolling of bank account transactions 2024-08-05 20:57:13 +02:00
Ahmed Bouhuolia
8e99a31455 fix: validate exclude and unexclude uncategorized transaction 2024-08-05 15:56:11 +02:00
Ahmed Bouhuolia
6d0d0689e1 Merge pull request #533 from bigcapitalhq/bulk-categorize-bank-transactions
feat: Bulk categorize bank transactions
2024-08-04 22:23:11 +02:00
Ahmed Bouhuolia
9836129e49 Merge branch 'develop' into bulk-categorize-bank-transactions 2024-08-04 22:23:02 +02:00
Ahmed Bouhuolia
86631ea8c3 chore: fix typing 2024-08-04 22:20:31 +02:00
Ahmed Bouhuolia
475ccd4903 Merge pull request #563 from bigcapitalhq/pause-resume-bank-feeds-syncing
feat: pause/resume bank account feeds syncing
2024-08-04 21:47:32 +02:00
Ahmed Bouhuolia
8608144ec1 chore: components description 2024-08-04 21:47:16 +02:00
Ahmed Bouhuolia
f9cf6d325a feat: pause bank account feeds 2024-08-04 21:14:05 +02:00
Ahmed Bouhuolia
fc0240c692 feat: confimation dialog on disconnecting bank account 2024-08-04 19:44:36 +02:00
Ahmed Bouhuolia
b84675325f feat: alert messages of pause.resume bank feeds 2024-08-04 16:05:35 +02:00
Ahmed Bouhuolia
647bed5c67 feat: control the multi-select switch 2024-08-04 15:42:53 +02:00
Ahmed Bouhuolia
00f5bb1d73 fix: decrement uncategorized transactions count 2024-08-04 13:15:20 +02:00
Ahmed Bouhuolia
208800b411 feat: wip pause/resume bank feeds syncing 2024-08-04 11:22:21 +02:00
Ahmed Bouhuolia
5e12a4cea4 feat: pause/resume bank account feeds syncing 2024-08-04 00:36:19 +02:00
Ahmed Bouhuolia
fdf3e34f1c feat: wip uncategorize bank transaction 2024-08-03 23:30:23 +02:00
Ahmed Bouhuolia
d74337fb94 feat: wip multi-select transactions to categorization and matching 2024-08-03 22:01:21 +02:00
Sachin
8cab012324 fix: due Amount on edit page is calculated wrong with "Exclusive of Tax" Invoice mode 2024-08-03 23:56:02 +05:30
Ahmed Bouhuolia
940b4f9175 Merge pull request #553 from oleynikd/attachments
Download attachments (documents) with original filenames
2024-08-02 02:42:52 +02:00
Ahmed Bouhuolia
5d0dd1fe3f Merge branch 'main' into develop 2024-08-01 20:10:01 +02:00
Ahmed Bouhuolia
ded4e2bb59 Merge pull request #560 from bigcapitalhq/fix-onboarding-on-small-screens
fix: Onboarding layout on small screens
2024-08-01 19:53:09 +02:00
Ahmed Bouhuolia
219e6fb466 fix: onboarding page layout on small screens 2024-08-01 19:51:25 +02:00
Denis
7147e230de Increased tax_amount_withheld decimal precision
Fixing #547
2024-08-01 16:31:14 +03:00
Ahmed Bouhuolia
5ce11f192f feat: reset the state once closing categorization aside 2024-08-01 14:02:02 +02:00
Ahmed Bouhuolia
71e865e9b7 Merge remote-tracking branch 'refs/remotes/origin/bulk-categorize-bank-transactions' into bulk-categorize-bank-transactions 2024-08-01 13:46:19 +02:00
Ahmed Bouhuolia
590506f183 Merge branch 'develop' into bulk-categorize-bank-transactions 2024-08-01 13:46:03 +02:00
Ahmed Bouhuolia
bed281a637 feat: wip multipe transactions categorization 2024-08-01 13:44:49 +02:00
Ahmed Bouhuolia
47dd767b3a feat: getting matched transactiosn from multi uncategorized transactions 2024-08-01 12:11:54 +02:00
Ahmed Bouhuolia
8623b69991 feat: getting matched transactiosn from multi uncategorized transactions 2024-08-01 12:11:40 +02:00
Denis
a1ddc81dac Fixed double slash in attachments route 2024-07-30 23:54:46 +03:00
Denis
832cdacebf Download attachments with original filenames 2024-07-30 23:48:15 +03:00
Ahmed Bouhuolia
9f979080b6 fix: remove console.log 2024-07-30 21:55:44 +02:00
Ahmed Bouhuolia
7f7301b31e Merge pull request #544 from bigcapitalhq/billing-subscription-page
feat: Billing subscription page
2024-07-30 21:44:55 +02:00
Ahmed Bouhuolia
6affbedef4 feat: description to billing page 2024-07-30 21:43:33 +02:00
Ahmed Bouhuolia
ba7f32c1bf feat: abstract the pricing plans for setup and billing page 2024-07-30 17:47:03 +02:00
allcontributors[bot]
305ce29ebb docs: add oleynikd as a contributor for bug (#551)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-07-30 11:14:12 +02:00
Ahmed Bouhuolia
4cd0405078 fix: quick payment received and payment made form initial values 2024-07-30 11:06:45 +02:00
Ahmed Bouhuolia
783102449f fix: create quick payment received and payment made transactions 2024-07-30 11:06:35 +02:00
Denis
ae617b2e1d Fixed Quick Payment Dialogs
PaymentReceives and BillsPayments Controllers expect 'amount' parameter, but webapp sends 'payment_amount'
2024-07-30 11:06:24 +02:00
Ahmed Bouhuolia
9090d0a7b2 Merge pull request #548 from oleynikd/oleynikd-dev
Fixed Quick Payment Dialogs
2024-07-30 11:04:25 +02:00
Ahmed Bouhuolia
ffc55fa81b fix: quick payment received and payment made form initial values 2024-07-30 11:02:49 +02:00
Ahmed Bouhuolia
07c57ed539 Merge branch 'develop' into billing-subscription-page 2024-07-30 10:03:31 +02:00
Ahmed Bouhuolia
788150f80d Merge pull request #549 from oleynikd/s3-path-style
Added support of path-style S3 endpoints
2024-07-30 00:06:16 +02:00
Ahmed Bouhuolia
c4e77e4e3b fix: create quick payment received and payment made transactions 2024-07-29 23:15:42 +02:00
Denis
c09384e49b Added support of path-style S3 endpoints
This can be very useful when using S3-compatible object storages like MinIO
2024-07-29 23:48:29 +03:00
Denis
4490c2d4b4 Fixed Quick Payment Dialogs
PaymentReceives and BillsPayments Controllers expect 'amount' parameter, but webapp sends 'payment_amount'
2024-07-29 22:49:07 +03:00
Ahmed Bouhuolia
e11f1a95f6 Merge pull request #529 from bigcapitalhq/disconnect-bank-account
feat: Disconnect bank account
2024-07-29 20:18:36 +02:00
Ahmed Bouhuolia
b91273eee4 Merge branch 'develop' into disconnect-bank-account 2024-07-29 20:17:09 +02:00
Ahmed Bouhuolia
b5d570417b fix: add events interfaces of disconnect bank account 2024-07-29 20:10:15 +02:00
Ahmed Bouhuolia
acd3265e35 feat: add migration to is_syncing_owner column in accounts table 2024-07-29 20:01:04 +02:00
Ahmed Bouhuolia
894c899847 feat: improvement in Plaid accounts disconnecting 2024-07-29 19:49:20 +02:00
Ahmed Bouhuolia
f6d4ec504f feat: tweaks in disconnecting bank account 2024-07-29 16:55:50 +02:00
Ahmed Bouhuolia
1a01461f5d feat: delete Plaid item once bank account deleted 2024-07-29 16:20:59 +02:00
Ahmed Bouhuolia
f5e18fc1fe feat: document the Redux mutation methods 2024-07-29 14:03:37 +02:00
Ahmed Bouhuolia
f64cd32985 Merge branch 'develop' into bulk-categorize-bank-transactions 2024-07-29 13:03:35 +02:00
Ahmed Bouhuolia
89552d7ee2 Merge pull request #532 from bigcapitalhq/bulk-exclude-bank-transactions
feat: Bulk exclude bank transactions
2024-07-29 13:01:56 +02:00
Ahmed Bouhuolia
4345623ea9 feat: document functions 2024-07-29 13:00:50 +02:00
Ahmed Bouhuolia
f457759e39 Merge branch 'develop' into bulk-exclude-bank-transactions 2024-07-29 12:00:49 +02:00
Ahmed Bouhuolia
14d5e82b4a fix: style of database checkbox 2024-07-29 12:00:34 +02:00
Ahmed Bouhuolia
333b6f5a4b feat: change subscription plan 2024-07-28 20:52:53 +02:00
Ahmed Bouhuolia
1660df20af feat: wip billing page 2024-07-28 17:53:55 +02:00
Ahmed Bouhuolia
14a9c4ba28 fix: style tweaks in billing page 2024-07-27 21:56:55 +02:00
Ahmed Bouhuolia
383be111fa feat: style the billing page 2024-07-27 21:47:17 +02:00
Ahmed Bouhuolia
7720b1cc34 feat: getting subscription endpoint 2024-07-27 17:39:50 +02:00
Ahmed Bouhuolia
db634cbb79 feat: pause, resume main subscription 2024-07-27 16:55:56 +02:00
Ahmed Bouhuolia
53f37f4f48 Merge pull request #546 from bigcapitalhq/remove-views-tabs
feat: Remove the views tabs bar from all tables
2024-07-25 19:21:50 +02:00
Ahmed Bouhuolia
0a7b522b87 chore: remove unused import 2024-07-25 19:21:16 +02:00
Ahmed Bouhuolia
9e6500ac79 feat: remove the views tabs bar from all tables 2024-07-25 19:17:54 +02:00
Ahmed Bouhuolia
b93cb546f4 Merge pull request #545 from bigcapitalhq/excessed-payments-as-credit
Excessed payments as credit
2024-07-25 18:57:31 +02:00
Ahmed Bouhuolia
6d17f9cbeb feat: record excessed payments as credit 2024-07-25 18:46:24 +02:00
Ahmed Bouhuolia
998e6de211 feat: billing subscription page 2024-07-25 15:21:01 +02:00
Ahmed Bouhuolia
6fb02f9869 feat: bulk categorize and match bank transactions 2024-07-18 19:41:23 +02:00
Ahmed Bouhuolia
449390143d feat: bulk categorizing bank transactions 2024-07-18 17:00:23 +02:00
Ahmed Bouhuolia
51471ed000 feat: exclude bank transactions in bulk 2024-07-17 23:19:59 +02:00
Ahmed Bouhuolia
fe214b1b2d feat: push CHANGELOG 2024-07-17 16:53:47 +02:00
Ahmed Bouhuolia
6b6b73b77c feat: send signup event to Loops (#531)
* feat: send signup event to Loops

* feat: fix
2024-07-17 15:56:05 +02:00
angelosorno
d805703c08 feat: Added Spanish language to the App 2024-07-16 14:56:05 -05:00
Ahmed Bouhuolia
c2815afbe3 feat: disconnect and update bank account 2024-07-16 17:09:00 +02:00
Ahmed Bouhuolia
fa7e6b1fca feat: disconnect bank account 2024-07-15 23:18:39 +02:00
Ahmed Bouhuolia
107a6f793b Merge pull request #526 from bigcapitalhq/monthly-plans
feat: upgrade the subscription plans
2024-07-14 14:21:57 +02:00
Ahmed Bouhuolia
67d155759e feat: backend the new monthly susbcription plans 2024-07-14 14:19:04 +02:00
Ahmed Bouhuolia
7e2e87256f Merge pull request #527 from bigcapitalhq/fix-sync-removed-transactions
fix: sync the removed bank transactions from the source
2024-07-13 21:56:13 +02:00
Ahmed Bouhuolia
df7790d7c1 fix: sync the removed bank transactions from the source 2024-07-13 21:54:44 +02:00
Ahmed Bouhuolia
72128a72c4 feat: add variant ids to new subscription plans 2024-07-13 19:53:52 +02:00
Ahmed Bouhuolia
eb3f23554f feat: upgrade the subscription plans 2024-07-13 18:19:18 +02:00
Ahmed Bouhuolia
69ddf43b3e fix: duplicated event emitter 2024-07-13 03:23:25 +02:00
Ahmed Bouhuolia
249eadaeaa Merge pull request #525 from bigcapitalhq/fix-plaid-transactions-syncing
fix: Plaid transactions syncing
2024-07-12 23:44:27 +02:00
Ahmed Bouhuolia
59168bc691 fix: Plaid transactions syncing 2024-07-12 23:43:20 +02:00
Ahmed Bouhuolia
81b26c6f13 fix(hotfix): uniqid import 2024-07-12 20:15:28 +02:00
Ahmed Bouhuolia
da435d85d9 Merge pull request #524 from bigcapitalhq/fix-cashflow-transactions-type
fix: Cashflow transactions types
2024-07-09 14:57:43 +02:00
Ahmed Bouhuolia
533006b90e fix: Cashflow transactions types 2024-07-09 14:47:30 +02:00
Ahmed Bouhuolia
d096e49d45 Merge pull request #523 from bigcapitalhq/matching-transactions-fixes
fix: Matching transactions bugs
2024-07-08 22:18:12 +02:00
Ahmed Bouhuolia
73acdb6240 fix: add bank rule categories 2024-07-08 21:48:16 +02:00
Ahmed Bouhuolia
38d4122d11 fix: matching transactions bugs 2024-07-08 19:37:11 +02:00
Ahmed Bouhuolia
24a77c81b3 fix: unexpected char in cashflow transactions report 2024-07-08 15:25:28 +02:00
Ahmed Bouhuolia
7f41b4280e fix: the database migration schema 2024-07-08 15:18:58 +02:00
Ahmed Bouhuolia
aa89653967 Merge pull request #522 from bigcapitalhq/reconcile-match-transactionss
Reconcile match transactionss
2024-07-07 23:52:30 +02:00
Ahmed Bouhuolia
b80bc95fa5 fix: increment/decrement uncategorized transactions on excluding 2024-07-07 23:35:26 +02:00
Ahmed Bouhuolia
9a5befbee7 fix: bank transactions report 2024-07-07 22:11:57 +02:00
Ahmed Bouhuolia
b7487f19d3 fix: improvements to bank matching transactions 2024-07-06 19:10:07 +02:00
Ahmed Bouhuolia
cd9039fe16 fix(server): match transactions query 2024-07-06 16:10:34 +02:00
Ahmed Bouhuolia
87f60f7461 feat: cashflow transaction matching 2024-07-04 22:44:20 +02:00
Ahmed Bouhuolia
202179ec0b feat: reconcile matching transactions 2024-07-04 19:21:05 +02:00
Ahmed Bouhuolia
168883a933 fix: syntax error 2024-07-04 17:50:48 +02:00
Ahmed Bouhuolia
f62ec83e29 Merge branch 'main' into develop 2024-07-04 17:28:05 +02:00
Ahmed Bouhuolia
eff8b41720 Merge pull request #519 from bigcapitalhq/change-settings-value-colum-to-text
fix: alter value column of the settings table to text instead of string
2024-07-04 09:05:17 +02:00
Ahmed Bouhuolia
632cc3d72e fix: alter value column of the settings table to text instead of string 2024-07-04 08:58:41 +02:00
Ahmed Bouhuolia
aefdaac68d Merge pull request #511 from bigcapitalhq/BIG-208
feat: Bank rules for uncategorized transactions
2024-07-03 19:43:28 +02:00
Ahmed Bouhuolia
b8a0a5509d fix: style matching bank transactions 2024-07-03 19:41:43 +02:00
Ahmed Bouhuolia
a5eb42edaf feat: wip bank transaction matching 2024-07-03 18:49:21 +02:00
Ahmed Bouhuolia
67b519db61 fix: filter the uncategorized transactions out of matched transactions 2024-07-03 17:23:12 +02:00
Ahmed Bouhuolia
91730d204e feat: get bank account meta summary 2024-07-02 19:21:31 +02:00
Ahmed Bouhuolia
8a09de9771 fix: bank rules 2024-07-02 12:17:01 +02:00
Ahmed Bouhuolia
50861940a8 feat: style the banking service 2024-07-01 20:11:30 +02:00
Ahmed Bouhuolia
55caf037cd fix: match transaction aside layout 2024-07-01 19:24:09 +02:00
Ahmed Bouhuolia
c95eec565d feat: auto fill categorize form from recognized transaction 2024-07-01 15:48:40 +02:00
Ahmed Bouhuolia
79616cf1eb feat: sort the matched transactions 2024-07-01 13:03:38 +02:00
Ahmed Bouhuolia
c27458ebcc feat: wip matching bank transactions 2024-07-01 12:02:59 +02:00
Ahmed Bouhuolia
da0fab9a58 fix: Bank rules conditions column 2024-07-01 10:48:11 +02:00
Ahmed Bouhuolia
5bbcb7913d fix: Delete bank rule if it has no associations 2024-06-30 16:54:25 +02:00
Ahmed Bouhuolia
48ff93b6ab feat: Typing the bank rules hooks 2024-06-30 08:27:32 +02:00
Ahmed Bouhuolia
f816e7f25c fix(webapp): Switch between uncategorized transactions tabs 2024-06-29 19:31:13 +02:00
Ahmed Bouhuolia
3cd66ba4d6 feat: merge the boot provider of categorize and matching forms 2024-06-29 17:00:36 +02:00
Ahmed Bouhuolia
5d5d4a1972 feat: Calculate the total pending when matching. 2024-06-29 14:35:24 +02:00
Ahmed Bouhuolia
b01528c06b fix: group matches to get possible and perfect matches 2024-06-29 13:21:59 +02:00
Ahmed Bouhuolia
cb1f587637 fix(server): Handle the delete error when the matched transaction 2024-06-29 10:30:03 +02:00
Ahmed Bouhuolia
978ce6c441 feat: excluded bank transactions 2024-06-27 22:14:53 +02:00
Ahmed Bouhuolia
fab22c9820 feat: endpoint to get recognized transactions 2024-06-27 14:23:17 +02:00
Ahmed Bouhuolia
7edf268e75 fix: mathcing bank transaction styling 2024-06-26 22:25:59 +02:00
Ahmed Bouhuolia
87bf29f28c fix(server): getting matched transaction transformer 2024-06-26 22:25:37 +02:00
Ahmed Bouhuolia
d305c7ad32 feat: toggle banking matching aside 2024-06-26 19:33:01 +02:00
Ahmed Bouhuolia
7a9c7209bc feat: hook up the matching form to the server 2024-06-26 17:39:12 +02:00
Ahmed Bouhuolia
d2d37820f5 feat(webapp): edit bank rule 2024-06-26 00:04:54 +02:00
Ahmed Bouhuolia
1889969191 feat: bank rules table 2024-06-25 23:45:31 +02:00
Ahmed Bouhuolia
8c2888fcd8 fix(server): delete bank rule 2024-06-25 23:44:57 +02:00
Ahmed Bouhuolia
47879d04b2 feat(webapp): bank rule 2024-06-25 22:20:36 +02:00
Ahmed Bouhuolia
f1f52ce972 feat(webapp): bank rule form 2024-06-25 17:31:32 +02:00
Ahmed Bouhuolia
dad8aeaff1 feat(webapp): rule form 2024-06-25 13:42:19 +02:00
Ahmed Bouhuolia
5aae45c8a8 feat(webapp): bank rules 2024-06-25 11:57:02 +02:00
Ahmed Bouhuolia
3e437a041c Merge pull request #518 from bigcapitalhq/BIG-213
fix: Tax rate not saving on creating a new invoice
2024-06-24 10:47:21 +02:00
Ahmed Bouhuolia
e783cfeafa fix: Tax rate not saving on creating a new invoice 2024-06-24 10:46:19 +02:00
Ahmed Bouhuolia
5dde7f5584 Merge pull request #516 from bigcapitalhq/BIG-212
fix: Reorder 'debit' and 'credit' columns
2024-06-24 10:44:28 +02:00
Ahmed Bouhuolia
8e0911ec85 fix: Reorder 'debit' and 'credit' columns 2024-06-24 10:43:34 +02:00
Ahmed Bouhuolia
66d2d6a612 feat: avoid categorize excluded transaction 2024-06-23 23:34:20 +02:00
Ahmed Bouhuolia
8dc2b18707 feat: recognize the syncd bank transactions 2024-06-23 18:49:46 +02:00
Ahmed Bouhuolia
589b29bbdd feat: validate the matched linked transacation on deleting. 2024-06-23 14:34:40 +02:00
Ahmed Bouhuolia
ca403872b3 feat: exclude bank transaction 2024-06-21 11:33:03 +02:00
Ahmed Bouhuolia
738a84bb4b feat: match bank transaction 2024-06-20 23:31:46 +02:00
Ahmed Bouhuolia
b37002bea6 feat: exclude/unexclude the uncategorized transactions 2024-06-20 13:50:29 +02:00
Ahmed Bouhuolia
b6deb842ff feat: retrieve the matching transactions 2024-06-20 10:20:18 +02:00
Ahmed Bouhuolia
d3230767dd feat: matching uncategorized transactions 2024-06-19 22:40:10 +02:00
Ahmed Bouhuolia
6c4b0cdac5 feat: auto recognize uncategorized transactions 2024-06-19 13:49:12 +02:00
Ahmed Bouhuolia
0b5cee070a feat: recognize uncategorized transactions 2024-06-18 21:43:54 +02:00
Ahmed Bouhuolia
906835c396 feat: bank rules for uncategorized transactions 2024-06-18 17:14:30 +02:00
Ahmed Bouhuolia
7b4afd3859 Update .env.example 2024-06-17 18:30:13 +02:00
Ahmed Bouhuolia
590715037b chore: dump CHANGELOG.md 2024-06-17 15:33:49 +02:00
Ahmed Bouhuolia
1e53a8e85e Merge pull request #506 from bigcapitalhq/BIG-206
feat: Setting up the date format in the whole system dates
2024-06-17 12:53:31 +02:00
Ahmed Bouhuolia
2ad77103ac feat: cashflow tranasction date format 2024-06-17 12:50:31 +02:00
Ahmed Bouhuolia
c1fc70863b Merge pull request #497 from bigcapitalhq/BIG-195
fix: Disable email confirmation does not work with invited users
2024-06-17 10:34:33 +02:00
Ahmed Bouhuolia
125dff8376 feat: format created at date 2024-06-17 10:27:02 +02:00
Ahmed Bouhuolia
84da7b7df5 Merge pull request #509 from bigcapitalhq/BIG-193
feat: Migrating to Envoy proxy instead of Nginx
2024-06-17 09:22:23 +02:00
Ahmed Bouhuolia
4c82f6f8ad feat: Migrating to Envoy proxy instead of Nginx 2024-06-15 11:54:19 +02:00
Ahmed Bouhuolia
0d7aad5448 Merge pull request #508 from bigcapitalhq/BIG-142
fix: add space between buttons on floating actions bar
2024-06-14 08:29:45 +02:00
Ahmed Bouhuolia
74b74a2722 fix: add space between buttons on floating actions bar 2024-06-14 08:27:30 +02:00
Ahmed Bouhuolia
3a0a0db8a7 feat: setting up the date format in the whole system dates 2024-06-12 19:43:42 +02:00
Ahmed Bouhuolia
265ea9ca48 Merge pull request #501 from bigcapitalhq/BIG-202
fix: Balance sheet and P/L nested accounts
2024-06-12 13:06:37 +02:00
Ahmed Bouhuolia
cfd37f8894 fix: Balance sheet and P/L nested accounts 2024-06-12 13:05:02 +02:00
Ahmed Bouhuolia
d1caa5c5ce fix: Disable email confirmation does not work with invited users 2024-06-10 15:59:33 +02:00
Ahmed Bouhuolia
d998d716b7 Merge pull request #496 from bigcapitalhq/fix-payment-receive-attachments
fix: Edit the payment received transactions with attachments
2024-06-10 13:41:44 +02:00
Ahmed Bouhuolia
031ccc4a0b fix: Edit the payment received transactions with attachments 2024-06-10 13:41:10 +02:00
Ahmed Bouhuolia
e4f61823b3 Merge pull request #485 from bigcapitalhq/BIG-186
fix: Closing balance in general ledger report does not sum the negative figures
2024-06-10 08:17:01 +02:00
Ahmed Bouhuolia
1cbc1c056f feat: general ledger filter nodes 2024-06-10 08:08:47 +02:00
Ahmed Bouhuolia
4d4ef54c56 Merge pull request #494 from bigcapitalhq/BIG-192
fix: Concurrency controlling multiple processes in Bigcapital CLI commands
2024-06-09 22:54:02 +02:00
Ahmed Bouhuolia
f7fcfefc78 fix: Concurrency controlling multiple processes in Bigcapital CLI commands 2024-06-09 22:52:56 +02:00
Ahmed Bouhuolia
858f347fd4 Merge pull request #493 from bigcapitalhq/BIG-198
fix: Something wrong in uploading uncategorized bank transactions
2024-06-09 21:30:32 +02:00
Ahmed Bouhuolia
4d73b59cf3 fix: Something wrong in uploading uncategorized bank transactions 2024-06-09 21:30:07 +02:00
Ahmed Bouhuolia
bc67f0cca8 fix: increment/decrement the uncategorized transactios on accounts 2024-06-09 21:05:43 +02:00
Ahmed Bouhuolia
ef2d1ff141 feat: Add COGS type to cash transactions categorization 2024-06-09 21:05:19 +02:00
Ahmed Bouhuolia
dc4cdb2a8f fix: Assign branch in categorize bank transaction 2024-06-09 20:05:15 +02:00
Ahmed Bouhuolia
8862810706 Merge pull request #489 from bigcapitalhq/fix-plaid-syncing
fix: Plaid data available syncing
2024-06-07 01:31:34 +02:00
Ahmed Bouhuolia
3dadbeac4d fix: all sql queries should be under one transaction 2024-06-07 01:30:08 +02:00
Ahmed Bouhuolia
494d2c1fe0 fix: TS typing 2024-06-07 01:11:19 +02:00
Ahmed Bouhuolia
d27562bd43 fix: Plaid data available syncing 2024-06-07 01:07:17 +02:00
Ahmed Bouhuolia
8b99e0938d fix: remove un-used code 2024-06-06 18:50:24 +02:00
Ahmed Bouhuolia
94192bfc29 fix: doctype general ledger 2024-06-06 18:48:33 +02:00
Ahmed Bouhuolia
708a4dda9e chore: remove the console.log 2024-06-06 18:44:19 +02:00
Ahmed Bouhuolia
10fcf94c92 feat: general ledger closing balance with accounts row 2024-06-06 18:42:07 +02:00
Ahmed Bouhuolia
fc9995c4da chore: dump CHANGELOG.md 2024-06-06 12:32:31 +02:00
Ahmed Bouhuolia
7dc769004d fix: billing variant id 2024-06-06 11:19:19 +02:00
Ahmed Bouhuolia
5dbfd36415 feat: optimize the style of general ledger sub-accounts rows 2024-06-05 22:42:12 +02:00
Ahmed Bouhuolia
044f11ff74 feat: general ledger sub-accounts 2024-06-05 21:45:01 +02:00
Ahmed Bouhuolia
6afe1a09c6 fix: Closing balance in general ledger report does not sum the negative figures. 2024-06-04 21:26:46 +02:00
Ahmed Bouhuolia
909a70e2c5 feat: correct the migration files 2024-06-04 17:42:29 +02:00
Ahmed Bouhuolia
84dd0fa86b Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-06-04 16:22:07 +02:00
Ahmed Bouhuolia
a4719fe15b fix: add Plaid env variables to docker-compose.prod file 2024-06-04 16:21:49 +02:00
Ahmed Bouhuolia
fd915b503f fix: Run migrations only for initialized tenants (#484) 2024-06-04 16:13:18 +02:00
Ahmed Bouhuolia
bbba54c08e fix: validate the s3 configures exist (#482) 2024-06-04 15:11:21 +02:00
Ahmed Bouhuolia
f241e2bede fix: Plaid syncs deposit imports as withdrawals (#481) 2024-06-03 21:56:29 +02:00
Ahmed Bouhuolia
175bc243f3 fix: Organize Plaid env variables for development and sandbox envs (#480) 2024-06-03 20:50:02 +02:00
Ahmed Bouhuolia
7c06c8bb8a fix: Lemon Squeezy redirect to base url (#479)
fix: Lemon Squeezy redirect to base url
2024-06-03 19:54:40 +02:00
Ahmed Bouhuolia
8fd930caac Merge pull request #478 from bigcapitalhq/virtual-docker-internal-network
feat: Internal docker virtual network
2024-06-02 21:25:55 +02:00
Ahmed Bouhuolia
e175307da4 feat: internal docker virtual network 2024-06-02 21:25:15 +02:00
Ahmed Bouhuolia
b1bf932e88 fix: add S3 env variables to docker-compose prod 2024-06-02 17:46:10 +02:00
Ahmed Bouhuolia
aa897212ab Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-06-02 17:35:49 +02:00
Ahmed Bouhuolia
890903e08b chore: change the variant id. 2024-06-02 17:34:52 +02:00
Ahmed Bouhuolia
16b2a33cf6 Merge pull request #476 from bigcapitalhq/abouolia-patch-1
Build and deploy develop Docker container
2024-06-02 16:57:36 +02:00
Ahmed Bouhuolia
382d4ab028 Build and deploy develop Docker container 2024-06-02 16:57:07 +02:00
Ahmed Bouhuolia
85f26e1079 Merge pull request #460 from bigcapitalhq/print-resources
feat: Export resource tables to pdf
2024-06-02 13:24:43 +02:00
Ahmed Bouhuolia
1b237323f6 feat: document export functions 2024-06-02 13:24:21 +02:00
Ahmed Bouhuolia
f15fecde54 feat: resource tables printing 2024-06-02 13:15:56 +02:00
Ahmed Bouhuolia
79be4266bb feat: style resource printable columns 2024-05-31 18:12:09 +02:00
Ahmed Bouhuolia
08ad117331 feat: migrate the printing from Attachment to Document model 2024-05-31 15:32:51 +02:00
Ahmed Bouhuolia
958f78e7a4 feat: improve UI experience of resource priting 2024-05-31 15:30:49 +02:00
Ahmed Bouhuolia
ba77351e44 Merge branch 'develop' into print-resources 2024-05-30 19:50:05 +02:00
Ahmed Bouhuolia
09a15966f0 Merge pull request #459 from bigcapitalhq/skip-sending-confirm-email-if-disabled
fix: skip send confirmation email if disabled
2024-05-30 18:38:17 +02:00
Ahmed Bouhuolia
7ff36e8c4f feat: add s3 env variables to .env.example 2024-05-30 17:58:58 +02:00
Ahmed Bouhuolia
8ed24748ec Merge pull request #461 from bigcapitalhq/big-157-attach-documents-to-transactions
feat: upload and attach documents
2024-05-30 17:47:53 +02:00
Ahmed Bouhuolia
6a6dcadaf9 fix: TS types 2024-05-30 17:47:27 +02:00
Ahmed Bouhuolia
308a4f62ae feat: catch exceptions link attachment service 2024-05-29 22:15:31 +02:00
Ahmed Bouhuolia
6e50de1d28 feat: style tweaks to upload attachments popover 2024-05-29 19:03:21 +02:00
Ahmed Bouhuolia
ceb133e29a feat: getting presigned url of the uploaded attachment 2024-05-29 16:16:08 +02:00
Ahmed Bouhuolia
e7871e34a9 feat: wip upload attachmentsx 2024-05-29 13:58:08 +02:00
Ahmed Bouhuolia
cfdbcea9c0 feat: wip UI upload attachments 2024-05-28 23:34:51 +02:00
Ahmed Bouhuolia
fcd61c6159 feat: wip UI attachments 2024-05-28 15:59:15 +02:00
Ahmed Bouhuolia
2244cc6116 feat: wip attach attachments to resource models 2024-05-26 21:59:39 +02:00
Ahmed Bouhuolia
15dbc4137c Merge branch 'develop' into big-157-attach-documents-to-transactions 2024-05-24 20:22:43 +02:00
Ahmed Bouhuolia
8f904fae3a feat: link and unlink document to resource model 2024-05-24 19:50:06 +02:00
Ahmed Bouhuolia
c8f31f33be feat: wip upload documents 2024-05-24 14:28:21 +02:00
Ahmed Bouhuolia
dc5bdf0b66 feat: print action when click on print button 2024-05-23 19:39:23 +02:00
Ahmed Bouhuolia
fe41f7976d feat: export resource tables to pdf 2024-05-23 14:23:49 +02:00
Ahmed Bouhuolia
2c7da86a00 fix: skip send confirmation email if disabled 2024-05-23 10:09:48 +02:00
Ahmed Bouhuolia
8c2b9fba29 fix: add the signup email confirmation env var (#458) 2024-05-22 21:10:56 +02:00
Ahmed Bouhuolia
7208b8fab5 feat: add the missing Newrelic env vars to docker-compose.prod file (#457) 2024-05-22 20:42:00 +02:00
Ahmed Bouhuolia
0836fe14e0 feat: handle http exceptions (#456) 2024-05-22 19:30:41 +02:00
Ahmed Bouhuolia
1227111fae fix: typo 2024-05-18 15:44:53 +02:00
Ahmed Bouhuolia
2ada57a2b4 fix: auto-increment setting parsing. (#453) 2024-05-17 12:49:04 +02:00
Ahmed Bouhuolia
e380c598d3 fix: Showing the real mail address on email confirmation view (#445)
* fix: Showing the real mail address on email confirmation view

* chore: remove the unused hook
2024-05-15 14:03:58 +02:00
allcontributors[bot]
370a8a4b91 docs: add ccantrell72 as a contributor for bug (#441)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-05-13 08:42:27 +02:00
Chris Cantrell
c9ba9500cc fix: typo in setup wizard (#440)
Fixed spelling error
2024-05-13 08:39:51 +02:00
Ahmed Bouhuolia
23d27cafc1 fix: Update Dockerfile 2024-05-10 11:36:01 +02:00
Ahmed Bouhuolia
92e3d31360 fix: use server container from Docker registry instead 2024-05-09 20:10:49 +02:00
Ahmed Bouhuolia
8aefa7709c feat: Combine arm64 and amd64 in one Github action runner (#437) 2024-05-09 19:25:51 +02:00
Ahmed Bouhuolia
3020295841 feat: Create a manifest list for webapp Docker image and push it to DockerHub. (#436) 2024-05-09 15:37:07 +02:00
Ahmed Bouhuolia
7f31a48755 chore: dump the CHANGELOG.md file 2024-05-06 23:38:11 +02:00
Ahmed Bouhuolia
8c0ef61038 hotfix: add the pnpm-workspace.yaml file to Dockerfile 2024-05-06 22:28:13 +02:00
Ahmed Bouhuolia
76bb82f2b4 chore: update the pnpm-lock file 2024-05-06 22:25:45 +02:00
Ahmed Bouhuolia
4c0dc276dd hotfix: rollback the pnpm-workspace.yaml file 2024-05-06 22:04:15 +02:00
Ahmed Bouhuolia
a69c4b4067 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-05-06 21:13:06 +02:00
Ahmed Bouhuolia
d81e544e82 chore: update pnpm-lock.yaml version 2024-05-06 21:11:39 +02:00
Ahmed Bouhuolia
6eeda23559 Update README.md 2024-05-06 20:04:21 +02:00
Ahmed Bouhuolia
cd046cbe27 hotbug: remove the pnpm-workspace.yaml from Dockerfile 2024-05-06 17:55:26 +02:00
Ahmed Bouhuolia
9b7bc1e5b9 Merge branch 'main' into develop 2024-05-06 17:51:24 +02:00
Ahmed Bouhuolia
987341ed29 Merge branch 'main' into develop 2024-05-06 17:50:02 +02:00
Ahmed Bouhuolia
d7cad17f1b Merge pull request #426 from bigcapitalhq/big-163-user-email-verification-after-signing-up
feat: User email verification after signing-up.
2024-05-06 17:46:26 +02:00
Ahmed Bouhuolia
dd02ae471e feat: redirect to the setup page if user is verified 2024-05-06 17:45:32 +02:00
Ahmed Bouhuolia
a5bfb0b02b fix: the email confirmation link on mail message 2024-05-06 17:36:36 +02:00
Ahmed Bouhuolia
f4440c9a03 feat: ability to enable/disable email confirmation from env variables 2024-05-03 18:30:19 +02:00
Ahmed Bouhuolia
cb88c234d1 feat: sync the isVerified state of authed user 2024-05-03 16:00:31 +02:00
Ahmed Bouhuolia
f6a0476fb4 feat: bump CHANGELOG 2024-05-03 11:28:11 +02:00
Ahmed Bouhuolia
63cdc45295 Merge pull request #430 from bigcapitalhq/export-resources
feat: Export resource data to csv, xlsx
2024-05-02 16:20:20 +02:00
Ahmed Bouhuolia
83a5010dc5 feat: flatten the nested columns of exported data 2024-05-02 15:38:57 +02:00
Ahmed Bouhuolia
55aab76c9b feat: configure columns of resources export 2024-05-01 20:15:35 +02:00
Ahmed Bouhuolia
495941f43a feat: style the export dialog form 2024-05-01 19:09:53 +02:00
Ahmed Bouhuolia
00a1e070c6 feat: export dialog on resource table 2024-05-01 16:26:10 +02:00
Ahmed Bouhuolia
fab71d2b65 feat: wip configure resources to be exportable 2024-05-01 12:45:24 +02:00
Ahmed Bouhuolia
9504bb5ccd fix: Running migration Docker container on Windows (#432) 2024-05-01 00:52:23 +02:00
Ahmed Bouhuolia
7e89966f20 feat: wip export resource data 2024-05-01 00:20:13 +02:00
Ahmed Bouhuolia
8a96c41258 feat: export resource data to csv, xlsx 2024-04-29 23:45:11 +02:00
Vederis Leunardus
4a713980bf feat: Pushing docker containers by version tag (#421)
* feat: build docker image based on tag version

* refactor: tabbing

* refactor: revert conflict
2024-04-29 11:39:47 +02:00
Ahmed Bouhuolia
2a1cbf6ced Merge branch 'main' into develop 2024-04-28 18:18:31 +02:00
Ahmed Bouhuolia
9103b60653 feat: New Relic tracking (#429) 2024-04-28 18:12:59 +02:00
Ahmed Bouhuolia
b9fc0cdd9e feat: wip email confirmation 2024-04-28 17:51:11 +02:00
Ahmed Bouhuolia
4368c18479 feat: User email verification after signing-up. 2024-04-26 12:21:40 +02:00
Ahmed Bouhuolia
7e5c6b6487 hotfix: parse the mail secure env variable (#424) 2024-04-24 21:09:56 +02:00
Ahmed Bouhuolia
b7214044bb Merge branch 'main' into develop 2024-04-24 20:05:49 +02:00
Ahmed Bouhuolia
93cb3615c3 Merge branch 'main' into develop 2024-04-24 20:05:33 +02:00
Ahmed Bouhuolia
7abfa6a162 feat: ability to enable/disable the bank connect feature (#423) 2024-04-24 20:01:04 +02:00
Ahmed Bouhuolia
1372a1f0a8 hotfix: fix the subscription plan when subscribe on cloud (#422) 2024-04-24 15:30:36 +02:00
allcontributors[bot]
484024ec28 docs: add cloudsbird as a contributor for code (#418)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-23 19:37:47 +02:00
Vederis Leunardus
46639c7b86 feat: Update Docker Build-Push Action and Add ARM64 Support (#412)
* feat: build for arm64 platform

* fix: typo and add buildx and qemu

* feat: update the docker login-action version

* feat: update the docker login-action version

* feat: add the digest
2024-04-23 19:33:27 +02:00
allcontributors[bot]
d10d1654c1 docs: add benpsnyder as a contributor for code (#417)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-22 10:21:09 +02:00
Ahmed Bouhuolia
2f06070ecb Merge pull request #414 from benpsnyder/feat/upgrade-to-latest-lerna
feat(repo): upgrade to latest lerna v8 and pnpm v9
2024-04-22 10:16:50 +02:00
Ahmed Bouhuolia
deefdb9bfd fix: update pnpm-lock.yaml file 2024-04-22 10:04:18 +02:00
Ben Snyder
3cc62d80de fix(repo): replace usage of yarn with pnpm/pnpx 2024-04-21 20:34:35 -04:00
Ben Snyder
4962c5d4d3 feat(repo): upgrade to latest lerna v8 and pnpm v9 2024-04-21 20:29:38 -04:00
Ahmed Bouhuolia
571a332658 Merge pull request #410 from bigcapitalhq/seed-free-subscription-to-tenants
feat: seed free subscription to tenants that have no subscription.
2024-04-19 09:36:35 +02:00
Ahmed Bouhuolia
b44c318a5d feat: seed free subscription to tenants that have no subscription. 2024-04-19 09:34:47 +02:00
Ahmed Bouhuolia
bd9717f4dc chore: Add Bigcapital Cloud link on README.md file 2024-04-17 19:51:05 +02:00
Ahmed Bouhuolia
f48aea8e5a feat: add the new env variables to docker compose 2024-04-17 19:21:35 +02:00
Ahmed Bouhuolia
0ac3a5dea9 fix: add /imports directory to storage dir 2024-04-17 18:25:17 +02:00
Ahmed Bouhuolia
56b40ad4cb fix: TS and linit errors 2024-04-17 17:51:21 +02:00
Ahmed Bouhuolia
9b6f934990 fix: add @lemonsqueezy/lemonsqueezy package dependencies. 2024-04-17 17:44:35 +02:00
Ahmed Bouhuolia
80e3522f8a Merge pull request #408 from bigcapitalhq/fix-import-store-absolute-path
fix: absolute storage imports path.
2024-04-17 17:37:39 +02:00
Ahmed Bouhuolia
7975643765 fix: absolute storage imports path. 2024-04-17 17:36:35 +02:00
Ahmed Bouhuolia
2ac7f86bdb Merge pull request #407 from bigcapitalhq/auto-subscribe-free
chore: add default value to env variable
2024-04-16 21:24:49 +02:00
Ahmed Bouhuolia
956b9b6812 chore: add default value to env variable 2024-04-16 21:22:40 +02:00
Ahmed Bouhuolia
60248ec3f6 Merge pull request #406 from bigcapitalhq/auto-subscribe-free
feat: auto subscribe to free plan once signup on community version.
2024-04-16 20:58:29 +02:00
Ahmed Bouhuolia
9d3f1541eb feat: auto subscribe to free plan once signup on community version. 2024-04-16 20:57:05 +02:00
Ahmed Bouhuolia
9b5f1a36ab Merge branch 'main' into develop 2024-04-16 12:58:22 +02:00
Ahmed Bouhuolia
8ee691e1ed Merge pull request #405 from bigcapitalhq/subscription-page-content
feat: subscription page content
2024-04-16 12:55:57 +02:00
Ahmed Bouhuolia
f9cb14da9e feat: subscription page content 2024-04-16 12:54:36 +02:00
Ahmed Bouhuolia
5e87581f4e hotfix: creating a vendor 2024-04-15 22:48:54 +02:00
Ahmed Bouhuolia
8ca9cf39da Merge pull request #404 from bigcapitalhq/optimize-ui-onboarding
feat: optimize the onboarding subscription experience.
2024-04-15 14:53:52 +02:00
Ahmed Bouhuolia
9001fea524 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-04-15 14:51:19 +02:00
Ahmed Bouhuolia
dea0d71732 Merge pull request #402 from bigcapitalhq/lemon-squeezy-payment
feat: Integrate Lemon Squeezy payment
2024-04-15 14:49:39 +02:00
Ahmed Bouhuolia
c191c4bd26 feat: remove other payment methods 2024-04-15 14:49:27 +02:00
Ahmed Bouhuolia
47d82ce591 feat: optimize the onboarding subscription experience. 2024-04-15 12:48:16 +02:00
Ahmed Bouhuolia
9321db2a3a feat: sweep up lemon squeezy webhooks. 2024-04-14 12:58:53 +02:00
Ahmed Bouhuolia
e486333c96 feat: sweep up the Lemon Squeezy integration 2024-04-14 12:44:02 +02:00
Ahmed Bouhuolia
a9748b23c0 feat: listen LemonSqueezy webhooks 2024-04-14 11:55:36 +02:00
Ahmed Bouhuolia
693ae61141 feat: integrate LemonSqueezy to subscription payment 2024-04-14 10:33:29 +02:00
Ahmed Bouhuolia
9807ac04b0 Revert "feat(webapp): deprecate the subscription step in onboarding process"
This reverts commit 0c1bf302e5.
2024-04-13 15:18:59 +02:00
Ahmed Bouhuolia
bddfde4138 Revert "feat(webapp): deprecate the subscription step in onboarding process"
This reverts commit 0c1bf302e5.
2024-04-13 14:07:32 +02:00
Ahmed Bouhuolia
a39dcd00d5 Revert "feat(server): deprecated the subscription module."
This reverts commit 3b79ac66ae.
2024-04-13 11:05:53 +02:00
Ahmed Bouhuolia
4d616e9287 Revert "feat(server): deprecated the subscription module."
This reverts commit 44fc26b156.
2024-04-13 10:17:48 +02:00
Ahmed Bouhuolia
dc52fb1de5 fix: lint error 2024-04-09 22:24:03 +02:00
Ahmed Bouhuolia
21a1777424 Merge branch 'release/v0.15.2' into develop 2024-04-09 22:11:10 +02:00
Ahmed Bouhuolia
16b721db91 Merge pull request #401 from bigcapitalhq/import-fields-hints
feat: add hints to import fields
2024-04-09 22:01:16 +02:00
Ahmed Bouhuolia
2baa667c5d fix(webapp): hotfix pdf request hook 2024-03-19 05:22:15 +02:00
1618 changed files with 82255 additions and 56413 deletions

View File

@@ -105,6 +105,69 @@
"contributions": [
"bug"
]
},
{
"login": "benpsnyder",
"name": "Ben Snyder",
"avatar_url": "https://avatars.githubusercontent.com/u/707567?v=4",
"profile": "https://snyder.tech",
"contributions": [
"code"
]
},
{
"login": "cloudsbird",
"name": "Vederis Leunardus",
"avatar_url": "https://avatars.githubusercontent.com/u/13505006?v=4",
"profile": "http://vederis.id",
"contributions": [
"code"
]
},
{
"login": "ccantrell72",
"name": "Chris Cantrell",
"avatar_url": "https://avatars.githubusercontent.com/u/104120598?v=4",
"profile": "http://www.pivoten.com",
"contributions": [
"bug"
]
},
{
"login": "oleynikd",
"name": "Denis",
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
"profile": "https://github.com/oleynikd",
"contributions": [
"bug"
]
},
{
"login": "mittalsam98",
"name": "Sachin Mittal",
"avatar_url": "https://avatars.githubusercontent.com/u/42431274?v=4",
"profile": "https://myself.vercel.app/",
"contributions": [
"bug"
]
},
{
"login": "Champetaman",
"name": "Camilo Oviedo",
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
"profile": "https://www.camilooviedo.com/",
"contributions": [
"code"
]
},
{
"login": "nklmantey",
"name": "Mantey",
"avatar_url": "https://avatars.githubusercontent.com/u/90279429?v=4",
"profile": "https://nklmantey.com/",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,

View File

@@ -48,6 +48,9 @@ SIGNUP_DISABLED=false
SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS=
# Sign-up Email Confirmation
SIGNUP_EMAIL_CONFIRMATION=false
# API rate limit (points,duration,block duration).
API_RATE_LIMIT=120,60,600
@@ -72,26 +75,28 @@ PLAID_ENV=sandbox
# Your Plaid keys, which can be found in the Plaid Dashboard.
# https://dashboard.plaid.com/account/keys
PLAID_CLIENT_ID=
PLAID_SECRET_DEVELOPMENT=
PLAID_SECRET_SANDBOX=
PLAID_SECRET=
PLAID_LINK_WEBHOOK=
# (Optional) Redirect URI settings section
# Only required for OAuth redirect URI testing (not common on desktop):
# Sandbox Mode:
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
# The OAuth redirect flow requires an endpoint on the developer's website
# that the bank website should redirect to. You will also need to configure
# this redirect URI for your client ID through the Plaid developer dashboard
# at https://dashboard.plaid.com/team/api.
# Development mode:
# When running in development mode, you must use an https:// url.
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
# Instructions to create a self-signed certificate for localhost can be found at
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
# If your system is not set up to run localhost with https://, you will be unable to test
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
LEMONSQUEEZY_API_KEY=
LEMONSQUEEZY_STORE_ID=
LEMONSQUEEZY_WEBHOOK_SECRET=
PLAID_SANDBOX_REDIRECT_URI=
PLAID_DEVELOPMENT_REDIRECT_URI=
# S3 documents and attachments
S3_REGION=US
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_ENDPOINT=
S3_BUCKET=
# PostHog
POSTHOG_API_KEY=
POSTHOG_HOST=
# Stripe Payment
STRIPE_PAYMENT_SECRET_KEY=
STRIPE_PAYMENT_PUBLISHABLE_KEY=
STRIPE_PAYMENT_CLIENT_ID=
STRIPE_PAYMENT_WEBHOOKS_SECRET=
STRIPE_PAYMENT_REDIRECT_URL=

View File

@@ -6,43 +6,66 @@ on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp
SERVER_IMAGE_NAME: bigcapital/bigcapital-server
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
SERVER_IMAGE_NAME: bigcapitalhq/server
jobs:
build-publish-webapp:
strategy:
fail-fast: false
name: Build and deploy webapp container
runs-on: ubuntu-latest
environment: production
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }}
images: ${{ env.WEBAPP_IMAGE_NAME }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
id: build
with:
context: .
file: ./packages/webapp/Dockerfile
push: true
tags: ghcr.io/bigcapitalhq/webapp:latest
labels: ${{ steps.meta.outputs.labels }}
context: ./
file: ./packages/webapp/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-webapp
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published webapp container successfully.
uses: rtCamp/action-slack-notify@v2
@@ -53,29 +76,52 @@ jobs:
name: Build and deploy server container
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
id: build
with:
context: ./
file: ./packages/server/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/bigcapitalhq/server:latest
tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}}
labels: ${{ steps.meta.outputs.labels }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-server
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published server container successfully.
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -0,0 +1,127 @@
# This workflow will build a docker container, publish it to Github Registry.
name: Build and Deploy Develop Docker Container
on:
push:
branches:
- develop
env:
WEBAPP_IMAGE_NAME: bigcapitalhq/webapp
SERVER_IMAGE_NAME: bigcapitalhq/server
jobs:
build-publish-webapp:
strategy:
fail-fast: false
name: Build and deploy webapp container
runs-on: ubuntu-latest
environment: production
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.WEBAPP_IMAGE_NAME }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v5
id: build
with:
context: ./
file: ./packages/webapp/Dockerfile
platforms: linux/amd64
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: bigcapitalhq/webapp:develop
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-webapp
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published webapp container successfully.
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
build-publish-server:
name: Build and deploy server container
runs-on: ubuntu-latest
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Container registry.
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Builds and push the Docker image.
- name: Build and push Docker image
uses: docker/build-push-action@v5
id: build
with:
context: ./
file: ./packages/server/Dockerfile
platforms: linux/amd64
push: true
tags: bigcapitalhq/server:develop
labels: ${{ steps.meta.outputs.labels }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-server
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Send notification to Slack channel.
- name: Slack Notification built and published server container successfully.
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -8,14 +8,14 @@ on:
- '**.ts'
- '**.tsx'
- '**/tsconfig.json'
- 'yarn.lock'
- 'pnpm-lock.yaml'
- '.github/workflows/e2e.yml'
pull_request:
paths:
- '**.ts'
- '**.tsx'
- '**/tsconfig.json'
- 'yarn.lock'
- 'pnpm-lock.yaml'
- '.github/workflows/e2e.yml'
defaults:

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn commitlint --edit
pnpx commitlint --edit

View File

@@ -2,6 +2,281 @@
All notable changes to Bigcapital server-side will be in this file.
# [0.20.5]
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
* fix: Invoice form layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/705
* refactor: Invoice, estimate, receipt, credit note and payment received date input fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/707
* feat: Add customize templates button to edit forms by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/708
* feat: Track account, invoice and item viewed events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/709
# [0.20.4]
* fix: Delete company logo from the PDF template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/699
* fix: Set max width/height to company logo of pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/700
# [0.20.3]
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
# [0.20.2]
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
# [0.20.1]
* fix: Getting uploaded object uri by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/684
# [0.20.0]
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
* feat: Onboard accounts to Stripe Connect by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/668
* feat: Upload company logo to invoice templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/670
* fix: Invoice pdf customize by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/672
* fix: Invoice customize bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/673
* feat: Clean up payment links endpoints by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/674
* feat: Hook up company logo to server-side pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/675
* feat: Company branding preferences by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/677
* feat: Pdf templates customer/company addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/678
* fix: Listen to Stripe session completed event by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/679
* feat: Track pdf templates Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/680
* fix: Branding customize content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/681
* feat: Listen to Stripe integration events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/682
* feat: Hook up customer/company address to invoice preview of payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/683
# [0.19.17]
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
# [0.19.16]
* feat: Tracking more Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/653
* fix: Expense cannot accept credit card as payment account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/654
* fix: Suspense the lazy loaded components in banking pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/657
* feat: Add help dropdown menu by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/656
* feat: Bank pages layout breaking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/658
* feat: Datatable UI improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/655
* fix: Array cast of recognize function rule ids by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/660
* fix: Payment made filling the form full amount field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/661
* feat: Tabular number of all money columns by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/659
* refactor: The expense G/L writer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/662
## [0.19.15] -
* fix: Bank transactions infinity scrolling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/648
* feat: Integrate multiple branches and warehouses to resource importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/645
* fix: Integrate multiple branches with expense resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/649
* feat: Cover more tracking events. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/650
* feat: Track banking service events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/651
## [0.19.14]
* fix: Import bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/643
* fix: Set default index to transaction entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/644
* feat(server): Events tracking using Posthog by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/646
## [0.19.13]
* fix: Subscription middleware by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/624
* fix: Getting the sheet columns in import sheet by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/641
## [0.19.12]
* fix: Typo one-click demo page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/640
## [0.19.11]
* fix: Avoid running the cost job in import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/635
* fix: Debounce scheduling calculating items cost by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/634
* fix: Expand the resources export page size limitation by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/636
* feat: Optimize loading perf. by splitting big chunks and lazy loading them by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/632
* fix: Use standard ISO 8601 format for exported data by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/638
* fix: Add customer type to customers resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/639
## [0.19.10]
* fix: Add subscription plans offer text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/629
## [0.19.9]
* fix: Make webapp package env variables dynamic by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/628
## [v0.19.8]
* fix: Cannot import items income and cost accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/617
* fix: Some bank account details hidden by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/618
* feat(ee): One-click demo account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/616
* feat: change banking service language by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/619
* feat(banking): Filter uncategorized bank transactions by date by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/590
* Fix: Syntax error caused error by @wolone in https://github.com/bigcapitalhq/bigcapital/pull/622
* fix: Listen to payment webhooks by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/623
* fix: Add prefix J-00001 to manual journals increments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/625
* fix: Disable sms service until Twilo integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/626
* fix: Style tweaks in onboarding page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/627
## [0.19.5] - 18-08-2024
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
## [v0.19.4]
* feat: Import and export tax rates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/591
* feat: Un-categorize bank transactions in bulk by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/587
* feat: Pending bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/589
* fix: Update `dev` Script in `package.json` to Use `cross-env` by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/588
* fix: Should not load branches on reconcile matching form if the branches not enabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/592
* fix: Rounding the total amount the pending and matched transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/593
## [v0.18.0] - 10-08-2024
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
* feat: Categorize & match bank transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
* feat: Reconcile match transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/522
* fix: Issues in matching transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/523
* fix: Cashflow transactions types by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/524
## [v0.17.5] - 17-06-2024
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
* fix: add space between buttons on floating actions bar by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/508
* feat: Migrating to Envoy proxy instead of Nginx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/509
* fix: Disable email confirmation does not work with invited users by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/497
* feat: Setting up the date format in the whole system dates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/506
## [0.17.0] - 04-06-2024
### New
* feat: Upload and attach documents by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/461
* feat: Export resource tables to pdf by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/460
* feat: Build and deploy develop Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/476
* feat: Internal docker virtual network by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/478
### Fixes
* fix: Skip send confirmation email if disabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/459
* fix: Lemon Squeezy redirect to base url by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/479
* fix: Organize Plaid env variables for development and sandbox envs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/480
* fix: Plaid syncs deposit imports as withdrawals by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/481
* fix: Validate the s3 configures exist by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/482
* fix: Run migrations only for initialized tenants by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/484
## [0.16.16] -
* feat: handle http exceptions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/456
* feat: add the missing Newrelic env vars to docker-compose.prod file by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/457
* fix: add the signup email confirmation env var by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/458
## [0.16.14] -
* fix: Typo in setup wizard by @ccantrell72 in https://github.com/bigcapitalhq/bigcapital/pull/440
* fix: Showing the real mail address on email confirmation view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/445
* fix: Auto-increment setting parsing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/453
## [0.16.12] -
* feat: Create a manifest list for `webapp` Docker image and push it to DockerHub. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/436
* feat: Combine arm64 and amd64 in one Github action runner by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/437
## [0.16.11] - 06-05-2024
### improvements
* feat: Export resource data to csv, xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/430
* feat: User email verification after signing-up. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/426
### Fixes
* feat(repo): upgrade to latest lerna v8 and pnpm v9 by @benpsnyder in https://github.com/bigcapitalhq/bigcapital/pull/414
* feat: Update Docker Build-Push Action and Add ARM64 Support by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/412
* feat: Pushing docker containers by version tag by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/421
## [0.16.10]
* fix: Running migration Docker container on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/432
## [0.16.9]
* feat: New Relic for tracking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/429
## [0.16.8]
* feat: Ability to enable/disable the bank connect feature by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/423
## [0.16.6]
* hotfix: fix the subscription plan when subscribe on cloud by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/422
## [0.16.5]
IMPORTANT: If you upgraded to the v0.16 recently you should upgrade to v0.16.4 as soon as possible, because there're some breaking changes affected the sign-in and some users reported couldn't sign-in.
* feat: Seed free subscription to tenants that have no subscription. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/410
## [0.16.3]
* feat: Integrate Lemon Squeezy payment by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/402
* feat: optimize the onboarding subscription experience. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/404
* feat: subscription page content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/405
* feat: auto subscribe to free plan once signup on community version. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/406
* chore: add default value to env variable by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/407
* fix: absolute storage imports path. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/408
## [0.16.0]
* feat: add convert to invoice button on estimate drawer toolbar by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/361
* feat(webapp): add mark as delivered to action bar of invoice details … by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/360
* feat(webapp): Dialog to choose the bank service provider by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/378
* feat: Categorize the bank synced transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/377
* feat: uncategorize the cashflow transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/381
* Import resources from csv/xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/382
* feat(webapp): import resource UI by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/386
* fix: import resources improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/388
* feat: add sample sheet to accounts and bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/389
* fix: show the unique row value in the import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/392
* feat: advanced parser for numeric and boolean import values by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/394
* feat: validate the given imported sheet whether is empty by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/395
* feat: linking relation with id in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/393
* feat: Aggregate rows import by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/396
* feat: clean up the imported temp files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/400
* feat: add hints to import fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/401
## [0.15.0]
* feat: Printing financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/363
* feat: Convert invoice status after sending mail notification by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/332
* feat: Bigcapital <> Plaid Integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/346
* fix: Broken transactions by vendor report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/369
* fix: Optimize the print style some financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/370
## [0.14.0] - 30-01-2024
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327

View File

@@ -1,6 +1,6 @@
<p align="center">
<p align="center">
<a href="https://bigcapital.ly" target="_blank">
<a href="https://bigcapital.app" target="_blank">
<img src="https://raw.githubusercontent.com/abouolia/blog/main/public/bigcapital.svg" alt="Bigcapital" width="280" height="75">
</a>
</p>
@@ -12,6 +12,9 @@
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
</a>
<a href="https://hub.docker.com/u/bigcapitalhq">
<img src="https://img.shields.io/docker/pulls/bigcapitalhq/webapp" />
</a>
<a href="https://discord.com/invite/c8nPBJafeb">
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
</a>
@@ -25,6 +28,10 @@
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
</a>
</p>
<p align="center">
<a href="https://my.bigcapital.app">Bigcapital Cloud</a>
</p>
</p>
# What's Bigcapital?
@@ -47,7 +54,7 @@ Bigcapital is available open-source under AGPL license. You can host it on your
### Docker
To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.ly/deployment/docker).
To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.app/deployment/docker).
## Development
@@ -70,7 +77,7 @@ You can integrate Bigcapital API with your system to organize your transactions
# Resources
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
- [Documentation](https://docs.bigcapital.app/) - Learn how to use.
- [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions.
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
@@ -118,6 +125,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ANasouf"><img src="https://avatars.githubusercontent.com/u/19536487?v=4?s=100" width="100px;" alt="ANasouf"/><br /><sub><b>ANasouf</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=ANasouf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ragnarlaud.dev"><img src="https://avatars.githubusercontent.com/u/3042904?v=4?s=100" width="100px;" alt="Ragnar Laud"/><br /><sub><b>Ragnar Laud</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Axprnio" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://snyder.tech"><img src="https://avatars.githubusercontent.com/u/707567?v=4?s=100" width="100px;" alt="Ben Snyder"/><br /><sub><b>Ben Snyder</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=benpsnyder" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
</tr>
</tbody>
</table>

View File

@@ -3,38 +3,31 @@
version: '3.3'
services:
nginx:
container_name: bigcapital-nginx-gateway
build:
context: ./docker/nginx
args:
- SERVER_PROXY_PORT=3000
- WEB_SSL=false
- SELF_SIGNED=false
volumes:
- ./data/logs/nginx/:/var/log/nginx
- ./docker/certbot/certs/:/var/certs
proxy:
image: envoyproxy/envoy:v1.30-latest
depends_on:
- server
- webapp
ports:
- '${PUBLIC_PROXY_PORT:-80}:80'
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
tty: true
depends_on:
- server
- webapp
deploy:
restart_policy:
condition: unless-stopped
volumes:
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
restart: on-failure
networks:
- bigcapital_network
webapp:
container_name: bigcapital-webapp
image: ghcr.io/bigcapitalhq/webapp:latest
deploy:
restart_policy:
condition: unless-stopped
image: bigcapitalhq/webapp:latest
restart: on-failure
networks:
- bigcapital_network
server:
container_name: bigcapital-server
image: ghcr.io/bigcapitalhq/server:latest
image: bigcapitalhq/server:latest
expose:
- '3000'
links:
@@ -45,9 +38,9 @@ services:
- mysql
- mongo
- redis
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
networks:
- bigcapital_network
environment:
# Mail
- MAIL_HOST=${MAIL_HOST}
@@ -88,10 +81,48 @@ services:
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
# Sign-up email confirmation
- SIGNUP_EMAIL_CONFIRMATION=${SIGNUP_EMAIL_CONFIRMATION}
# Gotenberg (Pdf generator)
- GOTENBERG_URL=${GOTENBERG_URL}
- GOTENBERG_DOCS_URL=${GOTENBERG_DOCS_URL}
# Exchange Rate
- EXCHANGE_RATE_SERVICE=${EXCHANGE_RATE_SERVICE}
- OPEN_EXCHANGE_RATE_APP_ID-${OPEN_EXCHANGE_RATE_APP_ID}
# Bank Sync
- BANKING_CONNECT=${BANKING_CONNECT}
# Plaid
- PLAID_ENV=${PLAID_ENV}
- PLAID_CLIENT_ID=${PLAID_CLIENT_ID}
- PLAID_SECRET=${PLAID_SECRET}
- PLAID_LINK_WEBHOOK=${PLAID_LINK_WEBHOOK}
# Lemon Squeez
- LEMONSQUEEZY_API_KEY=${LEMONSQUEEZY_API_KEY}
- LEMONSQUEEZY_STORE_ID=${LEMONSQUEEZY_STORE_ID}
- LEMONSQUEEZY_WEBHOOK_SECRET=${LEMONSQUEEZY_WEBHOOK_SECRET}
- HOSTED_ON_BIGCAPITAL_CLOUD=${HOSTED_ON_BIGCAPITAL_CLOUD}
# New Relic matrics tracking.
- NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=${NEW_RELIC_DISTRIBUTED_TRACING_ENABLED}
- NEW_RELIC_LOG=${NEW_RELIC_LOG}
- NEW_RELIC_AI_MONITORING_ENABLED=${NEW_RELIC_AI_MONITORING_ENABLED}
- NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED}
- NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED}
- NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}
- NEW_RELIC_APP_NAME=${NEW_RELIC_APP_NAME}
# S3
- S3_REGION=${S3_REGION}
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
- S3_ENDPOINT=${S3_ENDPOINT}
- S3_BUCKET=${S3_BUCKET}
database_migration:
container_name: bigcapital-database-migration
build:
@@ -108,12 +139,12 @@ services:
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
depends_on:
- mysql
networks:
- bigcapital_network
mysql:
container_name: bigcapital-mysql
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build:
context: ./docker/mariadb
environment:
@@ -125,34 +156,38 @@ services:
- mysql:/var/lib/mysql
expose:
- '3306'
networks:
- bigcapital_network
mongo:
container_name: bigcapital-mongo
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build: ./docker/mongo
expose:
- '27017'
volumes:
- mongo:/var/lib/mongodb
networks:
- bigcapital_network
redis:
container_name: bigcapital-redis
deploy:
restart_policy:
condition: unless-stopped
restart: on-failure
build:
context: ./docker/redis
expose:
- '6379'
volumes:
- redis:/data
networks:
- bigcapital_network
gotenberg:
image: gotenberg/gotenberg:7
expose:
- '9000'
networks:
- bigcapital_network
# Volumes
volumes:
@@ -167,3 +202,8 @@ volumes:
redis:
name: bigcapital_prod_redis
driver: local
# Networks
networks:
bigcapital_network:
driver: bridge

62
docker/envoy/envoy.yaml Normal file
View File

@@ -0,0 +1,62 @@
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ['*']
routes:
- match:
prefix: '/api'
route:
cluster: dynamic_server
- match:
prefix: '/'
route:
cluster: webapp
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: dynamic_server
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: dynamic_server
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: server
port_value: 3000
- name: webapp
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: webapp
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: webapp
port_value: 80

View File

@@ -1,4 +1,4 @@
FROM ghcr.io/bigcapitalhq/server:latest as build
FROM bigcapitalhq/server:latest as build
ARG DB_HOST= \
DB_USER= \
@@ -34,7 +34,5 @@ WORKDIR /app/packages/server
RUN git clone https://github.com/vishnubob/wait-for-it.git
ADD docker/migration/start.sh /
RUN chmod +x /start.sh
CMD ["/start.sh"]
# Once we listen the mysql port run the migration task.
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest"

View File

@@ -1,5 +0,0 @@
# Migrate the master system database.
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js system:migrate:latest
# Migrate all tenants.
./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js tenants:migrate:latest

View File

@@ -1,21 +0,0 @@
FROM nginx:1.11
RUN mkdir /etc/nginx/sites-available && rm /etc/nginx/conf.d/default.conf
ADD nginx.conf /etc/nginx/
COPY scripts /root/scripts/
COPY certs /etc/ssl/
COPY sites /etc/nginx/templates
ARG SERVER_PROXY_PORT=3000
ARG WEB_SSL=false
ARG SELF_SIGNED=false
ENV SERVER_PROXY_PORT=$SERVER_PROXY_PORT
ENV WEB_SSL=$WEB_SSL
ENV SELF_SIGNED=$SELF_SIGNED
RUN /bin/bash /root/scripts/build-nginx.sh
CMD nginx

View File

@@ -1,33 +0,0 @@
user www-data;
worker_processes auto;
pid /run/nginx.pid;
daemon off;
events {
worker_connections 2048;
use epoll;
}
http {
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 15;
types_hash_max_size 2048;
client_max_body_size 20M;
open_file_cache max=100;
gzip on;
gzip_disable "msie6";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}

View File

@@ -1,9 +0,0 @@
#!/bin/bash
for conf in /etc/nginx/templates/*.conf; do
mv $conf "/etc/nginx/sites-available/"$(basename $conf) > /dev/null
done
for template in /etc/nginx/templates/*.template; do
envsubst < $template > "/etc/nginx/sites-available/"$(basename $template)".conf"
done

View File

@@ -1,16 +0,0 @@
server {
listen 80 default_server;
location /api {
proxy_pass http://server:${SERVER_PROXY_PORT};
}
location / {
proxy_pass http://webapp;
}
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt/;
log_not_found off;
}
}

View File

@@ -2,6 +2,8 @@
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true,
"packages": ["packages/*"]
}
"packages": [
"packages/*",
"shared/*"
]
}

View File

@@ -4,11 +4,11 @@
"scripts": {
"dev": "lerna run dev",
"build": "lerna run build",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
"build:server": "lerna run build --scope \"@bigcapital/server\"",
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"test:e2e": "playwright test",
"prepare": "husky install"
},
@@ -19,7 +19,8 @@
"@faker-js/faker": "^8.0.2",
"@playwright/test": "^1.32.3",
"husky": "^8.0.3",
"lerna": "^6.4.1"
"lerna": "^8.1.2",
"pnpm": "^9.0.5"
},
"engines": {
"node": "16.x || 17.x || 18.x"
@@ -28,5 +29,8 @@
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"dependencies": {
"tsup": "^8.3.0"
}
}

View File

@@ -3,4 +3,6 @@
stdout.log
/dist
/build
/public/imports
/public/imports
dist

View File

@@ -78,6 +78,9 @@ ENV MAIL_HOST=$MAIL_HOST \
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
# New Relic config file.
ENV NEW_RELIC_NO_CONFIG_FILE=true
# Create app directory.
WORKDIR /app
@@ -87,11 +90,7 @@ RUN chown node:node /
RUN npm install -g pnpm
# Copy application dependency manifests to the container image.
COPY ./package*.json ./
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./lerna.json ./lerna.json
COPY ./packages/server/package*.json ./packages/server/
COPY --chown=node:node ./ ./
# Install application dependencies
RUN apk update
@@ -106,6 +105,6 @@ RUN pnpm install
COPY --chown=node:node ./packages/server ./packages/server
# # 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" ]

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,16 @@
"bigcapital": "./bin/bigcapital.js"
},
"dependencies": {
"@bigcapital/utils": "*",
"@bigcapital/email-components": "*",
"@bigcapital/pdf-templates": "*",
"@aws-sdk/client-s3": "^3.576.0",
"@aws-sdk/s3-request-presigner": "^3.583.0",
"@casl/ability": "^5.4.3",
"@hapi/boom": "^7.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@supercharge/promise-pool": "^3.2.0",
"@types/express": "^4.17.21",
"@types/i18n": "^0.8.7",
"@types/knex": "^0.16.1",
"@types/mathjs": "^6.0.12",
@@ -32,6 +40,7 @@
"agendash": "^3.1.0",
"app-root-path": "^3.0.0",
"async": "^3.2.0",
"async-mutex": "^0.5.0",
"axios": "^1.6.0",
"babel-loader": "^9.1.2",
"bcryptjs": "^2.4.3",
@@ -64,23 +73,25 @@
"is-my-json-valid": "^2.20.5",
"js-money": "^0.6.3",
"jsonwebtoken": "^8.5.1",
"knex": "^0.95.15",
"knex": "^3.1.0",
"knex-cleaner": "^1.3.0",
"knex-db-manager": "^0.6.1",
"libphonenumber-js": "^1.9.6",
"lodash": "^4.17.15",
"lru-cache": "^6.0.0",
"mathjs": "^9.4.0",
"memory-cache": "^0.2.0",
"mime-types": "^2.1.35",
"moment": "^2.24.0",
"moment-range": "^4.0.2",
"moment-timezone": "^0.5.43",
"mongodb": "^6.1.0",
"mongoose": "^5.10.0",
"multer": "1.4.5-lts.1",
"multer-s3": "^3.0.1",
"mustache": "^3.0.3",
"mysql": "^2.17.1",
"mysql2": "^1.6.5",
"newrelic": "^11.15.0",
"node-cache": "^4.2.1",
"nodemailer": "^6.3.0",
"nodemon": "^1.19.1",
@@ -89,28 +100,32 @@
"objection-filter": "^4.0.1",
"objection-soft-delete": "^1.0.7",
"objection-unique": "^1.2.2",
"plaid": "^10.3.0",
"pluralize": "^8.0.0",
"posthog-node": "^4.2.0",
"pug": "^3.0.2",
"puppeteer": "^10.2.0",
"plaid": "^10.3.0",
"qim": "0.0.52",
"ramda": "^0.27.1",
"rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4",
"source-map-loader": "^4.0.1",
"socket.io": "^4.7.4",
"source-map-loader": "^4.0.1",
"stripe": "^16.10.0",
"tmp-promise": "^3.0.3",
"ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0",
"typedi": "^0.8.0",
"uniqid": "^5.2.0",
"uuid": "^10.0.0",
"winston": "^3.2.1",
"xlsx": "^0.18.5",
"yup": "^0.28.1"
},
"devDependencies": {
"@types/lodash": "^4.14.158",
"@types/multer": "^1.4.11",
"@types/ramda": "^0.27.64",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",

View File

@@ -244,6 +244,7 @@
"account.field.active": "Active",
"account.field.currency": "Currency",
"account.field.balance": "Balance",
"account.field.bank_balance": "Bank Balance",
"account.field.parent_account": "Parent Account",
"account.field.created_at": "Created at",
"item.field.type": "Item Type",
@@ -331,7 +332,7 @@
"bill_payment.field.reference_no": "Reference No.",
"bill_payment.field.description": "Description",
"bill_payment.field.exchange_rate": "Exchange Rate",
"bill_payment.field.statement": "Statement",
"bill_payment.field.note": "Note",
"bill_payment.field.entries.bill": "Bill No.",
"bill_payment.field.entries.payment_amount": "Payment Amount",
"bill_payment.field.reference": "Reference No.",
@@ -431,6 +432,7 @@
"vendor.field.created_at": "Created at",
"vendor.field.balance": "Balance",
"vendor.field.status": "Status",
"vendor.field.note": "Note",
"vendor.field.currency": "Currency",
"vendor.field.status.active": "Active",
"vendor.field.status.inactive": "Inactive",

View File

@@ -0,0 +1,40 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
*,
*::before,
*::after {
box-sizing: border-box;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
body{
margin: 0;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #000;
background-color: #fff;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
body, h1, h2, h3, h4, h5, h6{
font-family: "Noto Sans", sans-serif;
font-optical-sizing: auto;
font-style: normal;
}

View File

@@ -1,35 +1 @@
@import "./normalize.scss";
*,
*::before,
*::after {
box-sizing: border-box;
}
th {
text-align: inherit; // 2
text-align: -webkit-match-parent; // 3
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
body{
margin: 0;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
direction: ltr;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}

View File

@@ -0,0 +1,38 @@
@import "../base.scss";
body {
font-family: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 12px;
line-height: 1.4;
margin: 0;
}
.sheet__title{
margin-bottom: 18px;
}
.sheet__title h2{
line-height: 1;
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
}
.sheet__table {
font-size: inherit;
line-height: inherit;
width: 100%;
}
.sheet__table {
table-layout: auto;
border-collapse: collapse;
width: 100%;
}
.sheet__table thead tr th {
border-top: 1px solid #000;
border-bottom: 1px solid #000;
background: #fff;
padding: 8px;
line-height: 1.2;
}
.sheet__table tbody tr td {
padding: 4px 8px;
border-bottom: 1px solid #CCC;
}

View File

@@ -0,0 +1,379 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none;
/* 1 */
text-decoration: underline;
/* 2 */
text-decoration: underline dotted;
/* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@@ -1,6 +1,9 @@
html(lang=locale)
head
title My Site - #{title}
style
include ../scss/normalize.css
include ../scss/base.css
block head
body
div.paper-template

View File

@@ -1,81 +1,227 @@
extends ../PaperTemplateLayout.pug
block head
style
if (isRtl)
include ../../css/modules/credit-rtl.css
else
include ../../css/modules/credit.css
- var prefix = 'bc'
style.
.#{prefix}-root {
color: #111;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {
/* Styles for the term value */
}
.#{prefix}-address-section{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: flex-start;
align-items: flex-start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address-section > * {
flex: 1 1;
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-table__cell--item .item {
display: flex;
flex-direction: column;
gap: 2px;
}
.#{prefix}-table__cell--item .item .item__description{
color: #5f6b7c;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--border-gray {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--border-dark {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--font-weight-bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
margin-bottom: 20px;
}
.#{prefix}-statement__label {
color: #666;
}
.#{prefix}-statement__value {
white-space: pre-line;
}
block content
div.credit
div.credit__header
div.paper
h1.title #{__('credit.paper.credit_note')}
if creditNote.creditNoteNumber
span.creditNoteNumber #{creditNote.creditNoteNumber}
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`) Credit Note
div.organization
h3.title #{organizationName}
if organizationEmail
span.email #{organizationEmail}
div(class=`${prefix}-terms-list`)
if showCreditNoteNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
div.credit__full-amount
div.label #{__('credit.paper.amount')}
div.amount #{creditNote.formattedAmount}
if showCreditNoteDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
div.credit__meta
div.credit__meta-item.credit__meta-item--amount
span.label #{__('credit.paper.remaining')}
span.value #{creditNote.formattedCreditsRemaining}
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
div.credit__meta-item.credit__meta-item--billed-to
span.label #{__("credit.paper.billed_to")}
span.value #{creditNote.customer.displayName}
div(class=`${prefix}-address-section`)
if showCompanyAddress
div(class=`${prefix}-address-from`)
div !{companyAddress}
div.credit__meta-item.credit__meta-item--credit-date
span.label #{__("credit.paper.credit_date")}
span.value #{creditNote.formattedCreditNoteDate}
if showCustomerAddress
div(class=`${prefix}-address-to`)
strong #{billedToLabel}
div !{customerAddress}
div.credit__table
table
thead
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
table(class=`${prefix}-table`)
thead
tr
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{'Item'}
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{'Quantity'}
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{'Rate'}
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) #{'Total'}
tbody
each entry in creditNote.entries
tr
td.item
div.title=entry.item.name
span.description=entry.description
td.rate=entry.rate
td.quantity=entry.quantity
td.total=entry.amount
each line in lines
tr(class=`${prefix}-table__row`)
td(class=`${prefix}-table__cell ${prefix}-table__cell--item ${prefix}-table__cell--item`)
div.item
div.item__label #{line.item}
div.item__description #{line.description}
td(class=`${prefix}-table__cell ${prefix}-table__cell--quantity ${prefix}-table__cell--right`) #{line.quantity}
td(class=`${prefix}-table__cell ${prefix}-table__cell--rate ${prefix}-table__cell--right`) #{line.rate}
td(class=`${prefix}-table__cell ${prefix}-table__cell--total ${prefix}-table__cell--right`) #{line.total}
div.credit__table-after
div.credit__table-total
table
tbody
tr.total
td #{__('credit.paper.total')}
td #{creditNote.formattedAmount}
tr.payment-amount
td #{__('credit.paper.credits_used')}
td #{creditNote.formattedCreditsUsed}
tr.blanace-due
td #{__('credit.paper.credits_remaining')}
td #{creditNote.formattedCreditsRemaining}
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
div(class=`${prefix}-totals__item-label`) #{subtotallabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
div.credit__footer
if creditNote.termsConditions
div.credit__conditions
h3 #{__("credit.paper.terms_conditions")}
p #{creditNote.termsConditions}
if showTotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark`)
div(class=`${prefix}-totals__item-amount`) #{totalLabel}:
div(class=`${prefix}-totals__item-label`) #{total}
if creditNote.note
div.credit__notes
h3 #{__("credit.paper.notes")}
p #{creditNote.note}
if showCustomerNote && customerNote
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{customerNoteLabel}:
div(class=`${prefix}-statement__value`) #{customerNote}
if showTermsConditions && termsConditions
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
div(class=`${prefix}-statement__value`) #{termsConditions}

View File

@@ -1,82 +1,242 @@
extends ../PaperTemplateLayout.pug
block head
style
if (isRtl)
include ../../css/modules/estimate-rtl.css
else
include ../../css/modules/estimate.css
block head
- var prefix = 'bc'
style.
.#{prefix}-root {
color: #111;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details {
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {
}
.#{prefix}-addresses{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
align-items: flex-start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-addresses > * {
flex: 1 1;
}
.#{prefix}-address {
}
.#{prefix}-address__item {
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__header--item{
width: 50%;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell--right{
text-align: right;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--item .item {
display: flex;
flex-direction: column;
gap: 2px;
}
.#{prefix}-table__cell--item .item .item__description{
color: #5f6b7c;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--border-gray {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--border-dark {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--font-weight-bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
margin-bottom: 20px;
}
.#{prefix}-statement__label {
color: #666;
}
.#{prefix}-statement__value {
white-space: pre-line;
}
block content
div.estimate
div.estimate__header
div.paper
h1.title #{__("estimate.paper.estimate")}
span.email #{saleEstimate.estimateNumber}
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
div.organization
h3.title #{organizationName}
if organizationEmail
span.email #{organizationEmail}
//- Header (invluces big title, details and logo)
div(class=`${prefix}-header`)
div.estimate__estimate-amount
div.label #{__('estimate.paper.estimate_amount')}
div.amount #{saleEstimate.formattedAmount}
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
h1(class=`${prefix}-big-title`) Estimate
div.estimate__meta
if saleEstimate.estimateNumber
div.estimate__meta-item.estimate__meta-item--estimate-number
span.label #{__("estimate.paper.estimate_number")}
span.value #{saleEstimate.estimateNumber}
//- Terms List
div(class=`${prefix}-terms`)
if showEstimateNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
div.estimate__meta-item.estimate__meta-item--billed-to
span.label #{__("estimate.paper.billed_to")}
span.value #{saleEstimate.customer.displayName}
if showEstimateDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
div(class=`${prefix}-terms-item__value`) #{estimateDate}
div.estimate__meta-item.estimate__meta-item--estimate-date
span.label #{__("estimate.paper.estimate_date")}
span.value #{saleEstimate.formattedEstimateDate}
if showExpirationDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
div(class=`${prefix}-terms-item__value`) #{expirationDate}
div.estimate__meta-item.estimate__meta-item--due-date
span.label #{__("estimate.paper.expiration_date")}
span.value #{saleEstimate.formattedExpirationDate}
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
div.estimate__table
table
thead
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
//- Addresses (Group section)
div(class=`${prefix}-addresses`)
if showCompanyAddress
div(class=`${prefix}-address-from`)
div !{companyAddress}
if showCustomerAddress
div(class=`${prefix}-address-to`)
strong #{billedToLabel}
div !{customerAddress}
//- Table section (Line items)
table(class=`${prefix}-table`)
thead
tr
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) Rate
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) Total
tbody
each entry in saleEstimate.entries
tr
td.item
div.title=entry.item.name
span.description=entry.description
td.rate=entry.rate
td.quantity=entry.quantity
td.total=entry.amount
each line in lines
tr
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
div.item
div.item__label #{line.item}
div.item__description #{line.description}
td(class=`${prefix}-table__cell ${prefix}-table__cell--quantity ${prefix}-table__cell--right`) #{line.quantity}
td(class=`${prefix}-table__cell ${prefix}-table__cell--rate ${prefix}-table__cell--right`) #{line.rate}
td(class=`${prefix}-table__cell ${prefix}-table__cell--total ${prefix}-table__cell--right`) #{line.total}
div.estimate__table-after
div.estimate__table-total
table
tbody
tr.subtotal
td #{__('estimate.paper.subtotal')}
td #{saleEstimate.formattedAmount}
tr.total
td #{__('estimate.paper.total')}
td #{saleEstimate.formattedAmount}
//- Totals section
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
if showTotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
div(class=`${prefix}-totals__item-label`) #{totalLabel}
div(class=`${prefix}-totals__item-amount`) #{total}
div.estimate__footer
if saleEstimate.termsConditions
div.estimate__conditions
h3 #{__("estimate.paper.conditions_title")}
p #{saleEstimate.termsConditions}
//- Statements section
if showTermsConditions && termsConditions
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
div(class=`${prefix}-statement__value`) #{termsConditions}
if saleEstimate.note
div.estimate__notes
h3 #{__("estimate.paper.notes_title")}
p #{saleEstimate.note}
if showCustomerNote && customerNote
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
div(class=`${prefix}-statement__value`) #{customerNote}

View File

@@ -0,0 +1,24 @@
block head
style
include ../../css/modules/export-resource-table.css
style.
!{customCSS}
block content
.sheet
.sheet__title
h2.sheetTitle= sheetTitle
p.sheetDesc= sheetDescription
table.sheet__table
thead
tr
each column in table.columns
th(style=column.style class='column--' + column.key)= column.name
tbody
each row in table.rows
tr(class=row.classNames)
each cell in row.cells
td(class='cell--' + cell.key)
span!= cell.value

View File

@@ -1,92 +0,0 @@
extends ../PaperTemplateLayout.pug
block head
style
if (isRtl)
include ../../css/modules/invoice-rtl.css
else
include ../../css/modules/invoice.css
block content
div.invoice
div.invoice__header
div.paper
h1.title #{__("invoice.paper.invoice")}
if saleInvoice.invoiceNo
span.invoiceNo #{saleInvoice.invoiceNo}
div.organization
h3.title #{organizationName}
if organizationEmail
span.email #{organizationEmail}
div.invoice__due-amount
div.label #{__('invoice.paper.invoice_amount')}
div.amount #{saleInvoice.totalFormatted}
div.invoice__meta
div.invoice__meta-item.invoice__meta-item--amount
span.label #{__('invoice.paper.due_amount')}
span.value #{saleInvoice.dueAmountFormatted}
div.invoice__meta-item.invoice__meta-item--billed-to
span.label #{__("invoice.paper.billed_to")}
span.value #{saleInvoice.customer.displayName}
div.invoice__meta-item.invoice__meta-item--invoice-date
span.label #{__("invoice.paper.invoice_date")}
span.value #{saleInvoice.invoiceDateFormatted}
div.invoice__meta-item.invoice__meta-item--due-date
span.label #{__("invoice.paper.due_date")}
span.value #{saleInvoice.dueDateFormatted}
div.invoice__table
table
thead
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
tbody
each entry in saleInvoice.entries
tr
td.item
div.title=entry.item.name
span.description=entry.description
td.rate=entry.rate
td.quantity=entry.quantity
td.total=entry.amount
div.invoice__table-after
div.invoice__table-total
table
tbody
tr.subtotal
td #{__('invoice.paper.subtotal')}
td #{saleInvoice.subtotalFormatted}
each tax in saleInvoice.taxes
tr.tax_line
td #{tax.name} [#{tax.taxRate}%]
td #{tax.taxRateAmountFormatted}
tr.total
td #{__('invoice.paper.total')}
td #{saleInvoice.totalFormatted}
tr.payment-amount
td #{__('invoice.paper.payment_amount')}
td #{saleInvoice.paymentAmountFormatted}
tr.blanace-due
td #{__('invoice.paper.balance_due')}
td #{saleInvoice.dueAmountFormatted}
div.invoice__footer
if saleInvoice.termsConditions
div.invoice__conditions
h3 #{__("invoice.paper.conditions_title")}
p #{saleInvoice.termsConditions}
if saleInvoice.invoiceMessage
div.invoice__notes
h3 #{__("invoice.paper.notes_title")}
p #{saleInvoice.invoiceMessage}

View File

@@ -0,0 +1,272 @@
extends ../PaperTemplateLayout.pug
block head
- var prefix = 'bc'
style.
.#{prefix}-root {
color: #111;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-details {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-detail {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-detail__label {
min-width: 120px;
color: #333;
}
.#{prefix}-detail__value {
/* Styles for detail values */
}
.#{prefix}-address-root {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
align-items: flex-start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address-from {
flex: 1;
}
.#{prefix}-address-from__item {
/* Styles for items in the billed-from address */
}
.#{prefix}-address-to {
flex: 1;
}
.#{prefix}-address-to__item {
/* Styles for items in the billed-to address */
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__header--item {
width: 50%;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-table__cell--item .item {
display: flex;
flex-direction: column;
gap: 2px;
}
.#{prefix}-table__cell--item .item__description {
color: #5f6b7c;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--border-gray {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--border-dark {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--font-weight-bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-paragraph {
margin-bottom: 20px;
}
.#{prefix}-paragraph__label {
color: #666;
}
.#{prefix}-paragraph__value {
white-space: pre-line;
}
block content
//- block head
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Invoice
//- Invoice details
div(class=`${prefix}-details`)
if showInvoiceNumber
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{invoiceNumberLabel}
div(class=`${prefix}-detail__value`) #{invoiceNumber}
if showDateIssue
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dateIssueLabel}
div(class=`${prefix}-detail__value`) #{dateIssue}
if showDueDate
div(class=`${prefix}-detail`)
div(class=`${prefix}-detail__label`) #{dueDateLabel}
div(class=`${prefix}-detail__value`) #{dueDate}
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(alt="Company logo", src=companyLogoUri)
//- Address section
div(class=`${prefix}-address-root`)
if showCompanyAddress
div(class=`${prefix}-address-from`)
div !{companyAddress}
if showCustomerAddress
div(class=`${prefix}-address-to`)
strong #{billedToLabel}
div !{customerAddress}
//- Invoice table
table(class=`${prefix}-table`)
thead
tr
th(class=`${prefix}-table__header ${prefix}-table__header--item`) #{lineItemLabel}
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) #{lineQuantityLabel}
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) #{lineRateLabel}
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) #{lineTotalLabel}
tbody
each line in lines
tr
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
div.item
div.item__label #{line.item}
div.item__description #{line.description}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.quantity}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
//- Totals section
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
if showDiscount
div(class=`${prefix}-totals__item`)
div(class=`${prefix}-totals__item-label`) #{discountLabel}
div(class=`${prefix}-totals__item-amount`) #{discount}
if showTaxes
each tax in taxes
div(class=`${prefix}-totals__item`)
div(class=`${prefix}-totals__item-label`) #{tax.label}
div(class=`${prefix}-totals__item-amount`) #{tax.amount}
if showTotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
div(class=`${prefix}-totals__item-label`) #{totalLabel}
div(class=`${prefix}-totals__item-amount`) #{total}
if showPaymentMade
div(class=`${prefix}-totals__item`)
div(class=`${prefix}-totals__item-label`) #{paymentMadeLabel}
div(class=`${prefix}-totals__item-amount`) #{paymentMade}
if showBalanceDue
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`)
div(class=`${prefix}-totals__item-label`) #{balanceDueLabel}
div(class=`${prefix}-totals__item-amount`) #{balanceDue}
//- Footer section
if showTermsConditions && termsConditions
div(class=`${prefix}-paragraph`)
if termsConditionsLabel
div(class=`${prefix}-paragraph__label`) #{termsConditionsLabel}
div(class=`${prefix}-paragraph__value`) #{termsConditions}
if showStatement && statement
div(class=`${prefix}-paragraph`)
if statementLabel
div(class=`${prefix}-paragraph__label`) #{statementLabel}
div(class=`${prefix}-paragraph__value`) #{statement}

View File

@@ -1,67 +1,192 @@
extends ../PaperTemplateLayout.pug
block head
style
if (isRtl)
include ../../css/modules/payment-rtl.css
else
include ../../css/modules/payment.css
- var prefix = 'bp3';
style.
.#{prefix}-root{
color: #111;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
-webkit-box-align: start;
align-items: start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-big-title{
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms-list{
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item{
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label{
min-width: 120px;
color: #333;
}
.#{prefix}-addresses{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
align-items: flex-start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-addresses > * {
flex: 1 1;
}
.#{prefix}-address__label{
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--gray-border {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--dark-border {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
block content
div.payment
div.payment__header
div.paper
h1.title #{__("payment.paper.payment_receipt")}
if paymentReceive.paymentReceiveNo
span.paymentNumber #{paymentReceive.paymentReceiveNo}
div(class=`${prefix}-root`)
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
div(class=`${prefix}-big-title`) Payment
div(class=`${prefix}-terms-list`)
if showPaymentReceivedNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
div.organization
h3.title #{organizationName}
if organizationEmail
span.email #{organizationEmail}
if showPaymentReceivedDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
div.payment__received-amount
div.label #{__('payment.paper.amount_received')}
div.amount #{paymentReceive.formattedAmount}
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt="Company Logo")
div(class=`${prefix}-addresses`)
if showCompanyAddress
div(class=`${prefix}-address-from`)
div !{companyAddress}
div.payment__meta
div.payment__meta-item.payment__meta-item--billed-to
span.label #{__("payment.paper.billed_to")}
span.value #{paymentReceive.customer.displayName}
if showCustomerAddress
div(class=`${prefix}-address-to`)
strong #{billedToLabel}
div !{customerAddress}
div.payment__meta-item.payment__meta-item--payment-date
span.label #{__("payment.paper.payment_date")}
span.value #{paymentReceive.formattedPaymentDate}
table(class=`${prefix}-table`)
thead
tr
th(class=`${prefix}-table__header`) Invoice #
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Invoice Amount
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Paid Amount
div.payment__table
table
thead
tr
th.item #{__("payment.paper.invoice_number")}
th.date #{__("payment.paper.invoice_date")}
th.invoiceAmount #{__("payment.paper.invoice_amount")}
th.paymentAmount #{__("payment.paper.payment_amount")}
tbody
each entry in paymentReceive.entries
tr
td.item=entry.invoice.invoiceNo
td.date=entry.invoice.invoiceDateFormatted
td.invoiceAmount=entry.invoice.totalFormatted
td.paymentAmount=entry.invoice.paymentAmountFormatted
tbody
each line in lines
tr
td(class=`${prefix}-table__cell`) #{line.invoiceNumber}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.invoiceAmount}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.paidAmount}
div.payment__table-after
div.payment__table-total
table
tbody
tr.payment-amount
td #{__('payment.paper.payment_amount')}
td #{paymentReceive.formattedAmount}
tr.blanace-due
td #{__('payment.paper.balance_due')}
td #{paymentReceive.customer.closingBalance}
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--gray-border`)
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
div.payment__footer
if paymentReceive.statement
div.payment__notes
h3 #{__("payment.paper.statement")}
p #{paymentReceive.statement}
if showTotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--dark-border`)
div(class=`${prefix}-totals__item-label`) #{totalLabel}
div(class=`${prefix}-totals__item-amount`) #{total}

View File

@@ -1,77 +1,231 @@
extends ../PaperTemplateLayout.pug
block head
style
if (isRtl)
include ../../css/modules/receipt-rtl.css
else
include ../../css/modules/receipt.css
- var prefix = 'bc'
style.
.#{prefix}-root {
color: #000;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-header{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
flex: 0 0 auto;
align-items: start;
justify-content: flex-start;
gap: 10px;
}
.#{prefix}-header-details{
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
flex: 1 1 0%;
}
.#{prefix}-logo-wrap img {
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-big-title {
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {}
.#{prefix}-address-section {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: flex-start;
align-items: flex-start;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address-section > * {
flex: 1 1 auto;
}
.#{prefix}-address {}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__header--item{
width: 50%;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-table__cell--item .item {
display: flex;
flex-direction: column;
gap: 2px;
}
.#{prefix}-table__cell--item .item .item__description{
color: #5f6b7c;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__line {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__line--gray-border {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__line--dark-border {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__line__label {
min-width: 160px;
}
.#{prefix}-totals__line__amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
margin-bottom: 20px;
}
.#{prefix}-statement__label {}
.#{prefix}-statement__value {
white-space: pre-line;
}
block content
div.receipt
div.receipt__header
div.paper
h1.title #{__("receipt.paper.receipt")}
span.receiptNumber #{saleReceipt.receiptNumber}
//- block head
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
div.organization
h3.title #{organizationName}
//- Header (includes big title, details and logo )
div(class=`${prefix}-header`)
//- Header details (includes big title and details )
div(class=`${prefix}-header-details`)
//- Title and company logo
h1(class=`${prefix}-big-title`) Receipt
div.receipt__receipt-amount
div.label #{__('receipt.paper.receipt_amount')}
div.amount #{saleReceipt.formattedAmount}
//- Terms List
div(class=`${prefix}-terms-list`)
if showReceiptNumber
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
span(class=`${prefix}-terms-item__value`)= receiptNumber
div.receipt__meta
div.receipt__meta-item.receipt__meta-item--billed-to
span.label #{__("receipt.paper.billed_to")}
span.value #{saleReceipt.customer.displayName}
if showReceiptDate
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
span(class=`${prefix}-terms-item__value`)= receiptDate
div.receipt__meta-item.receipt__meta-item--invoice-date
span.label #{__("receipt.paper.receipt_date")}
span.value #{saleReceipt.formattedReceiptDate}
//- Company logo
if showCompanyLogo && companyLogoUri
div(class=`${prefix}-logo-wrap`)
img(src=companyLogoUri alt=`Company Logo`)
if saleReceipt.receiptNumber
div.receipt__meta-item.receipt__meta-item--invoice-number
span.label #{__("receipt.paper.receipt_number")}
span.value #{saleReceipt.receiptNumber}
//- Address Section
div(class=`${prefix}-address-section`)
if showCompanyAddress
div(class=`${prefix}-address-from`)
div !{companyAddress}
div.receipt__table
table
thead
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
tbody
each entry in saleReceipt.entries
tr
td.item=entry.item.name
td.rate=entry.rate
td.quantity=entry.quantity
td.total=entry.amount
div.receipt__table-after
div.receipt__table-total
table
tbody
tr.total
td #{__('receipt.paper.total')}
td #{saleReceipt.formattedAmount}
tr.payment-amount
td #{__('receipt.paper.payment_amount')}
td #{saleReceipt.formattedAmount}
tr.blanace-due
td #{__('receipt.paper.balance_due')}
td #{'$0'}
if showCustomerAddress
div(class=`${prefix}-address-to`)
strong #{billedToLabel}
div !{customerAddress}
div.receipt__footer
if saleReceipt.statement
div.receipt__conditions
h3 #{__("receipt.paper.statement")}
p #{saleReceipt.statement}
//- Table Section
table(class=`${prefix}-table`)
thead(class=`${prefix}-table__header`)
tr
th(class=`${prefix}-table__header ${prefix}-table__header--item`) Item
th(class=`${prefix}-table__header ${prefix}-table__header--quantity ${prefix}-table__header--right`) Qty
th(class=`${prefix}-table__header ${prefix}-table__header--rate ${prefix}-table__header--right`) Rate
th(class=`${prefix}-table__header ${prefix}-table__header--total ${prefix}-table__header--right`) Total
tbody
each line in lines
tr(class=`${prefix}-table__row`)
td(class=`${prefix}-table__cell ${prefix}-table__cell--item`)
div.item
div.item__label #{line.item}
div.item__description #{line.description}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.quantity}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
if saleReceipt.receiptMessage
div.receipt__notes
h3 #{__("receipt.paper.notes")}
p #{saleReceipt.receiptMessage}
//- Totals Section
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__line ${prefix}-totals__line--gray-border`)
span(class=`${prefix}-totals__line__label`)= subtotalLabel
span(class=`${prefix}-totals__line__amount`)= subtotal
if showTotal
div(class=`${prefix}-totals__line ${prefix}-totals__line--dark-border`)
span(class=`${prefix}-totals__line__label`)= totalLabel
span(class=`${prefix}-totals__line__amount`)= total
//- Customer Note Section
if showCustomerNote && customerNote
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`)= customerNoteLabel
div(class=`${prefix}-statement__value`)= customerNote
//- Terms & Conditions Section
if showTermsConditions && termsConditions
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`)= termsConditionsLabel
div(class=`${prefix}-statement__value`)= termsConditions

View File

@@ -70,6 +70,10 @@ module.exports = {
src: `${RESOURCES_PATH}/scss/modules/financial-sheet.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
},
{
src: `${RESOURCES_PATH}/scss/modules/export-resource-table.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
},
],
// RTL builds.
rtl: [

View File

@@ -103,24 +103,20 @@ export default class AccountsController extends BaseController {
check('name')
.exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim()
.escape(),
.trim(),
check('code')
.optional({ nullable: true })
.isLength({ min: 3, max: 6 })
.trim()
.escape(),
.trim(),
check('currency_code').optional(),
check('account_type')
.exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim()
.escape(),
.trim(),
check('description')
.optional({ nullable: true })
.isLength({ max: DATATYPES_LENGTH.TEXT })
.trim()
.escape(),
.trim(),
check('parent_account_id')
.optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
@@ -136,23 +132,19 @@ export default class AccountsController extends BaseController {
check('name')
.exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim()
.escape(),
.trim(),
check('code')
.optional({ nullable: true })
.isLength({ min: 3, max: 6 })
.trim()
.escape(),
.trim(),
check('account_type')
.exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim()
.escape(),
.trim(),
check('description')
.optional({ nullable: true })
.isLength({ max: DATATYPES_LENGTH.TEXT })
.trim()
.escape(),
.trim(),
check('parent_account_id')
.optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
@@ -207,7 +199,6 @@ export default class AccountsController extends BaseController {
tenantId,
accountDTO
);
return res.status(200).send({
id: account.id,
message: 'The account has been created successfully.',

View File

@@ -0,0 +1,266 @@
import mime from 'mime-types';
import { Service, Inject } from 'typedi';
import { Router, Response, NextFunction, Request } from 'express';
import { body, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
import { AttachmentUploadPipeline } from '@/services/Attachments/S3UploadPipeline';
@Service()
export class AttachmentsController extends BaseController {
@Inject()
private attachmentsApplication: AttachmentsApplication;
@Inject()
private uploadPipelineService: AttachmentUploadPipeline;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/',
this.uploadPipelineService.validateS3Configured,
this.uploadPipelineService.uploadPipeline().single('file'),
this.validateUploadedFileExistance,
this.uploadAttachment.bind(this)
);
router.delete(
'/:id',
[param('id').exists()],
this.validationResult,
this.deleteAttachment.bind(this)
);
router.get(
'/:id',
[param('id').exists()],
this.validationResult,
this.getAttachment.bind(this)
);
router.post(
'/:id/link',
[body('modelRef').exists(), body('modelId').exists()],
this.validationResult
);
router.post(
'/:id/link',
[body('modelRef').exists(), body('modelId').exists()],
this.validationResult,
this.linkDocument.bind(this)
);
router.post(
'/:id/unlink',
[body('modelRef').exists(), body('modelId').exists()],
this.validationResult,
this.unlinkDocument.bind(this)
);
router.get(
'/:id/presigned-url',
[param('id').exists()],
this.validationResult,
this.getAttachmentPresignedUrl.bind(this)
);
return router;
}
/**
* Validates the upload file existance.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response|void}
*/
private validateUploadedFileExistance(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.file) {
return res.boom.badRequest(null, {
errorType: 'FILE_UPLOAD_FAILED',
message: 'Now file uploaded.',
});
}
next();
}
/**
* Uploads the attachments to S3 and store the file metadata to DB.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response|void}
*/
private async uploadAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const file = req.file;
try {
const data = await this.attachmentsApplication.upload(tenantId, file);
return res.status(200).send({
status: 200,
message: 'The document has uploaded successfully.',
data,
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the given attachment key.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async getAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { id } = req.params;
try {
const data = await this.attachmentsApplication.get(tenantId, id);
const byte = await data.Body.transformToByteArray();
const extension = mime.extension(data.ContentType);
const buffer = Buffer.from(byte);
res.set(
'Content-Disposition',
`filename="${req.params.id}.${extension}"`
);
res.set('Content-Type', data.ContentType);
res.send(buffer);
} catch (error) {
next(error);
}
}
/**
* Deletes the given document key.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async deleteAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { id: documentId } = req.params;
try {
await this.attachmentsApplication.delete(tenantId, documentId);
return res.status(200).send({
status: 200,
message: 'The document has been delete successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Links the given document key.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async linkDocument(
req: Request,
res: Response,
next: Function
): Promise<Response | void> {
const { tenantId } = req;
const { id: documentId } = req.params;
const { modelRef, modelId } = this.matchedBodyData(req);
try {
await this.attachmentsApplication.link(
tenantId,
documentId,
modelRef,
modelId
);
return res.status(200).send({
status: 200,
message: 'The document has been linked successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Links the given document key.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async unlinkDocument(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { id: documentId } = req.params;
const { modelRef, modelId } = this.matchedBodyData(req);
try {
await this.attachmentsApplication.link(
tenantId,
documentId,
modelRef,
modelId
);
return res.status(200).send({
status: 200,
message: 'The document has been linked successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retreives the presigned url of the given attachment key.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async getAttachmentPresignedUrl(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { id: documentKey } = req.params;
try {
const presignedUrl = await this.attachmentsApplication.getPresignedUrl(
tenantId,
documentKey
);
return res.status(200).send({ presignedUrl });
} catch (error) {
next(error);
}
}
}

View File

@@ -9,6 +9,8 @@ import { DATATYPES_LENGTH } from '@/data/DataTypes';
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
import JWTAuth from '@/api/middleware/jwtAuth';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
@Service()
export default class AuthenticationController extends BaseController {
@Inject()
@@ -28,6 +30,20 @@ export default class AuthenticationController extends BaseController {
asyncMiddleware(this.login.bind(this)),
this.handlerErrors
);
router.use('/register/verify/resend', JWTAuth);
router.use('/register/verify/resend', AttachCurrentTenantUser);
router.post(
'/register/verify/resend',
asyncMiddleware(this.registerVerifyResendMail.bind(this)),
this.handlerErrors
);
router.post(
'/register/verify',
this.signupVerifySchema,
this.validationResult,
asyncMiddleware(this.registerVerify.bind(this)),
this.handlerErrors
);
router.post(
'/register',
this.registerSchema,
@@ -74,31 +90,38 @@ export default class AuthenticationController extends BaseController {
.exists()
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('last_name')
.exists()
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('email')
.exists()
.isString()
.isEmail()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('password')
.exists()
.isString()
.isLength({ min: 6 })
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
];
}
private get signupVerifySchema(): ValidationChain[] {
return [
check('email')
.exists()
.isString()
.isEmail()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('token').exists().isString(),
];
}
/**
* Reset password schema.
* @returns {ValidationChain[]}
@@ -123,7 +146,7 @@ export default class AuthenticationController extends BaseController {
* @returns {ValidationChain[]}
*/
private get sendResetPasswordSchema(): ValidationChain[] {
return [check('email').exists().isEmail().trim().escape()];
return [check('email').exists().isEmail().trim()];
}
/**
@@ -131,7 +154,11 @@ export default class AuthenticationController extends BaseController {
* @param {Request} req
* @param {Response} res
*/
private async login(req: Request, res: Response, next: Function): Response {
private async login(
req: Request,
res: Response,
next: Function
): Promise<Response | null> {
const userDTO: ILoginDTO = this.matchedBodyData(req);
try {
@@ -166,6 +193,58 @@ export default class AuthenticationController extends BaseController {
}
}
/**
* Verifies the provider user's email after signin-up.
* @param {Request} req
* @param {Response}| res
* @param {Function} next
* @returns {Response|void}
*/
private async registerVerify(req: Request, res: Response, next: Function) {
const signUpVerifyDTO: { email: string; token: string } =
this.matchedBodyData(req);
try {
const user = await this.authApplication.signUpConfirm(
signUpVerifyDTO.email,
signUpVerifyDTO.token
);
return res.status(200).send({
type: 'success',
message: 'The given user has verified successfully',
user,
});
} catch (error) {
next(error);
}
}
/**
* Resends the confirmation email to the user.
* @param {Request} req
* @param {Response}| res
* @param {Function} next
*/
private async registerVerifyResendMail(
req: Request,
res: Response,
next: Function
) {
const { user } = req;
try {
const data = await this.authApplication.signUpConfirmResend(user.id);
return res.status(200).send({
type: 'success',
message: 'The given user has verified successfully',
data,
});
} catch (error) {
next(error);
}
}
/**
* Send reset password handler
* @param {Request} req

View File

@@ -0,0 +1,218 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { param, query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
import { GetPendingBankAccountTransactions } from '@/services/Cashflow/GetPendingBankAccountTransaction';
@Service()
export class BankAccountsController extends BaseController {
@Inject()
private getBankAccountSummaryService: GetBankAccountSummary;
@Inject()
private bankAccountsApp: BankAccountsApplication;
@Inject()
private getPendingTransactionsService: GetPendingBankAccountTransactions;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get('/:bankAccountId/meta', this.getBankAccountSummary.bind(this));
router.get(
'/pending_transactions',
[
query('account_id').optional().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
],
this.validationResult,
this.getBankAccountsPendingTransactions.bind(this)
);
router.post(
'/:bankAccountId/disconnect',
this.disconnectBankAccount.bind(this)
);
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
router.post(
'/:bankAccountId/pause_feeds',
[param('bankAccountId').exists().isNumeric().toInt()],
this.validationResult,
this.pauseBankAccountFeeds.bind(this)
);
router.post(
'/:bankAccountId/resume_feeds',
[param('bankAccountId').exists().isNumeric().toInt()],
this.validationResult,
this.resumeBankAccountFeeds.bind(this)
);
return router;
}
/**
* Retrieves the bank account meta summary.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getBankAccountSummary(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
const data =
await this.getBankAccountSummaryService.getBankAccountSummary(
tenantId,
bankAccountId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Retrieves the bank account pending transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async getBankAccountsPendingTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const data =
await this.getPendingTransactionsService.getPendingTransactions(
tenantId,
query
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Disonnect the given bank account.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async disconnectBankAccount(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.disconnectBankAccount(tenantId, bankAccountId);
return res.status(200).send({
id: bankAccountId,
message: 'The bank account has been disconnected.',
});
} catch (error) {
next(error);
}
}
/**
* Refresh the given bank account.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async refreshBankAccount(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.refreshBankAccount(tenantId, bankAccountId);
return res.status(200).send({
id: bankAccountId,
message: 'The bank account has been disconnected.',
});
} catch (error) {
next(error);
}
}
/**
* Resumes the bank account feeds sync.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | void>}
*/
async resumeBankAccountFeeds(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.resumeBankAccount(tenantId, bankAccountId);
return res.status(200).send({
message: 'The bank account feeds syncing has been resumed.',
id: bankAccountId,
});
} catch (error) {
next(error);
}
}
/**
* Pauses the bank account feeds sync.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | void>}
*/
async pauseBankAccountFeeds(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.pauseBankAccount(tenantId, bankAccountId);
return res.status(200).send({
message: 'The bank account feeds syncing has been paused.',
id: bankAccountId,
});
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,101 @@
import { Inject, Service } from 'typedi';
import { body, param } from 'express-validator';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/MatchBankTransactionsApplication';
@Service()
export class BankTransactionsMatchingController extends BaseController {
@Inject()
private bankTransactionsMatchingApp: MatchBankTransactionsApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/unmatch/:transactionId',
[param('transactionId').exists()],
this.validationResult,
this.unmatchMatchedBankTransaction.bind(this)
);
router.post(
'/match',
[
body('uncategorizedTransactions').exists().isArray({ min: 1 }),
body('uncategorizedTransactions.*').isNumeric().toInt(),
body('matchedTransactions').isArray({ min: 1 }),
body('matchedTransactions.*.reference_type').exists(),
body('matchedTransactions.*.reference_id').isNumeric().toInt(),
],
this.validationResult,
this.matchBankTransaction.bind(this)
);
return router;
}
/**
* Matches the given bank transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async matchBankTransaction(
req: Request<{ transactionId: number }>,
res: Response,
next: NextFunction
): Promise<Response | null> {
const { tenantId } = req;
const bodyData = this.matchedBodyData(req);
const uncategorizedTransactions = bodyData?.uncategorizedTransactions;
const matchedTransactions = bodyData?.matchedTransactions;
try {
await this.bankTransactionsMatchingApp.matchTransaction(
tenantId,
uncategorizedTransactions,
matchedTransactions
);
return res.status(200).send({
ids: uncategorizedTransactions,
message: 'The bank transaction has been matched.',
});
} catch (error) {
next(error);
}
}
/**
* Unmatches the matched bank transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async unmatchMatchedBankTransaction(
req: Request<{ transactionId: number }>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.bankTransactionsMatchingApp.unmatchMatchedTransaction(
tenantId,
transactionId
);
return res.status(200).send({
id: transactionId,
message: 'The bank matched transaction has been unmatched.',
});
} catch (error) {
next(error);
}
}
}

View File

@@ -2,17 +2,38 @@ import Container, { Inject, Service } from 'typedi';
import { Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { PlaidBankingController } from './PlaidBankingController';
import { BankingRulesController } from './BankingRulesController';
import { BankTransactionsMatchingController } from './BankTransactionsMatchingController';
import { RecognizedTransactionsController } from './RecognizedTransactionsController';
import { BankAccountsController } from './BankAccountsController';
import { BankingUncategorizedController } from './BankingUncategorizedController';
@Service()
export class BankingController extends BaseController {
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();
router.use('/plaid', Container.get(PlaidBankingController).router());
router.use('/rules', Container.get(BankingRulesController).router());
router.use(
'/matches',
Container.get(BankTransactionsMatchingController).router()
);
router.use(
'/recognized',
Container.get(RecognizedTransactionsController).router()
);
router.use(
'/bank_accounts',
Container.get(BankAccountsController).router()
);
router.use(
'/categorize',
Container.get(BankingUncategorizedController).router()
);
return router;
}
}

View File

@@ -0,0 +1,214 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { BankRulesApplication } from '@/services/Banking/Rules/BankRulesApplication';
import { body, param } from 'express-validator';
import {
ICreateBankRuleDTO,
IEditBankRuleDTO,
} from '@/services/Banking/Rules/types';
@Service()
export class BankingRulesController extends BaseController {
@Inject()
private bankRulesApplication: BankRulesApplication;
/**
* Bank rule DTO validation schema.
*/
private get bankRuleValidationSchema() {
return [
body('name').isString().exists(),
body('order').isInt({ min: 0 }),
// Apply to if transaction is.
body('apply_if_account_id')
.isInt({ min: 0 })
.optional({ nullable: true }),
body('apply_if_transaction_type').isIn(['deposit', 'withdrawal']),
// Conditions
body('conditions_type').isString().isIn(['and', 'or']).default('and'),
body('conditions').isArray({ min: 1 }),
body('conditions.*.field').exists().isIn(['description', 'amount']),
body('conditions.*.comparator')
.exists()
.isIn([
'equals',
'equal',
'contains',
'not_contain',
'bigger',
'bigger_or_equal',
'smaller',
'smaller_or_equal',
])
.default('contain')
.trim(),
body('conditions.*.value').exists().trim(),
// Assign
body('assign_category').isString(),
body('assign_account_id').isInt({ min: 0 }),
body('assign_payee').isString().optional({ nullable: true }),
body('assign_memo').isString().optional({ nullable: true }),
];
}
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/',
[...this.bankRuleValidationSchema],
this.validationResult,
this.createBankRule.bind(this)
);
router.post(
'/:id',
[param('id').toInt().exists(), ...this.bankRuleValidationSchema],
this.validationResult,
this.editBankRule.bind(this)
);
router.delete(
'/:id',
[param('id').toInt().exists()],
this.validationResult,
this.deleteBankRule.bind(this)
);
router.get(
'/:id',
[param('id').toInt().exists()],
this.validationResult,
this.getBankRule.bind(this)
);
router.get(
'/',
[param('id').toInt().exists()],
this.validationResult,
this.getBankRules.bind(this)
);
return router;
}
/**
* Creates a new bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async createBankRule(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const createBankRuleDTO = this.matchedBodyData(req) as ICreateBankRuleDTO;
try {
const bankRule = await this.bankRulesApplication.createBankRule(
tenantId,
createBankRuleDTO
);
return res.status(200).send({
id: bankRule.id,
message: 'The bank rule has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Edits the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async editBankRule(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: ruleId } = req.params;
const editBankRuleDTO = this.matchedBodyData(req) as IEditBankRuleDTO;
try {
await this.bankRulesApplication.editBankRule(
tenantId,
ruleId,
editBankRuleDTO
);
return res.status(200).send({
id: ruleId,
message: 'The bank rule has been updated successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Deletes the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async deleteBankRule(
req: Request<{ id: number }>,
res: Response,
next: NextFunction
) {
const { id: ruleId } = req.params;
const { tenantId } = req;
try {
await this.bankRulesApplication.deleteBankRule(tenantId, ruleId);
return res
.status(200)
.send({ message: 'The bank rule has been deleted.', id: ruleId });
} catch (error) {
next(error);
}
}
/**
* Retrieve the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getBankRule(req: Request, res: Response, next: NextFunction) {
const { id: ruleId } = req.params;
const { tenantId } = req;
try {
const bankRule = await this.bankRulesApplication.getBankRule(
tenantId,
ruleId
);
return res.status(200).send({ bankRule });
} catch (error) {
next(error);
}
}
/**
* Retrieves the bank rules.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getBankRules(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
try {
const bankRules = await this.bankRulesApplication.getBankRules(tenantId);
return res.status(200).send({ bankRules });
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,57 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { query } from 'express-validator';
import BaseController from '../BaseController';
import { GetAutofillCategorizeTransaction } from '@/services/Banking/RegonizeTranasctions/GetAutofillCategorizeTransaction';
@Service()
export class BankingUncategorizedController extends BaseController {
@Inject()
private getAutofillCategorizeTransactionService: GetAutofillCategorizeTransaction;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get(
'/autofill',
[
query('uncategorizedTransactionIds').isArray({ min: 1 }),
query('uncategorizedTransactionIds.*').isNumeric().toInt(),
],
this.validationResult,
this.getAutofillCategorizeTransaction.bind(this)
);
return router;
}
/**
* Retrieves the autofill values of the categorize form of the given
* uncategorized transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
public async getAutofillCategorizeTransaction(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const uncategorizedTransactionIds = req.query.uncategorizedTransactionIds;
try {
const data =
await this.getAutofillCategorizeTransactionService.getAutofillCategorizeTransaction(
tenantId,
uncategorizedTransactionIds
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,201 @@
import { Inject, Service } from 'typedi';
import { body, param, query } from 'express-validator';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '../BaseController';
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
import { map, parseInt, trim } from 'lodash';
@Service()
export class ExcludeBankTransactionsController extends BaseController {
@Inject()
private excludeBankTransactionApp: ExcludeBankTransactionsApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.put(
'/transactions/exclude',
[body('ids').exists()],
this.validationResult,
this.excludeBulkBankTransactions.bind(this)
);
router.put(
'/transactions/unexclude',
[body('ids').exists()],
this.validationResult,
this.unexcludeBulkBankTransactins.bind(this)
);
router.put(
'/transactions/:transactionId/exclude',
[param('transactionId').exists().toInt()],
this.validationResult,
this.excludeBankTransaction.bind(this)
);
router.put(
'/transactions/:transactionId/unexclude',
[param('transactionId').exists()],
this.validationResult,
this.unexcludeBankTransaction.bind(this)
);
router.get(
'/excluded',
[
query('account_id').optional().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
],
this.validationResult,
this.getExcludedBankTransactions.bind(this)
);
return router;
}
/**
* Marks a bank transaction as excluded.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns
*/
private async excludeBankTransaction(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.excludeBankTransactionApp.excludeBankTransaction(
tenantId,
transactionId
);
return res.status(200).send({
message: 'The bank transaction has been excluded.',
id: transactionId,
});
} catch (error) {
next(error);
}
}
/**
* Marks a bank transaction as not excluded.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async unexcludeBankTransaction(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.excludeBankTransactionApp.unexcludeBankTransaction(
tenantId,
transactionId
);
return res.status(200).send({
message: 'The bank transaction has been unexcluded.',
id: transactionId,
});
} catch (error) {
next(error);
}
}
/**
* Exclude bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async excludeBulkBankTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.excludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Unexclude the given bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private async unexcludeBulkBankTransactins(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | null> {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.unexcludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the excluded uncategorized bank transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async getExcludedBankTransactions(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
const data =
await this.excludeBankTransactionApp.getExcludedBankTransactions(
tenantId,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,91 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export class RecognizedTransactionsController extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get(
'/',
[
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('account_id').optional().isNumeric().toInt(),
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
],
this.validationResult,
this.getRecognizedTransactions.bind(this)
);
router.get(
'/transactions/:uncategorizedTransactionId',
this.getRecognizedTransaction.bind(this)
);
return router;
}
/**
* Retrieves the recognized bank transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getRecognizedTransactions(
req: Request<{ accountId: number }>,
res: Response,
next: NextFunction
) {
const filter = this.matchedQueryData(req);
const { tenantId } = req;
try {
const data = await this.cashflowApplication.getRecognizedTransactions(
tenantId,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Retrieves the recognized transaction of the ginen uncategorized transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getRecognizedTransaction(
req: Request<{ uncategorizedTransactionId: number }>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { uncategorizedTransactionId } = req.params;
try {
const data = await this.cashflowApplication.getRecognizedTransaction(
tenantId,
uncategorizedTransactionId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
}

View File

@@ -4,6 +4,7 @@ import CommandCashflowTransaction from './NewCashflowTransaction';
import DeleteCashflowTransaction from './DeleteCashflowTransaction';
import GetCashflowTransaction from './GetCashflowTransaction';
import GetCashflowAccounts from './GetCashflowAccounts';
import { ExcludeBankTransactionsController } from '../Banking/ExcludeBankTransactionsController';
@Service()
export default class CashflowController {
@@ -14,6 +15,7 @@ export default class CashflowController {
const router = Router();
router.use(Container.get(CommandCashflowTransaction).router());
router.use(Container.get(ExcludeBankTransactionsController).router());
router.use(Container.get(GetCashflowTransaction).router());
router.use(Container.get(GetCashflowAccounts).router());
router.use(Container.get(DeleteCashflowTransaction).router());

View File

@@ -31,7 +31,6 @@ export default class GetCashflowAccounts extends BaseController {
query('search_keyword').optional({ nullable: true }).isString().trim(),
],
this.asyncMiddleware(this.getCashflowAccounts),
this.catchServiceErrors
);
return router;
}
@@ -67,22 +66,4 @@ export default class GetCashflowAccounts extends BaseController {
next(error);
}
};
/**
* Catches the service errors.
* @param {Error} error - Error.
* @param {Request} req - Request.
* @param {Response} res - Response.
* @param {NextFunction} next -
*/
private catchServiceErrors(
error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
}
next(error);
}
}

View File

@@ -1,23 +1,37 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { param } from 'express-validator';
import { param, query } from 'express-validator';
import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
import { GetMatchedTransactionsFilter } from '@/services/Banking/Matching/types';
import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/MatchBankTransactionsApplication';
@Service()
export default class GetCashflowAccounts extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
@Inject()
private bankTransactionsMatchingApp: MatchBankTransactionsApplication;
/**
* Controller router.
*/
public router() {
const router = Router();
router.get(
'/transactions/matches',
[
query('uncategorizeTransactionsIds').exists().isArray({ min: 1 }),
query('uncategorizeTransactionsIds.*').exists().isNumeric().toInt(),
],
this.validationResult,
this.getMatchedTransactions.bind(this)
);
router.get(
'/transactions/:transactionId',
CheckPolicies(CashflowAction.View, AbilitySubject.Cashflow),
@@ -35,7 +49,7 @@ export default class GetCashflowAccounts extends BaseController {
* @param {NextFunction} next
*/
private getCashflowTransaction = async (
req: Request,
req: Request<{ transactionId: number }>,
res: Response,
next: NextFunction
) => {
@@ -47,7 +61,6 @@ export default class GetCashflowAccounts extends BaseController {
tenantId,
transactionId
);
return res.status(200).send({
cashflow_transaction: this.transfromToResponse(cashflowTransaction),
});
@@ -56,6 +69,39 @@ export default class GetCashflowAccounts extends BaseController {
}
};
/**
* Retrieves the matched transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getMatchedTransactions(
req: Request<
{ transactionId: number },
null,
null,
{ uncategorizeTransactionsIds: Array<number> }
>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const uncategorizeTransactionsIds = req.query.uncategorizeTransactionsIds;
const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter;
try {
const data =
await this.bankTransactionsMatchingApp.getMatchedTransactions(
tenantId,
uncategorizeTransactionsIds,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Catches the service errors.
* @param {Error} error - Error.

View File

@@ -1,10 +1,15 @@
import { Service, Inject } from 'typedi';
import { ValidationChain, check, param, query } from 'express-validator';
import { ValidationChain, body, check, param, query } from 'express-validator';
import { Router, Request, Response, NextFunction } from 'express';
import { omit } from 'lodash';
import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces';
import {
AbilitySubject,
CashflowAction,
ICategorizeCashflowTransactioDTO,
} from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
@@ -38,13 +43,23 @@ export default class NewCashflowTransactionController extends BaseController {
this.asyncMiddleware(this.newCashflowTransaction),
this.catchServiceErrors
);
router.post(
'/transactions/uncategorize/bulk',
[
body('ids').isArray({ min: 1 }),
body('ids.*').exists().isNumeric().toInt(),
],
this.validationResult,
this.uncategorizeBulkTransactions.bind(this),
this.catchServiceErrors
);
router.post(
'/transactions/:id/uncategorize',
this.revertCategorizedCashflowTransaction,
this.catchServiceErrors
);
router.post(
'/transactions/:id/categorize',
'/transactions/categorize',
this.categorizeCashflowTransactionValidationSchema,
this.validationResult,
this.categorizeCashflowTransaction,
@@ -69,6 +84,10 @@ export default class NewCashflowTransactionController extends BaseController {
param('id').exists().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
];
}
@@ -89,6 +108,7 @@ export default class NewCashflowTransactionController extends BaseController {
*/
public get categorizeCashflowTransactionValidationSchema() {
return [
check('uncategorized_transaction_ids').exists().isArray({ min: 1 }),
check('date').exists().isISO8601().toDate(),
check('credit_account_id').exists().isInt().toInt(),
check('transaction_number').optional(),
@@ -106,12 +126,11 @@ export default class NewCashflowTransactionController extends BaseController {
public get newTransactionValidationSchema() {
return [
check('date').exists().isISO8601().toDate(),
check('reference_no').optional({ nullable: true }).trim().escape(),
check('reference_no').optional({ nullable: true }).trim(),
check('description')
.optional({ nullable: true })
.isLength({ min: 3 })
.trim()
.escape(),
.trim(),
check('transaction_type').exists(),
check('amount').exists().isFloat().toFloat(),
@@ -161,7 +180,7 @@ export default class NewCashflowTransactionController extends BaseController {
* @param {NextFunction} next
*/
private revertCategorizedCashflowTransaction = async (
req: Request,
req: Request<{ id: number }>,
res: Response,
next: NextFunction
) => {
@@ -179,6 +198,34 @@ export default class NewCashflowTransactionController extends BaseController {
}
};
/**
* Uncategorize the given transactions in bulk.
* @param {Request<{}>} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private uncategorizeBulkTransactions = async (
req: Request<{}>,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { ids: uncategorizedTransactionIds } = this.matchedBodyData(req);
try {
await this.cashflowApplication.uncategorizeTransactions(
tenantId,
uncategorizedTransactionIds
);
return res.status(200).send({
message: 'The given transactions have been uncategorized successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Categorize the cashflow transaction.
* @param {Request} req
@@ -191,14 +238,19 @@ export default class NewCashflowTransactionController extends BaseController {
next: NextFunction
) => {
const { tenantId } = req;
const { id: cashflowTransactionId } = req.params;
const cashflowTransaction = this.matchedBodyData(req);
const matchedObject = this.matchedBodyData(req);
const categorizeDTO = omit(matchedObject, [
'uncategorizedTransactionIds',
]) as ICategorizeCashflowTransactioDTO;
const uncategorizedTransactionIds =
matchedObject.uncategorizedTransactionIds;
try {
await this.cashflowApplication.categorizeTransaction(
tenantId,
cashflowTransactionId,
cashflowTransaction
uncategorizedTransactionIds,
categorizeDTO
);
return res.status(200).send({
message: 'The cashflow transaction has been created successfully.',
@@ -269,7 +321,7 @@ export default class NewCashflowTransactionController extends BaseController {
* @param {NextFunction} next
*/
public getUncategorizedCashflowTransactions = async (
req: Request,
req: Request<{ id: number }>,
res: Response,
next: NextFunction
) => {

View File

@@ -56,7 +56,7 @@ export default class ContactsController extends BaseController {
*/
get autocompleteQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('column_sort_by').optional().trim(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(),
@@ -122,32 +122,27 @@ export default class ContactsController extends BaseController {
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('first_name')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('last_name')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('company_name')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('display_name')
.exists()
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('email')
@@ -165,120 +160,101 @@ export default class ContactsController extends BaseController {
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('personal_phone')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_1')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_2')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_city')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_country')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_email')
.optional({ nullable: true })
.isString()
.isEmail()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_postcode')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_phone')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_state')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_1')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_2')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_city')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_country')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_email')
.optional({ nullable: true })
.isString()
.isEmail()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_postcode')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_phone')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_state')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('note')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('active').optional().isBoolean().toBoolean(),
];

View File

@@ -106,11 +106,7 @@ export default class CustomersController extends ContactsController {
*/
get customerDTOSchema() {
return [
check('customer_type')
.exists()
.isIn(['business', 'individual'])
.trim()
.escape(),
check('customer_type').exists().isIn(['business', 'individual']).trim(),
];
}
@@ -123,7 +119,6 @@ export default class CustomersController extends ContactsController {
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: 3 }),
];
}
@@ -133,7 +128,7 @@ export default class CustomersController extends ContactsController {
*/
get validateListQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('column_sort_by').optional().trim(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),

View File

@@ -106,7 +106,6 @@ export default class VendorsController extends ContactsController {
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ min: 3, max: 3 }),
];
}
@@ -144,10 +143,8 @@ export default class VendorsController extends ContactsController {
try {
const vendor = await this.vendorsApplication.createVendor(
tenantId,
contactDTO,
user
contactDTO
);
return res.status(200).send({
id: vendor.id,
message: 'The vendor has been created successfully.',

View File

@@ -67,7 +67,7 @@ export default class CurrenciesController extends BaseController {
}
get currencyParamSchema(): ValidationChain[] {
return [param('currency_code').exists().trim().escape()];
return [param('currency_code').exists().trim()];
}
get listSchema(): ValidationChain[] {
@@ -187,11 +187,13 @@ export default class CurrenciesController extends BaseController {
}
if (error.errorType === 'currency_code_exists') {
return res.boom.badRequest(null, {
errors: [{
type: 'CURRENCY_CODE_EXISTS',
message: 'The given currency code is already exists.',
code: 200,
}],
errors: [
{
type: 'CURRENCY_CODE_EXISTS',
message: 'The given currency code is already exists.',
code: 200,
},
],
});
}
if (error.errorType === 'CANNOT_DELETE_BASE_CURRENCY') {

View File

@@ -5,13 +5,13 @@ import DashboardService from '@/services/Dashboard/DashboardService';
@Service()
export default class DashboardMetaController {
@Inject()
dashboardService: DashboardService;
private dashboardService: DashboardService;
/**
*
* Constructor router.
* @returns
*/
router() {
public router() {
const router = Router();
router.get('/boot', this.getDashboardBoot);
@@ -25,7 +25,7 @@ export default class DashboardMetaController {
* @param {Response} res -
* @param {NextFunction} next -
*/
getDashboardBoot = async (
private getDashboardBoot = async (
req: Request,
res: Response,
next: NextFunction

View File

@@ -84,12 +84,11 @@ export class ExpensesController extends BaseController {
/**
* Expense DTO schema.
*/
get expenseDTOSchema() {
private get expenseDTOSchema() {
return [
check('reference_no')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('payment_date').exists().isISO8601().toDate(),
check('payment_account_id')
@@ -123,13 +122,15 @@ export class ExpensesController extends BaseController {
check('categories.*.description')
.optional()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
check('categories.*.project_id')
.optional({ nullable: true })
.isInt({ max: DATATYPES_LENGTH.INT_10 })
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -141,7 +142,6 @@ export class ExpensesController extends BaseController {
check('reference_no')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('payment_date').exists().isISO8601().toDate(),
check('payment_account_id')
@@ -176,13 +176,15 @@ export class ExpensesController extends BaseController {
check('categories.*.description')
.optional()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
check('categories.*.project_id')
.optional({ nullable: true })
.isInt({ max: DATATYPES_LENGTH.INT_10 })
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -269,7 +271,7 @@ export class ExpensesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
async deleteExpense(req: Request, res: Response, next: NextFunction) {
private async deleteExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const { id: expenseId } = req.params;
@@ -291,7 +293,11 @@ export class ExpensesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
async publishExpense(req: Request, res: Response, next: NextFunction) {
private async publishExpense(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId, user } = req;
const { id: expenseId } = req.params;
@@ -313,7 +319,11 @@ export class ExpensesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
async getExpensesList(req: Request, res: Response, next: NextFunction) {
private async getExpensesList(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = {
sortOrder: 'desc',
@@ -343,7 +353,7 @@ export class ExpensesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
async getExpense(req: Request, res: Response, next: NextFunction) {
private async getExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: expenseId } = req.params;

View File

@@ -0,0 +1,87 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { ServiceError } from '@/exceptions';
import { ExportApplication } from '@/services/Export/ExportApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { convertAcceptFormatToFormat } from './_utils';
@Service()
export class ExportController extends BaseController {
@Inject()
private exportResourceApp: ExportApplication;
/**
* Router constructor method.
*/
public router() {
const router = Router();
router.get(
'/',
[
query('resource').exists(),
query('format').isIn(['csv', 'xlsx']).optional(),
],
this.validationResult,
this.export.bind(this),
);
return router;
}
/**
* Imports xlsx/csv to the given resource type.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async export(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const applicationFormat = convertAcceptFormatToFormat(acceptType);
const data = await this.exportResourceApp.export(
tenantId,
query.resource,
applicationFormat
);
// Retrieves the csv format.
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(data);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
res.setHeader(
'Content-Disposition',
'attachment; filename=output.xlsx'
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(data);
//
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
res.set({
'Content-Type': 'application/pdf',
'Content-Length': data.length,
});
res.send(data);
}
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,13 @@
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { ExportFormat } from '@/services/Export/common';
export const convertAcceptFormatToFormat = (accept: string): ExportFormat => {
switch (accept) {
case ACCEPT_TYPE.APPLICATION_CSV:
return ExportFormat.Csv;
case ACCEPT_TYPE.APPLICATION_PDF:
return ExportFormat.Pdf;
case ACCEPT_TYPE.APPLICATION_XLSX:
return ExportFormat.Xlsx;
}
};

View File

@@ -1,9 +1,7 @@
import { query } from 'express-validator';
import BaseController from "../BaseController";
import BaseController from '../BaseController';
export default class BaseFinancialReportController extends BaseController {
get sheetNumberFormatValidationSchema() {
return [
query('number_format.precision')
@@ -19,8 +17,7 @@ export default class BaseFinancialReportController extends BaseController {
query('number_format.negative_format')
.optional()
.isIn(['parentheses', 'mines'])
.trim()
.escape(),
.trim(),
];
}
}
}

View File

@@ -51,8 +51,7 @@ export default class InventoryDetailsController extends BaseController {
query('number_format.negative_format')
.optional()
.isIn(['parentheses', 'mines'])
.trim()
.escape(),
.trim(),
query('from_date').optional(),
query('to_date').optional(),

View File

@@ -36,7 +36,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle
return [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
query('transaction_type').optional().trim().escape(),
query('transaction_type').optional().trim(),
query('transaction_id').optional().isInt().toInt(),
oneOf(
[

View File

@@ -40,8 +40,7 @@ export default class TransactionsByReferenceController extends BaseController {
query('number_format.negative_format')
.optional()
.isIn(['parentheses', 'mines'])
.trim()
.escape(),
.trim(),
];
}

View File

@@ -16,7 +16,7 @@ export class ImportController extends BaseController {
/**
* Router constructor method.
*/
router() {
public router() {
const router = Router();
router.post(
@@ -240,11 +240,7 @@ export class ImportController extends BaseController {
errors: [{ type: 'IMPORTED_FILE_EXTENSION_INVALID' }],
});
}
return res.status(400).send({
errors: [{ type: error.errorType }],
});
}
next(error);
}
}

View File

@@ -1,5 +1,6 @@
import Multer from 'multer';
import { ServiceError } from '@/exceptions';
import { getImportsStoragePath } from '@/services/Import/_utils';
export function allowSheetExtensions(req, file, cb) {
if (
@@ -16,7 +17,8 @@ export function allowSheetExtensions(req, file, cb) {
const storage = Multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/imports');
const path = getImportsStoragePath();
cb(null, path);
},
filename: function (req, file, cb) {
// Add the creation timestamp to clean up temp files later.

View File

@@ -86,7 +86,7 @@ export default class InventoryAdjustmentsController extends BaseController {
*/
get validateListQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('column_sort_by').optional().trim(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),

View File

@@ -25,7 +25,7 @@ export default class InviteUsersController extends BaseController {
router.post(
'/send',
[
body('email').exists().trim().escape(),
body('email').exists().trim(),
body('role_id').exists().isNumeric().toInt(),
],
this.validationResult,
@@ -57,7 +57,7 @@ export default class InviteUsersController extends BaseController {
);
router.get(
'/invited/:token',
[param('token').exists().trim().escape()],
[param('token').exists().trim()],
this.validationResult,
asyncMiddleware(this.invited.bind(this)),
this.handleServicesError
@@ -72,10 +72,10 @@ export default class InviteUsersController extends BaseController {
*/
private get inviteUserDTO() {
return [
check('first_name').exists().trim().escape(),
check('last_name').exists().trim().escape(),
check('password').exists().trim().escape().isLength({ min: 5 }),
param('token').exists().trim().escape(),
check('first_name').exists().trim(),
check('last_name').exists().trim(),
check('password').exists().trim().isLength({ min: 5 }),
param('token').exists().trim(),
];
}

View File

@@ -73,13 +73,11 @@ export default class ItemsCategoriesController extends BaseController {
check('name')
.exists()
.trim()
.escape()
.isLength({ min: 0, max: DATATYPES_LENGTH.STRING }),
check('description')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_account_id')
.optional({ nullable: true })
@@ -101,9 +99,8 @@ export default class ItemsCategoriesController extends BaseController {
*/
get categoriesListValidationSchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
query('column_sort_by').optional().trim(),
query('sort_order').optional().trim().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(),
];
}
@@ -207,14 +204,12 @@ export default class ItemsCategoriesController extends BaseController {
};
try {
const {
itemCategories,
filterMeta,
} = await this.itemCategoriesService.getItemCategoriesList(
tenantId,
itemCategoriesFilter,
user
);
const { itemCategories, filterMeta } =
await this.itemCategoriesService.getItemCategoriesList(
tenantId,
itemCategoriesFilter,
user
);
return res.status(200).send({
item_categories: itemCategories,
filter_meta: this.transfromToResponse(filterMeta),

View File

@@ -96,13 +96,11 @@ export default class ItemsController extends BaseController {
.exists()
.isString()
.trim()
.escape()
.isIn(['service', 'non-inventory', 'inventory']),
check('code')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
// Purchase attributes.
check('purchasable').optional().isBoolean().toBoolean(),
@@ -141,13 +139,11 @@ export default class ItemsController extends BaseController {
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('purchase_description')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
check('purchase_tax_rate_id')
@@ -162,7 +158,6 @@ export default class ItemsController extends BaseController {
.optional()
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('active').optional().isBoolean().toBoolean(),
@@ -184,7 +179,7 @@ export default class ItemsController extends BaseController {
*/
private get validateListQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('column_sort_by').optional().trim(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),

View File

@@ -77,14 +77,14 @@ export default class ManualJournalsController extends BaseController {
/**
* Specific manual journal id param validation schema.
*/
get manualJournalParamSchema() {
private get manualJournalParamSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Manual journal DTO schema.
*/
get manualJournalValidationSchema() {
private get manualJournalValidationSchema() {
return [
check('date').exists().isISO8601(),
check('currency_code').optional(),
@@ -94,25 +94,21 @@ export default class ManualJournalsController extends BaseController {
.optional()
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('journal_type')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('reference')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('description')
.optional({ nullable: true })
.isString()
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('publish').optional().isBoolean().toBoolean(),
@@ -148,19 +144,22 @@ export default class ManualJournalsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
/**
* Manual journals list validation schema.
*/
get manualJournalsListSchema() {
private get manualJournalsListSchema() {
return [
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
query('column_sort_by').optional().trim().escape(),
query('column_sort_by').optional().trim(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(),
@@ -320,7 +319,7 @@ export default class ManualJournalsController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
getManualJournalsList = async (
private getManualJournalsList = async (
req: Request,
res: Response,
next: NextFunction

View File

@@ -61,15 +61,14 @@ export default class MediaController extends BaseController {
get uploadValidationSchema() {
return [
// check('attachment'),
check('model_name').optional().trim().escape(),
check('model_id').optional().isNumeric().toInt(),
check('model_name').optional().trim(),
check('model_id').optional().isNumeric(),
];
}
get linkValidationSchema() {
return [
check('model_name').exists().trim().escape(),
check('model_name').exists().trim(),
check('model_id').exists().isNumeric().toInt(),
]
}

View File

@@ -0,0 +1,87 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Service, Inject } from 'typedi';
import { body } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '@/api/controllers/BaseController';
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
import config from '@/config';
@Service()
export class OneClickDemoController extends BaseController {
@Inject()
private oneClickDemoApp: OneClickDemoApplication;
/**
* Router constructor method.
*/
router() {
const router = Router();
// Protects the endpoints if the feature is not enabled.
const protectMiddleware = (
req: Request,
res: Response,
next: NextFunction
) => {
// Add your protection logic here
if (config.oneClickDemoAccounts) {
next();
} else {
res.status(403).send({ message: 'Forbidden' });
}
};
router.post(
'/one_click',
protectMiddleware,
asyncMiddleware(this.oneClickDemo.bind(this))
);
router.post(
'/one_click_signin',
[body('demo_id').exists()],
this.validationResult,
protectMiddleware,
asyncMiddleware(this.oneClickSignIn.bind(this))
);
return router;
}
/**
* One-click demo application.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
try {
const data = await this.oneClickDemoApp.createOneClick();
return res.status(200).send({
data,
message: 'The one-click demo has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Sign-in to one-click demo account.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async oneClickSignIn(
req: Request,
res: Response,
next: NextFunction
) {
const { demoId } = this.matchedBodyData(req);
try {
const data = await this.oneClickDemoApp.autoSignIn(demoId);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
}

View File

@@ -6,6 +6,7 @@ import { check, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import JWTAuth from '@/api/middleware/jwtAuth';
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import OrganizationService from '@/services/Organization/OrganizationService';
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
@@ -25,10 +26,13 @@ export default class OrganizationController extends BaseController {
router() {
const router = Router();
// Should before build tenant database the user be authorized and
// most important than that, should be subscribed to any plan.
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.use('/build', SubscriptionMiddleware('main'));
router.post(
'/build',
this.buildOrganizationValidationSchema,
@@ -52,13 +56,13 @@ export default class OrganizationController extends BaseController {
}
/**
* Organization setup schema.
* @return {ValidationChain[]}
* Build organization validation schema.
* @returns {ValidationChain[]}
*/
private get commonOrganizationValidationSchema(): ValidationChain[] {
private get buildOrganizationValidationSchema(): ValidationChain[] {
return [
check('name').exists().trim(),
check('industry').optional({ nullable: true }).isString().trim().escape(),
check('industry').optional({ nullable: true }).isString().trim(),
check('location').exists().isString().isISO31661Alpha2(),
check('base_currency').exists().isISO4217(),
check('timezone').exists().isIn(moment.tz.names()),
@@ -68,26 +72,35 @@ export default class OrganizationController extends BaseController {
];
}
/**
* Build organization validation schema.
* @returns {ValidationChain[]}
*/
private get buildOrganizationValidationSchema(): ValidationChain[] {
return [...this.commonOrganizationValidationSchema];
}
/**
* Update organization validation schema.
* @returns {ValidationChain[]}
*/
private get updateOrganizationValidationSchema(): ValidationChain[] {
return [
...this.commonOrganizationValidationSchema,
check('tax_number')
.optional({ nullable: true })
.isString()
.trim()
.escape(),
// # 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(),
];
}
@@ -156,7 +169,7 @@ export default class OrganizationController extends BaseController {
next: NextFunction
) {
const { tenantId } = req;
const tenantDTO = this.matchedBodyData(req);
const tenantDTO = this.matchedBodyData(req, { includeOptionals: false });
try {
await this.organizationService.updateOrganization(tenantId, tenantDTO);

View File

@@ -33,17 +33,17 @@ export default class OrganizationDashboardController extends BaseController {
}
/**
*
* @param req
* @param res
* @param next
* @returns
* Detarmines whether the current authed organization to able to change its currency/.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response|void}
*/
private async baseCurrencyMutateAbility(
req: Request,
res: Response,
next: Function
) {
): Promise<Response|void> {
const { tenantId } = req;
try {

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,195 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import BaseController from '@/api/controllers/BaseController';
import { PdfTemplateApplication } from '@/services/PdfTemplate/PdfTemplateApplication';
@Service()
export class PdfTemplatesController extends BaseController {
@Inject()
public pdfTemplateApplication: PdfTemplateApplication;
/**
* Router constructor method.
*/
public router() {
const router = Router();
router.delete(
'/:template_id',
[param('template_id').exists().isInt().toInt()],
this.validationResult,
this.deletePdfTemplate.bind(this)
);
router.post(
'/:template_id',
[
param('template_id').exists().isInt().toInt(),
check('template_name').exists(),
check('attributes').exists(),
],
this.validationResult,
this.editPdfTemplate.bind(this)
);
router.get('/state', this.getOrganizationBrandingState.bind(this));
router.get(
'/',
[query('resource').optional()],
this.validationResult,
this.getPdfTemplates.bind(this)
);
router.get(
'/:template_id',
[param('template_id').exists().isInt().toInt()],
this.validationResult,
this.getPdfTemplate.bind(this)
);
router.post(
'/',
[
check('template_name').exists(),
check('resource').exists(),
check('attributes').exists(),
],
this.validationResult,
this.createPdfInvoiceTemplate.bind(this)
);
router.post(
'/:template_id/assign_default',
[param('template_id').exists().isInt().toInt()],
this.validationResult,
this.assginPdfTemplateAsDefault.bind(this)
);
return router;
}
async createPdfInvoiceTemplate(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { templateName, resource, attributes } = this.matchedBodyData(req);
try {
const result = await this.pdfTemplateApplication.createPdfTemplate(
tenantId,
templateName,
resource,
attributes
);
return res.status(201).send({
id: result.id,
message: 'The PDF template has been created successfully.',
});
} catch (error) {
next(error);
}
}
async editPdfTemplate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { template_id: templateId } = req.params;
const editTemplateDTO = this.matchedBodyData(req);
try {
const result = await this.pdfTemplateApplication.editPdfTemplate(
tenantId,
Number(templateId),
editTemplateDTO
);
return res.status(200).send({
id: result.id,
message: 'The PDF template has been updated successfully.',
});
} catch (error) {
next(error);
}
}
async deletePdfTemplate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { template_id: templateId } = req.params;
try {
await this.pdfTemplateApplication.deletePdfTemplate(
tenantId,
Number(templateId)
);
return res.status(204).send({
id: templateId,
message: 'The PDF template has been deleted successfully.',
});
} catch (error) {
next(error);
}
}
async getPdfTemplate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { template_id: templateId } = req.params;
try {
const template = await this.pdfTemplateApplication.getPdfTemplate(
tenantId,
Number(templateId)
);
return res.status(200).send(template);
} catch (error) {
next(error);
}
}
async getPdfTemplates(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const templates = await this.pdfTemplateApplication.getPdfTemplates(
tenantId,
query
);
return res.status(200).send(templates);
} catch (error) {
next(error);
}
}
async assginPdfTemplateAsDefault(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { template_id: templateId } = req.params;
try {
await this.pdfTemplateApplication.assignPdfTemplateAsDefault(
tenantId,
Number(templateId)
);
return res.status(204).send({
id: templateId,
message: 'The given pdf template has been assigned as default template',
});
} catch (error) {
next(error);
}
}
async getOrganizationBrandingState(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
const data =
await this.pdfTemplateApplication.getPdfTemplateBrandingState(tenantId);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
}

View File

@@ -29,8 +29,7 @@ export class ProjectsController extends BaseController {
check('cost_estimate').exists().isDecimal(),
],
this.validationResult,
asyncMiddleware(this.createProject.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.createProject.bind(this))
);
router.post(
'/:id',
@@ -43,8 +42,7 @@ export class ProjectsController extends BaseController {
check('cost_estimate').exists().isDecimal(),
],
this.validationResult,
asyncMiddleware(this.editProject.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.editProject.bind(this))
);
router.patch(
'/:projectId/status',
@@ -56,16 +54,14 @@ export class ProjectsController extends BaseController {
.isIn([IProjectStatus.InProgress, IProjectStatus.Closed]),
],
this.validationResult,
asyncMiddleware(this.editProject.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.editProject.bind(this))
);
router.get(
'/:id',
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
[param('id').exists().isInt().toInt()],
this.validationResult,
asyncMiddleware(this.getProject.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.getProject.bind(this))
);
router.get(
'/:projectId/billable/entries',
@@ -76,24 +72,21 @@ export class ProjectsController extends BaseController {
query('to_date').optional().isISO8601(),
],
this.validationResult,
asyncMiddleware(this.projectBillableEntries.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.projectBillableEntries.bind(this))
);
router.get(
'/',
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
[],
this.validationResult,
asyncMiddleware(this.getProjects.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.getProjects.bind(this))
);
router.delete(
'/:id',
CheckPolicies(ProjectAction.DELETE, AbilitySubject.Project),
[param('id').exists().isInt().toInt()],
this.validationResult,
asyncMiddleware(this.deleteProject.bind(this)),
this.catchServiceErrors
asyncMiddleware(this.deleteProject.bind(this))
);
return router;
}
@@ -252,22 +245,4 @@ export class ProjectsController extends BaseController {
next(error);
}
};
/**
* Transforms service errors to response.
* @param {Error}
* @param {Request} req
* @param {Response} res
* @param {ServiceError} error
*/
private catchServiceErrors(
error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
}
next(error);
}
}

View File

@@ -100,8 +100,8 @@ export default class BillsController extends BaseController {
*/
private get billValidationSchema() {
return [
check('bill_number').exists().trim().escape(),
check('reference_no').optional().trim().escape(),
check('bill_number').exists().trim(),
check('reference_no').optional().trim(),
check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(),
@@ -112,13 +112,12 @@ export default class BillsController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim().escape(),
check('note').optional().trim(),
check('open').default(false).isBoolean().toBoolean(),
check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
@@ -127,10 +126,7 @@ export default class BillsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.landed_cost')
.optional({ nullable: true })
.isBoolean()
@@ -142,12 +138,14 @@ export default class BillsController extends BaseController {
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
.escape()
.isString(),
check('entries.*.tax_rate_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -156,8 +154,8 @@ export default class BillsController extends BaseController {
*/
private get billEditValidationSchema() {
return [
check('bill_number').optional().trim().escape(),
check('reference_no').optional().trim().escape(),
check('bill_number').optional().trim(),
check('reference_no').optional().trim(),
check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(),
@@ -168,7 +166,7 @@ export default class BillsController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim().escape(),
check('note').optional().trim(),
check('open').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }),
@@ -182,14 +180,14 @@ export default class BillsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.landed_cost')
.optional({ nullable: true })
.isBoolean()
.toBoolean(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -217,8 +215,8 @@ export default class BillsController extends BaseController {
private get dueBillsListingValidationSchema() {
return [
query('vendor_id').optional().trim().escape(),
query('payment_made_id').optional().trim().escape(),
query('vendor_id').optional().trim(),
query('payment_made_id').optional().trim(),
];
}

View File

@@ -111,17 +111,21 @@ export default class BillsPayments extends BaseController {
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_account_id').exists().isNumeric().toInt(),
check('payment_number').optional({ nullable: true }).trim().escape(),
check('payment_number').optional({ nullable: true }).trim(),
check('payment_date').exists(),
check('statement').optional().trim().escape(),
check('reference').optional().trim().escape(),
check('statement').optional().trim(),
check('reference').optional().trim(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').exists().isArray({ min: 1 }),
check('entries').exists().isArray(),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}

View File

@@ -156,13 +156,10 @@ export default class VendorCreditController extends BaseController {
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number')
.optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_number').optional({ nullable: true }).trim(),
check('reference_no').optional().trim(),
check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim().escape(),
check('note').optional().trim(),
check('open').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -178,14 +175,14 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -199,13 +196,10 @@ export default class VendorCreditController extends BaseController {
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number')
.optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_number').optional({ nullable: true }).trim(),
check('reference_no').optional().trim(),
check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim().escape(),
check('note').optional().trim(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -220,14 +214,14 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
];
}
@@ -297,8 +291,7 @@ export default class VendorCreditController extends BaseController {
try {
const vendorCredit = await this.createVendorCreditService.newVendorCredit(
tenantId,
vendorCreditCreateDTO,
user
vendorCreditCreateDTO
);
return res.status(200).send({

View File

@@ -18,9 +18,7 @@ export default class ResourceController extends BaseController {
router.get(
'/:resource_model/meta',
[
param('resource_model').exists().trim().escape()
],
[param('resource_model').exists().trim()],
this.asyncMiddleware(this.resourceMeta.bind(this)),
this.handleServiceErrors
);
@@ -48,9 +46,7 @@ export default class ResourceController extends BaseController {
resourceModel
);
return res.status(200).send({
resource_meta: this.transfromToResponse(
resourceMeta,
),
resource_meta: this.transfromToResponse(resourceMeta),
});
} catch (error) {
next(error);

View File

@@ -27,6 +27,7 @@ import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCr
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { GetCreditNoteState } from '@/services/CreditNotes/GetCreditNoteState';
/**
* Credit notes controller.
* @service
@@ -81,6 +82,9 @@ export default class PaymentReceivesController extends BaseController {
@Inject()
creditNotePdf: GetCreditNotePdf;
@Inject()
getCreditNoteStateService: GetCreditNoteState;
/**
* Router constructor.
*/
@@ -105,6 +109,12 @@ export default class PaymentReceivesController extends BaseController {
this.asyncMiddleware(this.newCreditNote),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(CreditNoteAction.View, AbilitySubject.CreditNote),
this.asyncMiddleware(this.getCreditNoteState.bind(this)),
this.handleServiceErrors
);
// Get specific credit note.
router.get(
'/:id',
@@ -210,9 +220,9 @@ export default class PaymentReceivesController extends BaseController {
check('credit_note_date').exists().isISO8601().toDate(),
check('reference_no').optional(),
check('credit_note_number').optional({ nullable: true }).trim().escape(),
check('note').optional().trim().escape(),
check('terms_conditions').optional().trim().escape(),
check('credit_note_number').optional({ nullable: true }).trim(),
check('note').optional().trim(),
check('terms_conditions').optional().trim(),
check('open').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -228,14 +238,17 @@ export default class PaymentReceivesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
@@ -338,8 +351,7 @@ export default class PaymentReceivesController extends BaseController {
try {
const creditNote = await this.createCreditNoteService.newCreditNote(
tenantId,
creditNoteDTO,
user
creditNoteDTO
);
return res.status(200).send({
id: creditNote.id,
@@ -459,13 +471,14 @@ export default class PaymentReceivesController extends BaseController {
ACCEPT_TYPE.APPLICATION_PDF,
]);
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
const [pdfContent, filename] = await this.creditNotePdf.getCreditNotePdf(
tenantId,
creditNoteId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
} else {
@@ -734,6 +747,23 @@ export default class PaymentReceivesController extends BaseController {
}
};
private getCreditNoteState = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
try {
const data = await this.getCreditNoteStateService.getCreditNoteState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error

View File

@@ -9,9 +9,9 @@ import {
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
import PaymentsReceivedPages from '@/services/Sales/PaymentReceived/PaymentsReceivedPages';
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceived/PaymentReceivedApplication';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@@ -22,7 +22,7 @@ export default class PaymentReceivesController extends BaseController {
private paymentReceiveApplication: PaymentReceivesApplication;
@Inject()
private PaymentReceivesPages: PaymentReceivesPages;
private PaymentsReceivedPages: PaymentsReceivedPages;
@Inject()
private dynamicListService: DynamicListingService;
@@ -95,6 +95,12 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.getPaymentReceiveInvoices.bind(this)),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
this.getPaymentReceivedState.bind(this),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(PaymentReceiveAction.View, AbilitySubject.PaymentReceive),
@@ -150,20 +156,26 @@ export default class PaymentReceivesController extends BaseController {
check('customer_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_date').exists(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
check('statement').optional().trim().escape(),
check('payment_receive_no').optional({ nullable: true }).trim(),
check('statement').optional().trim(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({ min: 1 }),
check('entries').isArray({}),
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
@@ -173,7 +185,6 @@ export default class PaymentReceivesController extends BaseController {
private get validatePaymentReceiveList(): ValidationChain[] {
return [
query('stringified_filter_roles').optional().isJSON(),
query('view_slug').optional({ nullable: true }).isString().trim(),
query('column_sort_by').optional(),
@@ -227,7 +238,7 @@ export default class PaymentReceivesController extends BaseController {
try {
const storedPaymentReceive =
await this.paymentReceiveApplication.createPaymentReceive(
await this.paymentReceiveApplication.createPaymentReceived(
tenantId,
paymentReceive,
user
@@ -374,7 +385,7 @@ export default class PaymentReceivesController extends BaseController {
const { customerId } = this.matchedQueryData(req);
try {
const entries = await this.PaymentReceivesPages.getNewPageEntries(
const entries = await this.PaymentsReceivedPages.getNewPageEntries(
tenantId,
customerId
);
@@ -386,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.
* @async
@@ -402,7 +436,7 @@ export default class PaymentReceivesController extends BaseController {
try {
const { paymentReceive, entries } =
await this.PaymentReceivesPages.getPaymentReceiveEditPage(
await this.PaymentsReceivedPages.getPaymentReceiveEditPage(
tenantId,
paymentReceiveId,
user
@@ -439,7 +473,7 @@ export default class PaymentReceivesController extends BaseController {
]);
// Response in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent =
const [pdfContent, filename] =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceiveId
@@ -447,6 +481,7 @@ export default class PaymentReceivesController extends BaseController {
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
// Response in json format.

View File

@@ -51,7 +51,7 @@ export default class SalesEstimatesController extends BaseController {
router.post(
'/:id/approve',
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
[this.validateSpecificEstimateSchema],
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.approveSaleEstimate.bind(this)),
this.handleServiceErrors
@@ -59,7 +59,7 @@ export default class SalesEstimatesController extends BaseController {
router.post(
'/:id/reject',
CheckPolicies(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate),
[this.validateSpecificEstimateSchema],
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
this.handleServiceErrors
@@ -105,6 +105,12 @@ export default class SalesEstimatesController extends BaseController {
asyncMiddleware(this.deleteEstimate.bind(this)),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
this.getSaleEstimateState.bind(this),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleEstimateAction.View, AbilitySubject.SaleEstimate),
@@ -155,7 +161,7 @@ export default class SalesEstimatesController extends BaseController {
check('estimate_date').exists().isISO8601().toDate(),
check('expiration_date').exists().isISO8601().toDate(),
check('reference').optional(),
check('estimate_number').optional().trim().escape(),
check('estimate_number').optional().trim(),
check('delivered').default(false).isBoolean().toBoolean(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -168,10 +174,7 @@ export default class SalesEstimatesController extends BaseController {
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -181,9 +184,15 @@ export default class SalesEstimatesController extends BaseController {
.isNumeric()
.toInt(),
check('note').optional().trim().escape(),
check('terms_conditions').optional().trim().escape(),
check('send_to_email').optional().trim().escape(),
check('note').optional().trim(),
check('terms_conditions').optional().trim(),
check('send_to_email').optional().trim(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
@@ -389,13 +398,15 @@ export default class SalesEstimatesController extends BaseController {
]);
// Retrieves estimate in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimateId
);
const [pdfContent, filename] =
await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimateId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
// Retrieves estimates in json format.
@@ -543,6 +554,23 @@ export default class SalesEstimatesController extends BaseController {
}
};
private getSaleEstimateState = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
try {
const data = await this.saleEstimatesApplication.getSaleEstimateState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error

View File

@@ -36,6 +36,8 @@ export default class SaleInvoicesController extends BaseController {
[
...this.saleInvoiceValidationSchema,
check('from_estimate_id').optional().isNumeric().toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
],
this.validationResult,
asyncMiddleware(this.newSaleInvoice.bind(this)),
@@ -98,6 +100,8 @@ export default class SaleInvoicesController extends BaseController {
[
...this.saleInvoiceValidationSchema,
...this.specificSaleInvoiceValidation,
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
],
this.validationResult,
asyncMiddleware(this.editSaleInvoice.bind(this)),
@@ -126,6 +130,12 @@ export default class SaleInvoicesController extends BaseController {
this.asyncMiddleware(this.getInvoicePaymentTransactions),
this.handleServiceErrors
);
router.get(
'/state',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
asyncMiddleware(this.getSaleInvoiceState.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
@@ -134,6 +144,7 @@ export default class SaleInvoicesController extends BaseController {
asyncMiddleware(this.getSaleInvoice.bind(this)),
this.handleServiceErrors
);
router.get(
'/',
CheckPolicies(SaleInvoiceAction.View, AbilitySubject.SaleInvoice),
@@ -168,10 +179,21 @@ export default class SaleInvoicesController extends BaseController {
'/:id/mail',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('subject').isString().optional({ nullable: true }),
body('message').isString().optional({ nullable: true }),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('to').isArray().exists(),
body('to.*').isString().isEmail().optional(),
body('cc').isArray().optional({ nullable: true }),
body('cc.*').isString().isEmail().optional(),
body('bcc').isArray().optional({ nullable: true }),
body('bcc.*').isString().isEmail().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
@@ -179,7 +201,7 @@ export default class SaleInvoicesController extends BaseController {
this.handleServiceErrors
);
router.get(
'/:id/mail',
'/:id/mail/state',
[...this.specificSaleInvoiceValidation],
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
@@ -196,12 +218,12 @@ export default class SaleInvoicesController extends BaseController {
check('customer_id').exists().isNumeric().toInt(),
check('invoice_date').exists().isISO8601().toDate(),
check('due_date').exists().isISO8601().toDate(),
check('invoice_no').optional().trim().escape(),
check('reference_no').optional().trim().escape(),
check('invoice_no').optional().trim(),
check('reference_no').optional().trim(),
check('delivered').default(false).isBoolean().toBoolean(),
check('invoice_message').optional().trim().escape(),
check('terms_conditions').optional().trim().escape(),
check('invoice_message').optional().trim(),
check('terms_conditions').optional().trim(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -220,14 +242,10 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
.escape()
.isString(),
check('entries.*.tax_rate_id')
.optional({ nullable: true })
@@ -255,6 +273,14 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
// Payment methods.
check('payment_methods').optional({ nullable: true }).isArray(),
check('payment_methods.*.payment_integration_id').exists().toInt(),
check('payment_methods.*.enable').exists().isBoolean(),
];
}
@@ -423,19 +449,28 @@ export default class SaleInvoicesController extends BaseController {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
ACCEPT_TYPE.APPLICATION_TEXT_HTML,
]);
// Retrieves invoice in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
// Retrieves invoice in PDF format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const [pdfContent, filename] =
await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
// Retrieves invoice in json format.
// Retrieves invoice in html json format.
} else if (ACCEPT_TYPE.APPLICATION_TEXT_HTML === acceptType) {
const htmlContent = await this.saleInvoiceApplication.saleInvoiceHtml(
tenantId,
saleInvoiceId
);
return res.status(200).send({ htmlContent });
} else {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
tenantId,
@@ -445,6 +480,24 @@ export default class SaleInvoicesController extends BaseController {
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.
* @param {Request} req
@@ -743,7 +796,7 @@ export default class SaleInvoicesController extends BaseController {
}
/**
* Retrieves the default mail options of the given sale invoice.
* Retrieves the mail state of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
@@ -757,7 +810,7 @@ export default class SaleInvoicesController extends BaseController {
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
const data = await this.saleInvoiceApplication.getSaleInvoiceMailState(
tenantId,
invoiceId
);

View File

@@ -108,6 +108,12 @@ export default class SalesReceiptsController extends BaseController {
this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse
);
router.get(
'/state',
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
asyncMiddleware(this.getSaleReceiptState.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt),
@@ -130,8 +136,8 @@ export default class SalesReceiptsController extends BaseController {
check('deposit_account_id').exists().isNumeric().toInt(),
check('receipt_date').exists().isISO8601(),
check('receipt_number').optional().trim().escape(),
check('reference_no').optional().trim().escape(),
check('receipt_number').optional().trim(),
check('reference_no').optional().trim(),
check('closed').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -148,16 +154,20 @@ export default class SalesReceiptsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('receipt_message').optional().trim().escape(),
check('statement').optional().trim().escape(),
check('receipt_message').optional().trim(),
check('statement').optional().trim(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
@@ -346,13 +356,15 @@ export default class SalesReceiptsController extends BaseController {
]);
// Retrieves receipt in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceiptId
);
const [pdfContent, filename] =
await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceiptId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
// Retrieves receipt in json format.
@@ -365,6 +377,30 @@ export default class SalesReceiptsController extends BaseController {
}
}
/**
*
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleReceiptState(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
// Retrieves receipt in pdf format.
try {
const data = await this.saleReceiptsApplication.getSaleReceiptState(
tenantId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Sale receipt notification via SMS.
* @param {Request} req

View File

@@ -52,10 +52,7 @@ export default class SettingsController extends BaseController {
* Retrieve the application options from the storage.
*/
private get getSettingsSchema() {
return [
query('key').optional().trim().escape(),
query('group').optional().trim().escape(),
];
return [query('key').optional().trim(), query('group').optional().trim()];
}
/**

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,63 @@
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,
publicity,
expiryDate
);
res.status(200).json({ link });
} catch (error) {
next(error);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,180 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Service, Inject } from 'typedi';
import { body } from 'express-validator';
import JWTAuth from '@/api/middleware/jwtAuth';
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import SubscriptionService from '@/services/Subscription/SubscriptionService';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController';
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
import { SubscriptionApplication } from '@/services/Subscription/SubscriptionApplication';
@Service()
export class SubscriptionController extends BaseController {
@Inject()
private subscriptionService: SubscriptionService;
@Inject()
private lemonSqueezyService: LemonSqueezyService;
@Inject()
private subscriptionApp: SubscriptionApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.post(
'/lemon/checkout_url',
[body('variantId').exists().trim()],
this.validationResult,
this.getCheckoutUrl.bind(this)
);
router.post('/cancel', asyncMiddleware(this.cancelSubscription.bind(this)));
router.post('/resume', asyncMiddleware(this.resumeSubscription.bind(this)));
router.post(
'/change',
[body('variant_id').exists().trim()],
this.validationResult,
asyncMiddleware(this.changeSubscriptionPlan.bind(this))
);
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
return router;
}
/**
* Retrieve all subscriptions of the authenticated user's tenant.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getSubscriptions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
const subscriptions = await this.subscriptionService.getSubscriptions(
tenantId
);
return res.status(200).send({ subscriptions });
} catch (error) {
next(error);
}
}
/**
* Retrieves the LemonSqueezy checkout url.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getCheckoutUrl(
req: Request,
res: Response,
next: NextFunction
) {
const { variantId } = this.matchedBodyData(req);
const { user } = req;
try {
const checkout = await this.lemonSqueezyService.getCheckout(
variantId,
user
);
return res.status(200).send(checkout);
} catch (error) {
next(error);
}
}
/**
* Cancels the subscription of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async cancelSubscription(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
await this.subscriptionApp.cancelSubscription(tenantId);
return res.status(200).send({
status: 200,
message: 'The organization subscription has been canceled.',
});
} catch (error) {
next(error);
}
}
/**
* Resumes the subscription of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private async resumeSubscription(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
await this.subscriptionApp.resumeSubscription(tenantId);
return res.status(200).send({
status: 200,
message: 'The organization subscription has been resumed.',
});
} catch (error) {
next(error);
}
}
/**
* Changes the main subscription plan of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
public async changeSubscriptionPlan(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const body = this.matchedBodyData(req);
try {
await this.subscriptionApp.changeSubscriptionPlan(
tenantId,
body.variantId
);
return res.status(200).send({
message: 'The subscription plan has been changed.',
});
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1 @@
export * from './SubscriptionController';

View File

@@ -155,6 +155,7 @@ export default class UsersController extends BaseController {
try {
const user = await this.usersService.getUser(tenantId, userId);
return res.status(200).send({ user });
} catch (error) {
next(error);
@@ -229,7 +230,7 @@ export default class UsersController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
catchServiceErrors(
private catchServiceErrors(
error: Error,
req: Request,
res: Response,

View File

@@ -32,7 +32,7 @@ export default class ViewsController extends BaseController {
* Custom views list validation schema.
*/
get viewsListSchemaValidation() {
return [param('resource_model').exists().trim().escape()];
return [param('resource_model').exists().trim()];
}
/**

View File

@@ -17,7 +17,7 @@ export class WarehousesController extends BaseController {
*
* @returns
*/
router() {
public router() {
const router = Router();
router.post(

View File

@@ -1,47 +1,83 @@
import { Router } from 'express';
import { NextFunction, Router, Request, Response } from 'express';
import Container, { Inject, Service } from 'typedi';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
import { Request, Response } from 'express';
import { Inject, Service } from 'typedi';
import BaseController from '../BaseController';
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
import { StripeWebhooksController } from '../StripeIntegration/StripeWebhooksController';
@Service()
export class Webhooks extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
@Inject()
private lemonWebhooksService: LemonSqueezyWebhooks;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use(PlaidWebhookTenantBootMiddleware);
router.use('/plaid', PlaidWebhookTenantBootMiddleware);
router.post('/plaid', this.plaidWebhooks.bind(this));
router.post('/lemon', this.lemonWebhooks.bind(this));
router.use(Container.get(StripeWebhooksController).router());
return router;
}
/**
* Listens to Lemon Squeezy webhooks events.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
const data = req.body;
const signature = req.headers['x-signature'] as string ?? '';
const rawBody = req.rawBody;
try {
await this.lemonWebhooksService.handlePostWebhook(
rawBody,
data,
signature
);
return res.status(200).send();
} catch (error) {
next(error);
}
}
/**
* Listens to Plaid webhooks.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async plaidWebhooks(req: Request, res: Response) {
public async plaidWebhooks(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const {
webhook_type: webhookType,
webhook_code: webhookCode,
item_id: plaidItemId,
} = req.body;
await this.plaidApp.webhooks(
tenantId,
plaidItemId,
webhookType,
webhookCode
);
return res.status(200).send({ code: 200, message: 'ok' });
try {
const {
webhook_type: webhookType,
webhook_code: webhookCode,
item_id: plaidItemId,
} = req.body;
await this.plaidApp.webhooks(
tenantId,
plaidItemId,
webhookType,
webhookCode
);
return res.status(200).send({ code: 200, message: 'ok' });
} catch (error) {
next(error);
}
}
}

Some files were not shown because too many files have changed in this diff Show More