mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 16:19:49 +00:00
Compare commits
1 Commits
v0.18.11
...
reconcile-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45ad4aa1f1 |
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
## [v0.18.0] - 10-08-2024
|
|
||||||
|
|
||||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
|
||||||
* feat: Categorize & match bank transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
|
||||||
* feat: Reconcile match transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/522
|
|
||||||
* fix: Issues in matching transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/523
|
|
||||||
* fix: Cashflow transactions types by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/524
|
|
||||||
|
|
||||||
## [v0.17.5] - 17-06-2024
|
## [v0.17.5] - 17-06-2024
|
||||||
|
|
||||||
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ export class BankingRulesController extends BaseController {
|
|||||||
body('conditions.*.value').exists(),
|
body('conditions.*.value').exists(),
|
||||||
|
|
||||||
// Assign
|
// Assign
|
||||||
body('assign_category').isString(),
|
body('assign_category')
|
||||||
|
.isString()
|
||||||
|
.isIn([
|
||||||
|
'interest_income',
|
||||||
|
'other_income',
|
||||||
|
'deposit',
|
||||||
|
'expense',
|
||||||
|
'owner_drawings',
|
||||||
|
]),
|
||||||
body('assign_account_id').isInt({ min: 0 }),
|
body('assign_account_id').isInt({ min: 0 }),
|
||||||
body('assign_payee').isString().optional({ nullable: true }),
|
body('assign_payee').isString().optional({ nullable: true }),
|
||||||
body('assign_memo').isString().optional({ nullable: true }),
|
body('assign_memo').isString().optional({ nullable: true }),
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ export default class BillsPayments extends BaseController {
|
|||||||
check('vendor_id').exists().isNumeric().toInt(),
|
check('vendor_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_account_id').exists().isNumeric().toInt(),
|
check('payment_account_id').exists().isNumeric().toInt(),
|
||||||
check('payment_number').optional({ nullable: true }).trim().escape(),
|
check('payment_number').optional({ nullable: true }).trim().escape(),
|
||||||
check('payment_date').exists(),
|
check('payment_date').exists(),
|
||||||
@@ -119,7 +118,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
check('reference').optional().trim().escape(),
|
check('reference').optional().trim().escape(),
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
check('entries').exists().isArray(),
|
check('entries').exists().isArray({ min: 1 }),
|
||||||
check('entries.*.index').optional().isNumeric().toInt(),
|
check('entries.*.index').optional().isNumeric().toInt(),
|
||||||
check('entries.*.bill_id').exists().isNumeric().toInt(),
|
check('entries.*.bill_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
|
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ 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('reference_no').optional(),
|
check('reference_no').optional(),
|
||||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||||
@@ -159,7 +158,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({ min: 1 }),
|
||||||
|
|
||||||
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(),
|
||||||
|
|||||||
@@ -237,8 +237,4 @@ module.exports = {
|
|||||||
endpoint: process.env.S3_ENDPOINT,
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
bucket: process.env.S3_BUCKET || 'bigcapital-documents',
|
bucket: process.env.S3_BUCKET || 'bigcapital-documents',
|
||||||
},
|
},
|
||||||
|
|
||||||
loops: {
|
|
||||||
apiKey: process.env.LOOPS_API_KEY,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
export const CashflowTransactionTypes = {
|
|
||||||
OtherIncome: 'Other income',
|
|
||||||
OtherExpense: 'Other expense',
|
|
||||||
OwnerDrawing: 'Owner drawing',
|
|
||||||
OwnerContribution: 'Owner contribution',
|
|
||||||
TransferToAccount: 'Transfer to account',
|
|
||||||
TransferFromAccount: 'Transfer from account',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TransactionTypes = {
|
export const TransactionTypes = {
|
||||||
SaleInvoice: 'Sale invoice',
|
SaleInvoice: 'Sale invoice',
|
||||||
SaleReceipt: 'Sale receipt',
|
SaleReceipt: 'Sale receipt',
|
||||||
PaymentReceive: 'Payment received',
|
PaymentReceive: 'Payment receive',
|
||||||
Bill: 'Bill',
|
Bill: 'Bill',
|
||||||
BillPayment: 'Payment made',
|
BillPayment: 'Payment made',
|
||||||
VendorOpeningBalance: 'Vendor opening balance',
|
VendorOpeningBalance: 'Vendor opening balance',
|
||||||
@@ -26,10 +17,12 @@ export const TransactionTypes = {
|
|||||||
OtherExpense: 'Other expense',
|
OtherExpense: 'Other expense',
|
||||||
OwnerDrawing: 'Owner drawing',
|
OwnerDrawing: 'Owner drawing',
|
||||||
InvoiceWriteOff: 'Invoice write-off',
|
InvoiceWriteOff: 'Invoice write-off',
|
||||||
|
|
||||||
CreditNote: 'transaction_type.credit_note',
|
CreditNote: 'transaction_type.credit_note',
|
||||||
VendorCredit: 'transaction_type.vendor_credit',
|
VendorCredit: 'transaction_type.vendor_credit',
|
||||||
|
|
||||||
RefundCreditNote: 'transaction_type.refund_credit_note',
|
RefundCreditNote: 'transaction_type.refund_credit_note',
|
||||||
RefundVendorCredit: 'transaction_type.refund_vendor_credit',
|
RefundVendorCredit: 'transaction_type.refund_vendor_credit',
|
||||||
|
|
||||||
LandedCost: 'transaction_type.landed_cost',
|
LandedCost: 'transaction_type.landed_cost',
|
||||||
CashflowTransaction: CashflowTransactionTypes,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ exports.up = function (knex) {
|
|||||||
.integer('uncategorized_transaction_id')
|
.integer('uncategorized_transaction_id')
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('uncategorized_cashflow_transactions')
|
.inTable('uncategorized_cashflow_transactions');
|
||||||
.withKeyName('recognizedBankTransactionsUncategorizedTransIdForeign');
|
|
||||||
table
|
table
|
||||||
.integer('bank_rule_id')
|
.integer('bank_rule_id')
|
||||||
.unsigned()
|
.unsigned()
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
|
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
|
||||||
table
|
table.integer('recognized_transaction_id').unsigned();
|
||||||
.integer('recognized_transaction_id')
|
|
||||||
.unsigned()
|
|
||||||
.references('id')
|
|
||||||
.inTable('recognized_bank_transactions')
|
|
||||||
.withKeyName('uncategorizedCashflowTransRecognizedTranIdForeign');
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.createTable('matched_bank_transactions', (table) => {
|
return knex.schema.createTable('matched_bank_transactions', (table) => {
|
||||||
table.increments('id');
|
table.increments('id');
|
||||||
table
|
table.integer('uncategorized_transaction_id').unsigned();
|
||||||
.integer('uncategorized_transaction_id')
|
|
||||||
.unsigned()
|
|
||||||
.references('id')
|
|
||||||
.inTable('uncategorized_cashflow_transactions');
|
|
||||||
table.string('reference_type');
|
table.string('reference_type');
|
||||||
table.integer('reference_id').unsigned();
|
table.integer('reference_id').unsigned();
|
||||||
table.decimal('amount');
|
table.decimal('amount');
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex('accounts_transactions')
|
|
||||||
.whereIn('referenceType', [
|
|
||||||
'OtherIncome',
|
|
||||||
'OtherExpense',
|
|
||||||
'OwnerDrawing',
|
|
||||||
'OwnerContribution',
|
|
||||||
'TransferToAccount',
|
|
||||||
'TransferFromAccount',
|
|
||||||
])
|
|
||||||
.update({
|
|
||||||
transactionType: knex.raw(`
|
|
||||||
CASE
|
|
||||||
WHEN REFERENCE_TYPE = 'OtherIncome' THEN 'OtherIncome'
|
|
||||||
WHEN REFERENCE_TYPE = 'OtherExpense' THEN 'OtherExpense'
|
|
||||||
WHEN REFERENCE_TYPE = 'OwnerDrawing' THEN 'OwnerDrawing'
|
|
||||||
WHEN REFERENCE_TYPE = 'OwnerContribution' THEN 'OwnerContribution'
|
|
||||||
WHEN REFERENCE_TYPE = 'TransferToAccount' THEN 'TransferToAccount'
|
|
||||||
WHEN REFERENCE_TYPE = 'TransferFromAccount' THEN 'TransferFromAccount'
|
|
||||||
END
|
|
||||||
`),
|
|
||||||
referenceType: knex.raw(`
|
|
||||||
CASE
|
|
||||||
WHEN REFERENCE_TYPE IN ('OtherIncome', 'OtherExpense', 'OwnerDrawing', 'OwnerContribution', 'TransferToAccount', 'TransferFromAccount') THEN 'CashflowTransaction'
|
|
||||||
ELSE REFERENCE_TYPE
|
|
||||||
END
|
|
||||||
`),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
return knex('accounts_transactions')
|
|
||||||
.whereIn('transactionType', [
|
|
||||||
'OtherIncome',
|
|
||||||
'OtherExpense',
|
|
||||||
'OwnerDrawing',
|
|
||||||
'OwnerContribution',
|
|
||||||
'TransferToAccount',
|
|
||||||
'TransferFromAccount',
|
|
||||||
])
|
|
||||||
.update({
|
|
||||||
referenceType: knex.raw(`
|
|
||||||
CASE
|
|
||||||
WHEN TRANSACTION_TYPE = 'OtherIncome' THEN 'OtherIncome'
|
|
||||||
WHEN TRANSACTION_TYPE = 'OtherExpense' THEN 'OtherExpense'
|
|
||||||
WHEN TRANSACTION_TYPE = 'OwnerDrawing' THEN 'OwnerDrawing'
|
|
||||||
WHEN TRANSACTION_TYPE = 'OwnerContribution' THEN 'OwnerContribution'
|
|
||||||
WHEN TRANSACTION_TYPE = 'TransferToAccount' THEN 'TransferToAccount'
|
|
||||||
WHEN TRANSACTION_TYPE = 'TransferFromAccount' THEN 'TransferFromAccount'
|
|
||||||
ELSE REFERENCE_TYPE
|
|
||||||
END
|
|
||||||
`),
|
|
||||||
transactionType: knex.raw(`
|
|
||||||
CASE
|
|
||||||
WHEN TRANSACTION_TYPE IN ('OtherIncome', 'OtherExpense', 'OwnerDrawing', 'OwnerContribution', 'TransferToAccount', 'TransferFromAccount') THEN NULL
|
|
||||||
ELSE TRANSACTION_TYPE
|
|
||||||
END
|
|
||||||
`),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,8 @@ export default class SeedAccounts extends TenantSeeder {
|
|||||||
description: this.i18n.__(account.description),
|
description: this.i18n.__(account.description),
|
||||||
currencyCode: this.tenant.metadata.baseCurrency,
|
currencyCode: this.tenant.metadata.baseCurrency,
|
||||||
seededAt: new Date(),
|
seededAt: new Date(),
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
return knex('accounts').then(async () => {
|
return knex('accounts').then(async () => {
|
||||||
// Inserts seed entries.
|
// Inserts seed entries.
|
||||||
return knex('accounts').insert(data);
|
return knex('accounts').insert(data);
|
||||||
|
|||||||
@@ -9,28 +9,6 @@ export const TaxPayableAccount = {
|
|||||||
predefined: 1,
|
predefined: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UnearnedRevenueAccount = {
|
|
||||||
name: 'Unearned Revenue',
|
|
||||||
slug: 'unearned-revenue',
|
|
||||||
account_type: 'other-current-liability',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '50005',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PrepardExpenses = {
|
|
||||||
name: 'Prepaid Expenses',
|
|
||||||
slug: 'prepaid-expenses',
|
|
||||||
account_type: 'other-current-asset',
|
|
||||||
parent_account_id: null,
|
|
||||||
code: '100010',
|
|
||||||
active: true,
|
|
||||||
index: 1,
|
|
||||||
predefined: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: 'Bank Account',
|
name: 'Bank Account',
|
||||||
@@ -345,6 +323,4 @@ export default [
|
|||||||
index: 1,
|
index: 1,
|
||||||
predefined: 0,
|
predefined: 0,
|
||||||
},
|
},
|
||||||
UnearnedRevenueAccount,
|
|
||||||
PrepardExpenses,
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ export interface IAccountTransaction {
|
|||||||
referenceId: number;
|
referenceId: number;
|
||||||
|
|
||||||
referenceNumber?: string;
|
referenceNumber?: string;
|
||||||
|
|
||||||
transactionNumber?: string;
|
transactionNumber?: string;
|
||||||
transactionType?: string;
|
|
||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import {
|
import {
|
||||||
IFinancialSheetCommonMeta,
|
IFinancialSheetCommonMeta,
|
||||||
INumberFormatQuery,
|
INumberFormatQuery,
|
||||||
@@ -258,6 +257,7 @@ export interface IUncategorizedCashflowTransaction {
|
|||||||
categorized: boolean;
|
categorized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface CreateUncategorizedTransactionDTO {
|
export interface CreateUncategorizedTransactionDTO {
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
accountId: number;
|
accountId: number;
|
||||||
@@ -269,16 +269,3 @@ export interface CreateUncategorizedTransactionDTO {
|
|||||||
plaidTransactionId?: string | null;
|
plaidTransactionId?: string | null;
|
||||||
batch?: string;
|
batch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUncategorizedTransactionCreatingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUncategorizedTransactionCreatedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransaction: any;
|
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -130,9 +130,8 @@ export interface ICommandCashflowDeletedPayload {
|
|||||||
|
|
||||||
export interface ICashflowTransactionCategorizedPayload {
|
export interface ICashflowTransactionCategorizedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
uncategorizedTransaction: any;
|
cashflowTransactionId: number;
|
||||||
cashflowTransaction: ICashflowTransaction;
|
cashflowTransaction: ICashflowTransaction;
|
||||||
categorizeDTO: any;
|
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
export interface ICashflowTransactionUncategorizingPayload {
|
export interface ICashflowTransactionUncategorizingPayload {
|
||||||
|
|||||||
@@ -29,9 +29,4 @@ export interface ICashflowAccountTransaction {
|
|||||||
|
|
||||||
date: Date;
|
date: Date;
|
||||||
formattedDate: string;
|
formattedDate: string;
|
||||||
|
|
||||||
status: string;
|
|
||||||
formattedStatus: string;
|
|
||||||
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
|
||||||
|
|
||||||
|
|
||||||
export interface IImportFileCommitedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
importId: number;
|
|
||||||
meta: ImportFilePreviewPOJO;
|
|
||||||
}
|
|
||||||
@@ -40,8 +40,6 @@ export interface ILedgerEntry {
|
|||||||
date: Date | string;
|
date: Date | string;
|
||||||
|
|
||||||
transactionType: string;
|
transactionType: string;
|
||||||
transactionSubType?: string;
|
|
||||||
|
|
||||||
transactionId: number;
|
transactionId: number;
|
||||||
|
|
||||||
transactionNumber?: string;
|
transactionNumber?: string;
|
||||||
|
|||||||
@@ -110,10 +110,6 @@ import { ValidateMatchingOnPaymentMadeDelete } from '@/services/Banking/Matching
|
|||||||
import { ValidateMatchingOnCashflowDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete';
|
import { ValidateMatchingOnCashflowDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete';
|
||||||
import { RecognizeSyncedBankTranasctions } from '@/services/Banking/Plaid/subscribers/RecognizeSyncedBankTransactions';
|
import { RecognizeSyncedBankTranasctions } from '@/services/Banking/Plaid/subscribers/RecognizeSyncedBankTransactions';
|
||||||
import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/UnlinkBankRuleOnDeleteBankRule';
|
import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/UnlinkBankRuleOnDeleteBankRule';
|
||||||
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
|
|
||||||
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
|
|
||||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
|
||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -262,9 +258,6 @@ export const susbcribers = () => {
|
|||||||
// Bank Rules
|
// Bank Rules
|
||||||
TriggerRecognizedTransactions,
|
TriggerRecognizedTransactions,
|
||||||
UnlinkBankRuleOnDeleteBankRule,
|
UnlinkBankRuleOnDeleteBankRule,
|
||||||
DecrementUncategorizedTransactionOnMatching,
|
|
||||||
DecrementUncategorizedTransactionOnExclude,
|
|
||||||
DecrementUncategorizedTransactionOnCategorize,
|
|
||||||
|
|
||||||
// Validate matching
|
// Validate matching
|
||||||
ValidateMatchingOnCashflowDelete,
|
ValidateMatchingOnCashflowDelete,
|
||||||
@@ -275,8 +268,5 @@ export const susbcribers = () => {
|
|||||||
|
|
||||||
// Plaid
|
// Plaid
|
||||||
RecognizeSyncedBankTranasctions,
|
RecognizeSyncedBankTranasctions,
|
||||||
|
|
||||||
// Loops
|
|
||||||
LoopsEventsSubscriber
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
debit: number;
|
debit: number;
|
||||||
exchangeRate: number;
|
exchangeRate: number;
|
||||||
taxRate: number;
|
taxRate: number;
|
||||||
transactionType: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
@@ -54,7 +53,7 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
get referenceTypeFormatted() {
|
get referenceTypeFormatted() {
|
||||||
return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
return getTransactionTypeLabel(this.referenceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -525,9 +525,9 @@ export default class Bill extends mixin(TenantModel, [
|
|||||||
return notFoundBillsIds;
|
return notFoundBillsIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
static changePaymentAmount(billId, amount, trx) {
|
static changePaymentAmount(billId, amount) {
|
||||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||||
return this.query(trx)
|
return this.query()
|
||||||
.where('id', billId)
|
.where('id', billId)
|
||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[changeMethod]('payment_amount', Math.abs(amount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
getCashflowAccountTransactionsTypes,
|
getCashflowAccountTransactionsTypes,
|
||||||
getCashflowTransactionType,
|
getCashflowTransactionType,
|
||||||
} from '@/services/Cashflow/utils';
|
} from '@/services/Cashflow/utils';
|
||||||
|
import AccountTransaction from './AccountTransaction';
|
||||||
import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants';
|
import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants';
|
||||||
import { getCashflowTransactionFormattedType } from '@/utils/transactions-types';
|
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||||
|
|
||||||
export default class CashflowTransaction extends TenantModel {
|
export default class CashflowTransaction extends TenantModel {
|
||||||
transactionType: string;
|
transactionType: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -64,7 +64,7 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
get transactionTypeFormatted() {
|
get transactionTypeFormatted() {
|
||||||
return getCashflowTransactionFormattedType(this.transactionType);
|
return getTransactionTypeLabel(this.transactionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
get typeMeta() {
|
get typeMeta() {
|
||||||
@@ -95,34 +95,6 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
return !!this.uncategorizedTransaction;
|
return !!this.uncategorizedTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Model modifiers.
|
|
||||||
*/
|
|
||||||
static get modifiers() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Filter the published transactions.
|
|
||||||
*/
|
|
||||||
published(query) {
|
|
||||||
query.whereNot('published_at', null);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter the not categorized transactions.
|
|
||||||
*/
|
|
||||||
notCategorized(query) {
|
|
||||||
query.whereNull('cashflowTransactions.uncategorizedTransactionId');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter the categorized transactions.
|
|
||||||
*/
|
|
||||||
categorized(query) {
|
|
||||||
query.whereNotNull('cashflowTransactions.uncategorizedTransactionId');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
@@ -159,7 +131,8 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
to: 'accounts_transactions.referenceId',
|
to: 'accounts_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(builder) {
|
filter(builder) {
|
||||||
builder.where('reference_type', 'CashflowTransaction');
|
const referenceTypes = getCashflowAccountTransactionsTypes();
|
||||||
|
builder.whereIn('reference_type', referenceTypes);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -105,34 +105,8 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
* Filters the excluded transactions.
|
* Filters the excluded transactions.
|
||||||
*/
|
*/
|
||||||
excluded(query) {
|
excluded(query) {
|
||||||
query.whereNotNull('excluded_at');
|
query.whereNotNull('excluded_at')
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter out the recognized transactions.
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
recognized(query) {
|
|
||||||
query.whereNotNull('recognizedTransactionId');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter out the not recognized transactions.
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
notRecognized(query) {
|
|
||||||
query.whereNull('recognizedTransactionId');
|
|
||||||
},
|
|
||||||
|
|
||||||
categorized(query) {
|
|
||||||
query.whereNotNull('categorizeRefType');
|
|
||||||
query.whereNotNull('categorizeRefId');
|
|
||||||
},
|
|
||||||
|
|
||||||
notCategorized(query) {
|
|
||||||
query.whereNull('categorizeRefType');
|
|
||||||
query.whereNull('categorizeRefId');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,4 +158,56 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the count of uncategorized transactions for the associated account
|
||||||
|
* based on the specified operation.
|
||||||
|
* @param {QueryContext} queryContext - The query context for the transaction.
|
||||||
|
* @param {boolean} increment - Indicates whether to increment or decrement the count.
|
||||||
|
*/
|
||||||
|
private async updateUncategorizedTransactionCount(
|
||||||
|
queryContext: QueryContext,
|
||||||
|
increment: boolean,
|
||||||
|
amount: number = 1
|
||||||
|
) {
|
||||||
|
const operation = increment ? 'increment' : 'decrement';
|
||||||
|
|
||||||
|
await Account.query(queryContext.transaction)
|
||||||
|
.findById(this.accountId)
|
||||||
|
[operation]('uncategorized_transactions', amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs after insert.
|
||||||
|
* @param {QueryContext} queryContext
|
||||||
|
*/
|
||||||
|
public async $afterInsert(queryContext) {
|
||||||
|
await super.$afterInsert(queryContext);
|
||||||
|
await this.updateUncategorizedTransactionCount(queryContext, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs after update.
|
||||||
|
* @param {ModelOptions} opt
|
||||||
|
* @param {QueryContext} queryContext
|
||||||
|
*/
|
||||||
|
public async $afterUpdate(
|
||||||
|
opt: ModelOptions,
|
||||||
|
queryContext: QueryContext
|
||||||
|
): Promise<any> {
|
||||||
|
await super.$afterUpdate(opt, queryContext);
|
||||||
|
|
||||||
|
if (this.id && this.categorized) {
|
||||||
|
await this.updateUncategorizedTransactionCount(queryContext, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs after delete.
|
||||||
|
* @param {QueryContext} queryContext
|
||||||
|
*/
|
||||||
|
public async $afterDelete(queryContext: QueryContext) {
|
||||||
|
await super.$afterDelete(queryContext);
|
||||||
|
await this.updateUncategorizedTransactionCount(queryContext, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,7 @@ import { Account } from 'models';
|
|||||||
import TenantRepository from '@/repositories/TenantRepository';
|
import TenantRepository from '@/repositories/TenantRepository';
|
||||||
import { IAccount } from '@/interfaces';
|
import { IAccount } from '@/interfaces';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import { TaxPayableAccount } from '@/database/seeds/data/accounts';
|
||||||
PrepardExpenses,
|
|
||||||
TaxPayableAccount,
|
|
||||||
UnearnedRevenueAccount,
|
|
||||||
} from '@/database/seeds/data/accounts';
|
|
||||||
import { TenantMetadata } from '@/system/models';
|
|
||||||
|
|
||||||
export default class AccountRepository extends TenantRepository {
|
export default class AccountRepository extends TenantRepository {
|
||||||
/**
|
/**
|
||||||
@@ -184,67 +179,4 @@ export default class AccountRepository extends TenantRepository {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds or creates the unearned revenue.
|
|
||||||
* @param {Record<string, string>} extraAttrs
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async findOrCreateUnearnedRevenue(
|
|
||||||
extraAttrs: Record<string, string> = {},
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
// Retrieves the given tenant metadata.
|
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({
|
|
||||||
tenantId: this.tenantId,
|
|
||||||
});
|
|
||||||
const _extraAttrs = {
|
|
||||||
currencyCode: tenantMeta.baseCurrency,
|
|
||||||
...extraAttrs,
|
|
||||||
};
|
|
||||||
let result = await this.model
|
|
||||||
.query(trx)
|
|
||||||
.findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
result = await this.model.query(trx).insertAndFetch({
|
|
||||||
...UnearnedRevenueAccount,
|
|
||||||
..._extraAttrs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds or creates the prepard expenses account.
|
|
||||||
* @param {Record<string, string>} extraAttrs
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async findOrCreatePrepardExpenses(
|
|
||||||
extraAttrs: Record<string, string> = {},
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
// Retrieves the given tenant metadata.
|
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({
|
|
||||||
tenantId: this.tenantId,
|
|
||||||
});
|
|
||||||
const _extraAttrs = {
|
|
||||||
currencyCode: tenantMeta.baseCurrency,
|
|
||||||
...extraAttrs,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = await this.model
|
|
||||||
.query(trx)
|
|
||||||
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
result = await this.model.query(trx).insertAndFetch({
|
|
||||||
...PrepardExpenses,
|
|
||||||
..._extraAttrs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import CachableRepository from './CachableRepository';
|
|||||||
|
|
||||||
export default class TenantRepository extends CachableRepository {
|
export default class TenantRepository extends CachableRepository {
|
||||||
repositoryName: string;
|
repositoryName: string;
|
||||||
tenantId: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -13,8 +12,4 @@ export default class TenantRepository extends CachableRepository {
|
|||||||
constructor(knex, cache, i18n) {
|
constructor(knex, cache, i18n) {
|
||||||
super(knex, cache, i18n);
|
super(knex, cache, i18n);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTenantId(tenantId: number) {
|
|
||||||
this.tenantId = tenantId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,6 @@ export const transformLedgerEntryToTransaction = (
|
|||||||
referenceId: entry.transactionId,
|
referenceId: entry.transactionId,
|
||||||
|
|
||||||
transactionNumber: entry.transactionNumber,
|
transactionNumber: entry.transactionNumber,
|
||||||
transactionType: entry.transactionSubType,
|
|
||||||
|
|
||||||
referenceNumber: entry.referenceNumber,
|
referenceNumber: entry.referenceNumber,
|
||||||
|
|
||||||
note: entry.note,
|
note: entry.note,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { initialize } from 'objection';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetBankAccountSummary {
|
export class GetBankAccountSummary {
|
||||||
@@ -14,43 +14,22 @@ export class GetBankAccountSummary {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public async getBankAccountSummary(tenantId: number, bankAccountId: number) {
|
public async getBankAccountSummary(tenantId: number, bankAccountId: number) {
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
const {
|
const {
|
||||||
Account,
|
Account,
|
||||||
UncategorizedCashflowTransaction,
|
UncategorizedCashflowTransaction,
|
||||||
RecognizedBankTransaction,
|
RecognizedBankTransaction,
|
||||||
MatchedBankTransaction,
|
|
||||||
} = this.tenancy.models(tenantId);
|
} = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await initialize(knex, [
|
|
||||||
UncategorizedCashflowTransaction,
|
|
||||||
RecognizedBankTransaction,
|
|
||||||
MatchedBankTransaction,
|
|
||||||
]);
|
|
||||||
const bankAccount = await Account.query()
|
const bankAccount = await Account.query()
|
||||||
.findById(bankAccountId)
|
.findById(bankAccountId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Retrieves the uncategorized transactions count of the given bank account.
|
// Retrieves the uncategorized transactions count of the given bank account.
|
||||||
const uncategorizedTranasctionsCount =
|
const uncategorizedTranasctionsCount =
|
||||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
await UncategorizedCashflowTransaction.query()
|
||||||
// Include just the given account.
|
.where('accountId', bankAccountId)
|
||||||
q.where('accountId', bankAccountId);
|
.count('id as total')
|
||||||
|
.first();
|
||||||
// Only the not excluded.
|
|
||||||
q.modify('notExcluded');
|
|
||||||
|
|
||||||
// Only the not categorized.
|
|
||||||
q.modify('notCategorized');
|
|
||||||
|
|
||||||
// Only the not matched bank transactions.
|
|
||||||
q.withGraphJoined('matchedBankTransactions');
|
|
||||||
q.whereNull('matchedBankTransactions.id');
|
|
||||||
|
|
||||||
// Count the results.
|
|
||||||
q.count('uncategorized_cashflow_transactions.id as total');
|
|
||||||
q.first();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieves the recognized transactions count of the given bank account.
|
// Retrieves the recognized transactions count of the given bank account.
|
||||||
const recognizedTransactionsCount = await RecognizedBankTransaction.query()
|
const recognizedTransactionsCount = await RecognizedBankTransaction.query()
|
||||||
@@ -64,8 +43,8 @@ export class GetBankAccountSummary {
|
|||||||
.first();
|
.first();
|
||||||
|
|
||||||
const totalUncategorizedTransactions =
|
const totalUncategorizedTransactions =
|
||||||
uncategorizedTranasctionsCount?.total || 0;
|
uncategorizedTranasctionsCount?.total;
|
||||||
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
const totalRecognizedTransactions = recognizedTransactionsCount?.total;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: bankAccount.name,
|
name: bankAccount.name,
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { validateTransactionNotCategorized } from './utils';
|
import { validateTransactionNotCategorized } from './utils';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
IBankTransactionUnexcludedEventPayload,
|
|
||||||
IBankTransactionUnexcludingEventPayload,
|
|
||||||
} from './_types';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExcludeBankTransaction {
|
export class ExcludeBankTransaction {
|
||||||
@@ -17,9 +11,6 @@ export class ExcludeBankTransaction {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private uow: UnitOfWork;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given bank transaction as excluded.
|
* Marks the given bank transaction as excluded.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -40,23 +31,11 @@ export class ExcludeBankTransaction {
|
|||||||
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
await this.eventPublisher.emitAsync(events.bankTransactions.onExcluding, {
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
} as IBankTransactionUnexcludingEventPayload);
|
|
||||||
|
|
||||||
await UncategorizedCashflowTransaction.query(trx)
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
.findById(uncategorizedTransactionId)
|
.findById(uncategorizedTransactionId)
|
||||||
.patch({
|
.patch({
|
||||||
excludedAt: new Date(),
|
excludedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.eventPublisher.emitAsync(events.bankTransactions.onExcluded, {
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
} as IBankTransactionUnexcludedEventPayload);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { validateTransactionNotCategorized } from './utils';
|
import { validateTransactionNotCategorized } from './utils';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
IBankTransactionExcludedEventPayload,
|
|
||||||
IBankTransactionExcludingEventPayload,
|
|
||||||
} from './_types';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class UnexcludeBankTransaction {
|
export class UnexcludeBankTransaction {
|
||||||
@@ -17,9 +11,6 @@ export class UnexcludeBankTransaction {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private uow: UnitOfWork;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given bank transaction as excluded.
|
* Marks the given bank transaction as excluded.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -29,7 +20,7 @@ export class UnexcludeBankTransaction {
|
|||||||
public async unexcludeBankTransaction(
|
public async unexcludeBankTransaction(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
uncategorizedTransactionId: number
|
uncategorizedTransactionId: number
|
||||||
): Promise<void> {
|
) {
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const oldUncategorizedTransaction =
|
const oldUncategorizedTransaction =
|
||||||
@@ -40,27 +31,11 @@ export class UnexcludeBankTransaction {
|
|||||||
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.bankTransactions.onUnexcluding,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
} as IBankTransactionExcludingEventPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
await UncategorizedCashflowTransaction.query(trx)
|
await UncategorizedCashflowTransaction.query(trx)
|
||||||
.findById(uncategorizedTransactionId)
|
.findById(uncategorizedTransactionId)
|
||||||
.patch({
|
.patch({
|
||||||
excludedAt: null,
|
excludedAt: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.bankTransactions.onUnexcluded,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
} as IBankTransactionExcludedEventPayload
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
export interface ExcludedBankTransactionsQuery {
|
export interface ExcludedBankTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBankTransactionUnexcludingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBankTransactionUnexcludedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBankTransactionExcludingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
}
|
|
||||||
export interface IBankTransactionExcludedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import {
|
|
||||||
IBankTransactionExcludedEventPayload,
|
|
||||||
IBankTransactionUnexcludedEventPayload,
|
|
||||||
} from '../_types';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DecrementUncategorizedTransactionOnExclude {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.bankTransactions.onExcluded,
|
|
||||||
this.decrementUnCategorizedTransactionsOnExclude.bind(this)
|
|
||||||
);
|
|
||||||
bus.subscribe(
|
|
||||||
events.bankTransactions.onUnexcluded,
|
|
||||||
this.incrementUnCategorizedTransactionsOnUnexclude.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
|
||||||
* @param {IManualJournalDeletingPayload}
|
|
||||||
*/
|
|
||||||
public async decrementUnCategorizedTransactionsOnExclude({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
}: IBankTransactionExcludedEventPayload) {
|
|
||||||
const { UncategorizedCashflowTransaction, Account } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query(
|
|
||||||
trx
|
|
||||||
).findById(uncategorizedTransactionId);
|
|
||||||
|
|
||||||
await Account.query(trx)
|
|
||||||
.findById(transaction.accountId)
|
|
||||||
.decrement('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
|
||||||
* @param {IManualJournalDeletingPayload}
|
|
||||||
*/
|
|
||||||
public async incrementUnCategorizedTransactionsOnUnexclude({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
}: IBankTransactionUnexcludedEventPayload) {
|
|
||||||
const { UncategorizedCashflowTransaction, Account } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query().findById(
|
|
||||||
uncategorizedTransactionId
|
|
||||||
);
|
|
||||||
//
|
|
||||||
await Account.query(trx)
|
|
||||||
.findById(transaction.accountId)
|
|
||||||
.increment('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,9 +17,6 @@ export class GetMatchedTransactionBillsTransformer extends Transformer {
|
|||||||
'transactionNo',
|
'transactionNo',
|
||||||
'transactionType',
|
'transactionType',
|
||||||
'transsactionTypeFormatted',
|
'transsactionTypeFormatted',
|
||||||
'transactionNormal',
|
|
||||||
'referenceId',
|
|
||||||
'referenceType',
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,29 +100,4 @@ export class GetMatchedTransactionBillsTransformer extends Transformer {
|
|||||||
protected transsactionTypeFormatted() {
|
protected transsactionTypeFormatted() {
|
||||||
return 'Bill';
|
return 'Bill';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the bill transaction normal (debit or credit).
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNormal() {
|
|
||||||
return 'credit';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the match transaction reference id.
|
|
||||||
* @param bill
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected referenceId(bill) {
|
|
||||||
return bill.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the match transaction referenece type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected referenceType() {
|
|
||||||
return 'Bill';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
|
||||||
|
|
||||||
export class GetMatchedTransactionCashflowTransformer extends Transformer {
|
|
||||||
/**
|
|
||||||
* Include these attributes to sale credit note object.
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
public includeAttributes = (): string[] => {
|
|
||||||
return [
|
|
||||||
'referenceNo',
|
|
||||||
'amount',
|
|
||||||
'amountFormatted',
|
|
||||||
'transactionNo',
|
|
||||||
'date',
|
|
||||||
'dateFormatted',
|
|
||||||
'transactionId',
|
|
||||||
'transactionNo',
|
|
||||||
'transactionType',
|
|
||||||
'transsactionTypeFormatted',
|
|
||||||
'transactionNormal',
|
|
||||||
'referenceId',
|
|
||||||
'referenceType',
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude all attributes.
|
|
||||||
* @returns {Array<string>}
|
|
||||||
*/
|
|
||||||
public excludeAttributes = (): string[] => {
|
|
||||||
return ['*'];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the invoice reference number.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected referenceNo(invoice) {
|
|
||||||
return invoice.referenceNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction amount.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected amount(transaction) {
|
|
||||||
return transaction.amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction formatted amount.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected amountFormatted(transaction) {
|
|
||||||
return this.formatNumber(transaction.amount, {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
money: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date of the invoice.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {Date}
|
|
||||||
*/
|
|
||||||
protected date(transaction) {
|
|
||||||
return transaction.date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the date of the invoice.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected dateFormatted(transaction) {
|
|
||||||
return this.formatDate(transaction.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction ID of the invoice.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected transactionId(transaction) {
|
|
||||||
return transaction.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the invoice transaction number.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNo(transaction) {
|
|
||||||
return transaction.transactionNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the invoice transaction type.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
protected transactionType(transaction) {
|
|
||||||
return transaction.transactionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the invoice formatted transaction type.
|
|
||||||
* @param invoice
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transsactionTypeFormatted(transaction) {
|
|
||||||
return transaction.transactionTypeFormatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the cashflow transaction normal (credit or debit).
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNormal(transaction) {
|
|
||||||
return transaction.isCashCredit ? 'credit' : 'debit';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow transaction reference id.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected referenceId(transaction) {
|
|
||||||
return transaction.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow transaction reference type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected referenceType() {
|
|
||||||
return 'CashflowTransaction';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,9 +17,6 @@ export class GetMatchedTransactionExpensesTransformer extends Transformer {
|
|||||||
'transactionNo',
|
'transactionNo',
|
||||||
'transactionType',
|
'transactionType',
|
||||||
'transsactionTypeFormatted',
|
'transsactionTypeFormatted',
|
||||||
'transactionNormal',
|
|
||||||
'referenceType',
|
|
||||||
'referenceId',
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,29 +111,4 @@ export class GetMatchedTransactionExpensesTransformer extends Transformer {
|
|||||||
protected transsactionTypeFormatted() {
|
protected transsactionTypeFormatted() {
|
||||||
return 'Expense';
|
return 'Expense';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the expense transaction normal (credit or debit).
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNormal() {
|
|
||||||
return 'credit';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction reference type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected referenceType() {
|
|
||||||
return 'Expense';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction reference id.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected referenceId(transaction) {
|
|
||||||
return transaction.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ export class GetMatchedTransactionInvoicesTransformer extends Transformer {
|
|||||||
'transactionNo',
|
'transactionNo',
|
||||||
'transactionType',
|
'transactionType',
|
||||||
'transsactionTypeFormatted',
|
'transsactionTypeFormatted',
|
||||||
'transactionNormal',
|
|
||||||
'referenceType',
|
|
||||||
'referenceId'
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +49,7 @@ export class GetMatchedTransactionInvoicesTransformer extends Transformer {
|
|||||||
* @param invoice
|
* @param invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected amountFormatted(invoice) {
|
protected formatAmount(invoice) {
|
||||||
return this.formatNumber(invoice.dueAmount, {
|
return this.formatNumber(invoice.dueAmount, {
|
||||||
currencyCode: invoice.currencyCode,
|
currencyCode: invoice.currencyCode,
|
||||||
money: true,
|
money: true,
|
||||||
@@ -82,7 +79,7 @@ export class GetMatchedTransactionInvoicesTransformer extends Transformer {
|
|||||||
* @param invoice
|
* @param invoice
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
protected transactionId(invoice) {
|
protected getTransactionId(invoice) {
|
||||||
return invoice.id;
|
return invoice.id;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -111,28 +108,4 @@ export class GetMatchedTransactionInvoicesTransformer extends Transformer {
|
|||||||
protected transsactionTypeFormatted(invoice) {
|
protected transsactionTypeFormatted(invoice) {
|
||||||
return 'Sale invoice';
|
return 'Sale invoice';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction normal of invoice (credit or debit).
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNormal() {
|
|
||||||
return 'debit';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction reference type.
|
|
||||||
* @returns {string}
|
|
||||||
*/ protected referenceType() {
|
|
||||||
return 'SaleInvoice';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction reference id.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected referenceId(transaction) {
|
|
||||||
return transaction.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { sumBy } from 'lodash';
|
|
||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { AccountNormal } from '@/interfaces';
|
|
||||||
|
|
||||||
export class GetMatchedTransactionManualJournalsTransformer extends Transformer {
|
export class GetMatchedTransactionManualJournalsTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -19,9 +17,6 @@ export class GetMatchedTransactionManualJournalsTransformer extends Transformer
|
|||||||
'transactionNo',
|
'transactionNo',
|
||||||
'transactionType',
|
'transactionType',
|
||||||
'transsactionTypeFormatted',
|
'transsactionTypeFormatted',
|
||||||
'transactionNormal',
|
|
||||||
'referenceType',
|
|
||||||
'referenceId',
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,20 +37,13 @@ export class GetMatchedTransactionManualJournalsTransformer extends Transformer
|
|||||||
return manualJournal.referenceNo;
|
return manualJournal.referenceNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected total(manualJournal) {
|
|
||||||
const credit = sumBy(manualJournal?.entries, 'credit');
|
|
||||||
const debit = sumBy(manualJournal?.entries, 'debit');
|
|
||||||
|
|
||||||
return debit - credit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the manual journal amount.
|
* Retrieves the manual journal amount.
|
||||||
* @param manualJournal
|
* @param manualJournal
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
protected amount(manualJournal) {
|
protected amount(manualJournal) {
|
||||||
return Math.abs(this.total(manualJournal));
|
return manualJournal.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,31 +107,5 @@ export class GetMatchedTransactionManualJournalsTransformer extends Transformer
|
|||||||
protected transsactionTypeFormatted() {
|
protected transsactionTypeFormatted() {
|
||||||
return 'Manual Journal';
|
return 'Manual Journal';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the manual journal transaction normal (credit or debit).
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected transactionNormal(transaction) {
|
|
||||||
const amount = this.total(transaction);
|
|
||||||
|
|
||||||
return amount >= 0 ? AccountNormal.DEBIT : AccountNormal.CREDIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the manual journal reference type.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected referenceType() {
|
|
||||||
return 'ManualJournal';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the manual journal reference id.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
protected referenceId(transaction) {
|
|
||||||
return transaction.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { GetMatchedTransactionsByBills } from './GetMatchedTransactionsByBills';
|
|||||||
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals';
|
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { sortClosestMatchTransactions } from './_utils';
|
import { sortClosestMatchTransactions } from './_utils';
|
||||||
import { GetMatchedTransactionsByCashflow } from './GetMatchedTransactionsByCashflow';
|
|
||||||
import { GetMatchedTransactionsByInvoices } from './GetMatchedTransactionsByInvoices';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetMatchedTransactions {
|
export class GetMatchedTransactions {
|
||||||
@@ -17,7 +15,7 @@ export class GetMatchedTransactions {
|
|||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getMatchedInvoicesService: GetMatchedTransactionsByInvoices;
|
private getMatchedInvoicesService: GetMatchedTransactionsByExpenses;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getMatchedBillsService: GetMatchedTransactionsByBills;
|
private getMatchedBillsService: GetMatchedTransactionsByBills;
|
||||||
@@ -28,9 +26,6 @@ export class GetMatchedTransactions {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getMatchedExpensesService: GetMatchedTransactionsByExpenses;
|
private getMatchedExpensesService: GetMatchedTransactionsByExpenses;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getMatchedCashflowService: GetMatchedTransactionsByCashflow;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registered matched transactions types.
|
* Registered matched transactions types.
|
||||||
*/
|
*/
|
||||||
@@ -40,7 +35,6 @@ export class GetMatchedTransactions {
|
|||||||
{ type: 'Bill', service: this.getMatchedBillsService },
|
{ type: 'Bill', service: this.getMatchedBillsService },
|
||||||
{ type: 'Expense', service: this.getMatchedExpensesService },
|
{ type: 'Expense', service: this.getMatchedExpensesService },
|
||||||
{ type: 'ManualJournal', service: this.getMatchedManualJournalService },
|
{ type: 'ManualJournal', service: this.getMatchedManualJournalService },
|
||||||
{ type: 'Cashflow', service: this.getMatchedCashflowService },
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
||||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
||||||
@@ -23,25 +22,10 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: GetMatchedTransactionsFilter
|
filter: GetMatchedTransactionsFilter
|
||||||
) {
|
) {
|
||||||
const { Bill, MatchedBankTransaction } = this.tenancy.models(tenantId);
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
// Initialize the models metadata.
|
|
||||||
await initialize(knex, [Bill, MatchedBankTransaction]);
|
|
||||||
|
|
||||||
// Retrieves the bill matches.
|
|
||||||
const bills = await Bill.query().onBuild((q) => {
|
const bills = await Bill.query().onBuild((q) => {
|
||||||
q.withGraphJoined('matchedBankTransaction');
|
q.whereNotExists(Bill.relatedQuery('matchedBankTransaction'));
|
||||||
q.whereNull('matchedBankTransaction.id');
|
|
||||||
q.modify('published');
|
|
||||||
|
|
||||||
if (filter.fromDate) {
|
|
||||||
q.where('billDate', '>=', filter.fromDate);
|
|
||||||
}
|
|
||||||
if (filter.toDate) {
|
|
||||||
q.where('billDate', '<=', filter.toDate);
|
|
||||||
}
|
|
||||||
q.orderBy('billDate', 'DESC');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { initialize } from 'objection';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
|
||||||
import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer';
|
|
||||||
import { GetMatchedTransactionsFilter } from './types';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByType {
|
|
||||||
@Inject()
|
|
||||||
private transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the matched transactions of cash flow.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {GetMatchedTransactionsFilter} filter
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async getMatchedTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
filter: Omit<GetMatchedTransactionsFilter, 'transactionType'>
|
|
||||||
) {
|
|
||||||
const { CashflowTransaction, MatchedBankTransaction } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
// Initialize the ORM models metadata.
|
|
||||||
await initialize(knex, [CashflowTransaction, MatchedBankTransaction]);
|
|
||||||
|
|
||||||
const transactions = await CashflowTransaction.query().onBuild((q) => {
|
|
||||||
// Not matched to bank transaction.
|
|
||||||
q.withGraphJoined('matchedBankTransaction');
|
|
||||||
q.whereNull('matchedBankTransaction.id');
|
|
||||||
|
|
||||||
// Not categorized.
|
|
||||||
q.modify('notCategorized');
|
|
||||||
|
|
||||||
// Published.
|
|
||||||
q.modify('published');
|
|
||||||
|
|
||||||
if (filter.fromDate) {
|
|
||||||
q.where('date', '>=', filter.fromDate);
|
|
||||||
}
|
|
||||||
if (filter.toDate) {
|
|
||||||
q.where('date', '<=', filter.toDate);
|
|
||||||
}
|
|
||||||
q.orderBy('date', 'DESC');
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
transactions,
|
|
||||||
new GetMatchedTransactionCashflowTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the matched transaction of cash flow.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} transactionId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async getMatchedTransaction(tenantId: number, transactionId: number) {
|
|
||||||
const { CashflowTransaction, MatchedBankTransaction } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
// Initialize the ORM models metadata.
|
|
||||||
await initialize(knex, [CashflowTransaction, MatchedBankTransaction]);
|
|
||||||
|
|
||||||
const transactions = await CashflowTransaction.query()
|
|
||||||
.findById(transactionId)
|
|
||||||
.withGraphJoined('matchedBankTransaction')
|
|
||||||
.whereNull('matchedBankTransaction.id')
|
|
||||||
.modify('notCategorized')
|
|
||||||
.modify('published')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
transactions,
|
|
||||||
new GetMatchedTransactionCashflowTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
|
||||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
@@ -24,34 +23,22 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: GetMatchedTransactionsFilter
|
filter: GetMatchedTransactionsFilter
|
||||||
) {
|
) {
|
||||||
const { Expense, MatchedBankTransaction } = this.tenancy.models(tenantId);
|
const { Expense } = this.tenancy.models(tenantId);
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
// Initialize the models metadata.
|
|
||||||
await initialize(knex, [Expense, MatchedBankTransaction]);
|
|
||||||
|
|
||||||
// Retrieve the expense matches.
|
|
||||||
const expenses = await Expense.query().onBuild((query) => {
|
const expenses = await Expense.query().onBuild((query) => {
|
||||||
// Filter out the not matched to bank transactions.
|
query.whereNotExists(Expense.relatedQuery('matchedBankTransaction'));
|
||||||
query.withGraphJoined('matchedBankTransaction');
|
|
||||||
query.whereNull('matchedBankTransaction.id');
|
|
||||||
|
|
||||||
// Filter the published onyl
|
|
||||||
query.modify('filterByPublished');
|
|
||||||
|
|
||||||
if (filter.fromDate) {
|
if (filter.fromDate) {
|
||||||
query.where('paymentDate', '>=', filter.fromDate);
|
query.where('payment_date', '>=', filter.fromDate);
|
||||||
}
|
}
|
||||||
if (filter.toDate) {
|
if (filter.toDate) {
|
||||||
query.where('paymentDate', '<=', filter.toDate);
|
query.where('payment_date', '<=', filter.toDate);
|
||||||
}
|
}
|
||||||
if (filter.minAmount) {
|
if (filter.minAmount) {
|
||||||
query.where('totalAmount', '>=', filter.minAmount);
|
query.where('total_amount', '>=', filter.minAmount);
|
||||||
}
|
}
|
||||||
if (filter.maxAmount) {
|
if (filter.maxAmount) {
|
||||||
query.where('totalAmount', '<=', filter.maxAmount);
|
query.where('total_amount', '<=', filter.maxAmount);
|
||||||
}
|
}
|
||||||
query.orderBy('paymentDate', 'DESC');
|
|
||||||
});
|
});
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { initialize } from 'objection';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
||||||
import {
|
import {
|
||||||
@@ -8,6 +6,7 @@ import {
|
|||||||
MatchedTransactionsPOJO,
|
MatchedTransactionsPOJO,
|
||||||
} from './types';
|
} from './types';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -28,27 +27,10 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: GetMatchedTransactionsFilter
|
filter: GetMatchedTransactionsFilter
|
||||||
): Promise<MatchedTransactionsPOJO> {
|
): Promise<MatchedTransactionsPOJO> {
|
||||||
const { SaleInvoice, MatchedBankTransaction } =
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
// Initialize the models metadata.
|
|
||||||
await initialize(knex, [SaleInvoice, MatchedBankTransaction]);
|
|
||||||
|
|
||||||
// Retrieve the invoices that not matched, unpaid.
|
|
||||||
const invoices = await SaleInvoice.query().onBuild((q) => {
|
const invoices = await SaleInvoice.query().onBuild((q) => {
|
||||||
q.withGraphJoined('matchedBankTransaction');
|
q.whereNotExists(SaleInvoice.relatedQuery('matchedBankTransaction'));
|
||||||
q.whereNull('matchedBankTransaction.id');
|
|
||||||
q.modify('unpaid');
|
|
||||||
q.modify('published');
|
|
||||||
|
|
||||||
if (filter.fromDate) {
|
|
||||||
q.where('invoiceDate', '>=', filter.fromDate);
|
|
||||||
}
|
|
||||||
if (filter.toDate) {
|
|
||||||
q.where('invoiceDate', '<=', filter.toDate);
|
|
||||||
}
|
|
||||||
q.orderBy('invoiceDate', 'DESC');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer';
|
import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
@@ -20,26 +19,12 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: Omit<GetMatchedTransactionsFilter, 'transactionType'>
|
filter: Omit<GetMatchedTransactionsFilter, 'transactionType'>
|
||||||
) {
|
) {
|
||||||
const { ManualJournal, ManualJournalEntry, MatchedBankTransaction } =
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
|
|
||||||
await initialize(knex, [
|
|
||||||
ManualJournal,
|
|
||||||
ManualJournalEntry,
|
|
||||||
MatchedBankTransaction,
|
|
||||||
]);
|
|
||||||
const accountId = 1000;
|
|
||||||
|
|
||||||
const manualJournals = await ManualJournal.query().onBuild((query) => {
|
const manualJournals = await ManualJournal.query().onBuild((query) => {
|
||||||
query.withGraphJoined('matchedBankTransaction');
|
query.whereNotExists(
|
||||||
query.whereNull('matchedBankTransaction.id');
|
ManualJournal.relatedQuery('matchedBankTransaction')
|
||||||
|
);
|
||||||
query.withGraphJoined('entries');
|
|
||||||
query.where('entries.accountId', accountId);
|
|
||||||
|
|
||||||
query.modify('filterByPublished');
|
|
||||||
|
|
||||||
if (filter.fromDate) {
|
if (filter.fromDate) {
|
||||||
query.where('date', '>=', filter.fromDate);
|
query.where('date', '>=', filter.fromDate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, sumBy } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { PromisePool } from '@supercharge/promise-pool';
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { MatchTransactionsTypes } from './MatchTransactionsTypes';
|
import { MatchTransactionsTypes } from './MatchTransactionsTypes';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { sumMatchTranasctions } from './_utils';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class MatchBankTransactions {
|
export class MatchBankTransactions {
|
||||||
@@ -91,8 +90,9 @@ export class MatchBankTransactions {
|
|||||||
throw new ServiceError(error);
|
throw new ServiceError(error);
|
||||||
}
|
}
|
||||||
// Calculate the total given matching transactions.
|
// Calculate the total given matching transactions.
|
||||||
const totalMatchedTranasctions = sumMatchTranasctions(
|
const totalMatchedTranasctions = sumBy(
|
||||||
validatationResult.results
|
validatationResult.results,
|
||||||
|
'amount'
|
||||||
);
|
);
|
||||||
// Validates the total given matching transcations whether is not equal
|
// Validates the total given matching transcations whether is not equal
|
||||||
// uncategorized transaction amount.
|
// uncategorized transaction amount.
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { GetMatchedTransactionsByBills } from './GetMatchedTransactionsByBills';
|
|||||||
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals';
|
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals';
|
||||||
import { MatchTransactionsTypesRegistry } from './MatchTransactionsTypesRegistry';
|
import { MatchTransactionsTypesRegistry } from './MatchTransactionsTypesRegistry';
|
||||||
import { GetMatchedTransactionsByInvoices } from './GetMatchedTransactionsByInvoices';
|
import { GetMatchedTransactionsByInvoices } from './GetMatchedTransactionsByInvoices';
|
||||||
import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer';
|
|
||||||
import { GetMatchedTransactionsByCashflow } from './GetMatchedTransactionsByCashflow';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class MatchTransactionsTypes {
|
export class MatchTransactionsTypes {
|
||||||
@@ -27,10 +25,6 @@ export class MatchTransactionsTypes {
|
|||||||
type: 'ManualJournal',
|
type: 'ManualJournal',
|
||||||
service: GetMatchedTransactionsByManualJournals,
|
service: GetMatchedTransactionsByManualJournals,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'CashflowTransaction',
|
|
||||||
service: GetMatchedTransactionsByCashflow,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export class UnmatchMatchedBankTransaction {
|
|||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
await this.eventPublisher.emitAsync(events.bankMatch.onUnmatching, {
|
await this.eventPublisher.emitAsync(events.bankMatch.onUnmatching, {
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
trx,
|
||||||
} as IBankTransactionUnmatchingEventPayload);
|
} as IBankTransactionUnmatchingEventPayload);
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ export class UnmatchMatchedBankTransaction {
|
|||||||
|
|
||||||
await this.eventPublisher.emitAsync(events.bankMatch.onUnmatched, {
|
await this.eventPublisher.emitAsync(events.bankMatch.onUnmatched, {
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
trx,
|
||||||
} as IBankTransactionUnmatchingEventPayload);
|
} as IBankTransactionUnmatchingEventPayload);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { ERRORS } from './types';
|
import { ERRORS } from './types';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -19,13 +18,12 @@ export class ValidateTransactionMatched {
|
|||||||
public async validateTransactionNoMatchLinking(
|
public async validateTransactionNoMatchLinking(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
referenceType: string,
|
referenceType: string,
|
||||||
referenceId: number,
|
referenceId: number
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
) {
|
||||||
const { MatchedBankTransaction } = this.tenancy.models(tenantId);
|
const { MatchedBankTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const foundMatchedTransaction =
|
const foundMatchedTransaction =
|
||||||
await MatchedBankTransaction.query(trx).findOne({
|
await MatchedBankTransaction.query().findOne({
|
||||||
referenceType,
|
referenceType,
|
||||||
referenceId,
|
referenceId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,12 +20,3 @@ export const sortClosestMatchTransactions = (
|
|||||||
),
|
),
|
||||||
])(matches);
|
])(matches);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sumMatchTranasctions = (transactions: Array<any>) => {
|
|
||||||
return transactions.reduce(
|
|
||||||
(total, item) =>
|
|
||||||
total +
|
|
||||||
(item.transactionNormal === 'debit' ? 1 : -1) * parseFloat(item.amount),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
IBankTransactionMatchedEventPayload,
|
|
||||||
IBankTransactionUnmatchedEventPayload,
|
|
||||||
} from '../types';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DecrementUncategorizedTransactionOnMatching {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.bankMatch.onMatched,
|
|
||||||
this.decrementUnCategorizedTransactionsOnMatching.bind(this)
|
|
||||||
);
|
|
||||||
bus.subscribe(
|
|
||||||
events.bankMatch.onUnmatched,
|
|
||||||
this.incrementUnCategorizedTransactionsOnUnmatching.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
|
||||||
* @param {IManualJournalDeletingPayload}
|
|
||||||
*/
|
|
||||||
public async decrementUnCategorizedTransactionsOnMatching({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
}: IBankTransactionMatchedEventPayload) {
|
|
||||||
const { UncategorizedCashflowTransaction, Account } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query().findById(
|
|
||||||
uncategorizedTransactionId
|
|
||||||
);
|
|
||||||
await Account.query(trx)
|
|
||||||
.findById(transaction.accountId)
|
|
||||||
.decrement('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
|
||||||
* @param {IManualJournalDeletingPayload}
|
|
||||||
*/
|
|
||||||
public async incrementUnCategorizedTransactionsOnUnmatching({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
trx,
|
|
||||||
}: IBankTransactionUnmatchedEventPayload) {
|
|
||||||
const { UncategorizedCashflowTransaction, Account } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query().findById(
|
|
||||||
uncategorizedTransactionId
|
|
||||||
);
|
|
||||||
await Account.query(trx)
|
|
||||||
.findById(transaction.accountId)
|
|
||||||
.increment('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { ICommandCashflowDeletingPayload, IManualJournalDeletingPayload } from '@/interfaces';
|
import { IManualJournalDeletingPayload } from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
||||||
|
|
||||||
@@ -24,14 +24,13 @@ export class ValidateMatchingOnCashflowDelete {
|
|||||||
*/
|
*/
|
||||||
public async validateMatchingOnCashflowDeleting({
|
public async validateMatchingOnCashflowDeleting({
|
||||||
tenantId,
|
tenantId,
|
||||||
oldCashflowTransaction,
|
oldManualJournal,
|
||||||
trx,
|
trx,
|
||||||
}: ICommandCashflowDeletingPayload) {
|
}: IManualJournalDeletingPayload) {
|
||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'CashflowTransaction',
|
'ManualJournal',
|
||||||
oldCashflowTransaction.id,
|
oldManualJournal.id
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ export class ValidateMatchingOnExpenseDelete {
|
|||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'Expense',
|
'Expense',
|
||||||
oldExpense.id,
|
oldExpense.id
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ export class ValidateMatchingOnManualJournalDelete {
|
|||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'ManualJournal',
|
'ManualJournal',
|
||||||
oldManualJournal.id,
|
oldManualJournal.id
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ export class ValidateMatchingOnPaymentMadeDelete {
|
|||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'PaymentMade',
|
'PaymentMade',
|
||||||
oldBillPayment.id,
|
oldBillPayment.id
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ export class ValidateMatchingOnPaymentReceivedDelete {
|
|||||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||||
tenantId,
|
tenantId,
|
||||||
'PaymentReceive',
|
'PaymentReceive',
|
||||||
oldPaymentReceive.id,
|
oldPaymentReceive.id
|
||||||
trx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ export interface IBankTransactionMatchedEventPayload {
|
|||||||
|
|
||||||
export interface IBankTransactionUnmatchingEventPayload {
|
export interface IBankTransactionUnmatchingEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBankTransactionUnmatchedEventPayload {
|
export interface IBankTransactionUnmatchedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
trx?: Knex.Transaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMatchTransactionDTO {
|
export interface IMatchTransactionDTO {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTra
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import uniqid from 'uniqid';
|
import { uniqid } from 'uniqid';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
@@ -148,6 +148,7 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncAccountsTransactions(
|
public async syncAccountsTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
|
batchNo: string,
|
||||||
plaidAccountsTransactions: PlaidTransaction[],
|
plaidAccountsTransactions: PlaidTransaction[],
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -160,6 +161,7 @@ export class PlaidSyncDb {
|
|||||||
return this.syncAccountTranactions(
|
return this.syncAccountTranactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
plaidAccountId,
|
plaidAccountId,
|
||||||
|
batchNo,
|
||||||
plaidTransactions,
|
plaidTransactions,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ export class PlaidUpdateTransactions {
|
|||||||
added.concat(modified),
|
added.concat(modified),
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
// Sync removed transactions.
|
||||||
|
await this.plaidSync.syncRemoveTransactions(tenantId, removed, trx);
|
||||||
// Sync transactions cursor.
|
// Sync transactions cursor.
|
||||||
await this.plaidSync.syncTransactionsCursor(
|
await this.plaidSync.syncTransactionsCursor(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
|||||||
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||||
(
|
(
|
||||||
cashflowAccountId: number,
|
cashflowAccountId: number,
|
||||||
|
creditAccountId: number,
|
||||||
plaidTranasction: PlaidTransaction
|
plaidTranasction: PlaidTransaction
|
||||||
): CreateUncategorizedTransactionDTO => {
|
): CreateUncategorizedTransactionDTO => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export class RegonizeTransactionsJob {
|
|||||||
* Triggers sending invoice mail.
|
* Triggers sending invoice mail.
|
||||||
*/
|
*/
|
||||||
private handler = async (job, done: Function) => {
|
private handler = async (job, done: Function) => {
|
||||||
const { tenantId, batch } = job.attrs.data;
|
const { tenantId } = job.attrs.data;
|
||||||
const regonizeTransactions = Container.get(RecognizeTranasctionsService);
|
const regonizeTransactions = Container.get(RecognizeTranasctionsService);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await regonizeTransactions.recognizeTransactions(tenantId, batch);
|
await regonizeTransactions.recognizeTransactions(tenantId);
|
||||||
done();
|
done();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import {
|
|||||||
IBankRuleEventDeletedPayload,
|
IBankRuleEventDeletedPayload,
|
||||||
IBankRuleEventEditedPayload,
|
IBankRuleEventEditedPayload,
|
||||||
} from '../../Rules/types';
|
} from '../../Rules/types';
|
||||||
import { IImportFileCommitedEventPayload } from '@/interfaces/Import';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class TriggerRecognizedTransactions {
|
export class TriggerRecognizedTransactions {
|
||||||
@@ -29,10 +27,6 @@ export class TriggerRecognizedTransactions {
|
|||||||
events.bankRules.onDeleted,
|
events.bankRules.onDeleted,
|
||||||
this.recognizedTransactionsOnRuleDeleted.bind(this)
|
this.recognizedTransactionsOnRuleDeleted.bind(this)
|
||||||
);
|
);
|
||||||
bus.subscribe(
|
|
||||||
events.import.onImportCommitted,
|
|
||||||
this.triggerRecognizeTransactionsOnImportCommitted.bind(this)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,20 +73,4 @@ export class TriggerRecognizedTransactions {
|
|||||||
const payload = { tenantId };
|
const payload = { tenantId };
|
||||||
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers the recognize bank transactions once the imported file commit.
|
|
||||||
* @param {IImportFileCommitedEventPayload} payload -
|
|
||||||
*/
|
|
||||||
private async triggerRecognizeTransactionsOnImportCommitted({
|
|
||||||
tenantId,
|
|
||||||
importId,
|
|
||||||
meta,
|
|
||||||
}: IImportFileCommitedEventPayload) {
|
|
||||||
const importFile = await Import.query().findOne({ importId });
|
|
||||||
const batch = importFile.paramsParsed.batch;
|
|
||||||
const payload = { tenantId, batch };
|
|
||||||
|
|
||||||
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { upperFirst, camelCase } from 'lodash';
|
||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { getCashflowTransactionFormattedType } from '@/utils/transactions-types';
|
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||||
|
|
||||||
export class GetBankRulesTransformer extends Transformer {
|
export class GetBankRulesTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +29,8 @@ export class GetBankRulesTransformer extends Transformer {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected assignCategoryFormatted(bankRule: any) {
|
protected assignCategoryFormatted(bankRule: any) {
|
||||||
return getCashflowTransactionFormattedType(bankRule.assignCategory);
|
const assignCategory = upperFirst(camelCase(bankRule.assignCategory));
|
||||||
|
return getTransactionTypeLabel(assignCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,12 +34,11 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
currencyCode: transaction.currencyCode,
|
currencyCode: transaction.currencyCode,
|
||||||
exchangeRate: transaction.exchangeRate,
|
exchangeRate: transaction.exchangeRate,
|
||||||
|
|
||||||
transactionType: 'CashflowTransaction',
|
transactionType: transformCashflowTransactionType(
|
||||||
transactionId: transaction.id,
|
|
||||||
transactionNumber: transaction.transactionNumber,
|
|
||||||
transactionSubType: transformCashflowTransactionType(
|
|
||||||
transaction.transactionType
|
transaction.transactionType
|
||||||
),
|
),
|
||||||
|
transactionId: transaction.id,
|
||||||
|
transactionNumber: transaction.transactionNumber,
|
||||||
referenceNumber: transaction.referenceNo,
|
referenceNumber: transaction.referenceNo,
|
||||||
|
|
||||||
note: transaction.description,
|
note: transaction.description,
|
||||||
@@ -162,10 +161,12 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
cashflowTransactionId: number,
|
cashflowTransactionId: number,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
const transactionTypes = getCashflowAccountTransactionsTypes();
|
||||||
|
|
||||||
await this.ledgerStorage.deleteByReference(
|
await this.ledgerStorage.deleteByReference(
|
||||||
tenantId,
|
tenantId,
|
||||||
cashflowTransactionId,
|
cashflowTransactionId,
|
||||||
'CashflowTransaction',
|
transactionTypes,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export class CategorizeCashflowTransaction {
|
|||||||
cashflowTransactionDTO
|
cashflowTransactionDTO
|
||||||
);
|
);
|
||||||
// Updates the uncategorized transaction as categorized.
|
// Updates the uncategorized transaction as categorized.
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx).patchAndFetchById(
|
await UncategorizedCashflowTransaction.query(trx).patchAndFetchById(
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
{
|
{
|
||||||
@@ -98,9 +97,7 @@ export class CategorizeCashflowTransaction {
|
|||||||
events.cashflow.onTransactionCategorized,
|
events.cashflow.onTransactionCategorized,
|
||||||
{
|
{
|
||||||
tenantId,
|
tenantId,
|
||||||
cashflowTransaction,
|
// cashflowTransaction,
|
||||||
uncategorizedTransaction,
|
|
||||||
categorizeDTO,
|
|
||||||
trx,
|
trx,
|
||||||
} as ICashflowTransactionCategorizedPayload
|
} as ICashflowTransactionCategorizedPayload
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,13 +2,7 @@ import { Knex } from 'knex';
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import UnitOfWork from '../UnitOfWork';
|
import UnitOfWork from '../UnitOfWork';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { CreateUncategorizedTransactionDTO } from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
CreateUncategorizedTransactionDTO,
|
|
||||||
IUncategorizedTransactionCreatedEventPayload,
|
|
||||||
IUncategorizedTransactionCreatingEventPayload,
|
|
||||||
} from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CreateUncategorizedTransaction {
|
export class CreateUncategorizedTransaction {
|
||||||
@@ -18,9 +12,6 @@ export class CreateUncategorizedTransaction {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private uow: UnitOfWork;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an uncategorized cashflow transaction.
|
* Creates an uncategorized cashflow transaction.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -28,7 +19,7 @@ export class CreateUncategorizedTransaction {
|
|||||||
*/
|
*/
|
||||||
public create(
|
public create(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
|
createDTO: CreateUncategorizedTransactionDTO,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
@@ -36,30 +27,12 @@ export class CreateUncategorizedTransaction {
|
|||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
async (trx: Knex.Transaction) => {
|
async (trx: Knex.Transaction) => {
|
||||||
await this.eventPublisher.emitAsync(
|
const transaction = await UncategorizedCashflowTransaction.query(
|
||||||
events.cashflow.onTransactionUncategorizedCreating,
|
trx
|
||||||
{
|
).insertAndFetch({
|
||||||
tenantId,
|
...createDTO,
|
||||||
createUncategorizedTransactionDTO,
|
|
||||||
trx,
|
|
||||||
} as IUncategorizedTransactionCreatingEventPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx).insertAndFetch({
|
|
||||||
...createUncategorizedTransactionDTO,
|
|
||||||
});
|
});
|
||||||
|
return transaction;
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionUncategorizedCreated,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransaction,
|
|
||||||
createUncategorizedTransactionDTO,
|
|
||||||
trx,
|
|
||||||
} as IUncategorizedTransactionCreatedEventPayload
|
|
||||||
);
|
|
||||||
return uncategorizedTransaction;
|
|
||||||
},
|
},
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ export default class NewCashflowTransactionService {
|
|||||||
...fromDTO,
|
...fromDTO,
|
||||||
transactionNumber,
|
transactionNumber,
|
||||||
currencyCode: cashflowAccount.currencyCode,
|
currencyCode: cashflowAccount.currencyCode,
|
||||||
exchangeRate: fromDTO?.exchangeRate || 1,
|
|
||||||
transactionType: transformCashflowTransactionType(
|
transactionType: transformCashflowTransactionType(
|
||||||
fromDTO.transactionType
|
fromDTO.transactionType
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import { Importable } from '../Import/Importable';
|
import { Importable } from '../Import/Importable';
|
||||||
import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction';
|
import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction';
|
||||||
import { CreateUncategorizedTransactionDTO } from '@/interfaces';
|
import { CreateUncategorizedTransactionDTO } from '@/interfaces';
|
||||||
@@ -16,7 +15,6 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passing the sheet DTO to create uncategorized transaction.
|
* Passing the sheet DTO to create uncategorized transaction.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -45,7 +43,6 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
return {
|
return {
|
||||||
...createDTO,
|
...createDTO,
|
||||||
accountId: context.import.paramsParsed.accountId,
|
accountId: context.import.paramsParsed.accountId,
|
||||||
batch: context.import.paramsParsed.batch,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +54,6 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
return BankTransactionsSampleData;
|
return BankTransactionsSampleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
|
||||||
// # Params
|
|
||||||
// ------------------
|
|
||||||
/**
|
/**
|
||||||
* Params validation schema.
|
* Params validation schema.
|
||||||
* @returns {ValidationSchema[]}
|
* @returns {ValidationSchema[]}
|
||||||
@@ -85,17 +79,4 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
await Account.query().findById(params.accountId).throwIfNotFound({});
|
await Account.query().findById(params.accountId).throwIfNotFound({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformes the import params before storing them.
|
|
||||||
* @param {Record<string, any>} parmas
|
|
||||||
*/
|
|
||||||
public transformParams(parmas: Record<string, any>) {
|
|
||||||
const batch = uniqid();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...parmas,
|
|
||||||
batch,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import {
|
|
||||||
ICashflowTransactionCategorizedPayload,
|
|
||||||
ICashflowTransactionUncategorizedPayload,
|
|
||||||
} from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DecrementUncategorizedTransactionOnCategorize {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.cashflow.onTransactionCategorized,
|
|
||||||
this.decrementUnCategorizedTransactionsOnCategorized.bind(this)
|
|
||||||
);
|
|
||||||
bus.subscribe(
|
|
||||||
events.cashflow.onTransactionUncategorized,
|
|
||||||
this.incrementUnCategorizedTransactionsOnUncategorized.bind(this)
|
|
||||||
);
|
|
||||||
bus.subscribe(
|
|
||||||
events.cashflow.onTransactionUncategorizedCreated,
|
|
||||||
this.incrementUncategoirzedTransactionsOnCreated.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrement the uncategoirzed transactions on the account once categorizing.
|
|
||||||
* @param {ICashflowTransactionCategorizedPayload}
|
|
||||||
*/
|
|
||||||
public async decrementUnCategorizedTransactionsOnCategorized({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransaction,
|
|
||||||
}: ICashflowTransactionCategorizedPayload) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
await Account.query()
|
|
||||||
.findById(uncategorizedTransaction.accountId)
|
|
||||||
.decrement('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the uncategorized transaction on the given account on uncategorizing.
|
|
||||||
* @param {IManualJournalDeletingPayload}
|
|
||||||
*/
|
|
||||||
public async incrementUnCategorizedTransactionsOnUncategorized({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransaction,
|
|
||||||
}: ICashflowTransactionUncategorizedPayload) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
await Account.query()
|
|
||||||
.findById(uncategorizedTransaction.accountId)
|
|
||||||
.increment('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments uncategorized transactions count once creating a new transaction.
|
|
||||||
* @param {ICommandCashflowCreatedPayload} payload -
|
|
||||||
*/
|
|
||||||
public async incrementUncategoirzedTransactionsOnCreated({
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransaction,
|
|
||||||
trx,
|
|
||||||
}: any) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
if (!uncategorizedTransaction.accountId) return;
|
|
||||||
|
|
||||||
await Account.query(trx)
|
|
||||||
.findById(uncategorizedTransaction.accountId)
|
|
||||||
.increment('uncategorizedTransactions', 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import R from 'ramda';
|
import R from 'ramda';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { first, isEmpty } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
ICashflowAccountTransaction,
|
ICashflowAccountTransaction,
|
||||||
ICashflowAccountTransactionsQuery,
|
ICashflowAccountTransactionsQuery,
|
||||||
|
INumberFormatQuery,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import { runningAmount } from 'utils';
|
import { runningAmount } from 'utils';
|
||||||
import { CashflowAccountTransactionsRepo } from './CashflowAccountTransactionsRepo';
|
|
||||||
import { BankTransactionStatus } from './constants';
|
|
||||||
import { formatBankTransactionsStatus } from './utils';
|
|
||||||
|
|
||||||
export class CashflowAccountTransactionReport extends FinancialSheet {
|
export default class CashflowAccountTransactionReport extends FinancialSheet {
|
||||||
|
private transactions: any;
|
||||||
|
private openingBalance: number;
|
||||||
private runningBalance: any;
|
private runningBalance: any;
|
||||||
|
private numberFormat: INumberFormatQuery;
|
||||||
|
private baseCurrency: string;
|
||||||
private query: ICashflowAccountTransactionsQuery;
|
private query: ICashflowAccountTransactionsQuery;
|
||||||
private repo: CashflowAccountTransactionsRepo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -23,61 +23,19 @@ export class CashflowAccountTransactionReport extends FinancialSheet {
|
|||||||
* @param {ICashflowAccountTransactionsQuery} query -
|
* @param {ICashflowAccountTransactionsQuery} query -
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
repo: CashflowAccountTransactionsRepo,
|
transactions,
|
||||||
|
openingBalance: number,
|
||||||
query: ICashflowAccountTransactionsQuery
|
query: ICashflowAccountTransactionsQuery
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.repo = repo;
|
this.transactions = transactions;
|
||||||
|
this.openingBalance = openingBalance;
|
||||||
|
|
||||||
|
this.runningBalance = runningAmount(this.openingBalance);
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.runningBalance = runningAmount(this.repo.openingBalance);
|
this.numberFormat = query.numberFormat;
|
||||||
}
|
this.baseCurrency = 'USD';
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the transaction status.
|
|
||||||
* @param {} transaction
|
|
||||||
* @returns {BankTransactionStatus}
|
|
||||||
*/
|
|
||||||
private getTransactionStatus(transaction: any): BankTransactionStatus {
|
|
||||||
const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
|
|
||||||
`${transaction.referenceType}-${transaction.referenceId}`
|
|
||||||
);
|
|
||||||
const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
|
|
||||||
`${transaction.referenceType}-${transaction.referenceId}`
|
|
||||||
);
|
|
||||||
if (!isEmpty(categorizedTrans)) {
|
|
||||||
return BankTransactionStatus.Categorized;
|
|
||||||
} else if (!isEmpty(matchedTrans)) {
|
|
||||||
return BankTransactionStatus.Matched;
|
|
||||||
} else {
|
|
||||||
return BankTransactionStatus.Manual;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the uncategoized transaction id from the given transaction.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {number|null}
|
|
||||||
*/
|
|
||||||
private getUncategorizedTransId(transaction: any): number {
|
|
||||||
// The given transaction would be categorized, matched or not, so we'd take a look at
|
|
||||||
// the categorized transaction first to get the id if not exist, then should look at the matched
|
|
||||||
// transaction if not exist too, so the given transaction has no uncategorized transaction id.
|
|
||||||
const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
|
|
||||||
`${transaction.referenceType}-${transaction.referenceId}`
|
|
||||||
);
|
|
||||||
const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
|
|
||||||
`${transaction.referenceType}-${transaction.referenceId}`
|
|
||||||
);
|
|
||||||
// Relation between the transaction and matching always been one-to-one.
|
|
||||||
const firstCategorizedTrans = first(categorizedTrans);
|
|
||||||
const firstMatchedTrans = first(matchedTrans);
|
|
||||||
|
|
||||||
return (
|
|
||||||
firstCategorizedTrans?.id ||
|
|
||||||
firstMatchedTrans?.uncategorizedTransactionId ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,10 +44,6 @@ export class CashflowAccountTransactionReport extends FinancialSheet {
|
|||||||
* @returns {ICashflowAccountTransaction}
|
* @returns {ICashflowAccountTransaction}
|
||||||
*/
|
*/
|
||||||
private transactionNode = (transaction: any): ICashflowAccountTransaction => {
|
private transactionNode = (transaction: any): ICashflowAccountTransaction => {
|
||||||
const status = this.getTransactionStatus(transaction);
|
|
||||||
const uncategorizedTransactionId =
|
|
||||||
this.getUncategorizedTransId(transaction);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: transaction.date,
|
date: transaction.date,
|
||||||
formattedDate: moment(transaction.date).format('YYYY-MM-DD'),
|
formattedDate: moment(transaction.date).format('YYYY-MM-DD'),
|
||||||
@@ -113,9 +67,6 @@ export class CashflowAccountTransactionReport extends FinancialSheet {
|
|||||||
|
|
||||||
balance: 0,
|
balance: 0,
|
||||||
formattedBalance: '',
|
formattedBalance: '',
|
||||||
status,
|
|
||||||
formattedStatus: formatBankTransactionsStatus(status),
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,6 +146,6 @@ export class CashflowAccountTransactionReport extends FinancialSheet {
|
|||||||
* @returns {ICashflowAccountTransaction[]}
|
* @returns {ICashflowAccountTransaction[]}
|
||||||
*/
|
*/
|
||||||
public reportData(): ICashflowAccountTransaction[] {
|
public reportData(): ICashflowAccountTransaction[] {
|
||||||
return this.transactionsNode(this.repo.transactions);
|
return this.transactionsNode(this.transactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,30 @@
|
|||||||
import * as R from 'ramda';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ICashflowAccountTransactionsQuery } from '@/interfaces';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import { ICashflowAccountTransactionsQuery, IPaginationMeta } from '@/interfaces';
|
||||||
groupMatchedBankTransactions,
|
|
||||||
groupUncategorizedTransactions,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
export class CashflowAccountTransactionsRepo {
|
@Service()
|
||||||
private models: any;
|
export default class CashflowAccountTransactionsRepo {
|
||||||
public query: ICashflowAccountTransactionsQuery;
|
@Inject()
|
||||||
public transactions: any;
|
private tenancy: HasTenancyService;
|
||||||
public uncategorizedTransactions: any;
|
|
||||||
public uncategorizedTransactionsMapByRef: Map<string, any>;
|
|
||||||
public matchedBankTransactions: any;
|
|
||||||
public matchedBankTransactionsMapByRef: Map<string, any>;
|
|
||||||
public pagination: any;
|
|
||||||
public openingBalance: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @param {any} models
|
|
||||||
* @param {ICashflowAccountTransactionsQuery} query
|
|
||||||
*/
|
|
||||||
constructor(models: any, query: ICashflowAccountTransactionsQuery) {
|
|
||||||
this.models = models;
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Async initalize the resources.
|
|
||||||
*/
|
|
||||||
async asyncInit() {
|
|
||||||
await this.initCashflowAccountTransactions();
|
|
||||||
await this.initCashflowAccountOpeningBalance();
|
|
||||||
await this.initCategorizedTransactions();
|
|
||||||
await this.initMatchedTransactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cashflow account transactions.
|
* Retrieve the cashflow account transactions.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {ICashflowAccountTransactionsQuery} query -
|
* @param {ICashflowAccountTransactionsQuery} query -
|
||||||
*/
|
*/
|
||||||
async initCashflowAccountTransactions() {
|
async getCashflowAccountTransactions(
|
||||||
const { AccountTransaction } = this.models;
|
tenantId: number,
|
||||||
|
query: ICashflowAccountTransactionsQuery
|
||||||
|
) {
|
||||||
|
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const { results, pagination } = await AccountTransaction.query()
|
return AccountTransaction.query()
|
||||||
.where('account_id', this.query.accountId)
|
.where('account_id', query.accountId)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
{ column: 'date', order: 'desc' },
|
{ column: 'date', order: 'desc' },
|
||||||
{ column: 'created_at', order: 'desc' },
|
{ column: 'created_at', order: 'desc' },
|
||||||
])
|
])
|
||||||
.pagination(this.query.page - 1, this.query.pageSize);
|
.pagination(query.page - 1, query.pageSize);
|
||||||
|
|
||||||
this.transactions = results;
|
|
||||||
this.pagination = pagination;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,18 +34,22 @@ export class CashflowAccountTransactionsRepo {
|
|||||||
* @param {IPaginationMeta} pagination
|
* @param {IPaginationMeta} pagination
|
||||||
* @return {Promise<number>}
|
* @return {Promise<number>}
|
||||||
*/
|
*/
|
||||||
async initCashflowAccountOpeningBalance(): Promise<void> {
|
async getCashflowAccountOpeningBalance(
|
||||||
const { AccountTransaction } = this.models;
|
tenantId: number,
|
||||||
|
accountId: number,
|
||||||
|
pagination: IPaginationMeta
|
||||||
|
): Promise<number> {
|
||||||
|
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Retrieve the opening balance of credit and debit balances.
|
// Retrieve the opening balance of credit and debit balances.
|
||||||
const openingBalancesSubquery = AccountTransaction.query()
|
const openingBalancesSubquery = AccountTransaction.query()
|
||||||
.where('account_id', this.query.accountId)
|
.where('account_id', accountId)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
{ column: 'date', order: 'desc' },
|
{ column: 'date', order: 'desc' },
|
||||||
{ column: 'created_at', order: 'desc' },
|
{ column: 'created_at', order: 'desc' },
|
||||||
])
|
])
|
||||||
.limit(this.pagination.total)
|
.limit(pagination.total)
|
||||||
.offset(this.pagination.pageSize * (this.pagination.page - 1));
|
.offset(pagination.pageSize * (pagination.page - 1));
|
||||||
|
|
||||||
// Sumation of credit and debit balance.
|
// Sumation of credit and debit balance.
|
||||||
const openingBalances = await AccountTransaction.query()
|
const openingBalances = await AccountTransaction.query()
|
||||||
@@ -85,43 +60,6 @@ export class CashflowAccountTransactionsRepo {
|
|||||||
|
|
||||||
const openingBalance = openingBalances.debit - openingBalances.credit;
|
const openingBalance = openingBalances.debit - openingBalances.credit;
|
||||||
|
|
||||||
this.openingBalance = openingBalance;
|
return openingBalance;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the uncategorized transactions of the bank account.
|
|
||||||
*/
|
|
||||||
async initCategorizedTransactions() {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.models;
|
|
||||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
|
||||||
|
|
||||||
const uncategorizedTransactions =
|
|
||||||
await UncategorizedCashflowTransaction.query().whereIn(
|
|
||||||
['categorizeRefType', 'categorizeRefId'],
|
|
||||||
refs
|
|
||||||
);
|
|
||||||
|
|
||||||
this.uncategorizedTransactions = uncategorizedTransactions;
|
|
||||||
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
|
||||||
uncategorizedTransactions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the matched bank transactions of the bank account.
|
|
||||||
*/
|
|
||||||
async initMatchedTransactions(): Promise<void> {
|
|
||||||
const { MatchedBankTransaction } = this.models;
|
|
||||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
|
||||||
|
|
||||||
const matchedBankTransactions =
|
|
||||||
await MatchedBankTransaction.query().whereIn(
|
|
||||||
['referenceType', 'referenceId'],
|
|
||||||
refs
|
|
||||||
);
|
|
||||||
this.matchedBankTransactions = matchedBankTransactions;
|
|
||||||
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
|
||||||
matchedBankTransactions
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { includes } from 'lodash';
|
||||||
import * as qim from 'qim';
|
import * as qim from 'qim';
|
||||||
import { ICashflowAccountTransactionsQuery, IAccount } from '@/interfaces';
|
import { ICashflowAccountTransactionsQuery, IAccount } from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import { CashflowAccountTransactionReport } from './CashflowAccountTransactions';
|
import CashflowAccountTransactionsRepo from './CashflowAccountTransactionsRepo';
|
||||||
|
import CashflowAccountTransactionsReport from './CashflowAccountTransactions';
|
||||||
|
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
import I18nService from '@/services/I18n/I18nService';
|
import I18nService from '@/services/I18n/I18nService';
|
||||||
import { CashflowAccountTransactionsRepo } from './CashflowAccountTransactionsRepo';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CashflowAccountTransactionsService extends FinancialSheet {
|
export default class CashflowAccountTransactionsService extends FinancialSheet {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
cashflowTransactionsRepo: CashflowAccountTransactionsRepo;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
i18nService: I18nService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defaults balance sheet filter query.
|
* Defaults balance sheet filter query.
|
||||||
@@ -40,24 +50,59 @@ export default class CashflowAccountTransactionsService extends FinancialSheet {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: ICashflowAccountTransactionsQuery
|
query: ICashflowAccountTransactionsQuery
|
||||||
) {
|
) {
|
||||||
const models = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
const parsedQuery = { ...this.defaultQuery, ...query };
|
const parsedQuery = { ...this.defaultQuery, ...query };
|
||||||
|
|
||||||
// Initalize the bank transactions report repository.
|
// Retrieve the given account or throw not found service error.
|
||||||
const cashflowTransactionsRepo = new CashflowAccountTransactionsRepo(
|
const account = await Account.query().findById(parsedQuery.accountId);
|
||||||
models,
|
|
||||||
|
// Validates the cashflow account type.
|
||||||
|
this.validateCashflowAccountType(account);
|
||||||
|
|
||||||
|
// Retrieve the cashflow account transactions.
|
||||||
|
const { results: transactions, pagination } =
|
||||||
|
await this.cashflowTransactionsRepo.getCashflowAccountTransactions(
|
||||||
|
tenantId,
|
||||||
parsedQuery
|
parsedQuery
|
||||||
);
|
);
|
||||||
await cashflowTransactionsRepo.asyncInit();
|
// Retrieve the cashflow account opening balance.
|
||||||
|
const openingBalance =
|
||||||
|
await this.cashflowTransactionsRepo.getCashflowAccountOpeningBalance(
|
||||||
|
tenantId,
|
||||||
|
parsedQuery.accountId,
|
||||||
|
pagination
|
||||||
|
);
|
||||||
// Retrieve the computed report.
|
// Retrieve the computed report.
|
||||||
const report = new CashflowAccountTransactionReport(
|
const report = new CashflowAccountTransactionsReport(
|
||||||
cashflowTransactionsRepo,
|
transactions,
|
||||||
|
openingBalance,
|
||||||
parsedQuery
|
parsedQuery
|
||||||
);
|
);
|
||||||
const transactions = report.reportData();
|
const reportTranasctions = report.reportData();
|
||||||
const pagination = cashflowTransactionsRepo.pagination;
|
|
||||||
|
|
||||||
return { transactions, pagination };
|
return {
|
||||||
|
transactions: this.i18nService.i18nApply(
|
||||||
|
[[qim.$each, 'formattedTransactionType']],
|
||||||
|
reportTranasctions,
|
||||||
|
tenantId
|
||||||
|
),
|
||||||
|
pagination,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the cashflow account type.
|
||||||
|
* @param {IAccount} account -
|
||||||
|
*/
|
||||||
|
private validateCashflowAccountType(account: IAccount) {
|
||||||
|
const cashflowTypes = [
|
||||||
|
ACCOUNT_TYPE.CASH,
|
||||||
|
ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
ACCOUNT_TYPE.BANK,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!includes(cashflowTypes, account.accountType)) {
|
||||||
|
throw new ServiceError(ERRORS.ACCOUNT_ID_HAS_INVALID_TYPE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum BankTransactionStatus {
|
|
||||||
Categorized = 'categorized',
|
|
||||||
Matched = 'matched',
|
|
||||||
Manual = 'manual',
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as R from 'ramda';
|
|
||||||
|
|
||||||
export const groupUncategorizedTransactions = (
|
|
||||||
uncategorizedTransactions: any
|
|
||||||
): Map<string, any> => {
|
|
||||||
return new Map(
|
|
||||||
R.toPairs(
|
|
||||||
R.groupBy(
|
|
||||||
(transaction) =>
|
|
||||||
`${transaction.categorizeRefType}-${transaction.categorizeRefId}`,
|
|
||||||
uncategorizedTransactions
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const groupMatchedBankTransactions = (
|
|
||||||
uncategorizedTransactions: any
|
|
||||||
): Map<string, any> => {
|
|
||||||
return new Map(
|
|
||||||
R.toPairs(
|
|
||||||
R.groupBy(
|
|
||||||
(transaction) =>
|
|
||||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
|
||||||
uncategorizedTransactions
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatBankTransactionsStatus = (status) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'categorized':
|
|
||||||
return 'Categorized';
|
|
||||||
case 'matched':
|
|
||||||
return 'Matched';
|
|
||||||
case 'manual':
|
|
||||||
return 'Manual';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -15,10 +15,14 @@ import { ServiceError } from '@/exceptions';
|
|||||||
import { getUniqueImportableValue, trimObject } from './_utils';
|
import { getUniqueImportableValue, trimObject } from './_utils';
|
||||||
import { ImportableResources } from './ImportableResources';
|
import { ImportableResources } from './ImportableResources';
|
||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { Import } from '@/system/models';
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileCommon {
|
export class ImportFileCommon {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private importFileValidator: ImportFileDataValidator;
|
private importFileValidator: ImportFileDataValidator;
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
|
||||||
import { ImportFileProcess } from './ImportFileProcess';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { IImportFileCommitedEventPayload } from '@/interfaces/Import';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileProcessCommit {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importFile: ImportFileProcess;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits the imported file.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} importId
|
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
|
||||||
*/
|
|
||||||
public async commit(
|
|
||||||
tenantId: number,
|
|
||||||
importId: number
|
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
|
||||||
|
|
||||||
const meta = await this.importFile.import(tenantId, importId, trx);
|
|
||||||
|
|
||||||
// Commit the successed transaction.
|
|
||||||
await trx.commit();
|
|
||||||
|
|
||||||
// Triggers `onImportFileCommitted` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.import.onImportCommitted, {
|
|
||||||
meta,
|
|
||||||
importId,
|
|
||||||
tenantId,
|
|
||||||
} as IImportFileCommitedEventPayload);
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import { ImportFileProcess } from './ImportFileProcess';
|
|||||||
import { ImportFilePreview } from './ImportFilePreview';
|
import { ImportFilePreview } from './ImportFilePreview';
|
||||||
import { ImportSampleService } from './ImportSample';
|
import { ImportSampleService } from './ImportSample';
|
||||||
import { ImportFileMeta } from './ImportFileMeta';
|
import { ImportFileMeta } from './ImportFileMeta';
|
||||||
import { ImportFileProcessCommit } from './ImportFileProcessCommit';
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
export class ImportResourceApplication {
|
export class ImportResourceApplication {
|
||||||
@@ -28,9 +27,6 @@ export class ImportResourceApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private importMetaService: ImportFileMeta;
|
private importMetaService: ImportFileMeta;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importProcessCommit: ImportFileProcessCommit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the imported file and stores the import file meta under unqiue id.
|
* Reads the imported file and stores the import file meta under unqiue id.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -78,7 +74,7 @@ export class ImportResourceApplication {
|
|||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
*/
|
*/
|
||||||
public async process(tenantId: number, importId: number) {
|
public async process(tenantId: number, importId: number) {
|
||||||
return this.importProcessCommit.commit(tenantId, importId);
|
return this.importProcessService.import(tenantId, importId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { omit, sumBy } from 'lodash';
|
|||||||
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
|
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
|
||||||
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 CommandBillPaymentDTOTransformer {
|
export class CommandBillPaymentDTOTransformer {
|
||||||
@@ -24,14 +23,11 @@ export class CommandBillPaymentDTOTransformer {
|
|||||||
vendor: IVendor,
|
vendor: IVendor,
|
||||||
oldBillPayment?: IBillPayment
|
oldBillPayment?: IBillPayment
|
||||||
): Promise<IBillPayment> {
|
): Promise<IBillPayment> {
|
||||||
const amount =
|
|
||||||
billPaymentDTO.amount ?? sumBy(billPaymentDTO.entries, 'paymentAmount');
|
|
||||||
|
|
||||||
const initialDTO = {
|
const initialDTO = {
|
||||||
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
|
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
|
||||||
'paymentDate',
|
'paymentDate',
|
||||||
]),
|
]),
|
||||||
amount,
|
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
|
||||||
currencyCode: vendor.currencyCode,
|
currencyCode: vendor.currencyCode,
|
||||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||||
entries: billPaymentDTO.entries,
|
entries: billPaymentDTO.entries,
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
||||||
oldPaymentReceive?: IPaymentReceive
|
oldPaymentReceive?: IPaymentReceive
|
||||||
): Promise<IPaymentReceive> {
|
): Promise<IPaymentReceive> {
|
||||||
const amount =
|
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
paymentReceiveDTO.amount ??
|
|
||||||
sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
|
||||||
|
|
||||||
// Retreive the next invoice number.
|
// Retreive the next invoice number.
|
||||||
const autoNextNumber =
|
const autoNextNumber =
|
||||||
@@ -56,7 +54,7 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
||||||
'paymentDate',
|
'paymentDate',
|
||||||
]),
|
]),
|
||||||
amount,
|
amount: paymentAmount,
|
||||||
currencyCode: customer.currencyCode,
|
currencyCode: customer.currencyCode,
|
||||||
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
||||||
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getPrice } from '@lemonsqueezy/lemonsqueezy.js';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
import { Plan } from '@/system/models';
|
import { Plan } from '@/system/models';
|
||||||
import { Subscription } from './Subscription';
|
import { Subscription } from './Subscription';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class LemonSqueezyWebhooks {
|
export class LemonSqueezyWebhooks {
|
||||||
@@ -16,7 +18,7 @@ export class LemonSqueezyWebhooks {
|
|||||||
private subscriptionService: Subscription;
|
private subscriptionService: Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the Lemon Squeezy webhooks.
|
* handle the LemonSqueezy webhooks.
|
||||||
* @param {string} rawBody
|
* @param {string} rawBody
|
||||||
* @param {string} signature
|
* @param {string} signature
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
@@ -72,7 +74,7 @@ export class LemonSqueezyWebhooks {
|
|||||||
const variantId = attributes.variant_id as string;
|
const variantId = attributes.variant_id as string;
|
||||||
|
|
||||||
// We assume that the Plan table is up to date.
|
// We assume that the Plan table is up to date.
|
||||||
const plan = await Plan.query().findOne('lemonVariantId', variantId);
|
const plan = await Plan.query().findOne('slug', 'early-adaptor');
|
||||||
|
|
||||||
if (!plan) {
|
if (!plan) {
|
||||||
throw new Error(`Plan with variantId ${variantId} not found.`);
|
throw new Error(`Plan with variantId ${variantId} not found.`);
|
||||||
@@ -80,9 +82,26 @@ export class LemonSqueezyWebhooks {
|
|||||||
// Update the subscription in the database.
|
// Update the subscription in the database.
|
||||||
const priceId = attributes.first_subscription_item.price_id;
|
const priceId = attributes.first_subscription_item.price_id;
|
||||||
|
|
||||||
|
// Get the price data from Lemon Squeezy.
|
||||||
|
const priceData = await getPrice(priceId);
|
||||||
|
|
||||||
|
if (priceData.error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get the price data for the subscription ${eventBody.data.id}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const isUsageBased =
|
||||||
|
attributes.first_subscription_item.is_usage_based;
|
||||||
|
const price = isUsageBased
|
||||||
|
? priceData.data?.data.attributes.unit_price_decimal
|
||||||
|
: priceData.data?.data.attributes.unit_price;
|
||||||
|
|
||||||
// Create a new subscription of the tenant.
|
// Create a new subscription of the tenant.
|
||||||
if (webhookEvent === 'subscription_created') {
|
if (webhookEvent === 'subscription_created') {
|
||||||
await this.subscriptionService.newSubscribtion(tenantId, plan.slug);
|
await this.subscriptionService.newSubscribtion(
|
||||||
|
tenantId,
|
||||||
|
'early-adaptor'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (webhookEvent.startsWith('order_')) {
|
} else if (webhookEvent.startsWith('order_')) {
|
||||||
|
|||||||
@@ -77,12 +77,7 @@ export default class HasTenancyService {
|
|||||||
const knex = this.knex(tenantId);
|
const knex = this.knex(tenantId);
|
||||||
const i18n = this.i18n(tenantId);
|
const i18n = this.i18n(tenantId);
|
||||||
|
|
||||||
const repositories = tenantRepositoriesLoader(knex, cache, i18n);
|
return tenantRepositoriesLoader(knex, cache, i18n);
|
||||||
|
|
||||||
Object.values(repositories).forEach((repository) => {
|
|
||||||
repository.setTenantId(tenantId);
|
|
||||||
});
|
|
||||||
return repositories;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,13 +40,6 @@ export default {
|
|||||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* User subscription events.
|
|
||||||
*/
|
|
||||||
subscription: {
|
|
||||||
onSubscribed: 'onOrganizationSubscribed',
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenants managment service.
|
* Tenants managment service.
|
||||||
*/
|
*/
|
||||||
@@ -406,9 +399,6 @@ export default {
|
|||||||
onTransactionCategorizing: 'onTransactionCategorizing',
|
onTransactionCategorizing: 'onTransactionCategorizing',
|
||||||
onTransactionCategorized: 'onCashflowTransactionCategorized',
|
onTransactionCategorized: 'onCashflowTransactionCategorized',
|
||||||
|
|
||||||
onTransactionUncategorizedCreating: 'onTransactionUncategorizedCreating',
|
|
||||||
onTransactionUncategorizedCreated: 'onTransactionUncategorizedCreated',
|
|
||||||
|
|
||||||
onTransactionUncategorizing: 'onTransactionUncategorizing',
|
onTransactionUncategorizing: 'onTransactionUncategorizing',
|
||||||
onTransactionUncategorized: 'onTransactionUncategorized',
|
onTransactionUncategorized: 'onTransactionUncategorized',
|
||||||
|
|
||||||
@@ -649,17 +639,4 @@ export default {
|
|||||||
onUnmatching: 'onBankTransactionUnmathcing',
|
onUnmatching: 'onBankTransactionUnmathcing',
|
||||||
onUnmatched: 'onBankTransactionUnmathced',
|
onUnmatched: 'onBankTransactionUnmathced',
|
||||||
},
|
},
|
||||||
|
|
||||||
bankTransactions: {
|
|
||||||
onExcluding: 'onBankTransactionExclude',
|
|
||||||
onExcluded: 'onBankTransactionExcluded',
|
|
||||||
|
|
||||||
onUnexcluding: 'onBankTransactionUnexcluding',
|
|
||||||
onUnexcluded: 'onBankTransactionUnexcluded',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Import files.
|
|
||||||
import: {
|
|
||||||
onImportCommitted: 'onImportFileCommitted',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.table('subscription_plans', (table) => {
|
|
||||||
table.string('lemon_variant_id').nullable().index();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = (knex) => {
|
|
||||||
return knex.schema.table('subscription_plans', (table) => {
|
|
||||||
table.dropColumn('lemon_variant_id');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex('subscription_plans').insert([
|
|
||||||
// Capital Basic
|
|
||||||
{
|
|
||||||
name: 'Capital Basic (Monthly)',
|
|
||||||
slug: 'capital-basic-monthly',
|
|
||||||
price: 10,
|
|
||||||
active: true,
|
|
||||||
currency: 'USD',
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'month',
|
|
||||||
lemon_variant_id: '446152',
|
|
||||||
// lemon_variant_id: '450016',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Basic (Annually)',
|
|
||||||
slug: 'capital-basic-annually',
|
|
||||||
price: 90,
|
|
||||||
active: true,
|
|
||||||
currency: 'USD',
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'year',
|
|
||||||
lemon_variant_id: '446153',
|
|
||||||
// lemon_variant_id: '450018',
|
|
||||||
},
|
|
||||||
|
|
||||||
// # Capital Essential
|
|
||||||
{
|
|
||||||
name: 'Capital Essential (Monthly)',
|
|
||||||
slug: 'capital-essential-monthly',
|
|
||||||
price: 20,
|
|
||||||
active: true,
|
|
||||||
currency: 'USD',
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'month',
|
|
||||||
lemon_variant_id: '446155',
|
|
||||||
// lemon_variant_id: '450028',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Essential (Annually)',
|
|
||||||
slug: 'capital-essential-annually',
|
|
||||||
price: 180,
|
|
||||||
active: true,
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'year',
|
|
||||||
lemon_variant_id: '446156',
|
|
||||||
// lemon_variant_id: '450029',
|
|
||||||
},
|
|
||||||
|
|
||||||
// # Capital Plus
|
|
||||||
{
|
|
||||||
name: 'Capital Plus (Monthly)',
|
|
||||||
slug: 'capital-plus-monthly',
|
|
||||||
price: 25,
|
|
||||||
active: true,
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'month',
|
|
||||||
lemon_variant_id: '446165',
|
|
||||||
// lemon_variant_id: '450031',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Plus (Annually)',
|
|
||||||
slug: 'capital-plus-annually',
|
|
||||||
price: 228,
|
|
||||||
active: true,
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'year',
|
|
||||||
lemon_variant_id: '446164',
|
|
||||||
// lemon_variant_id: '450032',
|
|
||||||
},
|
|
||||||
|
|
||||||
// # Capital Big
|
|
||||||
{
|
|
||||||
name: 'Capital Big (Monthly)',
|
|
||||||
slug: 'capital-big-monthly',
|
|
||||||
price: 40,
|
|
||||||
active: true,
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'month',
|
|
||||||
lemon_variant_id: '446167',
|
|
||||||
// lemon_variant_id: '450024',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Big (Annually)',
|
|
||||||
slug: 'capital-big-annually',
|
|
||||||
price: 360,
|
|
||||||
active: true,
|
|
||||||
invoice_period: 1,
|
|
||||||
invoice_interval: 'year',
|
|
||||||
lemon_variant_id: '446168',
|
|
||||||
// lemon_variant_id: '450025',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {};
|
|
||||||
@@ -1,36 +1,5 @@
|
|||||||
import { isObject, upperFirst, camelCase } from 'lodash';
|
import { TransactionTypes } from '@/data/TransactionTypes';
|
||||||
import {
|
|
||||||
TransactionTypes,
|
|
||||||
CashflowTransactionTypes,
|
|
||||||
} from '@/data/TransactionTypes';
|
|
||||||
|
|
||||||
/**
|
export const getTransactionTypeLabel = (transactionType: string) => {
|
||||||
* Retrieves the formatted type of account transaction.
|
return TransactionTypes[transactionType];
|
||||||
* @param {string} referenceType
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const getTransactionTypeLabel = (
|
|
||||||
referenceType: string,
|
|
||||||
transactionType?: string
|
|
||||||
) => {
|
|
||||||
const _referenceType = upperFirst(camelCase(referenceType));
|
|
||||||
const _transactionType = upperFirst(camelCase(transactionType));
|
|
||||||
|
|
||||||
return isObject(TransactionTypes[_referenceType])
|
|
||||||
? TransactionTypes[_referenceType][_transactionType]
|
|
||||||
: TransactionTypes[_referenceType] || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the formatted type of cashflow transaction.
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @returns {string¿}
|
|
||||||
*/
|
|
||||||
export const getCashflowTransactionFormattedType = (
|
|
||||||
transactionType: string
|
|
||||||
) => {
|
|
||||||
const _transactionType = upperFirst(camelCase(transactionType));
|
|
||||||
|
|
||||||
return CashflowTransactionTypes[_transactionType] || null;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,10 +23,9 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #2F343C;
|
color: #2F343C;
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #252A31;
|
color: #404854;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricePer{
|
.pricePer{
|
||||||
@@ -58,21 +57,3 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.featureItem{
|
|
||||||
flex: 1;
|
|
||||||
color: #1C2127;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featurePopover :global .bp4-popover-content{
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.featurePopoverContent{
|
|
||||||
font-size: 12px
|
|
||||||
}
|
|
||||||
.featurePopoverLabel {
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Button, ButtonProps, Intent } from '@blueprintjs/core';
|
||||||
Button,
|
|
||||||
ButtonProps,
|
|
||||||
Intent,
|
|
||||||
Position,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
import { Box, Group, Stack } from '../Layout';
|
import { Box, Group, Stack } from '../Layout';
|
||||||
import styles from './PricingPlan.module.scss';
|
import styles from './PricingPlan.module.scss';
|
||||||
@@ -71,7 +64,7 @@ export interface PricingPriceProps {
|
|||||||
*/
|
*/
|
||||||
PricingPlan.Price = ({ price, subPrice }: PricingPriceProps) => {
|
PricingPlan.Price = ({ price, subPrice }: PricingPriceProps) => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4} className={styles.priceRoot}>
|
<Stack spacing={6} className={styles.priceRoot}>
|
||||||
<h4 className={styles.price}>{price}</h4>
|
<h4 className={styles.price}>{price}</h4>
|
||||||
<span className={styles.pricePer}>{subPrice}</span>
|
<span className={styles.pricePer}>{subPrice}</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -108,7 +101,7 @@ export interface PricingFeaturesProps {
|
|||||||
*/
|
*/
|
||||||
PricingPlan.Features = ({ children }: PricingFeaturesProps) => {
|
PricingPlan.Features = ({ children }: PricingFeaturesProps) => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing={14} className={styles.features}>
|
<Stack spacing={10} className={styles.features}>
|
||||||
{children}
|
{children}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -116,41 +109,15 @@ PricingPlan.Features = ({ children }: PricingFeaturesProps) => {
|
|||||||
|
|
||||||
export interface PricingFeatureLineProps {
|
export interface PricingFeatureLineProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
hintContent?: string;
|
|
||||||
hintLabel?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a single feature line within a list of features.
|
* Displays a single feature line within a list of features.
|
||||||
* @param children - The content of the feature line.
|
* @param children - The content of the feature line.
|
||||||
*/
|
*/
|
||||||
PricingPlan.FeatureLine = ({
|
PricingPlan.FeatureLine = ({ children }: PricingFeatureLineProps) => {
|
||||||
children,
|
return (
|
||||||
hintContent,
|
<Group noWrap spacing={12}>
|
||||||
hintLabel,
|
|
||||||
}: PricingFeatureLineProps) => {
|
|
||||||
return hintContent ? (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
<Stack spacing={5}>
|
|
||||||
{hintLabel && (
|
|
||||||
<Text className={styles.featurePopoverLabel}>{hintLabel}</Text>
|
|
||||||
)}
|
|
||||||
<Text className={styles.featurePopoverContent}>{hintContent}</Text>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
position={Position.TOP_LEFT}
|
|
||||||
popoverClassName={styles.featurePopover}
|
|
||||||
modifiers={{ offset: { enabled: true, offset: '0,10' } }}
|
|
||||||
minimal
|
|
||||||
>
|
|
||||||
<Group noWrap spacing={8} style={{ cursor: 'help' }}>
|
|
||||||
<CheckCircled height={12} width={12} />
|
|
||||||
<Box className={styles.featureItem}>{children}</Box>
|
|
||||||
</Group>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
<Group noWrap spacing={8}>
|
|
||||||
<CheckCircled height={12} width={12} />
|
<CheckCircled height={12} width={12} />
|
||||||
<Box className={styles.featureItem}>{children}</Box>
|
<Box className={styles.featureItem}>{children}</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ export const ACCOUNT_TYPE = {
|
|||||||
BANK: 'bank',
|
BANK: 'bank',
|
||||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||||
INVENTORY: 'inventory',
|
INVENTORY: 'inventory',
|
||||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||||
FIXED_ASSET: 'fixed-asset',
|
FIXED_ASSET: 'fixed-asset',
|
||||||
NON_CURRENT_ASSET: 'non-current-asset',
|
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||||
|
|
||||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||||
CREDIT_CARD: 'credit-card',
|
CREDIT_CARD: 'credit-card',
|
||||||
|
|||||||
@@ -39,12 +39,3 @@ export const TRANSACRIONS_TYPE = [
|
|||||||
'OtherExpense',
|
'OtherExpense',
|
||||||
'TransferToAccount',
|
'TransferToAccount',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MoneyCategoryPerCreditAccountRootType = {
|
|
||||||
OwnerContribution: ['equity'],
|
|
||||||
OtherIncome: ['income'],
|
|
||||||
OwnerDrawing: ['equity'],
|
|
||||||
OtherExpense: ['expense'],
|
|
||||||
TransferToAccount: ['asset'],
|
|
||||||
TransferFromAccount: ['asset'],
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,140 +1,10 @@
|
|||||||
interface SubscriptionPlanFeature {
|
// @ts-nocheck
|
||||||
text: string;
|
// Subscription plans.
|
||||||
hint?: string;
|
export const plans = [
|
||||||
label?: string;
|
|
||||||
style?: Record<string, string>;
|
|
||||||
}
|
|
||||||
interface SubscriptionPlan {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
description: string;
|
|
||||||
features: SubscriptionPlanFeature[];
|
|
||||||
featured?: boolean;
|
|
||||||
monthlyPrice: string;
|
|
||||||
monthlyPriceLabel: string;
|
|
||||||
annuallyPrice: string;
|
|
||||||
annuallyPriceLabel: string;
|
|
||||||
monthlyVariantId: string;
|
|
||||||
annuallyVariantId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SubscriptionPlans = [
|
];
|
||||||
{
|
|
||||||
name: 'Capital Basic',
|
// Payment methods.
|
||||||
slug: 'capital_basic',
|
export const paymentMethods = [
|
||||||
description: 'Good for service businesses that just started.',
|
|
||||||
features: [
|
];
|
||||||
{
|
|
||||||
text: 'Unlimited Sale Invoices',
|
|
||||||
hintLabel: 'Unlimited Sale Invoices',
|
|
||||||
hint: 'Good for service businesses that just started for service businesses that just started',
|
|
||||||
},
|
|
||||||
{ text: 'Unlimated Sale Estimates' },
|
|
||||||
{ text: 'Track GST and VAT' },
|
|
||||||
{ text: 'Connect Banks for Automatic Importing' },
|
|
||||||
{ text: 'Chart of Accounts' },
|
|
||||||
{
|
|
||||||
text: 'Manual Journals',
|
|
||||||
hintLabel: 'Manual Journals',
|
|
||||||
hint: 'Write manual journals entries for financial transactions not automatically captured by the system to adjust financial statements.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Basic Financial Reports & Insights',
|
|
||||||
hint: 'Balance sheet, profit & loss statement, cashflow statement, general ledger, journal sheet, A/P aging summary, A/R aging summary',
|
|
||||||
},
|
|
||||||
{ text: 'Unlimited User Seats' },
|
|
||||||
],
|
|
||||||
monthlyPrice: '$10',
|
|
||||||
monthlyPriceLabel: 'Per month',
|
|
||||||
annuallyPrice: '$7.5',
|
|
||||||
annuallyPriceLabel: 'Per month',
|
|
||||||
monthlyVariantId: '446152',
|
|
||||||
// monthlyVariantId: '450016',
|
|
||||||
annuallyVariantId: '446153',
|
|
||||||
// annuallyVariantId: '450018',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Essential',
|
|
||||||
slug: 'capital_plus',
|
|
||||||
description: 'Good for have inventory and want more financial reports.',
|
|
||||||
features: [
|
|
||||||
{ text: 'All Capital Basic features' },
|
|
||||||
{ text: 'Purchase Invoices' },
|
|
||||||
{
|
|
||||||
text: 'Multi Currency Transactions',
|
|
||||||
hintLabel: 'Multi Currency',
|
|
||||||
hint: 'Pay and get paid and do manual journals in any currency with real time exchange rates conversions.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Transactions Locking',
|
|
||||||
hintLabel: 'Transactions Locking',
|
|
||||||
hint: 'Transaction Locking freezes transactions to prevent any additions, modifications, or deletions of transactions recorded during the specified date.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Inventory Tracking',
|
|
||||||
hintLabel: 'Inventory Tracking',
|
|
||||||
hint: 'Track goods in the stock, cost of goods, and get notifications when quantity is low.',
|
|
||||||
},
|
|
||||||
{ text: 'Smart Financial Reports' },
|
|
||||||
{ text: 'Advanced Inventory Reports' },
|
|
||||||
],
|
|
||||||
monthlyPrice: '$20',
|
|
||||||
monthlyPriceLabel: 'Per month',
|
|
||||||
annuallyPrice: '$15',
|
|
||||||
annuallyPriceLabel: 'Per month',
|
|
||||||
// monthlyVariantId: '450028',
|
|
||||||
monthlyVariantId: '446155',
|
|
||||||
// annuallyVariantId: '450029',
|
|
||||||
annuallyVariantId: '446156',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Plus',
|
|
||||||
slug: 'essentials',
|
|
||||||
description: 'Good for business want financial and access control.',
|
|
||||||
features: [
|
|
||||||
{ text: 'All Capital Essential features' },
|
|
||||||
{ text: 'Custom User Roles Access' },
|
|
||||||
{ text: 'Vendor Credits' },
|
|
||||||
{
|
|
||||||
text: 'Budgeting',
|
|
||||||
hint: 'Create multiple budgets and compare targets with actuals to understand how your business is performing.',
|
|
||||||
},
|
|
||||||
{ text: 'Analysis Cost Center' },
|
|
||||||
],
|
|
||||||
monthlyPrice: '$25',
|
|
||||||
monthlyPriceLabel: 'Per month',
|
|
||||||
annuallyPrice: '$19',
|
|
||||||
annuallyPriceLabel: 'Per month',
|
|
||||||
featured: true,
|
|
||||||
// monthlyVariantId: '450031',
|
|
||||||
monthlyVariantId: '446165',
|
|
||||||
// annuallyVariantId: '450032',
|
|
||||||
annuallyVariantId: '446164',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Capital Big',
|
|
||||||
slug: 'essentials',
|
|
||||||
description: 'Good for businesses have multiple branches.',
|
|
||||||
features: [
|
|
||||||
{ text: 'All Capital Plus features' },
|
|
||||||
{
|
|
||||||
text: 'Multiple Branches',
|
|
||||||
hintLabel: '',
|
|
||||||
hint: 'Track the organization transactions and accounts in multiple branches.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Multiple Warehouses',
|
|
||||||
hintLabel: 'Multiple Warehouses',
|
|
||||||
hint: 'Track the organization inventory in multiple warehouses and transfer goods between them.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
monthlyPrice: '$40',
|
|
||||||
monthlyPriceLabel: 'Per month',
|
|
||||||
annuallyPrice: '$30',
|
|
||||||
annuallyPriceLabel: 'Per month',
|
|
||||||
// monthlyVariantId: '450024',
|
|
||||||
monthlyVariantId: '446167',
|
|
||||||
// annuallyVariantId: '450025',
|
|
||||||
annuallyVariantId: '446168',
|
|
||||||
},
|
|
||||||
] as SubscriptionPlan[];
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -69,14 +69,6 @@ function AccountDeleteTransactionAlert({
|
|||||||
'Cannot delete transaction converted from uncategorized transaction but you uncategorize it.',
|
'Cannot delete transaction converted from uncategorized transaction but you uncategorize it.',
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
} else if (
|
|
||||||
errors.find((e) => e.type === 'CANNOT_DELETE_TRANSACTION_MATCHED')
|
|
||||||
) {
|
|
||||||
AppToaster.show({
|
|
||||||
message:
|
|
||||||
'Cannot delete a transaction matched to the bank transaction',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
import { Button, Classes, Intent, Radio, Tag } from '@blueprintjs/core';
|
import { Button, Classes, Intent, Radio, Tag } from '@blueprintjs/core';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
@@ -17,11 +16,11 @@ import {
|
|||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCreateBankRule, useEditBankRule } from '@/hooks/query/bank-rules';
|
import { useCreateBankRule, useEditBankRule } from '@/hooks/query/bank-rules';
|
||||||
import {
|
import {
|
||||||
|
AssignTransactionTypeOptions,
|
||||||
FieldCondition,
|
FieldCondition,
|
||||||
Fields,
|
Fields,
|
||||||
RuleFormValues,
|
RuleFormValues,
|
||||||
TransactionTypeOptions,
|
TransactionTypeOptions,
|
||||||
getAccountRootFromMoneyCategory,
|
|
||||||
initialValues,
|
initialValues,
|
||||||
} from './_utils';
|
} from './_utils';
|
||||||
import { useRuleFormDialogBoot } from './RuleFormBoot';
|
import { useRuleFormDialogBoot } from './RuleFormBoot';
|
||||||
@@ -32,11 +31,6 @@ import {
|
|||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import { getAddMoneyInOptions, getAddMoneyOutOptions } from '@/constants';
|
|
||||||
|
|
||||||
// Retrieves the add money in button options.
|
|
||||||
const MoneyInOptions = getAddMoneyInOptions();
|
|
||||||
const MoneyOutOptions = getAddMoneyOutOptions();
|
|
||||||
|
|
||||||
function RuleFormContentFormRoot({
|
function RuleFormContentFormRoot({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
@@ -53,6 +47,7 @@ function RuleFormContentFormRoot({
|
|||||||
...initialValues,
|
...initialValues,
|
||||||
...transformToForm(transformToCamelCase(bankRule), initialValues),
|
...transformToForm(transformToCamelCase(bankRule), initialValues),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the form submitting.
|
// Handles the form submitting.
|
||||||
const handleSubmit = (
|
const handleSubmit = (
|
||||||
values: RuleFormValues,
|
values: RuleFormValues,
|
||||||
@@ -97,9 +92,8 @@ function RuleFormContentFormRoot({
|
|||||||
label={'Rule Name'}
|
label={'Rule Name'}
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
style={{ maxWidth: 300 }}
|
style={{ maxWidth: 300 }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FInputGroup name={'name'} fastField />
|
<FInputGroup name={'name'} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
@@ -107,22 +101,29 @@ function RuleFormContentFormRoot({
|
|||||||
label={'Apply the rule to account'}
|
label={'Apply the rule to account'}
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
style={{ maxWidth: 350 }}
|
style={{ maxWidth: 350 }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'applyIfAccountId'}
|
name={'applyIfAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByTypes={['cash', 'bank']}
|
filterByTypes={['cash', 'bank']}
|
||||||
fastField
|
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<RuleApplyIfTransactionTypeField />
|
<FFormGroup
|
||||||
|
name={'applyIfTransactionType'}
|
||||||
|
label={'Apply to transactions are'}
|
||||||
|
style={{ maxWidth: 350 }}
|
||||||
|
>
|
||||||
|
<FSelect
|
||||||
|
name={'applyIfTransactionType'}
|
||||||
|
items={TransactionTypeOptions}
|
||||||
|
popoverProps={{ minimal: true, inline: false }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'conditionsType'}
|
name={'conditionsType'}
|
||||||
label={'Categorize the transactions when'}
|
label={'Categorize the transactions when'}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FRadioGroup name={'conditionsType'}>
|
<FRadioGroup name={'conditionsType'}>
|
||||||
<Radio value={'and'} label={'All the following criteria matches'} />
|
<Radio value={'and'} label={'All the following criteria matches'} />
|
||||||
@@ -138,16 +139,34 @@ function RuleFormContentFormRoot({
|
|||||||
Then Assign
|
Then Assign
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<RuleAssignCategoryField />
|
<FFormGroup
|
||||||
<RuleAssignCategoryAccountField />
|
name={'assignCategory'}
|
||||||
|
label={'Transaction type'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
style={{ maxWidth: 300 }}
|
||||||
|
>
|
||||||
|
<FSelect
|
||||||
|
name={'assignCategory'}
|
||||||
|
items={AssignTransactionTypeOptions}
|
||||||
|
popoverProps={{ minimal: true, inline: false }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'assignAccountId'}
|
||||||
|
label={'Account category'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
style={{ maxWidth: 300 }}
|
||||||
|
>
|
||||||
|
<AccountsSelect name={'assignAccountId'} items={accounts} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'assignRef'}
|
name={'assignRef'}
|
||||||
label={'Reference'}
|
label={'Reference'}
|
||||||
style={{ maxWidth: 300 }}
|
style={{ maxWidth: 300 }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FInputGroup name={'assignRef'} fastField />
|
<FInputGroup name={'assignRef'} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<RuleFormActions />
|
<RuleFormActions />
|
||||||
@@ -184,13 +203,11 @@ function RuleFormConditions() {
|
|||||||
name={`conditions[${index}].field`}
|
name={`conditions[${index}].field`}
|
||||||
label={'Field'}
|
label={'Field'}
|
||||||
style={{ marginBottom: 0, flex: '1 0' }}
|
style={{ marginBottom: 0, flex: '1 0' }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FSelect
|
<FSelect
|
||||||
name={`conditions[${index}].field`}
|
name={`conditions[${index}].field`}
|
||||||
items={Fields}
|
items={Fields}
|
||||||
popoverProps={{ minimal: true, inline: false }}
|
popoverProps={{ minimal: true, inline: false }}
|
||||||
fastField
|
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
@@ -198,13 +215,11 @@ function RuleFormConditions() {
|
|||||||
name={`conditions[${index}].comparator`}
|
name={`conditions[${index}].comparator`}
|
||||||
label={'Condition'}
|
label={'Condition'}
|
||||||
style={{ marginBottom: 0, flex: '1 0' }}
|
style={{ marginBottom: 0, flex: '1 0' }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FSelect
|
<FSelect
|
||||||
name={`conditions[${index}].comparator`}
|
name={`conditions[${index}].comparator`}
|
||||||
items={FieldCondition}
|
items={FieldCondition}
|
||||||
popoverProps={{ minimal: true, inline: false }}
|
popoverProps={{ minimal: true, inline: false }}
|
||||||
fastField
|
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
@@ -212,9 +227,8 @@ function RuleFormConditions() {
|
|||||||
name={`conditions[${index}].value`}
|
name={`conditions[${index}].value`}
|
||||||
label={'Value'}
|
label={'Value'}
|
||||||
style={{ marginBottom: 0, flex: '1 0 ', width: '40%' }}
|
style={{ marginBottom: 0, flex: '1 0 ', width: '40%' }}
|
||||||
fastField
|
|
||||||
>
|
>
|
||||||
<FInputGroup name={`conditions[${index}].value`} fastField />
|
<FInputGroup name={`conditions[${index}].value`} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
@@ -270,104 +284,3 @@ function RuleFormActionsRoot({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RuleFormActions = R.compose(withDialogActions)(RuleFormActionsRoot);
|
const RuleFormActions = R.compose(withDialogActions)(RuleFormActionsRoot);
|
||||||
|
|
||||||
function RuleApplyIfTransactionTypeField() {
|
|
||||||
const { setFieldValue } = useFormikContext<RuleFormValues>();
|
|
||||||
|
|
||||||
const handleItemChange = useCallback(
|
|
||||||
(item: any) => {
|
|
||||||
setFieldValue('applyIfTransactionType', item.value);
|
|
||||||
setFieldValue('assignCategory', '');
|
|
||||||
setFieldValue('assignAccountId', '');
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FFormGroup
|
|
||||||
name={'applyIfTransactionType'}
|
|
||||||
label={'Apply to transactions are'}
|
|
||||||
style={{ maxWidth: 350 }}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FSelect
|
|
||||||
name={'applyIfTransactionType'}
|
|
||||||
items={TransactionTypeOptions}
|
|
||||||
popoverProps={{ minimal: true, inline: false }}
|
|
||||||
onItemChange={handleItemChange}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RuleAssignCategoryField() {
|
|
||||||
const { values, setFieldValue } = useFormikContext<RuleFormValues>();
|
|
||||||
|
|
||||||
// Retrieves the transaction types if it is deposit or withdrawal.
|
|
||||||
const transactionTypes = useMemo(
|
|
||||||
() =>
|
|
||||||
values?.applyIfTransactionType === 'deposit'
|
|
||||||
? MoneyInOptions
|
|
||||||
: MoneyOutOptions,
|
|
||||||
[values?.applyIfTransactionType],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles the select item change.
|
|
||||||
const handleItemChange = useCallback(
|
|
||||||
(item: any) => {
|
|
||||||
setFieldValue('assignCategory', item.value);
|
|
||||||
setFieldValue('assignAccountId', '');
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FFormGroup
|
|
||||||
name={'assignCategory'}
|
|
||||||
label={'Transaction type'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
style={{ maxWidth: 300 }}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FSelect
|
|
||||||
name={'assignCategory'}
|
|
||||||
items={transactionTypes}
|
|
||||||
popoverProps={{ minimal: true, inline: false }}
|
|
||||||
valueAccessor={'value'}
|
|
||||||
textAccessor={'name'}
|
|
||||||
onItemChange={handleItemChange}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RuleAssignCategoryAccountField() {
|
|
||||||
const { values } = useFormikContext<RuleFormValues>();
|
|
||||||
const { accounts } = useRuleFormDialogBoot();
|
|
||||||
|
|
||||||
const accountRoot = useMemo(
|
|
||||||
() => getAccountRootFromMoneyCategory(values.assignCategory),
|
|
||||||
[values.assignCategory],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FFormGroup
|
|
||||||
name={'assignAccountId'}
|
|
||||||
label={'Account category'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
style={{ maxWidth: 300 }}
|
|
||||||
fastField
|
|
||||||
shouldUpdateDeps={{ accountRoot }}
|
|
||||||
>
|
|
||||||
<AccountsSelect
|
|
||||||
name={'assignAccountId'}
|
|
||||||
items={accounts}
|
|
||||||
filterByRootTypes={accountRoot}
|
|
||||||
shouldUpdateDeps={{ accountRoot }}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { camelCase, get, upperFirst } from 'lodash';
|
|
||||||
import { MoneyCategoryPerCreditAccountRootType } from '@/constants/cashflowOptions';
|
|
||||||
|
|
||||||
export const initialValues = {
|
export const initialValues = {
|
||||||
name: '',
|
name: '',
|
||||||
order: 0,
|
order: 0,
|
||||||
applyIfAccountId: '',
|
applyIfAccountId: '',
|
||||||
applyIfTransactionType: 'deposit',
|
applyIfTransactionType: '',
|
||||||
conditionsType: 'and',
|
conditionsType: 'and',
|
||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
@@ -50,9 +47,3 @@ export const FieldCondition = [
|
|||||||
export const AssignTransactionTypeOptions = [
|
export const AssignTransactionTypeOptions = [
|
||||||
{ value: 'expense', text: 'Expense' },
|
{ value: 'expense', text: 'Expense' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getAccountRootFromMoneyCategory = (category: string): string[] => {
|
|
||||||
const _category = upperFirst(camelCase(category));
|
|
||||||
|
|
||||||
return get(MoneyCategoryPerCreditAccountRootType, _category) || [];
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
TableSkeletonHeader,
|
TableSkeletonHeader,
|
||||||
TableVirtualizedListRows,
|
TableVirtualizedListRows,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
AppToaster,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { TABLES } from '@/constants/tables';
|
import { TABLES } from '@/constants/tables';
|
||||||
|
|
||||||
@@ -21,11 +19,9 @@ import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
|||||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||||
import { useAccountTransactionsColumns, ActionsMenu } from './components';
|
import { useAccountTransactionsColumns, ActionsMenu } from './components';
|
||||||
import { useAccountTransactionsAllContext } from './AccountTransactionsAllBoot';
|
import { useAccountTransactionsAllContext } from './AccountTransactionsAllBoot';
|
||||||
import { useUnmatchMatchedUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
|
||||||
import { handleCashFlowTransactionType } from './utils';
|
import { handleCashFlowTransactionType } from './utils';
|
||||||
|
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import { useUncategorizeTransaction } from '@/hooks/query';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account transactions data table.
|
* Account transactions data table.
|
||||||
@@ -47,14 +43,14 @@ function AccountTransactionsDataTable({
|
|||||||
const { cashflowTransactions, isCashFlowTransactionsLoading } =
|
const { cashflowTransactions, isCashFlowTransactionsLoading } =
|
||||||
useAccountTransactionsAllContext();
|
useAccountTransactionsAllContext();
|
||||||
|
|
||||||
const { mutateAsync: uncategorizeTransaction } = useUncategorizeTransaction();
|
|
||||||
const { mutateAsync: unmatchTransaction } =
|
|
||||||
useUnmatchMatchedUncategorizedTransaction();
|
|
||||||
|
|
||||||
// Local storage memorizing columns widths.
|
// Local storage memorizing columns widths.
|
||||||
const [initialColumnsWidths, , handleColumnResizing] =
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions);
|
useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions);
|
||||||
|
|
||||||
|
// handle delete transaction
|
||||||
|
const handleDeleteTransaction = ({ reference_id }) => {
|
||||||
|
openAlert('account-transaction-delete', { referenceId: reference_id });
|
||||||
|
};
|
||||||
// Handle view details action.
|
// Handle view details action.
|
||||||
const handleViewDetailCashflowTransaction = (referenceType) => {
|
const handleViewDetailCashflowTransaction = (referenceType) => {
|
||||||
handleCashFlowTransactionType(referenceType, openDrawer);
|
handleCashFlowTransactionType(referenceType, openDrawer);
|
||||||
@@ -64,38 +60,6 @@ function AccountTransactionsDataTable({
|
|||||||
const referenceType = cell.row.original;
|
const referenceType = cell.row.original;
|
||||||
handleCashFlowTransactionType(referenceType, openDrawer);
|
handleCashFlowTransactionType(referenceType, openDrawer);
|
||||||
};
|
};
|
||||||
// Handles the unmatching the matched transaction.
|
|
||||||
const handleUnmatchTransaction = (transaction) => {
|
|
||||||
unmatchTransaction({ id: transaction.uncategorized_transaction_id })
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The bank transaction has been unmatched.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Handle uncategorize transaction.
|
|
||||||
const handleUncategorizeTransaction = (transaction) => {
|
|
||||||
uncategorizeTransaction(transaction.uncategorized_transaction_id)
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The bank transaction has been uncategorized.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CashflowTransactionsTable
|
<CashflowTransactionsTable
|
||||||
@@ -123,8 +87,7 @@ function AccountTransactionsDataTable({
|
|||||||
className="table-constrant"
|
className="table-constrant"
|
||||||
payload={{
|
payload={{
|
||||||
onViewDetails: handleViewDetailCashflowTransaction,
|
onViewDetails: handleViewDetailCashflowTransaction,
|
||||||
onUncategorize: handleUncategorizeTransaction,
|
onDelete: handleDeleteTransaction,
|
||||||
onUnmatch: handleUnmatchTransaction,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,17 +12,19 @@ import {
|
|||||||
AppToaster,
|
AppToaster,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { TABLES } from '@/constants/tables';
|
import { TABLES } from '@/constants/tables';
|
||||||
import { ActionsMenu } from './UncategorizedTransactions/components';
|
|
||||||
|
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
import { withBankingActions } from '../withBankingActions';
|
import { withBankingActions } from '../withBankingActions';
|
||||||
|
|
||||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||||
import { useAccountUncategorizedTransactionsColumns } from './components';
|
import {
|
||||||
|
ActionsMenu,
|
||||||
|
useAccountUncategorizedTransactionsColumns,
|
||||||
|
} from './components';
|
||||||
import { useAccountUncategorizedTransactionsContext } from './AllTransactionsUncategorizedBoot';
|
import { useAccountUncategorizedTransactionsContext } from './AllTransactionsUncategorizedBoot';
|
||||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
|
||||||
|
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account transactions data table.
|
* Account transactions data table.
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
|
||||||
import { Icon } from '@/components';
|
|
||||||
import { safeCallback } from '@/utils';
|
|
||||||
|
|
||||||
export function ActionsMenu({
|
|
||||||
payload: { onCategorize, onExclude },
|
|
||||||
row: { original },
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Menu>
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="reader-18" />}
|
|
||||||
text={'Categorize'}
|
|
||||||
onClick={safeCallback(onCategorize, original)}
|
|
||||||
/>
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem
|
|
||||||
text={'Exclude'}
|
|
||||||
onClick={safeCallback(onExclude, original)}
|
|
||||||
icon={<Icon icon="disable" iconSize={16} />}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,56 +5,44 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
MenuDivider,
|
||||||
Tag,
|
Tag,
|
||||||
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Box, FormatDateCell, Icon, MaterialProgressBar } from '@/components';
|
import {
|
||||||
|
Box,
|
||||||
|
Can,
|
||||||
|
FormatDateCell,
|
||||||
|
Icon,
|
||||||
|
MaterialProgressBar,
|
||||||
|
} from '@/components';
|
||||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
import { safeCallback } from '@/utils';
|
import { safeCallback } from '@/utils';
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
payload: { onUncategorize, onUnmatch },
|
payload: { onCategorize, onExclude },
|
||||||
row: { original },
|
row: { original },
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
{original.status === 'categorized' && (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="reader-18" />}
|
icon={<Icon icon="reader-18" />}
|
||||||
text={'Uncategorize'}
|
text={'Categorize'}
|
||||||
onClick={safeCallback(onUncategorize, original)}
|
onClick={safeCallback(onCategorize, original)}
|
||||||
/>
|
/>
|
||||||
)}
|
<MenuDivider />
|
||||||
{original.status === 'matched' && (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={'Unmatch'}
|
text={'Exclude'}
|
||||||
icon={<Icon icon="unlink" iconSize={16} />}
|
onClick={safeCallback(onExclude, original)}
|
||||||
onClick={safeCallback(onUnmatch, original)}
|
icon={<Icon icon="disable" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allTransactionsStatusAccessor = (transaction) => {
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
intent={
|
|
||||||
transaction.status === 'categorized'
|
|
||||||
? Intent.SUCCESS
|
|
||||||
: transaction.status === 'matched'
|
|
||||||
? Intent.SUCCESS
|
|
||||||
: Intent.NONE
|
|
||||||
}
|
|
||||||
minimal={transaction.status === 'manual'}
|
|
||||||
>
|
|
||||||
{transaction.formatted_status}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve account transctions table columns.
|
* Retrieve account transctions table columns.
|
||||||
*/
|
*/
|
||||||
@@ -82,7 +70,7 @@ export function useAccountTransactionsColumns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transaction_number',
|
id: 'transaction_number',
|
||||||
Header: 'Transaction #',
|
Header: intl.get('transaction_number'),
|
||||||
accessor: 'transaction_number',
|
accessor: 'transaction_number',
|
||||||
width: 160,
|
width: 160,
|
||||||
className: 'transaction_number',
|
className: 'transaction_number',
|
||||||
@@ -91,18 +79,13 @@ export function useAccountTransactionsColumns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'reference_number',
|
id: 'reference_number',
|
||||||
Header: 'Ref.#',
|
Header: intl.get('reference_no'),
|
||||||
accessor: 'reference_number',
|
accessor: 'reference_number',
|
||||||
width: 160,
|
width: 160,
|
||||||
className: 'reference_number',
|
className: 'reference_number',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'status',
|
|
||||||
Header: 'Status',
|
|
||||||
accessor: allTransactionsStatusAccessor,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'deposit',
|
id: 'deposit',
|
||||||
Header: intl.get('cash_flow.label.deposit'),
|
Header: intl.get('cash_flow.label.deposit'),
|
||||||
@@ -133,6 +116,16 @@ export function useAccountTransactionsColumns() {
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'balance',
|
||||||
|
Header: intl.get('balance'),
|
||||||
|
accessor: 'formatted_balance',
|
||||||
|
className: 'balance',
|
||||||
|
width: 150,
|
||||||
|
textOverview: true,
|
||||||
|
clickable: true,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -211,9 +204,10 @@ export function useAccountUncategorizedTransactionsColumns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'reference_number',
|
id: 'reference_number',
|
||||||
Header: 'Ref.#',
|
Header: intl.get('reference_no'),
|
||||||
accessor: 'reference_no',
|
accessor: 'reference_number',
|
||||||
width: 50,
|
width: 50,
|
||||||
|
className: 'reference_number',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
FFormGroup,
|
FFormGroup,
|
||||||
FInputGroup,
|
FInputGroup,
|
||||||
FTextArea,
|
FTextArea,
|
||||||
Icon,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
@@ -22,7 +21,7 @@ export default function CategorizeTransactionOtherIncome() {
|
|||||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
formatDate={(date) => date.toLocaleDateString()}
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
parseDate={(str) => new Date(str)}
|
parseDate={(str) => new Date(str)}
|
||||||
inputProps={{ fill: true, leftElement: <Icon icon={'date-range'} /> }}
|
inputProps={{ fill: true }}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
FFormGroup,
|
FFormGroup,
|
||||||
FInputGroup,
|
FInputGroup,
|
||||||
FTextArea,
|
FTextArea,
|
||||||
Icon,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
@@ -22,7 +21,7 @@ export default function CategorizeTransactionOwnerContribution() {
|
|||||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
formatDate={(date) => date.toLocaleDateString()}
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
parseDate={(str) => new Date(str)}
|
parseDate={(str) => new Date(str)}
|
||||||
inputProps={{ fill: true, leftElement: <Icon icon={'date-range'} /> }}
|
inputProps={{ fill: true }}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
FFormGroup,
|
FFormGroup,
|
||||||
FInputGroup,
|
FInputGroup,
|
||||||
FTextArea,
|
FTextArea,
|
||||||
Icon,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
@@ -22,7 +21,7 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
formatDate={(date) => date.toLocaleDateString()}
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
parseDate={(str) => new Date(str)}
|
parseDate={(str) => new Date(str)}
|
||||||
inputProps={{ fill: true, leftElement: <Icon icon={'date-range'} /> }}
|
inputProps={{ fill: true }}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user