mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
Compare commits
1 Commits
v0.18.6
...
reconcile-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45ad4aa1f1 |
@@ -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 }),
|
||||||
|
|||||||
@@ -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
|
|
||||||
`),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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,9 +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';
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -261,9 +258,6 @@ export const susbcribers = () => {
|
|||||||
// Bank Rules
|
// Bank Rules
|
||||||
TriggerRecognizedTransactions,
|
TriggerRecognizedTransactions,
|
||||||
UnlinkBankRuleOnDeleteBankRule,
|
UnlinkBankRuleOnDeleteBankRule,
|
||||||
DecrementUncategorizedTransactionOnMatching,
|
|
||||||
DecrementUncategorizedTransactionOnExclude,
|
|
||||||
DecrementUncategorizedTransactionOnCategorize,
|
|
||||||
|
|
||||||
// Validate matching
|
// Validate matching
|
||||||
ValidateMatchingOnCashflowDelete,
|
ValidateMatchingOnCashflowDelete,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,23 +84,20 @@ 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,
|
{
|
||||||
{
|
categorized: true,
|
||||||
categorized: true,
|
categorizeRefType: 'CashflowTransaction',
|
||||||
categorizeRefType: 'CashflowTransaction',
|
categorizeRefId: cashflowTransaction.id,
|
||||||
categorizeRefId: cashflowTransaction.id,
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
// Triggers `onCashflowTransactionCategorized` event.
|
// Triggers `onCashflowTransactionCategorized` event.
|
||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(
|
||||||
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,
|
return transaction;
|
||||||
} as IUncategorizedTransactionCreatingEventPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx).insertAndFetch({
|
|
||||||
...createUncategorizedTransactionDTO,
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
parsedQuery
|
|
||||||
);
|
|
||||||
await cashflowTransactionsRepo.asyncInit();
|
|
||||||
|
|
||||||
|
// Validates the cashflow account type.
|
||||||
|
this.validateCashflowAccountType(account);
|
||||||
|
|
||||||
|
// Retrieve the cashflow account transactions.
|
||||||
|
const { results: transactions, pagination } =
|
||||||
|
await this.cashflowTransactionsRepo.getCashflowAccountTransactions(
|
||||||
|
tenantId,
|
||||||
|
parsedQuery
|
||||||
|
);
|
||||||
|
// 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,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_')) {
|
||||||
|
|||||||
@@ -399,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',
|
||||||
|
|
||||||
@@ -642,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;
|
||||||
|
|
||||||
@@ -48,9 +47,9 @@
|
|||||||
}
|
}
|
||||||
.price {
|
.price {
|
||||||
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>
|
||||||
|
|||||||
@@ -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[];
|
|
||||||
|
|||||||
@@ -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={'Categorize'}
|
||||||
text={'Uncategorize'}
|
onClick={safeCallback(onCategorize, original)}
|
||||||
onClick={safeCallback(onUncategorize, original)}
|
/>
|
||||||
/>
|
<MenuDivider />
|
||||||
)}
|
<MenuItem
|
||||||
{original.status === 'matched' && (
|
text={'Exclude'}
|
||||||
<MenuItem
|
onClick={safeCallback(onExclude, original)}
|
||||||
text={'Unmatch'}
|
icon={<Icon icon="disable" iconSize={16} />}
|
||||||
icon={<Icon icon="unlink" iconSize={16} />}
|
/>
|
||||||
onClick={safeCallback(onUnmatch, original)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 CategorizeTransactionOtherExpense() {
|
|||||||
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 CategorizeTransactionOwnerDrawings() {
|
|||||||
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 CategorizeTransactionToAccount() {
|
|||||||
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>
|
||||||
|
|
||||||
|
|||||||
@@ -8,26 +8,16 @@ import {
|
|||||||
} from '../withBankingActions';
|
} from '../withBankingActions';
|
||||||
import { CategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
import { CategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
||||||
import { withBanking } from '../withBanking';
|
import { withBanking } from '../withBanking';
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
interface CategorizeTransactionAsideProps extends WithBankingActionsProps {}
|
interface CategorizeTransactionAsideProps extends WithBankingActionsProps {}
|
||||||
|
|
||||||
function CategorizeTransactionAsideRoot({
|
function CategorizeTransactionAsideRoot({
|
||||||
// #withBankingActions
|
// #withBankingActions
|
||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
closeReconcileMatchingTransaction,
|
|
||||||
|
|
||||||
// #withBanking
|
// #withBanking
|
||||||
selectedUncategorizedTransactionId,
|
selectedUncategorizedTransactionId,
|
||||||
}: CategorizeTransactionAsideProps) {
|
}: CategorizeTransactionAsideProps) {
|
||||||
//
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
closeReconcileMatchingTransaction();
|
|
||||||
},
|
|
||||||
[closeReconcileMatchingTransaction],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
closeMatchingTransactionAside();
|
closeMatchingTransactionAside();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
color: rgb(21, 82, 200),
|
color: rgb(21, 82, 200),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover:not(.active){
|
&:hover:not(.active){
|
||||||
border-color: #c0c0c0;
|
border-color: #c0c0c0;
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
||||||
box-shadow: 0 0 0 1px #CBCBCB;
|
border-color: #CBCBCB;
|
||||||
}
|
}
|
||||||
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
@@ -33,18 +34,10 @@
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox:global(.bp4-control.bp4-checkbox) :global input:checked ~ .bp4-control-indicator{
|
|
||||||
box-shadow: 0 0 0 1px #0069ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: #252A33;
|
color: #10161A;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
.label :global strong {
|
|
||||||
font-weight: 500;
|
|
||||||
font-variant-numeric:tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface MatchTransactionCheckboxProps {
|
|||||||
active?: boolean;
|
active?: boolean;
|
||||||
initialActive?: boolean;
|
initialActive?: boolean;
|
||||||
onChange?: (state: boolean) => void;
|
onChange?: (state: boolean) => void;
|
||||||
label: string | React.ReactNode;
|
label: string;
|
||||||
date: string;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ export function MatchTransactionCheckbox({
|
|||||||
position="apart"
|
position="apart"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={3}>
|
||||||
<span className={styles.label}>{label}</span>
|
<span className={styles.label}>{label}</span>
|
||||||
<Text className={styles.date}>Date: {date}</Text>
|
<Text className={styles.date}>Date: {date}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const MatchingReconcileFormSchema = Yup.object().shape({
|
|||||||
type: Yup.string().required().label('Type'),
|
type: Yup.string().required().label('Type'),
|
||||||
date: Yup.string().required().label('Date'),
|
date: Yup.string().required().label('Date'),
|
||||||
amount: Yup.string().required().label('Amount'),
|
amount: Yup.string().required().label('Amount'),
|
||||||
memo: Yup.string().required().min(3).label('Memo'),
|
memo: Yup.string().required().label('Memo'),
|
||||||
referenceNo: Yup.string().label('Refernece #'),
|
referenceNo: Yup.string().label('Refernece #'),
|
||||||
category: Yup.string().required().label('Categogry'),
|
category: Yup.string().required().label('Categogry'),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Button, Intent, Position, Tag } from '@blueprintjs/core';
|
import { Button, Intent, Position, Tag } from '@blueprintjs/core';
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Form, Formik, FormikValues, useFormikContext } from 'formik';
|
||||||
import moment from 'moment';
|
|
||||||
import { round } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
AccountsSelect,
|
AccountsSelect,
|
||||||
AppToaster,
|
AppToaster,
|
||||||
@@ -14,7 +12,6 @@ import {
|
|||||||
FInputGroup,
|
FInputGroup,
|
||||||
FMoneyInputGroup,
|
FMoneyInputGroup,
|
||||||
Group,
|
Group,
|
||||||
Icon,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { Aside } from '@/components/Aside/Aside';
|
import { Aside } from '@/components/Aside/Aside';
|
||||||
import { momentFormatter } from '@/utils';
|
import { momentFormatter } from '@/utils';
|
||||||
@@ -28,43 +25,29 @@ import {
|
|||||||
import { useCreateCashflowTransaction } from '@/hooks/query';
|
import { useCreateCashflowTransaction } from '@/hooks/query';
|
||||||
import { useAccountTransactionsContext } from '../../AccountTransactions/AccountTransactionsProvider';
|
import { useAccountTransactionsContext } from '../../AccountTransactions/AccountTransactionsProvider';
|
||||||
import { MatchingReconcileFormSchema } from './MatchingReconcileTransactionForm.schema';
|
import { MatchingReconcileFormSchema } from './MatchingReconcileTransactionForm.schema';
|
||||||
import { initialValues, transformToReq } from './_utils';
|
import { initialValues } from './_utils';
|
||||||
import { withBanking } from '../../withBanking';
|
|
||||||
|
|
||||||
interface MatchingReconcileTransactionFormProps {
|
|
||||||
onSubmitSuccess?: (values: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MatchingReconcileTransactionFormRoot({
|
function MatchingReconcileTransactionFormRoot({
|
||||||
closeReconcileMatchingTransaction,
|
closeReconcileMatchingTransaction,
|
||||||
reconcileMatchingTransactionPendingAmount,
|
}) {
|
||||||
|
|
||||||
// #props¿
|
|
||||||
onSubmitSuccess,
|
|
||||||
}: MatchingReconcileTransactionFormProps) {
|
|
||||||
// Mutation create cashflow transaction.
|
// Mutation create cashflow transaction.
|
||||||
const { mutateAsync: createCashflowTransactionMutate } =
|
const { mutateAsync: createCashflowTransactionMutate } =
|
||||||
useCreateCashflowTransaction();
|
useCreateCashflowTransaction();
|
||||||
|
|
||||||
const { accountId } = useAccountTransactionsContext();
|
const { accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
// Handles the aside close.
|
|
||||||
const handleAsideClose = () => {
|
const handleAsideClose = () => {
|
||||||
closeReconcileMatchingTransaction();
|
closeReconcileMatchingTransaction();
|
||||||
};
|
};
|
||||||
// Handle the form submitting.
|
|
||||||
const handleSubmit = (
|
const handleSubmit = (
|
||||||
values: MatchingReconcileTransactionValues,
|
values: MatchingReconcileTransactionValues,
|
||||||
{
|
{ setSubmitting }: FormikValues<MatchingReconcileTransactionValues>,
|
||||||
setSubmitting,
|
|
||||||
setErrors,
|
|
||||||
}: FormikHelpers<MatchingReconcileTransactionValues>,
|
|
||||||
) => {
|
) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
const _values = transformToReq(values, accountId);
|
const _values = transformToReq(values, accountId);
|
||||||
|
|
||||||
createCashflowTransactionMutate(_values)
|
createCashflowTransactionMutate(_values)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -72,34 +55,15 @@ function MatchingReconcileTransactionFormRoot({
|
|||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
closeReconcileMatchingTransaction();
|
closeReconcileMatchingTransaction();
|
||||||
onSubmitSuccess &&
|
|
||||||
onSubmitSuccess({ id: res.data.id, type: 'CashflowTransaction' });
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
if (
|
|
||||||
error.response.data?.errors?.find(
|
|
||||||
(e) => e.type === 'BRANCH_ID_REQUIRED',
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setErrors({
|
|
||||||
branchId: 'The branch is required.',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const _initialValues = {
|
AppToaster.show({
|
||||||
...initialValues,
|
message: 'Something went wrong.',
|
||||||
amount: round(Math.abs(reconcileMatchingTransactionPendingAmount), 2) || 0,
|
intent: Intent.DANGER,
|
||||||
date: moment().format('YYYY-MM-DD'),
|
});
|
||||||
type:
|
});
|
||||||
reconcileMatchingTransactionPendingAmount > 0 ? 'deposit' : 'withdrawal',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +75,7 @@ function MatchingReconcileTransactionFormRoot({
|
|||||||
<MatchingReconcileTransactionBoot>
|
<MatchingReconcileTransactionBoot>
|
||||||
<Formik
|
<Formik
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
initialValues={_initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={MatchingReconcileFormSchema}
|
validationSchema={MatchingReconcileFormSchema}
|
||||||
>
|
>
|
||||||
<Form className={styles.form}>
|
<Form className={styles.form}>
|
||||||
@@ -129,136 +93,11 @@ function MatchingReconcileTransactionFormRoot({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatchingReconcileTransactionForm = R.compose(
|
export const MatchingReconcileTransactionForm = R.compose(withBankingActions)(
|
||||||
withBankingActions,
|
MatchingReconcileTransactionFormRoot,
|
||||||
withBanking(({ reconcileMatchingTransactionPendingAmount }) => ({
|
);
|
||||||
reconcileMatchingTransactionPendingAmount,
|
|
||||||
})),
|
|
||||||
)(MatchingReconcileTransactionFormRoot);
|
|
||||||
|
|
||||||
function ReconcileMatchingType() {
|
export function MatchingReconcileTransactionFooter() {
|
||||||
const { setFieldValue, values } =
|
|
||||||
useFormikContext<MatchingReconcileFormValues>();
|
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
|
||||||
setFieldValue('type', value);
|
|
||||||
setFieldValue('category');
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<ContentTabs
|
|
||||||
value={values?.type || 'deposit'}
|
|
||||||
onChange={handleChange}
|
|
||||||
small
|
|
||||||
>
|
|
||||||
<ContentTabs.Tab id={'deposit'} title={'Deposit'} />
|
|
||||||
<ContentTabs.Tab id={'withdrawal'} title={'Withdrawal'} />
|
|
||||||
</ContentTabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CreateReconcileTransactionContent() {
|
|
||||||
const { branches } = useMatchingReconcileTransactionBoot();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box className={styles.content}>
|
|
||||||
<ReconcileMatchingType />
|
|
||||||
|
|
||||||
<FFormGroup label={'Date'} name={'date'} fastField>
|
|
||||||
<FDateInput
|
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
|
||||||
name={'date'}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: false,
|
|
||||||
position: Position.LEFT,
|
|
||||||
modifiers: {
|
|
||||||
preventOverflow: { enabled: true },
|
|
||||||
},
|
|
||||||
boundary: 'viewport',
|
|
||||||
}}
|
|
||||||
inputProps={{ fill: true, leftElement: <Icon icon={'date-range'} /> }}
|
|
||||||
fill
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
label={'Amount'}
|
|
||||||
name={'amount'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FMoneyInputGroup name={'amount'} fastField />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<MatchingReconcileCategoryField />
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
label={'Memo'}
|
|
||||||
name={'memo'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FInputGroup name={'memo'} fastField />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup label={'Reference No.'} name={'reference_no'} fastField>
|
|
||||||
<FInputGroup name={'reference_no'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
name={'branchId'}
|
|
||||||
label={'Branch'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<BranchSelect
|
|
||||||
name={'branchId'}
|
|
||||||
branches={branches}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: false,
|
|
||||||
position: Position.LEFT,
|
|
||||||
modifiers: {
|
|
||||||
preventOverflow: { enabled: true },
|
|
||||||
},
|
|
||||||
boundary: 'viewport',
|
|
||||||
}}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MatchingReconcileCategoryField() {
|
|
||||||
const { accounts } = useMatchingReconcileTransactionBoot();
|
|
||||||
const { values } = useFormikContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FFormGroup
|
|
||||||
label={'Category'}
|
|
||||||
name={'category'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<AccountsSelect
|
|
||||||
name={'category'}
|
|
||||||
items={accounts}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: false,
|
|
||||||
position: Position.LEFT,
|
|
||||||
modifiers: {
|
|
||||||
preventOverflow: { enabled: true },
|
|
||||||
},
|
|
||||||
boundary: 'viewport',
|
|
||||||
}}
|
|
||||||
filterByRootTypes={values.type === 'deposit' ? 'income' : 'expense'}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MatchingReconcileTransactionFooter() {
|
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -276,3 +115,85 @@ function MatchingReconcileTransactionFooter() {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ReconcileMatchingType() {
|
||||||
|
const { setFieldValue, values } =
|
||||||
|
useFormikContext<MatchingReconcileFormValues>();
|
||||||
|
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
setFieldValue('type', value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ContentTabs
|
||||||
|
value={values?.type || 'deposit'}
|
||||||
|
onChange={handleChange}
|
||||||
|
small
|
||||||
|
>
|
||||||
|
<ContentTabs.Tab id={'deposit'} title={'Deposit'} />
|
||||||
|
<ContentTabs.Tab id={'withdrawal'} title={'Withdrawal'} />
|
||||||
|
</ContentTabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreateReconcileTransactionContent() {
|
||||||
|
const { accounts, branches } = useMatchingReconcileTransactionBoot();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={styles.content}>
|
||||||
|
<ReconcileMatchingType />
|
||||||
|
|
||||||
|
<FFormGroup label={'Date'} name={'date'}>
|
||||||
|
<FDateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
name={'date'}
|
||||||
|
formatDate={(date) => date.toLocaleString()}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.LEFT,
|
||||||
|
}}
|
||||||
|
inputProps={{ fill: true }}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Amount'}
|
||||||
|
name={'amount'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<FMoneyInputGroup name={'amount'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Category'}
|
||||||
|
name={'category'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'category'}
|
||||||
|
items={accounts}
|
||||||
|
popoverProps={{ minimal: false, position: Position.LEFT }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Memo'}
|
||||||
|
name={'memo'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<FInputGroup name={'memo'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup label={'Reference No.'} name={'reference_no'}>
|
||||||
|
<FInputGroup name={'reference_no'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup name={'branchId'} label={'Branch'}>
|
||||||
|
<BranchSelect
|
||||||
|
name={'branchId'}
|
||||||
|
branches={branches}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { AnchorButton, Button, Intent, Tag, Text } from '@blueprintjs/core';
|
import { AnchorButton, Button, Intent, Tag, Text } from '@blueprintjs/core';
|
||||||
import { FastField, FastFieldProps, Formik, useFormikContext } from 'formik';
|
import { FastField, FastFieldProps, Formik, useFormikContext } from 'formik';
|
||||||
import { AppToaster, Box, FormatNumber, Group, Stack } from '@/components';
|
import { AppToaster, Box, FormatNumber, Group, Stack } from '@/components';
|
||||||
@@ -40,6 +39,9 @@ const initialValues = {
|
|||||||
function MatchingBankTransactionRoot({
|
function MatchingBankTransactionRoot({
|
||||||
// #withBankingActions
|
// #withBankingActions
|
||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
|
|
||||||
|
// #withBanking
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
}) {
|
}) {
|
||||||
const { uncategorizedTransactionId } = useCategorizeTransactionTabsBoot();
|
const { uncategorizedTransactionId } = useCategorizeTransactionTabsBoot();
|
||||||
const { mutateAsync: matchTransaction } = useMatchUncategorizedTransaction();
|
const { mutateAsync: matchTransaction } = useMatchUncategorizedTransaction();
|
||||||
@@ -69,18 +71,6 @@ function MatchingBankTransactionRoot({
|
|||||||
closeMatchingTransactionAside();
|
closeMatchingTransactionAside();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (
|
|
||||||
err.response?.data.errors.find(
|
|
||||||
(e) => e.type === 'TOTAL_MATCHING_TRANSACTIONS_INVALID',
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
AppToaster.show({
|
|
||||||
message: `The total amount does not equal the uncategorized transaction.`,
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
message: 'Something went wrong.',
|
message: 'Something went wrong.',
|
||||||
@@ -94,86 +84,25 @@ function MatchingBankTransactionRoot({
|
|||||||
uncategorizedTransactionId={uncategorizedTransactionId}
|
uncategorizedTransactionId={uncategorizedTransactionId}
|
||||||
>
|
>
|
||||||
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
||||||
<MatchingBankTransactionFormContent />
|
<>
|
||||||
|
<MatchingBankTransactionContent />
|
||||||
|
|
||||||
|
{openReconcileMatchingTransaction && (
|
||||||
|
<MatchingReconcileTransactionForm />
|
||||||
|
)}
|
||||||
|
{!openReconcileMatchingTransaction && <MatchTransactionFooter />}
|
||||||
|
</>
|
||||||
</Formik>
|
</Formik>
|
||||||
</MatchingTransactionBoot>
|
</MatchingTransactionBoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatchingBankTransaction = R.compose(withBankingActions)(
|
export const MatchingBankTransaction = R.compose(
|
||||||
MatchingBankTransactionRoot,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matching bank transaction form content.
|
|
||||||
* @returns {React.ReactNode}
|
|
||||||
*/
|
|
||||||
const MatchingBankTransactionFormContent = R.compose(
|
|
||||||
withBankingActions,
|
withBankingActions,
|
||||||
withBanking(({ openReconcileMatchingTransaction }) => ({
|
withBanking(({ openReconcileMatchingTransaction }) => ({
|
||||||
openReconcileMatchingTransaction,
|
openReconcileMatchingTransaction,
|
||||||
})),
|
})),
|
||||||
)(
|
)(MatchingBankTransactionRoot);
|
||||||
({
|
|
||||||
// #withBanking
|
|
||||||
openReconcileMatchingTransaction,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
isMatchingTransactionsFetching,
|
|
||||||
isMatchingTransactionsSuccess,
|
|
||||||
matches,
|
|
||||||
} = useMatchingTransactionBoot();
|
|
||||||
const [pending, setPending] = useState<null | {
|
|
||||||
refId: number;
|
|
||||||
refType: string;
|
|
||||||
}>(null);
|
|
||||||
|
|
||||||
const { setFieldValue } = useFormikContext();
|
|
||||||
|
|
||||||
// This effect is responsible for automatically marking a transaction as matched
|
|
||||||
// when the matching process is successful and not currently fetching.
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
pending &&
|
|
||||||
isMatchingTransactionsSuccess &&
|
|
||||||
!isMatchingTransactionsFetching
|
|
||||||
) {
|
|
||||||
const foundMatch = matches?.find(
|
|
||||||
(m) =>
|
|
||||||
m.referenceType === pending?.refType &&
|
|
||||||
m.referenceId === pending?.refId,
|
|
||||||
);
|
|
||||||
if (foundMatch) {
|
|
||||||
setFieldValue(`matched.${pending.refType}-${pending.refId}`, true);
|
|
||||||
}
|
|
||||||
setPending(null);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
isMatchingTransactionsFetching,
|
|
||||||
isMatchingTransactionsSuccess,
|
|
||||||
matches,
|
|
||||||
pending,
|
|
||||||
setFieldValue,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleReconcileFormSubmitSuccess = (payload) => {
|
|
||||||
setPending({ refId: payload.id, refType: payload.type });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MatchingBankTransactionContent />
|
|
||||||
|
|
||||||
{openReconcileMatchingTransaction && (
|
|
||||||
<MatchingReconcileTransactionForm
|
|
||||||
onSubmitSuccess={handleReconcileFormSubmitSuccess}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!openReconcileMatchingTransaction && <MatchTransactionFooter />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function MatchingBankTransactionContent() {
|
function MatchingBankTransactionContent() {
|
||||||
return (
|
return (
|
||||||
@@ -212,8 +141,8 @@ function PerfectMatchingTransactions() {
|
|||||||
key={index}
|
key={index}
|
||||||
label={`${match.transsactionTypeFormatted} for ${match.amountFormatted}`}
|
label={`${match.transsactionTypeFormatted} for ${match.amountFormatted}`}
|
||||||
date={match.dateFormatted}
|
date={match.dateFormatted}
|
||||||
transactionId={match.referenceId}
|
transactionId={match.transactionId}
|
||||||
transactionType={match.referenceType}
|
transactionType={match.transactionType}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -237,6 +166,9 @@ function PossibleMatchingTransactions() {
|
|||||||
<Box className={styles.matchBar}>
|
<Box className={styles.matchBar}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<h2 className={styles.matchBarTitle}>Possible Matches</h2>
|
<h2 className={styles.matchBarTitle}>Possible Matches</h2>
|
||||||
|
<Text style={{ fontSize: 12, color: '#5C7080' }}>
|
||||||
|
Transactions up to 20 Aug 2019
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -244,15 +176,10 @@ function PossibleMatchingTransactions() {
|
|||||||
{possibleMatches.map((match, index) => (
|
{possibleMatches.map((match, index) => (
|
||||||
<MatchTransactionField
|
<MatchTransactionField
|
||||||
key={index}
|
key={index}
|
||||||
label={
|
label={`${match.transsactionTypeFormatted} for ${match.amountFormatted}`}
|
||||||
<>
|
|
||||||
{`${match.transsactionTypeFormatted} for `}
|
|
||||||
<strong>{match.amountFormatted}</strong>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
date={match.dateFormatted}
|
date={match.dateFormatted}
|
||||||
transactionId={match.referenceId}
|
transactionId={match.transactionId}
|
||||||
transactionType={match.referenceType}
|
transactionType={match.transactionType}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -313,7 +240,7 @@ const MatchTransactionFooter = R.compose(withBankingActions)(
|
|||||||
submitForm();
|
submitForm();
|
||||||
};
|
};
|
||||||
const handleReconcileTransaction = () => {
|
const handleReconcileTransaction = () => {
|
||||||
openReconcileMatchingTransaction(totalPending);
|
openReconcileMatchingTransaction();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -331,7 +258,7 @@ const MatchTransactionFooter = R.compose(withBankingActions)(
|
|||||||
</AnchorButton>
|
</AnchorButton>
|
||||||
)}
|
)}
|
||||||
<Text
|
<Text
|
||||||
style={{ fontSize: 14, marginLeft: 'auto', color: '#404854' }}
|
style={{ fontSize: 14, marginLeft: 'auto', color: '#5F6B7C' }}
|
||||||
tagName="span"
|
tagName="span"
|
||||||
>
|
>
|
||||||
Pending <FormatNumber value={totalPending} currency={'USD'} />
|
Pending <FormatNumber value={totalPending} currency={'USD'} />
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import React, { createContext } from 'react';
|
|
||||||
import { defaultTo } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
import * as R from 'ramda';
|
import React, { createContext } from 'react';
|
||||||
import { useGetBankTransactionsMatches } from '@/hooks/query/bank-rules';
|
import { useGetBankTransactionsMatches } from '@/hooks/query/bank-rules';
|
||||||
|
|
||||||
interface MatchingTransactionBootValues {
|
interface MatchingTransactionBootValues {
|
||||||
isMatchingTransactionsLoading: boolean;
|
isMatchingTransactionsLoading: boolean;
|
||||||
isMatchingTransactionsFetching: boolean;
|
|
||||||
isMatchingTransactionsSuccess: boolean;
|
|
||||||
possibleMatches: Array<any>;
|
possibleMatches: Array<any>;
|
||||||
perfectMatchesCount: number;
|
perfectMatchesCount: number;
|
||||||
perfectMatches: Array<any>;
|
perfectMatches: Array<any>;
|
||||||
@@ -29,24 +26,13 @@ function MatchingTransactionBoot({
|
|||||||
const {
|
const {
|
||||||
data: matchingTransactions,
|
data: matchingTransactions,
|
||||||
isLoading: isMatchingTransactionsLoading,
|
isLoading: isMatchingTransactionsLoading,
|
||||||
isFetching: isMatchingTransactionsFetching,
|
|
||||||
isSuccess: isMatchingTransactionsSuccess,
|
|
||||||
} = useGetBankTransactionsMatches(uncategorizedTransactionId);
|
} = useGetBankTransactionsMatches(uncategorizedTransactionId);
|
||||||
|
|
||||||
const possibleMatches = defaultTo(matchingTransactions?.possibleMatches, []);
|
|
||||||
const perfectMatchesCount = matchingTransactions?.perfectMatches?.length || 0;
|
|
||||||
const perfectMatches = defaultTo(matchingTransactions?.perfectMatches, []);
|
|
||||||
|
|
||||||
const matches = R.concat(perfectMatches, possibleMatches);
|
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
isMatchingTransactionsLoading,
|
isMatchingTransactionsLoading,
|
||||||
isMatchingTransactionsFetching,
|
possibleMatches: defaultTo(matchingTransactions?.possibleMatches, []),
|
||||||
isMatchingTransactionsSuccess,
|
perfectMatchesCount: matchingTransactions?.perfectMatches?.length || 0,
|
||||||
possibleMatches,
|
perfectMatches: defaultTo(matchingTransactions?.perfectMatches, []),
|
||||||
perfectMatchesCount,
|
|
||||||
perfectMatches,
|
|
||||||
matches,
|
|
||||||
} as MatchingTransactionBootValues;
|
} as MatchingTransactionBootValues;
|
||||||
|
|
||||||
return <RuleFormBootContext.Provider value={provider} {...props} />;
|
return <RuleFormBootContext.Provider value={provider} {...props} />;
|
||||||
|
|||||||
@@ -24,14 +24,12 @@ export const useGetPendingAmountMatched = () => {
|
|||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const matchedItems = [...perfectMatches, ...possibleMatches].filter(
|
const matchedItems = [...perfectMatches, ...possibleMatches].filter(
|
||||||
(match) => {
|
(match) => {
|
||||||
const key = `${match.referenceType}-${match.referenceId}`;
|
const key = `${match.transactionType}-${match.transactionId}`;
|
||||||
return values.matched[key];
|
return values.matched[key];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const totalMatchedAmount = matchedItems.reduce(
|
const totalMatchedAmount = matchedItems.reduce(
|
||||||
(total, item) =>
|
(total, item) => total + parseFloat(item.amount),
|
||||||
total +
|
|
||||||
(item.transactionNormal === 'debit' ? 1 : -1) * parseFloat(item.amount),
|
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const amount = uncategorizedTransaction.amount;
|
const amount = uncategorizedTransaction.amount;
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ export const withBanking = (mapState) => {
|
|||||||
selectedUncategorizedTransactionId:
|
selectedUncategorizedTransactionId:
|
||||||
state.plaid.uncategorizedTransactionIdForMatching,
|
state.plaid.uncategorizedTransactionIdForMatching,
|
||||||
openReconcileMatchingTransaction:
|
openReconcileMatchingTransaction:
|
||||||
state.plaid.openReconcileMatchingTransaction.isOpen,
|
state.plaid.openReconcileMatchingTransaction,
|
||||||
|
|
||||||
reconcileMatchingTransactionPendingAmount:
|
|
||||||
state.plaid.openReconcileMatchingTransaction.pending,
|
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface WithBankingActionsProps {
|
|||||||
setUncategorizedTransactionIdForMatching: (
|
setUncategorizedTransactionIdForMatching: (
|
||||||
uncategorizedTransactionId: number,
|
uncategorizedTransactionId: number,
|
||||||
) => void;
|
) => void;
|
||||||
openReconcileMatchingTransaction: (pendingAmount: number) => void;
|
openReconcileMatchingTransaction: () => void;
|
||||||
closeReconcileMatchingTransaction: () => void;
|
closeReconcileMatchingTransaction: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
|||||||
dispatch(
|
dispatch(
|
||||||
setUncategorizedTransactionIdForMatching(uncategorizedTransactionId),
|
setUncategorizedTransactionIdForMatching(uncategorizedTransactionId),
|
||||||
),
|
),
|
||||||
openReconcileMatchingTransaction: (pendingAmount: number) =>
|
openReconcileMatchingTransaction: () =>
|
||||||
dispatch(openReconcileMatchingTransaction({ pending: pendingAmount })),
|
dispatch(openReconcileMatchingTransaction()),
|
||||||
closeReconcileMatchingTransaction: () =>
|
closeReconcileMatchingTransaction: () =>
|
||||||
dispatch(closeReconcileMatchingTransaction()),
|
dispatch(closeReconcileMatchingTransaction()),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,3 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 40px;
|
padding: 0 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.periodSwitch {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
@@ -1,65 +1,32 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { AppToaster, Group, T } from '@/components';
|
||||||
import * as R from 'ramda';
|
|
||||||
import { AppToaster } from '@/components';
|
|
||||||
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
|
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
|
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
|
||||||
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
|
|
||||||
import {
|
|
||||||
WithPlansProps,
|
|
||||||
withPlans,
|
|
||||||
} from '@/containers/Subscriptions/withPlans';
|
|
||||||
|
|
||||||
interface SubscriptionPricingFeature {
|
|
||||||
text: string;
|
|
||||||
hint?: string;
|
|
||||||
hintLabel?: string;
|
|
||||||
style?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubscriptionPricingProps {
|
interface SubscriptionPricingProps {
|
||||||
slug: string;
|
slug: string;
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
features?: Array<SubscriptionPricingFeature>;
|
features?: Array<String>;
|
||||||
featured?: boolean;
|
featured?: boolean;
|
||||||
monthlyPrice: string;
|
price: string;
|
||||||
monthlyPriceLabel: string;
|
pricePeriod: string;
|
||||||
annuallyPrice: string;
|
|
||||||
annuallyPriceLabel: string;
|
|
||||||
monthlyVariantId?: string;
|
|
||||||
annuallyVariantId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscriptionPricingCombinedProps
|
function SubscriptionPricing({
|
||||||
extends SubscriptionPricingProps,
|
featured,
|
||||||
WithPlansProps {}
|
|
||||||
|
|
||||||
function SubscriptionPlanRoot({
|
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
featured,
|
|
||||||
features,
|
features,
|
||||||
monthlyPrice,
|
price,
|
||||||
monthlyPriceLabel,
|
pricePeriod,
|
||||||
annuallyPrice,
|
}: SubscriptionPricingProps) {
|
||||||
annuallyPriceLabel,
|
|
||||||
monthlyVariantId,
|
|
||||||
annuallyVariantId,
|
|
||||||
|
|
||||||
// #withPlans
|
|
||||||
plansPeriod,
|
|
||||||
}: SubscriptionPricingCombinedProps) {
|
|
||||||
const { mutateAsync: getLemonCheckout, isLoading } =
|
const { mutateAsync: getLemonCheckout, isLoading } =
|
||||||
useGetLemonSqueezyCheckout();
|
useGetLemonSqueezyCheckout();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
const variantId =
|
getLemonCheckout({ variantId: '338516' })
|
||||||
SubscriptionPlansPeriod.Monthly === plansPeriod
|
|
||||||
? monthlyVariantId
|
|
||||||
: annuallyVariantId;
|
|
||||||
|
|
||||||
getLemonCheckout({ variantId })
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const checkoutUrl = res.data.data.attributes.url;
|
const checkoutUrl = res.data.data.attributes.url;
|
||||||
window.LemonSqueezy.Url.Open(checkoutUrl);
|
window.LemonSqueezy.Url.Open(checkoutUrl);
|
||||||
@@ -75,34 +42,37 @@ function SubscriptionPlanRoot({
|
|||||||
return (
|
return (
|
||||||
<PricingPlan featured={featured}>
|
<PricingPlan featured={featured}>
|
||||||
{featured && <PricingPlan.Featured>Most Popular</PricingPlan.Featured>}
|
{featured && <PricingPlan.Featured>Most Popular</PricingPlan.Featured>}
|
||||||
<PricingPlan.Header label={label} description={description} />
|
|
||||||
|
|
||||||
{plansPeriod === SubscriptionPlansPeriod.Monthly ? (
|
<PricingPlan.Header label={label} description={description} />
|
||||||
<PricingPlan.Price price={monthlyPrice} subPrice={monthlyPriceLabel} />
|
<PricingPlan.Price price={price} subPrice={pricePeriod} />
|
||||||
) : (
|
|
||||||
<PricingPlan.Price
|
|
||||||
price={annuallyPrice}
|
|
||||||
subPrice={annuallyPriceLabel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<PricingPlan.BuyButton loading={isLoading} onClick={handleClick}>
|
<PricingPlan.BuyButton loading={isLoading} onClick={handleClick}>
|
||||||
Subscribe
|
Subscribe
|
||||||
</PricingPlan.BuyButton>
|
</PricingPlan.BuyButton>
|
||||||
|
|
||||||
<PricingPlan.Features>
|
<PricingPlan.Features>
|
||||||
{features?.map((feature) => (
|
{features?.map((feature) => (
|
||||||
<PricingPlan.FeatureLine
|
<PricingPlan.FeatureLine>{feature}</PricingPlan.FeatureLine>
|
||||||
hintLabel={feature.hintLabel}
|
|
||||||
hintContent={feature.hint}
|
|
||||||
>
|
|
||||||
{feature.text}
|
|
||||||
</PricingPlan.FeatureLine>
|
|
||||||
))}
|
))}
|
||||||
</PricingPlan.Features>
|
</PricingPlan.Features>
|
||||||
</PricingPlan>
|
</PricingPlan>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubscriptionPlan = R.compose(
|
export function SubscriptionPlans({ plans }) {
|
||||||
withPlans(({ plansPeriod }) => ({ plansPeriod })),
|
return (
|
||||||
)(SubscriptionPlanRoot);
|
<Group spacing={18} noWrap align='stretch'>
|
||||||
|
{plans.map((plan, index) => (
|
||||||
|
<SubscriptionPricing
|
||||||
|
key={index}
|
||||||
|
slug={plan.slug}
|
||||||
|
label={plan.name}
|
||||||
|
description={plan.description}
|
||||||
|
features={plan.features}
|
||||||
|
featured={plan.featured}
|
||||||
|
price={plan.price}
|
||||||
|
pricePeriod={plan.pricePeriod}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Group } from '@/components';
|
|
||||||
import { SubscriptionPlan } from './SubscriptionPlan';
|
|
||||||
import { useSubscriptionPlans } from './hooks';
|
|
||||||
|
|
||||||
export function SubscriptionPlans() {
|
|
||||||
const subscriptionPlans = useSubscriptionPlans();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group spacing={14} noWrap align="stretch">
|
|
||||||
{subscriptionPlans.map((plan, index) => (
|
|
||||||
<SubscriptionPlan
|
|
||||||
key={index}
|
|
||||||
slug={plan.slug}
|
|
||||||
label={plan.name}
|
|
||||||
description={plan.description}
|
|
||||||
features={plan.features}
|
|
||||||
featured={plan.featured}
|
|
||||||
monthlyPrice={plan.monthlyPrice}
|
|
||||||
monthlyPriceLabel={plan.monthlyPriceLabel}
|
|
||||||
annuallyPrice={plan.annuallyPrice}
|
|
||||||
annuallyPriceLabel={plan.annuallyPriceLabel}
|
|
||||||
monthlyVariantId={plan.monthlyVariantId}
|
|
||||||
annuallyVariantId={plan.annuallyVariantId}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user