mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Compare commits
13 Commits
v0.17.0
...
fix-paymen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031ccc4a0b | ||
|
|
858f347fd4 | ||
|
|
4d73b59cf3 | ||
|
|
bc67f0cca8 | ||
|
|
ef2d1ff141 | ||
|
|
dc4cdb2a8f | ||
|
|
8862810706 | ||
|
|
3dadbeac4d | ||
|
|
494d2c1fe0 | ||
|
|
d27562bd43 | ||
|
|
fc9995c4da | ||
|
|
7dc769004d | ||
|
|
909a70e2c5 |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -2,6 +2,41 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
|
## [0.17.0] - 04-06-2024
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* feat: Upload and attach documents by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/461
|
||||||
|
* feat: Export resource tables to pdf by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/460
|
||||||
|
* feat: Build and deploy develop Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/476
|
||||||
|
* feat: Internal docker virtual network by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/478
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* fix: Skip send confirmation email if disabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/459
|
||||||
|
* fix: Lemon Squeezy redirect to base url by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/479
|
||||||
|
* fix: Organize Plaid env variables for development and sandbox envs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/480
|
||||||
|
* fix: Plaid syncs deposit imports as withdrawals by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/481
|
||||||
|
* fix: Validate the s3 configures exist by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/482
|
||||||
|
* fix: Run migrations only for initialized tenants by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/484
|
||||||
|
|
||||||
|
## [0.16.16] -
|
||||||
|
|
||||||
|
* feat: handle http exceptions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/456
|
||||||
|
* feat: add the missing Newrelic env vars to docker-compose.prod file by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/457
|
||||||
|
* fix: add the signup email confirmation env var by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/458
|
||||||
|
|
||||||
|
## [0.16.14] -
|
||||||
|
|
||||||
|
* fix: Typo in setup wizard by @ccantrell72 in https://github.com/bigcapitalhq/bigcapital/pull/440
|
||||||
|
* fix: Showing the real mail address on email confirmation view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/445
|
||||||
|
* fix: Auto-increment setting parsing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/453
|
||||||
|
|
||||||
|
## [0.16.12] -
|
||||||
|
|
||||||
|
* feat: Create a manifest list for `webapp` Docker image and push it to DockerHub. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/436
|
||||||
|
* feat: Combine arm64 and amd64 in one Github action runner by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/437
|
||||||
|
|
||||||
## [0.16.11] - 06-05-2024
|
## [0.16.11] - 06-05-2024
|
||||||
|
|
||||||
### improvements
|
### improvements
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { NextFunction, Router, Request, Response } from 'express';
|
||||||
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
|
||||||
import { Request, Response } from 'express';
|
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
import { LemonSqueezyWebhooks } from '@/services/Subscription/LemonSqueezyWebhooks';
|
||||||
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
|
||||||
@@ -34,7 +33,7 @@ export class Webhooks extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response, next: any) {
|
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] ?? '';
|
const signature = req.headers['x-signature'] ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
@@ -57,20 +56,25 @@ export class Webhooks extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
public async plaidWebhooks(req: Request, res: Response) {
|
public async plaidWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const {
|
|
||||||
webhook_type: webhookType,
|
|
||||||
webhook_code: webhookCode,
|
|
||||||
item_id: plaidItemId,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
await this.plaidApp.webhooks(
|
try {
|
||||||
tenantId,
|
const {
|
||||||
plaidItemId,
|
webhook_type: webhookType,
|
||||||
webhookType,
|
webhook_code: webhookCode,
|
||||||
webhookCode
|
item_id: plaidItemId,
|
||||||
);
|
} = req.body;
|
||||||
return res.status(200).send({ code: 200, message: 'ok' });
|
|
||||||
|
await this.plaidApp.webhooks(
|
||||||
|
tenantId,
|
||||||
|
plaidItemId,
|
||||||
|
webhookType,
|
||||||
|
webhookCode
|
||||||
|
);
|
||||||
|
return res.status(200).send({ code: 200, message: 'ok' });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('storage', (table) => {
|
||||||
|
table.increments('id').primary();
|
||||||
|
table.string('key').notNullable();
|
||||||
|
table.string('path').notNullable();
|
||||||
|
table.string('extension').notNullable();
|
||||||
|
table.integer('expire_in');
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('storage');
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('storage');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {};
|
||||||
@@ -164,3 +164,7 @@ export enum TaxRateAction {
|
|||||||
DELETE = 'Delete',
|
DELETE = 'Delete',
|
||||||
VIEW = 'View',
|
VIEW = 'View',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateAccountParams {
|
||||||
|
ignoreUniqueName: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
*/
|
*/
|
||||||
private async updateUncategorizedTransactionCount(
|
private async updateUncategorizedTransactionCount(
|
||||||
queryContext: QueryContext,
|
queryContext: QueryContext,
|
||||||
increment: boolean
|
increment: boolean,
|
||||||
|
amount: number = 1
|
||||||
) {
|
) {
|
||||||
const operation = increment ? 'increment' : 'decrement';
|
const operation = increment ? 'increment' : 'decrement';
|
||||||
const amount = increment ? 1 : -1;
|
|
||||||
|
|
||||||
await Account.query(queryContext.transaction)
|
await Account.query(queryContext.transaction)
|
||||||
.findById(this.accountId)
|
.findById(this.accountId)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
IAccountEventCreatedPayload,
|
IAccountEventCreatedPayload,
|
||||||
IAccountEventCreatingPayload,
|
IAccountEventCreatingPayload,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
|
CreateAccountParams,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
@@ -30,19 +31,22 @@ export class CreateAccount {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize the account creation.
|
* Authorize the account creation.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
*/
|
*/
|
||||||
private authorize = async (
|
private authorize = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO,
|
||||||
baseCurrency: string
|
baseCurrency: string,
|
||||||
|
params?: CreateAccountParams
|
||||||
) => {
|
) => {
|
||||||
// Validate account name uniquiness.
|
// Validate account name uniquiness.
|
||||||
await this.validator.validateAccountNameUniquiness(
|
if (!params.ignoreUniqueName) {
|
||||||
tenantId,
|
await this.validator.validateAccountNameUniquiness(
|
||||||
accountDTO.name
|
tenantId,
|
||||||
);
|
accountDTO.name
|
||||||
|
);
|
||||||
|
}
|
||||||
// Validate the account code uniquiness.
|
// Validate the account code uniquiness.
|
||||||
if (accountDTO.code) {
|
if (accountDTO.code) {
|
||||||
await this.validator.isAccountCodeUniqueOrThrowError(
|
await this.validator.isAccountCodeUniqueOrThrowError(
|
||||||
@@ -82,7 +86,7 @@ export class CreateAccount {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the create account DTO to input model.
|
* Transformes the create account DTO to input model.
|
||||||
* @param {IAccountCreateDTO} createAccountDTO
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
*/
|
*/
|
||||||
private transformDTOToModel = (
|
private transformDTOToModel = (
|
||||||
createAccountDTO: IAccountCreateDTO,
|
createAccountDTO: IAccountCreateDTO,
|
||||||
@@ -104,7 +108,8 @@ export class CreateAccount {
|
|||||||
public createAccount = async (
|
public createAccount = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction,
|
||||||
|
params: CreateAccountParams = { ignoreUniqueName: false }
|
||||||
): Promise<IAccount> => {
|
): Promise<IAccount> => {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -112,8 +117,12 @@ export class CreateAccount {
|
|||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Authorize the account creation.
|
// Authorize the account creation.
|
||||||
await this.authorize(tenantId, accountDTO, tenantMeta.baseCurrency);
|
await this.authorize(
|
||||||
|
tenantId,
|
||||||
|
accountDTO,
|
||||||
|
tenantMeta.baseCurrency,
|
||||||
|
params
|
||||||
|
);
|
||||||
// Transformes the DTO to model.
|
// Transformes the DTO to model.
|
||||||
const accountInputModel = this.transformDTOToModel(
|
const accountInputModel = this.transformDTOToModel(
|
||||||
accountDTO,
|
accountDTO,
|
||||||
@@ -148,3 +157,4 @@ export class CreateAccount {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { Inject, Service } from 'typedi';
|
|||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import { entries, groupBy } from 'lodash';
|
import { entries, groupBy } from 'lodash';
|
||||||
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
import { CreateAccount } from '@/services/Accounts/CreateAccount';
|
||||||
import { PlaidAccount, PlaidTransaction } from '@/interfaces';
|
import {
|
||||||
|
IAccountCreateDTO,
|
||||||
|
PlaidAccount,
|
||||||
|
PlaidTransaction,
|
||||||
|
} from '@/interfaces';
|
||||||
import {
|
import {
|
||||||
transformPlaidAccountToCreateAccount,
|
transformPlaidAccountToCreateAccount,
|
||||||
transformPlaidTrxsToCashflowCreate,
|
transformPlaidTrxsToCashflowCreate,
|
||||||
@@ -11,6 +15,7 @@ import {
|
|||||||
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
||||||
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';
|
||||||
|
|
||||||
const CONCURRENCY_ASYNC = 10;
|
const CONCURRENCY_ASYNC = 10;
|
||||||
|
|
||||||
@@ -28,6 +33,35 @@ export class PlaidSyncDb {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs the Plaid bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createBankAccountDTO
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async syncBankAccount(
|
||||||
|
tenantId: number,
|
||||||
|
createBankAccountDTO: IAccountCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
const plaidAccount = await Account.query().findOne(
|
||||||
|
'plaidAccountId',
|
||||||
|
createBankAccountDTO.plaidAccountId
|
||||||
|
);
|
||||||
|
// Can't continue if the Plaid account is already created.
|
||||||
|
if (plaidAccount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.createAccountService.createAccount(
|
||||||
|
tenantId,
|
||||||
|
createBankAccountDTO,
|
||||||
|
trx,
|
||||||
|
{ ignoreUniqueName: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs the plaid accounts to the system accounts.
|
* Syncs the plaid accounts to the system accounts.
|
||||||
* @param {number} tenantId Tenant ID.
|
* @param {number} tenantId Tenant ID.
|
||||||
@@ -37,7 +71,8 @@ export class PlaidSyncDb {
|
|||||||
public async syncBankAccounts(
|
public async syncBankAccounts(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccounts: PlaidAccount[],
|
plaidAccounts: PlaidAccount[],
|
||||||
institution: any
|
institution: any,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const transformToPlaidAccounts =
|
const transformToPlaidAccounts =
|
||||||
transformPlaidAccountToCreateAccount(institution);
|
transformPlaidAccountToCreateAccount(institution);
|
||||||
@@ -47,7 +82,7 @@ export class PlaidSyncDb {
|
|||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
accountCreateDTOs,
|
accountCreateDTOs,
|
||||||
(createAccountDTO: any) =>
|
(createAccountDTO: any) =>
|
||||||
this.createAccountService.createAccount(tenantId, createAccountDTO),
|
this.syncBankAccount(tenantId, createAccountDTO, trx),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,15 +96,16 @@ export class PlaidSyncDb {
|
|||||||
public async syncAccountTranactions(
|
public async syncAccountTranactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountId: number,
|
plaidAccountId: number,
|
||||||
plaidTranasctions: PlaidTransaction[]
|
plaidTranasctions: PlaidTransaction[],
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const cashflowAccount = await Account.query()
|
const cashflowAccount = await Account.query(trx)
|
||||||
.findOne({ plaidAccountId })
|
.findOne({ plaidAccountId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const openingEquityBalance = await Account.query().findOne(
|
const openingEquityBalance = await Account.query(trx).findOne(
|
||||||
'slug',
|
'slug',
|
||||||
'opening-balance-equity'
|
'opening-balance-equity'
|
||||||
);
|
);
|
||||||
@@ -87,7 +123,8 @@ export class PlaidSyncDb {
|
|||||||
(uncategoriedDTO) =>
|
(uncategoriedDTO) =>
|
||||||
this.cashflowApp.createUncategorizedTransaction(
|
this.cashflowApp.createUncategorizedTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategoriedDTO
|
uncategoriedDTO,
|
||||||
|
trx
|
||||||
),
|
),
|
||||||
{ concurrency: 1 }
|
{ concurrency: 1 }
|
||||||
);
|
);
|
||||||
@@ -100,7 +137,8 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncAccountsTransactions(
|
public async syncAccountsTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountsTransactions: PlaidTransaction[]
|
plaidAccountsTransactions: PlaidTransaction[],
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const groupedTrnsxByAccountId = entries(
|
const groupedTrnsxByAccountId = entries(
|
||||||
groupBy(plaidAccountsTransactions, 'account_id')
|
groupBy(plaidAccountsTransactions, 'account_id')
|
||||||
@@ -111,7 +149,8 @@ export class PlaidSyncDb {
|
|||||||
return this.syncAccountTranactions(
|
return this.syncAccountTranactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
plaidAccountId,
|
plaidAccountId,
|
||||||
plaidTransactions
|
plaidTransactions,
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
@@ -124,11 +163,12 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncRemoveTransactions(
|
public async syncRemoveTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidTransactionsIds: string[]
|
plaidTransactionsIds: string[],
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const cashflowTransactions = await CashflowTransaction.query().whereIn(
|
const cashflowTransactions = await CashflowTransaction.query(trx).whereIn(
|
||||||
'plaidTransactionId',
|
'plaidTransactionId',
|
||||||
plaidTransactionsIds
|
plaidTransactionsIds
|
||||||
);
|
);
|
||||||
@@ -140,7 +180,8 @@ export class PlaidSyncDb {
|
|||||||
(transactionId: number) =>
|
(transactionId: number) =>
|
||||||
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionId
|
transactionId,
|
||||||
|
trx
|
||||||
),
|
),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
@@ -155,11 +196,12 @@ export class PlaidSyncDb {
|
|||||||
public async syncTransactionsCursor(
|
public async syncTransactionsCursor(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidItemId: string,
|
plaidItemId: string,
|
||||||
lastCursor: string
|
lastCursor: string,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { PlaidItem } = this.tenancy.models(tenantId);
|
const { PlaidItem } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await PlaidItem.query().findOne({ plaidItemId }).patch({ lastCursor });
|
await PlaidItem.query(trx).findOne({ plaidItemId }).patch({ lastCursor });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,13 +211,16 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async updateLastFeedsUpdatedAt(
|
public async updateLastFeedsUpdatedAt(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountIds: string[]
|
plaidAccountIds: string[],
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
await Account.query(trx)
|
||||||
lastFeedsUpdatedAt: new Date(),
|
.whereIn('plaid_account_id', plaidAccountIds)
|
||||||
});
|
.patch({
|
||||||
|
lastFeedsUpdatedAt: new Date(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,12 +232,15 @@ export class PlaidSyncDb {
|
|||||||
public async updateAccountsFeedsActive(
|
public async updateAccountsFeedsActive(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccountIds: string[],
|
plaidAccountIds: string[],
|
||||||
isFeedsActive: boolean = true
|
isFeedsActive: boolean = true,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
await Account.query(trx)
|
||||||
isFeedsActive,
|
.whereIn('plaid_account_id', plaidAccountIds)
|
||||||
});
|
.patch({
|
||||||
|
isFeedsActive,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
||||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||||
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PlaidUpdateTransactions {
|
export class PlaidUpdateTransactions {
|
||||||
@@ -12,12 +14,40 @@ export class PlaidUpdateTransactions {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private plaidSync: PlaidSyncDb;
|
private plaidSync: PlaidSyncDb;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the fetching and storing of new, modified, or removed transactions
|
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||||
* @param {number} tenantId Tenant ID.
|
* @param {number} tenantId
|
||||||
* @param {string} plaidItemId the Plaid ID for the item.
|
* @param {number} plaidItemId
|
||||||
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
||||||
|
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
||||||
|
return this.updateTransactionsWork(tenantId, plaidItemId, trx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the fetching and storing the following:
|
||||||
|
* - New, modified, or removed transactions.
|
||||||
|
* - New bank accounts.
|
||||||
|
* - Last accounts feeds updated at.
|
||||||
|
* - Turn on the accounts feed flag.
|
||||||
|
* @param {number} tenantId - Tenant ID.
|
||||||
|
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||||
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
|
*/
|
||||||
|
public async updateTransactionsWork(
|
||||||
|
tenantId: number,
|
||||||
|
plaidItemId: string,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<{
|
||||||
|
addedCount: number;
|
||||||
|
modifiedCount: number;
|
||||||
|
removedCount: number;
|
||||||
|
}> {
|
||||||
// Fetch new transactions from plaid api.
|
// Fetch new transactions from plaid api.
|
||||||
const { added, modified, removed, cursor, accessToken } =
|
const { added, modified, removed, cursor, accessToken } =
|
||||||
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
await this.fetchTransactionUpdates(tenantId, plaidItemId);
|
||||||
@@ -29,28 +59,42 @@ export class PlaidUpdateTransactions {
|
|||||||
} = await plaidInstance.accountsGet(request);
|
} = await plaidInstance.accountsGet(request);
|
||||||
|
|
||||||
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { institution },
|
data: { institution },
|
||||||
} = await plaidInstance.institutionsGetById({
|
} = await plaidInstance.institutionsGetById({
|
||||||
institution_id: item.institution_id,
|
institution_id: item.institution_id,
|
||||||
country_codes: ['US', 'UK'],
|
country_codes: ['US', 'UK'],
|
||||||
});
|
});
|
||||||
// Update the DB.
|
// Sync bank accounts.
|
||||||
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution);
|
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution, trx);
|
||||||
|
// Sync bank account transactions.
|
||||||
await this.plaidSync.syncAccountsTransactions(
|
await this.plaidSync.syncAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
added.concat(modified)
|
added.concat(modified),
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
// Sync removed transactions.
|
||||||
|
await this.plaidSync.syncRemoveTransactions(tenantId, removed, trx);
|
||||||
|
// Sync transactions cursor.
|
||||||
|
await this.plaidSync.syncTransactionsCursor(
|
||||||
|
tenantId,
|
||||||
|
plaidItemId,
|
||||||
|
cursor,
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
await this.plaidSync.syncRemoveTransactions(tenantId, removed);
|
|
||||||
await this.plaidSync.syncTransactionsCursor(tenantId, plaidItemId, cursor);
|
|
||||||
|
|
||||||
// Update the last feeds updated at of the updated accounts.
|
// Update the last feeds updated at of the updated accounts.
|
||||||
await this.plaidSync.updateLastFeedsUpdatedAt(tenantId, plaidAccountsIds);
|
await this.plaidSync.updateLastFeedsUpdatedAt(
|
||||||
|
tenantId,
|
||||||
|
plaidAccountsIds,
|
||||||
|
trx
|
||||||
|
);
|
||||||
// Turn on the accounts feeds flag.
|
// Turn on the accounts feeds flag.
|
||||||
await this.plaidSync.updateAccountsFeedsActive(tenantId, plaidAccountsIds);
|
await this.plaidSync.updateAccountsFeedsActive(
|
||||||
|
tenantId,
|
||||||
|
plaidAccountsIds,
|
||||||
|
true,
|
||||||
|
trx
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
addedCount: added.length,
|
addedCount: added.length,
|
||||||
modifiedCount: modified.length,
|
modifiedCount: modified.length,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { DeleteCashflowTransaction } from './DeleteCashflowTransactionService';
|
import { DeleteCashflowTransaction } from './DeleteCashflowTransactionService';
|
||||||
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
||||||
@@ -119,11 +120,13 @@ export class CashflowApplication {
|
|||||||
*/
|
*/
|
||||||
public createUncategorizedTransaction(
|
public createUncategorizedTransaction(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO
|
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
return this.createUncategorizedTransactionService.create(
|
return this.createUncategorizedTransactionService.create(
|
||||||
tenantId,
|
tenantId,
|
||||||
createUncategorizedTransactionDTO
|
createUncategorizedTransactionDTO,
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Knex } from 'knex';
|
|||||||
import { transformCategorizeTransToCashflow } from './utils';
|
import { transformCategorizeTransToCashflow } from './utils';
|
||||||
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
||||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
||||||
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CategorizeCashflowTransaction {
|
export class CategorizeCashflowTransaction {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export class DeleteCashflowTransaction {
|
|||||||
*/
|
*/
|
||||||
public deleteCashflowTransaction = async (
|
public deleteCashflowTransaction = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
cashflowTransactionId: number
|
cashflowTransactionId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<{ oldCashflowTransaction: ICashflowTransaction }> => {
|
): Promise<{ oldCashflowTransaction: ICashflowTransaction }> => {
|
||||||
const { CashflowTransaction, CashflowTransactionLine } =
|
const { CashflowTransaction, CashflowTransactionLine } =
|
||||||
this.tenancy.models(tenantId);
|
this.tenancy.models(tenantId);
|
||||||
@@ -43,34 +44,44 @@ export class DeleteCashflowTransaction {
|
|||||||
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
|
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
|
||||||
|
|
||||||
// Starting database transaction.
|
// Starting database transaction.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onCashflowTransactionDelete` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleting, {
|
async (trx: Knex.Transaction) => {
|
||||||
trx,
|
// Triggers `onCashflowTransactionDelete` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(
|
||||||
oldCashflowTransaction,
|
events.cashflow.onTransactionDeleting,
|
||||||
} as ICommandCashflowDeletingPayload);
|
{
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldCashflowTransaction,
|
||||||
|
} as ICommandCashflowDeletingPayload
|
||||||
|
);
|
||||||
|
|
||||||
// Delete cashflow transaction associated lines first.
|
// Delete cashflow transaction associated lines first.
|
||||||
await CashflowTransactionLine.query(trx)
|
await CashflowTransactionLine.query(trx)
|
||||||
.where('cashflow_transaction_id', cashflowTransactionId)
|
.where('cashflow_transaction_id', cashflowTransactionId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
// Delete cashflow transaction.
|
// Delete cashflow transaction.
|
||||||
await CashflowTransaction.query(trx)
|
await CashflowTransaction.query(trx)
|
||||||
.findById(cashflowTransactionId)
|
.findById(cashflowTransactionId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
// Triggers `onCashflowTransactionDeleted` event.
|
// Triggers `onCashflowTransactionDeleted` event.
|
||||||
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleted, {
|
await this.eventPublisher.emitAsync(
|
||||||
trx,
|
events.cashflow.onTransactionDeleted,
|
||||||
tenantId,
|
{
|
||||||
cashflowTransactionId,
|
trx,
|
||||||
oldCashflowTransaction,
|
tenantId,
|
||||||
} as ICommandCashflowDeletedPayload);
|
cashflowTransactionId,
|
||||||
|
oldCashflowTransaction,
|
||||||
|
} as ICommandCashflowDeletedPayload
|
||||||
|
);
|
||||||
|
|
||||||
return { oldCashflowTransaction };
|
return { oldCashflowTransaction };
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -68,7 +68,11 @@ export const CASHFLOW_TRANSACTION_TYPE_META = {
|
|||||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
||||||
type: 'OtherExpense',
|
type: 'OtherExpense',
|
||||||
direction: CASHFLOW_DIRECTION.OUT,
|
direction: CASHFLOW_DIRECTION.OUT,
|
||||||
creditType: [ACCOUNT_TYPE.EXPENSE, ACCOUNT_TYPE.OTHER_EXPENSE],
|
creditType: [
|
||||||
|
ACCOUNT_TYPE.EXPENSE,
|
||||||
|
ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { upperFirst, camelCase, omit } from 'lodash';
|
import { upperFirst, camelCase } from 'lodash';
|
||||||
import {
|
import {
|
||||||
CASHFLOW_TRANSACTION_TYPE,
|
CASHFLOW_TRANSACTION_TYPE,
|
||||||
CASHFLOW_TRANSACTION_TYPE_META,
|
CASHFLOW_TRANSACTION_TYPE_META,
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
import {
|
||||||
ICashflowNewCommandDTO,
|
ICashflowNewCommandDTO,
|
||||||
ICashflowTransaction,
|
|
||||||
ICategorizeCashflowTransactioDTO,
|
ICategorizeCashflowTransactioDTO,
|
||||||
IUncategorizedCashflowTransaction,
|
IUncategorizedCashflowTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
@@ -42,8 +41,8 @@ export const getCashflowAccountTransactionsTypes = () => {
|
|||||||
/**
|
/**
|
||||||
* Tranasformes the given uncategorized transaction and categorized DTO
|
* Tranasformes the given uncategorized transaction and categorized DTO
|
||||||
* to cashflow create DTO.
|
* to cashflow create DTO.
|
||||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
||||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
||||||
* @returns {ICashflowNewCommandDTO}
|
* @returns {ICashflowNewCommandDTO}
|
||||||
*/
|
*/
|
||||||
export const transformCategorizeTransToCashflow = (
|
export const transformCategorizeTransToCashflow = (
|
||||||
@@ -62,6 +61,7 @@ export const transformCategorizeTransToCashflow = (
|
|||||||
transactionNumber: categorizeDTO.transactionNumber,
|
transactionNumber: categorizeDTO.transactionNumber,
|
||||||
transactionType: categorizeDTO.transactionType,
|
transactionType: categorizeDTO.transactionType,
|
||||||
uncategorizedTransactionId: uncategorizeModel.id,
|
uncategorizedTransactionId: uncategorizeModel.id,
|
||||||
|
branchId: categorizeDTO?.branchId,
|
||||||
publish: true,
|
publish: true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,17 +108,28 @@ export default class ResourceService {
|
|||||||
const $hasFields = (field) =>
|
const $hasFields = (field) =>
|
||||||
'undefined' !== typeof field.fields ? field : undefined;
|
'undefined' !== typeof field.fields ? field : undefined;
|
||||||
|
|
||||||
const $hasColumns = (column) =>
|
const $ColumnHasColumns = (column) =>
|
||||||
'undefined' !== typeof column.columns ? column : undefined;
|
'undefined' !== typeof column.columns ? column : undefined;
|
||||||
|
|
||||||
|
const $hasColumns = (columns) =>
|
||||||
|
'undefined' !== typeof columns ? columns : undefined;
|
||||||
|
|
||||||
const naviagations = [
|
const naviagations = [
|
||||||
['fields', qim.$each, 'name'],
|
['fields', qim.$each, 'name'],
|
||||||
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, 'name'],
|
['fields2', qim.$each, 'name'],
|
||||||
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||||
['columns', qim.$each, 'name'],
|
['columns', $hasColumns, qim.$each, 'name'],
|
||||||
['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'],
|
[
|
||||||
|
'columns',
|
||||||
|
$hasColumns,
|
||||||
|
qim.$each,
|
||||||
|
$ColumnHasColumns,
|
||||||
|
'columns',
|
||||||
|
qim.$each,
|
||||||
|
'name',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export class EditPaymentReceive {
|
|||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
oldPaymentReceive,
|
oldPaymentReceive,
|
||||||
|
paymentReceiveDTO,
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
trx,
|
trx,
|
||||||
} as IPaymentReceiveEditedPayload);
|
} as IPaymentReceiveEditedPayload);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { FSuggest } from '../Forms';
|
||||||
|
|
||||||
|
interface BranchSuggestFieldProps {
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BranchSuggestField({ ...props }: BranchSuggestFieldProps) {
|
||||||
|
return (
|
||||||
|
<FSuggest
|
||||||
|
valueAccessor={'id'}
|
||||||
|
labelAccessor={'code'}
|
||||||
|
textAccessor={'name'}
|
||||||
|
inputProps={{ placeholder: 'Select a branch' }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { first } from 'lodash';
|
||||||
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +35,12 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
isLoading: isUncategorizedTransactionLoading,
|
isLoading: isUncategorizedTransactionLoading,
|
||||||
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
||||||
|
|
||||||
|
// Retrieves the primary branch.
|
||||||
|
const primaryBranch = useMemo(
|
||||||
|
() => branches?.find((b) => b.primary) || first(branches),
|
||||||
|
[branches],
|
||||||
|
);
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
uncategorizedTransaction,
|
uncategorizedTransaction,
|
||||||
@@ -42,6 +49,7 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
accounts,
|
accounts,
|
||||||
isBranchesLoading,
|
isBranchesLoading,
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
|
primaryBranch,
|
||||||
};
|
};
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { FFormGroup, FeatureCan } from '@/components';
|
||||||
|
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
|
||||||
|
import { Features } from '@/constants';
|
||||||
|
import { BranchSuggestField } from '@/components/Branches/BranchSuggestField_';
|
||||||
|
|
||||||
|
export function CategorizeTransactionBranchField() {
|
||||||
|
const { branches } = useCategorizeTransactionBoot();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FFormGroup name={'branchId'} label={'Branch'} fastField inline>
|
||||||
|
<FeatureCan feature={Features.Branches}>
|
||||||
|
<BranchSuggestField
|
||||||
|
name={'branchId'}
|
||||||
|
items={branches}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FeatureCan>
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,8 +24,11 @@ function CategorizeTransactionFormRoot({
|
|||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
}) {
|
}) {
|
||||||
const { uncategorizedTransactionId, uncategorizedTransaction } =
|
const {
|
||||||
useCategorizeTransactionBoot();
|
uncategorizedTransactionId,
|
||||||
|
uncategorizedTransaction,
|
||||||
|
primaryBranch,
|
||||||
|
} = useCategorizeTransactionBoot();
|
||||||
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
||||||
|
|
||||||
// Callbacks handles form submit.
|
// Callbacks handles form submit.
|
||||||
@@ -37,18 +40,28 @@ function CategorizeTransactionFormRoot({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
|
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'The uncategorized transaction has been categorized.',
|
message: 'The uncategorized transaction has been categorized.',
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
AppToaster.show({
|
if (
|
||||||
message: 'Something went wrong!',
|
err.response.data?.errors?.some(
|
||||||
intent: Intent.DANGER,
|
(e) => e.type === 'BRANCH_ID_REQUIRED',
|
||||||
});
|
)
|
||||||
|
) {
|
||||||
|
setErrors({
|
||||||
|
branchId: 'The branch is required.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong!',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Form initial values in create and edit mode.
|
// Form initial values in create and edit mode.
|
||||||
@@ -60,6 +73,9 @@ function CategorizeTransactionFormRoot({
|
|||||||
* as well.
|
* as well.
|
||||||
*/
|
*/
|
||||||
...transformToCategorizeForm(uncategorizedTransaction),
|
...transformToCategorizeForm(uncategorizedTransaction),
|
||||||
|
|
||||||
|
/** Assign the primary branch id as default value. */
|
||||||
|
branchId: primaryBranch?.id || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherIncome() {
|
export default function CategorizeTransactionOtherIncome() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherIncome() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerContribution() {
|
export default function CategorizeTransactionOwnerContribution() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -63,6 +64,8 @@ export default function CategorizeTransactionOwnerContribution() {
|
|||||||
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
||||||
<FTextArea name={'description'} growVertically large fill />
|
<FTextArea name={'description'} growVertically large fill />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionTransferFrom() {
|
export default function CategorizeTransactionTransferFrom() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -47,7 +48,7 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
inline
|
inline
|
||||||
>
|
>
|
||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'to_account_id'}
|
name={'creditAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['asset']}
|
filterByRootTypes={['asset']}
|
||||||
fastField
|
fastField
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherExpense() {
|
export default function CategorizeTransactionOtherExpense() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherExpense() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerDrawings() {
|
export default function CategorizeTransactionOwnerDrawings() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOwnerDrawings() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionToAccount() {
|
export default function CategorizeTransactionToAccount() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -49,7 +50,7 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'creditAccountId'}
|
name={'creditAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['assset']}
|
filterByRootTypes={['asset']}
|
||||||
fastField
|
fastField
|
||||||
fill
|
fill
|
||||||
allowCreate
|
allowCreate
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const defaultInitialValues = {
|
|||||||
transactionType: '',
|
transactionType: '',
|
||||||
referenceNo: '',
|
referenceNo: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
branchId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function SubscriptionPricing({
|
|||||||
useGetLemonSqueezyCheckout();
|
useGetLemonSqueezyCheckout();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
getLemonCheckout({ variantId: '337977' })
|
getLemonCheckout({ variantId: '338516' })
|
||||||
.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);
|
||||||
|
|||||||
Reference in New Issue
Block a user