mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
Compare commits
10 Commits
all-contri
...
advanced-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0e227ff28 | ||
|
|
b590d2cb03 | ||
|
|
daf1cd38c0 | ||
|
|
3e2997d745 | ||
|
|
f3af3843dd | ||
|
|
b68d180785 | ||
|
|
341d47cc7b | ||
|
|
5c3a371e8a | ||
|
|
1141991e44 | ||
|
|
8cd3a6c48d |
@@ -132,15 +132,6 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "oleynikd",
|
|
||||||
"name": "Denis",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
|
|
||||||
"profile": "https://github.com/oleynikd",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
@@ -126,9 +126,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,12 @@ import { NextFunction, Request, Response, Router } from 'express';
|
|||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||||
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
|
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
|
||||||
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class BankAccountsController extends BaseController {
|
export class BankAccountsController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private getBankAccountSummaryService: GetBankAccountSummary;
|
private getBankAccountSummaryService: GetBankAccountSummary;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private bankAccountsApp: BankAccountsApplication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -20,11 +16,6 @@ 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.post(
|
|
||||||
'/:bankAccountId/disconnect',
|
|
||||||
this.disconnectBankAccount.bind(this)
|
|
||||||
);
|
|
||||||
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
|
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -55,58 +46,4 @@ export class BankAccountsController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disonnect the given bank account.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
* @returns {Promise<Response|null>}
|
|
||||||
*/
|
|
||||||
async disconnectBankAccount(
|
|
||||||
req: Request<{ bankAccountId: number }>,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { bankAccountId } = req.params;
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.bankAccountsApp.disconnectBankAccount(tenantId, bankAccountId);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
id: bankAccountId,
|
|
||||||
message: 'The bank account has been disconnected.',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the given bank account.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
* @returns {Promise<Response|null>}
|
|
||||||
*/
|
|
||||||
async refreshBankAccount(
|
|
||||||
req: Request<{ bankAccountId: number }>,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { bankAccountId } = req.params;
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.bankAccountsApp.refreshBankAccount(tenantId, bankAccountId);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
id: bankAccountId,
|
|
||||||
message: 'The bank account has been disconnected.',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { body, param, query } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import { NextFunction, Request, Response, Router } from 'express';
|
import { NextFunction, Request, Response, Router, query } from 'express';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
|
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
|
||||||
import { map, parseInt, trim } from 'lodash';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExcludeBankTransactionsController extends BaseController {
|
export class ExcludeBankTransactionsController extends BaseController {
|
||||||
@@ -16,21 +15,9 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
public router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.put(
|
|
||||||
'/transactions/exclude',
|
|
||||||
[body('ids').exists()],
|
|
||||||
this.validationResult,
|
|
||||||
this.excludeBulkBankTransactions.bind(this)
|
|
||||||
);
|
|
||||||
router.put(
|
|
||||||
'/transactions/unexclude',
|
|
||||||
[body('ids').exists()],
|
|
||||||
this.validationResult,
|
|
||||||
this.unexcludeBulkBankTransactins.bind(this)
|
|
||||||
);
|
|
||||||
router.put(
|
router.put(
|
||||||
'/transactions/:transactionId/exclude',
|
'/transactions/:transactionId/exclude',
|
||||||
[param('transactionId').exists().toInt()],
|
[param('transactionId').exists()],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.excludeBankTransaction.bind(this)
|
this.excludeBankTransaction.bind(this)
|
||||||
);
|
);
|
||||||
@@ -107,63 +94,6 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude bank transactions in bulk.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async excludeBulkBankTransactions(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { ids } = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.excludeBankTransactionApp.excludeBankTransactions(
|
|
||||||
tenantId,
|
|
||||||
ids
|
|
||||||
);
|
|
||||||
return res.status(200).send({
|
|
||||||
message: 'The given bank transactions have been excluded',
|
|
||||||
ids,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unexclude the given bank transactions in bulk.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
* @returns {Promise<Response | null>}
|
|
||||||
*/
|
|
||||||
private async unexcludeBulkBankTransactins(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
): Promise<Response | null> {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { ids } = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.excludeBankTransactionApp.unexcludeBankTransactions(
|
|
||||||
tenantId,
|
|
||||||
ids
|
|
||||||
);
|
|
||||||
return res.status(200).send({
|
|
||||||
message: 'The given bank transactions have been excluded',
|
|
||||||
ids,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the excluded uncategorized bank transactions.
|
* Retrieves the excluded uncategorized bank transactions.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -179,6 +109,7 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = this.matchedBodyData(req);
|
const filter = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
console.log('123');
|
||||||
try {
|
try {
|
||||||
const data =
|
const data =
|
||||||
await this.excludeBankTransactionApp.getExcludedBankTransactions(
|
await this.excludeBankTransactionApp.getExcludedBankTransactions(
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ export default class BillsPayments extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
check('prepard_expenses_account_id').optional().isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,8 +150,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
check('customer_id').exists().isNumeric().toInt(),
|
check('customer_id').exists().isNumeric().toInt(),
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
|
|
||||||
check('amount').exists().isNumeric().toFloat(),
|
|
||||||
check('payment_date').exists(),
|
check('payment_date').exists(),
|
||||||
|
check('amount').exists().isNumeric().toFloat(),
|
||||||
|
|
||||||
check('reference_no').optional(),
|
check('reference_no').optional(),
|
||||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||||
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
|
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
|
||||||
@@ -159,7 +160,8 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
check('entries').isArray({}),
|
check('entries').isArray(),
|
||||||
|
|
||||||
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
check('entries.*.index').optional().isNumeric().toInt(),
|
check('entries.*.index').optional().isNumeric().toInt(),
|
||||||
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
||||||
@@ -167,6 +169,11 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
check('unearned_revenue_account_id')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric()
|
||||||
|
.toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,13 +236,5 @@ module.exports = {
|
|||||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||||
endpoint: process.env.S3_ENDPOINT,
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
bucket: process.env.S3_BUCKET || 'bigcapital-documents',
|
bucket: process.env.S3_BUCKET || 'bigcapital-documents',
|
||||||
forcePathStyle: parseBoolean(
|
|
||||||
defaultTo(process.env.S3_FORCE_PATH_STYLE, false),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
loops: {
|
|
||||||
apiKey: process.env.LOOPS_API_KEY,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.table('accounts', (table) => {
|
|
||||||
table.string('plaid_item_id').nullable();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
return knex.schema.table('accounts', (table) => {
|
|
||||||
table.dropColumn('plaid_item_id');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.decimal('applied_amount', 13, 3).defaultTo(0);
|
||||||
|
table
|
||||||
|
.integer('unearned_revenue_account_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('accounts');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('payment_receives', (table) => {
|
||||||
|
table.dropColumn('applied_amount');
|
||||||
|
table.dropColumn('unearned_revenue_account_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('bills_payments', (table) => {
|
||||||
|
table.decimal('applied_amount', 13, 3).defaultTo(0);
|
||||||
|
table
|
||||||
|
.integer('prepard_expenses_account_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable('accounts');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('bills_payments', (table) => {
|
||||||
|
table.dropColumn('applied_amount');
|
||||||
|
table.dropColumn('prepard_expenses_account_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema
|
|
||||||
.table('accounts', (table) => {
|
|
||||||
table
|
|
||||||
.boolean('is_syncing_owner')
|
|
||||||
.defaultTo(false)
|
|
||||||
.after('is_feeds_active');
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return knex('accounts')
|
|
||||||
.whereNotNull('plaid_item_id')
|
|
||||||
.orWhereNotNull('plaid_account_id')
|
|
||||||
.update('is_syncing_owner', true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
table.dropColumn('is_syncing_owner');
|
|
||||||
};
|
|
||||||
@@ -15,7 +15,6 @@ export interface IAccountDTO {
|
|||||||
export interface IAccountCreateDTO extends IAccountDTO {
|
export interface IAccountCreateDTO extends IAccountDTO {
|
||||||
currencyCode?: string;
|
currencyCode?: string;
|
||||||
plaidAccountId?: string;
|
plaidAccountId?: string;
|
||||||
plaidItemId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAccountEditDTO extends IAccountDTO {}
|
export interface IAccountEditDTO extends IAccountDTO {}
|
||||||
@@ -38,8 +37,6 @@ export interface IAccount {
|
|||||||
accountNormal: string;
|
accountNormal: string;
|
||||||
accountParentType: string;
|
accountParentType: string;
|
||||||
bankBalance: string;
|
bankBalance: string;
|
||||||
plaidItemId: number | null
|
|
||||||
lastFeedsUpdatedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AccountNormal {
|
export enum AccountNormal {
|
||||||
|
|||||||
@@ -166,3 +166,10 @@ export interface IBillOpenedPayload {
|
|||||||
oldBill: IBill;
|
oldBill: IBill;
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IBillPrepardExpensesAppliedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
billId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export interface IBillPayment {
|
|||||||
|
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
|
||||||
|
prepardExpensesAccountId?: number;
|
||||||
|
isPrepardExpense: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBillPaymentEntryDTO {
|
export interface IBillPaymentEntryDTO {
|
||||||
@@ -38,6 +41,7 @@ export interface IBillPaymentEntryDTO {
|
|||||||
|
|
||||||
export interface IBillPaymentDTO {
|
export interface IBillPaymentDTO {
|
||||||
vendorId: number;
|
vendorId: number;
|
||||||
|
amount: number;
|
||||||
paymentAccountId: number;
|
paymentAccountId: number;
|
||||||
paymentNumber?: string;
|
paymentNumber?: string;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
@@ -47,6 +51,7 @@ export interface IBillPaymentDTO {
|
|||||||
entries: IBillPaymentEntryDTO[];
|
entries: IBillPaymentEntryDTO[];
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
attachments?: AttachmentLinkDTO[];
|
attachments?: AttachmentLinkDTO[];
|
||||||
|
prepardExpensesAccountId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBillReceivePageEntry {
|
export interface IBillReceivePageEntry {
|
||||||
@@ -119,3 +124,11 @@ export enum IPaymentMadeAction {
|
|||||||
Delete = 'Delete',
|
Delete = 'Delete',
|
||||||
View = 'View',
|
View = 'View',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPaymentPrepardExpensesAppliedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
billPaymentId: number;
|
||||||
|
billId: number;
|
||||||
|
appliedAmount: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,8 +25,13 @@ export interface IPaymentReceive {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
unearnedRevenueAccountId?: number;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceiveCreateDTO {
|
|
||||||
|
interface IPaymentReceivedCommonDTO {
|
||||||
|
unearnedRevenueAccountId?: number;
|
||||||
|
}
|
||||||
|
export interface IPaymentReceiveCreateDTO extends IPaymentReceivedCommonDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -41,7 +46,7 @@ export interface IPaymentReceiveCreateDTO {
|
|||||||
attachments?: AttachmentLinkDTO[];
|
attachments?: AttachmentLinkDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditDTO {
|
export interface IPaymentReceiveEditDTO extends IPaymentReceivedCommonDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
paymentDate: Date;
|
paymentDate: Date;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -184,3 +189,11 @@ export interface PaymentReceiveMailPresendEvent {
|
|||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
messageOptions: PaymentReceiveMailOptsDTO;
|
messageOptions: PaymentReceiveMailOptsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaymentReceiveUnearnedRevenueAppliedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
paymentReceiveId: number;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
appliedAmount: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,3 +216,9 @@ export interface ISaleInvoiceMailSent {
|
|||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
messageOptions: SendInvoiceMailDTO;
|
messageOptions: SendInvoiceMailDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SaleInvoiceAppliedUnearnedRevenueOnCreatedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
saleInvoiceId: number;
|
||||||
|
trx?: Knex.Transaction;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,69 @@
|
|||||||
|
import { forEach } from 'lodash';
|
||||||
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';
|
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';
|
||||||
|
import { createPlaidApiEvent } from './PlaidApiEventsDBSync';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
|
const OPTIONS = { clientApp: 'Plaid-Pattern' };
|
||||||
|
|
||||||
|
// We want to log requests to / responses from the Plaid API (via the Plaid client), as this data
|
||||||
|
// can be useful for troubleshooting.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging function for Plaid client methods that use an access_token as an argument. Associates
|
||||||
|
* the Plaid API event log entry with the item and user the request is for.
|
||||||
|
*
|
||||||
|
* @param {string} clientMethod the name of the Plaid client method called.
|
||||||
|
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
|
||||||
|
* @param {Object} response the response from the Plaid client.
|
||||||
|
*/
|
||||||
|
const defaultLogger = async (clientMethod, clientMethodArgs, response) => {
|
||||||
|
const accessToken = clientMethodArgs[0].access_token;
|
||||||
|
// const { id: itemId, user_id: userId } = await retrieveItemByPlaidAccessToken(
|
||||||
|
// accessToken
|
||||||
|
// );
|
||||||
|
// await createPlaidApiEvent(1, 1, clientMethod, clientMethodArgs, response);
|
||||||
|
|
||||||
|
// console.log(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging function for Plaid client methods that do not use access_token as an argument. These
|
||||||
|
* Plaid API event log entries will not be associated with an item or user.
|
||||||
|
*
|
||||||
|
* @param {string} clientMethod the name of the Plaid client method called.
|
||||||
|
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
|
||||||
|
* @param {Object} response the response from the Plaid client.
|
||||||
|
*/
|
||||||
|
const noAccessTokenLogger = async (
|
||||||
|
clientMethod,
|
||||||
|
clientMethodArgs,
|
||||||
|
response
|
||||||
|
) => {
|
||||||
|
// console.log(response);
|
||||||
|
|
||||||
|
// await createPlaidApiEvent(
|
||||||
|
// undefined,
|
||||||
|
// undefined,
|
||||||
|
// clientMethod,
|
||||||
|
// clientMethodArgs,
|
||||||
|
// response
|
||||||
|
// );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plaid client methods used in this app, mapped to their appropriate logging functions.
|
||||||
|
const clientMethodLoggingFns = {
|
||||||
|
accountsGet: defaultLogger,
|
||||||
|
institutionsGet: noAccessTokenLogger,
|
||||||
|
institutionsGetById: noAccessTokenLogger,
|
||||||
|
itemPublicTokenExchange: noAccessTokenLogger,
|
||||||
|
itemRemove: defaultLogger,
|
||||||
|
linkTokenCreate: noAccessTokenLogger,
|
||||||
|
transactionsSync: defaultLogger,
|
||||||
|
sandboxItemResetLogin: defaultLogger,
|
||||||
|
};
|
||||||
// Wrapper for the Plaid client. This allows us to easily log data for all Plaid client requests.
|
// Wrapper for the Plaid client. This allows us to easily log data for all Plaid client requests.
|
||||||
export class PlaidClientWrapper {
|
export class PlaidClientWrapper {
|
||||||
private static instance: PlaidClientWrapper;
|
constructor() {
|
||||||
private client: PlaidApi;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
// Initialize the Plaid client.
|
// Initialize the Plaid client.
|
||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
basePath: PlaidEnvironments[config.plaid.env],
|
basePath: PlaidEnvironments[config.plaid.env],
|
||||||
@@ -18,13 +75,26 @@ export class PlaidClientWrapper {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.client = new PlaidApi(configuration);
|
this.client = new PlaidApi(configuration);
|
||||||
|
|
||||||
|
// Wrap the Plaid client methods to add a logging function.
|
||||||
|
forEach(clientMethodLoggingFns, (logFn, method) => {
|
||||||
|
this[method] = this.createWrappedClientMethod(method, logFn);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getClient(): PlaidApi {
|
// Allows us to log API request data for troubleshooting purposes.
|
||||||
if (!PlaidClientWrapper.instance) {
|
createWrappedClientMethod(clientMethod, log) {
|
||||||
PlaidClientWrapper.instance = new PlaidClientWrapper();
|
return async (...args) => {
|
||||||
}
|
try {
|
||||||
return PlaidClientWrapper.instance.client;
|
const res = await this.client[clientMethod](...args);
|
||||||
|
await log(clientMethod, args, res);
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
await log(clientMethod, args, err?.response?.data);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ export const s3 = new S3Client({
|
|||||||
secretAccessKey: config.s3.secretAccessKey,
|
secretAccessKey: config.s3.secretAccessKey,
|
||||||
},
|
},
|
||||||
endpoint: config.s3.endpoint,
|
endpoint: config.s3.endpoint,
|
||||||
forcePathStyle: config.s3.forcePathStyle,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/
|
|||||||
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
|
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
|
||||||
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
|
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
|
||||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
||||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
import { AutoApplyUnearnedRevenueOnInvoiceCreated } from '@/services/Sales/PaymentReceives/events/AutoApplyUnearnedRevenueOnInvoiceCreated';
|
||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
import { AutoApplyPrepardExpensesOnBillCreated } from '@/services/Purchases/Bills/events/AutoApplyPrepardExpensesOnBillCreated';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -276,9 +276,5 @@ export const susbcribers = () => {
|
|||||||
|
|
||||||
// Plaid
|
// Plaid
|
||||||
RecognizeSyncedBankTranasctions,
|
RecognizeSyncedBankTranasctions,
|
||||||
DisconnectPlaidItemOnAccountDeleted,
|
|
||||||
|
|
||||||
// Loops
|
|
||||||
LoopsEventsSubscriber
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ export default class Account extends mixin(TenantModel, [
|
|||||||
const ExpenseEntry = require('models/ExpenseCategory');
|
const ExpenseEntry = require('models/ExpenseCategory');
|
||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
||||||
const PlaidItem = require('models/PlaidItem');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -322,18 +321,6 @@ export default class Account extends mixin(TenantModel, [
|
|||||||
query.where('categorized', false);
|
query.where('categorized', false);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Account model may belongs to a Plaid item.
|
|
||||||
*/
|
|
||||||
plaidItem: {
|
|
||||||
relation: Model.BelongsToOneRelation,
|
|
||||||
modelClass: PlaidItem.default,
|
|
||||||
join: {
|
|
||||||
from: 'accounts.plaidItemId',
|
|
||||||
to: 'plaid_items.plaidItemId',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export default class BillPayment extends mixin(TenantModel, [
|
|||||||
CustomViewBaseModel,
|
CustomViewBaseModel,
|
||||||
ModelSearchable,
|
ModelSearchable,
|
||||||
]) {
|
]) {
|
||||||
|
prepardExpensesAccountId: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -47,6 +49,14 @@ export default class BillPayment extends mixin(TenantModel, [
|
|||||||
return BillPaymentSettings;
|
return BillPaymentSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the payment is prepard expense.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get isPrepardExpense() {
|
||||||
|
return !!this.prepardExpensesAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,12 +13,7 @@ export class AccountTransformer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return ['formattedAmount', 'flattenName', 'bankBalanceFormatted'];
|
||||||
'formattedAmount',
|
|
||||||
'flattenName',
|
|
||||||
'bankBalanceFormatted',
|
|
||||||
'lastFeedsUpdatedAtFormatted',
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,15 +52,6 @@ export class AccountTransformer extends Transformer {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted last feeds update at.
|
|
||||||
* @param {IAccount} account
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected lastFeedsUpdatedAtFormatted = (account: IAccount): string => {
|
|
||||||
return this.formatDate(account.lastFeedsUpdatedAt);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the accounts collection to flat or nested array.
|
* Transformes the accounts collection to flat or nested array.
|
||||||
* @param {IAccount[]}
|
* @param {IAccount[]}
|
||||||
|
|||||||
@@ -96,11 +96,6 @@ export class CreateAccount {
|
|||||||
...createAccountDTO,
|
...createAccountDTO,
|
||||||
slug: kebabCase(createAccountDTO.name),
|
slug: kebabCase(createAccountDTO.name),
|
||||||
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
||||||
|
|
||||||
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
|
||||||
isSyncingOwner: Boolean(
|
|
||||||
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +117,12 @@ export class CreateAccount {
|
|||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Authorize the account creation.
|
// Authorize the account creation.
|
||||||
await this.authorize(tenantId, accountDTO, tenantMeta.baseCurrency, params);
|
await this.authorize(
|
||||||
|
tenantId,
|
||||||
|
accountDTO,
|
||||||
|
tenantMeta.baseCurrency,
|
||||||
|
params
|
||||||
|
);
|
||||||
// Transformes the DTO to model.
|
// Transformes the DTO to model.
|
||||||
const accountInputModel = this.transformDTOToModel(
|
const accountInputModel = this.transformDTOToModel(
|
||||||
accountDTO,
|
accountDTO,
|
||||||
@@ -157,3 +157,4 @@ export class CreateAccount {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { DisconnectBankAccount } from './DisconnectBankAccount';
|
|
||||||
import { RefreshBankAccountService } from './RefreshBankAccount';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class BankAccountsApplication {
|
|
||||||
@Inject()
|
|
||||||
private disconnectBankAccountService: DisconnectBankAccount;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private refreshBankAccountService: RefreshBankAccountService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the given bank account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankAccountId
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async disconnectBankAccount(tenantId: number, bankAccountId: number) {
|
|
||||||
return this.disconnectBankAccountService.disconnectBankAccount(
|
|
||||||
tenantId,
|
|
||||||
bankAccountId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the bank transactions of the given bank account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankAccountId
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async refreshBankAccount(tenantId: number, bankAccountId: number) {
|
|
||||||
return this.refreshBankAccountService.refreshBankAccount(
|
|
||||||
tenantId,
|
|
||||||
bankAccountId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import { PlaidClientWrapper } from '@/lib/Plaid';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
ERRORS,
|
|
||||||
IBankAccountDisconnectedEventPayload,
|
|
||||||
IBankAccountDisconnectingEventPayload,
|
|
||||||
} from './types';
|
|
||||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DisconnectBankAccount {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the given bank account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankAccountId
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async disconnectBankAccount(tenantId: number, bankAccountId: number) {
|
|
||||||
const { Account, PlaidItem } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieve the bank account or throw not found error.
|
|
||||||
const account = await Account.query()
|
|
||||||
.findById(bankAccountId)
|
|
||||||
.whereIn('account_type', [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK])
|
|
||||||
.withGraphFetched('plaidItem')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const oldPlaidItem = account.plaidItem;
|
|
||||||
|
|
||||||
if (!oldPlaidItem) {
|
|
||||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
|
||||||
}
|
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBankAccountDisconnecting` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnecting, {
|
|
||||||
tenantId,
|
|
||||||
bankAccountId,
|
|
||||||
} as IBankAccountDisconnectingEventPayload);
|
|
||||||
|
|
||||||
// Remove the Plaid item from the system.
|
|
||||||
await PlaidItem.query(trx).findById(account.plaidItemId).delete();
|
|
||||||
|
|
||||||
// Remove the plaid item association to the bank account.
|
|
||||||
await Account.query(trx).findById(bankAccountId).patch({
|
|
||||||
plaidAccountId: null,
|
|
||||||
plaidItemId: null,
|
|
||||||
isFeedsActive: false,
|
|
||||||
});
|
|
||||||
// Remove the Plaid item.
|
|
||||||
await plaidInstance.itemRemove({
|
|
||||||
access_token: oldPlaidItem.plaidAccessToken,
|
|
||||||
});
|
|
||||||
// Triggers `onBankAccountDisconnected` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnected, {
|
|
||||||
tenantId,
|
|
||||||
bankAccountId,
|
|
||||||
trx,
|
|
||||||
} as IBankAccountDisconnectedEventPayload);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { PlaidClientWrapper } from '@/lib/Plaid';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import { ERRORS } from './types';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class RefreshBankAccountService {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asks Plaid to trigger syncing the given bank account.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankAccountId
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async refreshBankAccount(tenantId: number, bankAccountId: number) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const bankAccount = await Account.query()
|
|
||||||
.findById(bankAccountId)
|
|
||||||
.withGraphFetched('plaidItem')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Can't continue if the given account is not linked with Plaid item.
|
|
||||||
if (!bankAccount.plaidItem) {
|
|
||||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
|
||||||
}
|
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
|
||||||
|
|
||||||
await plaidInstance.transactionsRefresh({
|
|
||||||
access_token: bankAccount.plaidItem.plaidAccessToken,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { IAccountEventDeletedPayload } from '@/interfaces';
|
|
||||||
import { PlaidClientWrapper } from '@/lib/Plaid';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DisconnectPlaidItemOnAccountDeleted {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.accounts.onDeleted,
|
|
||||||
this.handleDisconnectPlaidItemOnAccountDelete.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes Plaid item from the system and Plaid once the account deleted.
|
|
||||||
* @param {IAccountEventDeletedPayload} payload
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
private async handleDisconnectPlaidItemOnAccountDelete({
|
|
||||||
tenantId,
|
|
||||||
oldAccount,
|
|
||||||
trx,
|
|
||||||
}: IAccountEventDeletedPayload) {
|
|
||||||
const { PlaidItem, Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Can't continue if the deleted account is not linked to Plaid item.
|
|
||||||
if (!oldAccount.plaidItemId) return;
|
|
||||||
|
|
||||||
// Retrieves the Plaid item that associated to the deleted account.
|
|
||||||
const oldPlaidItem = await PlaidItem.query(trx).findOne(
|
|
||||||
'plaidItemId',
|
|
||||||
oldAccount.plaidItemId
|
|
||||||
);
|
|
||||||
// Unlink the Plaid item from all account before deleting it.
|
|
||||||
await Account.query(trx)
|
|
||||||
.where('plaidItemId', oldAccount.plaidItemId)
|
|
||||||
.patch({
|
|
||||||
plaidAccountId: null,
|
|
||||||
plaidItemId: null,
|
|
||||||
});
|
|
||||||
// Remove the Plaid item from the system.
|
|
||||||
await PlaidItem.query(trx)
|
|
||||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
if (oldPlaidItem) {
|
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
|
||||||
|
|
||||||
// Remove the Plaid item.
|
|
||||||
await plaidInstance.itemRemove({
|
|
||||||
access_token: oldPlaidItem.plaidAccessToken,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
export interface IBankAccountDisconnectingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
bankAccountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBankAccountDisconnectedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
bankAccountId: number;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ERRORS = {
|
|
||||||
BANK_ACCOUNT_NOT_CONNECTED: 'BANK_ACCOUNT_NOT_CONNECTED',
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import PromisePool from '@supercharge/promise-pool';
|
|
||||||
import { castArray } from 'lodash';
|
|
||||||
import { ExcludeBankTransaction } from './ExcludeBankTransaction';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ExcludeBankTransactions {
|
|
||||||
@Inject()
|
|
||||||
private excludeBankTransaction: ExcludeBankTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude bank transactions in bulk.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankTransactionIds
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async excludeBankTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
bankTransactionIds: Array<number> | number
|
|
||||||
) {
|
|
||||||
const _bankTransactionIds = castArray(bankTransactionIds);
|
|
||||||
|
|
||||||
await PromisePool.withConcurrency(1)
|
|
||||||
.for(_bankTransactionIds)
|
|
||||||
.process((bankTransactionId: number) => {
|
|
||||||
return this.excludeBankTransaction.excludeBankTransaction(
|
|
||||||
tenantId,
|
|
||||||
bankTransactionId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,6 @@ import { ExcludeBankTransaction } from './ExcludeBankTransaction';
|
|||||||
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
|
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
|
||||||
import { GetExcludedBankTransactionsService } from './GetExcludedBankTransactions';
|
import { GetExcludedBankTransactionsService } from './GetExcludedBankTransactions';
|
||||||
import { ExcludedBankTransactionsQuery } from './_types';
|
import { ExcludedBankTransactionsQuery } from './_types';
|
||||||
import { UnexcludeBankTransactions } from './UnexcludeBankTransactions';
|
|
||||||
import { ExcludeBankTransactions } from './ExcludeBankTransactions';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExcludeBankTransactionsApplication {
|
export class ExcludeBankTransactionsApplication {
|
||||||
@@ -17,12 +15,6 @@ export class ExcludeBankTransactionsApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getExcludedBankTransactionsService: GetExcludedBankTransactionsService;
|
private getExcludedBankTransactionsService: GetExcludedBankTransactionsService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private excludeBankTransactionsService: ExcludeBankTransactions;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private unexcludeBankTransactionsService: UnexcludeBankTransactions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a bank transaction as excluded.
|
* Marks a bank transaction as excluded.
|
||||||
* @param {number} tenantId - The ID of the tenant.
|
* @param {number} tenantId - The ID of the tenant.
|
||||||
@@ -64,36 +56,4 @@ export class ExcludeBankTransactionsApplication {
|
|||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude the given bank transactions in bulk.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {Array<number> | number} bankTransactionIds
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public excludeBankTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
bankTransactionIds: Array<number> | number
|
|
||||||
): Promise<void> {
|
|
||||||
return this.excludeBankTransactionsService.excludeBankTransactions(
|
|
||||||
tenantId,
|
|
||||||
bankTransactionIds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude the given bank transactions in bulk.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {Array<number> | number} bankTransactionIds
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public unexcludeBankTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
bankTransactionIds: Array<number> | number
|
|
||||||
): Promise<void> {
|
|
||||||
return this.unexcludeBankTransactionsService.unexcludeBankTransactions(
|
|
||||||
tenantId,
|
|
||||||
bankTransactionIds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import PromisePool from '@supercharge/promise-pool';
|
|
||||||
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
|
|
||||||
import { castArray } from 'lodash';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class UnexcludeBankTransactions {
|
|
||||||
@Inject()
|
|
||||||
private unexcludeBankTransaction: UnexcludeBankTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unexclude bank transactions in bulk.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} bankTransactionIds
|
|
||||||
*/
|
|
||||||
public async unexcludeBankTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
bankTransactionIds: Array<number> | number
|
|
||||||
) {
|
|
||||||
const _bankTransactionIds = castArray(bankTransactionIds);
|
|
||||||
|
|
||||||
await PromisePool.withConcurrency(1)
|
|
||||||
.for(_bankTransactionIds)
|
|
||||||
.process((bankTransactionId: number) => {
|
|
||||||
return this.unexcludeBankTransaction.unexcludeBankTransaction(
|
|
||||||
tenantId,
|
|
||||||
bankTransactionId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,7 @@ export class PlaidItemService {
|
|||||||
const { PlaidItem } = this.tenancy.models(tenantId);
|
const { PlaidItem } = this.tenancy.models(tenantId);
|
||||||
const { publicToken, institutionId } = itemDTO;
|
const { publicToken, institutionId } = itemDTO;
|
||||||
|
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = new PlaidClientWrapper();
|
||||||
|
|
||||||
// Exchange the public token for a private access token and store with the item.
|
// Exchange the public token for a private access token and store with the item.
|
||||||
const response = await plaidInstance.itemPublicTokenExchange({
|
const response = await plaidInstance.itemPublicTokenExchange({
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class PlaidLinkTokenService {
|
|||||||
webhook: config.plaid.linkWebhook,
|
webhook: config.plaid.linkWebhook,
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
};
|
};
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = new PlaidClientWrapper();
|
||||||
const createResponse = await plaidInstance.linkTokenCreate(linkTokenParams);
|
const createResponse = await plaidInstance.linkTokenCreate(linkTokenParams);
|
||||||
|
|
||||||
return createResponse.data;
|
return createResponse.data;
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import * as R from 'ramda';
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import { entries, groupBy } from 'lodash';
|
import { entries, groupBy } from 'lodash';
|
||||||
import {
|
|
||||||
AccountBase as PlaidAccountBase,
|
|
||||||
Item as PlaidItem,
|
|
||||||
Institution as PlaidInstitution,
|
|
||||||
} from 'plaid';
|
|
||||||
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
||||||
import {
|
import {
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
@@ -58,7 +53,6 @@ export class PlaidSyncDb {
|
|||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const plaidAccount = await Account.query().findOne(
|
const plaidAccount = await Account.query().findOne(
|
||||||
'plaidAccountId',
|
'plaidAccountId',
|
||||||
createBankAccountDTO.plaidAccountId
|
createBankAccountDTO.plaidAccountId
|
||||||
@@ -83,15 +77,13 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncBankAccounts(
|
public async syncBankAccounts(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccounts: PlaidAccountBase[],
|
plaidAccounts: PlaidAccount[],
|
||||||
institution: PlaidInstitution,
|
institution: any,
|
||||||
item: PlaidItem,
|
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const transformToPlaidAccounts = transformPlaidAccountToCreateAccount(
|
const transformToPlaidAccounts =
|
||||||
item,
|
transformPlaidAccountToCreateAccount(institution);
|
||||||
institution
|
|
||||||
);
|
|
||||||
const accountCreateDTOs = R.map(transformToPlaidAccounts)(plaidAccounts);
|
const accountCreateDTOs = R.map(transformToPlaidAccounts)(plaidAccounts);
|
||||||
|
|
||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class PlaidUpdateTransactions {
|
|||||||
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
||||||
|
|
||||||
const request = { access_token: accessToken };
|
const request = { access_token: accessToken };
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = new PlaidClientWrapper();
|
||||||
const {
|
const {
|
||||||
data: { accounts, item },
|
data: { accounts, item },
|
||||||
} = await plaidInstance.accountsGet(request);
|
} = await plaidInstance.accountsGet(request);
|
||||||
@@ -66,13 +66,7 @@ export class PlaidUpdateTransactions {
|
|||||||
country_codes: ['US', 'UK'],
|
country_codes: ['US', 'UK'],
|
||||||
});
|
});
|
||||||
// Sync bank accounts.
|
// Sync bank accounts.
|
||||||
await this.plaidSync.syncBankAccounts(
|
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution, trx);
|
||||||
tenantId,
|
|
||||||
accounts,
|
|
||||||
institution,
|
|
||||||
item,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Sync bank account transactions.
|
// Sync bank account transactions.
|
||||||
await this.plaidSync.syncAccountsTransactions(
|
await this.plaidSync.syncAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -147,7 +141,7 @@ export class PlaidUpdateTransactions {
|
|||||||
cursor: cursor,
|
cursor: cursor,
|
||||||
count: batchSize,
|
count: batchSize,
|
||||||
};
|
};
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = new PlaidClientWrapper();
|
||||||
const response = await plaidInstance.transactionsSync(request);
|
const response = await plaidInstance.transactionsSync(request);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
// Add this page of results
|
// Add this page of results
|
||||||
|
|||||||
@@ -1,28 +1,18 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import {
|
|
||||||
Item as PlaidItem,
|
|
||||||
Institution as PlaidInstitution,
|
|
||||||
AccountBase as PlaidAccount,
|
|
||||||
} from 'plaid';
|
|
||||||
import {
|
import {
|
||||||
CreateUncategorizedTransactionDTO,
|
CreateUncategorizedTransactionDTO,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
|
PlaidAccount,
|
||||||
PlaidTransaction,
|
PlaidTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the Plaid account to create cashflow account DTO.
|
* Transformes the Plaid account to create cashflow account DTO.
|
||||||
* @param {PlaidItem} item -
|
* @param {PlaidAccount} plaidAccount
|
||||||
* @param {PlaidInstitution} institution -
|
|
||||||
* @param {PlaidAccount} plaidAccount -
|
|
||||||
* @returns {IAccountCreateDTO}
|
* @returns {IAccountCreateDTO}
|
||||||
*/
|
*/
|
||||||
export const transformPlaidAccountToCreateAccount = R.curry(
|
export const transformPlaidAccountToCreateAccount = R.curry(
|
||||||
(
|
(institution: any, plaidAccount: PlaidAccount): IAccountCreateDTO => {
|
||||||
item: PlaidItem,
|
|
||||||
institution: PlaidInstitution,
|
|
||||||
plaidAccount: PlaidAccount
|
|
||||||
): IAccountCreateDTO => {
|
|
||||||
return {
|
return {
|
||||||
name: `${institution.name} - ${plaidAccount.name}`,
|
name: `${institution.name} - ${plaidAccount.name}`,
|
||||||
code: '',
|
code: '',
|
||||||
@@ -30,10 +20,9 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
|||||||
currencyCode: plaidAccount.balances.iso_currency_code,
|
currencyCode: plaidAccount.balances.iso_currency_code,
|
||||||
accountType: 'cash',
|
accountType: 'cash',
|
||||||
active: true,
|
active: true,
|
||||||
|
plaidAccountId: plaidAccount.account_id,
|
||||||
bankBalance: plaidAccount.balances.current,
|
bankBalance: plaidAccount.balances.current,
|
||||||
accountMask: plaidAccount.mask,
|
accountMask: plaidAccount.mask,
|
||||||
plaidAccountId: plaidAccount.account_id,
|
|
||||||
plaidItemId: item.item_id,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,4 +38,24 @@ export default class ContactTransfromer extends Transformer {
|
|||||||
? this.formatDate(contact.openingBalanceAt)
|
? this.formatDate(contact.openingBalanceAt)
|
||||||
: '';
|
: '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the unused credit balance.
|
||||||
|
* @param {IContact} contact
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected unusedCredit = (contact: IContact): number => {
|
||||||
|
return contact.balance > 0 ? 0 : Math.abs(contact.balance);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted unused credit balance.
|
||||||
|
* @param {IContact} contact
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedUnusedCredit = (contact: IContact): string => {
|
||||||
|
const unusedCredit = this.unusedCredit(contact);
|
||||||
|
|
||||||
|
return formatNumber(unusedCredit, { currencyCode: contact.currencyCode });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export default class CustomerTransfromer extends ContactTransfromer {
|
|||||||
'formattedOpeningBalanceAt',
|
'formattedOpeningBalanceAt',
|
||||||
'customerType',
|
'customerType',
|
||||||
'formattedCustomerType',
|
'formattedCustomerType',
|
||||||
|
'unusedCredit',
|
||||||
|
'formattedUnusedCredit',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Service } from 'typedi';
|
|
||||||
import ContactTransfromer from '../ContactTransformer';
|
import ContactTransfromer from '../ContactTransformer';
|
||||||
|
|
||||||
export default class VendorTransfromer extends ContactTransfromer {
|
export default class VendorTransfromer extends ContactTransfromer {
|
||||||
@@ -10,7 +9,9 @@ export default class VendorTransfromer extends ContactTransfromer {
|
|||||||
return [
|
return [
|
||||||
'formattedBalance',
|
'formattedBalance',
|
||||||
'formattedOpeningBalance',
|
'formattedOpeningBalance',
|
||||||
'formattedOpeningBalanceAt'
|
'formattedOpeningBalanceAt',
|
||||||
|
'unusedCredit',
|
||||||
|
'formattedUnusedCredit',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export const DEFAULT_VIEWS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
OPENING_BALANCE_DATE_REQUIRED: 'OPENING_BALANCE_DATE_REQUIRED',
|
OPENING_BALANCE_DATE_REQUIRED: 'OPENING_BALANCE_DATE_REQUIRED',
|
||||||
CONTACT_ALREADY_INACTIVE: 'CONTACT_ALREADY_INACTIVE',
|
CONTACT_ALREADY_INACTIVE: 'CONTACT_ALREADY_INACTIVE',
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import config from '@/config';
|
|
||||||
import { IAuthSignUpVerifiedEventPayload } from '@/interfaces';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { SystemUser } from '@/system/models';
|
|
||||||
|
|
||||||
export class LoopsEventsSubscriber {
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.auth.signUpConfirmed,
|
|
||||||
this.triggerEventOnSignupVerified.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Once the user verified sends the event to the Loops.
|
|
||||||
* @param {IAuthSignUpVerifiedEventPayload} param0
|
|
||||||
*/
|
|
||||||
public async triggerEventOnSignupVerified({
|
|
||||||
email,
|
|
||||||
userId,
|
|
||||||
}: IAuthSignUpVerifiedEventPayload) {
|
|
||||||
// Can't continue since the Loops the api key is not configured.
|
|
||||||
if (!config.loops.apiKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const user = await SystemUser.query().findById(userId);
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: 'POST',
|
|
||||||
url: 'https://app.loops.so/api/v1/events/send',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${config.loops.apiKey}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
email,
|
|
||||||
userId,
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
eventName: 'USER_VERIFIED',
|
|
||||||
eventProperties: {},
|
|
||||||
mailingLists: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await axios(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { sumBy } from 'lodash';
|
import { sumBy, chain } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { AccountNormal, IBillPayment, ILedgerEntry } from '@/interfaces';
|
import {
|
||||||
|
AccountNormal,
|
||||||
|
IBillPayment,
|
||||||
|
IBillPaymentEntry,
|
||||||
|
ILedger,
|
||||||
|
ILedgerEntry,
|
||||||
|
} from '@/interfaces';
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
@@ -21,6 +27,7 @@ export class BillPaymentGLEntries {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} billPaymentId
|
* @param {number} billPaymentId
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public writePaymentGLEntries = async (
|
public writePaymentGLEntries = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -65,6 +72,7 @@ export class BillPaymentGLEntries {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} billPaymentId
|
* @param {number} billPaymentId
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public rewritePaymentGLEntries = async (
|
public rewritePaymentGLEntries = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -102,7 +110,7 @@ export class BillPaymentGLEntries {
|
|||||||
* @param {IBillPayment} billPayment
|
* @param {IBillPayment} billPayment
|
||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
private getPaymentCommonEntry = (billPayment: IBillPayment) => {
|
private getPaymentCommonEntry = (billPayment: IBillPayment): ILedgerEntry => {
|
||||||
const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD');
|
const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -127,7 +135,7 @@ export class BillPaymentGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the payment total exchange gain/loss.
|
* Calculates the payment total exchange gain/loss.
|
||||||
* @param {IBillPayment} paymentReceive - Payment receive with entries.
|
* @param {IBillPayment} paymentReceive - Payment receive with entries.
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
private getPaymentExGainOrLoss = (billPayment: IBillPayment): number => {
|
private getPaymentExGainOrLoss = (billPayment: IBillPayment): number => {
|
||||||
@@ -141,10 +149,10 @@ export class BillPaymentGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment exchange gain/loss entries.
|
* Retrieves the payment exchange gain/loss entries.
|
||||||
* @param {IBillPayment} billPayment -
|
* @param {IBillPayment} billPayment -
|
||||||
* @param {number} APAccountId -
|
* @param {number} APAccountId -
|
||||||
* @param {number} gainLossAccountId -
|
* @param {number} gainLossAccountId -
|
||||||
* @param {string} baseCurrency -
|
* @param {string} baseCurrency -
|
||||||
* @returns {ILedgerEntry[]}
|
* @returns {ILedgerEntry[]}
|
||||||
*/
|
*/
|
||||||
private getPaymentExGainOrLossEntries = (
|
private getPaymentExGainOrLossEntries = (
|
||||||
@@ -186,7 +194,7 @@ export class BillPaymentGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment deposit GL entry.
|
* Retrieves the payment deposit GL entry.
|
||||||
* @param {IBillPayment} billPayment
|
* @param {IBillPayment} billPayment
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getPaymentGLEntry = (billPayment: IBillPayment): ILedgerEntry => {
|
private getPaymentGLEntry = (billPayment: IBillPayment): ILedgerEntry => {
|
||||||
@@ -198,6 +206,7 @@ export class BillPaymentGLEntries {
|
|||||||
accountId: billPayment.paymentAccountId,
|
accountId: billPayment.paymentAccountId,
|
||||||
accountNormal: AccountNormal.DEBIT,
|
accountNormal: AccountNormal.DEBIT,
|
||||||
index: 2,
|
index: 2,
|
||||||
|
indexGroup: 10,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,8 +235,8 @@ export class BillPaymentGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment GL entries.
|
* Retrieves the payment GL entries.
|
||||||
* @param {IBillPayment} billPayment
|
* @param {IBillPayment} billPayment
|
||||||
* @param {number} APAccountId
|
* @param {number} APAccountId
|
||||||
* @returns {ILedgerEntry[]}
|
* @returns {ILedgerEntry[]}
|
||||||
*/
|
*/
|
||||||
private getPaymentGLEntries = (
|
private getPaymentGLEntries = (
|
||||||
@@ -254,10 +263,53 @@ export class BillPaymentGLEntries {
|
|||||||
return [paymentEntry, payableEntry, ...exGainLossEntries];
|
return [paymentEntry, payableEntry, ...exGainLossEntries];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* BEFORE APPLYING TO PAYMENT TO BILLS.
|
||||||
|
* -----------------------------------------
|
||||||
|
* - Cash/Bank - Credit.
|
||||||
|
* - Prepard Expenses - Debit
|
||||||
|
*
|
||||||
|
* AFTER APPLYING BILLS TO PAYMENT.
|
||||||
|
* -----------------------------------------
|
||||||
|
* - Prepard Expenses - Credit
|
||||||
|
* - A/P - Debit
|
||||||
|
*
|
||||||
|
* @param {number} APAccountId - A/P account id.
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
*/
|
||||||
|
private getPrepardExpenseGLEntries = (
|
||||||
|
APAccountId: number,
|
||||||
|
billPayment: IBillPayment
|
||||||
|
) => {
|
||||||
|
const prepardExpenseEntry = this.getPrepardExpenseEntry(billPayment);
|
||||||
|
const withdrawalEntry = this.getPaymentGLEntry(billPayment);
|
||||||
|
|
||||||
|
const paymentLinesEntries = chain(billPayment.entries)
|
||||||
|
.map((billPaymentEntry) => {
|
||||||
|
const APEntry = this.getAccountPayablePaymentLineEntry(
|
||||||
|
APAccountId,
|
||||||
|
billPayment,
|
||||||
|
billPaymentEntry
|
||||||
|
);
|
||||||
|
const creditPrepardExpenseEntry = this.getCreditPrepardExpenseEntry(
|
||||||
|
billPayment,
|
||||||
|
billPaymentEntry
|
||||||
|
);
|
||||||
|
return [creditPrepardExpenseEntry, APEntry];
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.value();
|
||||||
|
const prepardExpenseEntries = [prepardExpenseEntry, withdrawalEntry];
|
||||||
|
const combinedEntries = [...prepardExpenseEntries, ...paymentLinesEntries];
|
||||||
|
|
||||||
|
return combinedEntries;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the bill payment ledger.
|
* Retrieves the bill payment ledger.
|
||||||
* @param {IBillPayment} billPayment
|
* @param {IBillPayment} billPayment
|
||||||
* @param {number} APAccountId
|
* @param {number} APAccountId
|
||||||
* @returns {Ledger}
|
* @returns {Ledger}
|
||||||
*/
|
*/
|
||||||
private getBillPaymentLedger = (
|
private getBillPaymentLedger = (
|
||||||
@@ -266,12 +318,79 @@ export class BillPaymentGLEntries {
|
|||||||
gainLossAccountId: number,
|
gainLossAccountId: number,
|
||||||
baseCurrency: string
|
baseCurrency: string
|
||||||
): Ledger => {
|
): Ledger => {
|
||||||
const entries = this.getPaymentGLEntries(
|
const entries = billPayment.isPrepardExpense
|
||||||
billPayment,
|
? this.getPrepardExpenseGLEntries(APAccountId, billPayment)
|
||||||
APAccountId,
|
: this.getPaymentGLEntries(
|
||||||
gainLossAccountId,
|
billPayment,
|
||||||
baseCurrency
|
APAccountId,
|
||||||
);
|
gainLossAccountId,
|
||||||
|
baseCurrency
|
||||||
|
);
|
||||||
return new Ledger(entries);
|
return new Ledger(entries);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the prepard expense GL entry.
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getPrepardExpenseEntry = (
|
||||||
|
billPayment: IBillPayment
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentCommonEntry(billPayment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
debit: billPayment.localAmount,
|
||||||
|
accountId: billPayment.prepardExpensesAccountId,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
indexGroup: 10,
|
||||||
|
index: 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the GL entries of credit prepard expense for the give payment line.
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
* @param {IBillPaymentEntry} billPaymentEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getCreditPrepardExpenseEntry = (
|
||||||
|
billPayment: IBillPayment,
|
||||||
|
billPaymentEntry: IBillPaymentEntry
|
||||||
|
) => {
|
||||||
|
const commonJournal = this.getPaymentCommonEntry(billPayment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
credit: billPaymentEntry.paymentAmount,
|
||||||
|
accountId: billPayment.prepardExpensesAccountId,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
index: 2,
|
||||||
|
indexGroup: 20,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the A/P debit of the payment line.
|
||||||
|
* @param {number} APAccountId
|
||||||
|
* @param {IBillPayment} billPayment
|
||||||
|
* @param {IBillPaymentEntry} billPaymentEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getAccountPayablePaymentLineEntry = (
|
||||||
|
APAccountId: number,
|
||||||
|
billPayment: IBillPayment,
|
||||||
|
billPaymentEntry: IBillPaymentEntry
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentCommonEntry(billPayment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
debit: billPaymentEntry.paymentAmount,
|
||||||
|
accountId: APAccountId,
|
||||||
|
index: 1,
|
||||||
|
indexGroup: 20,
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export class PaymentWriteGLEntriesSubscriber {
|
|||||||
*/
|
*/
|
||||||
public attach(bus) {
|
public attach(bus) {
|
||||||
bus.subscribe(events.billPayment.onCreated, this.handleWriteJournalEntries);
|
bus.subscribe(events.billPayment.onCreated, this.handleWriteJournalEntries);
|
||||||
|
bus.subscribe(
|
||||||
|
events.billPayment.onPrepardExpensesApplied,
|
||||||
|
this.handleWritePrepardExpenseGLEntries
|
||||||
|
);
|
||||||
bus.subscribe(
|
bus.subscribe(
|
||||||
events.billPayment.onEdited,
|
events.billPayment.onEdited,
|
||||||
this.handleRewriteJournalEntriesOncePaymentEdited
|
this.handleRewriteJournalEntriesOncePaymentEdited
|
||||||
@@ -28,7 +32,8 @@ export class PaymentWriteGLEntriesSubscriber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle bill payment writing journal entries once created.
|
* Handles bill payment writing journal entries once created.
|
||||||
|
* @param {IBillPaymentEventCreatedPayload} payload -
|
||||||
*/
|
*/
|
||||||
private handleWriteJournalEntries = async ({
|
private handleWriteJournalEntries = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -44,6 +49,22 @@ export class PaymentWriteGLEntriesSubscriber {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles rewrite prepard expense GL entries once the bill payment applying to bills.
|
||||||
|
* @param {IBillPaymentEventCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
private handleWritePrepardExpenseGLEntries = async ({
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
trx,
|
||||||
|
}: IBillPaymentEventCreatedPayload) => {
|
||||||
|
await this.billPaymentGLEntries.rewritePaymentGLEntries(
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle bill payment re-writing journal entries once the payment transaction be edited.
|
* Handle bill payment re-writing journal entries once the payment transaction be edited.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export class CommandBillPaymentDTOTransformer {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms create/edit DTO to model.
|
* Transforms create/edit DTO to model.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -24,17 +27,27 @@ export class CommandBillPaymentDTOTransformer {
|
|||||||
vendor: IVendor,
|
vendor: IVendor,
|
||||||
oldBillPayment?: IBillPayment
|
oldBillPayment?: IBillPayment
|
||||||
): Promise<IBillPayment> {
|
): Promise<IBillPayment> {
|
||||||
const amount =
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
billPaymentDTO.amount ?? sumBy(billPaymentDTO.entries, 'paymentAmount');
|
const appliedAmount = sumBy(billPaymentDTO.entries, 'paymentAmount');
|
||||||
|
|
||||||
|
const hasPrepardExpenses = appliedAmount < billPaymentDTO.amount;
|
||||||
|
const prepardExpensesAccount = hasPrepardExpenses
|
||||||
|
? await accountRepository.findOrCreatePrepardExpenses()
|
||||||
|
: null;
|
||||||
|
const prepardExpensesAccountId =
|
||||||
|
hasPrepardExpenses && prepardExpensesAccount
|
||||||
|
? billPaymentDTO.prepardExpensesAccountId ?? prepardExpensesAccount?.id
|
||||||
|
: billPaymentDTO.prepardExpensesAccountId;
|
||||||
|
|
||||||
const initialDTO = {
|
const initialDTO = {
|
||||||
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
|
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
|
||||||
'paymentDate',
|
'paymentDate',
|
||||||
]),
|
]),
|
||||||
amount,
|
appliedAmount,
|
||||||
currencyCode: vendor.currencyCode,
|
currencyCode: vendor.currencyCode,
|
||||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||||
entries: billPaymentDTO.entries,
|
entries: billPaymentDTO.entries,
|
||||||
|
prepardExpensesAccountId,
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import PromisePool, { ProcessHandler } from '@supercharge/promise-pool';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import {
|
||||||
|
IBillPayment,
|
||||||
|
IBillPrepardExpensesAppliedEventPayload,
|
||||||
|
IPaymentPrepardExpensesAppliedEventPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AutoApplyPrepardExpenses {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto apply prepard expenses to the given bill.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async autoApplyPrepardExpensesToBill(
|
||||||
|
tenantId: number,
|
||||||
|
billId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { BillPayment, Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const bill = await Bill.query(trx).findById(billId).throwIfNotFound();
|
||||||
|
|
||||||
|
const unappliedPayments = await BillPayment.query(trx)
|
||||||
|
.where('vendorId', bill.vendorId)
|
||||||
|
.whereRaw('amount - applied_amount > 0')
|
||||||
|
.whereNotNull('prepardExpensesAccountId');
|
||||||
|
|
||||||
|
let unappliedAmount = bill.total;
|
||||||
|
let appliedTotalAmount = 0; // Total applied amount after applying.
|
||||||
|
|
||||||
|
const precessHandler: ProcessHandler<IBillPayment, void> = async (
|
||||||
|
unappliedPayment: IBillPayment,
|
||||||
|
index: number,
|
||||||
|
pool
|
||||||
|
) => {
|
||||||
|
const appliedAmount = Math.min(unappliedAmount, unappliedPayment.amount);
|
||||||
|
unappliedAmount = unappliedAmount - appliedAmount;
|
||||||
|
appliedTotalAmount += appliedAmount;
|
||||||
|
|
||||||
|
// Stop applying once the unapplied amount reach zero or less.
|
||||||
|
if (appliedAmount <= 0) {
|
||||||
|
pool.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.applyBillToPaymentMade(
|
||||||
|
tenantId,
|
||||||
|
unappliedPayment.id,
|
||||||
|
bill.id,
|
||||||
|
appliedAmount,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
await PromisePool.withConcurrency(1)
|
||||||
|
.for(unappliedPayments)
|
||||||
|
.process(precessHandler);
|
||||||
|
|
||||||
|
// Increase the paid amount of the purchase invoice.
|
||||||
|
await Bill.changePaymentAmount(billId, appliedTotalAmount, trx);
|
||||||
|
|
||||||
|
// Triggers `onBillPrepardExpensesApplied` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onPrepardExpensesApplied, {
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
trx,
|
||||||
|
} as IBillPrepardExpensesAppliedEventPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given bill to payment made transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billPaymentId
|
||||||
|
* @param {number} billId
|
||||||
|
* @param {number} appliedAmount
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public applyBillToPaymentMade = async (
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentId: number,
|
||||||
|
billId: number,
|
||||||
|
appliedAmount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
const { BillPaymentEntry, BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
await BillPaymentEntry.query(trx).insert({
|
||||||
|
billPaymentId,
|
||||||
|
billId,
|
||||||
|
paymentAmount: appliedAmount,
|
||||||
|
});
|
||||||
|
await BillPayment.query(trx).increment('appliedAmount', appliedAmount);
|
||||||
|
|
||||||
|
// Triggers `onBillPaymentPrepardExpensesApplied` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.billPayment.onPrepardExpensesApplied,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
billId,
|
||||||
|
appliedAmount,
|
||||||
|
trx,
|
||||||
|
} as IPaymentPrepardExpensesAppliedEventPayload
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { AutoApplyPrepardExpenses } from '../AutoApplyPrepardExpenses';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { IBillCreatedPayload } from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AutoApplyPrepardExpensesOnBillCreated {
|
||||||
|
@Inject()
|
||||||
|
private autoApplyPrepardExpenses: AutoApplyPrepardExpenses;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.bill.onCreated,
|
||||||
|
this.handleAutoApplyPrepardExpensesOnBillCreated.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the auto apply prepard expenses on bill created.
|
||||||
|
* @param {IBillCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
private async handleAutoApplyPrepardExpensesOnBillCreated({
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
trx,
|
||||||
|
}: IBillCreatedPayload) {
|
||||||
|
await this.autoApplyPrepardExpenses.autoApplyPrepardExpensesToBill(
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import PromisePool, { ProcessHandler } from '@supercharge/promise-pool';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
IPaymentReceive,
|
||||||
|
PaymentReceiveUnearnedRevenueAppliedEventPayload,
|
||||||
|
SaleInvoiceAppliedUnearnedRevenueOnCreatedEventPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AutoApplyUnearnedRevenue {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto apply invoice to advanced payment received transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} invoiceId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async autoApplyUnearnedRevenueToInvoice(
|
||||||
|
tenantId: number,
|
||||||
|
saleInvoiceId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const invoice = await SaleInvoice.query(trx)
|
||||||
|
.findById(saleInvoiceId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const unappliedPayments = await PaymentReceive.query(trx)
|
||||||
|
.where('customerId', invoice.customerId)
|
||||||
|
.whereRaw('amount - applied_amount > 0')
|
||||||
|
.whereNotNull('unearnedRevenueAccountId');
|
||||||
|
|
||||||
|
let unappliedAmount = invoice.total;
|
||||||
|
let appliedTotalAmount = 0; // Total applied amount after applying.
|
||||||
|
|
||||||
|
const processHandler: ProcessHandler<
|
||||||
|
IPaymentReceive,
|
||||||
|
Promise<void>
|
||||||
|
> = async (unappliedPayment: IPaymentReceive, index: number, pool) => {
|
||||||
|
const appliedAmount = Math.min(unappliedAmount, unappliedPayment.amount);
|
||||||
|
unappliedAmount = unappliedAmount - appliedAmount;
|
||||||
|
appliedTotalAmount += appliedAmount;
|
||||||
|
|
||||||
|
// Stop applying once the unapplied amount reache zero or less.
|
||||||
|
if (appliedAmount <= 0) {
|
||||||
|
pool.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.applyInvoiceToPaymentReceived(
|
||||||
|
tenantId,
|
||||||
|
unappliedPayment.id,
|
||||||
|
invoice.id,
|
||||||
|
appliedAmount,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
await PromisePool.withConcurrency(1)
|
||||||
|
.for(unappliedPayments)
|
||||||
|
.process(processHandler);
|
||||||
|
|
||||||
|
// Increase the paid amount of the sale invoice.
|
||||||
|
await SaleInvoice.changePaymentAmount(
|
||||||
|
saleInvoiceId,
|
||||||
|
appliedTotalAmount,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
// Triggers event `onSaleInvoiceUnearnedRevenue`.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleInvoice.onUnearnedRevenueApplied,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId,
|
||||||
|
trx,
|
||||||
|
} as SaleInvoiceAppliedUnearnedRevenueOnCreatedEventPayload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given invoice to payment received transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentReceivedId
|
||||||
|
* @param {number} invoiceId
|
||||||
|
* @param {number} appliedAmount
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public applyInvoiceToPaymentReceived = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
appliedAmount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> => {
|
||||||
|
const { PaymentReceiveEntry, PaymentReceive } =
|
||||||
|
this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
await PaymentReceiveEntry.query(trx).insert({
|
||||||
|
paymentReceiveId,
|
||||||
|
invoiceId,
|
||||||
|
paymentAmount: appliedAmount,
|
||||||
|
});
|
||||||
|
await PaymentReceive.query(trx).increment('appliedAmount', appliedAmount);
|
||||||
|
|
||||||
|
// Triggers the event `onPaymentReceivedUnearnedRevenue`.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.paymentReceive.onUnearnedRevenueApplied,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
saleInvoiceId: invoiceId,
|
||||||
|
appliedAmount,
|
||||||
|
trx,
|
||||||
|
} as PaymentReceiveUnearnedRevenueAppliedEventPayload
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { PaymentReceiveValidators } from './PaymentReceiveValidators';
|
|||||||
import { PaymentReceiveIncrement } from './PaymentReceiveIncrement';
|
import { PaymentReceiveIncrement } from './PaymentReceiveIncrement';
|
||||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||||
import { formatDateFields } from '@/utils';
|
import { formatDateFields } from '@/utils';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentReceiveDTOTransformer {
|
export class PaymentReceiveDTOTransformer {
|
||||||
@@ -23,6 +24,9 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the create payment receive DTO to model object.
|
* Transformes the create payment receive DTO to model object.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -36,9 +40,8 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
||||||
oldPaymentReceive?: IPaymentReceive
|
oldPaymentReceive?: IPaymentReceive
|
||||||
): Promise<IPaymentReceive> {
|
): Promise<IPaymentReceive> {
|
||||||
const amount =
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
paymentReceiveDTO.amount ??
|
const appliedAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
|
||||||
|
|
||||||
// Retreive the next invoice number.
|
// Retreive the next invoice number.
|
||||||
const autoNextNumber =
|
const autoNextNumber =
|
||||||
@@ -52,17 +55,29 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
|
|
||||||
this.validators.validatePaymentNoRequire(paymentReceiveNo);
|
this.validators.validatePaymentNoRequire(paymentReceiveNo);
|
||||||
|
|
||||||
|
const hasUnearnedPayment = appliedAmount < paymentReceiveDTO.amount;
|
||||||
|
const unearnedRevenueAccount = hasUnearnedPayment
|
||||||
|
? await accountRepository.findOrCreateUnearnedRevenue()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const unearnedRevenueAccountId =
|
||||||
|
hasUnearnedPayment && unearnedRevenueAccount
|
||||||
|
? paymentReceiveDTO.unearnedRevenueAccountId ??
|
||||||
|
unearnedRevenueAccount?.id
|
||||||
|
: paymentReceiveDTO.unearnedRevenueAccountId;
|
||||||
|
|
||||||
const initialDTO = {
|
const initialDTO = {
|
||||||
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
||||||
'paymentDate',
|
'paymentDate',
|
||||||
]),
|
]),
|
||||||
amount,
|
appliedAmount,
|
||||||
currencyCode: customer.currencyCode,
|
currencyCode: customer.currencyCode,
|
||||||
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
||||||
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
||||||
entries: paymentReceiveDTO.entries.map((entry) => ({
|
entries: paymentReceiveDTO.entries.map((entry) => ({
|
||||||
...entry,
|
...entry,
|
||||||
})),
|
})),
|
||||||
|
unearnedRevenueAccountId,
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
this.branchDTOTransform.transformDTO<IPaymentReceive>(tenantId)
|
this.branchDTOTransform.transformDTO<IPaymentReceive>(tenantId)
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import { IPaymentReceive, ILedgerEntry, AccountNormal } from '@/interfaces';
|
||||||
IPaymentReceive,
|
|
||||||
ILedgerEntry,
|
|
||||||
AccountNormal,
|
|
||||||
IPaymentReceiveGLCommonEntry,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
import { TenantMetadata } from '@/system/models';
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { PaymentReceivedGLCommon } from './PaymentReceivedGLCommon';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentReceiveGLEntries {
|
export class PaymentReceiveGLEntries extends PaymentReceivedGLCommon {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
@@ -22,9 +17,9 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes payment GL entries to the storage.
|
* Writes payment GL entries to the storage.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} paymentReceiveId
|
* @param {number} paymentReceiveId - Payment received id.
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx - Knex transaction.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public writePaymentGLEntries = async (
|
public writePaymentGLEntries = async (
|
||||||
@@ -34,14 +29,19 @@ export class PaymentReceiveGLEntries {
|
|||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Retrieves the given tenant metadata.
|
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
|
||||||
|
|
||||||
// Retrieves the payment receive with associated entries.
|
// Retrieves the payment receive with associated entries.
|
||||||
const paymentReceive = await PaymentReceive.query(trx)
|
const paymentReceive = await PaymentReceive.query(trx)
|
||||||
.findById(paymentReceiveId)
|
.findById(paymentReceiveId)
|
||||||
.withGraphFetched('entries.invoice');
|
.withGraphFetched('entries.invoice');
|
||||||
|
|
||||||
|
// Cannot continue if the received payment is unearned revenue type,
|
||||||
|
// that type of transactions have different type of GL entries.
|
||||||
|
if (paymentReceive.unearnedRevenueAccountId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Retrieves the given tenant metadata.
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Retrives the payment receive ledger.
|
// Retrives the payment receive ledger.
|
||||||
const ledger = await this.getPaymentReceiveGLedger(
|
const ledger = await this.getPaymentReceiveGLedger(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -53,25 +53,6 @@ export class PaymentReceiveGLEntries {
|
|||||||
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverts the given payment receive GL entries.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} paymentReceiveId
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
*/
|
|
||||||
public revertPaymentGLEntries = async (
|
|
||||||
tenantId: number,
|
|
||||||
paymentReceiveId: number,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) => {
|
|
||||||
await this.ledgerStorage.deleteByReference(
|
|
||||||
tenantId,
|
|
||||||
paymentReceiveId,
|
|
||||||
'PaymentReceive',
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrites the given payment receive GL entries.
|
* Rewrites the given payment receive GL entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -92,10 +73,10 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment receive general ledger.
|
* Retrieves the payment receive general ledger.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {IPaymentReceive} paymentReceive -
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
* @param {string} baseCurrencyCode -
|
* @param {string} baseCurrencyCode -
|
||||||
* @param {Knex.Transaction} trx -
|
* @param {Knex.Transaction} trx -
|
||||||
* @returns {Ledger}
|
* @returns {Ledger}
|
||||||
*/
|
*/
|
||||||
public getPaymentReceiveGLedger = async (
|
public getPaymentReceiveGLedger = async (
|
||||||
@@ -126,100 +107,9 @@ export class PaymentReceiveGLEntries {
|
|||||||
return new Ledger(ledgerEntries);
|
return new Ledger(ledgerEntries);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the payment total exchange gain/loss.
|
|
||||||
* @param {IBillPayment} paymentReceive - Payment receive with entries.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private getPaymentExGainOrLoss = (
|
|
||||||
paymentReceive: IPaymentReceive
|
|
||||||
): number => {
|
|
||||||
return sumBy(paymentReceive.entries, (entry) => {
|
|
||||||
const paymentLocalAmount =
|
|
||||||
entry.paymentAmount * paymentReceive.exchangeRate;
|
|
||||||
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
|
|
||||||
|
|
||||||
return paymentLocalAmount - invoicePayment;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the common entry of payment receive.
|
|
||||||
* @param {IPaymentReceive} paymentReceive
|
|
||||||
* @returns {}
|
|
||||||
*/
|
|
||||||
private getPaymentReceiveCommonEntry = (
|
|
||||||
paymentReceive: IPaymentReceive
|
|
||||||
): IPaymentReceiveGLCommonEntry => {
|
|
||||||
return {
|
|
||||||
debit: 0,
|
|
||||||
credit: 0,
|
|
||||||
|
|
||||||
currencyCode: paymentReceive.currencyCode,
|
|
||||||
exchangeRate: paymentReceive.exchangeRate,
|
|
||||||
|
|
||||||
transactionId: paymentReceive.id,
|
|
||||||
transactionType: 'PaymentReceive',
|
|
||||||
|
|
||||||
transactionNumber: paymentReceive.paymentReceiveNo,
|
|
||||||
referenceNumber: paymentReceive.referenceNo,
|
|
||||||
|
|
||||||
date: paymentReceive.paymentDate,
|
|
||||||
userId: paymentReceive.userId,
|
|
||||||
createdAt: paymentReceive.createdAt,
|
|
||||||
|
|
||||||
branchId: paymentReceive.branchId,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the payment exchange gain/loss entry.
|
|
||||||
* @param {IPaymentReceive} paymentReceive -
|
|
||||||
* @param {number} ARAccountId -
|
|
||||||
* @param {number} exchangeGainOrLossAccountId -
|
|
||||||
* @param {string} baseCurrencyCode -
|
|
||||||
* @returns {ILedgerEntry[]}
|
|
||||||
*/
|
|
||||||
private getPaymentExchangeGainLossEntry = (
|
|
||||||
paymentReceive: IPaymentReceive,
|
|
||||||
ARAccountId: number,
|
|
||||||
exchangeGainOrLossAccountId: number,
|
|
||||||
baseCurrencyCode: string
|
|
||||||
): ILedgerEntry[] => {
|
|
||||||
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
|
|
||||||
const gainOrLoss = this.getPaymentExGainOrLoss(paymentReceive);
|
|
||||||
const absGainOrLoss = Math.abs(gainOrLoss);
|
|
||||||
|
|
||||||
return gainOrLoss
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
...commonJournal,
|
|
||||||
currencyCode: baseCurrencyCode,
|
|
||||||
exchangeRate: 1,
|
|
||||||
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
|
|
||||||
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
|
|
||||||
accountId: ARAccountId,
|
|
||||||
contactId: paymentReceive.customerId,
|
|
||||||
index: 3,
|
|
||||||
accountNormal: AccountNormal.CREDIT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...commonJournal,
|
|
||||||
currencyCode: baseCurrencyCode,
|
|
||||||
exchangeRate: 1,
|
|
||||||
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
|
|
||||||
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
|
|
||||||
accountId: exchangeGainOrLossAccountId,
|
|
||||||
index: 3,
|
|
||||||
accountNormal: AccountNormal.DEBIT,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment deposit GL entry.
|
* Retrieves the payment deposit GL entry.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getPaymentDepositGLEntry = (
|
private getPaymentDepositGLEntry = (
|
||||||
@@ -238,8 +128,8 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment receivable entry.
|
* Retrieves the payment receivable entry.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
* @param {number} ARAccountId
|
* @param {number} ARAccountId
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getPaymentReceivableEntry = (
|
private getPaymentReceivableEntry = (
|
||||||
@@ -262,15 +152,15 @@ export class PaymentReceiveGLEntries {
|
|||||||
* Records payment receive journal transactions.
|
* Records payment receive journal transactions.
|
||||||
*
|
*
|
||||||
* Invoice payment journals.
|
* Invoice payment journals.
|
||||||
* --------
|
* ------------
|
||||||
* - Account receivable -> Debit
|
* - Account Receivable -> Debit
|
||||||
* - Payment account [current asset] -> Credit
|
* - Payment Account [current asset] -> Credit
|
||||||
*
|
*
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IPaymentReceive} paymentRecieve - Payment receive model.
|
* @param {IPaymentReceive} paymentRecieve - Payment receive model.
|
||||||
* @param {number} ARAccountId - A/R account id.
|
* @param {number} ARAccountId - A/R account id.
|
||||||
* @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
|
* @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
|
||||||
* @param {string} baseCurrency - Base currency code.
|
* @param {string} baseCurrency - Base currency code.
|
||||||
* @returns {Promise<ILedgerEntry>}
|
* @returns {Promise<ILedgerEntry>}
|
||||||
*/
|
*/
|
||||||
public getPaymentReceiveGLEntries = (
|
public getPaymentReceiveGLEntries = (
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ export class PaymentReceiveValidators {
|
|||||||
const invoicesIds = paymentReceiveEntries.map(
|
const invoicesIds = paymentReceiveEntries.map(
|
||||||
(e: IPaymentReceiveEntryDTO) => e.invoiceId
|
(e: IPaymentReceiveEntryDTO) => e.invoiceId
|
||||||
);
|
);
|
||||||
|
|
||||||
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
||||||
|
|
||||||
const storedInvoicesMap = new Map(
|
const storedInvoicesMap = new Map(
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
AccountNormal,
|
||||||
|
ILedgerEntry,
|
||||||
|
IPaymentReceive,
|
||||||
|
IPaymentReceiveGLCommonEntry,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
|
|
||||||
|
export class PaymentReceivedGLCommon {
|
||||||
|
private ledgerStorage: LedgerStorageService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the common entry of payment receive.
|
||||||
|
* @param {IPaymentReceive} paymentReceive
|
||||||
|
* @returns {IPaymentReceiveGLCommonEntry}
|
||||||
|
*/
|
||||||
|
protected getPaymentReceiveCommonEntry = (
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): IPaymentReceiveGLCommonEntry => {
|
||||||
|
return {
|
||||||
|
debit: 0,
|
||||||
|
credit: 0,
|
||||||
|
|
||||||
|
currencyCode: paymentReceive.currencyCode,
|
||||||
|
exchangeRate: paymentReceive.exchangeRate,
|
||||||
|
|
||||||
|
transactionId: paymentReceive.id,
|
||||||
|
transactionType: 'PaymentReceive',
|
||||||
|
|
||||||
|
transactionNumber: paymentReceive.paymentReceiveNo,
|
||||||
|
referenceNumber: paymentReceive.referenceNo,
|
||||||
|
|
||||||
|
date: paymentReceive.paymentDate,
|
||||||
|
userId: paymentReceive.userId,
|
||||||
|
createdAt: paymentReceive.createdAt,
|
||||||
|
|
||||||
|
branchId: paymentReceive.branchId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment exchange gain/loss entry.
|
||||||
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
|
* @param {number} ARAccountId -
|
||||||
|
* @param {number} exchangeGainOrLossAccountId -
|
||||||
|
* @param {string} baseCurrencyCode -
|
||||||
|
* @returns {ILedgerEntry[]}
|
||||||
|
*/
|
||||||
|
protected getPaymentExchangeGainLossEntry = (
|
||||||
|
paymentReceive: IPaymentReceive,
|
||||||
|
ARAccountId: number,
|
||||||
|
exchangeGainOrLossAccountId: number,
|
||||||
|
baseCurrencyCode: string
|
||||||
|
): ILedgerEntry[] => {
|
||||||
|
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
|
||||||
|
const gainOrLoss = this.getPaymentExGainOrLoss(paymentReceive);
|
||||||
|
const absGainOrLoss = Math.abs(gainOrLoss);
|
||||||
|
|
||||||
|
return gainOrLoss
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...commonJournal,
|
||||||
|
currencyCode: baseCurrencyCode,
|
||||||
|
exchangeRate: 1,
|
||||||
|
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
|
||||||
|
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
|
||||||
|
accountId: ARAccountId,
|
||||||
|
contactId: paymentReceive.customerId,
|
||||||
|
index: 3,
|
||||||
|
accountNormal: AccountNormal.CREDIT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...commonJournal,
|
||||||
|
currencyCode: baseCurrencyCode,
|
||||||
|
exchangeRate: 1,
|
||||||
|
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
|
||||||
|
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
|
||||||
|
accountId: exchangeGainOrLossAccountId,
|
||||||
|
index: 3,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the payment total exchange gain/loss.
|
||||||
|
* @param {IBillPayment} paymentReceive - Payment receive with entries.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private getPaymentExGainOrLoss = (
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): number => {
|
||||||
|
return sumBy(paymentReceive.entries, (entry) => {
|
||||||
|
const paymentLocalAmount =
|
||||||
|
entry.paymentAmount * paymentReceive.exchangeRate;
|
||||||
|
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
|
||||||
|
|
||||||
|
return paymentLocalAmount - invoicePayment;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the given payment receive GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentReceiveId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public revertPaymentGLEntries = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
await this.ledgerStorage.deleteByReference(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
'PaymentReceive',
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { flatten } from 'lodash';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { PaymentReceivedGLCommon } from './PaymentReceivedGLCommon';
|
||||||
|
import {
|
||||||
|
AccountNormal,
|
||||||
|
ILedgerEntry,
|
||||||
|
IPaymentReceive,
|
||||||
|
IPaymentReceiveEntry,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentReceivedUnearnedGLEntries extends PaymentReceivedGLCommon {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private ledgerStorage: LedgerStorageService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes payment GL entries to the storage.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentReceiveId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public writePaymentGLEntries = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> => {
|
||||||
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieves the payment receive with associated entries.
|
||||||
|
const paymentReceive = await PaymentReceive.query(trx)
|
||||||
|
.findById(paymentReceiveId)
|
||||||
|
.withGraphFetched('entries.invoice');
|
||||||
|
|
||||||
|
// Stop early if
|
||||||
|
if (!paymentReceive.unearnedRevenueAccountId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Retrieves the given tenant metadata.
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
|
const ledger = await this.getPaymentReceiveGLedger(
|
||||||
|
tenantId,
|
||||||
|
paymentReceive
|
||||||
|
);
|
||||||
|
// Commit the ledger entries to the storage.
|
||||||
|
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites the given payment receive GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentReceiveId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public rewritePaymentGLEntries = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
// Reverts the payment GL entries.
|
||||||
|
await this.revertPaymentGLEntries(tenantId, paymentReceiveId, trx);
|
||||||
|
|
||||||
|
// Writes the payment GL entries.
|
||||||
|
await this.writePaymentGLEntries(tenantId, paymentReceiveId, trx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment received GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IPaymentReceive} paymentReceive
|
||||||
|
* @returns {Promise<Ledger>}
|
||||||
|
*/
|
||||||
|
private getPaymentReceiveGLedger = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
) => {
|
||||||
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the A/R account of the given currency.
|
||||||
|
const receivableAccount =
|
||||||
|
await accountRepository.findOrCreateAccountReceivable(
|
||||||
|
paymentReceive.currencyCode
|
||||||
|
);
|
||||||
|
// Retrieve the payment GL entries.
|
||||||
|
const entries = this.getPaymentGLEntries(
|
||||||
|
receivableAccount.id,
|
||||||
|
paymentReceive
|
||||||
|
);
|
||||||
|
const unearnedRevenueEntries =
|
||||||
|
this.getUnearnedRevenueEntries(paymentReceive);
|
||||||
|
|
||||||
|
const combinedEntries = [...unearnedRevenueEntries, ...entries];
|
||||||
|
|
||||||
|
return new Ledger(combinedEntries);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the payment received GL entries.
|
||||||
|
* @param {number} ARAccountId - A/R account id.
|
||||||
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
|
* @returns {Array<ILedgerEntry>}
|
||||||
|
*/
|
||||||
|
private getPaymentGLEntries = R.curry(
|
||||||
|
(
|
||||||
|
ARAccountId: number,
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): Array<ILedgerEntry> => {
|
||||||
|
const getPaymentEntryGLEntries = this.getPaymentEntryGLEntries(
|
||||||
|
ARAccountId,
|
||||||
|
paymentReceive
|
||||||
|
);
|
||||||
|
const entriesGroup = paymentReceive.entries.map((paymentEntry) => {
|
||||||
|
return getPaymentEntryGLEntries(paymentEntry);
|
||||||
|
});
|
||||||
|
return flatten(entriesGroup);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the payment entry GL entries.
|
||||||
|
* @param {IPaymentReceiveEntry} paymentReceivedEntry -
|
||||||
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
|
* @returns {Array<ILedgerEntry>}
|
||||||
|
*/
|
||||||
|
private getPaymentEntryGLEntries = R.curry(
|
||||||
|
(
|
||||||
|
ARAccountId: number,
|
||||||
|
paymentReceive: IPaymentReceive,
|
||||||
|
paymentReceivedEntry: IPaymentReceiveEntry
|
||||||
|
): Array<ILedgerEntry> => {
|
||||||
|
const unearnedRevenueEntry = this.getDebitUnearnedRevenueGLEntry(
|
||||||
|
paymentReceivedEntry.paymentAmount,
|
||||||
|
paymentReceive
|
||||||
|
);
|
||||||
|
const AREntry = this.getPaymentReceivableEntry(
|
||||||
|
paymentReceivedEntry.paymentAmount,
|
||||||
|
paymentReceive,
|
||||||
|
ARAccountId
|
||||||
|
);
|
||||||
|
return [unearnedRevenueEntry, AREntry];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment deposit GL entry.
|
||||||
|
* @param {IPaymentReceive} paymentReceive
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getDebitUnearnedRevenueGLEntry = (
|
||||||
|
amount: number,
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
debit: amount,
|
||||||
|
accountId: paymentReceive.unearnedRevenueAccountId,
|
||||||
|
accountNormal: AccountNormal.CREDIT,
|
||||||
|
index: 2,
|
||||||
|
indexGroup: 20,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment receivable entry.
|
||||||
|
* @param {IPaymentReceive} paymentReceive
|
||||||
|
* @param {number} ARAccountId
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getPaymentReceivableEntry = (
|
||||||
|
amount: number,
|
||||||
|
paymentReceive: IPaymentReceive,
|
||||||
|
ARAccountId: number
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
credit: amount,
|
||||||
|
contactId: paymentReceive.customerId,
|
||||||
|
accountId: ARAccountId,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
index: 1,
|
||||||
|
indexGroup: 20,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the unearned revenue entries.
|
||||||
|
* @param {IPaymentReceive} paymentReceived -
|
||||||
|
* @returns {Array<ILedgerEntry>}
|
||||||
|
*/
|
||||||
|
private getUnearnedRevenueEntries = (
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): Array<ILedgerEntry> => {
|
||||||
|
const depositEntry = this.getDepositPaymentGLEntry(paymentReceive);
|
||||||
|
const unearnedEntry = this.getUnearnedRevenueEntry(paymentReceive);
|
||||||
|
|
||||||
|
return [depositEntry, unearnedEntry];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the payment deposit entry.
|
||||||
|
* @param {IPaymentReceive} paymentReceived -
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getDepositPaymentGLEntry = (
|
||||||
|
paymentReceive: IPaymentReceive
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
debit: paymentReceive.amount,
|
||||||
|
accountId: paymentReceive.depositAccountId,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
indexGroup: 10,
|
||||||
|
index: 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unearned revenue entry.
|
||||||
|
* @param {IPaymentReceive} paymentReceived -
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getUnearnedRevenueEntry = (
|
||||||
|
paymentReceived: IPaymentReceive
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceived);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonJournal,
|
||||||
|
credit: paymentReceived.amount,
|
||||||
|
accountId: paymentReceived.unearnedRevenueAccountId,
|
||||||
|
accountNormal: AccountNormal.CREDIT,
|
||||||
|
indexGroup: 10,
|
||||||
|
index: 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { AutoApplyUnearnedRevenue } from '../AutoApplyUnearnedRevenue';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AutoApplyUnearnedRevenueOnInvoiceCreated {
|
||||||
|
@Inject()
|
||||||
|
private autoApplyUnearnedRevenue: AutoApplyUnearnedRevenue;
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
public attach(bus) {
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onCreated,
|
||||||
|
this.handleAutoApplyUnearnedRevenueOnInvoiceCreated.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the auto apply unearned revenue on invoice creating.
|
||||||
|
* @param
|
||||||
|
*/
|
||||||
|
private async handleAutoApplyUnearnedRevenueOnInvoiceCreated({
|
||||||
|
tenantId,
|
||||||
|
saleInvoice,
|
||||||
|
trx,
|
||||||
|
}) {
|
||||||
|
await this.autoApplyUnearnedRevenue.autoApplyUnearnedRevenueToInvoice(
|
||||||
|
tenantId,
|
||||||
|
saleInvoice.id,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,20 @@ import {
|
|||||||
IPaymentReceiveCreatedPayload,
|
IPaymentReceiveCreatedPayload,
|
||||||
IPaymentReceiveDeletedPayload,
|
IPaymentReceiveDeletedPayload,
|
||||||
IPaymentReceiveEditedPayload,
|
IPaymentReceiveEditedPayload,
|
||||||
|
PaymentReceiveUnearnedRevenueAppliedEventPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { PaymentReceiveGLEntries } from '@/services/Sales/PaymentReceives/PaymentReceiveGLEntries';
|
import { PaymentReceiveGLEntries } from '@/services/Sales/PaymentReceives/PaymentReceiveGLEntries';
|
||||||
|
import { PaymentReceivedUnearnedGLEntries } from '@/services/Sales/PaymentReceives/PaymentReceivedUnearnedGLEntries';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class PaymentReceivesWriteGLEntriesSubscriber {
|
export default class PaymentReceivesWriteGLEntriesSubscriber {
|
||||||
@Inject()
|
@Inject()
|
||||||
private paymentReceiveGLEntries: PaymentReceiveGLEntries;
|
private paymentReceiveGLEntries: PaymentReceiveGLEntries;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private paymentReceivedUnearnedGLEntries: PaymentReceivedUnearnedGLEntries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches events with handlers.
|
* Attaches events with handlers.
|
||||||
*/
|
*/
|
||||||
@@ -32,6 +37,7 @@ export default class PaymentReceivesWriteGLEntriesSubscriber {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle journal entries writing once the payment receive created.
|
* Handle journal entries writing once the payment receive created.
|
||||||
|
* @param {IPaymentReceiveCreatedPayload} payload -
|
||||||
*/
|
*/
|
||||||
private handleWriteJournalEntriesOnceCreated = async ({
|
private handleWriteJournalEntriesOnceCreated = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -43,14 +49,21 @@ export default class PaymentReceivesWriteGLEntriesSubscriber {
|
|||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
await this.paymentReceivedUnearnedGLEntries.writePaymentGLEntries(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle journal entries writing once the payment receive edited.
|
* Handle journal entries writing once the payment receive edited.
|
||||||
|
* @param {IPaymentReceiveEditedPayload} payload -
|
||||||
*/
|
*/
|
||||||
private handleOverwriteJournalEntriesOnceEdited = async ({
|
private handleOverwriteJournalEntriesOnceEdited = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
|
paymentReceiveId,
|
||||||
trx,
|
trx,
|
||||||
}: IPaymentReceiveEditedPayload) => {
|
}: IPaymentReceiveEditedPayload) => {
|
||||||
await this.paymentReceiveGLEntries.rewritePaymentGLEntries(
|
await this.paymentReceiveGLEntries.rewritePaymentGLEntries(
|
||||||
@@ -58,10 +71,16 @@ export default class PaymentReceivesWriteGLEntriesSubscriber {
|
|||||||
paymentReceive.id,
|
paymentReceive.id,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
await this.paymentReceivedUnearnedGLEntries.rewritePaymentGLEntries(
|
||||||
|
tenantId,
|
||||||
|
paymentReceiveId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles revert journal entries once deleted.
|
* Handles revert journal entries once deleted.
|
||||||
|
* @param {IPaymentReceiveDeletedPayload} payload -
|
||||||
*/
|
*/
|
||||||
private handleRevertJournalEntriesOnceDeleted = async ({
|
private handleRevertJournalEntriesOnceDeleted = async ({
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -40,13 +40,6 @@ export default {
|
|||||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* User subscription events.
|
|
||||||
*/
|
|
||||||
subscription: {
|
|
||||||
onSubscribed: 'onOrganizationSubscribed',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenants managment service.
|
* Tenants managment service.
|
||||||
*/
|
*/
|
||||||
@@ -149,6 +142,8 @@ export default {
|
|||||||
|
|
||||||
onMailReminderSend: 'onSaleInvoiceMailReminderSend',
|
onMailReminderSend: 'onSaleInvoiceMailReminderSend',
|
||||||
onMailReminderSent: 'onSaleInvoiceMailReminderSent',
|
onMailReminderSent: 'onSaleInvoiceMailReminderSent',
|
||||||
|
|
||||||
|
onUnearnedRevenueApplied: 'onSaleInvoiceUnearnedRevenue',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,6 +232,8 @@ export default {
|
|||||||
onPreMailSend: 'onPaymentReceivePreMailSend',
|
onPreMailSend: 'onPaymentReceivePreMailSend',
|
||||||
onMailSend: 'onPaymentReceiveMailSend',
|
onMailSend: 'onPaymentReceiveMailSend',
|
||||||
onMailSent: 'onPaymentReceiveMailSent',
|
onMailSent: 'onPaymentReceiveMailSent',
|
||||||
|
|
||||||
|
onUnearnedRevenueApplied: 'onPaymentReceivedUnearnedRevenue',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,6 +254,8 @@ export default {
|
|||||||
|
|
||||||
onOpening: 'onBillOpening',
|
onOpening: 'onBillOpening',
|
||||||
onOpened: 'onBillOpened',
|
onOpened: 'onBillOpened',
|
||||||
|
|
||||||
|
onPrepardExpensesApplied: 'onBillPrepardExpensesApplied'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,6 +273,8 @@ export default {
|
|||||||
|
|
||||||
onPublishing: 'onBillPaymentPublishing',
|
onPublishing: 'onBillPaymentPublishing',
|
||||||
onPublished: 'onBillPaymentPublished',
|
onPublished: 'onBillPaymentPublished',
|
||||||
|
|
||||||
|
onPrepardExpensesApplied: 'onBillPaymentPrepardExpensesApplied'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -658,11 +659,6 @@ export default {
|
|||||||
onUnexcluded: 'onBankTransactionUnexcluded',
|
onUnexcluded: 'onBankTransactionUnexcluded',
|
||||||
},
|
},
|
||||||
|
|
||||||
bankAccount: {
|
|
||||||
onDisconnecting: 'onBankAccountDisconnecting',
|
|
||||||
onDisconnected: 'onBankAccountDisconnected',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Import files.
|
// Import files.
|
||||||
import: {
|
import: {
|
||||||
onImportCommitted: 'onImportFileCommitted',
|
onImportCommitted: 'onImportFileCommitted',
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
import { Position, Toaster, Intent } from '@blueprintjs/core';
|
import { Position, Toaster, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
export const AppToaster = Toaster.create({
|
export const AppToaster = Toaster.create({
|
||||||
position: Position.TOP,
|
position: Position.RIGHT_BOTTOM,
|
||||||
intent: Intent.WARNING,
|
intent: Intent.WARNING,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DashboardPageContent } from '@/components';
|
|||||||
import { transformTableStateToQuery, compose } from '@/utils';
|
import { transformTableStateToQuery, compose } from '@/utils';
|
||||||
|
|
||||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||||
|
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||||
import withManualJournals from './withManualJournals';
|
import withManualJournals from './withManualJournals';
|
||||||
@@ -28,6 +29,7 @@ function ManualJournalsTable({
|
|||||||
<ManualJournalsActionsBar />
|
<ManualJournalsActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<ManualJournalsViewTabs />
|
||||||
<ManualJournalsDataTable />
|
<ManualJournalsDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</ManualJournalsListProvider>
|
</ManualJournalsListProvider>
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import '@/style/pages/Accounts/List.scss';
|
import '@/style/pages/Accounts/List.scss';
|
||||||
|
|
||||||
import { DashboardPageContent, DashboardContentTable } from '@/components';
|
import { DashboardPageContent, DashboardContentTable } from '@/components';
|
||||||
|
|
||||||
import { AccountsChartProvider } from './AccountsChartProvider';
|
import { AccountsChartProvider } from './AccountsChartProvider';
|
||||||
|
import AccountsViewsTabs from './AccountsViewsTabs';
|
||||||
import AccountsActionsBar from './AccountsActionsBar';
|
import AccountsActionsBar from './AccountsActionsBar';
|
||||||
import AccountsDataTable from './AccountsDataTable';
|
import AccountsDataTable from './AccountsDataTable';
|
||||||
|
|
||||||
import withAccounts from '@/containers/Accounts/withAccounts';
|
import withAccounts from '@/containers/Accounts/withAccounts';
|
||||||
import withAccountsTableActions from './withAccountsTableActions';
|
import withAccountsTableActions from './withAccountsTableActions';
|
||||||
|
|
||||||
import { transformAccountsStateToQuery } from './utils';
|
import { transformAccountsStateToQuery } from './utils';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
@@ -41,6 +41,8 @@ function AccountsChart({
|
|||||||
<AccountsActionsBar />
|
<AccountsActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<AccountsViewsTabs />
|
||||||
|
|
||||||
<DashboardContentTable>
|
<DashboardContentTable>
|
||||||
<AccountsDataTable />
|
<AccountsDataTable />
|
||||||
</DashboardContentTable>
|
</DashboardContentTable>
|
||||||
|
|||||||
@@ -11,19 +11,13 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Intent,
|
|
||||||
Tooltip,
|
|
||||||
MenuDivider,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
DashboardActionsBar,
|
DashboardActionsBar,
|
||||||
DashboardRowsHeightButton,
|
DashboardRowsHeightButton,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
AppToaster,
|
|
||||||
If,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
|
||||||
import { CashFlowMenuItems } from './utils';
|
import { CashFlowMenuItems } from './utils';
|
||||||
@@ -39,13 +33,6 @@ import withSettings from '@/containers/Settings/withSettings';
|
|||||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||||
|
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import {
|
|
||||||
useDisconnectBankAccount,
|
|
||||||
useUpdateBankAccount,
|
|
||||||
useExcludeUncategorizedTransactions,
|
|
||||||
useUnexcludeUncategorizedTransactions,
|
|
||||||
} from '@/hooks/query/bank-rules';
|
|
||||||
import { withBanking } from '../withBanking';
|
|
||||||
|
|
||||||
function AccountTransactionsActionsBar({
|
function AccountTransactionsActionsBar({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
@@ -56,27 +43,17 @@ function AccountTransactionsActionsBar({
|
|||||||
|
|
||||||
// #withSettingsActions
|
// #withSettingsActions
|
||||||
addSetting,
|
addSetting,
|
||||||
|
|
||||||
// #withBanking
|
|
||||||
uncategorizedTransationsIdsSelected,
|
|
||||||
excludedTransactionsIdsSelected,
|
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { accountId, currentAccount } = useAccountTransactionsContext();
|
const { accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
// Refresh cashflow infinity transactions hook.
|
// Refresh cashflow infinity transactions hook.
|
||||||
const { refresh } = useRefreshCashflowTransactionsInfinity();
|
const { refresh } = useRefreshCashflowTransactionsInfinity();
|
||||||
|
|
||||||
const { mutateAsync: disconnectBankAccount } = useDisconnectBankAccount();
|
|
||||||
const { mutateAsync: updateBankAccount } = useUpdateBankAccount();
|
|
||||||
|
|
||||||
// Retrieves the money in/out buttons options.
|
// Retrieves the money in/out buttons options.
|
||||||
const addMoneyInOptions = useMemo(() => getAddMoneyInOptions(), []);
|
const addMoneyInOptions = useMemo(() => getAddMoneyInOptions(), []);
|
||||||
const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []);
|
const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []);
|
||||||
|
|
||||||
const isFeedsActive = !!currentAccount.is_feeds_active;
|
|
||||||
const isSyncingOwner = currentAccount.is_syncing_owner;
|
|
||||||
|
|
||||||
// Handle table row size change.
|
// Handle table row size change.
|
||||||
const handleTableRowSizeChange = (size) => {
|
const handleTableRowSizeChange = (size) => {
|
||||||
addSetting('cashflowTransactions', 'tableSize', size);
|
addSetting('cashflowTransactions', 'tableSize', size);
|
||||||
@@ -105,92 +82,11 @@ function AccountTransactionsActionsBar({
|
|||||||
const handleBankRulesClick = () => {
|
const handleBankRulesClick = () => {
|
||||||
history.push(`/bank-rules?accountId=${accountId}`);
|
history.push(`/bank-rules?accountId=${accountId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the bank account disconnect click.
|
|
||||||
const handleDisconnectClick = () => {
|
|
||||||
disconnectBankAccount({ bankAccountId: accountId })
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The bank account has been disconnected.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// handles the bank update button click.
|
|
||||||
const handleBankUpdateClick = () => {
|
|
||||||
updateBankAccount({ bankAccountId: accountId })
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The transactions of the bank account has been updated.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Handle the refresh button click.
|
// Handle the refresh button click.
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => {
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
|
||||||
mutateAsync: excludeUncategorizedTransactions,
|
|
||||||
isLoading: isExcludingLoading,
|
|
||||||
} = useExcludeUncategorizedTransactions();
|
|
||||||
|
|
||||||
const {
|
|
||||||
mutateAsync: unexcludeUncategorizedTransactions,
|
|
||||||
isLoading: isUnexcludingLoading,
|
|
||||||
} = useUnexcludeUncategorizedTransactions();
|
|
||||||
|
|
||||||
// Handles the exclude uncategorized transactions in bulk.
|
|
||||||
const handleExcludeUncategorizedBtnClick = () => {
|
|
||||||
excludeUncategorizedTransactions({
|
|
||||||
ids: uncategorizedTransationsIdsSelected,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The selected transactions have been excluded.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles the unexclude categorized button click.
|
|
||||||
const handleUnexcludeUncategorizedBtnClick = () => {
|
|
||||||
unexcludeUncategorizedTransactions({
|
|
||||||
ids: excludedTransactionsIdsSelected,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The selected excluded transactions have been unexcluded.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -233,45 +129,6 @@ function AccountTransactionsActionsBar({
|
|||||||
onChange={handleTableRowSizeChange}
|
onChange={handleTableRowSizeChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={isSyncingOwner}>
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
isFeedsActive
|
|
||||||
? 'The bank syncing is active'
|
|
||||||
: 'The bank syncing is disconnected'
|
|
||||||
}
|
|
||||||
minimal={true}
|
|
||||||
position={Position.BOTTOM}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon={<Icon icon="feed" iconSize={16} />}
|
|
||||||
intent={isFeedsActive ? Intent.SUCCESS : Intent.DANGER}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
{!isEmpty(uncategorizedTransationsIdsSelected) && (
|
|
||||||
<Button
|
|
||||||
icon={<Icon icon="disable" iconSize={16} />}
|
|
||||||
text={'Exclude'}
|
|
||||||
onClick={handleExcludeUncategorizedBtnClick}
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
disabled={isExcludingLoading}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isEmpty(excludedTransactionsIdsSelected) && (
|
|
||||||
<Button
|
|
||||||
icon={<Icon icon="disable" iconSize={16} />}
|
|
||||||
text={'Unexclude'}
|
|
||||||
onClick={handleUnexcludeUncategorizedBtnClick}
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
disabled={isUnexcludingLoading}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
|
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
@@ -284,15 +141,7 @@ function AccountTransactionsActionsBar({
|
|||||||
}}
|
}}
|
||||||
content={
|
content={
|
||||||
<Menu>
|
<Menu>
|
||||||
<If condition={isSyncingOwner && isFeedsActive}>
|
|
||||||
<MenuItem onClick={handleBankUpdateClick} text={'Update'} />
|
|
||||||
<MenuDivider />
|
|
||||||
</If>
|
|
||||||
<MenuItem onClick={handleBankRulesClick} text={'Bank rules'} />
|
<MenuItem onClick={handleBankRulesClick} text={'Bank rules'} />
|
||||||
|
|
||||||
<If condition={isSyncingOwner && isFeedsActive}>
|
|
||||||
<MenuItem onClick={handleDisconnectClick} text={'Disconnect'} />
|
|
||||||
</If>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -315,13 +164,4 @@ export default compose(
|
|||||||
withSettings(({ cashflowTransactionsSettings }) => ({
|
withSettings(({ cashflowTransactionsSettings }) => ({
|
||||||
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
||||||
})),
|
})),
|
||||||
withBanking(
|
|
||||||
({
|
|
||||||
uncategorizedTransationsIdsSelected,
|
|
||||||
excludedTransactionsIdsSelected,
|
|
||||||
}) => ({
|
|
||||||
uncategorizedTransationsIdsSelected,
|
|
||||||
excludedTransactionsIdsSelected,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(AccountTransactionsActionsBar);
|
)(AccountTransactionsActionsBar);
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ function AccountTransactionsDataTable({
|
|||||||
|
|
||||||
// #withBankingActions
|
// #withBankingActions
|
||||||
setUncategorizedTransactionIdForMatching,
|
setUncategorizedTransactionIdForMatching,
|
||||||
setUncategorizedTransactionsSelected,
|
|
||||||
}) {
|
}) {
|
||||||
// Retrieve table columns.
|
// Retrieve table columns.
|
||||||
const columns = useAccountUncategorizedTransactionsColumns();
|
const columns = useAccountUncategorizedTransactionsColumns();
|
||||||
@@ -74,19 +73,12 @@ function AccountTransactionsDataTable({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle selected rows change.
|
|
||||||
const handleSelectedRowsChange = (selected) => {
|
|
||||||
const _selectedIds = selected?.map((row) => row.original.id);
|
|
||||||
setUncategorizedTransactionsSelected(_selectedIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CashflowTransactionsTable
|
<CashflowTransactionsTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={uncategorizedTransactions || []}
|
data={uncategorizedTransactions || []}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
selectionColumn={true}
|
|
||||||
loading={isUncategorizedTransactionsLoading}
|
loading={isUncategorizedTransactionsLoading}
|
||||||
headerLoading={isUncategorizedTransactionsLoading}
|
headerLoading={isUncategorizedTransactionsLoading}
|
||||||
expandColumnSpace={1}
|
expandColumnSpace={1}
|
||||||
@@ -107,7 +99,6 @@ function AccountTransactionsDataTable({
|
|||||||
'There is no uncategorized transactions in the current account.'
|
'There is no uncategorized transactions in the current account.'
|
||||||
}
|
}
|
||||||
className="table-constrant"
|
className="table-constrant"
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
|
||||||
payload={{
|
payload={{
|
||||||
onExclude: handleExcludeTransaction,
|
onExclude: handleExcludeTransaction,
|
||||||
onCategorize: handleCategorizeBtnClick,
|
onCategorize: handleCategorizeBtnClick,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import * as R from 'ramda';
|
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
TableFastCell,
|
TableFastCell,
|
||||||
@@ -19,20 +19,11 @@ import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
|
|||||||
|
|
||||||
import { ActionsMenu } from './_components';
|
import { ActionsMenu } from './_components';
|
||||||
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||||
import {
|
|
||||||
WithBankingActionsProps,
|
|
||||||
withBankingActions,
|
|
||||||
} from '../../withBankingActions';
|
|
||||||
|
|
||||||
interface ExcludeTransactionsTableProps extends WithBankingActionsProps {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the recognized account transactions datatable.
|
* Renders the recognized account transactions datatable.
|
||||||
*/
|
*/
|
||||||
function ExcludedTransactionsTableRoot({
|
export function ExcludedTransactionsTable() {
|
||||||
// #withBankingActions
|
|
||||||
setExcludedTransactionsSelected,
|
|
||||||
}: ExcludeTransactionsTableProps) {
|
|
||||||
const { excludedBankTransactions } = useExcludedTransactionsBoot();
|
const { excludedBankTransactions } = useExcludedTransactionsBoot();
|
||||||
const { mutateAsync: unexcludeBankTransaction } =
|
const { mutateAsync: unexcludeBankTransaction } =
|
||||||
useUnexcludeUncategorizedTransaction();
|
useUnexcludeUncategorizedTransaction();
|
||||||
@@ -64,12 +55,6 @@ function ExcludedTransactionsTableRoot({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle selected rows change.
|
|
||||||
const handleSelectedRowsChange = (selected) => {
|
|
||||||
const _selectedIds = selected?.map((row) => row.original.id);
|
|
||||||
setExcludedTransactionsSelected(_selectedIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CashflowTransactionsTable
|
<CashflowTransactionsTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
@@ -95,8 +80,6 @@ function ExcludedTransactionsTableRoot({
|
|||||||
onColumnResizing={handleColumnResizing}
|
onColumnResizing={handleColumnResizing}
|
||||||
noResults={'There is no excluded bank transactions.'}
|
noResults={'There is no excluded bank transactions.'}
|
||||||
className="table-constrant"
|
className="table-constrant"
|
||||||
selectionColumn={true}
|
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
|
||||||
payload={{
|
payload={{
|
||||||
onRestore: handleRestoreClick,
|
onRestore: handleRestoreClick,
|
||||||
}}
|
}}
|
||||||
@@ -104,10 +87,6 @@ function ExcludedTransactionsTableRoot({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExcludedTransactionsTable = R.compose(withBankingActions)(
|
|
||||||
ExcludedTransactionsTableRoot,
|
|
||||||
);
|
|
||||||
|
|
||||||
const DashboardConstrantTable = styled(DataTable)`
|
const DashboardConstrantTable = styled(DataTable)`
|
||||||
.table {
|
.table {
|
||||||
.thead {
|
.thead {
|
||||||
|
|||||||
@@ -1,27 +1,8 @@
|
|||||||
// @ts-nocheck
|
import { ExcludedTransactionsTable } from "../ExcludedTransactions/ExcludedTransactionsTable";
|
||||||
import { useEffect } from 'react';
|
import { ExcludedBankTransactionsTableBoot } from "../ExcludedTransactions/ExcludedTransactionsTableBoot";
|
||||||
import * as R from 'ramda';
|
import { AccountTransactionsCard } from "./AccountTransactionsCard";
|
||||||
import {
|
|
||||||
WithBankingActionsProps,
|
|
||||||
withBankingActions,
|
|
||||||
} from '../../withBankingActions';
|
|
||||||
import { ExcludedTransactionsTable } from '../ExcludedTransactions/ExcludedTransactionsTable';
|
|
||||||
import { ExcludedBankTransactionsTableBoot } from '../ExcludedTransactions/ExcludedTransactionsTableBoot';
|
|
||||||
import { AccountTransactionsCard } from './AccountTransactionsCard';
|
|
||||||
|
|
||||||
interface AccountExcludedTransactionsProps extends WithBankingActionsProps {}
|
|
||||||
|
|
||||||
function AccountExcludedTransactionsRoot({
|
|
||||||
// #withBankingActions
|
|
||||||
resetExcludedTransactionsSelected,
|
|
||||||
}: AccountExcludedTransactionsProps) {
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
resetExcludedTransactionsSelected();
|
|
||||||
},
|
|
||||||
[resetExcludedTransactionsSelected],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
export function AccountExcludedTransactions() {
|
||||||
return (
|
return (
|
||||||
<ExcludedBankTransactionsTableBoot>
|
<ExcludedBankTransactionsTableBoot>
|
||||||
<AccountTransactionsCard>
|
<AccountTransactionsCard>
|
||||||
@@ -30,7 +11,3 @@ function AccountExcludedTransactionsRoot({
|
|||||||
</ExcludedBankTransactionsTableBoot>
|
</ExcludedBankTransactionsTableBoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountExcludedTransactions = R.compose(withBankingActions)(
|
|
||||||
AccountExcludedTransactionsRoot,
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,26 +1,8 @@
|
|||||||
import * as R from 'ramda';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import AccountTransactionsUncategorizedTable from '../AccountTransactionsUncategorizedTable';
|
import AccountTransactionsUncategorizedTable from '../AccountTransactionsUncategorizedTable';
|
||||||
import { AccountUncategorizedTransactionsBoot } from '../AllTransactionsUncategorizedBoot';
|
import { AccountUncategorizedTransactionsBoot } from '../AllTransactionsUncategorizedBoot';
|
||||||
import { AccountTransactionsCard } from './AccountTransactionsCard';
|
import { AccountTransactionsCard } from './AccountTransactionsCard';
|
||||||
import {
|
|
||||||
WithBankingActionsProps,
|
|
||||||
withBankingActions,
|
|
||||||
} from '../../withBankingActions';
|
|
||||||
|
|
||||||
interface AccountUncategorizedTransactionsAllRootProps
|
|
||||||
extends WithBankingActionsProps {}
|
|
||||||
|
|
||||||
function AccountUncategorizedTransactionsAllRoot({
|
|
||||||
resetUncategorizedTransactionsSelected,
|
|
||||||
}: AccountUncategorizedTransactionsAllRootProps) {
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
resetUncategorizedTransactionsSelected();
|
|
||||||
},
|
|
||||||
[resetUncategorizedTransactionsSelected],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
export function AccountUncategorizedTransactionsAll() {
|
||||||
return (
|
return (
|
||||||
<AccountUncategorizedTransactionsBoot>
|
<AccountUncategorizedTransactionsBoot>
|
||||||
<AccountTransactionsCard>
|
<AccountTransactionsCard>
|
||||||
@@ -29,7 +11,3 @@ function AccountUncategorizedTransactionsAllRoot({
|
|||||||
</AccountUncategorizedTransactionsBoot>
|
</AccountUncategorizedTransactionsBoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountUncategorizedTransactionsAll = R.compose(
|
|
||||||
withBankingActions,
|
|
||||||
)(AccountUncategorizedTransactionsAllRoot);
|
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ export const withBanking = (mapState) => {
|
|||||||
|
|
||||||
reconcileMatchingTransactionPendingAmount:
|
reconcileMatchingTransactionPendingAmount:
|
||||||
state.plaid.openReconcileMatchingTransaction.pending,
|
state.plaid.openReconcileMatchingTransaction.pending,
|
||||||
|
|
||||||
uncategorizedTransationsIdsSelected:
|
|
||||||
state.plaid.uncategorizedTransactionsSelected,
|
|
||||||
|
|
||||||
excludedTransactionsIdsSelected: state.plaid.excludedTransactionsSelected,
|
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import {
|
|||||||
setUncategorizedTransactionIdForMatching,
|
setUncategorizedTransactionIdForMatching,
|
||||||
openReconcileMatchingTransaction,
|
openReconcileMatchingTransaction,
|
||||||
closeReconcileMatchingTransaction,
|
closeReconcileMatchingTransaction,
|
||||||
setUncategorizedTransactionsSelected,
|
|
||||||
resetUncategorizedTransactionsSelected,
|
|
||||||
resetExcludedTransactionsSelected,
|
|
||||||
setExcludedTransactionsSelected,
|
|
||||||
} from '@/store/banking/banking.reducer';
|
} from '@/store/banking/banking.reducer';
|
||||||
|
|
||||||
export interface WithBankingActionsProps {
|
export interface WithBankingActionsProps {
|
||||||
@@ -17,12 +13,6 @@ export interface WithBankingActionsProps {
|
|||||||
) => void;
|
) => void;
|
||||||
openReconcileMatchingTransaction: (pendingAmount: number) => void;
|
openReconcileMatchingTransaction: (pendingAmount: number) => void;
|
||||||
closeReconcileMatchingTransaction: () => void;
|
closeReconcileMatchingTransaction: () => void;
|
||||||
|
|
||||||
setUncategorizedTransactionsSelected: (ids: Array<string | number>) => void;
|
|
||||||
resetUncategorizedTransactionsSelected: () => void;
|
|
||||||
|
|
||||||
setExcludedTransactionsSelected: (ids: Array<string | number>) => void;
|
|
||||||
resetExcludedTransactionsSelected: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||||
@@ -38,40 +28,6 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
|||||||
dispatch(openReconcileMatchingTransaction({ pending: pendingAmount })),
|
dispatch(openReconcileMatchingTransaction({ pending: pendingAmount })),
|
||||||
closeReconcileMatchingTransaction: () =>
|
closeReconcileMatchingTransaction: () =>
|
||||||
dispatch(closeReconcileMatchingTransaction()),
|
dispatch(closeReconcileMatchingTransaction()),
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected uncategorized transactions.
|
|
||||||
* @param {Array<string | number>} ids
|
|
||||||
*/
|
|
||||||
setUncategorizedTransactionsSelected: (ids: Array<string | number>) =>
|
|
||||||
dispatch(
|
|
||||||
setUncategorizedTransactionsSelected({
|
|
||||||
transactionIds: ids,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the selected uncategorized transactions.
|
|
||||||
*/
|
|
||||||
resetUncategorizedTransactionsSelected: () =>
|
|
||||||
dispatch(resetUncategorizedTransactionsSelected()),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets excluded selected transactions.
|
|
||||||
* @param {Array<string | number>} ids
|
|
||||||
*/
|
|
||||||
setExcludedTransactionsSelected: (ids: Array<string | number>) =>
|
|
||||||
dispatch(
|
|
||||||
setExcludedTransactionsSelected({
|
|
||||||
ids,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the excluded selected transactions
|
|
||||||
*/
|
|
||||||
resetExcludedTransactionsSelected: () =>
|
|
||||||
dispatch(resetExcludedTransactionsSelected()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const withBankingActions = connect<
|
export const withBankingActions = connect<
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '@/style/pages/Customers/List.scss';
|
|||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
|
|
||||||
import CustomersActionsBar from './CustomersActionsBar';
|
import CustomersActionsBar from './CustomersActionsBar';
|
||||||
|
import CustomersViewsTabs from './CustomersViewsTabs';
|
||||||
import CustomersTable from './CustomersTable';
|
import CustomersTable from './CustomersTable';
|
||||||
import { CustomersListProvider } from './CustomersListProvider';
|
import { CustomersListProvider } from './CustomersListProvider';
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ function CustomersList({
|
|||||||
<CustomersActionsBar />
|
<CustomersActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<CustomersViewsTabs />
|
||||||
<CustomersTable />
|
<CustomersTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</CustomersListProvider>
|
</CustomersListProvider>
|
||||||
|
|||||||
@@ -160,6 +160,13 @@ export function useCustomersTableColumns() {
|
|||||||
width: 85,
|
width: 85,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'credit_balance',
|
||||||
|
Header: 'Credit Balance',
|
||||||
|
accessor: 'formatted_unused_credit',
|
||||||
|
width: 100,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
Header: intl.get('receivable_balance'),
|
Header: intl.get('receivable_balance'),
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import React from 'react';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { omit } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
import { CreateQuickPaymentMadeFormSchema } from './QuickPaymentMade.schema';
|
import { CreateQuickPaymentMadeFormSchema } from './QuickPaymentMade.schema';
|
||||||
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
|
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
|
||||||
import QuickPaymentMadeFormContent from './QuickPaymentMadeFormContent';
|
import QuickPaymentMadeFormContent from './QuickPaymentMadeFormContent';
|
||||||
|
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import { defaultPaymentMade, transformBillToForm, transformErrors } from './utils';
|
import { defaultPaymentMade, transformErrors } from './utils';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,24 +21,31 @@ function QuickPaymentMadeForm({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { bill, dialogName, createPaymentMadeMutate } =
|
|
||||||
useQuickPaymentMadeContext();
|
const {
|
||||||
|
bill,
|
||||||
|
dialogName,
|
||||||
|
createPaymentMadeMutate,
|
||||||
|
} = useQuickPaymentMadeContext();
|
||||||
|
|
||||||
// Initial form values.
|
// Initial form values
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
...defaultPaymentMade,
|
...defaultPaymentMade,
|
||||||
...transformBillToForm(bill),
|
...bill,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
|
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
|
||||||
const entries = [
|
const entries = [values]
|
||||||
{
|
.filter((entry) => entry.id && entry.payment_amount)
|
||||||
payment_amount: values.amount,
|
.map((entry) => ({
|
||||||
bill_id: values.bill_id,
|
bill_id: entry.id,
|
||||||
},
|
...pick(entry, ['payment_amount']),
|
||||||
];
|
}));
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
...omit(values, ['bill_id']),
|
...values,
|
||||||
|
vendor_id: values?.vendor?.id,
|
||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function QuickPaymentMadeFormFields({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/*------------ Amount Received -----------*/}
|
{/*------------ Amount Received -----------*/}
|
||||||
<FastField name={'amount'}>
|
<FastField name={'payment_amount'}>
|
||||||
{({
|
{({
|
||||||
form: { values, setFieldValue },
|
form: { values, setFieldValue },
|
||||||
field: { value },
|
field: { value },
|
||||||
@@ -135,7 +135,7 @@ function QuickPaymentMadeFormFields({
|
|||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--payment_amount', CLASSES.FILL)}
|
className={classNames('form-group--payment_amount', CLASSES.FILL)}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name="amount" />}
|
helperText={<ErrorMessage name="payment_amount" />}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputPrependText text={values.currency_code} />
|
<InputPrependText text={values.currency_code} />
|
||||||
@@ -144,7 +144,7 @@ function QuickPaymentMadeFormFields({
|
|||||||
value={value}
|
value={value}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
onChange={(amount) => {
|
onChange={(amount) => {
|
||||||
setFieldValue('amount', amount);
|
setFieldValue('payment_amount', amount);
|
||||||
}}
|
}}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
inputRef={(ref) => (paymentMadeFieldRef.current = ref)}
|
inputRef={(ref) => (paymentMadeFieldRef.current = ref)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { DialogContent } from '@/components';
|
import { DialogContent } from '@/components';
|
||||||
import {
|
import {
|
||||||
useBill,
|
useBill,
|
||||||
@@ -11,6 +11,7 @@ import { Features } from '@/constants';
|
|||||||
import { useFeatureCan } from '@/hooks/state';
|
import { useFeatureCan } from '@/hooks/state';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
|
|
||||||
const QuickPaymentMadeContext = React.createContext();
|
const QuickPaymentMadeContext = React.createContext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,14 +40,13 @@ function QuickPaymentMadeFormProvider({ query, billId, dialogName, ...props }) {
|
|||||||
isSuccess: isBranchesSuccess,
|
isSuccess: isBranchesSuccess,
|
||||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||||
|
|
||||||
const paymentBill = useMemo(
|
|
||||||
() => pick(bill, ['id', 'due_amount', 'vendor_id', 'currency_code']),
|
|
||||||
[bill],
|
|
||||||
);
|
|
||||||
|
|
||||||
// State provider.
|
// State provider.
|
||||||
const provider = {
|
const provider = {
|
||||||
bill: paymentBill,
|
bill: {
|
||||||
|
...pick(bill, ['id', 'due_amount', 'vendor', 'currency_code']),
|
||||||
|
vendor_id: bill?.vendor?.display_name,
|
||||||
|
payment_amount: bill?.due_amount,
|
||||||
|
},
|
||||||
accounts,
|
accounts,
|
||||||
branches,
|
branches,
|
||||||
dialogName,
|
dialogName,
|
||||||
|
|||||||
@@ -2,25 +2,24 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { first, pick } from 'lodash';
|
import { first } from 'lodash';
|
||||||
import { useFormikContext } from 'formik';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
|
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
|
||||||
import { PAYMENT_MADE_ERRORS } from '@/containers/Purchases/PaymentMades/constants';
|
import { PAYMENT_MADE_ERRORS } from '@/containers/Purchases/PaymentMades/constants';
|
||||||
|
|
||||||
// Default initial values of payment made.
|
// Default initial values of payment made.
|
||||||
export const defaultPaymentMade = {
|
export const defaultPaymentMade = {
|
||||||
bill_id: '',
|
|
||||||
vendor_id: '',
|
vendor_id: '',
|
||||||
payment_account_id: '',
|
payment_account_id: '',
|
||||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
reference: '',
|
reference: '',
|
||||||
payment_number: '',
|
payment_number: '',
|
||||||
amount: '',
|
|
||||||
// statement: '',
|
// statement: '',
|
||||||
exchange_rate: 1,
|
exchange_rate: 1,
|
||||||
branch_id: '',
|
branch_id: '',
|
||||||
|
entries: [{ bill_id: '', payment_amount: '' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformErrors = (errors, { setFieldError }) => {
|
export const transformErrors = (errors, { setFieldError }) => {
|
||||||
@@ -59,11 +58,3 @@ export const useSetPrimaryBranchToForm = () => {
|
|||||||
}
|
}
|
||||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformBillToForm = (bill) => {
|
|
||||||
return {
|
|
||||||
...pick(bill, ['vendor_id', 'currency_code']),
|
|
||||||
amount: bill.due_amount,
|
|
||||||
bill_id: bill.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { defaultTo, omit } from 'lodash';
|
import { pick, defaultTo, omit } from 'lodash';
|
||||||
|
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
|
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
|
||||||
@@ -12,11 +12,7 @@ import QuickPaymentReceiveFormContent from './QuickPaymentReceiveFormContent';
|
|||||||
|
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import {
|
import { defaultInitialValues, transformErrors } from './utils';
|
||||||
defaultInitialValues,
|
|
||||||
transformErrors,
|
|
||||||
transformInvoiceToForm,
|
|
||||||
} from './utils';
|
|
||||||
import { compose, transactionNumber } from '@/utils';
|
import { compose, transactionNumber } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,10 +26,14 @@ function QuickPaymentReceiveForm({
|
|||||||
paymentReceiveAutoIncrement,
|
paymentReceiveAutoIncrement,
|
||||||
paymentReceiveNumberPrefix,
|
paymentReceiveNumberPrefix,
|
||||||
paymentReceiveNextNumber,
|
paymentReceiveNextNumber,
|
||||||
preferredDepositAccount,
|
preferredDepositAccount
|
||||||
}) {
|
}) {
|
||||||
const { dialogName, invoice, createPaymentReceiveMutate } =
|
|
||||||
useQuickPaymentReceiveContext();
|
const {
|
||||||
|
dialogName,
|
||||||
|
invoice,
|
||||||
|
createPaymentReceiveMutate,
|
||||||
|
} = useQuickPaymentReceiveContext();
|
||||||
|
|
||||||
// Payment receive number.
|
// Payment receive number.
|
||||||
const nextPaymentNumber = transactionNumber(
|
const nextPaymentNumber = transactionNumber(
|
||||||
@@ -48,22 +48,24 @@ function QuickPaymentReceiveForm({
|
|||||||
payment_receive_no: nextPaymentNumber,
|
payment_receive_no: nextPaymentNumber,
|
||||||
}),
|
}),
|
||||||
deposit_account_id: defaultTo(preferredDepositAccount, ''),
|
deposit_account_id: defaultTo(preferredDepositAccount, ''),
|
||||||
...transformInvoiceToForm(invoice),
|
...invoice,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
|
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
|
||||||
const entries = [
|
const entries = [values]
|
||||||
{
|
.filter((entry) => entry.id && entry.payment_amount)
|
||||||
invoice_id: values.invoice_id,
|
.map((entry) => ({
|
||||||
payment_amount: values.amount,
|
invoice_id: entry.id,
|
||||||
},
|
...pick(entry, ['payment_amount']),
|
||||||
];
|
}));
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
...omit(values, ['payment_receive_no', 'invoice_id']),
|
...omit(values, ['payment_receive_no']),
|
||||||
...(!paymentReceiveAutoIncrement && {
|
...(!paymentReceiveAutoIncrement && {
|
||||||
payment_receive_no: values.payment_receive_no,
|
payment_receive_no: values.payment_receive_no,
|
||||||
}),
|
}),
|
||||||
|
customer_id: values.customer.id,
|
||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function QuickPaymentReceiveFormFields({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/*------------ Amount Received -----------*/}
|
{/*------------ Amount Received -----------*/}
|
||||||
<FastField name={'amount'}>
|
<FastField name={'payment_amount'}>
|
||||||
{({
|
{({
|
||||||
form: { values, setFieldValue },
|
form: { values, setFieldValue },
|
||||||
field: { value },
|
field: { value },
|
||||||
@@ -139,7 +139,7 @@ function QuickPaymentReceiveFormFields({
|
|||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--payment_amount', CLASSES.FILL)}
|
className={classNames('form-group--payment_amount', CLASSES.FILL)}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name="amount" />}
|
helperText={<ErrorMessage name="payment_amount" />}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputPrependText text={values.currency_code} />
|
<InputPrependText text={values.currency_code} />
|
||||||
@@ -148,7 +148,7 @@ function QuickPaymentReceiveFormFields({
|
|||||||
value={value}
|
value={value}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
onChange={(amount) => {
|
onChange={(amount) => {
|
||||||
setFieldValue('amount', amount);
|
setFieldValue('payment_amount', amount);
|
||||||
}}
|
}}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
inputRef={(ref) => (paymentReceiveFieldRef.current = ref)}
|
inputRef={(ref) => (paymentReceiveFieldRef.current = ref)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useContext, createContext, useMemo } from 'react';
|
import React, { useContext, createContext } from 'react';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { DialogContent } from '@/components';
|
import { DialogContent } from '@/components';
|
||||||
import { Features } from '@/constants';
|
import { Features } from '@/constants';
|
||||||
@@ -47,16 +47,15 @@ function QuickPaymentReceiveFormProvider({
|
|||||||
isSuccess: isBranchesSuccess,
|
isSuccess: isBranchesSuccess,
|
||||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||||
|
|
||||||
const invoicePayment = useMemo(
|
|
||||||
() => pick(invoice, ['id', 'due_amount', 'customer_id', 'currency_code']),
|
|
||||||
[invoice],
|
|
||||||
);
|
|
||||||
|
|
||||||
// State provider.
|
// State provider.
|
||||||
const provider = {
|
const provider = {
|
||||||
accounts,
|
accounts,
|
||||||
branches,
|
branches,
|
||||||
invoice: invoicePayment,
|
invoice: {
|
||||||
|
...pick(invoice, ['id', 'due_amount', 'customer', 'currency_code']),
|
||||||
|
customer_id: invoice?.customer?.display_name,
|
||||||
|
payment_amount: invoice.due_amount,
|
||||||
|
},
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
isSettingsLoading,
|
isSettingsLoading,
|
||||||
isBranchesSuccess,
|
isBranchesSuccess,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { first, pick } from 'lodash';
|
import { first } from 'lodash';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
@@ -10,16 +10,15 @@ import { useFormikContext } from 'formik';
|
|||||||
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
|
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
|
||||||
|
|
||||||
export const defaultInitialValues = {
|
export const defaultInitialValues = {
|
||||||
invoice_id: '',
|
|
||||||
customer_id: '',
|
customer_id: '',
|
||||||
deposit_account_id: '',
|
deposit_account_id: '',
|
||||||
payment_receive_no: '',
|
payment_receive_no: '',
|
||||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
reference_no: '',
|
reference_no: '',
|
||||||
amount: '',
|
|
||||||
// statement: '',
|
// statement: '',
|
||||||
exchange_rate: 1,
|
exchange_rate: 1,
|
||||||
branch_id: '',
|
branch_id: '',
|
||||||
|
entries: [{ invoice_id: '', payment_amount: '' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformErrors = (errors, { setFieldError }) => {
|
export const transformErrors = (errors, { setFieldError }) => {
|
||||||
@@ -45,9 +44,7 @@ export const transformErrors = (errors, { setFieldError }) => {
|
|||||||
}
|
}
|
||||||
if (getError('PAYMENT_ACCOUNT_CURRENCY_INVALID')) {
|
if (getError('PAYMENT_ACCOUNT_CURRENCY_INVALID')) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get(
|
message: intl.get('payment_Receive.error.payment_account_currency_invalid'),
|
||||||
'payment_Receive.error.payment_account_currency_invalid',
|
|
||||||
),
|
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -67,11 +64,3 @@ export const useSetPrimaryBranchToForm = () => {
|
|||||||
}
|
}
|
||||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformInvoiceToForm = (invoice) => {
|
|
||||||
return {
|
|
||||||
...pick(invoice, ['customer_id', 'currency_code']),
|
|
||||||
amount: invoice.due_amount,
|
|
||||||
invoice_id: invoice.id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '@/style/pages/Expense/List.scss';
|
|||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
|
|
||||||
import ExpenseActionsBar from './ExpenseActionsBar';
|
import ExpenseActionsBar from './ExpenseActionsBar';
|
||||||
|
import ExpenseViewTabs from './ExpenseViewTabs';
|
||||||
import ExpenseDataTable from './ExpenseDataTable';
|
import ExpenseDataTable from './ExpenseDataTable';
|
||||||
|
|
||||||
import withExpenses from './withExpenses';
|
import withExpenses from './withExpenses';
|
||||||
@@ -41,6 +42,7 @@ function ExpensesList({
|
|||||||
<ExpenseActionsBar />
|
<ExpenseActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<ExpenseViewTabs />
|
||||||
<ExpenseDataTable />
|
<ExpenseDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</ExpensesListProvider>
|
</ExpensesListProvider>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DashboardPageContent } from '@/components';
|
|||||||
import { ItemsListProvider } from './ItemsListProvider';
|
import { ItemsListProvider } from './ItemsListProvider';
|
||||||
|
|
||||||
import ItemsActionsBar from './ItemsActionsBar';
|
import ItemsActionsBar from './ItemsActionsBar';
|
||||||
|
import ItemsViewsTabs from './ItemsViewsTabs';
|
||||||
import ItemsDataTable from './ItemsDataTable';
|
import ItemsDataTable from './ItemsDataTable';
|
||||||
|
|
||||||
import withItems from './withItems';
|
import withItems from './withItems';
|
||||||
@@ -40,6 +41,7 @@ function ItemsList({
|
|||||||
<ItemsActionsBar />
|
<ItemsActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<ItemsViewsTabs />
|
||||||
<ItemsDataTable />
|
<ItemsDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</ItemsListProvider>
|
</ItemsListProvider>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '@/style/pages/Bills/List.scss';
|
|||||||
import { BillsListProvider } from './BillsListProvider';
|
import { BillsListProvider } from './BillsListProvider';
|
||||||
|
|
||||||
import BillsActionsBar from './BillsActionsBar';
|
import BillsActionsBar from './BillsActionsBar';
|
||||||
|
import BillsViewsTabs from './BillsViewsTabs';
|
||||||
import BillsTable from './BillsTable';
|
import BillsTable from './BillsTable';
|
||||||
|
|
||||||
import withBills from './withBills';
|
import withBills from './withBills';
|
||||||
@@ -41,6 +42,7 @@ function BillsList({
|
|||||||
<BillsActionsBar />
|
<BillsActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<BillsViewsTabs />
|
||||||
<BillsTable />
|
<BillsTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</BillsListProvider>
|
</BillsListProvider>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '@/style/pages/VendorsCreditNote/List.scss';
|
|||||||
|
|
||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
import VendorsCreditNoteActionsBar from './VendorsCreditNoteActionsBar';
|
import VendorsCreditNoteActionsBar from './VendorsCreditNoteActionsBar';
|
||||||
|
import VendorsCreditNoteViewTabs from './VendorsCreditNoteViewTabs';
|
||||||
import VendorsCreditNoteDataTable from './VendorsCreditNoteDataTable';
|
import VendorsCreditNoteDataTable from './VendorsCreditNoteDataTable';
|
||||||
|
|
||||||
import withVendorsCreditNotes from './withVendorsCreditNotes';
|
import withVendorsCreditNotes from './withVendorsCreditNotes';
|
||||||
@@ -36,6 +37,7 @@ function VendorsCreditNotesList({
|
|||||||
>
|
>
|
||||||
<VendorsCreditNoteActionsBar />
|
<VendorsCreditNoteActionsBar />
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<VendorsCreditNoteViewTabs />
|
||||||
<VendorsCreditNoteDataTable />
|
<VendorsCreditNoteDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</VendorsCreditNoteListProvider>
|
</VendorsCreditNoteListProvider>
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import React from 'react';
|
import * as Yup from 'yup';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
import { FormatNumber } from '@/components';
|
import { AccountsSelect, FFormGroup, FormatNumber } from '@/components';
|
||||||
import { usePaymentMadeFormContext } from '../../PaymentMadeFormProvider';
|
import { usePaymentMadeFormContext } from '../../PaymentMadeFormProvider';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
|
import { ACCOUNT_TYPE } from '@/constants';
|
||||||
import { usePaymentMadeExcessAmount } from '../../utils';
|
import { usePaymentMadeExcessAmount } from '../../utils';
|
||||||
|
|
||||||
interface ExcessPaymentValues {}
|
interface ExcessPaymentValues {
|
||||||
|
accountId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
accountId: '',
|
||||||
|
} as ExcessPaymentValues;
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
accountId: Yup.number().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_ACCOUNT_SLUG = 'prepaid-expenses';
|
||||||
|
|
||||||
function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
||||||
const {
|
const {
|
||||||
|
setFieldValue,
|
||||||
submitForm,
|
submitForm,
|
||||||
values: { currency_code: currencyCode },
|
values: { currency_code: currencyCode },
|
||||||
} = useFormikContext();
|
} = useFormikContext();
|
||||||
@@ -21,6 +37,7 @@ function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
|||||||
values: ExcessPaymentValues,
|
values: ExcessPaymentValues,
|
||||||
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
|
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
|
||||||
) => {
|
) => {
|
||||||
|
setFieldValue(values.accountId);
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
setIsExcessConfirmed(true);
|
setIsExcessConfirmed(true);
|
||||||
|
|
||||||
@@ -33,10 +50,20 @@ function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
|||||||
const handleCloseBtn = () => {
|
const handleCloseBtn = () => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
};
|
};
|
||||||
|
// Retrieves the default excess account id.
|
||||||
|
const defaultAccountId = useDefaultExcessPaymentDeposit();
|
||||||
|
|
||||||
const excessAmount = usePaymentMadeExcessAmount();
|
const excessAmount = usePaymentMadeExcessAmount();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik initialValues={{}} onSubmit={handleSubmit}>
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
...initialValues,
|
||||||
|
accountId: defaultAccountId,
|
||||||
|
}}
|
||||||
|
validationSchema={Schema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<ExcessPaymentDialogContentForm
|
<ExcessPaymentDialogContentForm
|
||||||
excessAmount={
|
excessAmount={
|
||||||
@@ -63,6 +90,7 @@ function ExcessPaymentDialogContentForm({
|
|||||||
onClose,
|
onClose,
|
||||||
}: ExcessPaymentDialogContentFormProps) {
|
}: ExcessPaymentDialogContentFormProps) {
|
||||||
const { submitForm, isSubmitting } = useFormikContext();
|
const { submitForm, isSubmitting } = useFormikContext();
|
||||||
|
const { accounts } = usePaymentMadeFormContext();
|
||||||
|
|
||||||
const handleCloseBtn = () => {
|
const handleCloseBtn = () => {
|
||||||
onClose && onClose();
|
onClose && onClose();
|
||||||
@@ -72,8 +100,26 @@ function ExcessPaymentDialogContentForm({
|
|||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<p style={{ marginBottom: 20 }}>
|
<p style={{ marginBottom: 20 }}>
|
||||||
Would you like to record the excess amount of{' '}
|
Would you like to record the excess amount of{' '}
|
||||||
<strong>{excessAmount}</strong> as credit payment from the vendor.
|
<strong>{excessAmount}</strong> as advanced payment from the customer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'accountId'}
|
||||||
|
label={'The excessed amount will be deposited in the'}
|
||||||
|
helperText={
|
||||||
|
'Only other current asset and non current asset accounts will show.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'accountId'}
|
||||||
|
items={accounts}
|
||||||
|
buttonProps={{ small: true }}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
|
ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
@@ -83,7 +129,7 @@ function ExcessPaymentDialogContentForm({
|
|||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onClick={() => submitForm()}
|
onClick={() => submitForm()}
|
||||||
>
|
>
|
||||||
Save Payment as Credit
|
Save Payment
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCloseBtn}>Cancel</Button>
|
<Button onClick={handleCloseBtn}>Cancel</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,3 +137,11 @@ function ExcessPaymentDialogContentForm({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useDefaultExcessPaymentDeposit = () => {
|
||||||
|
const { accounts } = usePaymentMadeFormContext();
|
||||||
|
return useMemo(() => {
|
||||||
|
return accounts?.find((a) => a.slug === DEFAULT_ACCOUNT_SLUG)?.id;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DashboardPageContent } from '@/components';
|
|||||||
import { PaymentMadesListProvider } from './PaymentMadesListProvider';
|
import { PaymentMadesListProvider } from './PaymentMadesListProvider';
|
||||||
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
|
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
|
||||||
import PaymentMadesTable from './PaymentMadesTable';
|
import PaymentMadesTable from './PaymentMadesTable';
|
||||||
|
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
|
||||||
|
|
||||||
import withPaymentMades from './withPaymentMade';
|
import withPaymentMades from './withPaymentMade';
|
||||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||||
@@ -40,6 +41,7 @@ function PaymentMadeList({
|
|||||||
<PaymentMadeActionsBar />
|
<PaymentMadeActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<PaymentMadeViewTabs />
|
||||||
<PaymentMadesTable />
|
<PaymentMadesTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</PaymentMadesListProvider>
|
</PaymentMadesListProvider>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '@/style/pages/CreditNote/List.scss';
|
|||||||
|
|
||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
import CreditNotesActionsBar from './CreditNotesActionsBar';
|
import CreditNotesActionsBar from './CreditNotesActionsBar';
|
||||||
|
import CreditNotesViewTabs from './CreditNotesViewTabs';
|
||||||
import CreditNotesDataTable from './CreditNotesDataTable';
|
import CreditNotesDataTable from './CreditNotesDataTable';
|
||||||
|
|
||||||
import withCreditNotes from './withCreditNotes';
|
import withCreditNotes from './withCreditNotes';
|
||||||
@@ -35,8 +36,8 @@ function CreditNotesList({
|
|||||||
tableStateChanged={creditNoteTableStateChanged}
|
tableStateChanged={creditNoteTableStateChanged}
|
||||||
>
|
>
|
||||||
<CreditNotesActionsBar />
|
<CreditNotesActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<CreditNotesViewTabs />
|
||||||
<CreditNotesDataTable />
|
<CreditNotesDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</CreditNotesListProvider>
|
</CreditNotesListProvider>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardContentTable, DashboardPageContent } from '@/components';
|
||||||
|
|
||||||
import '@/style/pages/SaleEstimate/List.scss';
|
import '@/style/pages/SaleEstimate/List.scss';
|
||||||
|
|
||||||
import EstimatesActionsBar from './EstimatesActionsBar';
|
import EstimatesActionsBar from './EstimatesActionsBar';
|
||||||
|
import EstimatesViewTabs from './EstimatesViewTabs';
|
||||||
import EstimatesDataTable from './EstimatesDataTable';
|
import EstimatesDataTable from './EstimatesDataTable';
|
||||||
|
|
||||||
import withEstimates from './withEstimates';
|
import withEstimates from './withEstimates';
|
||||||
@@ -40,6 +41,7 @@ function EstimatesList({
|
|||||||
<EstimatesActionsBar />
|
<EstimatesActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<EstimatesViewTabs />
|
||||||
<EstimatesDataTable />
|
<EstimatesDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</EstimatesListProvider>
|
</EstimatesListProvider>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '@/style/pages/SaleInvoice/List.scss';
|
|||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
import { InvoicesListProvider } from './InvoicesListProvider';
|
import { InvoicesListProvider } from './InvoicesListProvider';
|
||||||
|
|
||||||
|
import InvoiceViewTabs from './InvoiceViewTabs';
|
||||||
import InvoicesDataTable from './InvoicesDataTable';
|
import InvoicesDataTable from './InvoicesDataTable';
|
||||||
import InvoicesActionsBar from './InvoicesActionsBar';
|
import InvoicesActionsBar from './InvoicesActionsBar';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ function InvoicesList({
|
|||||||
<InvoicesActionsBar />
|
<InvoicesActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<InvoiceViewTabs />
|
||||||
<InvoicesDataTable />
|
<InvoicesDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</InvoicesListProvider>
|
</InvoicesListProvider>
|
||||||
|
|||||||
@@ -1,21 +1,37 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
import { useMemo } from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
import { FormatNumber } from '@/components';
|
import { AccountsSelect, FFormGroup, FormatNumber } from '@/components';
|
||||||
import { usePaymentReceiveFormContext } from '../../PaymentReceiveFormProvider';
|
import { usePaymentReceiveFormContext } from '../../PaymentReceiveFormProvider';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import { usePaymentReceivedTotalExceededAmount } from '../../utils';
|
import { usePaymentReceivedTotalExceededAmount } from '../../utils';
|
||||||
|
import { ACCOUNT_TYPE } from '@/constants';
|
||||||
|
|
||||||
interface ExcessPaymentValues {}
|
interface ExcessPaymentValues {
|
||||||
|
accountId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
accountId: '',
|
||||||
|
} as ExcessPaymentValues;
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
accountId: Yup.number().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_ACCOUNT_SLUG = 'unearned-revenue';
|
||||||
|
|
||||||
export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
||||||
const {
|
const {
|
||||||
|
setFieldValue,
|
||||||
submitForm,
|
submitForm,
|
||||||
values: { currency_code: currencyCode },
|
values: { currency_code: currencyCode },
|
||||||
} = useFormikContext();
|
} = useFormikContext();
|
||||||
const { setIsExcessConfirmed } = usePaymentReceiveFormContext();
|
const { setIsExcessConfirmed } = usePaymentReceiveFormContext();
|
||||||
|
const initialAccountId = useDefaultExcessPaymentDeposit();
|
||||||
const exceededAmount = usePaymentReceivedTotalExceededAmount();
|
const exceededAmount = usePaymentReceivedTotalExceededAmount();
|
||||||
|
|
||||||
const handleSubmit = (
|
const handleSubmit = (
|
||||||
@@ -24,6 +40,7 @@ export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
|||||||
) => {
|
) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
setIsExcessConfirmed(true);
|
setIsExcessConfirmed(true);
|
||||||
|
setFieldValue('unearned_revenue_account_id', values.accountId);
|
||||||
|
|
||||||
submitForm().then(() => {
|
submitForm().then(() => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
@@ -35,7 +52,14 @@ export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik initialValues={{}} onSubmit={handleSubmit}>
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
...initialValues,
|
||||||
|
accountId: initialAccountId,
|
||||||
|
}}
|
||||||
|
validationSchema={Schema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<ExcessPaymentDialogContentForm
|
<ExcessPaymentDialogContentForm
|
||||||
exceededAmount={
|
exceededAmount={
|
||||||
@@ -53,6 +77,7 @@ export const ExcessPaymentDialogContent = R.compose(withDialogActions)(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
||||||
|
const { accounts } = usePaymentReceiveFormContext();
|
||||||
const { submitForm, isSubmitting } = useFormikContext();
|
const { submitForm, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
const handleCloseBtn = () => {
|
const handleCloseBtn = () => {
|
||||||
@@ -64,8 +89,27 @@ function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
|||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<p style={{ marginBottom: 20 }}>
|
<p style={{ marginBottom: 20 }}>
|
||||||
Would you like to record the excess amount of{' '}
|
Would you like to record the excess amount of{' '}
|
||||||
<strong>{exceededAmount}</strong> as credit payment from the customer.
|
<strong>{exceededAmount}</strong> as advanced payment from the
|
||||||
|
customer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'accountId'}
|
||||||
|
label={'The excessed amount will be deposited in the'}
|
||||||
|
helperText={
|
||||||
|
'Only other other current liability and non current liability accounts will show.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'accountId'}
|
||||||
|
items={accounts}
|
||||||
|
buttonProps={{ small: true }}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||||
|
ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
@@ -76,7 +120,7 @@ function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onClick={() => submitForm()}
|
onClick={() => submitForm()}
|
||||||
>
|
>
|
||||||
Save Payment as Credit
|
Save Payment
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCloseBtn}>Cancel</Button>
|
<Button onClick={handleCloseBtn}>Cancel</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,3 +128,11 @@ function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useDefaultExcessPaymentDeposit = () => {
|
||||||
|
const { accounts } = usePaymentReceiveFormContext();
|
||||||
|
return useMemo(() => {
|
||||||
|
return accounts?.find((a) => a.slug === DEFAULT_ACCOUNT_SLUG)?.id;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const defaultPaymentReceive = {
|
|||||||
exchange_rate: 1,
|
exchange_rate: 1,
|
||||||
entries: [],
|
entries: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
unearned_revenue_account_id: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultRequestPaymentEntry = {
|
export const defaultRequestPaymentEntry = {
|
||||||
@@ -298,9 +299,10 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getExceededAmountFromValues = (values) => {
|
export const getExceededAmountFromValues = (values) => {
|
||||||
const totalApplied = sumBy(values.entries, 'payment_amount');
|
const totalApplied = sumBy(values.entries, 'payment_amount');
|
||||||
const totalAmount = values.amount;
|
const totalAmount = values.amount;
|
||||||
|
|
||||||
return totalAmount - totalApplied;
|
return totalAmount - totalApplied;
|
||||||
};
|
}
|
||||||
@@ -5,6 +5,7 @@ import '@/style/pages/PaymentReceive/List.scss';
|
|||||||
|
|
||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
import { PaymentReceivesListProvider } from './PaymentReceiptsListProvider';
|
import { PaymentReceivesListProvider } from './PaymentReceiptsListProvider';
|
||||||
|
import PaymentReceiveViewTabs from './PaymentReceiveViewTabs';
|
||||||
import PaymentReceivesTable from './PaymentReceivesTable';
|
import PaymentReceivesTable from './PaymentReceivesTable';
|
||||||
import PaymentReceiveActionsBar from './PaymentReceiveActionsBar';
|
import PaymentReceiveActionsBar from './PaymentReceiveActionsBar';
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ function PaymentReceiveList({
|
|||||||
<PaymentReceiveActionsBar />
|
<PaymentReceiveActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<PaymentReceiveViewTabs />
|
||||||
<PaymentReceivesTable />
|
<PaymentReceivesTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</PaymentReceivesListProvider>
|
</PaymentReceivesListProvider>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DashboardPageContent } from '@/components';
|
|||||||
|
|
||||||
import { VendorsListProvider } from './VendorsListProvider';
|
import { VendorsListProvider } from './VendorsListProvider';
|
||||||
import VendorActionsBar from './VendorActionsBar';
|
import VendorActionsBar from './VendorActionsBar';
|
||||||
|
import VendorViewsTabs from './VendorViewsTabs';
|
||||||
import VendorsTable from './VendorsTable';
|
import VendorsTable from './VendorsTable';
|
||||||
|
|
||||||
import withVendors from './withVendors';
|
import withVendors from './withVendors';
|
||||||
@@ -41,6 +42,7 @@ function VendorsList({
|
|||||||
<VendorActionsBar />
|
<VendorActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<VendorViewsTabs />
|
||||||
<VendorsTable />
|
<VendorsTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</VendorsListProvider>
|
</VendorsListProvider>
|
||||||
|
|||||||
@@ -183,6 +183,13 @@ export function useVendorsTableColumns() {
|
|||||||
width: 85,
|
width: 85,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'credit_balance',
|
||||||
|
Header: 'Credit Balance',
|
||||||
|
accessor: 'formatted_unused_credit',
|
||||||
|
width: 100,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
Header: intl.get('receivable_balance'),
|
Header: intl.get('receivable_balance'),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { DashboardPageContent } from '@/components';
|
import { DashboardPageContent } from '@/components';
|
||||||
import WarehouseTransfersActionsBar from './WarehouseTransfersActionsBar';
|
import WarehouseTransfersActionsBar from './WarehouseTransfersActionsBar';
|
||||||
|
import WarehouseTransfersViewTabs from './WarehouseTransfersViewTabs';
|
||||||
import WarehouseTransfersDataTable from './WarehouseTransfersDataTable';
|
import WarehouseTransfersDataTable from './WarehouseTransfersDataTable';
|
||||||
import withWarehouseTransfers from './withWarehouseTransfers';
|
import withWarehouseTransfers from './withWarehouseTransfers';
|
||||||
import withWarehouseTransfersActions from './withWarehouseTransfersActions';
|
import withWarehouseTransfersActions from './withWarehouseTransfersActions';
|
||||||
@@ -32,8 +33,8 @@ function WarehouseTransfersList({
|
|||||||
tableStateChanged={warehouseTransferTableStateChanged}
|
tableStateChanged={warehouseTransferTableStateChanged}
|
||||||
>
|
>
|
||||||
<WarehouseTransfersActionsBar />
|
<WarehouseTransfersActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
|
<WarehouseTransfersViewTabs />
|
||||||
<WarehouseTransfersDataTable />
|
<WarehouseTransfersDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</WarehouseTransfersListProvider>
|
</WarehouseTransfersListProvider>
|
||||||
|
|||||||
@@ -61,76 +61,6 @@ export function useCreateBankRule(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DisconnectBankAccountRes {}
|
|
||||||
interface DisconnectBankAccountValues {
|
|
||||||
bankAccountId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the given bank account.
|
|
||||||
* @param {UseMutationOptions<DisconnectBankAccountRes, Error, DisconnectBankAccountValues>} options
|
|
||||||
* @returns {UseMutationResult<DisconnectBankAccountRes, Error, DisconnectBankAccountValues>}
|
|
||||||
*/
|
|
||||||
export function useDisconnectBankAccount(
|
|
||||||
options?: UseMutationOptions<
|
|
||||||
DisconnectBankAccountRes,
|
|
||||||
Error,
|
|
||||||
DisconnectBankAccountValues
|
|
||||||
>,
|
|
||||||
): UseMutationResult<
|
|
||||||
DisconnectBankAccountRes,
|
|
||||||
Error,
|
|
||||||
DisconnectBankAccountValues
|
|
||||||
> {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const apiRequest = useApiRequest();
|
|
||||||
|
|
||||||
return useMutation<
|
|
||||||
DisconnectBankAccountRes,
|
|
||||||
Error,
|
|
||||||
DisconnectBankAccountValues
|
|
||||||
>(
|
|
||||||
({ bankAccountId }) =>
|
|
||||||
apiRequest.post(`/banking/bank_accounts/${bankAccountId}/disconnect`),
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
onSuccess: (res, values) => {
|
|
||||||
queryClient.invalidateQueries([t.ACCOUNT, values.bankAccountId]);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpdateBankAccountRes {}
|
|
||||||
interface UpdateBankAccountValues {
|
|
||||||
bankAccountId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the bank transactions of the bank account.
|
|
||||||
* @param {UseMutationOptions<UpdateBankAccountRes, Error, UpdateBankAccountValues>}
|
|
||||||
* @returns {UseMutationResult<UpdateBankAccountRes, Error, UpdateBankAccountValues>}
|
|
||||||
*/
|
|
||||||
export function useUpdateBankAccount(
|
|
||||||
options?: UseMutationOptions<
|
|
||||||
UpdateBankAccountRes,
|
|
||||||
Error,
|
|
||||||
UpdateBankAccountValues
|
|
||||||
>,
|
|
||||||
): UseMutationResult<UpdateBankAccountRes, Error, UpdateBankAccountValues> {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const apiRequest = useApiRequest();
|
|
||||||
|
|
||||||
return useMutation<DisconnectBankAccountRes, Error, UpdateBankAccountValues>(
|
|
||||||
({ bankAccountId }) =>
|
|
||||||
apiRequest.post(`/banking/bank_accounts/${bankAccountId}/update`),
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
onSuccess: () => {},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditBankRuleValues {
|
interface EditBankRuleValues {
|
||||||
id: number;
|
id: number;
|
||||||
value: any;
|
value: any;
|
||||||
@@ -265,20 +195,6 @@ export function useGetBankTransactionsMatches(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onValidateExcludeUncategorizedTransaction = (queryClient) => {
|
|
||||||
// Invalidate queries.
|
|
||||||
queryClient.invalidateQueries(QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY);
|
|
||||||
queryClient.invalidateQueries(
|
|
||||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
|
||||||
);
|
|
||||||
// Invalidate accounts.
|
|
||||||
queryClient.invalidateQueries(t.ACCOUNTS);
|
|
||||||
queryClient.invalidateQueries(t.ACCOUNT);
|
|
||||||
|
|
||||||
// invalidate bank account summary.
|
|
||||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
|
||||||
};
|
|
||||||
|
|
||||||
type ExcludeUncategorizedTransactionValue = number;
|
type ExcludeUncategorizedTransactionValue = number;
|
||||||
|
|
||||||
interface ExcludeUncategorizedTransactionRes {}
|
interface ExcludeUncategorizedTransactionRes {}
|
||||||
@@ -312,7 +228,19 @@ export function useExcludeUncategorizedTransaction(
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
onSuccess: (res, id) => {
|
onSuccess: (res, id) => {
|
||||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
// Invalidate queries.
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
|
||||||
|
);
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||||
|
);
|
||||||
|
// Invalidate accounts.
|
||||||
|
queryClient.invalidateQueries(t.ACCOUNTS);
|
||||||
|
queryClient.invalidateQueries(t.ACCOUNT);
|
||||||
|
|
||||||
|
// invalidate bank account summary.
|
||||||
|
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
@@ -353,83 +281,19 @@ export function useUnexcludeUncategorizedTransaction(
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
onSuccess: (res, id) => {
|
onSuccess: (res, id) => {
|
||||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
// Invalidate queries.
|
||||||
},
|
queryClient.invalidateQueries(
|
||||||
...options,
|
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
|
||||||
},
|
);
|
||||||
);
|
queryClient.invalidateQueries(
|
||||||
}
|
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||||
|
);
|
||||||
|
// Invalidate accounts.
|
||||||
|
queryClient.invalidateQueries(t.ACCOUNTS);
|
||||||
|
queryClient.invalidateQueries(t.ACCOUNT);
|
||||||
|
|
||||||
type ExcludeBankTransactionsValue = { ids: Array<number | string> };
|
// Invalidate bank account summary.
|
||||||
interface ExcludeBankTransactionsResponse {}
|
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||||
|
|
||||||
/**
|
|
||||||
* Excludes the uncategorized bank transactions in bulk.
|
|
||||||
* @param {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
|
|
||||||
* @returns {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
|
|
||||||
*/
|
|
||||||
export function useExcludeUncategorizedTransactions(
|
|
||||||
options?: UseMutationOptions<
|
|
||||||
ExcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
ExcludeBankTransactionsValue
|
|
||||||
>,
|
|
||||||
): UseMutationResult<
|
|
||||||
ExcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
ExcludeBankTransactionsValue
|
|
||||||
> {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const apiRequest = useApiRequest();
|
|
||||||
|
|
||||||
return useMutation<
|
|
||||||
ExcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
ExcludeBankTransactionsValue
|
|
||||||
>(
|
|
||||||
(value: { ids: Array<number | string> }) =>
|
|
||||||
apiRequest.put(`/cashflow/transactions/exclude`, { ids: value.ids }),
|
|
||||||
{
|
|
||||||
onSuccess: (res, id) => {
|
|
||||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
|
||||||
},
|
|
||||||
...options,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnexcludeBankTransactionsValue = { ids: Array<number | string> };
|
|
||||||
interface UnexcludeBankTransactionsResponse {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Excludes the uncategorized bank transactions in bulk.
|
|
||||||
* @param {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
|
|
||||||
* @returns {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
|
|
||||||
*/
|
|
||||||
export function useUnexcludeUncategorizedTransactions(
|
|
||||||
options?: UseMutationOptions<
|
|
||||||
UnexcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
UnexcludeBankTransactionsValue
|
|
||||||
>,
|
|
||||||
): UseMutationResult<
|
|
||||||
UnexcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
UnexcludeBankTransactionsValue
|
|
||||||
> {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const apiRequest = useApiRequest();
|
|
||||||
|
|
||||||
return useMutation<
|
|
||||||
UnexcludeBankTransactionsResponse,
|
|
||||||
Error,
|
|
||||||
UnexcludeBankTransactionsValue
|
|
||||||
>(
|
|
||||||
(value: { ids: Array<number | string> }) =>
|
|
||||||
apiRequest.put(`/cashflow/transactions/unexclude`, { ids: value.ids }),
|
|
||||||
{
|
|
||||||
onSuccess: (res, id) => {
|
|
||||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -635,11 +635,4 @@ export default {
|
|||||||
],
|
],
|
||||||
viewBox: '0 0 16 16',
|
viewBox: '0 0 16 16',
|
||||||
},
|
},
|
||||||
|
|
||||||
feed: {
|
|
||||||
path: [
|
|
||||||
'M1.99,11.99c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S3.1,11.99,1.99,11.99zM2.99,7.99c-0.55,0-1,0.45-1,1s0.45,1,1,1c1.66,0,3,1.34,3,3c0,0.55,0.45,1,1,1s1-0.45,1-1C7.99,10.23,5.75,7.99,2.99,7.99zM2.99,3.99c-0.55,0-1,0.45-1,1s0.45,1,1,1c3.87,0,7,3.13,7,7c0,0.55,0.45,1,1,1s1-0.45,1-1C11.99,8.02,7.96,3.99,2.99,3.99zM2.99-0.01c-0.55,0-1,0.45-1,1s0.45,1,1,1c6.08,0,11,4.92,11,11c0,0.55,0.45,1,1,1s1-0.45,1-1C15.99,5.81,10.17-0.01,2.99-0.01z',
|
|
||||||
],
|
|
||||||
viewBox: '0 0 16 16',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ interface StorePlaidState {
|
|||||||
openMatchingTransactionAside: boolean;
|
openMatchingTransactionAside: boolean;
|
||||||
uncategorizedTransactionIdForMatching: number | null;
|
uncategorizedTransactionIdForMatching: number | null;
|
||||||
openReconcileMatchingTransaction: { isOpen: boolean; pending: number };
|
openReconcileMatchingTransaction: { isOpen: boolean; pending: number };
|
||||||
|
|
||||||
uncategorizedTransactionsSelected: Array<number | string>;
|
|
||||||
excludedTransactionsSelected: Array<number | string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaidSlice = createSlice({
|
export const PlaidSlice = createSlice({
|
||||||
@@ -20,8 +17,6 @@ export const PlaidSlice = createSlice({
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
pending: 0,
|
pending: 0,
|
||||||
},
|
},
|
||||||
uncategorizedTransactionsSelected: [],
|
|
||||||
excludedTransactionsSelected: [],
|
|
||||||
} as StorePlaidState,
|
} as StorePlaidState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
|
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
|
||||||
@@ -57,46 +52,6 @@ export const PlaidSlice = createSlice({
|
|||||||
state.openReconcileMatchingTransaction.isOpen = false;
|
state.openReconcileMatchingTransaction.isOpen = false;
|
||||||
state.openReconcileMatchingTransaction.pending = 0;
|
state.openReconcileMatchingTransaction.pending = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected uncategorized transactions.
|
|
||||||
* @param {StorePlaidState} state
|
|
||||||
* @param {PayloadAction<{ transactionIds: Array<string | number> }>} action
|
|
||||||
*/
|
|
||||||
setUncategorizedTransactionsSelected: (
|
|
||||||
state: StorePlaidState,
|
|
||||||
action: PayloadAction<{ transactionIds: Array<string | number> }>,
|
|
||||||
) => {
|
|
||||||
state.uncategorizedTransactionsSelected = action.payload.transactionIds;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the selected uncategorized transactions.
|
|
||||||
* @param {StorePlaidState} state
|
|
||||||
*/
|
|
||||||
resetUncategorizedTransactionsSelected: (state: StorePlaidState) => {
|
|
||||||
state.uncategorizedTransactionsSelected = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets excluded selected transactions.
|
|
||||||
* @param {StorePlaidState} state
|
|
||||||
* @param {PayloadAction<{ ids: Array<string | number> }>} action
|
|
||||||
*/
|
|
||||||
setExcludedTransactionsSelected: (
|
|
||||||
state: StorePlaidState,
|
|
||||||
action: PayloadAction<{ ids: Array<string | number> }>,
|
|
||||||
) => {
|
|
||||||
state.excludedTransactionsSelected = action.payload.ids;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the excluded selected transactions
|
|
||||||
* @param {StorePlaidState} state
|
|
||||||
*/
|
|
||||||
resetExcludedTransactionsSelected: (state: StorePlaidState) => {
|
|
||||||
state.excludedTransactionsSelected = [];
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,10 +62,6 @@ export const {
|
|||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
openReconcileMatchingTransaction,
|
openReconcileMatchingTransaction,
|
||||||
closeReconcileMatchingTransaction,
|
closeReconcileMatchingTransaction,
|
||||||
setUncategorizedTransactionsSelected,
|
|
||||||
resetUncategorizedTransactionsSelected,
|
|
||||||
setExcludedTransactionsSelected,
|
|
||||||
resetExcludedTransactionsSelected,
|
|
||||||
} = PlaidSlice.actions;
|
} = PlaidSlice.actions;
|
||||||
|
|
||||||
export const getPlaidToken = (state: any) => state.plaid.plaidToken;
|
export const getPlaidToken = (state: any) => state.plaid.plaidToken;
|
||||||
|
|||||||
@@ -124,18 +124,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp4-control.bp4-checkbox .bp4-control-indicator {
|
.bp4-control.bp4-checkbox .bp4-control-indicator {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
&::before {
|
&:hover {
|
||||||
height: 15px;
|
height: 15px;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp4-control.bp4-checkbox input:not(:checked):not(:indeterminate) ~ .bp4-control-indicator{
|
.bp4-control.bp4-checkbox {
|
||||||
box-shadow: inset 0 0 0 1px #C5CBD3;
|
|
||||||
|
input:checked~.bp4-control-indicator,
|
||||||
|
input:indeterminate~.bp4-control-indicator {
|
||||||
|
border-color: #0052ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
|
|||||||
@@ -208,16 +208,12 @@ $dashboard-views-bar-height: 44px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.#{$ns}-minimal.#{$ns}-intent-danger {
|
&.#{$ns}-minimal.#{$ns}-intent-danger {
|
||||||
color: rgb(194, 48, 48);
|
color: #c23030;
|
||||||
|
|
||||||
&:not(.bp4-disabled)
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: rgba(219, 55, 55, 0.1);
|
background: rgba(219, 55, 55, 0.1);
|
||||||
}
|
}
|
||||||
&.bp4-disabled{
|
|
||||||
color: rgb(194, 48, 48, 0.6);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&.#{$ns}-minimal.#{$ns}-intent-success{
|
&.#{$ns}-minimal.#{$ns}-intent-success{
|
||||||
color: #1c6e42;
|
color: #1c6e42;
|
||||||
|
|||||||
Reference in New Issue
Block a user