Compare commits

..

163 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
278c8a01c5 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2023-07-18 20:03:31 +02:00
Ahmed Bouhuolia
f22dc9a18b fix(webapp): assign currency code of customer/vendor to the transaction form. 2023-07-18 20:02:45 +02:00
Ahmed Bouhuolia
b224a2c313 Merge pull request #196 from bigcapitalhq/fix-loading-status-on-financial-reports
fix(webapp): show loading message of cost computing job on financial reports
2023-07-17 01:51:02 +02:00
Ahmed Bouhuolia
d4a933ef18 fix(webapp): show loading message of cost computing job on financial reports 2023-07-17 01:41:13 +02:00
Ahmed Bouhuolia
da514403a1 chore: add recognition to README.md file 2023-07-07 02:34:00 +02:00
Ahmed Bouhuolia
d57f9e5171 chore: update README.md 2023-07-07 02:24:59 +02:00
Ahmed Bouhuolia
0a1299b8a6 Merge pull request #190 from bigcapitalhq/all-contributors/add-scheibling
docs: add scheibling as a contributor for bug
2023-07-07 02:21:52 +02:00
allcontributors[bot]
326a2038e7 docs: update .all-contributorsrc [skip ci] 2023-07-07 00:21:40 +00:00
allcontributors[bot]
94bb153120 docs: update README.md [skip ci] 2023-07-07 00:21:39 +00:00
Ahmed Bouhuolia
223756b7ae Update README.md 2023-07-07 02:20:47 +02:00
Ahmed Bouhuolia
a773ee9966 Merge pull request #189 from bigcapitalhq/all-contributors/add-ameir
docs: add ameir as a contributor for bug
2023-07-07 02:20:11 +02:00
Ahmed Bouhuolia
47790fba84 Merge branch 'develop' into all-contributors/add-ameir 2023-07-07 02:20:06 +02:00
Ahmed Bouhuolia
b122fdc43a Merge pull request #188 from bigcapitalhq/all-contributors/add-elforjani13
docs: add elforjani13 as a contributor for code
2023-07-07 02:18:56 +02:00
allcontributors[bot]
cb93f313ec docs: update .all-contributorsrc [skip ci] 2023-07-07 00:18:32 +00:00
allcontributors[bot]
74c31c5f20 docs: update README.md [skip ci] 2023-07-07 00:18:31 +00:00
allcontributors[bot]
d411ae3e68 docs: update .all-contributorsrc [skip ci] 2023-07-07 00:16:16 +00:00
allcontributors[bot]
aecef878ba docs: update README.md [skip ci] 2023-07-07 00:16:15 +00:00
Ahmed Bouhuolia
ec90056f7b Merge pull request #186 from bigcapitalhq/all-contributors/add-abouolia
docs: add abouolia as a contributor for code
2023-07-07 02:14:40 +02:00
Ahmed Bouhuolia
30f2f1fb4c Update README.md 2023-07-07 02:13:25 +02:00
Ahmed Bouhuolia
c72f5374f3 Update README.md 2023-07-07 02:13:13 +02:00
allcontributors[bot]
66de03b143 docs: create .all-contributorsrc [skip ci] 2023-07-07 00:07:19 +00:00
allcontributors[bot]
2b33583a03 docs: update README.md [skip ci] 2023-07-07 00:07:18 +00:00
Ahmed Bouhuolia
e5611b4446 Merge pull request #176 from bigcapitalhq/e2e-onboarding
feat(e2e): E2E onboarding process
2023-07-07 01:32:49 +02:00
Ahmed Bouhuolia
d12157a8d4 Merge branch 'develop' into e2e-onboarding 2023-07-07 01:26:58 +02:00
Ahmed Bouhuolia
b24badfa52 chore: add api rewrite to vercel configure 2023-07-07 01:14:37 +02:00
Ahmed Bouhuolia
c992562760 update CHANGELOG.md 2023-06-28 19:37:33 +02:00
Ahmed Bouhuolia
485138344c Merge pull request #182 from bigcapitalhq/abouhuolia/big-20-selecting-the-default-branch-for-opening-balance-branch-in
fix(webapp): no default branch for customer/vendor opening balance branch
2023-06-28 18:15:19 +02:00
Ahmed Bouhuolia
8d990ae85d fix(webapp): no default branch for customer/vendor opening balance branch 2023-06-28 18:07:47 +02:00
Ahmed Bouhuolia
7fbe51ddf2 Merge pull request #179 from bigcapitalhq/abouhuolia/big-29-no-currency-in-amount-field-on-money-inout-dialogs
fix(webapp): No currency in amount field on money in/out dialogs
2023-06-28 12:27:12 +02:00
Ahmed Bouhuolia
215eb97762 Merge branch 'develop' into abouhuolia/big-29-no-currency-in-amount-field-on-money-inout-dialogs 2023-06-28 12:23:06 +02:00
Ahmed Bouhuolia
3d4fd0b904 fix(webapp): transaction number duplicated variable name 2023-06-28 12:21:22 +02:00
Ahmed Bouhuolia
e552ff6449 Merge pull request #180 from bigcapitalhq/abouhuolia/big-40-storing-cash-flow-transaction-description
fix: Storing cash flow transaction description
2023-06-28 11:08:23 +02:00
Ahmed Bouhuolia
268942af42 Merge pull request #181 from bigcapitalhq/abouhuolia/big-39-the-statement-note-does-not-saving-in-payment-receive-and
fix: internal note of invoice/bill payment does not saving
2023-06-28 11:07:55 +02:00
Ahmed Bouhuolia
02489a907a fix(webapp): internal note of invoice/bill payment does not saving 2023-06-28 11:06:33 +02:00
Ahmed Bouhuolia
4e0037d1c0 fix(server): showing transaction statement on cash flow transactions details drawer 2023-06-28 01:00:27 +02:00
Ahmed Bouhuolia
39786e5b1f fix(server): storing cash flow transaction statement to GL entry note 2023-06-28 00:58:47 +02:00
Ahmed Bouhuolia
6373862044 fix(webapp): No currency in amount field on money in/out dialogs 2023-06-27 21:32:08 +02:00
Ahmed Bouhuolia
b46154ba59 feat(e2e): add default extra header to new e2e browser pages 2023-06-27 00:25:44 +02:00
Ahmed Bouhuolia
59e3a4016b Merge pull request #177 from bigcapitalhq/abouhuolia/big-38-payment-made-form-does-not-handle-not-unique-number-an-error
fix(webapp): payment made form does not handle not unique number an e…
2023-06-26 23:08:57 +02:00
Ahmed Bouhuolia
b6a1c9ab4b fix(webapp): payment made form does not handle not unique number an error message 2023-06-26 20:57:52 +02:00
Ahmed Bouhuolia
7171fb2a69 Merge branch 'develop' into e2e-onboarding 2023-06-23 16:12:03 +02:00
Ahmed Bouhuolia
c64a14aef3 Merge branch 'develop' into e2e-onboarding 2023-06-23 16:10:47 +02:00
Ahmed Bouhuolia
ac539aed34 chore: remove debug from playwright script 2023-06-23 16:10:29 +02:00
Ahmed Bouhuolia
c7b4846cb0 Merge pull request #171 from bigcapitalhq/abouhuolia/big-21-close-select-icon-clashes-with-caret-icon
feat(webapp): refactor customer and vendor select component
2023-06-23 16:06:55 +02:00
Ahmed Bouhuolia
d54aac9b32 Merge branch 'develop' into abouhuolia/big-21-close-select-icon-clashes-with-caret-icon 2023-06-23 16:05:21 +02:00
Ahmed Bouhuolia
fe87713df0 Merge pull request #172 from bigcapitalhq/abouhuolia/big-29-no-currency-in-amount-field-on-money-inout-dialogs
fix(webapp): should not show the form before loading account
2023-06-23 15:57:06 +02:00
Ahmed Bouhuolia
4770fdf709 Merge pull request #173 from bigcapitalhq/abouhuolia/big-37-item-drawer-floating
fix(webapp): style of quick item drawer
2023-06-23 15:55:58 +02:00
Ahmed Bouhuolia
1b3c525ba5 Merge pull request #170 from bigcapitalhq/split-components-in-seprate-files
chore(webapp): move auto-increment components in separate files
2023-06-23 15:55:22 +02:00
Ahmed Bouhuolia
b35d22d3b3 feat(e2e): onboarding process 2023-06-23 14:32:36 +02:00
Ahmed Bouhuolia
44fce6f33e feat(e2e): WIP e2e onboarding process 2023-06-23 02:45:30 +02:00
Ahmed Bouhuolia
e58a1d6ad1 chore(webapp): localization tweaks 2023-06-22 22:12:23 +02:00
Ahmed Bouhuolia
5f191cf335 fix(webapp): customer/vendor select should update deps 2023-06-22 22:07:39 +02:00
Ahmed Bouhuolia
46bf1cc39a fix(webapp): style of quick item drawer 2023-06-22 20:48:27 +02:00
Ahmed Bouhuolia
c98fe00f88 fix(webapp): should not show the form before loading account 2023-06-22 20:13:29 +02:00
Ahmed Bouhuolia
4b95c19d3e chore: update @blueprintjs-formik/select to the latest version 2023-06-22 19:54:54 +02:00
Ahmed Bouhuolia
eadaac30d6 feat(webapp): refactor customer and vendor select component 2023-06-22 17:26:33 +02:00
Ahmed Bouhuolia
ca4d543482 fix(webapp): quick create item drawer 2023-06-22 01:51:42 +02:00
Ahmed Bouhuolia
1f3adf4879 chore(webapp): move components in separate files 2023-06-22 01:30:05 +02:00
Ahmed Bouhuolia
532ad61500 Merge pull request #169 from bigcapitalhq/abouolia-patch-1
Rename writeInvoicesJEntries.ts to WriteInvoicesJEntries.ts
2023-06-19 23:03:25 +02:00
Ahmed Bouhuolia
a44d779670 Rename writeInvoicesJEntries.ts to WriteInvoicesJEntries.ts 2023-06-19 23:00:22 +02:00
Ahmed Bouhuolia
ab5f9f50d0 chore: update CHANGELOG file 2023-06-19 22:58:33 +02:00
Ahmed Bouhuolia
f3f10db6db chore: change the default BASE_URL env var value 2023-06-19 22:56:58 +02:00
Ahmed Bouhuolia
de5694681b Merge pull request #168 from bigcapitalhq/abouhuolia/big-30-create-a-new-warehouse-transfer-broken
fix(webapp): warehouses select component
2023-06-19 22:44:56 +02:00
Ahmed Bouhuolia
b1a997c287 fix(webapp): warehouses select component 2023-06-19 22:43:19 +02:00
Ahmed Bouhuolia
3e36146bce Merge pull request #167 from bigcapitalhq/abouhuolia/big-33-sending-emails-on-reset-password-and-registration
fix(server): sending emails on reset password and registration
2023-06-19 16:04:25 +02:00
Ahmed Bouhuolia
db833888c8 Merge pull request #160 from bigcapitalhq/abouhuolia/big-17-issue-in-manual-journal-placeholder-text
fix(webapp): manual journal placeholder text
2023-06-19 16:02:12 +02:00
Ahmed Bouhuolia
c415e3d693 Merge pull request #161 from bigcapitalhq/abouhuolia/big-32-sidebar
fix(webapp): rename sidebar localization keys names to be keyword path
2023-06-19 16:01:43 +02:00
Ahmed Bouhuolia
e145eabf02 Merge pull request #162 from bigcapitalhq/abouhuolia/big-22-make-the-remove-line-text-to-be-in-red
fix(webapp): change the remove line option text to be red to intent as danger action
2023-06-19 16:00:54 +02:00
Ahmed Bouhuolia
caab21647d Merge pull request #159 from bigcapitalhq/abouhuolia/big-24-adjustment-type-options-do-not-show-up-2
fix(webapp): adjustment type options do not show up
2023-06-19 16:00:20 +02:00
Ahmed Bouhuolia
98528e9e5b Merge pull request #158 from bigcapitalhq/abouhuolia/big-26-add-inventory-adjustment-option-to-the-item-drawer
feat(webapp): add Inventory Adjustment option to the item drawer
2023-06-19 15:59:11 +02:00
Ahmed Bouhuolia
b993fad37f fix(webapp): change the min password length of reset password 2023-06-19 15:55:20 +02:00
Ahmed Bouhuolia
94ea44b58e fix(server): change the reigster min password length 2023-06-19 15:55:06 +02:00
Ahmed Bouhuolia
877a57043a fix(server): sending emails on reset password and registration 2023-06-19 15:36:18 +02:00
Ahmed Bouhuolia
70415d1d63 fix(webapp): change the remove line option text to be red to intent as danger action 2023-06-15 20:49:23 +02:00
Ahmed Bouhuolia
ef2d1977d6 fix(webapp): rename sidebar localization keys names to be keyword path 2023-06-15 20:12:31 +02:00
Ahmed Bouhuolia
46ea26891d fix(webapp): manual journal placeholder text 2023-06-15 20:08:39 +02:00
Ahmed Bouhuolia
f750cede89 fix(webapp): adjustment type options do not show up 2023-06-15 20:00:34 +02:00
Ahmed Bouhuolia
e5d0f16096 feat(webapp): add Inventory Adjustment option to the item drawer 2023-06-15 19:49:23 +02:00
Ahmed Bouhuolia
706694c768 Merge pull request #157 from bigcapitalhq/abouhuolia/big-23-payment-made-details-drawer-does-not-show-up-3
fix(webapp): use all drawers names from common enum object
2023-06-14 21:53:55 +02:00
Ahmed Bouhuolia
d1a09e3b15 fix(webapp): use all drawers name from common enum object 2023-06-14 19:51:14 +02:00
Ahmed Bouhuolia
01c27b56ef Merge pull request #155 from bigcapitalhq/abouhuolia/big-19-delete-expense-transaction-does-not-work
fix(server): the expense transaction journal entries
2023-06-14 13:05:34 +02:00
Ahmed Bouhuolia
0d8fb8cf25 fix(server): the expense transaction journal entries 2023-06-14 13:00:26 +02:00
Ahmed Bouhuolia
6562e3ab8c chore: update CHANGELOG.md 2023-06-14 12:10:35 +02:00
Ahmed Bouhuolia
c93650ffd3 Merge pull request #139 from bigcapitalhq/change-onboarding-footer-links
chore(webapp): change the footer links of onboarding pages
2023-06-13 23:45:16 +02:00
Ahmed Bouhuolia
e6a2825065 chore(webapp): change the footer links of onboarding pages 2023-06-13 20:30:29 +02:00
Ahmed Bouhuolia
0c2a0b0010 Update README.md 2023-06-12 22:55:57 +02:00
Ahmed Bouhuolia
a332c51249 chore: change the default SIGNUP_DISABLED env variable 2023-06-12 21:15:27 +02:00
Ahmed Bouhuolia
0b6d0bc016 dump version 0.9.6 2023-06-12 19:43:36 +02:00
Ahmed Bouhuolia
e6336b1451 Merge pull request #136 from bigcapitalhq/add-monorepo-version-on-sidebar
feat(webapp): add monorepo version on the sidebar
2023-06-12 19:42:37 +02:00
Ahmed Bouhuolia
46290c4d37 Merge pull request #138 from bigcapitalhq/remove-duplicated-form-submitting
fix(webapp): remove duplicated form submitting
2023-06-12 18:55:18 +02:00
Ahmed Bouhuolia
ff2b7563c8 fix(webapp): remove duplicated form submitting 2023-06-12 18:47:28 +02:00
Ahmed Bouhuolia
b9572420ed feat(webapp): add monorepo version on the sidebar 2023-06-12 02:44:12 +02:00
Ahmed Bouhuolia
35ebb9c2aa Merge pull request #134 from bigcapitalhq/contacts-opening-balance-exchange-rate
fix(webapp): customer/vendor opening balance with exchange rate
2023-06-11 20:12:44 +02:00
Ahmed Bouhuolia
3ebeb29dc0 chore(webapp): vendor tabs 2023-06-11 20:12:26 +02:00
Ahmed Bouhuolia
8e98068538 fix(webapp): vendor form fastField of opening balance field 2023-06-11 20:11:30 +02:00
Ahmed Bouhuolia
6a72594faf fix(webapp): update condition of customer opening balance 2023-06-11 20:09:51 +02:00
Ahmed Bouhuolia
728729094a Merge branch 'develop' into contacts-opening-balance-exchange-rate 2023-06-11 19:56:54 +02:00
Ahmed Bouhuolia
93d540fbd2 fix(webapp): style tweaks on customer/vendor tabs 2023-06-11 19:53:15 +02:00
Ahmed Bouhuolia
eb9b6ce717 Merge pull request #133 from bigcapitalhq/add-duplicate-icon-to-list
fix(webapp): add duplicate icon to customers and vendors table
2023-06-11 19:36:52 +02:00
Ahmed Bouhuolia
f716d42d26 Merge pull request #135 from bigcapitalhq/fix-make-journal-with-different-accounts
fix(webapp): make journal error when create journal with accounts have different currency
2023-06-11 19:36:37 +02:00
Ahmed Bouhuolia
1c4c364f06 Merge pull request #132 from bigcapitalhq/filter-ledger-entries-ar-ap
fix(server): filter ledger entries that effect contact balance to AR/AP accounts only
2023-06-11 19:35:53 +02:00
Ahmed Bouhuolia
162ad91547 fix(webapp): handle make journal error when create journal with accounts have different currency 2023-06-11 19:34:24 +02:00
Ahmed Bouhuolia
2950e5ede4 fix(webapp): customer/vendor opening balnace with exchange rate 2023-06-11 19:29:26 +02:00
Ahmed Bouhuolia
73b041d8d2 fix(webapp): add deuplicate icon to customers and vendors table 2023-06-11 19:25:39 +02:00
Ahmed Bouhuolia
7bf008a9cb fix(server): filter ledger entries that effect contact balance to AR/AP accounts only 2023-06-11 19:23:31 +02:00
Ahmed Bouhuolia
4d9e3ccfb4 fix(server): disable webpack minification for class name reading 2023-06-08 22:38:43 +02:00
Ahmed Bouhuolia
1bfe19f26c fix: change the default charset value 2023-06-08 13:19:58 +02:00
Ahmed Bouhuolia
a371cedb67 Merge pull request #130 from bigcapitalhq/normalized
fix docker-compose line-ending issue on Windows
2023-06-06 20:40:42 +02:00
Ahmed Bouhuolia
4ed9c36ebd feat: set default value to env vars 2023-06-06 20:37:42 +02:00
ElforJani13
e24b23ce7e 🐛 FIX: 2023-06-06 19:49:25 +02:00
Ahmed Bouhuolia
19fe6e2423 fix: normalize nginx bash the line endings 2023-06-06 18:37:21 +02:00
Ahmed Bouhuolia
aec09f178b Merge pull request #128 from bigcapitalhq/BIG-435-migrate-to-maria-db-v-11
feat: migrate the server to MariaDB
2023-06-04 14:45:47 +02:00
Ahmed Bouhuolia
ffe51bae07 feat: migrate the server to MariaDB 2023-06-04 14:38:53 +02:00
Ahmed Bouhuolia
68231d5edb Merge pull request #125 from bigcapitalhq/docker-compose-env-variables
feat: add env variable to customize the proxy public ports
2023-05-31 20:30:44 +02:00
a.bouhuolia
e1ea5c402c feat: add env variable to customize the proxy public ports 2023-05-31 20:29:37 +02:00
a.bouhuolia
34b2c2c8b4 fix: copy package-lock.json inside the container 2023-05-31 13:07:30 +02:00
a.bouhuolia
5d96fe6aa0 chore(webapp): remove Sentry from webapp 2023-05-31 09:59:35 +02:00
Ameir Abdeldayem
d2b5084b42 chore: fix typo in README file (#124) 2023-05-30 10:46:51 +02:00
a.bouhuolia
81fb0734d5 update CHANGELOG 2023-05-28 15:04:31 +02:00
a.bouhuolia
3639ce44e5 chore: bump CHANGELOG v0.9.1 2023-05-28 15:02:14 +02:00
Ahmed Bouhuolia
a7c00d60d5 Merge pull request #121 from bigcapitalhq/BIG-429-clean-up-the-auto-increment-of-transactions
fix: the auto-increment of transactions.
2023-05-28 14:50:43 +02:00
a.bouhuolia
932750b62d fix(webapp): fix credit note and receipt auto-increment 2023-05-28 14:45:34 +02:00
a.bouhuolia
c90ffed67f fix(webapp): payment receive auto-increment 2023-05-26 00:02:47 +02:00
a.bouhuolia
e92c4486aa fix(webapp): auto-increment estimate transactions 2023-05-25 22:04:21 +02:00
a.bouhuolia
aaceea5338 fix(webapp): warehouse and branch reset on invoice form 2023-05-24 23:52:05 +02:00
a.bouhuolia
4d54d180bc fix(webapp): invoice transactions increment 2023-05-24 23:28:09 +02:00
Ahmed Bouhuolia
8fdd98e34d Merge pull request #122 from bigcapitalhq/BIG-434-delete-invoice-transaction-issue
fix(server): delete invoice transaction
2023-05-23 15:12:43 +02:00
a.bouhuolia
d53c5ee5e6 fix(server): delete invoice transaction 2023-05-23 15:11:56 +02:00
a.bouhuolia
4082e4e2b8 fix: auto-increment transaction field 2023-05-23 14:39:57 +02:00
a.bouhuolia
0c689459cb fix: auto-increment cashflow transactions 2023-05-23 13:56:35 +02:00
a.bouhuolia
40ef02f215 fix: auto-increment settings 2023-05-22 21:57:43 +02:00
a.bouhuolia
d369f0bb17 fix: the auto-increment of transactions. 2023-05-19 00:29:35 +02:00
Ahmed Bouhuolia
425d0293cc Merge pull request #120 from bigcapitalhq/BIG-433-fix-base-currency-should-be-enabled-with-account-model
BIG-433-fix-base-currency-should-be-enabled-with-account-model
2023-05-12 15:58:48 +02:00
a.bouhuolia
b621650975 fix(server): base currency should be enabled with account model. 2023-05-12 15:58:01 +02:00
a.bouhuolia
40948160fe fix(webapp): localization 2023-05-12 12:46:59 +02:00
Ahmed Bouhuolia
aa6b9dd295 Merge pull request #118 from bigcapitalhq/BIG-428-clean-up-the-preferences-pages
fix(webapp): general, accoutant and items preferences
2023-05-12 12:36:19 +02:00
a.bouhuolia
05c2232b97 chore(webapp): refactor the setup organization form to use Formik binded component 2023-05-12 12:31:55 +02:00
Ahmed Bouhuolia
8f6325d529 Merge pull request #119 from bigcapitalhq/fix-delete-journals-manual-journal
fix(server): deleting ledger entries of manual journal
2023-05-12 00:14:36 +02:00
a.bouhuolia
0aa681043d fix(server): deleting ledger entries of manual journal 2023-05-11 22:46:34 +02:00
a.bouhuolia
40bddfdfeb fix(webapp): accrual typo 2023-05-11 21:07:49 +02:00
a.bouhuolia
d6e2f01d70 fix(server): accrual typo 2023-05-11 21:07:01 +02:00
a.bouhuolia
2344d3d34d fix(webapp): general, accoutant and items preferences 2023-05-11 01:47:09 +02:00
a.bouhuolia
883c5dcb41 Merge branch 'signup-restrictions' into develop 2023-05-08 00:36:50 +02:00
a.bouhuolia
be10b8934d fix(webapp): change the error code handler 2023-05-08 00:35:44 +02:00
a.bouhuolia
ce38c71fa7 fix(server): should allowed email addresses and domain be irrespective. 2023-05-08 00:35:28 +02:00
Ahmed Bouhuolia
1162fbc7c3 Merge pull request #117 from bigcapitalhq/signup-restrictions
Sign-up restrictions for self-hosted
2023-05-08 00:18:56 +02:00
a.bouhuolia
18b9e25f2b chore: update .env.example 2023-05-07 23:59:41 +02:00
a.bouhuolia
dd26bdc482 feat(webapp): sign-up restrictions 2023-05-07 23:54:42 +02:00
a.bouhuolia
ad3c9ebfe9 feat(server): sign-up restrictions for self-hosted 2023-05-07 17:22:18 +02:00
a.bouhuolia
36611652da fix(webapp): resource meta of vendors list 2023-05-05 15:41:32 +02:00
a.bouhuolia
06c7ee71b4 fix(webapp): display transactions count in cashflow account 2023-05-05 13:54:45 +02:00
Ahmed Bouhuolia
54d3188666 Merge pull request #116 from bigcapitalhq/BIG-427-fix-sending-invite-email
fix(server): sending invite email
2023-05-05 00:30:24 +02:00
a.bouhuolia
3ceb9adda2 fix(server): sending invite email 2023-05-05 00:28:57 +02:00
Ahmed Bouhuolia
1249415054 Merge pull request #115 from bigcapitalhq/BIG-409-some-flag-icons-are-missing
fix(webapp): some flag icons are missing
2023-05-04 21:32:10 +02:00
a.bouhuolia
4d44ce4c7f fix(webapp): some flag icons are missing 2023-05-04 21:29:12 +02:00
Ahmed Bouhuolia
6c96c371c5 Merge pull request #114 from bigcapitalhq/BIG-279-select-specific-accounts-in-general-ledger-does-not-working
`BIG-279` Select specific accounts in general ledger does not working.
2023-05-04 14:29:35 +02:00
a.bouhuolia
6c61a69f10 feat(webapp): handle create item on Accounts select components 2023-05-04 14:24:45 +02:00
a.bouhuolia
981b65349d feat(webapp): allow to create a new account item in accounts list component. 2023-05-03 22:41:54 +02:00
a.bouhuolia
a7d29a31c8 refactor(webapp): all services with new AccountSelect and AccountMultiSelect components. 2023-05-01 00:13:23 +02:00
a.bouhuolia
c1d92b74f0 chore(Select):style the Select button. 2023-04-30 21:13:33 +02:00
a.bouhuolia
6f0f47f38a refactor(webapp): Accounts Select and MultiSelect components 2023-04-30 17:33:15 +02:00
a.bouhuolia
83510cfa70 feat(server): add structure query flat or tree to accounts chart endpoint 2023-04-30 17:24:49 +02:00
a.bouhuolia
903dc0522a chore: add CONTRIBUTING.md file 2023-04-27 01:56:46 +02:00
679 changed files with 6912 additions and 12260 deletions

53
.all-contributorsrc Normal file
View File

@@ -0,0 +1,53 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitType": "docs",
"commitConvention": "angular",
"contributors": [
{
"login": "abouolia",
"name": "Ahmed Bouhuolia",
"avatar_url": "https://avatars.githubusercontent.com/u/2197422?v=4",
"profile": "https://github.com/abouolia",
"contributions": [
"code"
]
},
{
"login": "ameir",
"name": "Ameir Abdeldayem",
"avatar_url": "https://avatars.githubusercontent.com/u/374330?v=4",
"profile": "http://ameir.net",
"contributions": [
"bug"
]
},
{
"login": "elforjani13",
"name": "ElforJani13",
"avatar_url": "https://avatars.githubusercontent.com/u/39470382?v=4",
"profile": "https://github.com/elforjani13",
"contributions": [
"code"
]
},
{
"login": "scheibling",
"name": "Lars Scheibling",
"avatar_url": "https://avatars.githubusercontent.com/u/24367830?v=4",
"profile": "https://scheibling.se",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,
"skipCi": true,
"repoType": "github",
"repoHost": "https://github.com",
"projectName": "bigcapital",
"projectOwner": "bigcapitalhq"
}

View File

@@ -8,27 +8,42 @@ MAIL_FROM_NAME=
MAIL_FROM_ADDRESS=
# Database
DB_USER=
DB_HOST=
DB_PASSWORD=
DB_CHARSET=
DB_HOST=mysql
DB_USER=bigcapital
DB_PASSWORD=bigcapital
DB_ROOT_PASSWORD=root
DB_CHARSET=utf8
# System database
SYSTEM_DB_NAME=bigcapital_system
# SYSTEM_DB_USER=
# SYSTEM_DB_PASSWORD=
# SYSTEM_DB_NAME=
# SYSTEM_DB_CHARSET=
# Tenants databases
# Tenant databases
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
# MongoDB
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
# Authentication
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
# TENANT_DB_HOST=
# TENANT_DB_USER=
# TENANT_DB_PASSWORD=
# TENANT_DB_CHARSET=
# Application
BASE_URL=https://bigcapital.ly
CONTACT_US_MAIL=support@bigcapital.ly
BASE_URL=http://example.com
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
# Jobs MongoDB
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
# App proxy
PUBLIC_PROXY_PORT=80
PUBLIC_PROXY_SSL_PORT=443
# Agendash
AGENDASH_AUTH_USER=agendash
AGENDASH_AUTH_PASSWORD=123123
# Sign-up restrictions
SIGNUP_DISABLED=false
SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS=

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
docker/nginx/scripts/build-nginx.sh text eol=lf
docker/mariadb/docker-entrypoint.sh text eol=lf

View File

@@ -2,6 +2,100 @@
All notable changes to Bigcapital server-side will be in this file.
# [0.9.9] - 28-06-2023
* refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
* fix: Style of quick item drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/173
* fix: Should not show the form before loading account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/172
* fix: Payment made form does not handle not unique number an e… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/177
* fix: Internal note of invoice/bill payment does not saving by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/181
* fix: Storing cash flow transaction description by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/180
* fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179
* fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182
# [0.9.8] - 19-06-2023
`bigcapitalhq/webapp`
* add: Inventory Adjustment option to the item drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/158
* fix: use all drawers names from common enum object by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/157
* fix: adjustment type options do not show up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/159
* fix: change the remove line text to be red to intent as a danger action by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/162
* fix: rename sidebar localization keys names to be keyword path by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/161
* fix: manual journal placeholder text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/160
* fix: warehouses select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/168
`bigcapitalhq/server`
* fix: sending emails on reset password and registration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/167
## [0.9.7] - 14-06-2023
`@bigcapital/webapp`
* fix: change the footer links of onboarding pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/139
`@bigcapital/server`
* fix: expense transaction journal entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/155
## [0.9.6] - 12-06-2023
`@bigcapital/webapp`
* fix: remove duplicated form submitting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/138
* feat: add monorepo version on the application sidebar by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/136
## [0.9.5] - 11-06-2023
`@bigcapital/server`
* fix: filter ledger entries that effect contact balance to AR/AP accounts only by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/132
`@bigcapital/webapp`
* fix: catch journal error when create a journal with accounts have different currency by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/135
* fix: add duplicate icon to context menu of customers and vendors table by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/133
* fix: customer/vendor opening balance with exchange rate by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/134
## [0.9.4] - 08-06-2023
`@bigcapital/monorepo`
- fixed: docker-compose line-ending issue on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/130
`@bigcapital/server`
- fixed: Disable Webpack minification for JS class name reading.
## [0.9.3] -04-06-2023
`@bigcapital/monorepo`
* Added: Add env variable to customize the proxy public ports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/125
* Added: Migrate the server database to MariaDB by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/128
## [0.9.2] - 31-05-2023
`@bigcapital/webapp`
- fixed: move `packaeg-lock.json` inside docker container.
- fixed: remove Sentry from the web client.
## [0.9.1] - 28-05-2023
`@bigcapital/server`
- fix: deleting ledger entries of manual journal.
- fix: base currency should be enabled.
- fix: delete invoice transaction issue.
`@bigcapital/webapp`
- fix: general, accoutant and items preferences.
- fix: auto-increment sale invoices, estiamtes, credit notes, payments and manual journals.
- refactor: the setup organization form to use binded Formik components.
## [0.9.0] - 06-05-2023
`@bigcapital/server`
- [Sign-up restrictions](https://docs.bigcapital.ly/docs/deployment/signup_restriction) for self-hosting instances to disable signup or control the allowed email addresses and domains that can sign-up.
## [0.8.3] - 06-04-2023
`@bigcaptial/monorepo`

View File

@@ -34,7 +34,7 @@ Contributions via pull requests are much appreciated. Once the approach is agree
## Contribute to Backend
- Clone the `bigcapital` repository and `cd` into `bigcapital` directory.
- Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit the command on root directory and it will install dependencies of all packages.
- Install all npm dependencies of the monorepo, you don't have to change directory to the `backend` package. just hit these command on root directory and it will install dependencies of all packages.
```
npm install
@@ -47,7 +47,7 @@ npm run bootstrap
docker-compose up -d
```
Wait some seconds, and hit `docker-compose ps` to see the result and you should see the same result below.
Wait some seconds, and hit `docker-compose ps` and you should see the same result below.
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
@@ -122,7 +122,7 @@ There are many other ways to get involved with the community and to participate
- Use the product, submitting GitHub issues when a problem is found.
- Help code review pull requests and participate in issue threads.
- Submit a new feature request as an issue.
- Help answer questions on forums such as Stack Overflow and SigNoz Community Slack Channel.
- Help answer questions on forums such as Bigcapital Community Discord Channel.
- Tell others about the project on Twitter, your blog, etc.
**[`^top^`](#)**

View File

@@ -7,6 +7,24 @@
<p align="center">
Simple, smart online accounting software for small and medium businesses.
</p>
<p align="center">
<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://discord.com/invite/c8nPBJafeb">
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
</a>
<a href="https://github.com/bigcapitalhq/bigcapital/graphs/contributors">
<img src="https://img.shields.io/github/contributors/bigcapitalhq/bigcapital" alt="" />
</a>
<a href="https://github.com/bigcapitalhq/bigcapital/blob/develop/LICENSE">
<img src="https://img.shields.io/github/license/bigcapitalhq/bigcapital" alt="" />
</a>
<a href="https://twitter.com/bigcapitalhq">
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
</a>
</p>
</p>
# What's Bigcapital?
@@ -22,10 +40,45 @@ Bigcapital is a smart and open-source accounting and inventory software, Bigcapi
# Resources
- [Documentation](https://docs.bigcapital.ly/) - 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.
- [Source Code](https://github.com/bigcapitalhq/bigcapital) - Github repo.
# Changlog
# Changelog
Please see [Releases](https://github.com/bigcapitalhq/bigcapital/releases) for more information what has changed recently.
# Recognition
<a href="https://news.ycombinator.com/item?id=36118990">
<img
style="width: 250px; height: 54px;" width="250" height="54"
alt="Featured on Hacker News"
src="https://hackernews-badge.vercel.app/api?id=36118990"
/>
</a>
# Contributors
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/abouolia"><img src="https://avatars.githubusercontent.com/u/2197422?v=4?s=100" width="100px;" alt="Ahmed Bouhuolia"/><br /><sub><b>Ahmed Bouhuolia</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=abouolia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/elforjani13"><img src="https://avatars.githubusercontent.com/u/39470382?v=4?s=100" width="100px;" alt="ElforJani13"/><br /><sub><b>ElforJani13</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=elforjani13" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ameir.net"><img src="https://avatars.githubusercontent.com/u/374330?v=4?s=100" width="100px;" alt="Ameir Abdeldayem"/><br /><sub><b>Ameir Abdeldayem</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aameir" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -15,14 +15,14 @@ services:
- ./data/logs/nginx/:/var/log/nginx
- ./docker/certbot/certs/:/var/certs
ports:
- "80:80"
- "443:443"
- "${PUBLIC_PROXY_PORT:-80}:80"
- "${PUBLIC_PROXY_SSL_PORT:-443}:443"
tty: true
depends_on:
- server
- webapp
webapp:
webapp:
container_name: bigcapital-webapp
image: ghcr.io/bigcapitalhq/webapp:latest
@@ -72,6 +72,11 @@ services:
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
# Sign-up restrictions
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
database_migration:
container_name: bigcapital-database-migration
build:
@@ -89,12 +94,12 @@ services:
mysql:
container_name: bigcapital-mysql
build:
context: ./docker/mysql
context: ./docker/mariadb
environment:
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
- MYSQL_USER=${DB_USER}
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- mysql:/var/lib/mysql
expose:

View File

@@ -6,14 +6,14 @@
version: '3.3'
services:
mysql:
mariadb:
build:
context: ./docker/mysql
context: ./docker/mariadb
environment:
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
- MYSQL_USER=${DB_USER}
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- mysql:/var/lib/mysql
expose:

View File

@@ -1,4 +1,4 @@
FROM mysql:5.7
FROM mariadb:10.2
USER root
ADD my.cnf /etc/mysql/conf.d/my.cnf
@@ -17,7 +17,7 @@ ENV MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
COPY ./init.sql /scripts/init.template.sql
COPY ./docker-entrypoint.sh /docker-entrypoint-initdb.d/docker-initialize.sh
# The scripts in the docker-entrypoint-initdb.d/ directory are executed as
# The scripts in the `docker-entrypoint-initdb.d/` directory are executed as
# the mysql user inside the MySQL Docker container.
RUN chown -R mysql:root /docker-entrypoint-initdb.d
RUN chown -R mysql:root /scripts

View File

@@ -1,2 +1,3 @@
GRANT ALL PRIVILEGES ON *.* TO '{MYSQL_USER}'@'%' IDENTIFIED BY '{MYSQL_PASSWORD}' WITH GRANT OPTION;
FLUSH PRIVILEGES;

13
e2e/_utils.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Page } from '@playwright/test';
export const clearLocalStorage = (page: Page) => {
return page.evaluate(`window.localStorage.clear()`);
};
export const defaultPageConfig = () => {
return {
extraHTTPHeaders: {
'ngrok-skip-browser-warning': 'any-value',
},
};
};

View File

@@ -1,14 +1,23 @@
import { test, expect, Page } from '@playwright/test';
import { faker } from '@faker-js/faker';
import { clearLocalStorage, defaultPageConfig } from './_utils';
let authPage: Page;
test.describe('authentication', () => {
test.beforeAll(async ({ browser }) => {
authPage = await browser.newPage();
authPage = await browser.newPage({ ...defaultPageConfig() });
});
test.afterAll(async () => {
await authPage.close();
});
test.afterEach(async ({ context }) => {
context.clearCookies();
await clearLocalStorage(authPage);
});
test.describe('login', () => {
test.beforeAll(async () => {
test.beforeEach(async () => {
await authPage.goto('/auth/login');
});
test('should show the login page.', async () => {
@@ -30,10 +39,23 @@ test.describe('authentication', () => {
await authPage.getByRole('link', { name: 'Sign up' }).click();
await expect(authPage.url()).toContain('/auth/register');
});
test('should the email or password is not correct.', async () => {
await authPage.getByLabel('Email Address').click();
await authPage.getByLabel('Email Address').fill(faker.internet.email());
await authPage.getByLabel('Password').click();
await authPage.getByLabel('Password').fill(faker.internet.password());
await authPage.getByRole('button', { name: 'Log in' }).click();
await expect(authPage.locator('body')).toContainText(
'The email and password you entered did not match our records.'
);
});
});
test.describe('register', () => {
test.beforeAll(async () => {
test.beforeEach(async () => {
await authPage.goto('/auth/register');
});
test('should first name, last name, email and password be required.', async () => {
@@ -52,10 +74,36 @@ test.describe('authentication', () => {
'Password is a required field'
);
});
test('should signup successfully.', async () => {
const form = authPage.locator('form');
await form.getByLabel('First Name').click();
await form.getByLabel('First Name').fill(faker.person.firstName());
await form.getByLabel('Email').click();
await form.getByLabel('Email').fill(faker.internet.email());
await form.getByLabel('Last Name').click();
await form.getByLabel('Last Name').fill(faker.person.lastName());
await form.getByLabel('Password').click();
await form.getByLabel('Password').fill(faker.internet.password());
await authPage.getByRole('button', { name: 'Register' }).click();
await expect(authPage.locator('h1')).toContainText(
'Register a New Organization now!'
);
});
});
test.describe('reset password', () => {
test.beforeAll(async () => {
test.beforeAll(async ({ browser }) => {
authPage = await browser.newPage({ ...defaultPageConfig() });
});
test.afterAll(async () => {
await authPage.close();
});
test.beforeEach(async () => {
await authPage.goto('/auth/send_reset_password');
});
test('should email be required.', async () => {

86
e2e/onboarding.spec.ts Normal file
View File

@@ -0,0 +1,86 @@
import { test, expect, Page } from '@playwright/test';
import { faker } from '@faker-js/faker';
import { defaultPageConfig } from './_utils';
let authPage: Page;
let businessLegalName: string = faker.company.name();
test.describe('onboarding', () => {
test.beforeAll(async ({ browser }) => {
authPage = await browser.newPage({ ...defaultPageConfig() });
await authPage.goto('/auth/register');
const form = authPage.locator('form');
await form.getByLabel('First Name').fill(faker.person.firstName());
await form.getByLabel('Email').fill(faker.internet.email());
await form.getByLabel('Last Name').fill(faker.person.lastName());
await form.getByLabel('Password').fill(faker.internet.password());
await authPage.getByRole('button', { name: 'Register' }).click();
});
test('should validation catch all required fields', async () => {
const form = authPage.locator('form');
await authPage.getByRole('button', { name: 'Save & Continue' }).click();
await expect(form).toContainText('Organization name is a required field');
await expect(form).toContainText('Location is a required field');
await expect(form).toContainText('Base currency is a required field');
await expect(form).toContainText('Fiscal year is a required field');
await expect(form).toContainText('Time zone is a required field');
});
test.describe('after onboarding', () => {
test.beforeAll(async () => {
await authPage.getByLabel('Legal Organization Name').click();
await authPage
.getByLabel('Legal Organization Name')
.fill(businessLegalName);
// Fill Business Location.
await authPage
.getByRole('button', { name: 'Select Business Location...' })
.click();
await authPage.locator('a').filter({ hasText: 'Albania' }).click();
// Fill Base Currency.
await authPage
.getByRole('button', { name: 'Select Base Currency...' })
.click();
await authPage
.locator('a')
.filter({ hasText: 'AED - United Arab Emirates Dirham' })
.click();
// Fill Fasical Year.
await authPage
.getByRole('button', { name: 'Select Fiscal Year...' })
.click();
await authPage.locator('a').filter({ hasText: 'June - May' }).click();
// Fill Timezone.
await authPage
.getByRole('button', { name: 'Select Time Zone...' })
.click();
await authPage.getByText('Pacific/Marquesas-09:30').click();
// Click on Submit button
await authPage.getByRole('button', { name: 'Save & Continue' }).click();
});
test('should onboarding process success', async () => {
await expect(authPage.locator('body')).toContainText(
'Congrats! You are ready to go',
{
timeout: 30000,
}
);
});
test('should go to the dashboard after clicking on "Go to dashboard" button.', async () => {
await authPage.getByRole('button', { name: 'Go to dashboard' }).click();
await expect(
authPage.locator('[data-testId="dashboard-topbar"] h1')
).toContainText(businessLegalName);
});
});
});

View File

@@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.0.0",
"version": "0.9.6",
"npmClient": "npm"
}

14
package-lock.json generated
View File

@@ -333,6 +333,12 @@
"@jridgewell/trace-mapping": "0.3.9"
}
},
"@faker-js/faker": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.0.2.tgz",
"integrity": "sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==",
"dev": true
},
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -945,6 +951,7 @@
"version": "1.32.3",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz",
"integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
@@ -954,7 +961,8 @@
"playwright-core": {
"version": "1.32.3",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.3.tgz",
"integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg=="
"integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==",
"dev": true
}
}
},
@@ -1003,7 +1011,8 @@
"@types/node": {
"version": "18.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA=="
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==",
"dev": true
},
"@types/normalize-package-data": {
"version": "2.4.1",
@@ -2324,6 +2333,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {

View File

@@ -18,12 +18,13 @@
"shared/*"
],
"devDependencies": {
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2",
"@commitlint/config-lerna-scopes": "^17.4.2",
"@faker-js/faker": "^8.0.2",
"@playwright/test": "^1.32.3",
"husky": "^8.0.3",
"lerna": "^6.4.1",
"@commitlint/cli": "^17.4.2",
"@playwright/test": "^1.32.3"
"lerna": "^6.4.1"
},
"engines": {
"node": "14.x"

View File

@@ -34,7 +34,11 @@ ARG MAIL_HOST= \
BASE_URL= \
# Agendash
AGENDASH_AUTH_USER=agendash \
AGENDASH_AUTH_PASSWORD=123123
AGENDASH_AUTH_PASSWORD=123123 \
# Sign-up restriction
SIGNUP_DISABLED= \
SIGNUP_ALLOWED_DOMAINS= \
SIGNUP_ALLOWED_EMAILS=
ENV MAIL_HOST=$MAIL_HOST \
MAIL_USERNAME=$MAIL_USERNAME \
@@ -68,7 +72,11 @@ ENV MAIL_HOST=$MAIL_HOST \
# MongoDB
MONGODB_DATABASE_URL=$MONGODB_DATABASE_URL \
# Application
BASE_URL=$BASE_URL
BASE_URL=$BASE_URL \
# Sign-up restriction
SIGNUP_DISABLED=$SIGNUP_DISABLED \
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
# Create app directory.
WORKDIR /app

View File

@@ -1,6 +1,6 @@
{
"name": "@bigcapital/server",
"version": "1.7.1",
"version": "0.9.5",
"description": "",
"main": "src/server.ts",
"scripts": {

View File

@@ -65,6 +65,9 @@ exports.getCommonWebpackOptions = ({
},
],
},
optimization: {
minimize: false,
},
};
if (isDev) {

View File

@@ -3,7 +3,12 @@ import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '@/api/controllers/BaseController';
import { AbilitySubject, AccountAction, IAccountDTO } from '@/interfaces';
import {
AbilitySubject,
AccountAction,
IAccountDTO,
IAccountsStructureType,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { DATATYPES_LENGTH } from '@/data/DataTypes';
@@ -172,6 +177,11 @@ export default class AccountsController extends BaseController {
query('inactive_mode').optional().isBoolean().toBoolean(),
query('search_keyword').optional({ nullable: true }).isString().trim(),
query('structure')
.optional()
.isString()
.isIn([IAccountsStructureType.Tree, IAccountsStructureType.Flat]),
];
}
@@ -341,6 +351,7 @@ export default class AccountsController extends BaseController {
sortOrder: 'desc',
columnSortBy: 'created_at',
inactiveMode: false,
structure: IAccountsStructureType.Tree,
...this.matchedQueryData(req),
};

View File

@@ -49,6 +49,7 @@ export default class AuthenticationController extends BaseController {
asyncMiddleware(this.resetPassword.bind(this)),
this.handlerErrors
);
router.get('/meta', asyncMiddleware(this.getAuthMeta.bind(this)));
return router;
}
@@ -91,6 +92,7 @@ export default class AuthenticationController extends BaseController {
check('password')
.exists()
.isString()
.isLength({ min: 6 })
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
@@ -105,7 +107,7 @@ export default class AuthenticationController extends BaseController {
return [
check('password')
.exists()
.isLength({ min: 5 })
.isLength({ min: 6 })
.custom((value, { req }) => {
if (value !== req.body.confirm_password) {
throw new Error("Passwords don't match");
@@ -207,6 +209,23 @@ export default class AuthenticationController extends BaseController {
}
}
/**
* Retrieves the authentication meta for SPA.
* @param {Request} req
* @param {Response} res
* @param {Function} next
* @returns {Response|void}
*/
private async getAuthMeta(req: Request, res: Response, next: Function) {
try {
const meta = await this.authApplication.getAuthMeta();
return res.status(200).send({ meta });
} catch (error) {
next(error);
}
}
/**
* Handles the service errors.
*/
@@ -247,6 +266,30 @@ export default class AuthenticationController extends BaseController {
errors: [{ type: 'EMAIL.EXISTS', code: 600 }],
});
}
if (error.errorType === 'SIGNUP_RESTRICTED') {
return res.status(400).send({
errors: [
{
type: 'SIGNUP_RESTRICTED',
message:
'Sign-up is restricted no one can sign-up to the system.',
code: 700,
},
],
});
}
if (error.errorType === 'SIGNUP_RESTRICTED_NOT_ALLOWED') {
return res.status(400).send({
errors: [
{
type: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
message:
'Sign-up is restricted the given email address is not allowed to sign-up.',
code: 710,
},
],
});
}
}
next(error);
}

View File

@@ -41,7 +41,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
get balanceSheetValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('accounting_method').optional().isIn(['cash', 'accural']),
query('accounting_method').optional().isIn(['cash', 'accrual']),
query('from_date').optional(),
query('to_date').optional(),

View File

@@ -67,6 +67,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
try {
const { data, query, meta } =
await this.generalLedgetService.generalLedger(tenantId, filter);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),

View File

@@ -58,7 +58,7 @@ export default class OrganizationController extends BaseController {
private get organizationValidationSchema(): ValidationChain[] {
return [
check('name').exists().trim(),
check('industry').optional().isString(),
check('industry').optional({ nullable: true }).isString().trim().escape(),
check('location').exists().isString().isISO31661Alpha2(),
check('base_currency').exists().isISO4217(),
check('timezone').exists().isIn(moment.tz.names()),

View File

@@ -4,6 +4,7 @@ import moment from 'moment';
global.__root_dir = path.join(__dirname, '..');
global.__resources_dir = path.join(global.__root_dir, 'resources');
global.__locales_dir = path.join(global.__resources_dir, 'locales');
global.__views_dir = path.join(global.__root_dir, 'views');
moment.prototype.toMySqlDateTime = function () {
return this.format('YYYY-MM-DD HH:mm:ss');

View File

@@ -1,5 +1,6 @@
import dotenv from 'dotenv';
import path from 'path';
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
dotenv.config();
@@ -94,16 +95,15 @@ module.exports = {
* JWT secret.
*/
jwtSecret: process.env.JWT_SECRET,
/**
*
*/
resetPasswordSeconds: 600,
/**
*
* Application base URL.
*/
customerSuccess: {
email: 'success@bigcapital.ly',
phoneNumber: '(218) 92 791 8381',
},
baseURL: process.env.BASE_URL,
/**
@@ -137,13 +137,16 @@ module.exports = {
},
/**
* Users registeration configuration.
* Sign-up restrictions
*/
registration: {
countries: {
whitelist: ['LY'],
blacklist: [],
},
signupRestrictions: {
disabled: parseBoolean<boolean>(process.env.SIGNUP_DISABLED, false),
allowedDomains: castCommaListEnvVarToArray(
process.env.SIGNUP_ALLOWED_DOMAINS
),
allowedEmails: castCommaListEnvVarToArray(
process.env.SIGNUP_ALLOWED_EMAILS
),
},
/**
@@ -153,8 +156,6 @@ module.exports = {
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
},
protocol: '',
hostname: '',
scheduleComputeItemCost: 'in 5 seconds',
/**

View File

@@ -3,17 +3,17 @@ import AccountsData from '../data/accounts';
export default class SeedAccounts extends TenantSeeder {
/**
* Seeds initial accounts to the organization.
* Seeds initial accounts to the organization.
*/
up(knex) {
const data = AccountsData.map((account) => {
return {
...account,
name: this.i18n.__(account.name),
description: this.i18n.__(account.description),
currencyCode: this.tenant.metadata.baseCurrency,
};
});
const data = AccountsData.map((account) => ({
...account,
name: this.i18n.__(account.name),
description: this.i18n.__(account.description),
currencyCode: this.tenant.metadata.baseCurrency,
seededAt: new Date(),
})
);
return knex('accounts').then(async () => {
// Inserts seed entries.
return knex('accounts').insert(data);

View File

@@ -8,7 +8,7 @@ export default class SeedSettings extends TenantSeeder {
up() {
const settings = [
// Orgnization settings.
{ group: 'organization', key: 'accounting_basis', value: 'accural' },
{ group: 'organization', key: 'accounting_basis', value: 'accrual' },
// Accounts settings.
{ group: 'accounts', key: 'account_code_unique', value: true },

View File

@@ -79,9 +79,15 @@ export interface IAccountTransaction {
}
export interface IAccountResponse extends IAccount {}
export enum IAccountsStructureType {
Tree = 'tree',
Flat = 'flat',
}
export interface IAccountsFilter extends IDynamicListFilterDTO {
stringifiedFilterRoles?: string;
onlyInactive: boolean;
structure?: IAccountsStructureType;
}
export interface IAccountType {

View File

@@ -74,4 +74,8 @@ export interface IAuthSendingResetPassword {
export interface IAuthSendedResetPassword {
user: ISystemUser,
token: string;
}
export interface IAuthGetMetaPOJO {
signupDisabled: boolean;
}

View File

@@ -44,7 +44,7 @@ export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
noneZero: boolean;
basis: 'cash' | 'accural';
basis: 'cash' | 'accrual';
accountIds: number[];
percentageOfColumn: boolean;

View File

@@ -4,6 +4,8 @@ export interface ILedger {
getEntries(): ILedgerEntry[];
filter(cb: (entry: ILedgerEntry) => boolean): ILedger;
whereAccountId(accountId: number): ILedger;
whereContactId(contactId: number): ILedger;
whereFromDate(fromDate: Date | string): ILedger;
@@ -39,6 +41,8 @@ export interface ILedgerEntry {
index: number;
indexGroup?: number;
note?: string;
userId?: number;
itemId?: number;
branchId?: number;

View File

@@ -4,7 +4,7 @@ export interface ITrialBalanceSheetQuery {
fromDate: Date | string;
toDate: Date | string;
numberFormat: INumberFormatQuery;
basis: 'cash' | 'accural';
basis: 'cash' | 'accrual';
noneZero: boolean;
noneTransactions: boolean;
onlyActive: boolean;

View File

@@ -1,6 +1,7 @@
import { AnyObject } from '@casl/ability/dist/types/types';
import { ITenant } from '@/interfaces';
import { Model } from 'objection';
import { Tenant } from '@/system/models';
export interface ISystemUser extends Model {
id: number;
@@ -54,20 +55,52 @@ export interface IUserInvite {
export interface IInviteUserService {
acceptInvite(token: string, inviteUserInput: IInviteUserInput): Promise<void>;
/**
* Re-send user invite.
* @param {number} tenantId -
* @param {string} email -
* @return {Promise<{ invite: IUserInvite }>}
*/
resendInvite(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<{
invite: IUserInvite;
user: ITenantUser;
}>;
/**
* Sends invite mail to the given email from the given tenant and user.
* @param {number} tenantId -
* @param {string} email -
* @param {IUser} authorizedUser -
* @return {Promise<IUserInvite>}
*/
sendInvite(
tenantId: number,
email: string,
sendInviteDTO: IUserSendInviteDTO,
authorizedUser: ISystemUser
): Promise<{
invite: IUserInvite;
invitedUser: ITenantUser;
}>;
}
export interface IAcceptInviteUserService {
/**
* Accept the received invite.
* @param {string} token
* @param {IInviteUserInput} inviteUserInput
* @throws {ServiceErrors}
* @returns {Promise<void>}
*/
acceptInvite(token: string, inviteUserDTO: IInviteUserInput): Promise<void>;
/**
* Validate the given invite token.
* @param {string} token - the given token string.
* @throws {ServiceError}
*/
checkInvite(
token: string
): Promise<{ inviteToken: IUserInvite; orgName: object }>;
@@ -121,7 +154,7 @@ export interface IUserInvitedEventPayload {
tenantId: number;
user: ITenantUser;
}
export interface IUserInviteTenantSyncedEventPayload{
export interface IUserInviteTenantSyncedEventPayload {
invite: IUserInvite;
authorizedUser: ISystemUser;
tenantId: number;
@@ -143,10 +176,10 @@ export interface IAcceptInviteEventPayload {
export interface ICheckInviteEventPayload {
inviteToken: IUserInvite;
tenant: ITenant
tenant: Tenant;
}
export interface IUserSendInviteDTO {
email: string;
roleId: number;
}
}

View File

@@ -1,38 +1,34 @@
import { Container, Inject } from 'typedi';
import AuthenticationService from '@/services/Authentication/AuthApplication';
import { Container } from 'typedi';
import AuthenticationMailMesssages from '@/services/Authentication/AuthenticationMailMessages';
export default class WelcomeEmailJob {
export default class ResetPasswordEmailJob {
/**
* Constructor method.
* @param {Agenda} agenda
* @param {Agenda} agenda
*/
constructor(agenda) {
agenda.define(
'reset-password-mail',
{ priority: 'high' },
this.handler.bind(this),
this.handler.bind(this)
);
}
/**
* Handle send welcome mail job.
* @param {Job} job
* @param {Function} done
* @param {Job} job
* @param {Function} done
*/
public async handler(job, done: Function): Promise<void> {
const { data } = job.attrs;
const { user, token } = data;
const Logger = Container.get('logger');
const authService = Container.get(AuthenticationService);
const authService = Container.get(AuthenticationMailMesssages);
Logger.info(`[send_reset_password] started.`, { data });
try {
await authService.mailMessages.sendResetPasswordMessage(user, token);
Logger.info(`[send_reset_password] finished.`, { data });
done()
await authService.sendResetPasswordMessage(user, token);
done();
} catch (error) {
Logger.error(`[send_reset_password] error.`, { data, error });
console.log(error);
done(error);
}
}

View File

@@ -1,22 +0,0 @@
import { Container } from 'typedi';
export default class SmsNotification {
constructor(agenda) {
agenda.define('sms-notification', { priority: 'high' }, this.handler);
}
/**
*
* @param {Job}job
*/
async handler(job) {
const { message, to } = job.attrs.data;
const smsClient = Container.get('SMSClient');
try {
await smsClient.sendMessage(to, message);
} catch (error) {
done(e);
}
}
}

View File

@@ -1,5 +1,6 @@
import { Container, Inject } from 'typedi';
import InviteUserService from '@/services/InviteUsers/AcceptInviteUser';
import SendInviteUsersMailMessage from '@/services/InviteUsers/SendInviteUsersMailMessage';
export default class UserInviteMailJob {
/**
@@ -21,24 +22,17 @@ export default class UserInviteMailJob {
*/
public async handler(job, done: Function): Promise<void> {
const { invite, authorizedUser, tenantId } = job.attrs.data;
const Logger = Container.get('logger');
const inviteUsersService = Container.get(InviteUserService);
Logger.info(`Send invite user mail - started: ${job.attrs.data}`);
const sendInviteMailMessage = Container.get(SendInviteUsersMailMessage);
try {
await inviteUsersService.mailMessages.sendInviteMail(
await sendInviteMailMessage.sendInviteMail(
tenantId,
authorizedUser,
invite
);
Logger.info(`Send invite user mail - finished: ${job.attrs.data}`);
done();
} catch (error) {
Logger.info(
`Send invite user mail - error: ${job.attrs.data}, error: ${error}`
);
console.log(error);
done(error);
}
}

View File

@@ -1,35 +0,0 @@
import { Container, Inject } from 'typedi';
import AuthenticationService from '@/services/Authentication/AuthApplication';
export default class WelcomeSMSJob {
/**
* Constructor method.
* @param {Agenda} agenda
*/
constructor(agenda) {
agenda.define('welcome-sms', { priority: 'high' }, this.handler);
}
/**
* Handle send welcome mail job.
* @param {Job} job
* @param {Function} done
*/
public async handler(job, done: Function): Promise<void> {
const { tenant, user } = job.attrs.data;
const Logger = Container.get('logger');
const authService = Container.get(AuthenticationService);
Logger.info(`[welcome_sms] started: ${job.attrs.data}`);
try {
await authService.smsMessages.sendWelcomeMessage(tenant, user);
Logger.info(`[welcome_sms] finished`, { tenant, user });
done();
} catch (error) {
Logger.info(`[welcome_sms] error`, { error, tenant, user });
done(error);
}
}
}

View File

@@ -1,39 +0,0 @@
import { Container } from 'typedi';
import AuthenticationService from '@/services/Authentication/AuthApplication';
export default class WelcomeEmailJob {
/**
* Constructor method.
* @param {Agenda} agenda -
*/
constructor(agenda) {
// Welcome mail and SMS message.
agenda.define(
'welcome-email',
{ priority: 'high' },
this.handler.bind(this),
);
}
/**
* Handle send welcome mail job.
* @param {Job} job
* @param {Function} done
*/
public async handler(job, done: Function): Promise<void> {
const { organizationId, user } = job.attrs.data;
const Logger: any = Container.get('logger');
const authService = Container.get(AuthenticationService);
Logger.info(`[welcome_mail] started: ${job.attrs.data}`);
try {
await authService.mailMessages.sendWelcomeMessage(user, organizationId);
Logger.info(`[welcome_mail] finished: ${job.attrs.data}`);
done();
} catch (error) {
Logger.error(`[welcome_mail] error: ${job.attrs.data}, error: ${error}`);
done(error);
}
}
}

View File

@@ -109,7 +109,7 @@ export default class Mail {
* Retrieve view content from the view directory.
*/
private getViewContent(): string {
const filePath = path.join(global.__root_dir, `../views/${this.view}`);
const filePath = path.join(global.__views_dir, `/${this.view}`);
return fs.readFileSync(filePath, 'utf8');
}
}

View File

@@ -2,6 +2,7 @@ import moment from 'moment';
import * as R from 'ramda';
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
import { formatNumber } from 'utils';
import { isArrayLikeObject } from 'lodash/fp';
export class Transformer {
public context: any;
@@ -39,12 +40,33 @@ export class Transformer {
return object;
};
/**
*
* @param object
* @returns
*/
protected preCollectionTransform = (object: any) => {
return object;
};
/**
*
* @param object
* @returns
*/
protected postCollectionTransform = (object: any) => {
return object;
};
/**
*
*/
public work = (object: any) => {
if (Array.isArray(object)) {
return object.map(this.getTransformation);
const preTransformed = this.preCollectionTransform(object);
const transformed = preTransformed.map(this.getTransformation);
return this.postCollectionTransform(transformed);
} else if (isObject(object)) {
return this.getTransformation(object);
}

View File

@@ -22,7 +22,7 @@ import SaleInvoiceAutoIncrementSubscriber from '@/subscribers/SaleInvoices/AutoI
import SaleInvoiceConvertFromEstimateSubscriber from '@/subscribers/SaleInvoices/ConvertFromEstimate';
import PaymentReceiveAutoSerialSubscriber from '@/subscribers/PaymentReceive/AutoSerialIncrement';
import SyncSystemSendInvite from '@/services/InviteUsers/SyncSystemSendInvite';
import InviteSendMainNotification from '@/services/InviteUsers/InviteSendMailNotification';
import InviteSendMainNotification from '@/services/InviteUsers/InviteSendMailNotificationSubscribe';
import SyncTenantAcceptInvite from '@/services/InviteUsers/SyncTenantAcceptInvite';
import SyncTenantUserMutate from '@/services/Users/SyncTenantUserSaved';
import { SyncTenantUserDelete } from '@/services/Users/SyncTenantUserDeleted';
@@ -31,7 +31,6 @@ import OrgBuildSmsNotificationSubscriber from '@/subscribers/Organization/BuildS
import PurgeUserAbilityCache from '@/services/Users/PurgeUserAbilityCache';
import ResetLoginThrottleSubscriber from '@/subscribers/Authentication/ResetLoginThrottle';
import AuthenticationSubscriber from '@/subscribers/Authentication/SendResetPasswordMail';
import AuthSendWelcomeMailSubscriber from '@/subscribers/Authentication/SendWelcomeMail';
import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedUser';
import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer';
import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer';
@@ -120,7 +119,6 @@ export const susbcribers = () => {
PurgeUserAbilityCache,
ResetLoginThrottleSubscriber,
AuthenticationSubscriber,
AuthSendWelcomeMailSubscriber,
PurgeAuthorizedUserOnceRoleMutate,
SendSmsNotificationToCustomer,
SendSmsNotificationSaleReceipt,

View File

@@ -1,24 +1,18 @@
import Agenda from 'agenda';
import WelcomeEmailJob from 'jobs/welcomeEmail';
import WelcomeSMSJob from 'jobs/WelcomeSMS';
import ResetPasswordMailJob from 'jobs/ResetPasswordMail';
import ComputeItemCost from 'jobs/ComputeItemCost';
import RewriteInvoicesJournalEntries from 'jobs/writeInvoicesJEntries';
import RewriteInvoicesJournalEntries from 'jobs/WriteInvoicesJEntries';
import UserInviteMailJob from 'jobs/UserInviteMail';
import OrganizationSetupJob from 'jobs/OrganizationSetup';
import OrganizationUpgrade from 'jobs/OrganizationUpgrade';
import SmsNotification from 'jobs/SmsNotification';
export default ({ agenda }: { agenda: Agenda }) => {
new WelcomeEmailJob(agenda);
new ResetPasswordMailJob(agenda);
new WelcomeSMSJob(agenda);
new UserInviteMailJob(agenda);
new ComputeItemCost(agenda);
new RewriteInvoicesJournalEntries(agenda);
new OrganizationSetupJob(agenda);
new OrganizationUpgrade(agenda);
new SmsNotification(agenda);
agenda.start();
};

View File

@@ -1,9 +1,14 @@
import { Service, Inject } from 'typedi';
import async from 'async';
import { Knex } from 'knex';
import { ILedger, ISaleContactsBalanceQueuePayload } from '@/interfaces';
import {
ILedger,
ILedgerEntry,
ISaleContactsBalanceQueuePayload,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TenantMetadata } from '@/system/models';
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
@Service()
export class LedgerContactsBalanceStorage {
@@ -49,6 +54,29 @@ export class LedgerContactsBalanceStorage {
await this.saveContactBalance(tenantId, ledger, contactId, trx);
};
/**
* Filters AP/AR ledger entries.
* @param {number} tenantId
* @param {Knex.Transaction} trx
* @returns {Promise<(entry: ILedgerEntry) => boolean>}
*/
private filterARAPLedgerEntris = async (
tenantId: number,
trx?: Knex.Transaction
): Promise<(entry: ILedgerEntry) => boolean> => {
const { Account } = this.tenancy.models(tenantId);
const ARAPAcounts = await Account.query(trx).whereIn('accountType', [
ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
]);
const ARAPAcountsIds = ARAPAcounts.map((a) => a.id);
return (entry: ILedgerEntry) => {
return ARAPAcountsIds.indexOf(entry.accountId) !== -1;
};
};
/**
*
* @param {number} tenantId
@@ -63,16 +91,24 @@ export class LedgerContactsBalanceStorage {
trx?: Knex.Transaction
): Promise<void> => {
const { Contact } = this.tenancy.models(tenantId);
const contact = await Contact.query().findById(contactId);
const contact = await Contact.query(trx).findById(contactId);
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
// Detarmines whether the contact has foreign currency.
const isForeignContact = contact.currencyCode !== tenantMeta.baseCurrency;
// Filters the ledger base on the given contact id.
const contactLedger = ledger.whereContactId(contactId);
const filterARAPLedgerEntris = await this.filterARAPLedgerEntris(
tenantId,
trx
);
const contactLedger = ledger
// Filter entries only that have contact id.
.whereContactId(contactId)
// Filter entries on AR/AP accounts.
.filter(filterARAPLedgerEntris);
const closingBalance = isForeignContact
? contactLedger

View File

@@ -10,7 +10,7 @@ export class LedgerRevert {
private tenancy: HasTenancyService;
@Inject()
ledgerStorage: LedgerStorageService;
private ledgerStorage: LedgerStorageService;
/**
* Reverts the jouranl entries.

View File

@@ -21,6 +21,8 @@ export const transformLedgerEntryToTransaction = (
transactionNumber: entry.transactionNumber,
referenceNumber: entry.referenceNumber,
note: entry.note,
index: entry.index,
indexGroup: entry.indexGroup,

View File

@@ -1,6 +1,11 @@
import { IAccount } from '@/interfaces';
import { IAccount, IAccountsStructureType } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import {
assocDepthLevelToObjectTree,
flatToNestedArray,
formatNumber,
nestedArrayToFlatten,
} from 'utils';
export class AccountTransformer extends Transformer {
/**
@@ -8,7 +13,23 @@ export class AccountTransformer extends Transformer {
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount'];
return ['formattedAmount', 'flattenName'];
};
/**
* Retrieves the flatten name with all dependants accounts names.
* @param {IAccount} account -
* @returns {string}
*/
public flattenName = (account: IAccount): string => {
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
account.id
);
const prefixAccounts = parentDependantsIds.map((dependId) => {
const node = this.options.accountsGraph.getNodeData(dependId);
return `${node.name}: `;
});
return `${prefixAccounts}${account.name}`;
};
/**
@@ -17,8 +38,28 @@ export class AccountTransformer extends Transformer {
* @returns {string}
*/
protected formattedAmount = (account: IAccount): string => {
return formatNumber(account.amount, {
currencyCode: account.currencyCode,
return formatNumber(account.amount, { currencyCode: account.currencyCode });
};
/**
* Transformes the accounts collection to flat or nested array.
* @param {IAccount[]}
* @returns {IAccount[]}
*/
protected postCollectionTransform = (accounts: IAccount[]) => {
// Transfom the flatten to accounts tree.
const transformed = flatToNestedArray(accounts, {
id: 'id',
parentId: 'parentAccountId',
});
// Associate `accountLevel` attr to indicate object depth.
const transformed2 = assocDepthLevelToObjectTree(
transformed,
1,
'accountLevel'
);
return this.options.structure === IAccountsStructureType.Flat
? nestedArrayToFlatten(transformed2)
: transformed2;
};
}

View File

@@ -22,15 +22,19 @@ export class GetAccount {
*/
public getAccount = async (tenantId: number, accountId: number) => {
const { Account } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
// Find the given account or throw not found error.
const account = await Account.query().findById(accountId).throwIfNotFound();
const accountsGraph = await accountRepository.getDependencyGraph();
// Transformes the account model to POJO.
const transformed = await this.transformer.transform(
tenantId,
account,
new AccountTransformer()
new AccountTransformer(),
{ accountsGraph }
);
return this.i18nService.i18nApply(
[['accountTypeLabel'], ['accountNormalFormatted']],

View File

@@ -1,6 +1,11 @@
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import { IAccountsFilter, IAccountResponse, IFilterMeta } from '@/interfaces';
import {
IAccountsFilter,
IAccountResponse,
IFilterMeta,
IAccountsStructureType,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { AccountTransformer } from './AccountTransform';
@@ -38,6 +43,7 @@ export class GetAccounts {
filterDTO: IAccountsFilter
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
const { Account } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
// Parses the stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
@@ -53,17 +59,16 @@ export class GetAccounts {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
});
// Retrievs the formatted accounts collection.
const preTransformedAccounts = await this.transformer.transform(
const accountsGraph = await accountRepository.getDependencyGraph();
// Retrieves the transformed accounts collection.
const transformedAccounts = await this.transformer.transform(
tenantId,
accounts,
new AccountTransformer()
new AccountTransformer(),
{ accountsGraph, structure: filterDTO.structure }
);
// Transform accounts to nested array.
const transformedAccounts = flatToNestedArray(preTransformedAccounts, {
id: 'id',
parentId: 'parentAccountId',
});
return {
accounts: transformedAccounts,

View File

@@ -1,8 +1,14 @@
import { Service, Inject, Container } from 'typedi';
import { IRegisterDTO, ISystemUser, IPasswordReset } from '@/interfaces';
import {
IRegisterDTO,
ISystemUser,
IPasswordReset,
IAuthGetMetaPOJO,
} from '@/interfaces';
import { AuthSigninService } from './AuthSignin';
import { AuthSignupService } from './AuthSignup';
import { AuthSendResetPassword } from './AuthSendResetPassword';
import { GetAuthMeta } from './GetAuthMeta';
@Service()
export default class AuthenticationApplication {
@@ -15,6 +21,9 @@ export default class AuthenticationApplication {
@Inject()
private authResetPasswordService: AuthSendResetPassword;
@Inject()
private authGetMeta: GetAuthMeta;
/**
* Signin and generates JWT token.
* @throws {ServiceError}
@@ -53,4 +62,12 @@ export default class AuthenticationApplication {
public async resetPassword(token: string, password: string): Promise<void> {
return this.authResetPasswordService.resetPassword(token, password);
}
/**
* Retrieves the authentication meta for SPA.
* @returns {Promise<IAuthGetMetaPOJO>}
*/
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return this.authGetMeta.getAuthMeta();
}
}

View File

@@ -1,4 +1,4 @@
import { omit } from 'lodash';
import { isEmpty, omit } from 'lodash';
import moment from 'moment';
import { ServiceError } from '@/exceptions';
import {
@@ -13,6 +13,7 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import TenantsManagerService from '../Tenancy/TenantsManager';
import events from '@/subscribers/events';
import { hashPassword } from '@/utils';
import config from '@/config';
export class AuthSignupService {
@Inject()
@@ -33,6 +34,9 @@ export class AuthSignupService {
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
const { systemUserRepository } = this.sysRepositories;
// Validates the signup disable restrictions.
await this.validateSignupRestrictions(signupDTO.email);
// Validates the given email uniqiness.
await this.validateEmailUniqiness(signupDTO.email);
@@ -74,4 +78,34 @@ export class AuthSignupService {
throw new ServiceError(ERRORS.EMAIL_EXISTS);
}
}
/**
* Validate sign-up disable restrictions.
* @param {string} email
*/
private async validateSignupRestrictions(email: string) {
// Can't continue if the signup is not disabled.
if (!config.signupRestrictions.disabled) return;
// Validate the allowed email addresses and domains.
if (
!isEmpty(config.signupRestrictions.allowedEmails) ||
!isEmpty(config.signupRestrictions.allowedDomains)
) {
const emailDomain = email.split('@').pop();
const isAllowedEmail =
config.signupRestrictions.allowedEmails.indexOf(email) !== -1;
const isAllowedDomain = config.signupRestrictions.allowedDomains.some(
(domain) => emailDomain === domain
);
if (!isAllowedEmail && !isAllowedDomain) {
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED);
}
// Throw error if the signup is disabled with no exceptions.
} else {
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED);
}
}
}

View File

@@ -5,59 +5,24 @@ import Mail from '@/lib/Mail';
@Service()
export default class AuthenticationMailMesssages {
/**
* Sends welcome message.
* @param {ISystemUser} user - The system user.
* @param {string} organizationName -
* @return {Promise<void>}
*/
async sendWelcomeMessage(
user: ISystemUser,
organizationId: string
): Promise<void> {
const root = __dirname + '/../../../views/images/bigcapital.png';
const mail = new Mail()
.setView('mail/Welcome.html')
.setSubject('Welcome to Bigcapital')
.setTo(user.email)
.setAttachments([
{
filename: 'bigcapital.png',
path: root,
cid: 'bigcapital_logo',
},
])
.setData({
firstName: user.firstName,
organizationId,
successPhoneNumber: config.customerSuccess.phoneNumber,
successEmail: config.customerSuccess.email,
});
await mail.send();
}
/**
* Sends reset password message.
* @param {ISystemUser} user - The system user.
* @param {string} token - Reset password token.
* @return {Promise<void>}
*/
async sendResetPasswordMessage(
public async sendResetPasswordMessage(
user: ISystemUser,
token: string
): Promise<void> {
const root = __dirname + '/../../../views/images/bigcapital.png';
const mail = new Mail()
await new Mail()
.setSubject('Bigcapital - Password Reset')
.setView('mail/ResetPassword.html')
.setTo(user.email)
.setAttachments([
{
filename: 'bigcapital.png',
path: root,
path: `${global.__views_dir}/images/bigcapital.png`,
cid: 'bigcapital_logo',
},
])
@@ -65,9 +30,7 @@ export default class AuthenticationMailMesssages {
resetPasswordUrl: `${config.baseURL}/auth/reset_password/${token}`,
first_name: user.firstName,
last_name: user.lastName,
contact_us_email: config.contactUsMail,
});
await mail.send();
})
.send();
}
}

View File

@@ -1,19 +0,0 @@
import { Service, Inject } from 'typedi';
import { ISystemUser, ITenant } from '@/interfaces';
@Service()
export default class AuthenticationSMSMessages {
@Inject('SMSClient')
smsClient: any;
/**
* Sends welcome sms message.
* @param {ITenant} tenant
* @param {ISystemUser} user
*/
sendWelcomeMessage(tenant: ITenant, user: ISystemUser) {
const message: string = `Hi ${user.firstName}, Welcome to Bigcapital, You've joined the new workspace, if you need any help please don't hesitate to contact us.`;
return this.smsClient.sendMessage(user.phoneNumber, message);
}
}

View File

@@ -0,0 +1,16 @@
import { Service } from 'typedi';
import { IAuthGetMetaPOJO } from '@/interfaces';
import config from '@/config';
@Service()
export class GetAuthMeta {
/**
* Retrieves the authentication meta for SPA.
* @returns {Promise<IAuthGetMetaPOJO>}
*/
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return {
signupDisabled: config.signupRestrictions.disabled,
};
}
}

View File

@@ -7,4 +7,6 @@ export const ERRORS = {
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
EMAIL_EXISTS: 'EMAIL_EXISTS',
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
};

View File

@@ -25,8 +25,8 @@ export default class CashflowTransactionJournalEntries {
/**
* Retrieves the common entry of cashflow transaction.
* @param {ICashflowTransaction} cashflowTransaction
* @returns {}
* @param {ICashflowTransaction} cashflowTransaction
* @returns {Partial<ILedgerEntry>}
*/
private getCommonEntry = (cashflowTransaction: ICashflowTransaction) => {
const { entries, ...transaction } = cashflowTransaction;
@@ -41,7 +41,9 @@ export default class CashflowTransactionJournalEntries {
),
transactionId: transaction.id,
transactionNumber: transaction.transactionNumber,
referenceNo: transaction.referenceNo,
referenceNumber: transaction.referenceNo,
note: transaction.description,
branchId: cashflowTransaction.branchId,
userId: cashflowTransaction.userId,
@@ -76,9 +78,9 @@ export default class CashflowTransactionJournalEntries {
/**
* Retrieves the cashflow credit GL entry.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns {ILedgerEntry}
*/
private getCashflowCreditGLEntry = (
@@ -102,10 +104,10 @@ export default class CashflowTransactionJournalEntries {
/**
* Retrieves the cashflow transaction GL entry.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns {ILedgerEntry[]}
*/
private getJournalEntries = (
cashflowTransaction: ICashflowTransaction
@@ -118,7 +120,7 @@ export default class CashflowTransactionJournalEntries {
/**
* Retrieves the cashflow GL ledger.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransaction} cashflowTransaction
* @returns {Ledger}
*/
private getCashflowLedger = (cashflowTransaction: ICashflowTransaction) => {
@@ -130,6 +132,7 @@ export default class CashflowTransactionJournalEntries {
* Write the journal entries of the given cashflow transaction.
* @param {number} tenantId
* @param {ICashflowTransaction} cashflowTransaction
* @return {Promise<void>}
*/
public writeJournalEntries = async (
tenantId: number,
@@ -153,6 +156,7 @@ export default class CashflowTransactionJournalEntries {
* Delete the journal entries.
* @param {number} tenantId - Tenant id.
* @param {number} cashflowTransactionId - Cashflow transaction id.
* @return {Promise<void>}
*/
public revertJournalEntries = async (
tenantId: number,

View File

@@ -55,7 +55,7 @@ export class CreateCustomer {
} as ICustomerEventCreatingPayload);
// Creates a new contact as customer.
const customer = await Contact.query().insertAndFetch({
const customer = await Contact.query(trx).insertAndFetch({
...customerObj,
});
// Triggers `onCustomerCreated` event.

View File

@@ -5,18 +5,13 @@ import {
ICreditNoteDeletedPayload,
ICreditNoteEditedPayload,
ICreditNoteOpenedPayload,
IRefundCreditNoteOpenedPayload,
} from '@/interfaces';
import CreditNoteGLEntries from './CreditNoteGLEntries';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class CreditNoteGLEntriesSubscriber {
@Inject()
creditNoteGLEntries: CreditNoteGLEntries;
@Inject()
tenancy: HasTenancyService;
private creditNoteGLEntries: CreditNoteGLEntries;
/**
* Attaches events with handlers.

View File

@@ -9,7 +9,6 @@ import events from '@/subscribers/events';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { CommandExpenseValidator } from './CommandExpenseValidator';
import { ExpenseCategory } from 'models';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
@@ -37,7 +36,7 @@ export class DeleteExpense {
expenseId: number,
authorizedUser: ISystemUser
): Promise<void> => {
const { Expense } = this.tenancy.models(tenantId);
const { Expense, ExpenseCategory } = this.tenancy.models(tenantId);
// Retrieves the expense transaction with associated entries or
// throw not found error.
@@ -60,7 +59,7 @@ export class DeleteExpense {
} as IExpenseDeletingPayload);
// Deletes expense associated entries.
await ExpenseCategory.query(trx).findById(expenseId).delete();
await ExpenseCategory.query(trx).where('expenseId', expenseId).delete();
// Deletes expense transactions.
await Expense.query(trx).findById(expenseId).delete();

View File

@@ -46,7 +46,7 @@ export class ExpenseGLEntries {
...commonEntry,
credit: expense.localAmount,
accountId: expense.paymentAccountId,
accountNormal: AccountNormal.CREDIT,
accountNormal: AccountNormal.DEBIT,
index: 1,
};
};

View File

@@ -5,7 +5,7 @@ import { ICashflowAccountTransactionsQuery, IPaginationMeta } from '@/interfaces
@Service()
export default class CashflowAccountTransactionsRepo {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Retrieve the cashflow account transactions.

View File

@@ -17,7 +17,7 @@ export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({
formatMoney: 'total',
precision: 2,
},
basis: 'accural',
basis: 'accrual',
noneZero: false,
noneTransactions: false,

View File

@@ -35,7 +35,7 @@ export default class TrialBalanceSheetService extends FinancialSheet {
formatMoney: 'total',
precision: 2,
},
basis: 'accural',
basis: 'accrual',
noneZero: false,
noneTransactions: true,
onlyActive: false,

View File

@@ -12,9 +12,12 @@ import {
} from '@/interfaces';
import { ERRORS } from './constants';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { IAcceptInviteUserService } from '@/interfaces';
@Service()
export default class AcceptInviteUserService {
export default class AcceptInviteUserService
implements IAcceptInviteUserService
{
@Inject()
private eventPublisher: EventPublisher;

View File

@@ -1,7 +1,4 @@
import {
IUserInvitedEventPayload,
IUserInviteTenantSyncedEventPayload,
} from '@/interfaces';
import { IUserInviteTenantSyncedEventPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { Inject, Service } from 'typedi';

View File

@@ -1,12 +1,12 @@
import path from 'path';
import { ISystemUser } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import Mail from '@/lib/Mail';
import { Service, Container } from 'typedi';
import config from '@/config';
import { Service } from 'typedi';
import { Tenant } from '@/system/models';
import config from '@/config';
@Service()
export default class InviteUsersMailMessages {
export default class SendInviteUsersMailMessage {
/**
* Sends invite mail to the given email.
* @param user
@@ -18,7 +18,7 @@ export default class InviteUsersMailMessages {
.findById(tenantId)
.withGraphFetched('metadata');
const root = __dirname + '/../../../views/images/bigcapital.png';
const root = path.join(global.__views_dir, '/images/bigcapital.png');
const mail = new Mail()
.setSubject(`${fromUser.firstName} has invited you to join a Bigcapital`)

View File

@@ -8,7 +8,7 @@ import { IAcceptInviteEventPayload } from '@/interfaces';
@Service()
export default class SyncTenantAcceptInvite {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Attaches events with handlers.

View File

@@ -74,17 +74,15 @@ export default class InviteTenantUserService implements IInviteUserService {
/**
* Re-send user invite.
* @param {number} tenantId -
* @param {string} email -
* @param {number} tenantId -
* @param {string} email -
* @return {Promise<{ invite: IUserInvite }>}
*/
public async resendInvite(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<{
user: ITenantUser;
}> {
): Promise<{ user: ITenantUser }> {
// Retrieve the user by id or throw not found service error.
const user = await this.getUserByIdOrThrowError(tenantId, userId);

View File

@@ -1,11 +1,10 @@
import { difference, sumBy, omit, map } from 'lodash';
import { difference } from 'lodash';
import { Service, Inject } from 'typedi';
import { ServiceError } from '@/exceptions';
import {
IManualJournalDTO,
IManualJournalEntry,
IManualJournal,
IManualJournalEntryDTO,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants';
@@ -286,7 +285,7 @@ export class CommandManualJournalValidators {
public validateJournalCurrencyWithAccountsCurrency = async (
tenantId: number,
manualJournalDTO: IManualJournalDTO,
baseCurrency: string,
baseCurrency: string
) => {
const { Account } = this.tenancy.models(tenantId);

View File

@@ -3,25 +3,20 @@ import * as R from 'ramda';
import {
IManualJournal,
IManualJournalEntry,
IAccount,
ILedgerEntry,
} from '@/interfaces';
import { Knex } from 'knex';
import Ledger from '@/services/Accounting/Ledger';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { LedgerRevert } from '@/services/Accounting/LedgerStorageRevert';
@Service()
export class ManualJournalGLEntries {
@Inject()
ledgerStorage: LedgerStorageService;
private ledgerStorage: LedgerStorageService;
@Inject()
ledgerRevert: LedgerRevert;
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Create manual journal GL entries.
@@ -77,7 +72,7 @@ export class ManualJournalGLEntries {
manualJournalId: number,
trx?: Knex.Transaction
): Promise<void> => {
return this.ledgerRevert.revertGLEntries(
return this.ledgerStorage.deleteByReference(
tenantId,
manualJournalId,
'Journal',
@@ -86,7 +81,7 @@ export class ManualJournalGLEntries {
};
/**
*
* Retrieves the ledger of the given manual journal.
* @param {IManualJournal} manualJournal
* @returns {Ledger}
*/
@@ -97,11 +92,13 @@ export class ManualJournalGLEntries {
};
/**
*
* Retrieves the common entry details of the manual journal
* @param {IManualJournal} manualJournal
* @returns {}
* @returns {Partial<ILedgerEntry>}
*/
private getManualJournalCommonEntry = (manualJournal: IManualJournal) => {
private getManualJournalCommonEntry = (
manualJournal: IManualJournal
): Partial<ILedgerEntry> => {
return {
transactionNumber: manualJournal.journalNumber,
referenceNumber: manualJournal.reference,
@@ -118,7 +115,8 @@ export class ManualJournalGLEntries {
};
/**
*
* Retrieves the ledger entry of the given manual journal and
* its associated entry.
* @param {IManualJournal} manualJournal -
* @param {IManualJournalEntry} entry -
* @returns {ILedgerEntry}
@@ -149,7 +147,7 @@ export class ManualJournalGLEntries {
);
/**
*
* Retrieves the ledger of the given manual journal.
* @param {IManualJournal} manualJournal
* @returns {ILedgerEntry[]}
*/

View File

@@ -23,8 +23,11 @@ export class ProjectBillableBillSubscriber {
events.saleInvoice.onCreated,
this.handleIncreaseBillableBill
);
bus.subscribe(events.saleInvoice.onEdited, this.handleDecreaseBillableBill);
bus.subscribe(events.saleInvoice.onDeleted, this.handleEditBillableBill);
bus.subscribe(events.saleInvoice.onEdited, this.handleEditBillableBill);
bus.subscribe(
events.saleInvoice.onDeleted,
this.handleDecreaseBillableBill
);
}
/**

View File

@@ -1,7 +1,11 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import async from 'async';
import { ISaleInvoice, ISaleInvoiceDTO, ProjectLinkRefType } from '@/interfaces';
import {
ISaleInvoice,
ISaleInvoiceDTO,
ProjectLinkRefType,
} from '@/interfaces';
import { ProjectBillableExpense } from './ProjectBillableExpense';
import { filterEntriesByRefType } from './_utils';

View File

@@ -21,13 +21,10 @@ export class ProjectBillableExpensesSubscriber {
events.saleInvoice.onCreated,
this.handleIncreaseBillableExpenses
);
bus.subscribe(
events.saleInvoice.onEdited,
this.handleDecreaseBillableExpenses
);
bus.subscribe(events.saleInvoice.onEdited, this.handleEditBillableExpenses);
bus.subscribe(
events.saleInvoice.onDeleted,
this.handleEditBillableExpenses
this.handleDecreaseBillableExpenses
);
}

View File

@@ -4,7 +4,7 @@ import events from '@/subscribers/events';
@Service()
export default class AuthenticationSubscriber {
@Inject('agenda')
agenda: any;
private agenda: any;
/**
* Attaches events with handlers.

View File

@@ -1,28 +0,0 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
@Service()
export default class AuthSendWelcomeMailSubscriber {
@Inject('agenda')
agenda: any;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(events.auth.signUp, this.sendWelcomeEmailOnceUserRegister);
}
/**
* Sends welcome email once the user register.
*/
private sendWelcomeEmailOnceUserRegister = async (payload) => {
const { tenant, user } = payload;
// Send welcome mail to the user.
await this.agenda.now('welcome-email', {
organizationId: tenant.organizationId,
user,
});
};
}

View File

@@ -419,6 +419,58 @@ export const parseDate = (date: string) => {
return date ? moment(date).utcOffset(0).format('YYYY-MM-DD') : '';
};
const nestedArrayToFlatten = (
collection,
property = 'children',
parseItem = (a, level) => a,
level = 1
) => {
const parseObject = (obj) =>
parseItem(
{
..._.omit(obj, [property]),
},
level
);
return collection.reduce((items, currentValue, index) => {
let localItems = [...items];
const parsedItem = parseObject(currentValue, level);
localItems.push(parsedItem);
if (Array.isArray(currentValue[property])) {
const flattenArray = nestedArrayToFlatten(
currentValue[property],
property,
parseItem,
level + 1
);
localItems = _.concat(localItems, flattenArray);
}
return localItems;
}, []);
};
const assocDepthLevelToObjectTree = (
objects,
level = 1,
propertyName = 'level'
) => {
for (let i = 0; i < objects.length; i++) {
const object = objects[i];
object[propertyName] = level;
if (object.children) {
assocDepthLevelToObjectTree(object.children, level + 1, propertyName);
}
}
return objects;
};
const castCommaListEnvVarToArray = (envVar: string): Array<string> => {
return envVar ? envVar?.split(',')?.map(_.trim) : [];
};
export {
templateRender,
accumSum,
@@ -449,4 +501,7 @@ export {
dateRangeFromToCollection,
transformToMapKeyValue,
mergeObjectsBykey,
nestedArrayToFlatten,
assocDepthLevelToObjectTree,
castCommaListEnvVarToArray
};

View File

@@ -1,411 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bigcapital | Reset your password</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
/*All the styling goes here*/
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%;
}
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%;
}
.wrapper {
box-sizing: border-box;
padding: 20px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: bold;
line-height: 1.4;
margin: 0;
margin-bottom: 0;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #3498db;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%;
}
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
.btn-primary table td {
background-color: #2d95fd;
}
.btn-primary a {
background-color: #2d95fd;
border-color: #2d95fd;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.mb4{
margin-bottom: 4rem;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
.btn-primary table td:hover {
background-color: #004dd0 !important;
}
.btn-primary a:hover {
background-color: #004dd0 !important;
border-color: #004dd0 !important;
}
}
[data-icon="bigcapital"] path {
fill: #004dd0;
}
[data-icon='bigcapital'] .path-1,
[data-icon='bigcapital'] .path-13 {
fill: #2d95fd;
}
</style>
</head>
<body class="">
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<table role="presentation" class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p class="align-center">
<svg data-icon="bigcapital" class="bigcapital" width="190" height="37" viewBox="0 0 309.09 42.89"><desc>bigcapital</desc><path d="M56,3.16,61.33,8.5,31.94,37.9l-5.35-5.35Z" class="path-1" fill-rule="evenodd"></path><path d="M29.53,6.94l5.35,5.34L5.49,41.67.14,36.33l15.8-15.8Z" class="path-2" fill-rule="evenodd"></path><path d="M94.36,38.87H79.62v-31H94c6.33,0,10.22,3.15,10.22,8V16a7.22,7.22,0,0,1-4.07,6.69c3.58,1.37,5.8,3.45,5.8,7.61v.09C106,36,101.35,38.87,94.36,38.87Zm3.1-21.81c0-2-1.59-3.19-4.47-3.19H86.26v6.55h6.29c3,0,4.91-1,4.91-3.28Zm1.72,12.39c0-2.08-1.54-3.37-5-3.37H86.26V32.9h8.1c3,0,4.82-1.06,4.82-3.36Z" class="path-3" fill-rule="evenodd"></path><path d="M110.56,12.54v-6h7.08v6Zm.18,26.33V15.15h6.72V38.87Z" class="path-4" fill-rule="evenodd"></path><path d="M134,46a22.55,22.55,0,0,1-10.49-2.47l2.3-5a15.52,15.52,0,0,0,8,2.17c4.61,0,6.78-2.21,6.78-6.46V33.08c-2,2.39-4.16,3.85-7.75,3.85-5.53,0-10.53-4-10.53-11.07v-.09c0-7.08,5.09-11.06,10.53-11.06a9.63,9.63,0,0,1,7.66,3.54v-3.1h6.72V33.52C147.2,42.46,142.78,46,134,46Zm6.6-20.27a5.79,5.79,0,0,0-11.56,0v.09a5.42,5.42,0,0,0,5.76,5.49,5.49,5.49,0,0,0,5.8-5.49Z" class="path-5" fill-rule="evenodd"></path><path d="M164,39.41a12.11,12.11,0,0,1-12.35-12.26v-.09a12.18,12.18,0,0,1,12.44-12.35c4.47,0,7.25,1.5,9.47,4l-4.12,4.43a6.93,6.93,0,0,0-5.4-2.61c-3.36,0-5.75,3-5.75,6.46v.09c0,3.63,2.34,6.55,6,6.55,2.26,0,3.8-1,5.44-2.53l3.94,4A12,12,0,0,1,164,39.41Z" class="path-6" fill-rule="evenodd"></path><path d="M191.51,38.87V36.31a9.15,9.15,0,0,1-7.17,3c-4.47,0-8.15-2.57-8.15-7.26V32c0-5.18,3.94-7.57,9.56-7.57a16.74,16.74,0,0,1,5.8,1V25c0-2.79-1.72-4.34-5.09-4.34a17.57,17.57,0,0,0-6.55,1.28l-1.68-5.13a21,21,0,0,1,9.21-1.9c7.34,0,10.57,3.8,10.57,10.22V38.87Zm.13-9.55a10.3,10.3,0,0,0-4.29-.89c-2.88,0-4.65,1.15-4.65,3.27v.09c0,1.82,1.5,2.88,3.67,2.88,3.15,0,5.27-1.73,5.27-4.16Z" class="path-7" fill-rule="evenodd"></path><path d="M217.49,39.32a9.1,9.1,0,0,1-7.39-3.54V46h-6.73V15.15h6.73v3.41a8.7,8.7,0,0,1,7.39-3.85c5.53,0,10.8,4.34,10.8,12.26v.09C228.29,35,223.11,39.32,217.49,39.32ZM221.56,27c0-3.94-2.66-6.55-5.8-6.55S210,23,210,27v.09c0,3.94,2.61,6.55,5.75,6.55s5.8-2.57,5.8-6.55Z" class="path-8" fill-rule="evenodd"></path><path d="M232.93,12.54v-6H240v6Zm.18,26.33V15.15h6.73V38.87Z" class="path-9" fill-rule="evenodd"></path><path d="M253.73,39.27c-4.11,0-6.9-1.63-6.9-7.12V20.91H244V15.15h2.83V9.09h6.73v6.06h5.57v5.76h-5.57V31c0,1.55.66,2.3,2.16,2.3A6.84,6.84,0,0,0,259,32.5v5.4A9.9,9.9,0,0,1,253.73,39.27Z" class="path-10" fill-rule="evenodd"></path><path d="M277.55,38.87V36.31a9.15,9.15,0,0,1-7.18,3c-4.46,0-8.14-2.57-8.14-7.26V32c0-5.18,3.94-7.57,9.56-7.57a16.74,16.74,0,0,1,5.8,1V25c0-2.79-1.73-4.34-5.09-4.34A17.57,17.57,0,0,0,266,21.92l-1.68-5.13a20.94,20.94,0,0,1,9.2-1.9c7.35,0,10.58,3.8,10.58,10.22V38.87Zm.13-9.55a10.31,10.31,0,0,0-4.3-.89c-2.87,0-4.64,1.15-4.64,3.27v.09c0,1.82,1.5,2.88,3.67,2.88,3.14,0,5.27-1.73,5.27-4.16Z" class="path-11" fill-rule="evenodd"></path><path d="M289.72,38.87V6.57h6.72v32.3Z" class="path-12" fill-rule="evenodd"></path><path d="M302.06,38.87V31.79h7.17v7.08Z" class="path-13" fill-rule="evenodd"></path></svg>
</p>
<hr />
<p class="align-center">
<h3>License Code</h3>
</p>
<p class="mgb-1x">
<h1>{{ licenseCode }}</h1>
</p>
<p class="email-note">
This is an automatically generated email please do not reply to
this email. If you face any issues, please contact us at <a href="mailto:{{ successEmail }}">{{ successEmail }}</a> or call <u>{{ successPhoneNumber }}</u></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div class="footer">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block powered-by">
Powered by <a href="http://htmlemail.io">Bigcapital.com</a>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@@ -391,10 +391,8 @@
</tr>
</tbody>
</table>
<p>If you did not make this request, please contact us or ignore this message.</p>
<p class="email-note">
This is an automatically generated email please do not reply to
this email. If you face any issues, please contact us at {{ contact_us_email }}</p>
<p>If this was a mistake, just ignore this email and nothing will happen.</p>
<p class="email-note">This is an automatically generated email please do not reply to this email.</p>
</td>
</tr>
</table>

View File

@@ -1,407 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bigcapital | Reset your password</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%;
}
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%;
}
.wrapper {
box-sizing: border-box;
padding: 20px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: bold;
line-height: 1.4;
margin: 0;
margin-bottom: 0;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #3498db;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%;
}
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
.btn-primary table td {
background-color: #2d95fd;
}
.btn-primary a {
background-color: #2d95fd;
border-color: #2d95fd;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.mb4{
margin-bottom: 4rem;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
.btn-primary table td:hover {
background-color: #004dd0 !important;
}
.btn-primary a:hover {
background-color: #004dd0 !important;
border-color: #004dd0 !important;
}
}
[data-icon="bigcapital"] path {
fill: #004dd0;
}
[data-icon='bigcapital'] .path-1,
[data-icon='bigcapital'] .path-13 {
fill: #2d95fd;
}
</style>
</head>
<body class="">
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<table role="presentation" class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p class="align-center">
<img src="cid:bigcapital_logo" />
</p>
<hr />
<p class="align-center">
<h3>Hi {{ firstName }}, Welcome to Bigcapital, </h3>
</p>
<p class="mgb-1x">
Your organization Id: <strong>{{ organizationId }}</strong>
</p>
<p class="mgb-1x">We are available to help you get started and answer any questions you may have. You can also email <a href="mailto:{{ successEmail }}">{{ successEmail }}</a> or call <u>{{ successPhoneNumber }}</u> about your set-up questions.</p>
<p class="mgb-2-5x">Thank you for trusting Bigcapital Software for your business needs. We look forward to serving you!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div class="footer">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block powered-by">
Made by Bigcapital Technologies, Inc
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@@ -5,10 +5,10 @@ USER root
WORKDIR /app
# Install dependencies
COPY package.json ./
COPY package*.json ./
COPY lerna.json ./
COPY ./packages/webapp/package.json /app/packages/webapp/package.json
COPY ./packages/webapp/package*.json /app/packages/webapp/
RUN npm install
RUN npm run bootstrap

View File

@@ -1,7 +1,18 @@
const path = require('path');
const webpack = require('webpack');
const dotenv = require('dotenv-webpack');
module.exports = {
webpack: {
plugins: [
new dotenv(),
new webpack.DefinePlugin({
'process.env': {
MONOREPO_VERSION: JSON.stringify(require('../../lerna.json').version),
},
}),
],
alias: {
'@': path.resolve(__dirname, 'src'),
},

View File

@@ -1,6 +1,6 @@
{
"name": "@bigcapital/webapp",
"version": "1.7.1",
"version": "0.9.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1205,9 +1205,9 @@
}
},
"@blueprintjs-formik/core": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.2.1.tgz",
"integrity": "sha512-YGJe+QorDGbkWDSUg6x69LYGN62Kgvb92Iz/voqmszVRKj4KcoPvd/7coF8Jmu+ZQE6LcwM/9ccB2i63L99ITA==",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.4.tgz",
"integrity": "sha512-gksuBYXXvX7IhZXbPEFEAHgmJWp1vt/GTMW0GYBkCoGBAvXy08hIHjMc85M0WNyen+tic3NRas6I2wsjgokgqw==",
"requires": {
"lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0",
@@ -1227,9 +1227,9 @@
}
},
"@blueprintjs-formik/select": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.1.5.tgz",
"integrity": "sha512-EqGbuoiS1VrWpzjd39uVhBAmfVobdpgqalGcpODyGA+XAYoft1UU12yzTzrEOwBZpQKiC12UQwekUPspYBsVKA==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.5.tgz",
"integrity": "sha512-Sztf5dOemedUBfEjnDWD8ryfMU/x95hyhIgJT5/ywC/jQfX+d/K2OhujklTrCDzQilUeAJLoVkSdV+w77n8ckQ==",
"requires": {
"lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0",
@@ -1929,137 +1929,6 @@
"reselect": "^4.1.7"
}
},
"@sentry/browser": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.19.7.tgz",
"integrity": "sha512-oDbklp4O3MtAM4mtuwyZLrgO1qDVYIujzNJQzXmi9YzymJCuzMLSRDvhY83NNDCRxf0pds4DShgYeZdbSyKraA==",
"requires": {
"@sentry/core": "6.19.7",
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/core": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.7.tgz",
"integrity": "sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw==",
"requires": {
"@sentry/hub": "6.19.7",
"@sentry/minimal": "6.19.7",
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/hub": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.7.tgz",
"integrity": "sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA==",
"requires": {
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/minimal": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.7.tgz",
"integrity": "sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ==",
"requires": {
"@sentry/hub": "6.19.7",
"@sentry/types": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/react": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.19.7.tgz",
"integrity": "sha512-VzJeBg/v41jfxUYPkH2WYrKjWc4YiMLzDX0f4Zf6WkJ4v3IlDDSkX6DfmWekjTKBho6wiMkSNy2hJ1dHfGZ9jA==",
"requires": {
"@sentry/browser": "6.19.7",
"@sentry/minimal": "6.19.7",
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/tracing": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.19.7.tgz",
"integrity": "sha512-ol4TupNnv9Zd+bZei7B6Ygnr9N3Gp1PUrNI761QSlHtPC25xXC5ssSD3GMhBgyQrcvpuRcCFHVNNM97tN5cZiA==",
"requires": {
"@sentry/hub": "6.19.7",
"@sentry/minimal": "6.19.7",
"@sentry/types": "6.19.7",
"@sentry/utils": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/types": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.7.tgz",
"integrity": "sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg=="
},
"@sentry/utils": {
"version": "6.19.7",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.7.tgz",
"integrity": "sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA==",
"requires": {
"@sentry/types": "6.19.7",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sheerun/mutationobserver-shim": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
@@ -5985,11 +5854,27 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"requires": {
"dotenv": "^8.2.0"
}
},
"dotenv-expand": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"dotenv-webpack": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz",
"integrity": "sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==",
"requires": {
"dotenv-defaults": "^2.0.2"
}
},
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -7298,6 +7183,11 @@
"locate-path": "^3.0.0"
}
},
"flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
},
"flat-cache": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
@@ -17861,4 +17751,4 @@
"integrity": "sha512-7UlRWU4Q3uCMCeDVMOm7eBrIu145OqsIJ3p6zq58l8UsSYwKWxc6zEapC5YA9tIeh0oheb4cT9Kk2Wq353loFg=="
}
}
}
}

View File

@@ -1,11 +1,11 @@
{
"name": "@bigcapital/webapp",
"version": "1.7.1",
"version": "0.9.6",
"private": true,
"dependencies": {
"@blueprintjs-formik/core": "^0.2.1",
"@blueprintjs-formik/core": "^0.3.4",
"@blueprintjs-formik/datetime": "^0.3.4",
"@blueprintjs-formik/select": "^0.1.4",
"@blueprintjs-formik/select": "^0.2.5",
"@blueprintjs/core": "^3.50.2",
"@blueprintjs/datetime": "^3.23.12",
"@blueprintjs/popover2": "^0.11.1",
@@ -16,8 +16,6 @@
"@casl/react": "^2.3.0",
"@craco/craco": "^5.9.0",
"@reduxjs/toolkit": "^1.2.5",
"@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
@@ -44,7 +42,9 @@
"deep-map-keys": "^2.0.1",
"deepdash": "^5.3.9",
"dependency-graph": "^0.11.0",
"dotenv-webpack": "^8.0.1",
"fast-deep-equal": "^3.1.3",
"flat": "^5.0.2",
"formik": "^2.2.5",
"http-proxy-middleware": "^1.0.0",
"jest": "24.9.0",

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

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