mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
Compare commits
135 Commits
v0.19.3
...
fix-gettin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92acbcbe0 | ||
|
|
c986585cd9 | ||
|
|
250f0a30ef | ||
|
|
84b5e1adc1 | ||
|
|
2ab28370db | ||
|
|
a88a525326 | ||
|
|
fce8e2c5a4 | ||
|
|
095608266c | ||
|
|
0ec8aaa330 | ||
|
|
9fcb3ef77d | ||
|
|
dc61c57daf | ||
|
|
af284f3f6d | ||
|
|
c43123db76 | ||
|
|
ebbcab3926 | ||
|
|
a235f573c0 | ||
|
|
84a0b8f495 | ||
|
|
b87321c897 | ||
|
|
fc6ebfea5c | ||
|
|
c9fe6d9b37 | ||
|
|
161d60393a | ||
|
|
79413fa85e | ||
|
|
58552c6c94 | ||
|
|
2072e35cfa | ||
|
|
1eaac9d691 | ||
|
|
a916e8a0cb | ||
|
|
a56f560036 | ||
|
|
0fb886936c | ||
|
|
670136916f | ||
|
|
768297f137 | ||
|
|
ef505a0a62 | ||
|
|
42d40620ec | ||
|
|
959ef7a691 | ||
|
|
60f03f534b | ||
|
|
e44ebb700a | ||
|
|
8e94c7a755 | ||
|
|
3a2ca36c07 | ||
|
|
88ece74c8a | ||
|
|
67a8610328 | ||
|
|
278d61ce61 | ||
|
|
e72d6ad6b8 | ||
|
|
bf66b31679 | ||
|
|
b4d426d2e8 | ||
|
|
7f7dd270e7 | ||
|
|
f6bad8fe30 | ||
|
|
820b363f79 | ||
|
|
07740a51ef | ||
|
|
d15fb6fe19 | ||
|
|
5749ccec81 | ||
|
|
4a99f6c0cf | ||
|
|
59f480f9d5 | ||
|
|
6cb9c919b5 | ||
|
|
1062b65b5b | ||
|
|
bf3a70cabd | ||
|
|
f46cd28f87 | ||
|
|
dffcfe50aa | ||
|
|
fac55efbc7 | ||
|
|
8b90ce5f6c | ||
|
|
705b8da053 | ||
|
|
4a05ccc692 | ||
|
|
3200d65d90 | ||
|
|
3f23038227 | ||
|
|
408c807fc2 | ||
|
|
d29079a8c5 | ||
|
|
cca596b4a9 | ||
|
|
b768f18294 | ||
|
|
fed620505d | ||
|
|
a008aea3f3 | ||
|
|
25297bc191 | ||
|
|
1989887b25 | ||
|
|
e4fb126d39 | ||
|
|
5fcb2d9cc9 | ||
|
|
06ea631732 | ||
|
|
2f21107a43 | ||
|
|
fb8118bea8 | ||
|
|
4ba1c0aa22 | ||
|
|
169f115fa0 | ||
|
|
61ab2b78d9 | ||
|
|
93732430fc | ||
|
|
0215206220 | ||
|
|
3c8956fedf | ||
|
|
4477ada1ad | ||
|
|
fde9ccc5ca | ||
|
|
bbbd96f159 | ||
|
|
9f4de8115f | ||
|
|
d9f241a2f8 | ||
|
|
ee96dc68cc | ||
|
|
b12f090d13 | ||
|
|
f6ce761a27 | ||
|
|
8c89e04f54 | ||
|
|
f97b127a69 | ||
|
|
5c1fa8f5cd | ||
|
|
64c4d7b5a4 | ||
|
|
ea2fad648b | ||
|
|
9b8b51cb91 | ||
|
|
09b7e74d65 | ||
|
|
7137e06d99 | ||
|
|
fc29b765f7 | ||
|
|
2946475f89 | ||
|
|
d2193fdac0 | ||
|
|
ec2b7e332e | ||
|
|
a3704df6dd | ||
|
|
c3c784e52c | ||
|
|
bbcf695a6c | ||
|
|
038d4dd5a7 | ||
|
|
961e4b99e8 | ||
|
|
9991eebaaf | ||
|
|
cd90fede54 | ||
|
|
a2d28648bd | ||
|
|
3097d05eda | ||
|
|
ff94d8d9b2 | ||
|
|
79cc09fad9 | ||
|
|
c1b29c3f23 | ||
|
|
cf4bb3007e | ||
|
|
193a86cf30 | ||
|
|
7a81f14eb2 | ||
|
|
14d1f0bd1d | ||
|
|
82f8648c59 | ||
|
|
c928940d32 | ||
|
|
0a78d56015 | ||
|
|
1a5716873e | ||
|
|
01b7c86ab9 | ||
|
|
0ca209b195 | ||
|
|
be6f6e3c73 | ||
|
|
cb016be78c | ||
|
|
fc085f2328 | ||
|
|
9a34f3e283 | ||
|
|
7054e862d5 | ||
|
|
faa81abee4 | ||
|
|
6d01f2a323 | ||
|
|
72678bb936 | ||
|
|
df8b68fda6 | ||
|
|
9ae5644af9 | ||
|
|
e8830c5911 | ||
|
|
7699889bd6 | ||
|
|
35a061d188 |
@@ -150,6 +150,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Champetaman",
|
||||||
|
"name": "Camilo Oviedo",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
|
||||||
|
"profile": "https://www.camilooviedo.com/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
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.19.4] - 18-08-2024
|
||||||
|
|
||||||
|
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
||||||
|
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
||||||
|
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
|
||||||
|
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
|
||||||
|
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
|
||||||
|
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
|
||||||
|
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
|
||||||
|
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
|
||||||
|
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
|
||||||
|
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
|
||||||
|
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
|
||||||
|
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
|
||||||
|
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
|
||||||
|
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
|
||||||
|
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
|
||||||
|
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
|
||||||
|
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
||||||
|
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
||||||
|
|
||||||
## [v0.18.0] - 10-08-2024
|
## [v0.18.0] - 10-08-2024
|
||||||
|
|
||||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"agendash": "^3.1.0",
|
"agendash": "^3.1.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
"async": "^3.2.0",
|
"async": "^3.2.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -69,9 +70,8 @@
|
|||||||
"is-my-json-valid": "^2.20.5",
|
"is-my-json-valid": "^2.20.5",
|
||||||
"js-money": "^0.6.3",
|
"js-money": "^0.6.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"knex": "^0.95.15",
|
"knex": "^3.1.0",
|
||||||
"knex-cleaner": "^1.3.0",
|
"knex-cleaner": "^1.3.0",
|
||||||
"knex-db-manager": "^0.6.1",
|
|
||||||
"libphonenumber-js": "^1.9.6",
|
"libphonenumber-js": "^1.9.6",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lru-cache": "^6.0.0",
|
"lru-cache": "^6.0.0",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { NextFunction, Request, Response, Router } from 'express';
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { param, query } from 'express-validator';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
|
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
|
||||||
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
|
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
|
||||||
import { param } from 'express-validator';
|
import { GetPendingBankAccountTransactions } from '@/services/Cashflow/GetPendingBankAccountTransaction';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class BankAccountsController extends BaseController {
|
export class BankAccountsController extends BaseController {
|
||||||
@@ -13,6 +14,9 @@ export class BankAccountsController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private bankAccountsApp: BankAccountsApplication;
|
private bankAccountsApp: BankAccountsApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPendingTransactionsService: GetPendingBankAccountTransactions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -20,6 +24,16 @@ export class BankAccountsController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/:bankAccountId/meta', this.getBankAccountSummary.bind(this));
|
router.get('/:bankAccountId/meta', this.getBankAccountSummary.bind(this));
|
||||||
|
router.get(
|
||||||
|
'/pending_transactions',
|
||||||
|
[
|
||||||
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
|
query('page').optional().isNumeric().toInt(),
|
||||||
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
this.getBankAccountsPendingTransactions.bind(this)
|
||||||
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:bankAccountId/disconnect',
|
'/:bankAccountId/disconnect',
|
||||||
this.disconnectBankAccount.bind(this)
|
this.disconnectBankAccount.bind(this)
|
||||||
@@ -27,17 +41,13 @@ export class BankAccountsController extends BaseController {
|
|||||||
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
|
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
|
||||||
router.post(
|
router.post(
|
||||||
'/:bankAccountId/pause_feeds',
|
'/:bankAccountId/pause_feeds',
|
||||||
[
|
[param('bankAccountId').exists().isNumeric().toInt()],
|
||||||
param('bankAccountId').exists().isNumeric().toInt(),
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.pauseBankAccountFeeds.bind(this)
|
this.pauseBankAccountFeeds.bind(this)
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:bankAccountId/resume_feeds',
|
'/:bankAccountId/resume_feeds',
|
||||||
[
|
[param('bankAccountId').exists().isNumeric().toInt()],
|
||||||
param('bankAccountId').exists().isNumeric().toInt(),
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.resumeBankAccountFeeds.bind(this)
|
this.resumeBankAccountFeeds.bind(this)
|
||||||
);
|
);
|
||||||
@@ -72,6 +82,32 @@ export class BankAccountsController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the bank account pending transactions.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async getBankAccountsPendingTransactions(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const query = this.matchedQueryData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data =
|
||||||
|
await this.getPendingTransactionsService.getPendingTransactions(
|
||||||
|
tenantId,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
return res.status(200).send(data);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disonnect the given bank account.
|
* Disonnect the given bank account.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -33,7 +33,16 @@ export class BankingRulesController extends BaseController {
|
|||||||
body('conditions.*.field').exists().isIn(['description', 'amount']),
|
body('conditions.*.field').exists().isIn(['description', 'amount']),
|
||||||
body('conditions.*.comparator')
|
body('conditions.*.comparator')
|
||||||
.exists()
|
.exists()
|
||||||
.isIn(['equals', 'contains', 'not_contain'])
|
.isIn([
|
||||||
|
'equals',
|
||||||
|
'equal',
|
||||||
|
'contains',
|
||||||
|
'not_contain',
|
||||||
|
'bigger',
|
||||||
|
'bigger_or_equal',
|
||||||
|
'smaller',
|
||||||
|
'smaller_or_equal',
|
||||||
|
])
|
||||||
.default('contain')
|
.default('contain')
|
||||||
.trim(),
|
.trim(),
|
||||||
body('conditions.*.value').exists().trim(),
|
body('conditions.*.value').exists().trim(),
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getExcludedBankTransactions.bind(this)
|
this.getExcludedBankTransactions.bind(this)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export class RecognizedTransactionsController extends BaseController {
|
|||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getRecognizedTransactions.bind(this)
|
this.getRecognizedTransactions.bind(this)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ValidationChain, check, param, query } from 'express-validator';
|
import { ValidationChain, body, check, param, query } from 'express-validator';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
@@ -43,6 +43,16 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
this.asyncMiddleware(this.newCashflowTransaction),
|
this.asyncMiddleware(this.newCashflowTransaction),
|
||||||
this.catchServiceErrors
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
|
router.post(
|
||||||
|
'/transactions/uncategorize/bulk',
|
||||||
|
[
|
||||||
|
body('ids').isArray({ min: 1 }),
|
||||||
|
body('ids.*').exists().isNumeric().toInt(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
this.uncategorizeBulkTransactions.bind(this),
|
||||||
|
this.catchServiceErrors
|
||||||
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/transactions/:id/uncategorize',
|
'/transactions/:id/uncategorize',
|
||||||
this.revertCategorizedCashflowTransaction,
|
this.revertCategorizedCashflowTransaction,
|
||||||
@@ -74,6 +84,10 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
param('id').exists().isNumeric().toInt(),
|
param('id').exists().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||||
|
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
|
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +198,34 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncategorize the given transactions in bulk.
|
||||||
|
* @param {Request<{}>} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response | null>}
|
||||||
|
*/
|
||||||
|
private uncategorizeBulkTransactions = async (
|
||||||
|
req: Request<{}>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { ids: uncategorizedTransactionIds } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.cashflowApplication.uncategorizeTransactions(
|
||||||
|
tenantId,
|
||||||
|
uncategorizedTransactionIds
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'The given transactions have been uncategorized successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Categorize the cashflow transaction.
|
* Categorize the cashflow transaction.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
||||||
|
import config from '@/config';
|
||||||
|
@Service()
|
||||||
|
export class OneClickDemoController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
private oneClickDemoApp: OneClickDemoApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor method.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
// Protects the endpoints if the feature is not enabled.
|
||||||
|
const protectMiddleware = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
// Add your protection logic here
|
||||||
|
if (config.oneClickDemoAccounts) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.status(403).send({ message: 'Forbidden' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
router.post(
|
||||||
|
'/one_click',
|
||||||
|
protectMiddleware,
|
||||||
|
asyncMiddleware(this.oneClickDemo.bind(this))
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/one_click_signin',
|
||||||
|
[body('demo_id').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
protectMiddleware,
|
||||||
|
asyncMiddleware(this.oneClickSignIn.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-click demo application.
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
* @param {NextFunction} next -
|
||||||
|
*/
|
||||||
|
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.oneClickDemoApp.createOneClick();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
data,
|
||||||
|
message: 'The one-click demo has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign-in to one-click demo account.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private async oneClickSignIn(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { demoId } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
|
import PaymentsReceivedPages from '@/services/Sales/PaymentReceived/PaymentsReceivedPages';
|
||||||
|
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceived/PaymentReceivedApplication';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
|
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
@@ -22,7 +22,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
private paymentReceiveApplication: PaymentReceivesApplication;
|
private paymentReceiveApplication: PaymentReceivesApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private PaymentReceivesPages: PaymentReceivesPages;
|
private PaymentsReceivedPages: PaymentsReceivedPages;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
@@ -229,7 +229,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const storedPaymentReceive =
|
const storedPaymentReceive =
|
||||||
await this.paymentReceiveApplication.createPaymentReceive(
|
await this.paymentReceiveApplication.createPaymentReceived(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
user
|
user
|
||||||
@@ -376,7 +376,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { customerId } = this.matchedQueryData(req);
|
const { customerId } = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entries = await this.PaymentReceivesPages.getNewPageEntries(
|
const entries = await this.PaymentsReceivedPages.getNewPageEntries(
|
||||||
tenantId,
|
tenantId,
|
||||||
customerId
|
customerId
|
||||||
);
|
);
|
||||||
@@ -404,7 +404,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { paymentReceive, entries } =
|
const { paymentReceive, entries } =
|
||||||
await this.PaymentReceivesPages.getPaymentReceiveEditPage(
|
await this.PaymentsReceivedPages.getPaymentReceiveEditPage(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
user
|
user
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export class SubscriptionController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
|
await this.subscriptionApp.cancelSubscription(tenantId);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class Webhooks extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] ?? '';
|
const signature = req.headers['x-signature'] as string ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import { BankingController } from './controllers/Banking/BankingController';
|
|||||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||||
import { ExportController } from './controllers/Export/ExportController';
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||||
|
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -80,6 +81,7 @@ export default () => {
|
|||||||
app.use('/jobs', Container.get(Jobs).router());
|
app.use('/jobs', Container.get(Jobs).router());
|
||||||
app.use('/account', Container.get(Account).router());
|
app.use('/account', Container.get(Account).router());
|
||||||
app.use('/webhooks', Container.get(Webhooks).router());
|
app.use('/webhooks', Container.get(Webhooks).router());
|
||||||
|
app.use('/demo', Container.get(OneClickDemoController).router())
|
||||||
|
|
||||||
// - Dashboard routes.
|
// - Dashboard routes.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||||
|
|
||||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { tenantId } = req.user;
|
const { tenantId } = req.user;
|
||||||
|
|
||||||
const Logger = Container.get('logger');
|
const settings = await initializeTenantSettings(tenantId);
|
||||||
|
req.settings = settings;
|
||||||
|
|
||||||
|
res.on('finish', async () => {
|
||||||
|
await settings.save();
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const initializeTenantSettings = async (tenantId: number) => {
|
||||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||||
|
|
||||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||||
@@ -18,10 +28,5 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
|||||||
|
|
||||||
await settings.load();
|
await settings.load();
|
||||||
|
|
||||||
req.settings = settings;
|
return settings;
|
||||||
|
|
||||||
res.on('finish', async () => {
|
|
||||||
await settings.save();
|
|
||||||
});
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Request } from 'express';
|
|||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||||
import rtlDetect from 'rtl-detect';
|
import rtlDetect from 'rtl-detect';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
|
|
||||||
export default (req: Request, tenant: ITenant) => {
|
export default (req: Request, tenant: ITenant) => {
|
||||||
const { id: tenantId, organizationId } = tenant;
|
const { id: tenantId, organizationId } = tenant;
|
||||||
@@ -16,7 +17,7 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
|
|
||||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||||
|
|
||||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
tenantContainer.set('i18n', injectI18nUtils());
|
||||||
|
|
||||||
const knexInstance = tenantServices.knex(tenantId);
|
const knexInstance = tenantServices.knex(tenantId);
|
||||||
const models = tenantServices.models(tenantId);
|
const models = tenantServices.models(tenantId);
|
||||||
@@ -33,14 +34,35 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const injectI18nUtils = (req) => {
|
export const injectI18nUtils = (req) => {
|
||||||
const locale = req.getLocale();
|
const globalI18n = Container.get('i18n');
|
||||||
|
const locale = globalI18n.getLocale();
|
||||||
const direction = rtlDetect.getLangDir(locale);
|
const direction = rtlDetect.getLangDir(locale);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
__: req.__,
|
__: globalI18n.__,
|
||||||
direction,
|
direction,
|
||||||
isRtl: direction === 'rtl',
|
isRtl: direction === 'rtl',
|
||||||
isLtr: direction === 'ltr',
|
isLtr: direction === 'ltr',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initalizeTenantServices = async (tenantId: number) => {
|
||||||
|
const tenant = await Tenant.query()
|
||||||
|
.findById(tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
|
const tenantServices = Container.get(TenancyService);
|
||||||
|
const tenantsManager = Container.get(TenantsManagerService);
|
||||||
|
|
||||||
|
// Initialize the knex instance.
|
||||||
|
tenantsManager.setupKnexInstance(tenant);
|
||||||
|
|
||||||
|
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||||
|
tenantContainer.set('i18n', injectI18nUtils());
|
||||||
|
|
||||||
|
tenantServices.knex(tenantId);
|
||||||
|
tenantServices.models(tenantId);
|
||||||
|
tenantServices.repositories(tenantId);
|
||||||
|
tenantServices.cache(tenantId);
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
tenant: {
|
tenant: {
|
||||||
db_client: process.env.TENANT_DB_CLIENT || process.env.DB_CLIENT || 'mysql',
|
db_client: process.env.TENANT_DB_CLIENT || process.env.DB_CLIENT || 'mysql',
|
||||||
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX,
|
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX || 'bigcapital_tenant_',
|
||||||
db_host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
db_host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
||||||
db_user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
db_user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
||||||
db_password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
db_password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||||
@@ -245,4 +245,12 @@ module.exports = {
|
|||||||
loops: {
|
loops: {
|
||||||
apiKey: process.env.LOOPS_API_KEY,
|
apiKey: process.env.LOOPS_API_KEY,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-click demo accounts.
|
||||||
|
*/
|
||||||
|
oneClickDemoAccounts: {
|
||||||
|
enable: parseBoolean(process.env.ONE_CLICK_DEMO_ACCOUNTS, false),
|
||||||
|
demoUrl: process.env.ONE_CLICK_DEMO_ACCOUNTS_URL || '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
|
||||||
|
table.boolean('pending').defaultTo(false);
|
||||||
|
table.string('pending_plaid_transaction_id').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
|
||||||
|
table.dropColumn('pending');
|
||||||
|
table.dropColumn('pending_plaid_transaction_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ export default class SeedSettings extends TenantSeeder {
|
|||||||
|
|
||||||
// Manual journals settings.
|
// Manual journals settings.
|
||||||
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
||||||
|
{ group: 'manual_journals', key: 'number_prefix', value: 'J-' },
|
||||||
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
||||||
|
|
||||||
// Sale invoices settings.
|
// Sale invoices settings.
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export interface IRegisterDTO {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
organizationName: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILoginDTO {
|
export interface ILoginDTO {
|
||||||
@@ -77,6 +76,10 @@ export interface IAuthSendedResetPassword {
|
|||||||
|
|
||||||
export interface IAuthGetMetaPOJO {
|
export interface IAuthGetMetaPOJO {
|
||||||
signupDisabled: boolean;
|
signupDisabled: boolean;
|
||||||
|
oneClickDemo: {
|
||||||
|
enable: boolean;
|
||||||
|
demoUrl: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthSignUpVerifingEventPayload {
|
export interface IAuthSignUpVerifingEventPayload {
|
||||||
|
|||||||
@@ -268,6 +268,8 @@ export interface CreateUncategorizedTransactionDTO {
|
|||||||
description?: string;
|
description?: string;
|
||||||
referenceNo?: string | null;
|
referenceNo?: string | null;
|
||||||
plaidTransactionId?: string | null;
|
plaidTransactionId?: string | null;
|
||||||
|
pending?: boolean;
|
||||||
|
pendingPlaidTransactionId?: string | null;
|
||||||
batch?: string;
|
batch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,3 +285,17 @@ export interface IUncategorizedTransactionCreatedEventPayload {
|
|||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPendingTransactionRemovingEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
uncategorizedTransactionId: number;
|
||||||
|
pendingTransaction: IUncategorizedCashflowTransaction;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPendingTransactionRemovedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
uncategorizedTransactionId: number;
|
||||||
|
pendingTransaction: IUncategorizedCashflowTransaction;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,11 +167,18 @@ export interface CategorizeTransactionAsExpenseDTO {
|
|||||||
export interface IGetUncategorizedTransactionsQuery {
|
export interface IGetUncategorizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
minAmount?: number;
|
||||||
|
maxAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IGetRecognizedTransactionsQuery {
|
export interface IGetRecognizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
minAmount?: number;
|
||||||
|
maxAmount?: number;
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ export interface IGeneralLedgerSheetAccountTransaction {
|
|||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
note?: string;
|
note?: string;
|
||||||
|
|
||||||
transactionType?: string;
|
transactionTypeFormatted: string;
|
||||||
transactionNumber: string;
|
transactionNumber: string;
|
||||||
|
|
||||||
referenceId?: number;
|
referenceId?: number;
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
|||||||
|
|
||||||
export interface IImportFileCommitedEventPayload {
|
export interface IImportFileCommitedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
importId: number;
|
importId: string;
|
||||||
meta: ImportFilePreviewPOJO;
|
meta: ImportFilePreviewPOJO;
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import { ILedgerEntry } from './Ledger';
|
|||||||
import { ISaleInvoice } from './SaleInvoice';
|
import { ISaleInvoice } from './SaleInvoice';
|
||||||
import { AttachmentLinkDTO } from './Attachments';
|
import { AttachmentLinkDTO } from './Attachments';
|
||||||
|
|
||||||
export interface IPaymentReceive {
|
export interface IPaymentReceived {
|
||||||
id?: number;
|
id?: number;
|
||||||
customerId: number;
|
customerId: number;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
@@ -19,14 +19,14 @@ export interface IPaymentReceive {
|
|||||||
depositAccountId: number;
|
depositAccountId: number;
|
||||||
paymentReceiveNo: string;
|
paymentReceiveNo: string;
|
||||||
statement: string;
|
statement: string;
|
||||||
entries: IPaymentReceiveEntry[];
|
entries: IPaymentReceivedEntry[];
|
||||||
userId: number;
|
userId: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceiveCreateDTO {
|
export interface IPaymentReceivedCreateDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -35,13 +35,13 @@ export interface IPaymentReceiveCreateDTO {
|
|||||||
depositAccountId: number;
|
depositAccountId: number;
|
||||||
paymentReceiveNo?: string;
|
paymentReceiveNo?: string;
|
||||||
statement: string;
|
statement: string;
|
||||||
entries: IPaymentReceiveEntryDTO[];
|
entries: IPaymentReceivedEntryDTO[];
|
||||||
|
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
attachments?: AttachmentLinkDTO[];
|
attachments?: AttachmentLinkDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditDTO {
|
export interface IPaymentReceivedEditDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -50,12 +50,12 @@ export interface IPaymentReceiveEditDTO {
|
|||||||
depositAccountId: number;
|
depositAccountId: number;
|
||||||
paymentReceiveNo?: string;
|
paymentReceiveNo?: string;
|
||||||
statement: string;
|
statement: string;
|
||||||
entries: IPaymentReceiveEntryDTO[];
|
entries: IPaymentReceivedEntryDTO[];
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
attachments?: AttachmentLinkDTO[];
|
attachments?: AttachmentLinkDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEntry {
|
export interface IPaymentReceivedEntry {
|
||||||
id?: number;
|
id?: number;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
@@ -64,15 +64,15 @@ export interface IPaymentReceiveEntry {
|
|||||||
invoice?: ISaleInvoice;
|
invoice?: ISaleInvoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEntryDTO {
|
export interface IPaymentReceivedEntryDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
index: number;
|
index: number;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId?: number;
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
paymentAmount: number;
|
paymentAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
export interface IPaymentsReceivedFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,65 +88,65 @@ export interface IPaymentReceivePageEntry {
|
|||||||
date: Date | string;
|
date: Date | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditPage {
|
export interface IPaymentReceivedEditPage {
|
||||||
paymentReceive: IPaymentReceive;
|
paymentReceive: IPaymentReceived;
|
||||||
entries: IPaymentReceivePageEntry[];
|
entries: IPaymentReceivePageEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentsReceiveService {
|
export interface IPaymentsReceivedService {
|
||||||
validateCustomerHasNoPayments(
|
validateCustomerHasNoPayments(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
customerId: number
|
customerId: number
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveSmsDetails {
|
export interface IPaymentReceivedSmsDetails {
|
||||||
customerName: string;
|
customerName: string;
|
||||||
customerPhoneNumber: string;
|
customerPhoneNumber: string;
|
||||||
smsMessage: string;
|
smsMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveCreatingPayload {
|
export interface IPaymentReceivedCreatingPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO;
|
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveCreatedPayload {
|
export interface IPaymentReceivedCreatedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
paymentReceive: IPaymentReceive;
|
paymentReceive: IPaymentReceived;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
authorizedUser: ISystemUser;
|
authorizedUser: ISystemUser;
|
||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO;
|
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditedPayload {
|
export interface IPaymentReceivedEditedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
paymentReceive: IPaymentReceive;
|
paymentReceive: IPaymentReceived;
|
||||||
oldPaymentReceive: IPaymentReceive;
|
oldPaymentReceive: IPaymentReceived;
|
||||||
paymentReceiveDTO: IPaymentReceiveEditDTO;
|
paymentReceiveDTO: IPaymentReceivedEditDTO;
|
||||||
authorizedUser: ISystemUser;
|
authorizedUser: ISystemUser;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditingPayload {
|
export interface IPaymentReceivedEditingPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
oldPaymentReceive: IPaymentReceive;
|
oldPaymentReceive: IPaymentReceived;
|
||||||
paymentReceiveDTO: IPaymentReceiveEditDTO;
|
paymentReceiveDTO: IPaymentReceivedEditDTO;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveDeletingPayload {
|
export interface IPaymentReceivedDeletingPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
oldPaymentReceive: IPaymentReceive;
|
oldPaymentReceive: IPaymentReceived;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceiveDeletedPayload {
|
export interface IPaymentReceivedDeletedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
oldPaymentReceive: IPaymentReceive;
|
oldPaymentReceive: IPaymentReceived;
|
||||||
authorizedUser: ISystemUser;
|
authorizedUser: ISystemUser;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,3 +33,7 @@ export interface IOrganizationBuildEventPayload {
|
|||||||
buildDTO: IOrganizationBuildDTO;
|
buildDTO: IOrganizationBuildDTO;
|
||||||
systemUser: ISystemUser;
|
systemUser: ISystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IOrganizationBuiltEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
}
|
||||||
8
packages/server/src/interfaces/Subscription.ts
Normal file
8
packages/server/src/interfaces/Subscription.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SubscriptionPayload {
|
||||||
|
lemonSqueezyId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubscriptionPaymentStatus {
|
||||||
|
Succeed = 'succeed',
|
||||||
|
Failed = 'failed',
|
||||||
|
}
|
||||||
@@ -51,5 +51,4 @@ export interface ISystemService {
|
|||||||
cache();
|
cache();
|
||||||
repositories();
|
repositories();
|
||||||
knex();
|
knex();
|
||||||
dbManager();
|
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ export * from './Times';
|
|||||||
export * from './ProjectProfitabilitySummary';
|
export * from './ProjectProfitabilitySummary';
|
||||||
export * from './TaxRate';
|
export * from './TaxRate';
|
||||||
export * from './Plaid';
|
export * from './Plaid';
|
||||||
|
export * from './Subscription';
|
||||||
|
|
||||||
export interface I18nService {
|
export interface I18nService {
|
||||||
__: (input: string) => string;
|
__: (input: string) => string;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default class ComputeItemCostJob {
|
|||||||
|
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'compute-item-cost',
|
'compute-item-cost',
|
||||||
{ priority: 'high', concurrency: 1 },
|
{ priority: 'high', concurrency: 20 },
|
||||||
this.handler.bind(this)
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
this.agenda.on('start:compute-item-cost', this.onJobStart.bind(this));
|
this.agenda.on('start:compute-item-cost', this.onJobStart.bind(this));
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class OrganizationSetupJob {
|
|||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'organization-setup',
|
'organization-setup',
|
||||||
{ priority: 'high', concurrency: 1 },
|
{ priority: 'high', concurrency: 20 },
|
||||||
this.handler
|
this.handler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default class WriteInvoicesJournalEntries {
|
|||||||
|
|
||||||
agenda.define(
|
agenda.define(
|
||||||
eventName,
|
eventName,
|
||||||
{ priority: 'normal', concurrency: 1 },
|
{ priority: 'normal', concurrency: 20 },
|
||||||
this.handler.bind(this)
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||||
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
||||||
|
import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||||
|
|
||||||
export class Transformer {
|
export class Transformer {
|
||||||
public context: any;
|
public context: any;
|
||||||
@@ -155,19 +156,35 @@ export class Transformer {
|
|||||||
this.dateFormat = format;
|
this.dateFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date.
|
||||||
|
* @param {} date
|
||||||
|
* @param {string} format -
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected formatDate(date, format?: string) {
|
||||||
|
// Use the export date format if the async operation is in exporting,
|
||||||
|
// otherwise use the given or default format.
|
||||||
|
const _format = this.context.exportAls.isExport
|
||||||
|
? EXPORT_DTE_FORMAT
|
||||||
|
: format || this.dateFormat;
|
||||||
|
|
||||||
|
return date ? moment(date).format(_format) : '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param date
|
* @param date
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatDate(date) {
|
protected formatDateFromNow(date) {
|
||||||
return date ? moment(date).format(this.dateFormat) : '';
|
return date ? moment(date).fromNow(true) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param number
|
* @param number
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatNumber(number, props?) {
|
protected formatNumber(number, props?) {
|
||||||
return formatNumber(number, { money: false, ...props });
|
return formatNumber(number, { money: false, ...props });
|
||||||
@@ -177,7 +194,7 @@ export class Transformer {
|
|||||||
*
|
*
|
||||||
* @param money
|
* @param money
|
||||||
* @param options
|
* @param options
|
||||||
* @returns
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
protected formatMoney(money, options?) {
|
protected formatMoney(money, options?) {
|
||||||
return formatNumber(money, {
|
return formatNumber(money, {
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import { isNull } from 'lodash';
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { TenantMetadata } from '@/system/models';
|
import { TenantMetadata } from '@/system/models';
|
||||||
import { Transformer } from './Transformer';
|
import { Transformer } from './Transformer';
|
||||||
|
import { ImportAls } from '@/services/Import/ImportALS';
|
||||||
|
import { ExportAls } from '@/services/Export/ExportAls';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class TransformerInjectable {
|
export class TransformerInjectable {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private exportAls: ExportAls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the application context of all tenant transformers.
|
* Retrieves the application context of all tenant transformers.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -17,10 +22,12 @@ export class TransformerInjectable {
|
|||||||
async getApplicationContext(tenantId: number) {
|
async getApplicationContext(tenantId: number) {
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
const organization = await TenantMetadata.query().findOne({ tenantId });
|
const organization = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
const exportAls = this.exportAls;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
organization,
|
organization,
|
||||||
i18n,
|
i18n,
|
||||||
|
exportAls,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import knexManager from 'knex-db-manager';
|
|
||||||
import { systemKnexConfig, systemDbManager } from 'config/knexConfig';
|
|
||||||
|
|
||||||
export default () => knexManager.databaseManagerFactory({
|
|
||||||
knex: systemKnexConfig,
|
|
||||||
dbManager: systemDbManager,
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,6 @@ import LoggerInstance from '@/loaders/logger';
|
|||||||
import agendaFactory from '@/loaders/agenda';
|
import agendaFactory from '@/loaders/agenda';
|
||||||
import SmsClientLoader from '@/loaders/smsClient';
|
import SmsClientLoader from '@/loaders/smsClient';
|
||||||
import mailInstance from '@/loaders/mail';
|
import mailInstance from '@/loaders/mail';
|
||||||
import dbManagerFactory from '@/loaders/dbManager';
|
|
||||||
import i18n from '@/loaders/i18n';
|
import i18n from '@/loaders/i18n';
|
||||||
import repositoriesLoader from '@/loaders/systemRepositories';
|
import repositoriesLoader from '@/loaders/systemRepositories';
|
||||||
import Cache from '@/services/Cache';
|
import Cache from '@/services/Cache';
|
||||||
@@ -16,7 +15,6 @@ export default ({ mongoConnection, knex }) => {
|
|||||||
try {
|
try {
|
||||||
const agendaInstance = agendaFactory({ mongoConnection });
|
const agendaInstance = agendaFactory({ mongoConnection });
|
||||||
const smsClientInstance = SmsClientLoader(config.easySMSGateway.api_key);
|
const smsClientInstance = SmsClientLoader(config.easySMSGateway.api_key);
|
||||||
const dbManager = dbManagerFactory(knex);
|
|
||||||
const cacheInstance = new Cache();
|
const cacheInstance = new Cache();
|
||||||
|
|
||||||
Container.set('logger', LoggerInstance);
|
Container.set('logger', LoggerInstance);
|
||||||
@@ -24,7 +22,6 @@ export default ({ mongoConnection, knex }) => {
|
|||||||
Container.set('SMSClient', smsClientInstance);
|
Container.set('SMSClient', smsClientInstance);
|
||||||
Container.set('mail', mailInstance);
|
Container.set('mail', mailInstance);
|
||||||
|
|
||||||
Container.set('dbManager', dbManager);
|
|
||||||
LoggerInstance.info(
|
LoggerInstance.info(
|
||||||
'[DI] Database manager has been injected into container.'
|
'[DI] Database manager has been injected into container.'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/E
|
|||||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
||||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||||
|
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||||
|
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
||||||
|
import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscription/events/TriggerInvalidateCacheOnSubscriptionChange';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -245,8 +248,10 @@ export const susbcribers = () => {
|
|||||||
DeleteCashflowTransactionOnUncategorize,
|
DeleteCashflowTransactionOnUncategorize,
|
||||||
PreventDeleteTransactionOnDelete,
|
PreventDeleteTransactionOnDelete,
|
||||||
|
|
||||||
|
// Subscription
|
||||||
SubscribeFreeOnSignupCommunity,
|
SubscribeFreeOnSignupCommunity,
|
||||||
SendVerfiyMailOnSignUp,
|
SendVerfiyMailOnSignUp,
|
||||||
|
TriggerInvalidateCacheOnSubscriptionChange,
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
AttachmentsOnSaleInvoiceCreated,
|
AttachmentsOnSaleInvoiceCreated,
|
||||||
@@ -277,8 +282,12 @@ export const susbcribers = () => {
|
|||||||
// Plaid
|
// Plaid
|
||||||
RecognizeSyncedBankTranasctions,
|
RecognizeSyncedBankTranasctions,
|
||||||
DisconnectPlaidItemOnAccountDeleted,
|
DisconnectPlaidItemOnAccountDeleted,
|
||||||
|
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||||
|
|
||||||
// Loops
|
// Loops
|
||||||
LoopsEventsSubscriber
|
LoopsEventsSubscriber,
|
||||||
|
|
||||||
|
// Demo Account
|
||||||
|
SeedInitialDemoAccountDataOnOrgBuild,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,4 +34,4 @@
|
|||||||
// import 'services/Sales/SaleInvoiceWriteoffSubscriber';
|
// import 'services/Sales/SaleInvoiceWriteoffSubscriber';
|
||||||
// import 'subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
// import 'subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
||||||
// import 'subscribers/SaleReceipt/SendNotificationToCustomer';
|
// import 'subscribers/SaleReceipt/SendNotificationToCustomer';
|
||||||
// import 'services/Sales/PaymentReceives/PaymentReceiveSmsSubscriber';
|
// import 'services/Sales/PaymentReceived/PaymentReceiveSmsSubscriber';
|
||||||
@@ -2,6 +2,7 @@ import { I18n } from 'i18n';
|
|||||||
|
|
||||||
export default () => new I18n({
|
export default () => new I18n({
|
||||||
locales: ['en', 'ar'],
|
locales: ['en', 'ar'],
|
||||||
|
defaultLocale: 'en',
|
||||||
register: global,
|
register: global,
|
||||||
directory: global.__locales_dir,
|
directory: global.__locales_dir,
|
||||||
updateFiles: false,
|
updateFiles: false,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoic
|
|||||||
import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob';
|
import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob';
|
||||||
import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob';
|
import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob';
|
||||||
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
||||||
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
import { PaymentReceivedMailNotificationJob } from '@/services/Sales/PaymentReceived/PaymentReceivedMailNotificationJob';
|
||||||
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
||||||
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
|
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
|
||||||
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
|
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
|
||||||
@@ -28,7 +28,7 @@ export default ({ agenda }: { agenda: Agenda }) => {
|
|||||||
new SendSaleInvoiceReminderMailJob(agenda);
|
new SendSaleInvoiceReminderMailJob(agenda);
|
||||||
new SendSaleEstimateMailJob(agenda);
|
new SendSaleEstimateMailJob(agenda);
|
||||||
new SaleReceiptMailNotificationJob(agenda);
|
new SaleReceiptMailNotificationJob(agenda);
|
||||||
new PaymentReceiveMailNotificationJob(agenda);
|
new PaymentReceivedMailNotificationJob(agenda);
|
||||||
new PlaidFetchTransactionsJob(agenda);
|
new PlaidFetchTransactionsJob(agenda);
|
||||||
new ImportDeleteExpiredFilesJobs(agenda);
|
new ImportDeleteExpiredFilesJobs(agenda);
|
||||||
new SendVerifyMailJob(agenda);
|
new SendVerifyMailJob(agenda);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
|
|||||||
import ModelSetting from './ModelSetting';
|
import ModelSetting from './ModelSetting';
|
||||||
import BillPaymentSettings from './BillPayment.Settings';
|
import BillPaymentSettings from './BillPayment.Settings';
|
||||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
|
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
|
|
||||||
export default class BillPayment extends mixin(TenantModel, [
|
export default class BillPayment extends mixin(TenantModel, [
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
customerType: {
|
||||||
|
name: 'Customer Type',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'formattedCustomerType',
|
||||||
|
},
|
||||||
firstName: {
|
firstName: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -135,116 +140,117 @@ export default {
|
|||||||
openingBalance: {
|
openingBalance: {
|
||||||
name: 'vendor.field.opening_balance',
|
name: 'vendor.field.opening_balance',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
openingBalanceAt: {
|
||||||
name: 'vendor.field.opening_balance_at',
|
name: 'vendor.field.opening_balance_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false
|
printable: false,
|
||||||
|
accessor: 'formattedOpeningBalanceAt'
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currencyCode: {
|
||||||
name: 'vendor.field.currency',
|
name: 'vendor.field.currency',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'vendor.field.status',
|
name: 'vendor.field.status',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'vendor.field.note',
|
name: 'vendor.field.note',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
name: 'vendor.field.created_at',
|
name: 'vendor.field.created_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false
|
printable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
|
|||||||
@@ -257,25 +257,25 @@ export default {
|
|||||||
name: 'item.field.sell_price',
|
name: 'item.field.sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_price: {
|
costPrice: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
costAccount: {
|
costAccountId: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
sellAccount: {
|
sellAccountId: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
inventoryAccount: {
|
inventoryAccountId: {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
|
import { castArray, omit, pick } from 'lodash';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
@@ -16,7 +17,15 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryAndThrowIfHasRelations = ({ type, message }) => {
|
queryAndThrowIfHasRelations = ({
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
excludeRelations = [],
|
||||||
|
includedRelations = [],
|
||||||
|
}) => {
|
||||||
|
const _excludeRelations = castArray(excludeRelations);
|
||||||
|
const _includedRelations = castArray(includedRelations);
|
||||||
|
|
||||||
const model = this.modelClass();
|
const model = this.modelClass();
|
||||||
const modelRelations = Object.keys(model.relationMappings).filter(
|
const modelRelations = Object.keys(model.relationMappings).filter(
|
||||||
(relation) =>
|
(relation) =>
|
||||||
@@ -25,9 +34,20 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
) !== -1
|
) !== -1
|
||||||
);
|
);
|
||||||
const relations = model.secureDeleteRelations || modelRelations;
|
const relations = model.secureDeleteRelations || modelRelations;
|
||||||
|
const filteredByIncluded = relations.filter((r) =>
|
||||||
|
_includedRelations.includes(r)
|
||||||
|
);
|
||||||
|
const filteredByExcluded = relations.filter(
|
||||||
|
(r) => !excludeRelations.includes(r)
|
||||||
|
);
|
||||||
|
const filteredRelations = !isEmpty(_includedRelations)
|
||||||
|
? filteredByIncluded
|
||||||
|
: !isEmpty(_excludeRelations)
|
||||||
|
? filteredByExcluded
|
||||||
|
: relations;
|
||||||
|
|
||||||
this.runAfter((model, query) => {
|
this.runAfter((model, query) => {
|
||||||
const nonEmptyRelations = relations.filter(
|
const nonEmptyRelations = filteredRelations.filter(
|
||||||
(relation) => !isEmpty(model[relation])
|
(relation) => !isEmpty(model[relation])
|
||||||
);
|
);
|
||||||
if (nonEmptyRelations.length > 0) {
|
if (nonEmptyRelations.length > 0) {
|
||||||
@@ -36,7 +56,7 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
return model;
|
return model;
|
||||||
});
|
});
|
||||||
return this.onBuild((query) => {
|
return this.onBuild((query) => {
|
||||||
relations.forEach((relation) => {
|
filteredRelations.forEach((relation) => {
|
||||||
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
||||||
selectId(builder) {
|
selectId(builder) {
|
||||||
builder.select('id');
|
builder.select('id');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
|
|||||||
import ModelSetting from './ModelSetting';
|
import ModelSetting from './ModelSetting';
|
||||||
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
|
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
|
|
||||||
export default class PaymentReceive extends mixin(TenantModel, [
|
export default class PaymentReceive extends mixin(TenantModel, [
|
||||||
|
|||||||
69
packages/server/src/models/TaxRate.settings.ts
Normal file
69
packages/server/src/models/TaxRate.settings.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
export default {
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
exportable: true,
|
||||||
|
importable: true,
|
||||||
|
print: {
|
||||||
|
pageTitle: 'Tax Rates',
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
name: {
|
||||||
|
name: 'Tax Rate Name',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'Code',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'code',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
isNonRecoverable: {
|
||||||
|
name: 'Is Non Recoverable',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: {},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'Tax name',
|
||||||
|
fieldType: 'name',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'Code',
|
||||||
|
fieldType: 'code',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
isNonRecoverable: {
|
||||||
|
name: 'Is Non Recoverable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -2,8 +2,13 @@ import { mixin, Model, raw } from 'objection';
|
|||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
|
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
|
||||||
|
import TaxRateMeta from './TaxRate.settings';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
export default class TaxRate extends mixin(TenantModel, [
|
||||||
|
ModelSetting,
|
||||||
|
ModelSearchable,
|
||||||
|
]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -25,6 +30,13 @@ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
|||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tax rate meta.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return TaxRateMeta;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import * as R from 'ramda';
|
import moment from 'moment';
|
||||||
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSettings from './ModelSetting';
|
import ModelSettings from './ModelSetting';
|
||||||
import Account from './Account';
|
|
||||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||||
|
|
||||||
export default class UncategorizedCashflowTransaction extends mixin(
|
export default class UncategorizedCashflowTransaction extends mixin(
|
||||||
@@ -21,6 +20,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
plaidTransactionId!: string;
|
plaidTransactionId!: string;
|
||||||
recognizedTransactionId!: number;
|
recognizedTransactionId!: number;
|
||||||
excludedAt: Date;
|
excludedAt: Date;
|
||||||
|
pending: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
@@ -46,7 +46,8 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
'isDepositTransaction',
|
'isDepositTransaction',
|
||||||
'isWithdrawalTransaction',
|
'isWithdrawalTransaction',
|
||||||
'isRecognized',
|
'isRecognized',
|
||||||
'isExcluded'
|
'isExcluded',
|
||||||
|
'isPending',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +100,14 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
return !!this.excludedAt;
|
return !!this.excludedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the transaction is pending.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public get isPending(): boolean {
|
||||||
|
return !!this.pending;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
@@ -143,6 +152,42 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
query.whereNull('categorizeRefType');
|
query.whereNull('categorizeRefType');
|
||||||
query.whereNull('categorizeRefId');
|
query.whereNull('categorizeRefId');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the not pending transactions.
|
||||||
|
*/
|
||||||
|
notPending(query) {
|
||||||
|
query.where('pending', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the pending transactions.
|
||||||
|
*/
|
||||||
|
pending(query) {
|
||||||
|
query.where('pending', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
minAmount(query, minAmount) {
|
||||||
|
query.where('amount', '>=', minAmount);
|
||||||
|
},
|
||||||
|
|
||||||
|
maxAmount(query, maxAmount) {
|
||||||
|
query.where('amount', '<=', maxAmount);
|
||||||
|
},
|
||||||
|
|
||||||
|
toDate(query, toDate) {
|
||||||
|
const dateFormat = 'YYYY-MM-DD';
|
||||||
|
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||||
|
|
||||||
|
query.where('date', '<=', _toDate);
|
||||||
|
},
|
||||||
|
|
||||||
|
fromDate(query, fromDate) {
|
||||||
|
const dateFormat = 'YYYY-MM-DD';
|
||||||
|
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||||
|
|
||||||
|
query.where('date', '>=', _fromDate);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ export default class Ledger implements ILedger {
|
|||||||
|
|
||||||
transactionId: entry.referenceId,
|
transactionId: entry.referenceId,
|
||||||
transactionType: entry.referenceType,
|
transactionType: entry.referenceType,
|
||||||
|
transactionSubType: entry.transactionType,
|
||||||
|
|
||||||
transactionNumber: entry.transactionNumber,
|
transactionNumber: entry.transactionNumber,
|
||||||
referenceNumber: entry.referenceNumber,
|
referenceNumber: entry.referenceNumber,
|
||||||
@@ -262,6 +263,8 @@ export default class Ledger implements ILedger {
|
|||||||
|
|
||||||
taxRateId: entry.taxRateId,
|
taxRateId: entry.taxRateId,
|
||||||
taxRate: entry.taxRate,
|
taxRate: entry.taxRate,
|
||||||
|
|
||||||
|
note: entry.note,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export class AccountsApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new account.
|
* Creates a new account.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
* @returns {Promise<IAccount>}
|
* @returns {Promise<IAccount>}
|
||||||
*/
|
*/
|
||||||
public createAccount = (
|
public createAccount = (
|
||||||
@@ -108,8 +108,8 @@ export class AccountsApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the account details.
|
* Retrieves the account details.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} accountId
|
* @param {number} accountId
|
||||||
* @returns {Promise<IAccount>}
|
* @returns {Promise<IAccount>}
|
||||||
*/
|
*/
|
||||||
public getAccount = (tenantId: number, accountId: number) => {
|
public getAccount = (tenantId: number, accountId: number) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { AccountsApplication } from './AccountsApplication';
|
import { AccountsApplication } from './AccountsApplication';
|
||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||||
|
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AccountsExportable extends Exportable {
|
export class AccountsExportable extends Exportable {
|
||||||
@@ -20,7 +21,7 @@ export class AccountsExportable extends Exportable {
|
|||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
...query,
|
...query,
|
||||||
structure: IAccountsStructureType.Flat,
|
structure: IAccountsStructureType.Flat,
|
||||||
pageSize: 12000,
|
pageSize: EXPORT_SIZE_LIMIT,
|
||||||
page: 1,
|
page: 1,
|
||||||
} as IAccountsFilter;
|
} as IAccountsFilter;
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export class DeleteAccount {
|
|||||||
.throwIfNotFound()
|
.throwIfNotFound()
|
||||||
.queryAndThrowIfHasRelations({
|
.queryAndThrowIfHasRelations({
|
||||||
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
|
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
|
||||||
|
excludeRelations: ['uncategorizedTransactions', 'plaidItem']
|
||||||
});
|
});
|
||||||
// Authorize before delete account.
|
// Authorize before delete account.
|
||||||
await this.authorize(tenantId, accountId, oldAccount);
|
await this.authorize(tenantId, accountId, oldAccount);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
IPaymentReceiveCreatedPayload,
|
IPaymentReceivedCreatedPayload,
|
||||||
IPaymentReceiveCreatingPayload,
|
IPaymentReceivedCreatingPayload,
|
||||||
IPaymentReceiveDeletingPayload,
|
IPaymentReceivedDeletingPayload,
|
||||||
IPaymentReceiveEditedPayload,
|
IPaymentReceivedEditedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { LinkAttachment } from '../LinkAttachment';
|
import { LinkAttachment } from '../LinkAttachment';
|
||||||
@@ -50,13 +50,13 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the attachment keys on creating payment.
|
* Validates the attachment keys on creating payment.
|
||||||
* @param {IPaymentReceiveCreatingPayload}
|
* @param {IPaymentReceivedCreatingPayload}
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async validateAttachmentsOnPaymentCreate({
|
private async validateAttachmentsOnPaymentCreate({
|
||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
tenantId,
|
tenantId,
|
||||||
}: IPaymentReceiveCreatingPayload): Promise<void> {
|
}: IPaymentReceivedCreatingPayload): Promise<void> {
|
||||||
if (isEmpty(paymentReceiveDTO.attachments)) {
|
if (isEmpty(paymentReceiveDTO.attachments)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles linking the attachments of the created payment.
|
* Handles linking the attachments of the created payment.
|
||||||
* @param {IPaymentReceiveCreatedPayload}
|
* @param {IPaymentReceivedCreatedPayload}
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async handleAttachmentsOnPaymentCreated({
|
private async handleAttachmentsOnPaymentCreated({
|
||||||
@@ -75,7 +75,7 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveCreatedPayload): Promise<void> {
|
}: IPaymentReceivedCreatedPayload): Promise<void> {
|
||||||
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
||||||
|
|
||||||
const keys = paymentReceiveDTO.attachments?.map(
|
const keys = paymentReceiveDTO.attachments?.map(
|
||||||
@@ -92,14 +92,14 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles unlinking all the unpresented keys of the edited payment.
|
* Handles unlinking all the unpresented keys of the edited payment.
|
||||||
* @param {IPaymentReceiveEditedPayload}
|
* @param {IPaymentReceivedEditedPayload}
|
||||||
*/
|
*/
|
||||||
private async handleUnlinkUnpresentedKeysOnPaymentEdited({
|
private async handleUnlinkUnpresentedKeysOnPaymentEdited({
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveEditedPayload) {
|
}: IPaymentReceivedEditedPayload) {
|
||||||
const keys = paymentReceiveDTO.attachments?.map(
|
const keys = paymentReceiveDTO.attachments?.map(
|
||||||
(attachment) => attachment.key
|
(attachment) => attachment.key
|
||||||
);
|
);
|
||||||
@@ -114,7 +114,7 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles linking all the presented keys of the edited payment.
|
* Handles linking all the presented keys of the edited payment.
|
||||||
* @param {IPaymentReceiveEditedPayload}
|
* @param {IPaymentReceivedEditedPayload}
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async handleLinkPresentedKeysOnPaymentEdited({
|
private async handleLinkPresentedKeysOnPaymentEdited({
|
||||||
@@ -122,7 +122,7 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveEditedPayload) {
|
}: IPaymentReceivedEditedPayload) {
|
||||||
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
||||||
|
|
||||||
const keys = paymentReceiveDTO.attachments?.map(
|
const keys = paymentReceiveDTO.attachments?.map(
|
||||||
@@ -146,7 +146,7 @@ export class AttachmentsOnPaymentsReceived {
|
|||||||
tenantId,
|
tenantId,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveDeletingPayload) {
|
}: IPaymentReceivedDeletingPayload) {
|
||||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||||
tenantId,
|
tenantId,
|
||||||
'PaymentReceive',
|
'PaymentReceive',
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ export class GetAuthMeta {
|
|||||||
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||||
return {
|
return {
|
||||||
signupDisabled: config.signupRestrictions.disabled,
|
signupDisabled: config.signupRestrictions.disabled,
|
||||||
|
oneClickDemo: {
|
||||||
|
enable: config.oneClickDemoAccounts.enable,
|
||||||
|
demoUrl: config.oneClickDemoAccounts.demoUrl,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export class GetBankAccountSummary {
|
|||||||
q.withGraphJoined('matchedBankTransactions');
|
q.withGraphJoined('matchedBankTransactions');
|
||||||
q.whereNull('matchedBankTransactions.id');
|
q.whereNull('matchedBankTransactions.id');
|
||||||
|
|
||||||
|
// Exclude the pending transactions.
|
||||||
|
q.modify('notPending');
|
||||||
|
|
||||||
// Count the results.
|
// Count the results.
|
||||||
q.count('uncategorized_cashflow_transactions.id as total');
|
q.count('uncategorized_cashflow_transactions.id as total');
|
||||||
q.first();
|
q.first();
|
||||||
@@ -65,16 +68,32 @@ export class GetBankAccountSummary {
|
|||||||
q.withGraphJoined('recognizedTransaction');
|
q.withGraphJoined('recognizedTransaction');
|
||||||
q.whereNotNull('recognizedTransaction.id');
|
q.whereNotNull('recognizedTransaction.id');
|
||||||
|
|
||||||
|
// Exclude the pending transactions.
|
||||||
|
q.modify('notPending');
|
||||||
|
|
||||||
// Count the results.
|
// Count the results.
|
||||||
q.count('uncategorized_cashflow_transactions.id as total');
|
q.count('uncategorized_cashflow_transactions.id as total');
|
||||||
q.first();
|
q.first();
|
||||||
});
|
});
|
||||||
|
// Retrieves excluded transactions count.
|
||||||
const excludedTransactionsCount =
|
const excludedTransactionsCount =
|
||||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||||
q.where('accountId', bankAccountId);
|
q.where('accountId', bankAccountId);
|
||||||
q.modify('excluded');
|
q.modify('excluded');
|
||||||
|
|
||||||
|
// Exclude the pending transactions.
|
||||||
|
q.modify('notPending');
|
||||||
|
|
||||||
|
// Count the results.
|
||||||
|
q.count('uncategorized_cashflow_transactions.id as total');
|
||||||
|
q.first();
|
||||||
|
});
|
||||||
|
// Retrieves the pending transactions count.
|
||||||
|
const pendingTransactionsCount =
|
||||||
|
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||||
|
q.where('accountId', bankAccountId);
|
||||||
|
q.modify('pending');
|
||||||
|
|
||||||
// Count the results.
|
// Count the results.
|
||||||
q.count('uncategorized_cashflow_transactions.id as total');
|
q.count('uncategorized_cashflow_transactions.id as total');
|
||||||
q.first();
|
q.first();
|
||||||
@@ -83,14 +102,15 @@ export class GetBankAccountSummary {
|
|||||||
const totalUncategorizedTransactions =
|
const totalUncategorizedTransactions =
|
||||||
uncategorizedTranasctionsCount?.total || 0;
|
uncategorizedTranasctionsCount?.total || 0;
|
||||||
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
||||||
|
|
||||||
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
|
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
|
||||||
|
const totalPendingTransactions = pendingTransactionsCount?.total || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: bankAccount.name,
|
name: bankAccount.name,
|
||||||
totalUncategorizedTransactions,
|
totalUncategorizedTransactions,
|
||||||
totalRecognizedTransactions,
|
totalRecognizedTransactions,
|
||||||
totalExcludedTransactions,
|
totalExcludedTransactions,
|
||||||
|
totalPendingTransactions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { initialize } from 'objection';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { IAccountEventDeletePayload } from '@/interfaces';
|
||||||
|
import { DeleteBankRulesService } from '../../Rules/DeleteBankRules';
|
||||||
|
import { RevertRecognizedTransactions } from '../../RegonizeTranasctions/RevertRecognizedTransactions';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeleteUncategorizedTransactionsOnAccountDeleting {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private deleteBankRules: DeleteBankRulesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private revertRecognizedTransactins: RevertRecognizedTransactions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.accounts.onDelete,
|
||||||
|
this.handleDeleteBankRulesOnAccountDeleting.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles revert the recognized transactions and delete all the bank rules
|
||||||
|
* associated to the deleted bank account.
|
||||||
|
* @param {IAccountEventDeletePayload}
|
||||||
|
*/
|
||||||
|
private async handleDeleteBankRulesOnAccountDeleting({
|
||||||
|
tenantId,
|
||||||
|
oldAccount,
|
||||||
|
trx,
|
||||||
|
}: IAccountEventDeletePayload) {
|
||||||
|
const knex = this.tenancy.knex(tenantId);
|
||||||
|
const {
|
||||||
|
BankRule,
|
||||||
|
UncategorizedCashflowTransaction,
|
||||||
|
MatchedBankTransaction,
|
||||||
|
RecognizedBankTransaction,
|
||||||
|
} = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const foundAssociatedRules = await BankRule.query(trx).where(
|
||||||
|
'applyIfAccountId',
|
||||||
|
oldAccount.id
|
||||||
|
);
|
||||||
|
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
|
||||||
|
|
||||||
|
await initialize(knex, [
|
||||||
|
UncategorizedCashflowTransaction,
|
||||||
|
RecognizedBankTransaction,
|
||||||
|
MatchedBankTransaction,
|
||||||
|
]);
|
||||||
|
// Revert the recognized transactions of the given bank rules.
|
||||||
|
await this.revertRecognizedTransactins.revertRecognizedTransactions(
|
||||||
|
tenantId,
|
||||||
|
foundAssociatedRulesIds,
|
||||||
|
null,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
// Delete the associated uncategorized transactions.
|
||||||
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
|
.where('accountId', oldAccount.id)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete the given bank rules.
|
||||||
|
await this.deleteBankRules.deleteBankRules(
|
||||||
|
tenantId,
|
||||||
|
foundAssociatedRulesIds,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ export class DisconnectPlaidItemOnAccountDeleted {
|
|||||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
.findOne('plaidItemId', oldAccount.plaidItemId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
|
// Remove Plaid item once the transaction resolve.
|
||||||
if (oldPlaidItem) {
|
if (oldPlaidItem) {
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = PlaidClientWrapper.getClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import moment from 'moment';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { ExcludedBankTransactionsQuery } from './_types';
|
import { ExcludedBankTransactionsQuery } from './_types';
|
||||||
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
@@ -39,6 +40,18 @@ export class GetExcludedBankTransactionsService {
|
|||||||
if (_query.accountId) {
|
if (_query.accountId) {
|
||||||
q.where('account_id', _query.accountId);
|
q.where('account_id', _query.accountId);
|
||||||
}
|
}
|
||||||
|
if (_query.minDate) {
|
||||||
|
q.modify('fromDate', _query.minDate);
|
||||||
|
}
|
||||||
|
if (_query.maxDate) {
|
||||||
|
q.modify('toDate', _query.maxDate);
|
||||||
|
}
|
||||||
|
if (_query.minAmount) {
|
||||||
|
q.modify('minAmount', _query.minAmount);
|
||||||
|
}
|
||||||
|
if (_query.maxAmount) {
|
||||||
|
q.modify('maxAmount', _query.maxAmount);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
.pagination(_query.page - 1, _query.pageSize);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ export interface ExcludedBankTransactionsQuery {
|
|||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
minAmount?: number;
|
||||||
|
maxAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBankTransactionUnexcludingEventPayload {
|
export interface IBankTransactionUnexcludingEventPayload {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class GetMatchedTransactions {
|
|||||||
.whereIn('id', uncategorizedTransactionIds)
|
.whereIn('id', uncategorizedTransactionIds)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const totalPending = Math.abs(sumBy(uncategorizedTransactions, 'amount'));
|
const totalPending = sumBy(uncategorizedTransactions, 'amount');
|
||||||
|
|
||||||
const filtered = filter.transactionType
|
const filtered = filter.transactionType
|
||||||
? this.registered.filter((item) => item.type === filter.transactionType)
|
? this.registered.filter((item) => item.type === filter.transactionType)
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
import { initialize } from 'objection';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { first } from 'lodash';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
||||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
import {
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
GetMatchedTransactionsFilter,
|
||||||
|
IMatchTransactionDTO,
|
||||||
|
MatchedTransactionPOJO,
|
||||||
|
} from './types';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
|
import { CreateBillPayment } from '@/services/Purchases/BillPayments/CreateBillPayment';
|
||||||
|
import { IBillPaymentDTO } from '@/interfaces';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private transformer: TransformerInjectable;
|
private createPaymentMadeService: CreateBillPayment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the matched transactions.
|
* Retrieves the matched transactions.
|
||||||
@@ -71,4 +78,62 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
|
|||||||
new GetMatchedTransactionBillsTransformer()
|
new GetMatchedTransactionBillsTransformer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the common matched transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Array<number>} uncategorizedTransactionIds
|
||||||
|
* @param {IMatchTransactionDTO} matchTransactionDTO
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public async createMatchedTransaction(
|
||||||
|
tenantId: number,
|
||||||
|
uncategorizedTransactionIds: Array<number>,
|
||||||
|
matchTransactionDTO: IMatchTransactionDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
await super.createMatchedTransaction(
|
||||||
|
tenantId,
|
||||||
|
uncategorizedTransactionIds,
|
||||||
|
matchTransactionDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
const { Bill, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
||||||
|
this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
||||||
|
const uncategorizedTransaction =
|
||||||
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
|
.findById(uncategorizedTransactionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const bill = await Bill.query(trx)
|
||||||
|
.findById(matchTransactionDTO.referenceId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const createPaymentMadeDTO: IBillPaymentDTO = {
|
||||||
|
vendorId: bill.vendorId,
|
||||||
|
paymentAccountId: uncategorizedTransaction.accountId,
|
||||||
|
paymentDate: uncategorizedTransaction.date,
|
||||||
|
exchangeRate: 1,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
paymentAmount: bill.dueAmount,
|
||||||
|
billId: bill.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
branchId: bill.branchId,
|
||||||
|
};
|
||||||
|
// Create a new bill payment associated to the matched bill.
|
||||||
|
const billPayment = await this.createPaymentMadeService.createBillPayment(
|
||||||
|
tenantId,
|
||||||
|
createPaymentMadeDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
// Link the create bill payment with matched transaction.
|
||||||
|
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
||||||
|
referenceType: 'BillPayment',
|
||||||
|
referenceId: billPayment.id,
|
||||||
|
}, trx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
import { initialize } from 'objection';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { first } from 'lodash';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
||||||
import {
|
import {
|
||||||
GetMatchedTransactionsFilter,
|
GetMatchedTransactionsFilter,
|
||||||
|
IMatchTransactionDTO,
|
||||||
MatchedTransactionPOJO,
|
MatchedTransactionPOJO,
|
||||||
MatchedTransactionsPOJO,
|
MatchedTransactionsPOJO,
|
||||||
} from './types';
|
} from './types';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
|
import { CreatePaymentReceived } from '@/services/Sales/PaymentReceived/CreatePaymentReceived';
|
||||||
|
import { IPaymentReceivedCreateDTO } from '@/interfaces';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
|
||||||
@Inject()
|
@Inject()
|
||||||
protected tenancy: HasTenancyService;
|
protected transformer: TransformerInjectable;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected transformer: TransformerInjectable;
|
protected createPaymentReceivedService: CreatePaymentReceived;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the matched transactions.
|
* Retrieves the matched transactions.
|
||||||
@@ -78,4 +82,64 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
|
|||||||
new GetMatchedTransactionInvoicesTransformer()
|
new GetMatchedTransactionInvoicesTransformer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the common matched transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Array<number>} uncategorizedTransactionIds
|
||||||
|
* @param {IMatchTransactionDTO} matchTransactionDTO
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public async createMatchedTransaction(
|
||||||
|
tenantId: number,
|
||||||
|
uncategorizedTransactionIds: Array<number>,
|
||||||
|
matchTransactionDTO: IMatchTransactionDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
await super.createMatchedTransaction(
|
||||||
|
tenantId,
|
||||||
|
uncategorizedTransactionIds,
|
||||||
|
matchTransactionDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
const { SaleInvoice, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
||||||
|
this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
||||||
|
const uncategorizedTransaction =
|
||||||
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
|
.findById(uncategorizedTransactionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const invoice = await SaleInvoice.query(trx)
|
||||||
|
.findById(matchTransactionDTO.referenceId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const createPaymentReceivedDTO: IPaymentReceivedCreateDTO = {
|
||||||
|
customerId: invoice.customerId,
|
||||||
|
paymentDate: uncategorizedTransaction.date,
|
||||||
|
amount: invoice.dueAmount,
|
||||||
|
depositAccountId: uncategorizedTransaction.accountId,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
invoiceId: invoice.id,
|
||||||
|
paymentAmount: invoice.dueAmount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
branchId: invoice.branchId,
|
||||||
|
};
|
||||||
|
// Create a payment received associated to the matched invoice.
|
||||||
|
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
|
||||||
|
tenantId,
|
||||||
|
createPaymentReceivedDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
// Link the create payment received with matched invoice transaction.
|
||||||
|
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
||||||
|
referenceType: 'PaymentReceive',
|
||||||
|
referenceId: paymentReceived.id,
|
||||||
|
}, trx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import {
|
import {
|
||||||
IBillPaymentEventDeletedPayload,
|
IBillPaymentEventDeletedPayload,
|
||||||
IPaymentReceiveDeletedPayload,
|
IPaymentReceivedDeletedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
@@ -23,7 +23,7 @@ export class ValidateMatchingOnPaymentMadeDelete {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the payment made transaction whether matched with bank transaction on deleting.
|
* Validates the payment made transaction whether matched with bank transaction on deleting.
|
||||||
* @param {IPaymentReceiveDeletedPayload}
|
* @param {IPaymentReceivedDeletedPayload}
|
||||||
*/
|
*/
|
||||||
public async validateMatchingOnPaymentMadeDeleting({
|
public async validateMatchingOnPaymentMadeDeleting({
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { IPaymentReceiveDeletedPayload } from '@/interfaces';
|
import { IPaymentReceivedDeletedPayload } from '@/interfaces';
|
||||||
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@@ -20,13 +20,13 @@ export class ValidateMatchingOnPaymentReceivedDelete {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the payment received transaction whether matched with bank transaction on deleting.
|
* Validates the payment received transaction whether matched with bank transaction on deleting.
|
||||||
* @param {IPaymentReceiveDeletedPayload}
|
* @param {IPaymentReceivedDeletedPayload}
|
||||||
*/
|
*/
|
||||||
public async validateMatchingOnPaymentReceivedDeleting({
|
public async validateMatchingOnPaymentReceivedDeleting({
|
||||||
tenantId,
|
tenantId,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveDeletedPayload) {
|
}: IPaymentReceivedDeletedPayload) {
|
||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'PaymentReceive',
|
'PaymentReceive',
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { Knex } from 'knex';
|
|||||||
import uniqid from 'uniqid';
|
import uniqid from 'uniqid';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
|
import { RemovePendingUncategorizedTransaction } from '@/services/Cashflow/RemovePendingUncategorizedTransaction';
|
||||||
|
|
||||||
const CONCURRENCY_ASYNC = 10;
|
const CONCURRENCY_ASYNC = 10;
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ export class PlaidSyncDb {
|
|||||||
private cashflowApp: CashflowApplication;
|
private cashflowApp: CashflowApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
private removePendingTransaction: RemovePendingUncategorizedTransaction;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private eventPublisher: EventPublisher;
|
private eventPublisher: EventPublisher;
|
||||||
@@ -185,21 +186,22 @@ export class PlaidSyncDb {
|
|||||||
plaidTransactionsIds: string[],
|
plaidTransactionsIds: string[],
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const cashflowTransactions = await CashflowTransaction.query(trx).whereIn(
|
const uncategorizedTransactions =
|
||||||
'plaidTransactionId',
|
await UncategorizedCashflowTransaction.query(trx).whereIn(
|
||||||
plaidTransactionsIds
|
'plaidTransactionId',
|
||||||
);
|
plaidTransactionsIds
|
||||||
const cashflowTransactionsIds = cashflowTransactions.map(
|
);
|
||||||
|
const uncategorizedTransactionsIds = uncategorizedTransactions.map(
|
||||||
(trans) => trans.id
|
(trans) => trans.id
|
||||||
);
|
);
|
||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
cashflowTransactionsIds,
|
uncategorizedTransactionsIds,
|
||||||
(transactionId: number) =>
|
(uncategorizedTransactionId: number) =>
|
||||||
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
this.removePendingTransaction.removePendingTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionId,
|
uncategorizedTransactionId,
|
||||||
trx
|
trx
|
||||||
),
|
),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import { Knex } from 'knex';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
||||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||||
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PlaidUpdateTransactions {
|
export class PlaidUpdateTransactions {
|
||||||
@@ -19,9 +19,9 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} plaidItemId
|
* @param {number} plaidItemId - Plaid item id.
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
||||||
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
||||||
@@ -73,6 +73,12 @@ export class PlaidUpdateTransactions {
|
|||||||
item,
|
item,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
// Sync removed transactions.
|
||||||
|
await this.plaidSync.syncRemoveTransactions(
|
||||||
|
tenantId,
|
||||||
|
removed?.map((r) => r.transaction_id),
|
||||||
|
trx
|
||||||
|
);
|
||||||
// Sync bank account transactions.
|
// Sync bank account transactions.
|
||||||
await this.plaidSync.syncAccountsTransactions(
|
await this.plaidSync.syncAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import {
|
|||||||
Item as PlaidItem,
|
Item as PlaidItem,
|
||||||
Institution as PlaidInstitution,
|
Institution as PlaidInstitution,
|
||||||
AccountBase as PlaidAccount,
|
AccountBase as PlaidAccount,
|
||||||
|
TransactionBase as PlaidTransactionBase,
|
||||||
} from 'plaid';
|
} from 'plaid';
|
||||||
import {
|
import {
|
||||||
CreateUncategorizedTransactionDTO,
|
CreateUncategorizedTransactionDTO,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
PlaidTransaction,
|
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +48,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
|||||||
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||||
(
|
(
|
||||||
cashflowAccountId: number,
|
cashflowAccountId: number,
|
||||||
plaidTranasction: PlaidTransaction
|
plaidTranasction: PlaidTransactionBase
|
||||||
): CreateUncategorizedTransactionDTO => {
|
): CreateUncategorizedTransactionDTO => {
|
||||||
return {
|
return {
|
||||||
date: plaidTranasction.date,
|
date: plaidTranasction.date,
|
||||||
@@ -64,6 +64,8 @@ export const transformPlaidTrxsToCashflowCreate = R.curry(
|
|||||||
accountId: cashflowAccountId,
|
accountId: cashflowAccountId,
|
||||||
referenceNo: plaidTranasction.payment_meta?.reference_number,
|
referenceNo: plaidTranasction.payment_meta?.reference_number,
|
||||||
plaidTransactionId: plaidTranasction.transaction_id,
|
plaidTransactionId: plaidTranasction.transaction_id,
|
||||||
|
pending: plaidTranasction.pending,
|
||||||
|
pendingPlaidTransactionId: plaidTranasction.pending_transaction_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,17 +33,29 @@ const matchNumberCondition = (
|
|||||||
transaction: UncategorizedCashflowTransaction,
|
transaction: UncategorizedCashflowTransaction,
|
||||||
condition: IBankRuleCondition
|
condition: IBankRuleCondition
|
||||||
) => {
|
) => {
|
||||||
|
const conditionValue = parseFloat(condition.value);
|
||||||
|
const transactionAmount =
|
||||||
|
condition.field === 'amount'
|
||||||
|
? Math.abs(transaction[condition.field])
|
||||||
|
: (transaction[condition.field] as unknown as number);
|
||||||
|
|
||||||
switch (condition.comparator) {
|
switch (condition.comparator) {
|
||||||
case BankRuleConditionComparator.Equals:
|
case BankRuleConditionComparator.Equals:
|
||||||
return transaction[condition.field] === condition.value;
|
case BankRuleConditionComparator.Equal:
|
||||||
case BankRuleConditionComparator.Contains:
|
return transactionAmount === conditionValue;
|
||||||
return transaction[condition.field]
|
|
||||||
?.toString()
|
case BankRuleConditionComparator.BiggerOrEqual:
|
||||||
.includes(condition.value.toString());
|
return transactionAmount >= conditionValue;
|
||||||
case BankRuleConditionComparator.NotContain:
|
|
||||||
return !transaction[condition.field]
|
case BankRuleConditionComparator.Bigger:
|
||||||
?.toString()
|
return transactionAmount > conditionValue;
|
||||||
.includes(condition.value.toString());
|
|
||||||
|
case BankRuleConditionComparator.Smaller:
|
||||||
|
return transactionAmount < conditionValue;
|
||||||
|
|
||||||
|
case BankRuleConditionComparator.SmallerOrEqual:
|
||||||
|
return transactionAmount <= conditionValue;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -53,18 +65,19 @@ const matchTextCondition = (
|
|||||||
transaction: UncategorizedCashflowTransaction,
|
transaction: UncategorizedCashflowTransaction,
|
||||||
condition: IBankRuleCondition
|
condition: IBankRuleCondition
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
const transactionValue = transaction[condition.field] as string;
|
||||||
|
|
||||||
switch (condition.comparator) {
|
switch (condition.comparator) {
|
||||||
case BankRuleConditionComparator.Equals:
|
case BankRuleConditionComparator.Equals:
|
||||||
return transaction[condition.field] === condition.value;
|
case BankRuleConditionComparator.Equal:
|
||||||
|
return transactionValue === condition.value;
|
||||||
case BankRuleConditionComparator.Contains:
|
case BankRuleConditionComparator.Contains:
|
||||||
const fieldValue = lowerCase(transaction[condition.field]);
|
const fieldValue = lowerCase(transactionValue);
|
||||||
const conditionValue = lowerCase(condition.value);
|
const conditionValue = lowerCase(condition.value);
|
||||||
|
|
||||||
return fieldValue.includes(conditionValue);
|
return fieldValue.includes(conditionValue);
|
||||||
case BankRuleConditionComparator.NotContain:
|
case BankRuleConditionComparator.NotContain:
|
||||||
return !transaction[condition.field]?.includes(
|
return !transactionValue?.includes(condition.value.toString());
|
||||||
condition.value.toString()
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -101,8 +114,8 @@ const determineFieldType = (field: string): string => {
|
|||||||
case 'amount':
|
case 'amount':
|
||||||
return 'number';
|
return 'number';
|
||||||
case 'description':
|
case 'description':
|
||||||
return 'text';
|
case 'payee':
|
||||||
default:
|
default:
|
||||||
return 'unknown';
|
return 'text';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { isEqual, omit } from 'lodash';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import {
|
import {
|
||||||
IBankRuleEventCreatedPayload,
|
IBankRuleEventCreatedPayload,
|
||||||
@@ -55,10 +56,22 @@ export class TriggerRecognizedTransactions {
|
|||||||
private async recognizedTransactionsOnRuleEdited({
|
private async recognizedTransactionsOnRuleEdited({
|
||||||
tenantId,
|
tenantId,
|
||||||
editRuleDTO,
|
editRuleDTO,
|
||||||
|
oldBankRule,
|
||||||
|
bankRule,
|
||||||
ruleId,
|
ruleId,
|
||||||
}: IBankRuleEventEditedPayload) {
|
}: IBankRuleEventEditedPayload) {
|
||||||
const payload = { tenantId, ruleId };
|
const payload = { tenantId, ruleId };
|
||||||
|
|
||||||
|
// Cannot continue if the new and old bank rule values are the same,
|
||||||
|
// after excluding `createdAt` and `updatedAt` dates.
|
||||||
|
if (
|
||||||
|
isEqual(
|
||||||
|
omit(bankRule, ['createdAt', 'updatedAt']),
|
||||||
|
omit(oldBankRule, ['createdAt', 'updatedAt'])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this.agenda.now(
|
await this.agenda.now(
|
||||||
'rerecognize-uncategorized-transactions-job',
|
'rerecognize-uncategorized-transactions-job',
|
||||||
payload
|
payload
|
||||||
@@ -87,12 +100,14 @@ export class TriggerRecognizedTransactions {
|
|||||||
private async triggerRecognizeTransactionsOnImportCommitted({
|
private async triggerRecognizeTransactionsOnImportCommitted({
|
||||||
tenantId,
|
tenantId,
|
||||||
importId,
|
importId,
|
||||||
meta,
|
|
||||||
}: IImportFileCommitedEventPayload) {
|
}: IImportFileCommitedEventPayload) {
|
||||||
const importFile = await Import.query().findOne({ importId });
|
const importFile = await Import.query().findOne({ importId });
|
||||||
const batch = importFile.paramsParsed.batch;
|
const batch = importFile.paramsParsed.batch;
|
||||||
const payload = { tenantId, transactionsCriteria: { batch } };
|
const payload = { tenantId, transactionsCriteria: { batch } };
|
||||||
|
|
||||||
|
// Cannot continue if the imported resource is not bank account transactions.
|
||||||
|
if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
|
||||||
|
|
||||||
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,31 +26,39 @@ export class DeleteBankRuleSerivce {
|
|||||||
* @param {number} ruleId
|
* @param {number} ruleId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async deleteBankRule(tenantId: number, ruleId: number): Promise<void> {
|
public async deleteBankRule(
|
||||||
|
tenantId: number,
|
||||||
|
ruleId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const oldBankRule = await BankRule.query()
|
const oldBankRule = await BankRule.query()
|
||||||
.findById(ruleId)
|
.findById(ruleId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onBankRuleDeleting` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onBankRuleDeleting` event.
|
||||||
oldBankRule,
|
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
||||||
ruleId,
|
tenantId,
|
||||||
trx,
|
oldBankRule,
|
||||||
} as IBankRuleEventDeletingPayload);
|
ruleId,
|
||||||
|
trx,
|
||||||
|
} as IBankRuleEventDeletingPayload);
|
||||||
|
|
||||||
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete();
|
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete()
|
||||||
await BankRule.query(trx).findById(ruleId).delete();
|
await BankRule.query(trx).findById(ruleId).delete();
|
||||||
|
|
||||||
// Triggers `onBankRuleDeleted` event.
|
// Triggers `onBankRuleDeleted` event.
|
||||||
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
ruleId,
|
ruleId,
|
||||||
trx,
|
trx,
|
||||||
} as IBankRuleEventDeletedPayload);
|
} as IBankRuleEventDeletedPayload);
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import PromisePool from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteBankRuleSerivce } from './DeleteBankRule';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeleteBankRulesService {
|
||||||
|
@Inject()
|
||||||
|
private deleteBankRuleService: DeleteBankRuleSerivce;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete bank rules.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number | Array<number>} bankRuleId
|
||||||
|
*/
|
||||||
|
async deleteBankRules(
|
||||||
|
tenantId: number,
|
||||||
|
bankRuleId: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
const bankRulesIds = uniq(castArray(bankRuleId));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(bankRulesIds)
|
||||||
|
.process(async (bankRuleId: number) => {
|
||||||
|
await this.deleteBankRuleService.deleteBankRule(
|
||||||
|
tenantId,
|
||||||
|
bankRuleId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ export class EditBankRuleService {
|
|||||||
|
|
||||||
const oldBankRule = await BankRule.query()
|
const oldBankRule = await BankRule.query()
|
||||||
.findById(ruleId)
|
.findById(ruleId)
|
||||||
|
.withGraphFetched('conditions')
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const tranformDTO = this.transformDTO(editRuleDTO);
|
const tranformDTO = this.transformDTO(editRuleDTO);
|
||||||
@@ -64,15 +65,15 @@ export class EditBankRuleService {
|
|||||||
} as IBankRuleEventEditingPayload);
|
} as IBankRuleEventEditingPayload);
|
||||||
|
|
||||||
// Updates the given bank rule.
|
// Updates the given bank rule.
|
||||||
await BankRule.query(trx).upsertGraphAndFetch({
|
const bankRule = await BankRule.query(trx).upsertGraphAndFetch({
|
||||||
...tranformDTO,
|
...tranformDTO,
|
||||||
id: ruleId,
|
id: ruleId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Triggers `onBankRuleEdited` event.
|
// Triggers `onBankRuleEdited` event.
|
||||||
await this.eventPublisher.emitAsync(events.bankRules.onEdited, {
|
await this.eventPublisher.emitAsync(events.bankRules.onEdited, {
|
||||||
tenantId,
|
tenantId,
|
||||||
oldBankRule,
|
oldBankRule,
|
||||||
|
bankRule,
|
||||||
ruleId,
|
ruleId,
|
||||||
editRuleDTO,
|
editRuleDTO,
|
||||||
trx,
|
trx,
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
export enum BankRuleConditionField {
|
export enum BankRuleConditionField {
|
||||||
Amount = 'Amount',
|
Amount = 'amount',
|
||||||
Description = 'Description',
|
Description = 'description',
|
||||||
Payee = 'Payee',
|
Payee = 'payee',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BankRuleConditionComparator {
|
export enum BankRuleConditionComparator {
|
||||||
Contains = 'contains',
|
Contains = 'contains',
|
||||||
Equals = 'equals',
|
Equals = 'equals',
|
||||||
|
Equal = 'equal',
|
||||||
NotContain = 'not_contain',
|
NotContain = 'not_contain',
|
||||||
|
Bigger = 'bigger',
|
||||||
|
BiggerOrEqual = 'bigger_or_equal',
|
||||||
|
Smaller = 'smaller',
|
||||||
|
SmallerOrEqual = 'smaller_or_equal',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBankRuleCondition {
|
export interface IBankRuleCondition {
|
||||||
@@ -56,7 +61,15 @@ export enum BankRuleAssignCategory {
|
|||||||
export interface IBankRuleConditionDTO {
|
export interface IBankRuleConditionDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
field: string;
|
field: string;
|
||||||
comparator: string;
|
comparator:
|
||||||
|
| 'contains'
|
||||||
|
| 'equals'
|
||||||
|
| 'not_contains'
|
||||||
|
| 'equal'
|
||||||
|
| 'bigger'
|
||||||
|
| 'bigger_or_equal'
|
||||||
|
| 'smaller'
|
||||||
|
| 'smaller_or_equal';
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +112,8 @@ export interface IBankRuleEventEditingPayload {
|
|||||||
export interface IBankRuleEventEditedPayload {
|
export interface IBankRuleEventEditedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
ruleId: number;
|
ruleId: number;
|
||||||
|
oldBankRule: IBankRule;
|
||||||
|
bankRule: IBankRule;
|
||||||
editRuleDTO: IEditBankRuleDTO;
|
editRuleDTO: IEditBankRuleDTO;
|
||||||
trx?: Knex.Transaction;
|
trx?: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import {
|
import {
|
||||||
IPaymentReceiveCreatingPayload,
|
IPaymentReceivedCreatingPayload,
|
||||||
IPaymentReceiveEditingPayload,
|
IPaymentReceivedEditingPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { ValidateBranchExistance } from '../../Integrations/ValidateBranchExistance';
|
import { ValidateBranchExistance } from '../../Integrations/ValidateBranchExistance';
|
||||||
|
|
||||||
@@ -28,12 +28,12 @@ export class PaymentReceiveBranchValidateSubscriber {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate branch existance on estimate creating.
|
* Validate branch existance on estimate creating.
|
||||||
* @param {IPaymentReceiveCreatingPayload} payload
|
* @param {IPaymentReceivedCreatingPayload} payload
|
||||||
*/
|
*/
|
||||||
private validateBranchExistanceOnPaymentCreating = async ({
|
private validateBranchExistanceOnPaymentCreating = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
}: IPaymentReceiveCreatingPayload) => {
|
}: IPaymentReceivedCreatingPayload) => {
|
||||||
await this.validateBranchExistance.validateTransactionBranchWhenActive(
|
await this.validateBranchExistance.validateTransactionBranchWhenActive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveDTO.branchId
|
paymentReceiveDTO.branchId
|
||||||
@@ -42,12 +42,12 @@ export class PaymentReceiveBranchValidateSubscriber {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate branch existance once estimate editing.
|
* Validate branch existance once estimate editing.
|
||||||
* @param {IPaymentReceiveEditingPayload} payload
|
* @param {IPaymentReceivedEditingPayload} payload
|
||||||
*/
|
*/
|
||||||
private validateBranchExistanceOnPaymentEditing = async ({
|
private validateBranchExistanceOnPaymentEditing = async ({
|
||||||
paymentReceiveDTO,
|
paymentReceiveDTO,
|
||||||
tenantId,
|
tenantId,
|
||||||
}: IPaymentReceiveEditingPayload) => {
|
}: IPaymentReceivedEditingPayload) => {
|
||||||
await this.validateBranchExistance.validateTransactionBranchWhenActive(
|
await this.validateBranchExistance.validateTransactionBranchWhenActive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveDTO.branchId
|
paymentReceiveDTO.branchId
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return ['formattedAmount'];
|
return [
|
||||||
|
'formattedAmount',
|
||||||
|
'lastFeedsUpdatedAt',
|
||||||
|
'lastFeedsUpdatedAtFormatted',
|
||||||
|
'lastFeedsUpdatedFromNow',
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +34,7 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted account amount.
|
* Retrieve formatted account amount.
|
||||||
* @param {IAccount} invoice
|
* @param {IAccount} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedAmount = (account: IAccount): string => {
|
protected formattedAmount = (account: IAccount): string => {
|
||||||
@@ -37,4 +42,22 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
currencyCode: account.currencyCode,
|
currencyCode: account.currencyCode,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the last feeds update at formatted date.
|
||||||
|
* @param {IAccount} account
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected lastFeedsUpdatedAtFormatted(account: IAccount): string {
|
||||||
|
return this.formatDate(account.lastFeedsUpdatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the last feeds updated from now.
|
||||||
|
* @param {IAccount} account
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected lastFeedsUpdatedFromNow(account: IAccount): string {
|
||||||
|
return this.formatDateFromNow(account.lastFeedsUpdatedAt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import GetCashflowAccountsService from './GetCashflowAccountsService';
|
|||||||
import { GetCashflowTransactionService } from './GetCashflowTransactionsService';
|
import { GetCashflowTransactionService } from './GetCashflowTransactionsService';
|
||||||
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
||||||
import { GetRecognizedTransactionService } from './GetRecognizedTransaction';
|
import { GetRecognizedTransactionService } from './GetRecognizedTransaction';
|
||||||
|
import { UncategorizeCashflowTransactionsBulk } from './UncategorizeCashflowTransactionsBulk';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CashflowApplication {
|
export class CashflowApplication {
|
||||||
@@ -39,6 +40,9 @@ export class CashflowApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private uncategorizeTransactionService: UncategorizeCashflowTransaction;
|
private uncategorizeTransactionService: UncategorizeCashflowTransaction;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uncategorizeTransasctionsService: UncategorizeCashflowTransactionsBulk;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private categorizeTransactionService: CategorizeCashflowTransaction;
|
private categorizeTransactionService: CategorizeCashflowTransaction;
|
||||||
|
|
||||||
@@ -155,6 +159,22 @@ export class CashflowApplication {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncategorize the given transactions in bulk.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number | Array<number>} transactionId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public uncategorizeTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
transactionId: number | Array<number>
|
||||||
|
) {
|
||||||
|
return this.uncategorizeTransasctionsService.uncategorizeBulk(
|
||||||
|
tenantId,
|
||||||
|
transactionId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Categorize the given cashflow transaction.
|
* Categorize the given cashflow transaction.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Inject } from 'typedi';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { GetPendingBankAccountTransactionTransformer } from './GetPendingBankAccountTransactionTransformer';
|
||||||
|
|
||||||
|
export class GetPendingBankAccountTransactions {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the given bank accounts pending transaction.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {GetPendingTransactionsQuery} filter - Pending transactions query.
|
||||||
|
*/
|
||||||
|
async getPendingTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
filter?: GetPendingTransactionsQuery
|
||||||
|
) {
|
||||||
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const _filter = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
...filter,
|
||||||
|
};
|
||||||
|
const { results, pagination } =
|
||||||
|
await UncategorizedCashflowTransaction.query()
|
||||||
|
.onBuild((q) => {
|
||||||
|
q.modify('pending');
|
||||||
|
|
||||||
|
if (_filter?.accountId) {
|
||||||
|
q.where('accountId', _filter.accountId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.pagination(_filter.page - 1, _filter.pageSize);
|
||||||
|
|
||||||
|
const data = await this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
results,
|
||||||
|
new GetPendingBankAccountTransactionTransformer()
|
||||||
|
);
|
||||||
|
return { data, pagination };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetPendingTransactionsQuery {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
accountId?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
|
import { formatNumber } from '@/utils';
|
||||||
|
|
||||||
|
export class GetPendingBankAccountTransactionTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to sale invoice object.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'formattedAmount',
|
||||||
|
'formattedDate',
|
||||||
|
'formattedDepositAmount',
|
||||||
|
'formattedWithdrawalAmount',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude all attributes.
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formattes the transaction date.
|
||||||
|
* @param transaction
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public formattedDate(transaction) {
|
||||||
|
return this.formatDate(transaction.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted amount.
|
||||||
|
* @param transaction
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public formattedAmount(transaction) {
|
||||||
|
return formatNumber(transaction.amount, {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted deposit amount.
|
||||||
|
* @param transaction
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedDepositAmount(transaction) {
|
||||||
|
if (transaction.isDepositTransaction) {
|
||||||
|
return formatNumber(transaction.deposit, {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted withdrawal amount.
|
||||||
|
* @param transaction
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedWithdrawalAmount(transaction) {
|
||||||
|
if (transaction.isWithdrawalTransaction) {
|
||||||
|
return formatNumber(transaction.withdrawal, {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ export class GetRecognizedTransactionsService {
|
|||||||
) {
|
) {
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const _filter = {
|
const _query = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...filter,
|
...filter,
|
||||||
@@ -34,13 +34,33 @@ export class GetRecognizedTransactionsService {
|
|||||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||||
q.withGraphFetched('recognizedTransaction.bankRule');
|
q.withGraphFetched('recognizedTransaction.bankRule');
|
||||||
q.whereNotNull('recognizedTransactionId');
|
q.whereNotNull('recognizedTransactionId');
|
||||||
|
|
||||||
|
// Exclude the excluded transactions.
|
||||||
q.modify('notExcluded');
|
q.modify('notExcluded');
|
||||||
|
|
||||||
if (_filter.accountId) {
|
// Exclude the pending transactions.
|
||||||
q.where('accountId', _filter.accountId);
|
q.modify('notPending');
|
||||||
|
|
||||||
|
if (_query.accountId) {
|
||||||
|
q.where('accountId', _query.accountId);
|
||||||
|
}
|
||||||
|
if (_query.minDate) {
|
||||||
|
q.modify('fromDate', _query.minDate);
|
||||||
|
}
|
||||||
|
if (_query.maxDate) {
|
||||||
|
q.modify('toDate', _query.maxDate);
|
||||||
|
}
|
||||||
|
if (_query.minAmount) {
|
||||||
|
q.modify('minAmount', _query.minAmount);
|
||||||
|
}
|
||||||
|
if (_query.maxAmount) {
|
||||||
|
q.modify('maxAmount', _query.maxAmount);
|
||||||
|
}
|
||||||
|
if (_query.accountId) {
|
||||||
|
q.where('accountId', _query.accountId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pagination(_filter.page - 1, _filter.pageSize);
|
.pagination(_query.page - 1, _query.pageSize);
|
||||||
|
|
||||||
const data = await this.transformer.transform(
|
const data = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export class GetUncategorizedTransactions {
|
|||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.where('accountId', accountId);
|
q.where('accountId', accountId);
|
||||||
q.where('categorized', false);
|
q.where('categorized', false);
|
||||||
|
|
||||||
q.modify('notExcluded');
|
q.modify('notExcluded');
|
||||||
|
q.modify('notPending');
|
||||||
|
|
||||||
q.withGraphFetched('account');
|
q.withGraphFetched('account');
|
||||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||||
@@ -60,6 +62,19 @@ export class GetUncategorizedTransactions {
|
|||||||
|
|
||||||
q.whereNull('matchedBankTransactions.id');
|
q.whereNull('matchedBankTransactions.id');
|
||||||
q.orderBy('date', 'DESC');
|
q.orderBy('date', 'DESC');
|
||||||
|
|
||||||
|
if (_query.minDate) {
|
||||||
|
q.modify('fromDate', _query.minDate);
|
||||||
|
}
|
||||||
|
if (_query.maxDate) {
|
||||||
|
q.modify('toDate', _query.maxDate);
|
||||||
|
}
|
||||||
|
if (_query.minAmount) {
|
||||||
|
q.modify('minAmount', _query.minAmount);
|
||||||
|
}
|
||||||
|
if (_query.maxAmount) {
|
||||||
|
q.modify('maxAmount', _query.maxAmount);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
.pagination(_query.page - 1, _query.pageSize);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '../UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import {
|
||||||
|
IPendingTransactionRemovedEventPayload,
|
||||||
|
IPendingTransactionRemovingEventPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class RemovePendingUncategorizedTransaction {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REmoves the pending uncategorized transaction.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} uncategorizedTransactionId -
|
||||||
|
* @param {Knex.Transaction} trx -
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async removePendingTransaction(
|
||||||
|
tenantId: number,
|
||||||
|
uncategorizedTransactionId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const pendingTransaction = await UncategorizedCashflowTransaction.query(trx)
|
||||||
|
.findById(uncategorizedTransactionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
if (!pendingTransaction.isPending) {
|
||||||
|
throw new ServiceError(ERRORS.TRANSACTION_NOT_PENDING);
|
||||||
|
}
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.bankTransactions.onPendingRemoving,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
uncategorizedTransactionId,
|
||||||
|
pendingTransaction,
|
||||||
|
trx,
|
||||||
|
} as IPendingTransactionRemovingEventPayload
|
||||||
|
);
|
||||||
|
// Removes the pending uncategorized transaction.
|
||||||
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
|
.findById(uncategorizedTransactionId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.bankTransactions.onPendingRemoved,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
uncategorizedTransactionId,
|
||||||
|
pendingTransaction,
|
||||||
|
trx,
|
||||||
|
} as IPendingTransactionRemovedEventPayload
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import PromisePool from '@supercharge/promise-pool';
|
||||||
|
import { castArray } from 'lodash';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class UncategorizeCashflowTransactionsBulk {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uncategorizeTransaction: UncategorizeCashflowTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncategorize the given bank transactions in bulk.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} uncategorizedTransactionId
|
||||||
|
*/
|
||||||
|
public async uncategorizeBulk(
|
||||||
|
tenantId: number,
|
||||||
|
uncategorizedTransactionId: number | Array<number>
|
||||||
|
) {
|
||||||
|
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||||
|
|
||||||
|
const result = await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||||
|
.for(uncategorizedTransactionIds)
|
||||||
|
.process(async (_uncategorizedTransactionId: number, index, pool) => {
|
||||||
|
await this.uncategorizeTransaction.uncategorize(
|
||||||
|
tenantId,
|
||||||
|
_uncategorizedTransactionId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIGRATION_CONCURRENCY = 1;
|
||||||
@@ -87,7 +87,7 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the import params before storing them.
|
* Transforms the import params before storing them.
|
||||||
* @param {Record<string, any>} parmas
|
* @param {Record<string, any>} parmas
|
||||||
*/
|
*/
|
||||||
public transformParams(parmas: Record<string, any>) {
|
public transformParams(parmas: Record<string, any>) {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export const ERRORS = {
|
|||||||
'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
|
'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
|
||||||
CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED:
|
CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED:
|
||||||
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
|
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
|
||||||
|
CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION:
|
||||||
CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION: 'CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION',
|
'CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION',
|
||||||
TRANSACTION_NOT_CATEGORIZED: 'TRANSACTION_NOT_CATEGORIZED'
|
TRANSACTION_NOT_CATEGORIZED: 'TRANSACTION_NOT_CATEGORIZED',
|
||||||
|
TRANSACTION_NOT_PENDING: 'TRANSACTION_NOT_PENDING',
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum CASHFLOW_DIRECTION {
|
export enum CASHFLOW_DIRECTION {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import PromisePool from '@supercharge/promise-pool';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import {
|
||||||
ICashflowTransactionCategorizedPayload,
|
ICashflowTransactionCategorizedPayload,
|
||||||
ICashflowTransactionUncategorizedPayload,
|
ICashflowTransactionUncategorizedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import PromisePool from '@supercharge/promise-pool';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class DecrementUncategorizedTransactionOnCategorize {
|
export class DecrementUncategorizedTransactionOnCategorize {
|
||||||
@@ -36,13 +36,17 @@ export class DecrementUncategorizedTransactionOnCategorize {
|
|||||||
public async decrementUnCategorizedTransactionsOnCategorized({
|
public async decrementUnCategorizedTransactionsOnCategorized({
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategorizedTransactions,
|
uncategorizedTransactions,
|
||||||
trx
|
trx,
|
||||||
}: ICashflowTransactionCategorizedPayload) {
|
}: ICashflowTransactionCategorizedPayload) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await PromisePool.withConcurrency(1)
|
await PromisePool.withConcurrency(1)
|
||||||
.for(uncategorizedTransactions)
|
.for(uncategorizedTransactions)
|
||||||
.process(async (uncategorizedTransaction) => {
|
.process(async (uncategorizedTransaction) => {
|
||||||
|
// Cannot continue if the transaction is still pending.
|
||||||
|
if (uncategorizedTransaction.isPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await Account.query(trx)
|
await Account.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.decrement('uncategorizedTransactions', 1);
|
.decrement('uncategorizedTransactions', 1);
|
||||||
@@ -56,13 +60,17 @@ export class DecrementUncategorizedTransactionOnCategorize {
|
|||||||
public async incrementUnCategorizedTransactionsOnUncategorized({
|
public async incrementUnCategorizedTransactionsOnUncategorized({
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategorizedTransactions,
|
uncategorizedTransactions,
|
||||||
trx
|
trx,
|
||||||
}: ICashflowTransactionUncategorizedPayload) {
|
}: ICashflowTransactionUncategorizedPayload) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await PromisePool.withConcurrency(1)
|
await PromisePool.withConcurrency(1)
|
||||||
.for(uncategorizedTransactions)
|
.for(uncategorizedTransactions)
|
||||||
.process(async (uncategorizedTransaction) => {
|
.process(async (uncategorizedTransaction) => {
|
||||||
|
// Cannot continue if the transaction is still pending.
|
||||||
|
if (uncategorizedTransaction.isPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await Account.query(trx)
|
await Account.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
@@ -82,6 +90,9 @@ export class DecrementUncategorizedTransactionOnCategorize {
|
|||||||
|
|
||||||
if (!uncategorizedTransaction.accountId) return;
|
if (!uncategorizedTransaction.accountId) return;
|
||||||
|
|
||||||
|
// Cannot continue if the transaction is still pending.
|
||||||
|
if (uncategorizedTransaction.isPending) return;
|
||||||
|
|
||||||
await Account.query(trx)
|
await Account.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IItemsFilter } from '@/interfaces';
|
import { IItemsFilter } from '@/interfaces';
|
||||||
import { CustomersApplication } from './CustomersApplication';
|
import { CustomersApplication } from './CustomersApplication';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
|
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CustomersExportable extends Exportable {
|
export class CustomersExportable extends Exportable {
|
||||||
@@ -17,9 +18,9 @@ export class CustomersExportable extends Exportable {
|
|||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
|
||||||
...query,
|
...query,
|
||||||
pageSize: 12,
|
page: 1,
|
||||||
|
pageSize: EXPORT_SIZE_LIMIT,
|
||||||
} as IItemsFilter;
|
} as IItemsFilter;
|
||||||
|
|
||||||
return this.customersApplication
|
return this.customersApplication
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IItemsFilter } from '@/interfaces';
|
import { IItemsFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { VendorsApplication } from './VendorsApplication';
|
import { VendorsApplication } from './VendorsApplication';
|
||||||
|
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class VendorsExportable extends Exportable {
|
export class VendorsExportable extends Exportable {
|
||||||
@@ -17,9 +18,9 @@ export class VendorsExportable extends Exportable {
|
|||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
|
||||||
...query,
|
...query,
|
||||||
pageSize: 12,
|
page: 1,
|
||||||
|
pageSize: EXPORT_SIZE_LIMIT,
|
||||||
} as IItemsFilter;
|
} as IItemsFilter;
|
||||||
|
|
||||||
return this.vendorsApplication
|
return this.vendorsApplication
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import { PaymentReceiveValidators } from '../Sales/PaymentReceives/PaymentReceiveValidators';
|
import { PaymentReceivedValidators } from '../Sales/PaymentReceived/PaymentReceivedValidators';
|
||||||
import BaseCreditNotes from './CreditNotes';
|
import BaseCreditNotes from './CreditNotes';
|
||||||
import {
|
import {
|
||||||
IApplyCreditToInvoicesDTO,
|
IApplyCreditToInvoicesDTO,
|
||||||
@@ -26,7 +26,7 @@ export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
|||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private paymentReceiveValidators: PaymentReceiveValidators;
|
private paymentReceiveValidators: PaymentReceivedValidators;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private uow: UnitOfWork;
|
private uow: UnitOfWork;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
'formattedAllocatedCostAmount',
|
'formattedAllocatedCostAmount',
|
||||||
'formattedDate',
|
'formattedDate',
|
||||||
'formattedCreatedAt',
|
'formattedCreatedAt',
|
||||||
|
'formattedPublishedAt',
|
||||||
'categories',
|
'categories',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -91,4 +92,13 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
protected attachments = (expense: IExpense) => {
|
protected attachments = (expense: IExpense) => {
|
||||||
return this.item(expense.attachments, new AttachmentTransformer());
|
return this.item(expense.attachments, new AttachmentTransformer());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted published at date.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedPublishedAt = (expense: IExpense): string => {
|
||||||
|
return this.formatDate(expense.publishedAt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IExpensesFilter } from '@/interfaces';
|
import { IExpensesFilter } from '@/interfaces';
|
||||||
import { ExpensesApplication } from './ExpensesApplication';
|
import { ExpensesApplication } from './ExpensesApplication';
|
||||||
|
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExpensesExportable extends Exportable {
|
export class ExpensesExportable extends Exportable {
|
||||||
@@ -19,7 +20,7 @@ export class ExpensesExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12000,
|
pageSize: EXPORT_SIZE_LIMIT,
|
||||||
} as IExpensesFilter;
|
} as IExpensesFilter;
|
||||||
|
|
||||||
return this.expensesApplication
|
return this.expensesApplication
|
||||||
|
|||||||
48
packages/server/src/services/Export/ExportAls.ts
Normal file
48
packages/server/src/services/Export/ExportAls.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import { AsyncLocalStorage } from 'async_hooks';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExportAls {
|
||||||
|
private als: AsyncLocalStorage<Map<string, any>>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.als = new AsyncLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a callback function within the context of a new AsyncLocalStorage store.
|
||||||
|
* @param callback The function to be executed within the AsyncLocalStorage context.
|
||||||
|
* @returns The result of the callback function.
|
||||||
|
*/
|
||||||
|
public run<T>(callback: () => T): T {
|
||||||
|
return this.als.run<T>(new Map(), () => {
|
||||||
|
this.markAsExport();
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current AsyncLocalStorage store.
|
||||||
|
* @returns The current store or undefined if not in a valid context.
|
||||||
|
*/
|
||||||
|
public getStore(): Map<string, any> | undefined {
|
||||||
|
return this.als.getStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current context as an export operation.
|
||||||
|
* @param flag Boolean flag to set or unset the export status. Defaults to true.
|
||||||
|
*/
|
||||||
|
public markAsExport(flag: boolean = true): void {
|
||||||
|
const store = this.getStore();
|
||||||
|
store?.set('isExport', flag);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the current context is an export operation.
|
||||||
|
* @returns {boolean} True if the context is an export operation, false otherwise.
|
||||||
|
*/
|
||||||
|
public get isExport(): boolean {
|
||||||
|
return !!this.getStore()?.get('isExport');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,13 @@ import { SaleInvoicesExportable } from '../Sales/Invoices/SaleInvoicesExportable
|
|||||||
import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable';
|
import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable';
|
||||||
import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable';
|
import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable';
|
||||||
import { BillsExportable } from '../Purchases/Bills/BillsExportable';
|
import { BillsExportable } from '../Purchases/Bills/BillsExportable';
|
||||||
import { PaymentsReceivedExportable } from '../Sales/PaymentReceives/PaymentsReceivedExportable';
|
import { PaymentsReceivedExportable } from '../Sales/PaymentReceived/PaymentsReceivedExportable';
|
||||||
import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable';
|
import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable';
|
||||||
import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable';
|
import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable';
|
||||||
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
|
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
|
||||||
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
|
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
|
||||||
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
|
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
|
||||||
|
import { TaxRatesExportable } from '../TaxRates/TaxRatesExportable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExportableResources {
|
export class ExportableResources {
|
||||||
@@ -46,6 +47,7 @@ export class ExportableResources {
|
|||||||
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
|
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
|
||||||
{ resource: 'CreditNote', exportable: CreditNotesExportable },
|
{ resource: 'CreditNote', exportable: CreditNotesExportable },
|
||||||
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
|
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
|
||||||
|
{ resource: 'TaxRate', exportable: TaxRatesExportable },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Errors, ExportFormat } from './common';
|
|||||||
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
|
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
|
||||||
import { flatDataCollections, getDataAccessor } from './utils';
|
import { flatDataCollections, getDataAccessor } from './utils';
|
||||||
import { ExportPdf } from './ExportPdf';
|
import { ExportPdf } from './ExportPdf';
|
||||||
|
import { ExportAls } from './ExportAls';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExportResourceService {
|
export class ExportResourceService {
|
||||||
@@ -22,13 +23,33 @@ export class ExportResourceService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private exportPdf: ExportPdf;
|
private exportPdf: ExportPdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private exportAls: ExportAls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {string} resourceName
|
||||||
|
* @param {ExportFormat} format
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async export(
|
||||||
|
tenantId: number,
|
||||||
|
resourceName: string,
|
||||||
|
format: ExportFormat = ExportFormat.Csv
|
||||||
|
) {
|
||||||
|
return this.exportAls.run(() =>
|
||||||
|
this.exportAlsRun(tenantId, resourceName, format)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the given resource data through csv, xlsx or pdf.
|
* Exports the given resource data through csv, xlsx or pdf.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {string} resourceName - Resource name.
|
* @param {string} resourceName - Resource name.
|
||||||
* @param {ExportFormat} format - File format.
|
* @param {ExportFormat} format - File format.
|
||||||
*/
|
*/
|
||||||
public async export(
|
public async exportAlsRun(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
format: ExportFormat = ExportFormat.Csv
|
format: ExportFormat = ExportFormat.Csv
|
||||||
|
|||||||
2
packages/server/src/services/Export/constants.ts
Normal file
2
packages/server/src/services/Export/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const EXPORT_SIZE_LIMIT = 9999999;
|
||||||
|
export const EXPORT_DTE_FORMAT = 'YYYY-MM-DD';
|
||||||
@@ -15,6 +15,7 @@ import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
|||||||
import { flatToNestedArray } from '@/utils';
|
import { flatToNestedArray } from '@/utils';
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import { calculateRunningBalance } from './_utils';
|
import { calculateRunningBalance } from './_utils';
|
||||||
|
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet.
|
* General ledger sheet.
|
||||||
@@ -90,11 +91,13 @@ export default class GeneralLedgerSheet extends R.compose(
|
|||||||
date: entry.date,
|
date: entry.date,
|
||||||
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||||
|
|
||||||
transactionNumber: entry.transactionNumber,
|
referenceType: entry.transactionType,
|
||||||
referenceType: entry.referenceType,
|
referenceId: entry.transactionId,
|
||||||
referenceId: entry.referenceId,
|
|
||||||
referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted),
|
|
||||||
|
|
||||||
|
transactionNumber: entry.transactionNumber,
|
||||||
|
transactionTypeFormatted: this.i18n.__(
|
||||||
|
getTransactionTypeLabel(entry.transactionType, entry.transactionSubType)
|
||||||
|
),
|
||||||
contactName: get(contact, 'displayName'),
|
contactName: get(contact, 'displayName'),
|
||||||
contactType: get(contact, 'contactService'),
|
contactType: get(contact, 'contactService'),
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
return [
|
return [
|
||||||
{ key: 'date', accessor: 'dateFormatted' },
|
{ key: 'date', accessor: 'dateFormatted' },
|
||||||
{ key: 'account_name', accessor: 'account.name' },
|
{ key: 'account_name', accessor: 'account.name' },
|
||||||
{ key: 'reference_type', accessor: 'referenceTypeFormatted' },
|
{ key: 'reference_type', accessor: 'transactionTypeFormatted' },
|
||||||
{ key: 'reference_number', accessor: 'transactionNumber' },
|
{ key: 'reference_number', accessor: 'transactionNumber' },
|
||||||
{ key: 'description', accessor: 'note' },
|
{ key: 'description', accessor: 'note' },
|
||||||
{ key: 'credit', accessor: 'formattedCredit' },
|
{ key: 'credit', accessor: 'formattedCredit' },
|
||||||
|
|||||||
105
packages/server/src/services/Import/ImportALS.ts
Normal file
105
packages/server/src/services/Import/ImportALS.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import { AsyncLocalStorage } from 'async_hooks';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ImportAls {
|
||||||
|
private als: AsyncLocalStorage<Map<string, any>>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.als = new AsyncLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a callback function within the context of a new AsyncLocalStorage store.
|
||||||
|
* @param callback The function to be executed within the AsyncLocalStorage context.
|
||||||
|
* @returns The result of the callback function.
|
||||||
|
*/
|
||||||
|
public run<T>(callback: () => T): T {
|
||||||
|
return this.als.run<T>(new Map(), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a callback function in preview mode within the AsyncLocalStorage context.
|
||||||
|
* @param callback The function to be executed in preview mode.
|
||||||
|
* @returns The result of the callback function.
|
||||||
|
*/
|
||||||
|
public runPreview<T>(callback: () => T): T {
|
||||||
|
return this.run(() => {
|
||||||
|
this.markAsImport();
|
||||||
|
this.markAsImportPreview();
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a callback function in commit mode within the AsyncLocalStorage context.
|
||||||
|
* @param {() => T} callback - The function to be executed in commit mode.
|
||||||
|
* @returns {T} The result of the callback function.
|
||||||
|
*/
|
||||||
|
public runCommit<T>(callback: () => T): T {
|
||||||
|
return this.run(() => {
|
||||||
|
this.markAsImport();
|
||||||
|
this.markAsImportCommit();
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current AsyncLocalStorage store.
|
||||||
|
* @returns The current store or undefined if not in a valid context.
|
||||||
|
*/
|
||||||
|
public getStore(): Map<string, any> | undefined {
|
||||||
|
return this.als.getStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current context as an import operation.
|
||||||
|
* @param flag Boolean flag to set or unset the import status. Defaults to true.
|
||||||
|
*/
|
||||||
|
public markAsImport(flag: boolean = true): void {
|
||||||
|
const store = this.getStore();
|
||||||
|
store?.set('isImport', flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current context as an import commit operation.
|
||||||
|
* @param flag Boolean flag to set or unset the import commit status. Defaults to true.
|
||||||
|
*/
|
||||||
|
public markAsImportCommit(flag: boolean = true): void {
|
||||||
|
const store = this.getStore();
|
||||||
|
store?.set('isImportCommit', flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current context as an import preview operation.
|
||||||
|
* @param {boolean} flag - Boolean flag to set or unset the import preview status. Defaults to true.
|
||||||
|
*/
|
||||||
|
public markAsImportPreview(flag: boolean = true): void {
|
||||||
|
const store = this.getStore();
|
||||||
|
store?.set('isImportPreview', flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current context is an import operation.
|
||||||
|
* @returns {boolean} True if the context is an import operation, false otherwise.
|
||||||
|
*/
|
||||||
|
public get isImport(): boolean {
|
||||||
|
return !!this.getStore()?.get('isImport');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current context is an import commit operation.
|
||||||
|
* @returns {boolean} True if the context is an import commit operation, false otherwise.
|
||||||
|
*/
|
||||||
|
public get isImportCommit(): boolean {
|
||||||
|
return !!this.getStore()?.get('isImportCommit');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current context is an import preview operation.
|
||||||
|
* @returns {boolean} True if the context is an import preview operation, false otherwise.
|
||||||
|
*/
|
||||||
|
public get isImportPreview(): boolean {
|
||||||
|
return !!this.getStore()?.get('isImportPreview');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import XLSX from 'xlsx';
|
|
||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
@@ -28,22 +27,6 @@ export class ImportFileCommon {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private resource: ResourceService;
|
private resource: ResourceService;
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the columns of the imported data based on the provided mapping attributes.
|
|
||||||
* @param {Record<string, any>[]} body - The array of data objects to map.
|
|
||||||
* @param {ImportMappingAttr[]} map - The mapping attributes.
|
|
||||||
* @returns {Record<string, any>[]} - The mapped data objects.
|
|
||||||
*/
|
|
||||||
public parseXlsxSheet(buffer: Buffer): Record<string, unknown>[] {
|
|
||||||
const workbook = XLSX.read(buffer, { type: 'buffer', raw: true });
|
|
||||||
|
|
||||||
const firstSheetName = workbook.SheetNames[0];
|
|
||||||
const worksheet = workbook.Sheets[firstSheetName];
|
|
||||||
|
|
||||||
return XLSX.utils.sheet_to_json(worksheet, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports the given parsed data to the resource storage through registered importable service.
|
* Imports the given parsed data to the resource storage through registered importable service.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class ImportFileMapping {
|
|||||||
*/
|
*/
|
||||||
public async mapping(
|
public async mapping(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: number,
|
importId: string,
|
||||||
maps: ImportMappingAttr[]
|
maps: ImportMappingAttr[]
|
||||||
): Promise<ImportFileMapPOJO> {
|
): Promise<ImportFileMapPOJO> {
|
||||||
const importFile = await Import.query()
|
const importFile = await Import.query()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
import { ImportFilePreviewPOJO } from './interfaces';
|
||||||
import { ImportFileProcess } from './ImportFileProcess';
|
import { ImportFileProcess } from './ImportFileProcess';
|
||||||
|
import { ImportAls } from './ImportALS';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFilePreview {
|
export class ImportFilePreview {
|
||||||
@@ -11,15 +12,33 @@ export class ImportFilePreview {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private importFile: ImportFileProcess;
|
private importFile: ImportFileProcess;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private importAls: ImportAls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview the imported file results before commiting the transactions.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {string} importId -
|
||||||
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
|
*/
|
||||||
|
public async preview(
|
||||||
|
tenantId: number,
|
||||||
|
importId: string
|
||||||
|
): Promise<ImportFilePreviewPOJO> {
|
||||||
|
return this.importAls.runPreview<Promise<ImportFilePreviewPOJO>>(() =>
|
||||||
|
this.previewAlsRun(tenantId, importId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preview the imported file results before commiting the transactions.
|
* Preview the imported file results before commiting the transactions.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} importId
|
* @param {number} importId
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
*/
|
*/
|
||||||
public async preview(
|
public async previewAlsRun(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: number
|
importId: string
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
): Promise<ImportFilePreviewPOJO> {
|
||||||
const knex = this.tenancy.knex(tenantId);
|
const knex = this.tenancy.knex(tenantId);
|
||||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user