Compare commits

..

82 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
7a33f79268 chore: update mysql docker container restart policy 2023-07-23 20:05:34 +02:00
Ahmed Bouhuolia
ef5ef647d4 chore: change docker restart policy to unless-stopped 2023-07-23 19:54:55 +02:00
Suhaib Affan
ce62a0524c Added restart policy to docker compose files. 2023-07-19 20:19:56 -04:00
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
146 changed files with 2646 additions and 3696 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

@@ -29,7 +29,7 @@ TENANT_DB_NAME_PERFIX=bigcapital_tenant_
# TENANT_DB_CHARSET= # TENANT_DB_CHARSET=
# Application # Application
BASE_URL=https://bigcapital.ly BASE_URL=http://example.com
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
# Jobs MongoDB # Jobs MongoDB

View File

@@ -2,6 +2,42 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
# [0.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 ## [0.9.6] - 12-06-2023
`@bigcapital/webapp` `@bigcapital/webapp`

View File

@@ -47,3 +47,38 @@ Bigcapital is a smart and open-source accounting and inventory software, Bigcapi
# Changelog # Changelog
Please see [Releases](https://github.com/bigcapitalhq/bigcapital/releases) for more information what has changed recently. 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

@@ -21,10 +21,16 @@ services:
depends_on: depends_on:
- server - server
- webapp - webapp
deploy:
restart_policy:
condition: unless-stopped
webapp: webapp:
container_name: bigcapital-webapp container_name: bigcapital-webapp
image: ghcr.io/bigcapitalhq/webapp:latest image: ghcr.io/bigcapitalhq/webapp:latest
deploy:
restart_policy:
condition: unless-stopped
server: server:
container_name: bigcapital-server container_name: bigcapital-server
@@ -37,6 +43,9 @@ services:
- mysql - mysql
- mongo - mongo
- redis - redis
deploy:
restart_policy:
condition: unless-stopped
environment: environment:
# Mail # Mail
- MAIL_HOST=${MAIL_HOST} - MAIL_HOST=${MAIL_HOST}
@@ -93,6 +102,9 @@ services:
mysql: mysql:
container_name: bigcapital-mysql container_name: bigcapital-mysql
deploy:
restart_policy:
condition: unless-stopped
build: build:
context: ./docker/mariadb context: ./docker/mariadb
environment: environment:
@@ -106,7 +118,10 @@ services:
- '3306' - '3306'
mongo: mongo:
container_name: bigcapital-mongo container_name: bigcapital-mongo
deploy:
restart_policy:
condition: unless-stopped
build: ./docker/mongo build: ./docker/mongo
expose: expose:
- '27017' - '27017'
@@ -115,6 +130,9 @@ services:
redis: redis:
container_name: bigcapital-redis container_name: bigcapital-redis
deploy:
restart_policy:
condition: unless-stopped
build: build:
context: ./docker/redis context: ./docker/redis
expose: expose:

View File

@@ -20,6 +20,9 @@ services:
- '3306' - '3306'
ports: ports:
- '3306:3306' - '3306:3306'
deploy:
restart_policy:
condition: unless-stopped
mongo: mongo:
build: ./docker/mongo build: ./docker/mongo
@@ -29,6 +32,9 @@ services:
- mongo:/var/lib/mongodb - mongo:/var/lib/mongodb
ports: ports:
- '27017:27017' - '27017:27017'
deploy:
restart_policy:
condition: unless-stopped
redis: redis:
build: build:
@@ -37,6 +43,9 @@ services:
- "6379" - "6379"
volumes: volumes:
- redis:/data - redis:/data
deploy:
restart_policy:
condition: unless-stopped
# Volumes # Volumes
volumes: volumes:

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 { test, expect, Page } from '@playwright/test';
import { faker } from '@faker-js/faker';
import { clearLocalStorage, defaultPageConfig } from './_utils';
let authPage: Page; let authPage: Page;
test.describe('authentication', () => { test.describe('authentication', () => {
test.beforeAll(async ({ browser }) => { 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.describe('login', () => {
test.beforeAll(async () => { test.beforeEach(async () => {
await authPage.goto('/auth/login'); await authPage.goto('/auth/login');
}); });
test('should show the login page.', async () => { test('should show the login page.', async () => {
@@ -30,10 +39,23 @@ test.describe('authentication', () => {
await authPage.getByRole('link', { name: 'Sign up' }).click(); await authPage.getByRole('link', { name: 'Sign up' }).click();
await expect(authPage.url()).toContain('/auth/register'); 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.describe('register', () => {
test.beforeAll(async () => { test.beforeEach(async () => {
await authPage.goto('/auth/register'); await authPage.goto('/auth/register');
}); });
test('should first name, last name, email and password be required.', async () => { test('should first name, last name, email and password be required.', async () => {
@@ -52,10 +74,36 @@ test.describe('authentication', () => {
'Password is a required field' '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.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'); await authPage.goto('/auth/send_reset_password');
}); });
test('should email be required.', async () => { 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);
});
});
});

14
package-lock.json generated
View File

@@ -333,6 +333,12 @@
"@jridgewell/trace-mapping": "0.3.9" "@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": { "@gar/promisify": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -945,6 +951,7 @@
"version": "1.32.3", "version": "1.32.3",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz",
"integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==", "integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==",
"dev": true,
"requires": { "requires": {
"@types/node": "*", "@types/node": "*",
"fsevents": "2.3.2", "fsevents": "2.3.2",
@@ -954,7 +961,8 @@
"playwright-core": { "playwright-core": {
"version": "1.32.3", "version": "1.32.3",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.3.tgz", "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": { "@types/node": {
"version": "18.14.6", "version": "18.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", "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": { "@types/normalize-package-data": {
"version": "2.4.1", "version": "2.4.1",
@@ -2324,6 +2333,7 @@
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true "optional": true
}, },
"function-bind": { "function-bind": {

View File

@@ -18,12 +18,13 @@
"shared/*" "shared/*"
], ],
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2", "@commitlint/config-conventional": "^17.4.2",
"@commitlint/config-lerna-scopes": "^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", "husky": "^8.0.3",
"lerna": "^6.4.1", "lerna": "^6.4.1"
"@commitlint/cli": "^17.4.2",
"@playwright/test": "^1.32.3"
}, },
"engines": { "engines": {
"node": "14.x" "node": "14.x"

View File

@@ -92,6 +92,7 @@ export default class AuthenticationController extends BaseController {
check('password') check('password')
.exists() .exists()
.isString() .isString()
.isLength({ min: 6 })
.trim() .trim()
.escape() .escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
@@ -106,7 +107,7 @@ export default class AuthenticationController extends BaseController {
return [ return [
check('password') check('password')
.exists() .exists()
.isLength({ min: 5 }) .isLength({ min: 6 })
.custom((value, { req }) => { .custom((value, { req }) => {
if (value !== req.body.confirm_password) { if (value !== req.body.confirm_password) {
throw new Error("Passwords don't match"); throw new Error("Passwords don't match");

View File

@@ -95,16 +95,15 @@ module.exports = {
* JWT secret. * JWT secret.
*/ */
jwtSecret: process.env.JWT_SECRET, jwtSecret: process.env.JWT_SECRET,
/**
*
*/
resetPasswordSeconds: 600, resetPasswordSeconds: 600,
/** /**
* * Application base URL.
*/ */
customerSuccess: {
email: 'success@bigcapital.ly',
phoneNumber: '(218) 92 791 8381',
},
baseURL: process.env.BASE_URL, baseURL: process.env.BASE_URL,
/** /**
@@ -137,16 +136,6 @@ module.exports = {
}, },
}, },
/**
* Users registeration configuration.
*/
registration: {
countries: {
whitelist: ['LY'],
blacklist: [],
},
},
/** /**
* Sign-up restrictions * Sign-up restrictions
*/ */
@@ -167,8 +156,6 @@ module.exports = {
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT, browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
}, },
protocol: '',
hostname: '',
scheduleComputeItemCost: 'in 5 seconds', scheduleComputeItemCost: 'in 5 seconds',
/** /**

View File

@@ -41,6 +41,8 @@ export interface ILedgerEntry {
index: number; index: number;
indexGroup?: number; indexGroup?: number;
note?: string;
userId?: number; userId?: number;
itemId?: number; itemId?: number;
branchId?: number; branchId?: number;

View File

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

@@ -31,7 +31,6 @@ import OrgBuildSmsNotificationSubscriber from '@/subscribers/Organization/BuildS
import PurgeUserAbilityCache from '@/services/Users/PurgeUserAbilityCache'; import PurgeUserAbilityCache from '@/services/Users/PurgeUserAbilityCache';
import ResetLoginThrottleSubscriber from '@/subscribers/Authentication/ResetLoginThrottle'; import ResetLoginThrottleSubscriber from '@/subscribers/Authentication/ResetLoginThrottle';
import AuthenticationSubscriber from '@/subscribers/Authentication/SendResetPasswordMail'; import AuthenticationSubscriber from '@/subscribers/Authentication/SendResetPasswordMail';
import AuthSendWelcomeMailSubscriber from '@/subscribers/Authentication/SendWelcomeMail';
import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedUser'; import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedUser';
import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer'; import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer';
import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer'; import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer';
@@ -120,7 +119,6 @@ export const susbcribers = () => {
PurgeUserAbilityCache, PurgeUserAbilityCache,
ResetLoginThrottleSubscriber, ResetLoginThrottleSubscriber,
AuthenticationSubscriber, AuthenticationSubscriber,
AuthSendWelcomeMailSubscriber,
PurgeAuthorizedUserOnceRoleMutate, PurgeAuthorizedUserOnceRoleMutate,
SendSmsNotificationToCustomer, SendSmsNotificationToCustomer,
SendSmsNotificationSaleReceipt, SendSmsNotificationSaleReceipt,

View File

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

View File

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

View File

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

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

View File

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

@@ -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> </tr>
</tbody> </tbody>
</table> </table>
<p>If you did not make this request, please contact us or ignore this message.</p> <p>If this was a mistake, just ignore this email and nothing will happen.</p>
<p class="email-note"> <p class="email-note">This is an automatically generated email please do not reply to this email.</p>
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>
</td> </td>
</tr> </tr>
</table> </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

@@ -1,6 +1,6 @@
{ {
"name": "@bigcapital/webapp", "name": "@bigcapital/webapp",
"version": "1.7.1", "version": "0.9.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1205,9 +1205,9 @@
} }
}, },
"@blueprintjs-formik/core": { "@blueprintjs-formik/core": {
"version": "0.3.3", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.4.tgz",
"integrity": "sha512-ko7g54YSEcSq2K/GEpmiTG0foGLqe7DwgXGhkGxYEiHhLAUv8WvQmrFsm8e/KOW7n8mLGq0uaZVe2l8m3JTGGQ==", "integrity": "sha512-gksuBYXXvX7IhZXbPEFEAHgmJWp1vt/GTMW0GYBkCoGBAvXy08hIHjMc85M0WNyen+tic3NRas6I2wsjgokgqw==",
"requires": { "requires": {
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0", "lodash.keyby": "^4.6.0",
@@ -1227,9 +1227,9 @@
} }
}, },
"@blueprintjs-formik/select": { "@blueprintjs-formik/select": {
"version": "0.2.3", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.3.tgz", "resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.5.tgz",
"integrity": "sha512-j/zkX0B9wgtoHgK6Z/rlowB7F7zemrAajBU+d3caCoEYMMqwAI0XA++GytqrIhv5fEGjkZ1hkxS9j8eqX8vtjA==", "integrity": "sha512-Sztf5dOemedUBfEjnDWD8ryfMU/x95hyhIgJT5/ywC/jQfX+d/K2OhujklTrCDzQilUeAJLoVkSdV+w77n8ckQ==",
"requires": { "requires": {
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0", "lodash.keyby": "^4.6.0",
@@ -17751,4 +17751,4 @@
"integrity": "sha512-7UlRWU4Q3uCMCeDVMOm7eBrIu145OqsIJ3p6zq58l8UsSYwKWxc6zEapC5YA9tIeh0oheb4cT9Kk2Wq353loFg==" "integrity": "sha512-7UlRWU4Q3uCMCeDVMOm7eBrIu145OqsIJ3p6zq58l8UsSYwKWxc6zEapC5YA9tIeh0oheb4cT9Kk2Wq353loFg=="
} }
} }
} }

View File

@@ -3,9 +3,9 @@
"version": "0.9.6", "version": "0.9.6",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@blueprintjs-formik/core": "^0.3.3", "@blueprintjs-formik/core": "^0.3.4",
"@blueprintjs-formik/datetime": "^0.3.4", "@blueprintjs-formik/datetime": "^0.3.4",
"@blueprintjs-formik/select": "^0.2.3", "@blueprintjs-formik/select": "^0.2.5",
"@blueprintjs/core": "^3.50.2", "@blueprintjs/core": "^3.50.2",
"@blueprintjs/datetime": "^3.23.12", "@blueprintjs/datetime": "^3.23.12",
"@blueprintjs/popover2": "^0.11.1", "@blueprintjs/popover2": "^0.11.1",

View File

@@ -5,59 +5,20 @@ import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from '../Forms'; import { FSelect } from '../Forms';
/** /**
* * Branch select field.
* @param {*} query
* @param {*} branch
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const branchItemPredicate = (query, branch, _index, exactMatch) => {
const normalizedTitle = branch.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param {*} film
* @param {*} param1
* @returns
*/
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
const text = `${branch.name}`;
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
label={branch.code}
key={branch.id}
onClick={handleClick}
text={text}
/>
);
};
const branchSelectProps = {
itemPredicate: branchItemPredicate,
itemRenderer: branchItemRenderer,
valueAccessor: 'id',
labelAccessor: 'name',
};
/**
*
* @param {*} param0 * @param {*} param0
* @returns * @returns {JSX.Element}
*/ */
export function BranchSelect({ branches, ...rest }) { export function BranchSelect({ branches, ...rest }) {
return <FSelect {...branchSelectProps} {...rest} items={branches} />; return (
<FSelect
valueAccessor={'id'}
textAccessor={'name'}
labelAccessor={'code'}
{...rest}
items={branches}
/>
);
} }
/** /**

View File

@@ -1,119 +0,0 @@
// @ts-nocheck
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import { FormattedMessage as T } from '@/components';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { MenuItem, Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import {
itemPredicate,
handleContactRenderer,
createNewItemRenderer,
createNewItemFromQuery,
} from './utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
function CustomerSelectFieldRoot({
// #withDrawerActions
openDrawer,
// #ownProps
contacts,
initialContactId,
selectedContactId,
defaultSelectText = <T id={'select_contact'} />,
onContactSelected,
popoverFill = false,
disabled = false,
allowCreate,
buttonProps,
...restProps
}) {
const localContacts = useMemo(
() =>
contacts.map((contact) => ({
...contact,
_id: `${contact.id}_${contact.contact_type}`,
})),
[contacts],
);
const initialContact = useMemo(
() => contacts.find((a) => a.id === initialContactId),
[initialContactId, contacts],
);
const [selecetedContact, setSelectedContact] = useState(
initialContact || null,
);
useEffect(() => {
if (typeof selectedContactId !== 'undefined') {
const account = selectedContactId
? contacts.find((a) => a.id === selectedContactId)
: null;
setSelectedContact(account);
}
}, [selectedContactId, contacts, setSelectedContact]);
const handleContactSelect = useCallback(
(contact) => {
if (contact.id) {
setSelectedContact({ ...contact });
onContactSelected && onContactSelected(contact);
} else {
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
}
},
[setSelectedContact, onContactSelected, openDrawer],
);
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
return (
<Select
items={localContacts}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
itemRenderer={handleContactRenderer}
itemPredicate={itemPredicate}
filterable={true}
disabled={disabled}
onItemSelect={handleContactSelect}
popoverProps={{ minimal: true, usePortal: !popoverFill }}
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
inputProps={{
placeholder: intl.get('filter_'),
}}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
createNewItemPosition={'top'}
{...restProps}
>
<Button
disabled={disabled}
text={
selecetedContact ? selecetedContact.display_name : defaultSelectText
}
{...buttonProps}
/>
</Select>
);
}
export const CustomerSelectField = R.compose(withDrawerActions)(
CustomerSelectFieldRoot,
);

View File

@@ -0,0 +1,48 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { createNewItemFromQuery, createNewItemRenderer } from './utils';
import { FSelect } from '../Forms';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
/**
* Customer select field.
* @returns {React.ReactNode}
*/
function CustomerSelectRoot({
// #withDrawerActions
openDrawer,
// #ownProps
items,
allowCreate,
...props
}) {
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
};
return (
<FSelect
items={items}
textAccessor={'display_name'}
labelAccessor={'code'}
valueAccessor={'id'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...props}
/>
);
}
export const CustomersSelect = R.compose(withDrawerActions)(CustomerSelectRoot);

View File

@@ -1,3 +1,3 @@
// @ts-nocheck // @ts-nocheck
export * from './CustomerSelectField';
export * from './CustomerDrawerLink'; export * from './CustomerDrawerLink';
export * from './CustomersSelect';

View File

@@ -53,7 +53,7 @@ function DashboardTopbar({
}; };
return ( return (
<div class="dashboard__topbar"> <div class="dashboard__topbar" data-testId={'dashboard-topbar'}>
<div class="dashboard__topbar-left"> <div class="dashboard__topbar-left">
<div class="dashboard__topbar-sidebar-toggle"> <div class="dashboard__topbar-sidebar-toggle">
<Tooltip <Tooltip

View File

@@ -16,7 +16,7 @@ export function FSelect({ ...props }) {
/> />
); );
}; };
return <Select input={input} {...props} fill={true} />; return <Select input={input} fill={true} {...props} />;
} }
const SelectButton = styled(Button)` const SelectButton = styled(Button)`

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Button } from '@blueprintjs/core';
import { Icon } from '@/components';
export function FormWarehouseSelectButton({ text }) {
return (
<Button
text={intl.get('page_form.warehouse_button.label', { text })}
minimal={true}
small={true}
icon={<Icon icon={'warehouse-16'} iconSize={16} />}
/>
);
}
export function FormBranchSelectButton({ text }) {
return (
<Button
text={intl.get('page_form.branch_button.label', { text })}
minimal={true}
small={true}
icon={<Icon icon={'branch-16'} iconSize={16} />}
/>
);
}

View File

@@ -1,3 +1,4 @@
// @ts-nocheck // @ts-nocheck
export * from './FormTopbar'; export * from './FormTopbar';
export * from './FormTopbarSelectInputs';
export * from './PageFormBigNumber'; export * from './PageFormBigNumber';

View File

@@ -1,118 +0,0 @@
// @ts-nocheck
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import { FormattedMessage as T } from '@/components';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { MenuItem, Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import {
itemPredicate,
handleContactRenderer,
createNewItemFromQuery,
createNewItemRenderer,
} from './utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
function VendorSelectFieldRoot({
// #withDrawerActions
openDrawer,
// #ownProps
contacts,
initialContactId,
selectedContactId,
defaultSelectText = <T id={'select_contact'} />,
onContactSelected,
popoverFill = false,
disabled = false,
allowCreate,
buttonProps,
...restProps
}) {
const localContacts = useMemo(
() =>
contacts.map((contact) => ({
...contact,
_id: `${contact.id}_${contact.contact_type}`,
})),
[contacts],
);
const initialContact = useMemo(
() => contacts.find((a) => a.id === initialContactId),
[initialContactId, contacts],
);
const [selecetedContact, setSelectedContact] = useState(
initialContact || null,
);
useEffect(() => {
if (typeof selectedContactId !== 'undefined') {
const account = selectedContactId
? contacts.find((a) => a.id === selectedContactId)
: null;
setSelectedContact(account);
}
}, [selectedContactId, contacts, setSelectedContact]);
const handleContactSelect = useCallback(
(contact) => {
if (contact.id) {
setSelectedContact({ ...contact });
onContactSelected && onContactSelected(contact);
} else {
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
}
},
[setSelectedContact, onContactSelected, openDrawer],
);
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
return (
<Select
items={localContacts}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
itemRenderer={handleContactRenderer}
itemPredicate={itemPredicate}
filterable={true}
disabled={disabled}
onItemSelect={handleContactSelect}
popoverProps={{ minimal: true, usePortal: !popoverFill }}
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
inputProps={{
placeholder: intl.get('filter_'),
}}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
createNewItemPosition={'top'}
{...restProps}
>
<Button
disabled={disabled}
text={
selecetedContact ? selecetedContact.display_name : defaultSelectText
}
{...buttonProps}
/>
</Select>
);
}
export const VendorSelectField = R.compose(withDrawerActions)(
VendorSelectFieldRoot,
);

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { createNewItemFromQuery, createNewItemRenderer } from './utils';
import { FSelect } from '../Forms';
import { DRAWERS } from '@/constants/drawers';
/**
* Vendor select.
* @returns {React.ReactNode}
*/
function VendorsSelectRoot({
// #withDrawerActions
openDrawer,
// #ownProps
items,
allowCreate,
...restProps
}) {
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
};
return (
<FSelect
items={items}
textAccessor={'display_name'}
labelAccessor={'code'}
valueAccessor={'id'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...restProps}
/>
);
}
export const VendorsSelect = R.compose(withDrawerActions)(VendorsSelectRoot);

View File

@@ -1,3 +1,3 @@
// @ts-nocheck // @ts-nocheck
export * from './VendorDrawerLink' export * from './VendorDrawerLink'
export * from './VendorSelectField' export * from './VendorsSelect';

View File

@@ -1,75 +1,21 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from '../Forms'; import { FSelect } from '../Forms';
/** /**
* * Warehouse select field.
* @param {*} query
* @param {*} warehouse
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const warehouseItemPredicate = (query, warehouse, _index, exactMatch) => {
const normalizedTitle = warehouse.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return (
`${warehouse.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
);
}
};
/**
*
* @param {*} film
* @param {*} param1
* @returns
*/
const warehouseItemRenderer = (
warehouse,
{ handleClick, modifiers, query },
) => {
const text = `${warehouse.name}`;
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
label={warehouse.code}
key={warehouse.id}
onClick={handleClick}
text={text}
/>
);
};
const warehouseSelectProps = {
itemPredicate: warehouseItemPredicate,
itemRenderer: warehouseItemRenderer,
valueAccessor: 'id',
labelAccessor: 'name',
};
/**
*
* @param {*} param0 * @param {*} param0
* @returns * @returns
*/ */
export function WarehouseSelect({ warehouses, ...rest }) { export function WarehouseSelect({ warehouses, ...rest }) {
return <FSelect {...warehouseSelectProps} {...rest} items={warehouses} />; return (
} <FSelect
valueAccessor={'id'}
/** labelAccessor={'code'}
* textAccessor={'name'}
* @param {*} param0 popoverProps={{ minimal: true, usePortal: true, inline: false }}
* @returns {...rest}
*/ items={warehouses}
export function WarehouseSelectButton({ label, ...rest }) { />
return <Button text={label} />; );
} }

View File

@@ -1,7 +0,0 @@
// @ts-nocheck
import intl from 'react-intl-universal';
export default [
{ name: intl.get('decrement'), value: 'decrement' },
{ name: intl.get('increment'), value: 'increment' },
]

View File

@@ -4,7 +4,6 @@ export * from './tableStyle';
export * from './features'; export * from './features';
export * from './cellTypes'; export * from './cellTypes';
export * from './classes'; export * from './classes';
export * from './adjustmentType';
export * from './cashflowOptions'; export * from './cashflowOptions';
export const Align = { Left: 'left', Right: 'right', Center: 'center' }; export const Align = { Left: 'left', Right: 'right', Center: 'center' };

View File

@@ -25,13 +25,14 @@ import {
CashflowAction, CashflowAction,
PreferencesAbility, PreferencesAbility,
} from '@/constants/abilityOption'; } from '@/constants/abilityOption';
import { DialogsName } from './dialogs';
export const SidebarMenu = [ export const SidebarMenu = [
// --------------- // ---------------
// # Homepage // # Homepage
// --------------- // ---------------
{ {
text: <T id={'homepage'} />, text: <T id={'sidebar.homepage'} />,
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
disabled: false, disabled: false,
href: '/', href: '/',
@@ -41,20 +42,20 @@ export const SidebarMenu = [
// # Sales & Inventory // # Sales & Inventory
// --------------- // ---------------
{ {
text: <T id={'sales_inventory'} />, text: <T id={'sidebar.sales_inventory'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'items'} />, text: <T id={'sidebar.items'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Items, overlayId: ISidebarMenuOverlayIds.Items,
children: [ children: [
{ {
text: <T id={'items'} />, text: <T id={'sidebar.items'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'items'} />, text: <T id={'sidebar.items'} />,
href: '/items', href: '/items',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -63,7 +64,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'inventory_adjustments'} />, text: <T id={'sidebar.inventory_adjustments'} />,
href: '/inventory-adjustments', href: '/inventory-adjustments',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -71,12 +72,6 @@ export const SidebarMenu = [
ability: InventoryAdjustmentAction.View, ability: InventoryAdjustmentAction.View,
}, },
}, },
{
text: <T id={'sidebar_warehouse_transfer'} />,
href: '/warehouses-transfers',
type: ISidebarMenuItemType.Link,
feature: Features.Warehouses,
},
{ {
text: <T id={'category_list'} />, text: <T id={'category_list'} />,
href: '/items/categories', href: '/items/categories',
@@ -86,31 +81,20 @@ export const SidebarMenu = [
ability: ItemAction.View, ability: ItemAction.View,
}, },
}, },
],
},
{
text: <T id={'New tasks'} />,
type: ISidebarMenuItemType.Group,
children: [
{ {
text: <T id={'New inventory item'} />, text: <T id={'sidebar.warehouse_transfer'} />,
href: '/items/new', href: '/warehouses-transfers',
type: ISidebarMenuItemType.Link,
permission: {
subject: AbilitySubject.Item,
ability: ItemAction.Create,
},
},
{
text: (
<T id={'warehouse_transfer.label.new_warehouse_transfer'} />
),
href: '/warehouses-transfers/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
feature: Features.Warehouses, feature: Features.Warehouses,
}, },
],
},
{
text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group,
children: [
{ {
text: <T id={'New service'} />, text: <T id={'sidebar.new_inventory_item'} />,
href: '/items/new', href: '/items/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -119,15 +103,30 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'New item category'} />, text: <T id={'sidebar.new_service'} />,
href: '/items/categories/new', href: '/items/new',
type: ISidebarMenuItemType.Dialog, type: ISidebarMenuItemType.Link,
dialogName: 'item-category-form',
permission: { permission: {
subject: AbilitySubject.Item, subject: AbilitySubject.Item,
ability: ItemAction.Create, ability: ItemAction.Create,
}, },
}, },
{
text: <T id={'sidebar.new_item_category'} />,
href: '/items/categories/new',
type: ISidebarMenuItemType.Dialog,
dialogName: DialogsName.ItemCategoryForm,
permission: {
subject: AbilitySubject.Item,
ability: ItemAction.Create,
},
},
{
text: <T id={'sidebar.new_warehouse_transfer'} />,
href: '/warehouses-transfers/new',
type: ISidebarMenuItemType.Link,
feature: Features.Warehouses,
},
], ],
}, },
], ],
@@ -138,16 +137,16 @@ export const SidebarMenu = [
// # Sales // # Sales
// --------------- // ---------------
{ {
text: <T id={'sales'} />, text: <T id={'sidebar.sales'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Sales, overlayId: ISidebarMenuOverlayIds.Sales,
children: [ children: [
{ {
text: <T id={'sales'} />, text: <T id={'sidebar.sales'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'estimates'} />, text: <T id={'sidebar.estimates'} />,
href: '/estimates', href: '/estimates',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -156,7 +155,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'invoices'} />, text: <T id={'sidebar.invoices'} />,
href: '/invoices', href: '/invoices',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -165,7 +164,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'receipts'} />, text: <T id={'sidebar.receipts'} />,
href: '/receipts', href: '/receipts',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -174,12 +173,12 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'sidebar_credit_note'} />, text: <T id={'sidebar.credit_notes'} />,
href: '/credit-notes', href: '/credit-notes',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
}, },
{ {
text: <T id={'payment_receives'} />, text: <T id={'sidebar.payment_receives'} />,
href: '/payment-receives', href: '/payment-receives',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -190,11 +189,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'new_estimate'} />, text: <T id={'sidebar.new_estimate'} />,
href: '/estimates/new', href: '/estimates/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -203,7 +202,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'new_invoice'} />, text: <T id={'sidebar.new_invoice'} />,
href: '/invoices/new', href: '/invoices/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -212,7 +211,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'new_receipt'} />, text: <T id={'sidebar.new_receipt'} />,
href: '/receipts/new', href: '/receipts/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -221,12 +220,12 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'credit_note.label.new_credit_note'} />, text: <T id={'sidebar.new_credit_note'} />,
href: '/credit-notes/new', href: '/credit-notes/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
}, },
{ {
text: <T id={'new_payment_receive'} />, text: <T id={'sidebar.new_payment_receive'} />,
href: '/payment-receives/new', href: '/payment-receives/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -242,12 +241,12 @@ export const SidebarMenu = [
// # Purchases // # Purchases
// --------------- // ---------------
{ {
text: <T id={'purchases'} />, text: <T id={'sidebar.purchases'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Purchases, overlayId: ISidebarMenuOverlayIds.Purchases,
children: [ children: [
{ {
text: <T id={'purchases'} />, text: <T id={'sidebar.purchases'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
@@ -276,11 +275,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'New purchase invoice'} />, text: <T id={'sidebar.new_purchase_invoice'} />,
href: '/bills/new', href: '/bills/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -289,7 +288,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'vendor_credits.label.new_vendor_credit'} />, text: <T id={'sidebar.new_vendor_credit'} />,
href: '/vendor-credits/new', href: '/vendor-credits/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -298,7 +297,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'new_payment_made'} />, text: <T id={'sidebar.new_payment_made'} />,
href: '/payment-mades/new', href: '/payment-mades/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -314,16 +313,16 @@ export const SidebarMenu = [
// # Contacts // # Contacts
// --------------- // ---------------
{ {
text: <T id={'contacts'} />, text: <T id={'sidebar.contacts'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Contacts, overlayId: ISidebarMenuOverlayIds.Contacts,
children: [ children: [
{ {
text: <T id={'contacts'} />, text: <T id={'sidebar.contacts'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'customers'} />, text: <T id={'sidebar.customers'} />,
href: '/customers', href: '/customers',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -332,7 +331,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'vendors'} />, text: <T id={'sidebar.vendors'} />,
href: '/vendors', href: '/vendors',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -343,11 +342,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'new_customer'} />, text: <T id={'sidebar.new_customer'} />,
href: '/customers/new', href: '/customers/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -356,7 +355,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'new_vendor'} />, text: <T id={'sidebar.new_vendor'} />,
href: '/vendors/new', href: '/vendors/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -372,20 +371,20 @@ export const SidebarMenu = [
// # Accounting // # Accounting
// --------------- // ---------------
{ {
text: <T id={'accounting'} />, text: <T id={'sidebar.accounting'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'financial'} />, text: <T id={'sidebar.financial'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Financial, overlayId: ISidebarMenuOverlayIds.Financial,
children: [ children: [
{ {
text: <T id={'financial'} />, text: <T id={'sidebar.financial'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'accounts_chart'} />, text: <T id={'sidebar.accounts_chart'} />,
href: '/accounts', href: '/accounts',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -394,7 +393,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'manual_journals'} />, text: <T id={'sidebar.manual_journals'} />,
href: '/manual-journals', href: '/manual-journals',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -410,11 +409,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'make_journal_entry'} />, text: <T id={'sidebar.make_journal_entry'} />,
href: '/make-journal-entry', href: '/make-journal-entry',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -441,7 +440,7 @@ export const SidebarMenu = [
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'siebar.cashflow.label_cash_and_bank_accounts'} />, text: <T id={'sidebar.cash_bank_accounts'} />,
href: '/cashflow-accounts', href: '/cashflow-accounts',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -452,42 +451,45 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
divider: true, divider: true,
children: [ children: [
{ {
text: <T id={'cash_flow.label.add_money_in'} />, text: <T id={'sidebar.add_money_in'} />,
href: '/cashflow-accounts', href: '/cashflow-accounts',
type: ISidebarMenuItemType.Dialog, type: ISidebarMenuItemType.Dialog,
dialogName: 'money-in', dialogName: DialogsName.MoneyInForm,
permission: { permission: {
subject: AbilitySubject.Cashflow, subject: AbilitySubject.Cashflow,
ability: CashflowAction.Create, ability: CashflowAction.Create,
}, },
}, },
{ {
text: <T id={'cash_flow.label.add_money_out'} />, text: <T id={'sidebar.add_money_out'} />,
href: '/cashflow-accounts', href: '/cashflow-accounts',
type: ISidebarMenuItemType.Dialog, type: ISidebarMenuItemType.Dialog,
dialogName: DialogsName.MoneyOutForm,
permission: { permission: {
subject: AbilitySubject.Cashflow, subject: AbilitySubject.Cashflow,
ability: CashflowAction.Create, ability: CashflowAction.Create,
}, },
}, },
{ {
text: <T id={'cash_flow.label.add_cash_account'} />, text: <T id={'sidebar.add_cash_account'} />,
href: '/cashflow-accounts', href: '/cashflow-accounts',
type: ISidebarMenuItemType.Dialog, type: ISidebarMenuItemType.Dialog,
dialogName: DialogsName.AccountForm,
permission: { permission: {
subject: AbilitySubject.Cashflow, subject: AbilitySubject.Cashflow,
ability: CashflowAction.Create, ability: CashflowAction.Create,
}, },
}, },
{ {
text: <T id={'cash_flow.label.add_bank_account'} />, text: <T id={'sidebar.add_bank_account'} />,
href: '/cashflow-accounts', href: '/cashflow-accounts',
type: ISidebarMenuItemType.Dialog, type: ISidebarMenuItemType.Dialog,
dialogName: DialogsName.AccountForm,
permission: { permission: {
subject: AbilitySubject.Cashflow, subject: AbilitySubject.Cashflow,
ability: CashflowAction.Create, ability: CashflowAction.Create,
@@ -501,16 +503,16 @@ export const SidebarMenu = [
// # Expenses // # Expenses
// --------------- // ---------------
{ {
text: <T id={'expenses'} />, text: <T id={'sidebar.expenses'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Expenses, overlayId: ISidebarMenuOverlayIds.Expenses,
children: [ children: [
{ {
text: <T id={'expenses'} />, text: <T id={'sidebar.expenses'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'expenses'} />, text: <T id={'sidebar.expenses'} />,
href: '/expenses', href: '/expenses',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -521,11 +523,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'New tasks'} />, text: <T id={'sidebar.new_tasks'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'new_expense'} />, text: <T id={'sidebar.new_expense'} />,
href: '/expenses/new', href: '/expenses/new',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -541,16 +543,16 @@ export const SidebarMenu = [
// # Projects Management // # Projects Management
// --------------------- // ---------------------
// { // {
// text: 'Projects', // text: <T id={'sidebar.projects'} />,
// type: ISidebarMenuItemType.Overlay, // type: ISidebarMenuItemType.Overlay,
// overlayId: ISidebarMenuOverlayIds.Projects, // overlayId: ISidebarMenuOverlayIds.Projects,
// children: [ // children: [
// { // {
// text: 'Projects', // text: <T id={'sidebar.projects'} />,
// type: ISidebarMenuItemType.Group, // type: ISidebarMenuItemType.Group,
// children: [ // children: [
// { // {
// text: 'Projects', // text: <T id={'sidebar.projects'} />,
// href: '/projects', // href: '/projects',
// type: ISidebarMenuItemType.Link, // type: ISidebarMenuItemType.Link,
// permission: { // permission: {
@@ -561,11 +563,11 @@ export const SidebarMenu = [
// ], // ],
// }, // },
// { // {
// text: <T id={'New tasks'} />, // text: <T id={'sidebar.new_tasks'} />,
// type: ISidebarMenuItemType.Group, // type: ISidebarMenuItemType.Group,
// children: [ // children: [
// { // {
// text: <T id={'projects.label.new_project'} />, // text: <T id={'sidebar.new_project'} />,
// type: ISidebarMenuItemType.Dialog, // type: ISidebarMenuItemType.Dialog,
// dialogName: 'project-form', // dialogName: 'project-form',
// permission: { // permission: {
@@ -574,18 +576,18 @@ export const SidebarMenu = [
// }, // },
// }, // },
// { // {
// text: <T id={'projects.label.new_time_entry'} />, // text: <T id={'sidebar.new_time_entry'} />,
// type: ISidebarMenuItemType.Dialog, // type: ISidebarMenuItemType.Dialog,
// dialogName: 'project-time-entry-form', // dialogName: 'project-time-entry-form',
// }, // },
// ], // ],
// }, // },
// { // {
// text: <T id={'Reports'} />, // text: <T id={'sidebar.reports'} />,
// type: ISidebarMenuItemType.Group, // type: ISidebarMenuItemType.Group,
// children: [ // children: [
// { // {
// text: <T id={'project_profitability_summary'} />, // text: <T id={'sidebar.project_profitability_summary'} />,
// href: '/financial-reports/project-profitability-summary', // href: '/financial-reports/project-profitability-summary',
// type: ISidebarMenuItemType.Link, // type: ISidebarMenuItemType.Link,
// }, // },
@@ -597,16 +599,16 @@ export const SidebarMenu = [
// # Reports // # Reports
// --------------- // ---------------
{ {
text: <T id={'Reports'} />, text: <T id={'sidebar.reports'} />,
type: ISidebarMenuItemType.Overlay, type: ISidebarMenuItemType.Overlay,
overlayId: ISidebarMenuOverlayIds.Reports, overlayId: ISidebarMenuOverlayIds.Reports,
children: [ children: [
{ {
text: <T id={'Reports'} />, text: <T id={'sidebar.reports'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'balance_sheet'} />, text: <T id={'sidebar.balance_sheet'} />,
href: '/financial-reports/balance-sheet', href: '/financial-reports/balance-sheet',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -615,7 +617,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'trial_balance_sheet'} />, text: <T id={'sidebar.trial_balance_sheet'} />,
href: '/financial-reports/trial-balance-sheet', href: '/financial-reports/trial-balance-sheet',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -624,7 +626,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'journal'} />, text: <T id={'sidebar.journal'} />,
href: '/financial-reports/journal-sheet', href: '/financial-reports/journal-sheet',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -633,7 +635,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'general_ledger'} />, text: <T id={'sidebar.general_ledger'} />,
href: '/financial-reports/general-ledger', href: '/financial-reports/general-ledger',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -642,7 +644,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'profit_loss_sheet'} />, text: <T id={'sidebar.profit_loss_sheet'} />,
href: '/financial-reports/profit-loss-sheet', href: '/financial-reports/profit-loss-sheet',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -651,7 +653,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'cash_flow_statement'} />, text: <T id={'sidebar.cash_flow_statement'} />,
href: '/financial-reports/cash-flow', href: '/financial-reports/cash-flow',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -660,7 +662,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'AR_Aging_Summary'} />, text: <T id={'sidebar.ar_aging_Summary'} />,
href: '/financial-reports/receivable-aging-summary', href: '/financial-reports/receivable-aging-summary',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -669,7 +671,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'AP_Aging_Summary'} />, text: <T id={'sidebar.ap_aging_summary'} />,
href: '/financial-reports/payable-aging-summary', href: '/financial-reports/payable-aging-summary',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -680,11 +682,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'Sales/Purchases'} />, text: <T id={'sidebar.sales_purchases'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'purchases_by_items'} />, text: <T id={'sidebar.purchases_by_items'} />,
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
href: '/financial-reports/purchases-by-items', href: '/financial-reports/purchases-by-items',
permission: { permission: {
@@ -693,7 +695,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'sales_by_items'} />, text: <T id={'sidebar.sales_by_items'} />,
href: '/financial-reports/sales-by-items', href: '/financial-reports/sales-by-items',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -702,7 +704,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'customers_transactions'} />, text: <T id={'sidebar.customers_transactions'} />,
href: '/financial-reports/transactions-by-customers', href: '/financial-reports/transactions-by-customers',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -711,7 +713,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'vendors_transactions'} />, text: <T id={'sidebar.vendors_transactions'} />,
href: '/financial-reports/transactions-by-vendors', href: '/financial-reports/transactions-by-vendors',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -720,7 +722,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'customers_balance_summary'} />, text: <T id={'sidebar.customers_balance_summary'} />,
href: '/financial-reports/customers-balance-summary', href: '/financial-reports/customers-balance-summary',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -729,7 +731,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'vendors_balance_summary'} />, text: <T id={'sidebar.vendors_balance_summary'} />,
href: '/financial-reports/vendors-balance-summary', href: '/financial-reports/vendors-balance-summary',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -740,11 +742,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'inventory'} />, text: <T id={'sidebar.inventory'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'inventory_item_details'} />, text: <T id={'sidebar.inventory_item_details'} />,
href: '/financial-reports/inventory-item-details', href: '/financial-reports/inventory-item-details',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -753,7 +755,7 @@ export const SidebarMenu = [
}, },
}, },
{ {
text: <T id={'inventory_valuation'} />, text: <T id={'sidebar.inventory_valuation'} />,
href: '/financial-reports/inventory-valuation', href: '/financial-reports/inventory-valuation',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {
@@ -766,11 +768,11 @@ export const SidebarMenu = [
], ],
}, },
{ {
text: <T id={'system'} />, text: <T id={'sidebar.system'} />,
type: ISidebarMenuItemType.Group, type: ISidebarMenuItemType.Group,
children: [ children: [
{ {
text: <T id={'preferences'} />, text: <T id={'sidebar.preferences'} />,
href: '/preferences', href: '/preferences',
type: ISidebarMenuItemType.Link, type: ISidebarMenuItemType.Link,
permission: { permission: {

View File

@@ -1,14 +1,8 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { import { InputGroup, FormGroup, Position } from '@blueprintjs/core';
InputGroup, import { FastField, ErrorMessage } from 'formik';
FormGroup,
Position,
ControlGroup,
} from '@blueprintjs/core';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import * as R from 'ramda';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
@@ -20,99 +14,15 @@ import {
} from '@/utils'; } from '@/utils';
import { import {
Hint, Hint,
FieldHint,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
InputPrependButton,
CurrencySelectList, CurrencySelectList,
FormattedMessage as T, FormattedMessage as T,
FInputGroup,
FFormGroup,
} from '@/components'; } from '@/components';
import { useMakeJournalFormContext } from './MakeJournalProvider'; import { useMakeJournalFormContext } from './MakeJournalProvider';
import { JournalExchangeRateInputField } from './components'; import { JournalExchangeRateInputField } from './components';
import { currenciesFieldShouldUpdate } from './utils'; import { currenciesFieldShouldUpdate } from './utils';
import { MakeJournalTransactionNoField } from './MakeJournalTransactionNoField';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
/**
* Journal number field of make journal form.
*/
const MakeJournalTransactionNoField = R.compose(
withDialogActions,
withSettings(({ manualJournalsSettings }) => ({
journalAutoIncrement: manualJournalsSettings?.autoIncrement,
})),
)(
({
// #withDialog
openDialog,
// #withSettings
journalAutoIncrement,
}) => {
const { setFieldValue, values } = useFormikContext();
const handleJournalNumberChange = () => {
openDialog('journal-number-form');
};
const handleJournalNoBlur = (event) => {
const newValue = event.target.value;
if (values.journal_number !== newValue && journalAutoIncrement) {
openDialog('journal-number-form', {
initialFormValues: {
onceManualNumber: newValue,
incrementMode: 'manual-transaction',
},
});
}
if (!journalAutoIncrement) {
setFieldValue('journal_number', newValue);
setFieldValue('journal_number_manually', newValue);
}
};
return (
<FFormGroup
name={'journal_number'}
label={<T id={'journal_no'} />}
labelInfo={
<>
<FieldRequiredHint />
<FieldHint />
</>
}
fill={true}
inline={true}
fastField={true}
>
<ControlGroup fill={true}>
<FInputGroup
name={'journal_number'}
fill={true}
asyncControl={true}
onBlur={handleJournalNoBlur}
fastField={true}
onChange={() => {}}
/>
<InputPrependButton
buttonProps={{
onClick: handleJournalNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_journal_number'} />,
position: Position.BOTTOM_LEFT,
}}
/>
</ControlGroup>
</FFormGroup>
);
},
);
/** /**
* Make journal entries header. * Make journal entries header.

View File

@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import intl from 'react-intl-universal';
import { FFormGroup, FEditableText, FormattedMessage as T } from '@/components'; import { FFormGroup, FEditableText, FormattedMessage as T } from '@/components';
export function MakeJournalFormFooterLeft() { export function MakeJournalFormFooterLeft() {
@@ -13,7 +14,7 @@ export function MakeJournalFormFooterLeft() {
> >
<FEditableText <FEditableText
name={'description'} name={'description'}
placeholder={<T id={'make_jorunal.decscrption.placeholder'} />} placeholder={intl.get('make_jorunal.decscrption.placeholder')}
/> />
</DescriptionFormGroup> </DescriptionFormGroup>
</React.Fragment> </React.Fragment>

View File

@@ -11,6 +11,7 @@ import {
FeatureCan, FeatureCan,
FormTopbar, FormTopbar,
DetailsBarSkeletonBase, DetailsBarSkeletonBase,
FormBranchSelectButton,
} from '@/components'; } from '@/components';
import { useMakeJournalFormContext } from './MakeJournalProvider'; import { useMakeJournalFormContext } from './MakeJournalProvider';
@@ -51,18 +52,9 @@ function MakeJournalFormSelectBranch() {
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={MakeJournalBranchSelectButton} input={FormBranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> fill={false}
);
}
function MakeJournalBranchSelectButton({ label }) {
return (
<Button
text={intl.get('make_journal.branch_button.label', { label })}
minimal={true}
small={true}
icon={<Icon icon={'branch-16'} iconSize={16} />}
/> />
); );
} }

View File

@@ -0,0 +1,97 @@
// @ts-nocheck
import React from 'react';
import { Position, ControlGroup } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import * as R from 'ramda';
import {
FieldHint,
FieldRequiredHint,
Icon,
InputPrependButton,
FormattedMessage as T,
FInputGroup,
FFormGroup,
} from '@/components';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
/**
* Journal number field of make journal form.
*/
export const MakeJournalTransactionNoField = R.compose(
withDialogActions,
withSettings(({ manualJournalsSettings }) => ({
journalAutoIncrement: manualJournalsSettings?.autoIncrement,
})),
)(
({
// #withDialog
openDialog,
// #withSettings
journalAutoIncrement,
}) => {
const { setFieldValue, values } = useFormikContext();
const handleJournalNumberChange = () => {
openDialog('journal-number-form');
};
const handleJournalNoBlur = (event) => {
const newValue = event.target.value;
if (values.journal_number !== newValue && journalAutoIncrement) {
openDialog('journal-number-form', {
initialFormValues: {
onceManualNumber: newValue,
incrementMode: 'manual-transaction',
},
});
}
if (!journalAutoIncrement) {
setFieldValue('journal_number', newValue);
setFieldValue('journal_number_manually', newValue);
}
};
return (
<FFormGroup
name={'journal_number'}
label={<T id={'journal_no'} />}
labelInfo={
<>
<FieldRequiredHint />
<FieldHint />
</>
}
fill={true}
inline={true}
fastField={true}
>
<ControlGroup fill={true}>
<FInputGroup
name={'journal_number'}
fill={true}
asyncControl={true}
onBlur={handleJournalNoBlur}
fastField={true}
onChange={() => {}}
/>
<InputPrependButton
buttonProps={{
onClick: handleJournalNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_journal_number'} />,
position: Position.BOTTOM_LEFT,
}}
/>
</ControlGroup>
</FFormGroup>
);
},
);
MakeJournalTransactionNoField.displayName = 'MakeJournalTransactionNoField';

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Menu, MenuItem, Position, Button } from '@blueprintjs/core'; import { Menu, MenuItem, Position, Button, Intent } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2'; import { Popover2 } from '@blueprintjs/popover2';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import * as R from 'ramda'; import * as R from 'ramda';
@@ -74,6 +74,7 @@ export const ActionsCellRenderer = ({
const exampleMenu = ( const exampleMenu = (
<Menu> <Menu>
<MenuItem <MenuItem
intent={Intent.DANGER}
onClick={handleClickRemoveRole} onClick={handleClickRemoveRole}
text={intl.get('make_journal.entries.remove_row')} text={intl.get('make_journal.entries.remove_row')}
/> />

View File

@@ -23,11 +23,11 @@ export const RegisterSchema = Yup.object().shape({
first_name: Yup.string().required().label(intl.get('first_name_')), first_name: Yup.string().required().label(intl.get('first_name_')),
last_name: Yup.string().required().label(intl.get('last_name_')), last_name: Yup.string().required().label(intl.get('last_name_')),
email: Yup.string().email().required().label(intl.get('email')), email: Yup.string().email().required().label(intl.get('email')),
password: Yup.string().min(4).required().label(intl.get('password')), password: Yup.string().min(6).required().label(intl.get('password')),
}); });
export const ResetPasswordSchema = Yup.object().shape({ export const ResetPasswordSchema = Yup.object().shape({
password: Yup.string().min(4).required().label(intl.get('password')), password: Yup.string().min(6).required().label(intl.get('password')),
confirm_password: Yup.string() confirm_password: Yup.string()
.oneOf([Yup.ref('password'), null]) .oneOf([Yup.ref('password'), null])
.required() .required()

View File

@@ -1,18 +1,22 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React, { useMemo } from 'react';
import { useFormikContext } from 'formik';
import OwnerContributionFormFields from './OwnerContribution/OwnerContributionFormFields'; import OwnerContributionFormFields from './OwnerContribution/OwnerContributionFormFields';
import OtherIncomeFormFields from './OtherIncome/OtherIncomeFormFields'; import OtherIncomeFormFields from './OtherIncome/OtherIncomeFormFields';
import TransferFromAccountFormFields from './TransferFromAccount/TransferFromAccountFormFields'; import TransferFromAccountFormFields from './TransferFromAccount/TransferFromAccountFormFields';
import { MoneyInFieldsProvider } from './MoneyInFieldsProvider';
/** /**
* * Money-in dialog content.
* @param param0 * Switches between fields based on the given transaction type.
* @returns * @returns {JSX.Element}
*/ */
export default function MoneyInContentFields({ accountType }) { export default function MoneyInContentFields() {
const handleTransactionType = () => { const { values } = useFormikContext();
switch (accountType) {
const transactionFields = useMemo(() => {
switch (values.transaction_type) {
case 'owner_contribution': case 'owner_contribution':
return <OwnerContributionFormFields />; return <OwnerContributionFormFields />;
@@ -24,6 +28,10 @@ export default function MoneyInContentFields({ accountType }) {
default: default:
break; break;
} }
}; }, [values.transaction_type]);
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
// Cannot continue if transaction type or account is not selected.
if (!values.transaction_type || !values.cashflow_account_id) return null;
return <MoneyInFieldsProvider>{transactionFields}</MoneyInFieldsProvider>;
} }

View File

@@ -1,6 +1,5 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { MoneyInDialogProvider } from './MoneyInDialogProvider'; import { MoneyInDialogProvider } from './MoneyInDialogProvider';
import MoneyInForm from './MoneyInForm'; import MoneyInForm from './MoneyInForm';

View File

@@ -1,11 +1,10 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React, { useState } from 'react';
import { DialogContent } from '@/components'; import { DialogContent } from '@/components';
import { Features } from '@/constants'; import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state'; import { useFeatureCan } from '@/hooks/state';
import { import {
useCreateCashflowTransaction, useCreateCashflowTransaction,
useAccount,
useAccounts, useAccounts,
useBranches, useBranches,
useCashflowAccounts, useCashflowAccounts,
@@ -18,21 +17,20 @@ const MoneyInDialogContent = React.createContext();
* Money in dialog provider. * Money in dialog provider.
*/ */
function MoneyInDialogProvider({ function MoneyInDialogProvider({
accountId, accountId: defaultAccountId,
accountType, accountType,
dialogName, dialogName,
...props ...props
}) { }) {
// Holds the selected account id of the dialog.
const [accountId, setAccountId] = useState<number | null>(defaultAccountId);
// Detarmines whether the feature is enabled.
const { featureCan } = useFeatureCan(); const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches); const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches accounts list. // Fetches accounts list.
const { isFetching: isAccountsLoading, data: accounts } = useAccounts(); const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Fetches the branches list. // Fetches the branches list.
const { const {
@@ -41,10 +39,11 @@ function MoneyInDialogProvider({
isSuccess: isBranchesSuccess, isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan }); } = useBranches({}, { enabled: isBranchFeatureCan });
// Fetch cash flow list . // Fetch cash flow list.
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } = const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
useCashflowAccounts({}, { keepPreviousData: true }); useCashflowAccounts({}, { keepPreviousData: true });
// Mutation create cashflow transaction.
const { mutateAsync: createCashflowTransactionMutate } = const { mutateAsync: createCashflowTransactionMutate } =
useCreateCashflowTransaction(); useCreateCashflowTransaction();
@@ -54,12 +53,15 @@ function MoneyInDialogProvider({
// Submit payload. // Submit payload.
const [submitPayload, setSubmitPayload] = React.useState({}); const [submitPayload, setSubmitPayload] = React.useState({});
// provider. // Provider data.
const provider = { const provider = {
accounts, accounts,
account,
branches, branches,
accountId, accountId,
defaultAccountId,
setAccountId,
accountType, accountType,
isAccountsLoading, isAccountsLoading,
isBranchesSuccess, isBranchesSuccess,
@@ -73,15 +75,14 @@ function MoneyInDialogProvider({
setSubmitPayload, setSubmitPayload,
}; };
const isLoading =
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading;
return ( return (
<DialogContent <DialogContent isLoading={isLoading}>
isLoading={
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} /> <MoneyInDialogContent.Provider value={provider} {...props} />
</DialogContent> </DialogContent>
); );

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import { ExchangeRateMutedField } from '@/components';
import { useForeignAccount } from './utils';
import { useFormikContext } from 'formik';
import { useMoneyInFieldsContext } from './MoneyInFieldsProvider';
export function MoneyInExchangeRateField() {
const { account } = useMoneyInFieldsContext();
const { values } = useFormikContext();
const isForeigAccount = useForeignAccount();
if (!isForeigAccount) return null;
return (
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
);
}

View File

@@ -0,0 +1,34 @@
// @ts-nocheck
import React from 'react';
import { DialogContent } from '@/components';
import { useAccount } from '@/hooks/query';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
const MoneyInFieldsContext = React.createContext();
/**
* Money in dialog provider.
*/
function MoneyInFieldsProvider({ ...props }) {
const { accountId } = useMoneyInDailogContext();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Provider data.
const provider = {
account,
};
const isLoading = isAccountLoading;
return (
<DialogContent isLoading={isLoading}>
<MoneyInFieldsContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useMoneyInFieldsContext = () => React.useContext(MoneyInFieldsContext);
export { MoneyInFieldsProvider, useMoneyInFieldsContext };

View File

@@ -53,7 +53,6 @@ function MoneyInForm({
accountId, accountId,
accountType, accountType,
createCashflowTransactionMutate, createCashflowTransactionMutate,
submitPayload,
} = useMoneyInDailogContext(); } = useMoneyInDailogContext();
// transaction number. // transaction number.
@@ -61,7 +60,6 @@ function MoneyInForm({
transactionNumberPrefix, transactionNumberPrefix,
transactionNextNumber, transactionNextNumber,
); );
// Initial form values. // Initial form values.
const initialValues = { const initialValues = {
...defaultInitialValues, ...defaultInitialValues,
@@ -95,15 +93,13 @@ function MoneyInForm({
}; };
return ( return (
<div> <Formik
<Formik validationSchema={CreateMoneyInFormSchema}
validationSchema={CreateMoneyInFormSchema} initialValues={initialValues}
initialValues={initialValues} onSubmit={handleFormSubmit}
onSubmit={handleFormSubmit} >
> <MoneyInFormContent />
<MoneyInFormContent /> </Formik>
</Formik>
</div>
); );
} }

View File

@@ -12,17 +12,13 @@ import { useMoneyInDailogContext } from './MoneyInDialogProvider';
* Money in form fields. * Money in form fields.
*/ */
function MoneyInFormFields() { function MoneyInFormFields() {
const { values } = useFormikContext();
// Money in dialog context. // Money in dialog context.
const { accountId } = useMoneyInDailogContext(); const { defaultAccountId } = useMoneyInDailogContext();
return ( return (
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<If condition={!accountId}> {!defaultAccountId && <TransactionTypeFields />}
<TransactionTypeFields /> <MoneyInContentFields />
</If>
<MoneyInContentFields accountType={values.transaction_type} />
</div> </div>
); );
} }

View File

@@ -1,10 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { import {
Classes, Classes,
FormGroup, FormGroup,
InputGroup,
TextArea, TextArea,
Position, Position,
ControlGroup, ControlGroup,
@@ -18,14 +17,15 @@ import {
FieldRequiredHint, FieldRequiredHint,
Col, Col,
Row, Row,
If,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
ExchangeRateMutedField, FInputGroup,
FFormGroup,
FTextArea,
FMoneyInputGroup,
} from '@/components'; } from '@/components';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants'; import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants';
import { import {
@@ -36,22 +36,18 @@ import {
} from '@/utils'; } from '@/utils';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider'; import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { import { useSetPrimaryBranchToForm, BranchRowDivider } from '../utils';
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import { MoneyInOutTransactionNoField } from '../../_components'; import { MoneyInOutTransactionNoField } from '../../_components';
import { useMoneyInFieldsContext } from '../MoneyInFieldsProvider';
import { MoneyInExchangeRateField } from '../MoneyInExchangeRateField';
/** /**
* Other income form fields. * Other income form fields.
*/ */
export default function OtherIncomeFormFields() { export default function OtherIncomeFormFields() {
// Money in dialog context. // Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext(); const { accounts, branches } = useMoneyInDailogContext();
const { values } = useFormikContext(); const { account } = useMoneyInFieldsContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -61,17 +57,14 @@ export default function OtherIncomeFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup name={'amount'} label={<T id={'branch'} />}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
@@ -106,53 +99,32 @@ export default function OtherIncomeFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Transaction number -----------*/} {/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}> {/*------------ Amount -----------*/}
{({ <Row>
form: { values, setFieldValue }, <Col xs={10}>
field: { value }, <FFormGroup
meta: { error, touched }, name={'amount'}
}) => (
<FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account.currency_code} /> <InputPrependText text={account.currency_code} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FFormGroup>
)} </Col>
</FastField> </Row>
{/*------------ Exchange rate -----------*/}
<MoneyInExchangeRateField />
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ other income account -----------*/} {/*------------ other income account -----------*/}
@@ -182,43 +154,24 @@ export default function OtherIncomeFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup label={<T id={'reference_no'} />} name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/}
<FastField name={'description'}> {/*------------ Description -----------*/}
{({ field, meta: { error, touched } }) => ( <FFormGroup name={'description'} label={<T id={'description'} />}>
<FormGroup <FTextArea
label={<T id={'description'} />} name={'description'}
className={'form-group--description'} growVertically={true}
intent={inputIntent({ error, touched })} large={true}
helperText={<ErrorMessage name={'description'} />} fill={true}
> />
<TextArea </FFormGroup>
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -1,32 +1,24 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { DateInput } from '@blueprintjs/datetime';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
InputPrependText, InputPrependText,
MoneyInputGroup,
FieldRequiredHint, FieldRequiredHint,
Col, Col,
Row, Row,
If,
ExchangeRateMutedField,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
FeatureCan, FeatureCan,
FFormGroup,
FMoneyInputGroup,
FTextArea,
FInputGroup,
} from '@/components'; } from '@/components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { ACCOUNT_TYPE, CLASSES, Features } from '@/constants'; import { ACCOUNT_TYPE, CLASSES, Features } from '@/constants';
import { import {
inputIntent, inputIntent,
@@ -37,10 +29,11 @@ import {
import { useMoneyInDailogContext } from '../MoneyInDialogProvider'; import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { import {
useSetPrimaryBranchToForm, useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider, BranchRowDivider,
} from '../../MoneyInDialog/utils'; } from '../../MoneyInDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components'; import { MoneyInOutTransactionNoField } from '../../_components';
import { useMoneyInFieldsContext } from '../MoneyInFieldsProvider';
import { MoneyInExchangeRateField } from '../MoneyInExchangeRateField';
/** /**
/** /**
@@ -48,13 +41,8 @@ import { MoneyInOutTransactionNoField } from '../../_components';
*/ */
export default function OwnerContributionFormFields() { export default function OwnerContributionFormFields() {
// Money in dialog context. // Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext(); const { accounts, branches } = useMoneyInDailogContext();
const { account } = useMoneyInFieldsContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -64,21 +52,19 @@ export default function OwnerContributionFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup name={'branch_id'} label={<T id={'branch'} />}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
</FeatureCan> </FeatureCan>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ Date -----------*/} {/*------------ Date -----------*/}
@@ -113,47 +99,26 @@ export default function OwnerContributionFormFields() {
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/}
<Field name={'amount'}> {/*------------ Amount -----------*/}
{({ <Row>
form: { values, setFieldValue }, <Col xs={10}>
field: { value }, <FFormGroup
meta: { error, touched }, name={'amount'}
}) => (
<FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account?.currency_code} /> <InputPrependText text={account?.currency_code || '--'} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FFormGroup>
)} </Col>
</Field> </Row>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/} {/*------------ Exchange rate -----------*/}
<ExchangeRateMutedField <MoneyInExchangeRateField />
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ equity account -----------*/} {/*------------ equity account -----------*/}
@@ -181,43 +146,24 @@ export default function OwnerContributionFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup name={'reference_no'} label={<T id={'reference_no'} />}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/}
<FastField name={'description'}> {/*------------ Description -----------*/}
{({ field, meta: { error, touched } }) => ( <FFormGroup name={'description'} label={<T id={'description'} />}>
<FormGroup <FTextArea
label={<T id={'description'} />} name={'description'}
className={'form-group--description'} growVertically={true}
intent={inputIntent({ error, touched })} large={true}
helperText={<ErrorMessage name={'description'} />} fill={true}
> />
<TextArea </FFormGroup>
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -10,6 +10,8 @@ import {
ListSelect, ListSelect,
Col, Col,
Row, Row,
FFormGroup,
FSelect,
} from '@/components'; } from '@/components';
import { inputIntent } from '@/utils'; import { inputIntent } from '@/utils';
import { CLASSES, getAddMoneyInOptions } from '@/constants'; import { CLASSES, getAddMoneyInOptions } from '@/constants';
@@ -21,7 +23,7 @@ import { useMoneyInDailogContext } from './MoneyInDialogProvider';
*/ */
export default function TransactionTypeFields() { export default function TransactionTypeFields() {
// Money in dialog context. // Money in dialog context.
const { cashflowAccounts } = useMoneyInDailogContext(); const { cashflowAccounts, setAccountId } = useMoneyInDailogContext();
// Retrieves the add money in button options. // Retrieves the add money in button options.
const addMoneyInOptions = useMemo(() => getAddMoneyInOptions(), []); const addMoneyInOptions = useMemo(() => getAddMoneyInOptions(), []);
@@ -29,6 +31,23 @@ export default function TransactionTypeFields() {
return ( return (
<div className="trasnaction-type-fileds"> <div className="trasnaction-type-fileds">
<Row> <Row>
<Col xs={5}>
{/*------------ Transaction type -----------*/}
<FFormGroup
name={'transaction_type'}
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
>
<FSelect
name={'transaction_type'}
items={addMoneyInOptions}
popoverProps={{ minimal: true }}
valueAccessor={'value'}
textAccessor={'name'}
/>
</FFormGroup>
</Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Current account -----------*/} {/*------------ Current account -----------*/}
<FastField name={'cashflow_account_id'}> <FastField name={'cashflow_account_id'}>
@@ -46,9 +65,10 @@ export default function TransactionTypeFields() {
> >
<AccountsSuggestField <AccountsSuggestField
accounts={cashflowAccounts} accounts={cashflowAccounts}
onAccountSelected={({ id }) => onAccountSelected={({ id }) => {
form.setFieldValue('cashflow_account_id', id) form.setFieldValue('cashflow_account_id', id);
} setAccountId(id);
}}
inputProps={{ inputProps={{
intent: inputIntent({ error, touched }), intent: inputIntent({ error, touched }),
}} }}
@@ -56,39 +76,6 @@ export default function TransactionTypeFields() {
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/*------------ Transaction type -----------*/}
</Col>
<Col xs={5}>
<Field name={'transaction_type'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="transaction_type" />}
intent={inputIntent({ error, touched })}
className={classNames(
CLASSES.FILL,
'form-group--transaction_type',
)}
>
<ListSelect
items={addMoneyInOptions}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</Field>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@@ -1,33 +1,27 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
InputPrependText, InputPrependText,
MoneyInputGroup,
FieldRequiredHint, FieldRequiredHint,
Col, Col,
Row, Row,
If,
ExchangeRateMutedField,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
FMoneyInputGroup,
FInputGroup,
FFormGroup,
FTextArea,
} from '@/components'; } from '@/components';
import { useAutofocus } from '@/hooks'; import { MoneyInOutTransactionNoField } from '../../_components';
import { MoneyInExchangeRateField } from '../MoneyInExchangeRateField';
import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants'; import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants';
import { import {
inputIntent, inputIntent,
momentFormatter, momentFormatter,
@@ -35,25 +29,19 @@ import {
handleDateChange, handleDateChange,
} from '@/utils'; } from '@/utils';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider'; import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { useMoneyInFieldsContext } from '../MoneyInFieldsProvider';
import { import {
useSetPrimaryBranchToForm, useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider, BranchRowDivider,
} from '../../MoneyInDialog/utils'; } from '../../MoneyInDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/** /**
* Transfer from account form fields. * Transfer from account form fields.
*/ */
export default function TransferFromAccountFormFields() { export default function TransferFromAccountFormFields() {
// Money in dialog context. // Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext(); const { accounts, branches } = useMoneyInDailogContext();
const { account } = useMoneyInFieldsContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
const { values } = useFormikContext();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -63,17 +51,14 @@ export default function TransferFromAccountFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup label={<T id={'branch'} />} name={'branch_id'}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
@@ -112,50 +97,27 @@ export default function TransferFromAccountFormFields() {
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/} {/*------------ Amount -----------*/}
<FastField name={'amount'}> <Row>
{({ <Col xs={10}>
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup <FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account.currency_code} /> <InputPrependText text={account.currency_code || '--'} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FormGroup>
)} </Col>
</FastField> </Row>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/} {/*------------ Exchange rate -----------*/}
<ExchangeRateMutedField <MoneyInExchangeRateField />
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ transfer from account -----------*/} {/*------------ Transfer from account -----------*/}
<FastField name={'credit_account_id'}> <FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => ( {({ form, field, meta: { error, touched } }) => (
<FormGroup <FormGroup
@@ -185,43 +147,24 @@ export default function TransferFromAccountFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup name={'reference_no'} label={<T id={'reference_no'} />}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/}
<FastField name={'description'}> {/*------------ Description -----------*/}
{({ field, meta: { error, touched } }) => ( <FormGroup name={'description'} label={<T id={'description'} />}>
<FormGroup <FTextArea
label={<T id={'description'} />} name={'description'}
className={'form-group--description'} growVertically={true}
intent={inputIntent({ error, touched })} large={true}
helperText={<ErrorMessage name={'description'} />} fill={true}
> />
<TextArea </FormGroup>
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -3,7 +3,6 @@ import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Dialog, DialogSuspense } from '@/components'; import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect'; import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils'; import { compose } from '@/utils';
const MoneyInDialogContent = React.lazy(() => import('./MoneyInDialogContent')); const MoneyInDialogContent = React.lazy(() => import('./MoneyInDialogContent'));

View File

@@ -6,6 +6,7 @@ import { transactionNumber } from '@/utils';
import { isEqual, isNull, first } from 'lodash'; import { isEqual, isNull, first } from 'lodash';
import { useMoneyInDailogContext } from './MoneyInDialogProvider'; import { useMoneyInDailogContext } from './MoneyInDialogProvider';
import { useMoneyInFieldsContext } from './MoneyInFieldsProvider';
export const useObserveTransactionNoSettings = (prefix, nextNumber) => { export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
@@ -33,7 +34,7 @@ export const useSetPrimaryBranchToForm = () => {
export const useForeignAccount = () => { export const useForeignAccount = () => {
const { values } = useFormikContext(); const { values } = useFormikContext();
const { account } = useMoneyInDailogContext(); const { account } = useMoneyInFieldsContext();
return ( return (
!isEqual(account.currency_code, values.currency_code) && !isEqual(account.currency_code, values.currency_code) &&

View File

@@ -1,13 +1,22 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React, { useMemo } from 'react';
import { useFormikContext } from 'formik';
import OtherExpnseFormFields from './OtherExpense/OtherExpnseFormFields'; import OtherExpnseFormFields from './OtherExpense/OtherExpnseFormFields';
import OwnerDrawingsFormFields from './OwnerDrawings/OwnerDrawingsFormFields'; import OwnerDrawingsFormFields from './OwnerDrawings/OwnerDrawingsFormFields';
import TransferToAccountFormFields from './TransferToAccount/TransferToAccountFormFields'; import TransferToAccountFormFields from './TransferToAccount/TransferToAccountFormFields';
import { MoneyOutFieldsProvider } from './MoneyOutFieldsProvider';
function MoneyOutContentFields({ accountType }) { /**
const handleTransactionType = () => { * Money out content fields.
switch (accountType) { * Switches between form fields based on the given transaction type.
* @returns {JSX.Element}
*/
function MoneyOutContentFields() {
const { values } = useFormikContext();
const transactionType = useMemo(() => {
switch (values.transaction_type) {
case 'OwnerDrawing': case 'OwnerDrawing':
return <OwnerDrawingsFormFields />; return <OwnerDrawingsFormFields />;
@@ -19,8 +28,12 @@ function MoneyOutContentFields({ accountType }) {
default: default:
break; break;
} }
}; }, [values.transaction_type]);
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
// Cannot continue if transaction type or account is not selected.
if (!values.transaction_type || !values.cashflow_account_id) return null;
return <MoneyOutFieldsProvider>{transactionType}</MoneyOutFieldsProvider>;
} }
export default MoneyOutContentFields; export default MoneyOutContentFields;

View File

@@ -1,11 +1,10 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React, { useState } from 'react';
import { DialogContent } from '@/components'; import { DialogContent } from '@/components';
import { Features } from '@/constants'; import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state'; import { useFeatureCan } from '@/hooks/state';
import { import {
useAccounts, useAccounts,
useAccount,
useBranches, useBranches,
useCreateCashflowTransaction, useCreateCashflowTransaction,
useCashflowAccounts, useCashflowAccounts,
@@ -17,7 +16,15 @@ const MoneyInDialogContent = React.createContext();
/** /**
* Money out dialog provider. * Money out dialog provider.
*/ */
function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) { function MoneyOutProvider({
accountId: defaultAccountId,
accountType,
dialogName,
...props
}) {
// Holds the selected account id of the dialog.
const [accountId, setAccountId] = useState<number | null>(defaultAccountId);
// Features guard. // Features guard.
const { featureCan } = useFeatureCan(); const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches); const isBranchFeatureCan = featureCan(Features.Branches);
@@ -25,11 +32,6 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
// Fetches accounts list. // Fetches accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts(); const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Fetches the branches list. // Fetches the branches list.
const { const {
data: branches, data: branches,
@@ -41,6 +43,7 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } = const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
useCashflowAccounts({}, { keepPreviousData: true }); useCashflowAccounts({}, { keepPreviousData: true });
// Mutation to create a new cashflow account.
const { mutateAsync: createCashflowTransactionMutate } = const { mutateAsync: createCashflowTransactionMutate } =
useCreateCashflowTransaction(); useCreateCashflowTransaction();
@@ -50,11 +53,13 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
// Submit payload. // Submit payload.
const [submitPayload, setSubmitPayload] = React.useState({}); const [submitPayload, setSubmitPayload] = React.useState({});
// provider. // Provider data.
const provider = { const provider = {
accounts,
account,
accountId, accountId,
setAccountId,
defaultAccountId,
accounts,
accountType, accountType,
branches, branches,
isAccountsLoading, isAccountsLoading,
@@ -69,15 +74,14 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
setSubmitPayload, setSubmitPayload,
}; };
const isLoading =
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading;
return ( return (
<DialogContent <DialogContent isLoading={isLoading}>
isLoading={
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} /> <MoneyInDialogContent.Provider value={provider} {...props} />
</DialogContent> </DialogContent>
); );

View File

@@ -0,0 +1,31 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { useForeignAccount } from './utils';
import { ExchangeRateMutedField } from '@/components';
import { useMoneyOutFieldsContext } from './MoneyOutFieldsProvider';
/**
* Money-out exchange rate field.
* @returns {JSX.Element}
*/
export function MoneyOutExchangeRateField() {
const { values } = useFormikContext();
const { account } = useMoneyOutFieldsContext();
const isForeigAccount = useForeignAccount();
// Cannot continue if the account is not foreign account.
if (!isForeigAccount) return null;
return (
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values?.currency_code}
toCurrency={account?.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
);
}

View File

@@ -0,0 +1,34 @@
// @ts-nocheck
import React from 'react';
import { DialogContent } from '@/components';
import { useAccount } from '@/hooks/query';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
const MoneyOutFieldsContext = React.createContext();
/**
* Money out fields dialog provider.
*/
function MoneyOutFieldsProvider({ ...props }) {
const { accountId } = useMoneyOutDialogContext();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Provider data.
const provider = {
account,
};
const isLoading = isAccountLoading;
return (
<DialogContent isLoading={isLoading}>
<MoneyOutFieldsContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useMoneyOutFieldsContext = () => React.useContext(MoneyOutFieldsContext);
export { MoneyOutFieldsProvider, useMoneyOutFieldsContext };

View File

@@ -24,18 +24,16 @@ function MoneyOutFloatingActions({
useMoneyOutDialogContext(); useMoneyOutDialogContext();
// handle submit as draft button click. // handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => { const handleSubmitDraftBtnClick = () => {
setSubmitPayload({ publish: false }); setSubmitPayload({ publish: false });
submitForm(); submitForm();
}; };
// Handle submit button click. // Handle submit button click.
const handleSubmittBtnClick = (event) => { const handleSubmittBtnClick = () => {
setSubmitPayload({ publish: true }); setSubmitPayload({ publish: true });
}; };
// Handle close button click. // Handle close button click.
const handleCloseBtnClick = (event) => { const handleCloseBtnClick = () => {
closeDialog(dialogName); closeDialog(dialogName);
}; };
@@ -49,7 +47,7 @@ function MoneyOutFloatingActions({
> >
<T id={'close'} /> <T id={'close'} />
</Button> </Button>
<Button <Button
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
disabled={isSubmitting} disabled={isSubmitting}

View File

@@ -92,15 +92,13 @@ function MoneyOutForm({
}); });
}; };
return ( return (
<div> <Formik
<Formik validationSchema={CreateMoneyOutSchema}
validationSchema={CreateMoneyOutSchema} initialValues={initialValues}
initialValues={initialValues} onSubmit={handleFormSubmit}
onSubmit={handleFormSubmit} >
> <MoneyOutFormContent />
<MoneyOutFormContent /> </Formik>
</Formik>
</div>
); );
} }

View File

@@ -17,6 +17,7 @@ export default function MoneyOutFormDialog() {
setFieldValue('transaction_number', incrementNumber || ''); setFieldValue('transaction_number', incrementNumber || '');
setFieldValue('transaction_number_manually', manually); setFieldValue('transaction_number_manually', manually);
}; };
return ( return (
<React.Fragment> <React.Fragment>
<TransactionNumberDialog <TransactionNumberDialog

View File

@@ -1,28 +1,18 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { useFormikContext } from 'formik';
import { Classes } from '@blueprintjs/core'; import { Classes } from '@blueprintjs/core';
import { If } from '@/components';
import MoneyOutContentFields from './MoneyOutContentFields'; import MoneyOutContentFields from './MoneyOutContentFields';
import TransactionTypeFields from './TransactionTypeFields'; import TransactionTypeFields from './TransactionTypeFields';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
/** /**
* Money out form fields. * Money out form fields.
*/ */
function MoneyOutFormFields() { function MoneyOutFormFields() {
// Money in dialog context.
const { accountId } = useMoneyOutDialogContext();
const { values } = useFormikContext();
return ( return (
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<If condition={!accountId}> <TransactionTypeFields />
<TransactionTypeFields /> <MoneyOutContentFields />
</If>
<MoneyOutContentFields accountType={values.transaction_type} />
</div> </div>
); );
} }

View File

@@ -1,33 +1,25 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
InputPrependText, InputPrependText,
MoneyInputGroup,
FieldRequiredHint, FieldRequiredHint,
Col, Col,
Row, Row,
If,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
ExchangeRateMutedField, FTextArea,
FFormGroup,
FInputGroup,
FMoneyInputGroup,
} from '@/components'; } from '@/components';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { Features, ACCOUNT_TYPE } from '@/constants'; import { Features, ACCOUNT_TYPE } from '@/constants';
import { import {
inputIntent, inputIntent,
momentFormatter, momentFormatter,
@@ -36,25 +28,18 @@ import {
} from '@/utils'; } from '@/utils';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider'; import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { import { useSetPrimaryBranchToForm, BranchRowDivider } from '../utils';
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import { MoneyInOutTransactionNoField } from '../../_components'; import { MoneyInOutTransactionNoField } from '../../_components';
import { MoneyOutExchangeRateField } from '../MoneyOutExchangeRateField';
import { useMoneyOutFieldsContext } from '../MoneyOutFieldsProvider';
/** /**
* Other expense form fields. * Other expense form fields.
*/ */
export default function OtherExpnseFormFields() { export default function OtherExpnseFormFields() {
// Money in dialog context. // Money in dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext(); const { accounts, branches } = useMoneyOutDialogContext();
const { account } = useMoneyOutFieldsContext();
const isForeigAccount = useForeignAccount();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -64,21 +49,19 @@ export default function OtherExpnseFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup name={'branch_id'} label={<T id={'branch'} />}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
</FeatureCan> </FeatureCan>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ Date -----------*/} {/*------------ Date -----------*/}
@@ -113,47 +96,27 @@ export default function OtherExpnseFormFields() {
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/} {/*------------ amount -----------*/}
<FastField name={'amount'}>
{({ <Row>
form: { values, setFieldValue }, <Col xs={10}>
field: { value }, <FFormGroup
meta: { error, touched }, name={'amount'}
}) => (
<FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account.currency_code} /> <InputPrependText text={account.currency_code} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FFormGroup>
)} </Col>
</FastField> </Row>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/} {/*------------ Exchange rate -----------*/}
<ExchangeRateMutedField <MoneyOutExchangeRateField />
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ other expense account -----------*/} {/*------------ other expense account -----------*/}
@@ -183,44 +146,24 @@ export default function OtherExpnseFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup name={'reference_no'} label={<T id={'reference_no'} />}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/} {/*------------ description -----------*/}
<FastField name={'description'}> <FFormGroup name={'description'} label={<T id={'description'} />}>
{({ field, meta: { error, touched } }) => ( <FTextArea
<FormGroup name={'description'}
label={<T id={'description'} />} growVertically={true}
className={'form-group--description'} large={true}
intent={inputIntent({ error, touched })} fill={true}
helperText={<ErrorMessage name={'description'} />} />
> </FFormGroup>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -1,31 +1,24 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
InputPrependText, InputPrependText,
MoneyInputGroup,
FieldRequiredHint, FieldRequiredHint,
If,
Col, Col,
Row, Row,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
ExchangeRateMutedField, FFormGroup,
FTextArea,
FInputGroup,
FMoneyInputGroup,
} from '@/components'; } from '@/components';
import { useAutofocus } from '@/hooks';
import { CLASSES, Features, ACCOUNT_TYPE } from '@/constants'; import { CLASSES, Features, ACCOUNT_TYPE } from '@/constants';
import { import {
inputIntent, inputIntent,
@@ -36,21 +29,19 @@ import {
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider'; import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { import {
useSetPrimaryBranchToForm, useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider, BranchRowDivider,
} from '../../MoneyOutDialog/utils'; } from '../../MoneyOutDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components'; import { MoneyInOutTransactionNoField } from '../../_components';
import { useMoneyOutFieldsContext } from '../MoneyOutFieldsProvider';
import { MoneyOutExchangeRateField } from '../MoneyOutExchangeRateField';
/** /**
* Owner drawings form fields. * Owner drawings form fields.
*/ */
export default function OwnerDrawingsFormFields() { export default function OwnerDrawingsFormFields() {
// Money out dialog context. // Money out dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext(); const { accounts, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext(); const { account } = useMoneyOutFieldsContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -60,21 +51,19 @@ export default function OwnerDrawingsFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup label={<T id={'branch'} />} name={'branch_id'}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
</FeatureCan> </FeatureCan>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ Date -----------*/} {/*------------ Date -----------*/}
@@ -109,48 +98,27 @@ export default function OwnerDrawingsFormFields() {
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/}
<Field name={'amount'}> {/*------------ Amount -----------*/}
{({
form: { values, setFieldValue }, <Row>
field: { value }, <Col xs={10}>
meta: { error, touched },
}) => (
<FormGroup <FormGroup
name={'amount'}
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account.currency_code} /> <InputPrependText text={account.currency_code} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FormGroup>
)} </Col>
</Field> </Row>
{/*------------ Exchange rate -----------*/}
<MoneyOutExchangeRateField />
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values?.currency_code}
toCurrency={account?.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ equitty account -----------*/} {/*------------ equitty account -----------*/}
@@ -177,44 +145,24 @@ export default function OwnerDrawingsFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup name={'reference_no'} label={<T id={'reference_no'} />}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/} {/*------------ description -----------*/}
<FastField name={'description'}> <FFormGroup name={'description'} label={<T id={'description'} />}>
{({ field, meta: { error, touched } }) => ( <FTextArea
<FormGroup name={'description'}
label={<T id={'description'} />} growVertically={true}
className={'form-group--description'} large={true}
intent={inputIntent({ error, touched })} fill={true}
helperText={<ErrorMessage name={'description'} />} />
> </FFormGroup>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -1,22 +1,21 @@
// @ts-nocheck // @ts-nocheck
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { FastField, Field, ErrorMessage } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { FormGroup } from '@blueprintjs/core'; import { FormGroup } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
FieldRequiredHint, FieldRequiredHint,
ListSelect,
Col, Col,
Row, Row,
FSelect,
FFormGroup,
} from '@/components'; } from '@/components';
import { inputIntent } from '@/utils'; import { inputIntent } from '@/utils';
import { CLASSES } from '@/constants/classes';
import { getAddMoneyOutOptions } from '@/constants/cashflowOptions'; import { getAddMoneyOutOptions } from '@/constants/cashflowOptions';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider'; import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
import { CLASSES } from '@/constants/classes';
/** /**
* Transaction type fields. * Transaction type fields.
@@ -27,9 +26,32 @@ function TransactionTypeFields() {
const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []); const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []);
// Money in dialog context.
const { defaultAccountId, setAccountId } = useMoneyOutDialogContext();
// Cannot continue if the default account id is defined.
if (defaultAccountId) return null;
return ( return (
<div className="trasnaction-type-fileds"> <div className="trasnaction-type-fileds">
<Row> <Row>
{/*------------ Transaction type -----------*/}
<Col xs={5}>
<FFormGroup
name={'transaction_type'}
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
>
<FSelect
name={'transaction_type'}
items={addMoneyOutOptions}
popoverProps={{ minimal: true }}
valueAccessor={'value'}
textAccessor={'name'}
/>
</FFormGroup>
</Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Current account -----------*/} {/*------------ Current account -----------*/}
<FastField name={'cashflow_account_id'}> <FastField name={'cashflow_account_id'}>
@@ -47,9 +69,10 @@ function TransactionTypeFields() {
> >
<AccountsSuggestField <AccountsSuggestField
accounts={cashflowAccounts} accounts={cashflowAccounts}
onAccountSelected={({ id }) => onAccountSelected={({ id }) => {
form.setFieldValue('cashflow_account_id', id) form.setFieldValue('cashflow_account_id', id);
} setAccountId(id);
}}
inputProps={{ inputProps={{
intent: inputIntent({ error, touched }), intent: inputIntent({ error, touched }),
}} }}
@@ -57,39 +80,6 @@ function TransactionTypeFields() {
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/*------------ Transaction type -----------*/}
</Col>
<Col xs={5}>
<Field name={'transaction_type'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="transaction_type" />}
intent={inputIntent({ error, touched })}
className={classNames(
CLASSES.FILL,
'form-group--transaction_type',
)}
>
<ListSelect
items={addMoneyOutOptions}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</Field>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@@ -1,65 +1,46 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
FormattedMessage as T, FormattedMessage as T,
AccountsSuggestField, AccountsSuggestField,
InputPrependText, InputPrependText,
MoneyInputGroup,
FieldRequiredHint, FieldRequiredHint,
Icon,
Col, Col,
Row, Row,
If,
InputPrependButton,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
BranchSelectButton, BranchSelectButton,
ExchangeRateMutedField, FFormGroup,
FTextArea,
FMoneyInputGroup,
FInputGroup,
} from '@/components'; } from '@/components';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { ACCOUNT_TYPE } from '@/constants/accountTypes'; import { ACCOUNT_TYPE } from '@/constants/accountTypes';
import { import {
inputIntent, inputIntent,
momentFormatter, momentFormatter,
tansformDateValue, tansformDateValue,
handleDateChange, handleDateChange,
compose,
} from '@/utils'; } from '@/utils';
import { Features } from '@/constants'; import { Features } from '@/constants';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider'; import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { import { useSetPrimaryBranchToForm, BranchRowDivider } from '../utils';
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { MoneyInOutTransactionNoField } from '../../_components'; import { MoneyInOutTransactionNoField } from '../../_components';
import { MoneyOutExchangeRateField } from '../MoneyOutExchangeRateField';
import { useMoneyOutFieldsContext } from '../MoneyOutFieldsProvider';
/** /**
* Transfer to account form fields. * Transfer to account form fields.
*/ */
export default function TransferToAccountFormFields() { export default function TransferToAccountFormFields() {
// Money in dialog context. // Money in dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext(); const { accounts, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext(); const { account } = useMoneyOutFieldsContext();
const isForeigAccount = useForeignAccount();
const accountRef = useAutofocus();
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
@@ -69,21 +50,19 @@ export default function TransferToAccountFormFields() {
<FeatureCan feature={Features.Branches}> <FeatureCan feature={Features.Branches}>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<FormGroup <FFormGroup label={<T id={'branch'} />} name={'branch_id'}>
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton} input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
</Col> </Col>
</Row> </Row>
<BranchRowDivider /> <BranchRowDivider />
</FeatureCan> </FeatureCan>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ Date -----------*/} {/*------------ Date -----------*/}
@@ -113,52 +92,32 @@ export default function TransferToAccountFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Transaction number -----------*/} {/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField /> <MoneyInOutTransactionNoField />
</Col> </Col>
</Row> </Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}> {/*------------ Amount -----------*/}
{({ <Row>
form: { values, setFieldValue }, <Col xs={10}>
field: { value }, <FFormGroup
meta: { error, touched }, name={'amount'}
}) => (
<FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={account.currency_code} /> <InputPrependText text={account.currency_code} />
<FMoneyInputGroup name={'amount'} minimal={true} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={accountRef}
intent={inputIntent({ error, touched })}
/>
</ControlGroup> </ControlGroup>
</FormGroup> </FFormGroup>
)} </Col>
</FastField> </Row>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/} {/*------------ Exchange rate -----------*/}
<ExchangeRateMutedField <MoneyOutExchangeRateField />
name={'exchange_rate'}
fromCurrency={values?.currency_code}
toCurrency={account?.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row> <Row>
<Col xs={5}> <Col xs={5}>
{/*------------ transfer from account -----------*/} {/*------------ transfer from account -----------*/}
@@ -191,43 +150,24 @@ export default function TransferToAccountFormFields() {
)} )}
</FastField> </FastField>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
{/*------------ Reference -----------*/} {/*------------ Reference -----------*/}
<FastField name={'reference_no'}> <FFormGroup name={'reference_no'} label={<T id={'reference_no'} />}>
{({ form, field, meta: { error, touched } }) => ( <FInputGroup name={'reference_no'} />
<FormGroup </FFormGroup>
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col> </Col>
</Row> </Row>
{/*------------ description -----------*/}
<FastField name={'description'}> {/*------------ Description -----------*/}
{({ field, meta: { error, touched } }) => ( <FFormGroup name={'description'} label={<T id={'description'} />}>
<FormGroup <FTextArea
label={<T id={'description'} />} name={'description'}
className={'form-group--description'} growVertically={true}
intent={inputIntent({ error, touched })} large={true}
helperText={<ErrorMessage name={'description'} />} fill={true}
> />
<TextArea </FFormGroup>
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -6,6 +6,7 @@ import { transactionNumber } from '@/utils';
import { first, isEqual, isNull } from 'lodash'; import { first, isEqual, isNull } from 'lodash';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider'; import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
import { useMoneyOutFieldsContext } from './MoneyOutFieldsProvider';
export const useObserveTransactionNoSettings = (prefix, nextNumber) => { export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
@@ -33,7 +34,7 @@ export const useSetPrimaryBranchToForm = () => {
export const useForeignAccount = () => { export const useForeignAccount = () => {
const { values } = useFormikContext(); const { values } = useFormikContext();
const { account } = useMoneyOutDialogContext(); const { account } = useMoneyOutFieldsContext();
return ( return (
!isEqual(account.currency_code, values.currency_code) && !isEqual(account.currency_code, values.currency_code) &&

View File

@@ -38,11 +38,11 @@ export const MoneyInOutSyncIncrementSettingsToForm = R.compose(
// Do not update if the invoice auto-increment is disabled. // Do not update if the invoice auto-increment is disabled.
if (!transactionAutoIncrement) return null; if (!transactionAutoIncrement) return null;
const transactionNumber = transactionNumber( const newTransactionNumber = transactionNumber(
transactionNumberPrefix, transactionNumberPrefix,
transactionNextNumber, transactionNextNumber,
); );
setFieldValue('transaction_number', transactionNumber); setFieldValue('transaction_number', newTransactionNumber);
}, [setFieldValue, transactionNumberPrefix, transactionNextNumber]); }, [setFieldValue, transactionNumberPrefix, transactionNextNumber]);
return null; return null;

View File

@@ -10,7 +10,6 @@ import {
InputPrependText, InputPrependText,
CurrencySelectList, CurrencySelectList,
BranchSelect, BranchSelect,
BranchSelectButton,
FeatureCan, FeatureCan,
Row, Row,
Col, Col,
@@ -78,12 +77,10 @@ export default function CustomerFinancialPanel() {
label={<T id={'customer.label.opening_branch'} />} label={<T id={'customer.label.opening_branch'} />}
name={'opening_balance_branch_id'} name={'opening_balance_branch_id'}
inline={true} inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
> >
<BranchSelect <BranchSelect
name={'opening_balance_branch_id'} name={'opening_balance_branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FFormGroup> </FFormGroup>

View File

@@ -23,7 +23,6 @@ import {
BranchSelect, BranchSelect,
WarehouseSelect, WarehouseSelect,
BranchSelectButton, BranchSelectButton,
WarehouseSelectButton,
AccountsSuggestField, AccountsSuggestField,
} from '@/components'; } from '@/components';
import { import {
@@ -34,16 +33,17 @@ import {
toSafeNumber, toSafeNumber,
} from '@/utils'; } from '@/utils';
import { Features, CLASSES } from '@/constants'; import { Features, CLASSES } from '@/constants';
import adjustmentType from '@/constants/adjustmentType';
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider'; import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
import { useFeatureCan } from '@/hooks/state';
import InventoryAdjustmentQuantityFields from './InventoryAdjustmentQuantityFields';
import { import {
diffQuantity, diffQuantity,
useSetPrimaryBranchToForm, useSetPrimaryBranchToForm,
useSetPrimaryWarehouseToForm, useSetPrimaryWarehouseToForm,
useGetAdjustmentTypeOptions,
} from './utils'; } from './utils';
import { useFeatureCan } from '@/hooks/state';
import InventoryAdjustmentQuantityFields from './InventoryAdjustmentQuantityFields';
/** /**
* Inventory adjustment form dialogs fields. * Inventory adjustment form dialogs fields.
@@ -52,6 +52,9 @@ export default function InventoryAdjustmentFormDialogFields() {
// Features guard. // Features guard.
const { featureCan } = useFeatureCan(); const { featureCan } = useFeatureCan();
// Retrieves memorized adjustment types options.
const adjustmentTypes = useGetAdjustmentTypeOptions();
const dateFieldRef = useAutofocus(); const dateFieldRef = useAutofocus();
// Inventory adjustment dialog context. // Inventory adjustment dialog context.
@@ -90,7 +93,6 @@ export default function InventoryAdjustmentFormDialogFields() {
<WarehouseSelect <WarehouseSelect
name={'warehouse_id'} name={'warehouse_id'}
warehouses={warehouses} warehouses={warehouses}
input={WarehouseSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>
@@ -149,7 +151,7 @@ export default function InventoryAdjustmentFormDialogFields() {
className={classNames(CLASSES.FILL, 'form-group--type')} className={classNames(CLASSES.FILL, 'form-group--type')}
> >
<ListSelect <ListSelect
items={adjustmentType} items={adjustmentTypes}
onItemSelect={(type) => { onItemSelect={(type) => {
const result = diffQuantity( const result = diffQuantity(
toSafeNumber(values.quantity), toSafeNumber(values.quantity),

View File

@@ -1,5 +1,6 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider'; import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
import { first } from 'lodash'; import { first } from 'lodash';
@@ -48,3 +49,12 @@ export const useSetPrimaryBranchToForm = () => {
} }
}, [isBranchesSuccess, setFieldValue, branches]); }, [isBranchesSuccess, setFieldValue, branches]);
}; };
export const getAdjustmentTypeOptions = () => [
{ name: intl.get('decrement'), value: 'decrement' },
{ name: intl.get('increment'), value: 'increment' },
];
export const useGetAdjustmentTypeOptions = () => {
return useMemo(() => getAdjustmentTypeOptions(), []);
};

View File

@@ -7,6 +7,7 @@ import { Intent } from '@blueprintjs/core';
import { AppToaster } from '@/components'; import { AppToaster } from '@/components';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
import { PAYMENT_MADE_ERRORS } from '@/containers/Purchases/PaymentMades/constants';
// Default initial values of payment made. // Default initial values of payment made.
export const defaultPaymentMade = { export const defaultPaymentMade = {
@@ -24,16 +25,16 @@ export const defaultPaymentMade = {
export const transformErrors = (errors, { setFieldError }) => { export const transformErrors = (errors, { setFieldError }) => {
const getError = (errorType) => errors.find((e) => e.type === errorType); const getError = (errorType) => errors.find((e) => e.type === errorType);
if (getError('PAYMENT.NUMBER.NOT.UNIQUE')) { if (getError(PAYMENT_MADE_ERRORS.PAYMENT_NUMBER_NOT_UNIQUE)) {
setFieldError('payment_number', intl.get('payment_number_is_not_unique')); setFieldError('payment_number', intl.get('payment_number_is_not_unique'));
} }
if (getError('INVALID_BILL_PAYMENT_AMOUNT')) { if (getError(PAYMENT_MADE_ERRORS.INVALID_BILL_PAYMENT_AMOUNT)) {
setFieldError( setFieldError(
'payment_amount', 'payment_amount',
intl.get('the_payment_amount_bigger_than_invoice_due_amount'), intl.get('the_payment_amount_bigger_than_invoice_due_amount'),
); );
} }
if (getError('WITHDRAWAL_ACCOUNT_CURRENCY_INVALID')) { if (getError(PAYMENT_MADE_ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID)) {
AppToaster.show({ AppToaster.show({
message: intl.get( message: intl.get(
'payment_made.error.withdrawal_account_currency_invalid', 'payment_made.error.withdrawal_account_currency_invalid',

View File

@@ -1,12 +1,13 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { Card } from '@/components'; import { Card, CommercialDocBox } from '@/components';
import CashflowTransactionDrawerActionBar from './CashflowTransactionDrawerActionBar'; import CashflowTransactionDrawerActionBar from './CashflowTransactionDrawerActionBar';
import CashflowTransactionDrawerHeader from './CashflowTransactionDrawerHeader'; import CashflowTransactionDrawerHeader from './CashflowTransactionDrawerHeader';
import CashflowTransactionDrawerTable from './CashflowTransactionDrawerTable'; import CashflowTransactionDrawerTable from './CashflowTransactionDrawerTable';
import CashflowTransactionDrawerFooter from './CashflowTransactionDrawerFooter'; import CashflowTransactionDrawerTableFooter from './CashflowTransactionDrawerTableFooter';
import { CashflowTransactionDrawerFooter } from './CashflowTransactionDrawerFooter';
/** /**
* Cashflow transaction view details. * Cashflow transaction view details.
*/ */
@@ -16,11 +17,12 @@ export default function CashflowTransactionDrawerDetails() {
<CashflowTransactionDrawerActionBar /> <CashflowTransactionDrawerActionBar />
<div className="cashflow-drawer__content"> <div className="cashflow-drawer__content">
<Card> <CommercialDocBox>
<CashflowTransactionDrawerHeader /> <CashflowTransactionDrawerHeader />
<CashflowTransactionDrawerTable /> <CashflowTransactionDrawerTable />
<CashflowTransactionDrawerTableFooter />
<CashflowTransactionDrawerFooter /> <CashflowTransactionDrawerFooter />
</Card> </CommercialDocBox>
</div> </div>
</div> </div>
); );

View File

@@ -1,35 +1,18 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { CommercialDocFooter, DetailsMenu, DetailItem, T } from '@/components';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider'; import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
import { T, FormatNumber } from '@/components';
export default function CashflowTransactionDrawerFooter() { export function CashflowTransactionDrawerFooter() {
const { const { cashflowTransaction } = useCashflowTransactionDrawerContext();
cashflowTransaction: { formatted_amount },
} = useCashflowTransactionDrawerContext();
return ( return (
<div className="cashflow-drawer__content-footer"> <CommercialDocFooter>
<div class="total-lines"> <DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<div class="total-lines__line total-lines__line--subtotal"> <DetailItem label={<T id={'cash_flow.drawer.label.statement'} />}>
<div class="title"> {cashflowTransaction.description}
<T id={'manual_journal.details.subtotal'} /> </DetailItem>
</div> </DetailsMenu>
<div class="debit"> </CommercialDocFooter>
<FormatNumber value={formatted_amount} />
</div>
<div class="credit">
<FormatNumber value={formatted_amount} />
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">
<T id={'manual_journal.details.total'} />
</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>
</div>
</div>
); );
} }

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
import React from 'react';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
import { T, FormatNumber } from '@/components';
export default function CashflowTransactionDrawerTableFooter() {
const {
cashflowTransaction: { formatted_amount },
} = useCashflowTransactionDrawerContext();
return (
<div className="cashflow-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">
<T id={'manual_journal.details.subtotal'} />
</div>
<div class="debit">
<FormatNumber value={formatted_amount} />
</div>
<div class="credit">
<FormatNumber value={formatted_amount} />
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">
<T id={'manual_journal.details.total'} />
</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>
</div>
</div>
);
}

View File

@@ -1,6 +1,5 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { import {
CommercialDocFooter, CommercialDocFooter,
T, T,

View File

@@ -21,6 +21,7 @@ import {
FormattedMessage as T, FormattedMessage as T,
Can, Can,
} from '@/components'; } from '@/components';
import { ItemDetailActionsMoreBtn } from './ItemDetailActionsMoreBtn';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { DRAWERS } from '@/constants/drawers'; import { DRAWERS } from '@/constants/drawers';
@@ -72,6 +73,7 @@ function ItemDetailActionsBar({
onClick={handleDeleteItem} onClick={handleDeleteItem}
/> />
</Can> </Can>
<ItemDetailActionsMoreBtn />
</NavbarGroup> </NavbarGroup>
</DashboardActionsBar> </DashboardActionsBar>
); );

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { Can, Icon, T } from '@/components';
import {
Button,
Menu,
MenuItem,
Popover,
PopoverInteractionKind,
Position,
} from '@blueprintjs/core';
import {
AbilitySubject,
InventoryAdjustmentAction,
} from '@/constants/abilityOption';
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
/**
* Invoice details more actions menu.
* @returns {React.JSX}
*/
export const ItemDetailActionsMoreBtn = R.compose(withDialogActions)(
({
//#withDialogActions,
openDialog,
}) => {
const { itemId, item } = useItemDetailDrawerContext();
// Cannot continue if the item type is not inventory.
if (item.type !== 'inventory') return null;
const handleInventoryAdjustment = () => {
openDialog('inventory-adjustment', { itemId });
};
return (
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<Can
I={InventoryAdjustmentAction.Edit}
a={AbilitySubject.InventoryAdjustment}
>
<MenuItem
text={<T id={'item.view_drawer.make_adjustment'} />}
onClick={handleInventoryAdjustment}
/>
</Can>
</Menu>
}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
},
);

View File

@@ -4,9 +4,8 @@ import {
DrawerHeaderContent, DrawerHeaderContent,
DrawerBody, DrawerBody,
FormattedMessage as T, FormattedMessage as T,
Drawer,
} from '@/components'; } from '@/components';
import { DRAWERS } from '@/constants/drawers';
import QuickCreateItemDrawerForm from './QuickCreateItemDrawerForm'; import QuickCreateItemDrawerForm from './QuickCreateItemDrawerForm';
/** /**
@@ -16,7 +15,7 @@ export default function QuickCreateItemDrawerContent({ itemName }) {
return ( return (
<React.Fragment> <React.Fragment>
<DrawerHeaderContent <DrawerHeaderContent
name={DRAWER.QUICK_CREATE_ITEM} name={DRAWERS.QUICK_CREATE_ITEM}
title={<T id={'create_a_new_item'} />} title={<T id={'create_a_new_item'} />}
/> />
<DrawerBody> <DrawerBody>

View File

@@ -17,16 +17,6 @@ import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { DRAWERS } from '@/constants/drawers'; import { DRAWERS } from '@/constants/drawers';
/**
* Drawer item form loading.
* @returns {JSX}
*/
function DrawerItemFormLoading({ children }) {
const { isFormLoading } = useItemFormContext();
return <DrawerLoading loading={isFormLoading}>{children}</DrawerLoading>;
}
/** /**
* Quick create/edit item drawer form. * Quick create/edit item drawer form.
*/ */
@@ -72,6 +62,16 @@ function QuickCreateItemDrawerForm({
); );
} }
/**
* Drawer item form loading.
* @returns {JSX}
*/
function DrawerItemFormLoading({ children }) {
const { isFormLoading } = useItemFormContext();
return <DrawerLoading loading={isFormLoading}>{children}</DrawerLoading>;
}
export default R.compose( export default R.compose(
withDrawerActions, withDrawerActions,
withDashboardActions, withDashboardActions,
@@ -79,10 +79,15 @@ export default R.compose(
const ItemFormCard = styled(Card)` const ItemFormCard = styled(Card)`
margin: 15px; margin: 15px;
padding: 25px;
margin-bottom: calc(15px + 65px); margin-bottom: calc(15px + 65px);
.page-form__floating-actions { .page-form {
margin-left: -36px; padding: 0;
margin-right: -36px;
&__floating-actions {
margin-left: -41px;
margin-right: -41px;
}
} }
`; `;

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { MenuItem, Menu, Button, Position } from '@blueprintjs/core'; import { MenuItem, Menu, Button, Position, Intent } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2'; import { Popover2 } from '@blueprintjs/popover2';
import { Align, CellType, Features } from '@/constants'; import { Align, CellType, Features } from '@/constants';
@@ -47,6 +47,7 @@ export function ActionsCellRenderer({
const exampleMenu = ( const exampleMenu = (
<Menu> <Menu>
<MenuItem <MenuItem
intent={Intent.DANGER}
onClick={onRemoveRole} onClick={onRemoveRole}
text={<T id={'item_entries.remove_row'} />} text={<T id={'item_entries.remove_row'} />}
/> />

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core'; import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components'; import { CustomersSelect, FormattedMessage as T } from '@/components';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
@@ -121,33 +121,39 @@ export default function ExpenseFormHeader() {
)} )}
</FastField> </FastField>
<FastField {/* ----------- Customer ----------- */}
name={'customer_id'} <ExpenseFormCustomerSelect />
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer'} />}
className={classNames('form-group--select-list', Classes.FILL)}
labelInfo={<Hint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'assign_to_customer'} />}
inline={true}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
}}
allowCreate={true}
popoverFill={true}
/>
</FormGroup>
)}
</FastField>
</div> </div>
); );
} }
/**
* Customer select field of expense form.
* @returns {React.ReactNode}
*/
function ExpenseFormCustomerSelect() {
const { customers } = useExpenseFormContext();
return (
<FormGroup
label={<T id={'customer'} />}
labelInfo={<Hint />}
inline={true}
name={'customer_id'}
fastField={true}
shouldUpdateDeps={{ items: customers }}
shouldUpdate={customersFieldShouldUpdate}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
allowCreate={true}
popoverFill={true}
fastField={true}
shouldUpdateDeps={{ items: customers }}
shouldUpdate={customersFieldShouldUpdate}
/>
</FormGroup>
);
}

View File

@@ -1,16 +1,15 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import { Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
import { useSetPrimaryBranchToForm } from './utils'; import { useSetPrimaryBranchToForm } from './utils';
import { useFeatureCan } from '@/hooks/state'; import { useFeatureCan } from '@/hooks/state';
import { Features } from '@/constants'; import { Features } from '@/constants';
import { import {
Icon,
BranchSelect, BranchSelect,
FeatureCan, FeatureCan,
FormTopbar, FormTopbar,
DetailsBarSkeletonBase, DetailsBarSkeletonBase,
FormBranchSelectButton,
} from '@/components'; } from '@/components';
import { useExpenseFormContext } from './ExpenseFormPageProvider'; import { useExpenseFormContext } from './ExpenseFormPageProvider';
@@ -51,19 +50,9 @@ function ExpenseFormSelectBranch() {
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={ExpenseBranchSelectButton} input={FormBranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> fill={false}
);
}
function ExpenseBranchSelectButton({ label }) {
return (
<Button
text={intl.get('expense.branch_button.label', { label })}
minimal={true}
small={true}
icon={<Icon icon={'branch-16'} iconSize={16} />}
/> />
); );
} }

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Button, Menu, MenuItem } from '@blueprintjs/core'; import { Button, Intent, Menu, MenuItem } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2'; import { Popover2 } from '@blueprintjs/popover2';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
@@ -51,6 +51,7 @@ const ActionsCellRenderer = ({
const exampleMenu = ( const exampleMenu = (
<Menu> <Menu>
<MenuItem <MenuItem
intent={Intent.DANGER}
onClick={handleClickRemoveRole} onClick={handleClickRemoveRole}
text={intl.get('expense.entries.remove_row')} text={intl.get('expense.entries.remove_row')}
/> />

View File

@@ -104,7 +104,7 @@ export const transformToEditForm = (
*/ */
export const customersFieldShouldUpdate = (newProps, oldProps) => { export const customersFieldShouldUpdate = (newProps, oldProps) => {
return ( return (
newProps.customers !== oldProps.customers || newProps.shouldUpdateDeps.items !== oldProps.shouldUpdateDeps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };

View File

@@ -21,19 +21,21 @@ export function BalanceSheetAlerts() {
refetchBalanceSheet(); refetchBalanceSheet();
}; };
// Can't display any error if the report is loading. // Can't display any error if the report is loading.
if (isLoading) return null; if (isLoading) {
return null;
}
// Can't continue if the cost compute job is not running.
if (!balanceSheet.meta.is_cost_compute_running) {
return null;
}
return ( return (
<If condition={balanceSheet.meta.is_cost_compute_running}> <FinancialComputeAlert>
<FinancialComputeAlert> <Icon icon="info-block" iconSize={12} />{' '}
<Icon icon="info-block" iconSize={12} />{' '} <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'report.compute_running.refresh'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> </Button>
<T id={'report.compute_running.refresh'} /> </FinancialComputeAlert>
</Button>
</FinancialComputeAlert>
</If>
); );
} }

View File

@@ -7,6 +7,7 @@ import FinancialLoadingBar from '../FinancialLoadingBar';
import { dynamicColumns } from './dynamicColumns'; import { dynamicColumns } from './dynamicColumns';
import { useCashFlowStatementContext } from './CashFlowStatementProvider'; import { useCashFlowStatementContext } from './CashFlowStatementProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
/** /**
* Retrieve cash flow statement columns. * Retrieve cash flow statement columns.
@@ -27,6 +28,7 @@ export const useCashFlowStatementColumns = () => {
*/ */
export function CashFlowStatementLoadingBar() { export function CashFlowStatementLoadingBar() {
const { isCashFlowFetching } = useCashFlowStatementContext(); const { isCashFlowFetching } = useCashFlowStatementContext();
return ( return (
<If condition={isCashFlowFetching}> <If condition={isCashFlowFetching}>
<FinancialLoadingBar /> <FinancialLoadingBar />
@@ -45,20 +47,21 @@ export function CashFlowStatementAlerts() {
const handleRecalcReport = () => { const handleRecalcReport = () => {
refetchCashFlow(); refetchCashFlow();
}; };
// Can't display any error if the report is loading // Can't display any error if the report is loading
if (isCashFlowLoading) { if (isCashFlowLoading) {
return null; return null;
} }
// Can't continue if the cost compute is not running.
if (!cashFlowStatement.meta.is_cost_compute_running) {
return null;
}
return ( return (
<If condition={cashFlowStatement.meta.is_cost_compute_running}> <FinancialComputeAlert>
<div className="alert-compute-running"> <Icon icon="info-block" iconSize={12} />
<Icon icon="info-block" iconSize={12} /> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<Button onClick={handleRecalcReport} minimal={true} small={true}> <T id={'refresh'} />
<T id={'refresh'} /> </Button>
</Button> </FinancialComputeAlert>
</div>
</If>
); );
} }

View File

@@ -8,6 +8,7 @@ import { getColumnWidth } from '@/utils';
import { useGeneralLedgerContext } from './GeneralLedgerProvider'; import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { Align } from '@/constants'; import { Align } from '@/constants';
/** /**
@@ -116,17 +117,18 @@ export function GeneralLedgerSheetAlerts() {
if (isLoading) { if (isLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is not running.
if (!generalLedger.meta.is_cost_compute_running) {
return null;
}
return ( return (
<If condition={generalLedger.meta.is_cost_compute_running}> <FinancialComputeAlert>
<div class="alert-compute-running"> <Icon icon="info-block" iconSize={12} />
<Icon icon="info-block" iconSize={12} /> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<Button onClick={handleRecalcReport} minimal={true} small={true}> <T id={'refresh'} />
<T id={'refresh'} /> </Button>
</Button> </FinancialComputeAlert>
</div>
</If>
); );
} }

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