diff --git a/packages/server/src/api/controllers/Banking/BankingController.ts b/packages/server/src/api/controllers/Banking/BankingController.ts index 27838a285..109371311 100644 --- a/packages/server/src/api/controllers/Banking/BankingController.ts +++ b/packages/server/src/api/controllers/Banking/BankingController.ts @@ -2,6 +2,7 @@ import Container, { Inject, Service } from 'typedi'; import { Router } from 'express'; import BaseController from '@/api/controllers/BaseController'; import { PlaidBankingController } from './PlaidBankingController'; +import { BankingRulesController } from './BankingRulesController'; @Service() export class BankingController extends BaseController { @@ -12,6 +13,7 @@ export class BankingController extends BaseController { const router = Router(); router.use('/plaid', Container.get(PlaidBankingController).router()); + router.use('/rules', Container.get(BankingRulesController).router()); return router; } diff --git a/packages/server/src/api/controllers/Banking/BankingRulesController.ts b/packages/server/src/api/controllers/Banking/BankingRulesController.ts new file mode 100644 index 000000000..e4b5ea26c --- /dev/null +++ b/packages/server/src/api/controllers/Banking/BankingRulesController.ts @@ -0,0 +1,202 @@ +import { Inject, Service } from 'typedi'; +import { NextFunction, Request, Response, Router } from 'express'; +import BaseController from '@/api/controllers/BaseController'; +import { BankRulesApplication } from '@/services/Banking/Rules/BankRulesApplication'; +import { body, param } from 'express-validator'; +import { + ICreateBankRuleDTO, + IEditBankRuleDTO, +} from '@/services/Banking/Rules/types'; + +@Service() +export class BankingRulesController extends BaseController { + @Inject() + private bankRulesApplication: BankRulesApplication; + + /** + * Bank rule DTO validation schema. + */ + private get bankRuleValidationSchema() { + return [ + body('name').isString().exists(), + body('order').isInt({ min: 0 }), + + // Apply to if transaction is. + body('apply_if_account_id') + .isInt({ min: 0 }) + .optional({ nullable: true }), + body('apply_if_transaction_type').isIn(['deposit', 'withdrawal']), + + // Conditions + body('conditions_type').isString().isIn(['and', 'or']).default('and'), + body('conditions').isArray({ min: 1 }), + body('conditions.*.field').exists().isIn(['description', 'amount']), + body('conditions.*.comparator') + .exists() + .isIn(['equals', 'contains', 'not_contain']) + .default('contain'), + body('conditions.*.value').exists(), + + // Assign + body('assign_category') + .isString() + .isIn([ + 'interest_income', + 'other_income', + 'deposit', + 'expense', + 'owner_drawings', + ]), + body('assign_account_id').isInt({ min: 0 }), + body('assign_payee').isString().optional({ nullable: true }), + body('assign_memo').isString().optional({ nullable: true }), + ]; + } + + /** + * Router constructor. + */ + public router() { + const router = Router(); + + router.post( + '/', + [...this.bankRuleValidationSchema], + this.validationResult, + this.createBankRule.bind(this) + ); + router.post( + '/:id', + [param('id').toInt().exists(), ...this.bankRuleValidationSchema], + this.validationResult, + this.editBankRule.bind(this) + ); + router.delete( + '/:id', + [param('id').toInt().exists()], + this.validationResult, + this.deleteBankRule.bind(this) + ); + router.get( + '/:id', + [param('id').toInt().exists()], + this.validationResult, + this.getBankRule.bind(this) + ); + router.get( + '/', + [param('id').toInt().exists()], + this.validationResult, + this.getBankRules.bind(this) + ); + return router; + } + + /** + * Creates a new bank rule. + * @param {Request} req + * @param {Response} res + * @param next + */ + public async createBankRule(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const createBankRuleDTO = this.matchedBodyData(req) as ICreateBankRuleDTO; + + try { + const bankRule = await this.bankRulesApplication.createBankRule( + tenantId, + createBankRuleDTO + ); + return res.status(200).send({ + id: bankRule.id, + message: 'The bank rule has been created successfully.', + }); + } catch (error) { + next(error); + } + } + + /** + * Edits the given bank rule. + * @param req + * @param res + * @param next + */ + public async editBankRule(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const { id: ruleId } = req.params; + const editBankRuleDTO = this.matchedBodyData(req) as IEditBankRuleDTO; + + try { + await this.bankRulesApplication.editBankRule( + tenantId, + ruleId, + editBankRuleDTO + ); + return res.status(200).send({ + id: ruleId, + message: 'The bank rule has been updated successfully.', + }); + } catch (error) { + next(error); + } + } + + /** + * Deletes the given bank rule. + * @param req + * @param res + * @param next + */ + public async deleteBankRule(req: Request, res: Response, next: NextFunction) { + const { id: ruleId } = req.params; + try { + await this.bankRulesApplication.deleteBankRule(tenantId, ruleId); + + return res + .status(200) + .send({ message: 'The bank rule has been deleted.' }); + } catch (error) { + next(error); + } + } + + /** + * Retrieve the given bank rule. + * @param req + * @param res + * @param next + */ + public async getBankRule(req: Request, res: Response, next: NextFunction) { + const { id: ruleId } = req.params; + const { tenantId } = req; + + try { + const bankRule = await this.bankRulesApplication.getBankRule( + tenantId, + ruleId + ); + + return res.status(200).send({ bankRule }); + } catch (error) { + next(error); + } + } + + /** + * Retrieves the bank rules. + * @param req + * @param res + * @param next + */ + public async getBankRules(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + + try { + const bankRules = await this.bankRulesApplication.getBankRules(tenantId); + return res.status(200).send({ bankRules }); + } catch (error) { + next(error); + } + } +} diff --git a/packages/server/src/database/migrations/20240618100137_create_bank_rules_table.js b/packages/server/src/database/migrations/20240618100137_create_bank_rules_table.js new file mode 100644 index 000000000..ec0136117 --- /dev/null +++ b/packages/server/src/database/migrations/20240618100137_create_bank_rules_table.js @@ -0,0 +1,33 @@ +exports.up = function (knex) { + return knex.schema + .createTable('bank_rules', (table) => { + table.increments('id').primary(); + table.string('name'); + table.integer('order').unsigned(); + + table.integer('apply_if_account_id').unsigned(); + table.string('apply_if_transaction_type'); + + table.string('assign_category'); + table.integer('assign_account_id').unsigned(); + table.string('assign_payee'); + table.string('assign_memo'); + + table.string('conditions_type'); + + table.timestamps(); + }) + .createTable('bank_rule_conditions', (table) => { + table.increments('id').primary(); + table.integer('rule_id').unsigned(); + table.string('field'); + table.string('comparator'); + table.string('value'); + }); +}; + +exports.down = function (knex) { + return knex.schema + .dropTableIfExists('bank_rules') + .dropTableIfExists('bank_rule_conditions'); +}; diff --git a/packages/server/src/loaders/tenantModels.ts b/packages/server/src/loaders/tenantModels.ts index 5183e85ae..0debb29b5 100644 --- a/packages/server/src/loaders/tenantModels.ts +++ b/packages/server/src/loaders/tenantModels.ts @@ -64,6 +64,8 @@ import PlaidItem from 'models/PlaidItem'; import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction'; import Document from '@/models/Document'; import DocumentLink from '@/models/DocumentLink'; +import { BankRule } from '@/models/BankRule'; +import { BankRuleCondition } from '@/models/BankRuleCondition'; export default (knex) => { const models = { @@ -131,6 +133,8 @@ export default (knex) => { DocumentLink, PlaidItem, UncategorizedCashflowTransaction, + BankRule, + BankRuleCondition, }; return mapValues(models, (model) => model.bindKnex(knex)); }; diff --git a/packages/server/src/models/BankRule.ts b/packages/server/src/models/BankRule.ts new file mode 100644 index 000000000..1f93a3415 --- /dev/null +++ b/packages/server/src/models/BankRule.ts @@ -0,0 +1,46 @@ +import TenantModel from 'models/TenantModel'; +import { Model } from 'objection'; + +export class BankRule extends TenantModel { + /** + * Table name + */ + static get tableName() { + return 'bank_rules'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['created_at', 'updated_at']; + } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return []; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + const { BankRuleCondition } = require('models/BankRuleCondition'); + + return { + /** + * Sale invoice associated entries. + */ + conditions: { + relation: Model.HasManyRelation, + modelClass: BankRuleCondition, + join: { + from: 'bank_rules.id', + to: 'bank_rule_conditions.ruleId', + }, + }, + }; + } +} diff --git a/packages/server/src/models/BankRuleCondition.ts b/packages/server/src/models/BankRuleCondition.ts new file mode 100644 index 000000000..ff0fa7a06 --- /dev/null +++ b/packages/server/src/models/BankRuleCondition.ts @@ -0,0 +1,24 @@ +import TenantModel from 'models/TenantModel'; + +export class BankRuleCondition extends TenantModel { + /** + * Table name. + */ + static get tableName() { + return 'bank_rule_conditions'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return []; + } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return []; + } +} diff --git a/packages/server/src/services/Banking/Rules/BankRulesApplication.ts b/packages/server/src/services/Banking/Rules/BankRulesApplication.ts new file mode 100644 index 000000000..c24f47c32 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/BankRulesApplication.ts @@ -0,0 +1,79 @@ +import { Inject, Service } from 'typedi'; +import { CreateBankRuleService } from './CreateBankRule'; +import { DeleteBankRuleSerivce } from './DeleteBankRule'; +import { EditBankRuleService } from './EditBankRule'; +import { GetBankRuleService } from './GetBankRule'; +import { GetBankRulesService } from './GetBankRules'; +import { ICreateBankRuleDTO, IEditBankRuleDTO } from './types'; + +@Service() +export class BankRulesApplication { + @Inject() + private createBankRuleService: CreateBankRuleService; + + @Inject() + private editBankRuleService: EditBankRuleService; + + @Inject() + private deleteBankRuleService: DeleteBankRuleSerivce; + + @Inject() + private getBankRuleService: GetBankRuleService; + + @Inject() + private getBankRulesService: GetBankRulesService; + + /** + * Creates new bank rule. + * @param {number} tenantId + * @param {ICreateBankRuleDTO} createRuleDTO + * @returns + */ + public createBankRule(tenantId: number, createRuleDTO: ICreateBankRuleDTO) { + return this.createBankRuleService.createBankRule(tenantId, createRuleDTO); + } + + /** + * Edits the given bank rule. + * @param {number} tenantId + * @param {IEditBankRuleDTO} editRuleDTO + * @returns + */ + public editBankRule( + tenantId: number, + ruleId: number, + editRuleDTO: IEditBankRuleDTO + ) { + return this.editBankRuleService.editBankRule(tenantId, ruleId, editRuleDTO); + } + + /** + * Deletes the given bank rule. + * @param {number} tenantId + * @param {number} ruleId + * @returns + */ + public deleteBankRule(tenantId: number, ruleId: number) { + return this.deleteBankRuleService.deleteBankRule(tenantId, ruleId); + } + + /** + * Retrieves the given bank rule. + * @param {number} tenantId + * @param {number} ruleId + * @returns + */ + public getBankRule(tenantId: number, ruleId: number) { + return this.getBankRuleService.getBankRule(tenantId, ruleId); + } + + /** + * Retrieves the bank rules of the given account. + * @param {number} tenantId + * @param {number} accountId + * @returns + */ + public getBankRules(tenantId: number) { + return this.getBankRulesService.getBankRules(tenantId); + } +} diff --git a/packages/server/src/services/Banking/Rules/CreateBankRule.ts b/packages/server/src/services/Banking/Rules/CreateBankRule.ts new file mode 100644 index 000000000..9385d0a79 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/CreateBankRule.ts @@ -0,0 +1,65 @@ +import { Knex } from 'knex'; +import { Inject, Service } from 'typedi'; +import { + IBankRuleEventCreatedPayload, + IBankRuleEventCreatingPayload, + ICreateBankRuleDTO, +} from './types'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import UnitOfWork from '@/services/UnitOfWork'; +import events from '@/subscribers/events'; + +@Service() +export class CreateBankRuleService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private eventPublisher: EventPublisher; + + /** + * Transformes the DTO to model. + * @param {ICreateBankRuleDTO} createDTO + * @returns + */ + private transformDTO(createDTO: ICreateBankRuleDTO) { + return { + ...createDTO, + }; + } + + /** + * Creates a new bank rule. + * @param {number} tenantId + * @param {ICreateBankRuleDTO} createRuleDTO + */ + public createBankRule(tenantId: number, createRuleDTO: ICreateBankRuleDTO) { + const { BankRule } = this.tenancy.models(tenantId); + + const transformDTO = this.transformDTO(createRuleDTO); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onBankRuleCreating` event. + await this.eventPublisher.emitAsync(events.bankRules.onCreating, { + createRuleDTO, + trx, + } as IBankRuleEventCreatingPayload); + + const bankRule = await BankRule.query(trx).upsertGraph({ + ...transformDTO, + }); + + // Triggers `onBankRuleCreated` event. + await this.eventPublisher.emitAsync(events.bankRules.onCreated, { + createRuleDTO, + trx, + } as IBankRuleEventCreatedPayload); + + return bankRule; + }); + } +} diff --git a/packages/server/src/services/Banking/Rules/DeleteBankRule.ts b/packages/server/src/services/Banking/Rules/DeleteBankRule.ts new file mode 100644 index 000000000..9045f2a9b --- /dev/null +++ b/packages/server/src/services/Banking/Rules/DeleteBankRule.ts @@ -0,0 +1,53 @@ +import { Knex } from 'knex'; +import UnitOfWork from '@/services/UnitOfWork'; +import { Inject, Service } from 'typedi'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; +import { + IBankRuleEventDeletedPayload, + IBankRuleEventDeletingPayload, +} from './types'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; + +@Service() +export class DeleteBankRuleSerivce { + @Inject() + private uow: UnitOfWork; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private tenancy: HasTenancyService; + + /** + * Deletes the given bank rule. + * @param {number} tenantId + * @param {number} ruleId + * @returns {Promise} + */ + public async deleteBankRule(tenantId: number, ruleId: number) { + const { BankRule } = this.tenancy.models(tenantId); + + const oldBankRule = await BankRule.query() + .findById(ruleId) + .throwIfNotFound(); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onBankRuleDeleting` event. + await this.eventPublisher.emitAsync(events.bankRules.onDeleting, { + oldBankRule, + ruleId, + trx, + } as IBankRuleEventDeletingPayload); + + await BankRule.query(trx).findById(ruleId).delete(); + + // Triggers `onBankRuleDeleted` event. + await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, { + ruleId, + trx, + } as IBankRuleEventDeletedPayload); + }); + } +} diff --git a/packages/server/src/services/Banking/Rules/EditBankRule.ts b/packages/server/src/services/Banking/Rules/EditBankRule.ts new file mode 100644 index 000000000..346541d62 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/EditBankRule.ts @@ -0,0 +1,80 @@ +import { Knex } from 'knex'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import UnitOfWork from '@/services/UnitOfWork'; +import events from '@/subscribers/events'; +import { Inject, Service } from 'typedi'; +import { + IBankRuleEventEditedPayload, + IBankRuleEventEditingPayload, + IEditBankRuleDTO, +} from './types'; + +@Service() +export class EditBankRuleService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private eventPublisher: EventPublisher; + + /** + * + * @param createDTO + * @returns + */ + private transformDTO(createDTO: IEditBankRuleDTO) { + return { + ...createDTO, + }; + } + + /** + * Edits the given bank rule. + * @param {number} tenantId + * @param {number} ruleId - + * @param {IEditBankRuleDTO} editBankDTO + */ + public async editBankRule( + tenantId: number, + ruleId: number, + editRuleDTO: IEditBankRuleDTO + ) { + const { BankRule } = this.tenancy.models(tenantId); + + const oldBankRule = await BankRule.query() + .findById(ruleId) + .throwIfNotFound(); + + const tranformDTO = this.transformDTO(editRuleDTO); + + return this.uow.withTransaction( + tenantId, + async (trx?: Knex.Transaction) => { + // Triggers `onBankRuleEditing` event. + await this.eventPublisher.emitAsync(events.bankRules.onEditing, { + oldBankRule, + ruleId, + editRuleDTO, + trx, + } as IBankRuleEventEditingPayload); + + // Updates the given bank rule. + await BankRule.query() + .findById(ruleId) + .patch({ ...tranformDTO }); + + // Triggers `onBankRuleEdited` event. + await this.eventPublisher.emitAsync(events.bankRules.onEdited, { + oldBankRule, + ruleId, + editRuleDTO, + trx, + } as IBankRuleEventEditedPayload); + } + ); + } +} diff --git a/packages/server/src/services/Banking/Rules/GetBankRule.ts b/packages/server/src/services/Banking/Rules/GetBankRule.ts new file mode 100644 index 000000000..82e141f4f --- /dev/null +++ b/packages/server/src/services/Banking/Rules/GetBankRule.ts @@ -0,0 +1,34 @@ +import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; +import { BankRule } from '@/models/BankRule'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { Inject, Service } from 'typedi'; +import { GetBankRuleTransformer } from './GetBankRuleTransformer'; + +@Service() +export class GetBankRuleService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private transformer: TransformerInjectable; + + /** + * Retrieves the bank rule. + * @param {number} tenantId + * @param {number} ruleId + * @returns + */ + async getBankRule(tenantId: number, ruleId: number) { + const { BankRule } = this.tenancy.models(tenantId); + + const bankRule = await BankRule.query() + .findById(ruleId) + .withGraphFetched('conditions'); + + return this.transformer.transform( + tenantId, + bankRule, + new GetBankRuleTransformer() + ); + } +} diff --git a/packages/server/src/services/Banking/Rules/GetBankRuleTransformer.ts b/packages/server/src/services/Banking/Rules/GetBankRuleTransformer.ts new file mode 100644 index 000000000..93cb6cab5 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/GetBankRuleTransformer.ts @@ -0,0 +1,11 @@ +import { Transformer } from '@/lib/Transformer/Transformer'; + +export class GetBankRuleTransformer extends Transformer { + /** + * Include these attributes to sale invoice object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return []; + }; +} diff --git a/packages/server/src/services/Banking/Rules/GetBankRules.ts b/packages/server/src/services/Banking/Rules/GetBankRules.ts new file mode 100644 index 000000000..d543956cb --- /dev/null +++ b/packages/server/src/services/Banking/Rules/GetBankRules.ts @@ -0,0 +1,31 @@ +import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { Inject, Service } from 'typedi'; +import { GetBankRulesTransformer } from './GetBankRulesTransformer'; + +@Service() +export class GetBankRulesService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private transformer: TransformerInjectable; + + /** + * Retrieves the bank rules of the given account. + * @param {number} tenantId + * @param {number} accountId + * @returns + */ + public async getBankRules(tenantId: number) { + const { BankRule } = this.tenancy.models(tenantId); + + const bankRule = await BankRule.query(); + + return this.transformer.transform( + tenantId, + bankRule, + new GetBankRulesTransformer() + ); + } +} diff --git a/packages/server/src/services/Banking/Rules/GetBankRulesTransformer.ts b/packages/server/src/services/Banking/Rules/GetBankRulesTransformer.ts new file mode 100644 index 000000000..634798826 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/GetBankRulesTransformer.ts @@ -0,0 +1,11 @@ +import { Transformer } from '@/lib/Transformer/Transformer'; + +export class GetBankRulesTransformer extends Transformer { + /** + * Include these attributes to sale invoice object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return []; + }; +} diff --git a/packages/server/src/services/Banking/Rules/types.ts b/packages/server/src/services/Banking/Rules/types.ts new file mode 100644 index 000000000..4ed5aff21 --- /dev/null +++ b/packages/server/src/services/Banking/Rules/types.ts @@ -0,0 +1,64 @@ +import { Knex } from 'knex'; + +export enum BankRuleAssignCategory { + InterestIncome = 'InterestIncome', + OtherIncome = 'OtherIncome', + Deposit = 'Deposit', + Expense = 'Expense', + OwnerDrawings = 'OwnerDrawings', +} + +export interface IBankRuleConditionDTO { + id?: number; + field: string; + comparator: string; + value: number; +} + +export interface IBankRuleCommonDTO { + name: string; + order?: number; + applyIfAccountId: number; + applyIfTransactionType: string; + + conditions: IBankRuleConditionDTO[]; + + assignCategory: BankRuleAssignCategory; + assignAccountId: number; + assignPayee?: string; + assignMemo?: string; +} + +export interface ICreateBankRuleDTO extends IBankRuleCommonDTO {} +export interface IEditBankRuleDTO extends IBankRuleCommonDTO {} + +export interface IBankRuleEventCreatingPayload { + createRuleDTO: ICreateBankRuleDTO; + trx?: Knex.Transaction; +} +export interface IBankRuleEventCreatedPayload { + createRuleDTO: ICreateBankRuleDTO; + trx?: Knex.Transaction; +} + +export interface IBankRuleEventEditingPayload { + ruleId: number; + oldBankRule: any; + editRuleDTO: IEditBankRuleDTO; + trx?: Knex.Transaction; +} +export interface IBankRuleEventEditedPayload { + ruleId: number; + editRuleDTO: IEditBankRuleDTO; + trx?: Knex.Transaction; +} + +export interface IBankRuleEventDeletingPayload { + oldBankRule: any; + ruleId: number; + trx?: Knex.Transaction; +} +export interface IBankRuleEventDeletedPayload { + ruleId: number; + trx?: Knex.Transaction; +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index b96cadf95..c57941f1e 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -205,7 +205,7 @@ export default { onPreMailSend: 'onSaleReceiptPreMailSend', onMailSend: 'onSaleReceiptMailSend', - onMailSent: 'onSaleReceiptMailSent', + onMailSent: 'onSaleReceiptMailSent', }, /** @@ -229,7 +229,7 @@ export default { onPreMailSend: 'onPaymentReceivePreMailSend', onMailSend: 'onPaymentReceiveMailSend', - onMailSent: 'onPaymentReceiveMailSent', + onMailSent: 'onPaymentReceiveMailSent', }, /** @@ -617,4 +617,15 @@ export default { plaid: { onItemCreated: 'onPlaidItemCreated', }, + + bankRules: { + onCreating: 'onBankRuleCreating', + onCreated: 'onBankRuleCreated', + + onEditing: 'onBankRuleEditing', + onEdited: 'onBankRuleEdited', + + onDeleting: 'onBankRuleDeleting', + onDeleted: 'onBankRuleDeleted', + }, };