From 7150fdf66cdca0d02dc6e0af41cf5d51365af46e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 13 Nov 2022 15:12:50 +1100 Subject: [PATCH 01/23] Bank Transaction Rules --- app/Factory/BankTransactionRuleFactory.php | 30 ++ app/Filters/BankTransactionRuleFilters.php | 133 +++++ .../BankTransactionRuleController.php | 504 ++++++++++++++++++ .../CreateBankTransactionRuleRequest.php | 28 + .../DestroyBankTransactionRuleRequest.php | 27 + .../EditBankTransactionRuleRequest.php | 27 + .../ShowBankTransactionRuleRequest.php | 27 + .../StoreBankTransactionRuleRequest.php | 63 +++ .../UpdateBankTransactionRuleRequest.php | 60 +++ app/Models/BankTransactionRule.php | 69 +++ .../BankTransactionRuleRepository.php | 34 ++ .../BankTransactionRuleTransformer.php | 91 ++++ ...13_034143_bank_transaction_rules_table.php | 53 ++ 13 files changed, 1146 insertions(+) create mode 100644 app/Factory/BankTransactionRuleFactory.php create mode 100644 app/Filters/BankTransactionRuleFilters.php create mode 100644 app/Http/Controllers/BankTransactionRuleController.php create mode 100644 app/Http/Requests/BankTransactionRule/CreateBankTransactionRuleRequest.php create mode 100644 app/Http/Requests/BankTransactionRule/DestroyBankTransactionRuleRequest.php create mode 100644 app/Http/Requests/BankTransactionRule/EditBankTransactionRuleRequest.php create mode 100644 app/Http/Requests/BankTransactionRule/ShowBankTransactionRuleRequest.php create mode 100644 app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php create mode 100644 app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php create mode 100644 app/Models/BankTransactionRule.php create mode 100644 app/Repositories/BankTransactionRuleRepository.php create mode 100644 app/Transformers/BankTransactionRuleTransformer.php create mode 100644 database/migrations/2022_11_13_034143_bank_transaction_rules_table.php diff --git a/app/Factory/BankTransactionRuleFactory.php b/app/Factory/BankTransactionRuleFactory.php new file mode 100644 index 000000000000..69aeb3ddb3bb --- /dev/null +++ b/app/Factory/BankTransactionRuleFactory.php @@ -0,0 +1,30 @@ +user_id = $user_id; + $bank_transaction_rule->company_id = $company_id; + + $bank_transaction_rule->name = ''; + $bank_transaction_rule->rules = []; + + return $bank_transaction_rule; + } +} diff --git a/app/Filters/BankTransactionRuleFilters.php b/app/Filters/BankTransactionRuleFilters.php new file mode 100644 index 000000000000..bda5158e2a16 --- /dev/null +++ b/app/Filters/BankTransactionRuleFilters.php @@ -0,0 +1,133 @@ +=1) + return $this->builder->where('name', 'like', '%'.$name.'%'); + + return $this->builder; + } + + /** + * Filter based on search text. + * + * @param string query filter + * @return Builder + * @deprecated + */ + public function filter(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + return $this->builder->where(function ($query) use ($filter) { + $query->where('bank_transaction_rules.name', 'like', '%'.$filter.'%'); + }); + + } + + /** + * Filters the list based on the status + * archived, active, deleted. + * + * @param string filter + * @return Builder + */ + public function status(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + $table = 'bank_transaction_rules'; + $filters = explode(',', $filter); + + return $this->builder->where(function ($query) use ($filters, $table) { + $query->whereNull($table.'.id'); + + if (in_array(parent::STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table.'.deleted_at'); + } + + if (in_array(parent::STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table.'.deleted_at'); + + if (! in_array($table, ['users'])) { + $query->where($table.'.is_deleted', '=', 0); + } + }); + } + + if (in_array(parent::STATUS_DELETED, $filters)) { + $query->orWhere($table.'.is_deleted', '=', 1); + } + }); + } + + /** + * Sorts the list based on $sort. + * + * @param string sort formatted as column|asc + * @return Builder + */ + public function sort(string $sort) : Builder + { + $sort_col = explode('|', $sort); + + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Returns the base query. + * + * @param int company_id + * @param User $user + * @return Builder + * @deprecated + */ + public function baseQuery(int $company_id, User $user) : Builder + { + + } + + /** + * Filters the query by the users company ID. + * + * @return Illuminate\Database\Query\Builder + */ + public function entityFilter() + { + //return $this->builder->whereCompanyId(auth()->user()->company()->id); + return $this->builder->company(); + } +} diff --git a/app/Http/Controllers/BankTransactionRuleController.php b/app/Http/Controllers/BankTransactionRuleController.php new file mode 100644 index 000000000000..c9c8b04f0d9e --- /dev/null +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -0,0 +1,504 @@ +bank_transaction_repo = $bank_transaction_repo; + } + + /** + * @OA\Get( + * path="/api/v1/bank_transaction_rules", + * operationId="getBankTransactionRules", + * tags={"bank_transaction_rules"}, + * summary="Gets a list of bank_transaction_rules", + * description="Lists all bank transaction rules", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\Parameter( + * name="rows", + * in="query", + * description="The number of bank integrations to return", + * example="50", + * required=false, + * @OA\Schema( + * type="number", + * format="integer", + * ), + * ), + * @OA\Response( + * response=200, + * description="A list of bank integrations", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * @param BankTransactionFilters $filter + * @return Response|mixed + */ + public function index(BankTransactionRuleFilters $filters) + { + + $bank_transaction_rules = BankTransactionRule::filter($filters); + + return $this->listResponse($bank_transaction_rules); + + } + + /** + * Display the specified resource. + * + * @param ShowBankTransactionRuleRequest $request + * @param BankTransactionRule $bank_transaction_rule + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_transaction_rules/{id}", + * operationId="showBankTransactionRule", + * tags={"bank_transaction_rules"}, + * summary="Shows a bank_transaction", + * description="Displays a bank_transaction by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Bank Transaction RuleHashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction rule object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function show(ShowBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule) + { + return $this->itemResponse($bank_transaction_rule); + } + + + /** + * Show the form for editing the specified resource. + * + * @param EditBankTransactionRuleRequest $request + * @param BankTransactionRule $bank_transaction_rule + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_transaction_rules/{id}/edit", + * operationId="editBankTransactionRule", + * tags={"bank_transaction_rules"}, + * summary="Shows a bank_transaction for editing", + * description="Displays a bank_transaction by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Bank Transaction Rule Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction rule object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function edit(EditBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule) + { + return $this->itemResponse($bank_transaction_rule); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateBankTransactionRuleRequest $request + * @param BankTransactionRule $bank_transaction_rule + * @return Response + * + * + * + * @OA\Put( + * path="/api/v1/bank_transaction_rules/{id}", + * operationId="updateBankTransactionRule", + * tags={"bank_transaction_rules"}, + * summary="Updates a bank_transaction Rule", + * description="Handles the updating of a bank_transaction rule by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Bank Transaction Rule Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction rule object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function update(UpdateBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule) + { + + //stubs for updating the model + $bank_transaction = $this->bank_transaction_repo->save($request->all(), $bank_transaction_rule); + + return $this->itemResponse($bank_transaction_rule->fresh()); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateBankTransactionRuleRequest $request + * @return Response + * + * + * + * @OA\Get( + * path="/api/v1/bank_transaction_rules/create", + * operationId="getBankTransactionRulesCreate", + * tags={"bank_transaction_rules"}, + * summary="Gets a new blank bank_transaction rule object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A blank bank_transaction rule object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function create(CreateBankTransactionRuleRequest $request) + { + $bank_transaction_rule = BankTransactionRuleFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + return $this->itemResponse($bank_transaction_rule); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreBankTransactionRuleRequest $request + * @return Response + * + * + * + * @OA\Post( + * path="/api/v1/bank_transaction_rules", + * operationId="storeBankTransaction", + * tags={"bank_transaction_rules"}, + * summary="Adds a bank_transaction rule", + * description="Adds an bank_transaction to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved bank_transaction rule object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function store(StoreBankTransactionRuleRequest $request) + { + //stub to store the model + $bank_transaction_rule = $this->bank_transaction_repo->save($request->all(), BankTransactionRuleFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id)); + + return $this->itemResponse($bank_transaction_rule); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyBankTransactionRuleRequest $request + * @param BankTransactionRule $bank_transaction_rule + * @return Response + * + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/bank_transaction_rules/{id}", + * operationId="deleteBankTransactionRule", + * tags={"bank_transaction_rules"}, + * summary="Deletes a bank_transaction rule", + * description="Handles the deletion of a bank_transaction rule by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Bank Transaction Rule Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function destroy(DestroyBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule) + { + $this->bank_transaction_repo->delete($bank_transaction_rule); + + return $this->itemResponse($bank_transaction_rule->fresh()); + } + + + /** + * Perform bulk actions on the list view. + * + * @return Collection + * + * @OA\Post( + * path="/api/v1/bank_transation_rules/bulk", + * operationId="bulkBankTransactionRules", + * tags={"bank_transaction_rules"}, + * summary="Performs bulk actions on an array of bank_transation rules", + * description="", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\RequestBody( + * description="Action paramters", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="array", + * @OA\Items( + * type="integer", + * description="Array of hashed IDs to be bulk 'actioned", + * example="[0,1,2,3]", + * ), + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="The Bulk Action response", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function bulk() + { + $action = request()->input('action'); + + if(!in_array($action, ['archive', 'restore', 'delete'])) + return response()->json(['message' => 'Unsupported action.'], 400); + + $ids = request()->input('ids'); + + $bank_transaction_rules = BankTransactionRule::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); + + $bank_transaction_rules->each(function ($bank_transaction_rule, $key) use ($action) { + if (auth()->user()->can('edit', $bank_transaction_rule)) { + $this->bank_transaction_repo->{$action}($bank_transaction_rule); + } + }); + + /* Need to understand which permission are required for the given bulk action ie. view / edit */ + + return $this->listResponse(BankTransactionRule::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/BankTransactionRule/CreateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/CreateBankTransactionRuleRequest.php new file mode 100644 index 000000000000..24fa6e8e15c6 --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/CreateBankTransactionRuleRequest.php @@ -0,0 +1,28 @@ +user()->can('create', BankTransactionRule::class); + } +} diff --git a/app/Http/Requests/BankTransactionRule/DestroyBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/DestroyBankTransactionRuleRequest.php new file mode 100644 index 000000000000..b19f87fb009e --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/DestroyBankTransactionRuleRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_transaction_rule); + } +} diff --git a/app/Http/Requests/BankTransactionRule/EditBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/EditBankTransactionRuleRequest.php new file mode 100644 index 000000000000..9edf1aaa164b --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/EditBankTransactionRuleRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_transaction_rule); + } +} diff --git a/app/Http/Requests/BankTransactionRule/ShowBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/ShowBankTransactionRuleRequest.php new file mode 100644 index 000000000000..9a9eb6fc5d54 --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/ShowBankTransactionRuleRequest.php @@ -0,0 +1,27 @@ +user()->can('view', $this->bank_transaction_rule); + } +} diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php new file mode 100644 index 000000000000..158a59bf462e --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -0,0 +1,63 @@ +user()->can('create', BankTransactionRule::class); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $rules = [ + 'name' => 'bail|required|string' + ]; + + if (isset($this->currency_id)) + $rules['category_Id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + + if(isset($this->vendor_id)) + $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + if(isset($this->client_id)) + $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $input = $this->decodePrimaryKeys($input); + + $this->replace($input); + } + + + +} diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php new file mode 100644 index 000000000000..3ccbc9565364 --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -0,0 +1,60 @@ +user()->can('edit', $this->bank_transaction); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $rules = [ + 'name' => 'bail|required|string' + ]; + + if (isset($this->currency_id)) + $rules['category_Id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + + if(isset($this->vendor_id)) + $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + if(isset($this->client_id)) + $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $input = $this->decodePrimaryKeys($input); + + $this->replace($input); + } + +} diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php new file mode 100644 index 000000000000..fa6cccd4cadc --- /dev/null +++ b/app/Models/BankTransactionRule.php @@ -0,0 +1,69 @@ +belongsTo(Company::class); + } + + public function vendor() + { + return $this->belongsTo(Vendor::class); + } + + public function client() + { + return $this->belongsTo(Client::class); + } + + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + + public function expense_cateogry() + { + return $this->belongsTo(ExpenseCategory::class)->withTrashed(); + } + +} \ No newline at end of file diff --git a/app/Repositories/BankTransactionRuleRepository.php b/app/Repositories/BankTransactionRuleRepository.php new file mode 100644 index 000000000000..cfc5d7579168 --- /dev/null +++ b/app/Repositories/BankTransactionRuleRepository.php @@ -0,0 +1,34 @@ +fill($data); + + $bank_transaction_rule->save(); + + return $bank_transaction_rule + } + +} diff --git a/app/Transformers/BankTransactionRuleTransformer.php b/app/Transformers/BankTransactionRuleTransformer.php new file mode 100644 index 000000000000..e2b709121e3c --- /dev/null +++ b/app/Transformers/BankTransactionRuleTransformer.php @@ -0,0 +1,91 @@ + (string) $this->encodePrimaryKey($bank_transaction_rule->id), + 'name' => (string) $bank_transaction_rule->name, + 'rules' => $bank_transaction_rule->rules ?: (array) [], + 'auto_convert' => (bool) $bank_transaction_rule->auto_convert, + 'matches_on_all' => (bool) $bank_transaction_rule->matches_on_all, + 'applies_to' => (string) $bank_transaction_rule->applies_to, + 'record_as' => (string) $bank_transaction_rule->record_as, + 'client_id' => $this->encodePrimaryKey($bank_transaction_rule->client_id) ?: '', + 'vendor_id' => $this->encodePrimaryKey($bank_transaction_rule->vendor_id) ?: '', + 'category_id' => $this->encodePrimaryKey($bank_transaction_rule->category_id) ?: '', + 'is_deleted' => (bool) $bank_transaction_rule->is_deleted, + 'created_at' => (int) $bank_transaction_rule->created_at, + 'updated_at' => (int) $bank_transaction_rule->updated_at, + 'archived_at' => (int) $bank_transaction_rule->deleted_at, + ]; + } + + public function includeCompany(BankTransactionRule $bank_transaction_rule) + { + $transformer = new CompanyTransformer($this->serializer); + + return $this->includeItem($bank_transaction_rule->company, $transformer, Company::class); + } + + public function includeClient(BankTransactionRule $bank_transaction_rule) + { + $transformer = new ClientTransformer($this->serializer); + + return $this->includeItem($bank_transaction_rule->expense, $transformer, Client::class); + } + + public function includeVendor(BankTransactionRule $bank_transaction_rule) + { + $transformer = new VendorTransformer($this->serializer); + + return $this->includeItem($bank_transaction_rule->vendor, $transformer, Vendor::class); + } + +} diff --git a/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php b/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php new file mode 100644 index 000000000000..6201e4e78e6c --- /dev/null +++ b/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php @@ -0,0 +1,53 @@ +id(); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + + $table->string('name'); //name of rule + $table->mediumText('rules')->nullable(); //array of rule objects + $table->boolean('auto_convert')->default(false); //auto convert to match + $table->boolean('matches_on_all')->default(false); //match on all rules or just one + $table->string('applies_to')->default('CREDIT'); //CREDIT/DEBIT + $table->string('record_as')->default('CREDIT'); //CREDIT/DEBIT + + $table->unsignedInteger('client_id')->nullable(); + $table->unsignedInteger('vendor_id')->nullable(); + $table->unsignedInteger('category_id')->nullable(); + + $table->boolean('is_deleted')->default(0); + $table->timestamps(6); + $table->softDeletes('deleted_at', 6); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + }); + + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}; From 6ef21be16c6c1b9acb9cc61c63cfb3b133f9aeea Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 13 Nov 2022 15:21:37 +1100 Subject: [PATCH 02/23] Bank transaction rules --- .../BankTransactionRuleController.php | 3 +- app/Policies/BankTransactionRulePolicy.php | 31 ++++++ app/Providers/AuthServiceProvider.php | 3 + .../BankTransactionRuleRepository.php | 2 +- .../factories/BankTransactionRuleFactory.php | 31 ++++++ routes/api.php | 6 +- tests/Feature/BankTransactionRuleApiTest.php | 102 ++++++++++++++++++ tests/MockAccountData.php | 11 ++ 8 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 app/Policies/BankTransactionRulePolicy.php create mode 100644 database/factories/BankTransactionRuleFactory.php create mode 100644 tests/Feature/BankTransactionRuleApiTest.php diff --git a/app/Http/Controllers/BankTransactionRuleController.php b/app/Http/Controllers/BankTransactionRuleController.php index c9c8b04f0d9e..1f8ae13b2bd6 100644 --- a/app/Http/Controllers/BankTransactionRuleController.php +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -30,6 +30,7 @@ use App\Models\BankTransactionRule; use App\Repositories\BankTransactionRepository; use App\Repositories\BankTransactionRuleRepository; use App\Services\Bank\BankMatchingService; +use App\Transformers\BankTransactionRuleTransformer; use App\Transformers\BankTransactionTransformer; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; @@ -42,7 +43,7 @@ class BankTransactionRuleController extends BaseController protected $entity_type = BankTransactionRule::class; - protected $entity_transformer = BankTransactionTransformer::class; + protected $entity_transformer = BankTransactionRuleTransformer::class; protected $bank_transaction_repo; diff --git a/app/Policies/BankTransactionRulePolicy.php b/app/Policies/BankTransactionRulePolicy.php new file mode 100644 index 000000000000..933aed306f2e --- /dev/null +++ b/app/Policies/BankTransactionRulePolicy.php @@ -0,0 +1,31 @@ +isAdmin(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 9b9f779eeb9d..eef723dcaab3 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -15,6 +15,7 @@ use App\Models\Activity; use App\Models\Bank; use App\Models\BankIntegration; use App\Models\BankTransaction; +use App\Models\BankTransactionRule; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; @@ -45,6 +46,7 @@ use App\Models\Webhook; use App\Policies\ActivityPolicy; use App\Policies\BankIntegrationPolicy; use App\Policies\BankTransactionPolicy; +use App\Policies\BankTransactionRulePolicy; use App\Policies\ClientPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; @@ -86,6 +88,7 @@ class AuthServiceProvider extends ServiceProvider Activity::class => ActivityPolicy::class, BankIntegration::class => BankIntegrationPolicy::class, BankTransaction::class => BankTransactionPolicy::class, + BankTransactionRule::class => BankTransactionRulePolicy::class, Client::class => ClientPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, diff --git a/app/Repositories/BankTransactionRuleRepository.php b/app/Repositories/BankTransactionRuleRepository.php index cfc5d7579168..52ae23dbdc8c 100644 --- a/app/Repositories/BankTransactionRuleRepository.php +++ b/app/Repositories/BankTransactionRuleRepository.php @@ -28,7 +28,7 @@ class BankTransactionRuleRepository extends BaseRepository $bank_transaction_rule->save(); - return $bank_transaction_rule + return $bank_transaction_rule; } } diff --git a/database/factories/BankTransactionRuleFactory.php b/database/factories/BankTransactionRuleFactory.php new file mode 100644 index 000000000000..24a5a38fb297 --- /dev/null +++ b/database/factories/BankTransactionRuleFactory.php @@ -0,0 +1,31 @@ +$this->faker->name(), + ]; + } +} diff --git a/routes/api.php b/routes/api.php index d601bf2bd942..e20d1315f290 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,9 +14,10 @@ use App\Http\Controllers\AccountController; use App\Http\Controllers\ActivityController; use App\Http\Controllers\Auth\ForgotPasswordController; use App\Http\Controllers\Auth\LoginController; -use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BankIntegrationController; use App\Http\Controllers\BankTransactionController; +use App\Http\Controllers\BankTransactionRuleController; +use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ChartController; use App\Http\Controllers\ClientController; @@ -119,6 +120,9 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale Route::post('bank_transactions/bulk', [BankTransactionController::class, 'bulk'])->name('bank_transactions.bulk'); Route::post('bank_transactions/match', [BankTransactionController::class, 'match'])->name('bank_transactions.match'); + Route::resource('bank_transaction_rules', BankTransactionRuleController::class); // name = (clients. index / create / show / update / destroy / edit + Route::post('bank_transaction_rules/bulk', [BankTransactionRuleController::class, 'bulk'])->name('bank_transaction_rules.bulk'); + Route::post('check_subdomain', [SubdomainController::class, 'index'])->name('check_subdomain'); Route::get('ping', [PingController::class, 'index'])->name('ping'); Route::get('health_check', [PingController::class, 'health'])->name('health_check'); diff --git a/tests/Feature/BankTransactionRuleApiTest.php b/tests/Feature/BankTransactionRuleApiTest.php new file mode 100644 index 000000000000..30ff202c0644 --- /dev/null +++ b/tests/Feature/BankTransactionRuleApiTest.php @@ -0,0 +1,102 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + } + + public function testBankTransactionRuleGet() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/bank_transaction_rules/'.$this->encodePrimaryKey($this->bank_transaction_rule->id)); + + $response->assertStatus(200); + } + + public function testBankTransactionRuleArchived() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction_rule->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transaction_rules/bulk?action=archive', $data); + + $arr = $response->json(); + + $this->assertNotNull($arr['data'][0]['archived_at']); + } + + public function testBankTransactionRuleRestored() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction_rule->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transaction_rules/bulk?action=restore', $data); + + $arr = $response->json(); + + $this->assertEquals(0, $arr['data'][0]['archived_at']); + } + + public function testBankTransactionRuleDeleted() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction_rule->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transaction_rules/bulk?action=delete', $data); + + $arr = $response->json(); + + $this->assertTrue($arr['data'][0]['is_deleted']); + } +} diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 0ccb86849bc7..2c62a670e04c 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -26,6 +26,7 @@ use App\Jobs\Company\CreateCompanyTaskStatuses; use App\Models\Account; use App\Models\BankIntegration; use App\Models\BankTransaction; +use App\Models\BankTransactionRule; use App\Models\Client; use App\Models\ClientContact; use App\Models\Company; @@ -153,6 +154,11 @@ trait MockAccountData */ public $bank_transaction; + /** + * @var + */ + public $bank_transaction_rule; + /** * @var */ @@ -572,6 +578,11 @@ trait MockAccountData 'bank_integration_id' => $this->bank_integration->id, ]); + $this->bank_transaction_rule = BankTransactionRule::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + ]); + $invitations = CreditInvitation::whereCompanyId($this->credit->company_id) ->whereCreditId($this->credit->id); From dc6aca74b2b1601241b0d55c092e35c82ebfdcee Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 13 Nov 2022 15:41:34 +1100 Subject: [PATCH 03/23] Pad out operators for rules --- .../StoreBankTransactionRuleRequest.php | 7 ++++- .../UpdateBankTransactionRuleRequest.php | 7 ++++- app/Models/BankTransactionRule.php | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index 158a59bf462e..9641bfd1846f 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -33,7 +33,12 @@ class StoreBankTransactionRuleRequest extends Request { /* Ensure we have a client name, and that all emails are unique*/ $rules = [ - 'name' => 'bail|required|string' + 'name' => 'bail|required|string', + 'rules' => 'bail|array', + 'auto_convert' => 'bail|sometimes|bool', + 'matches_on_all' => 'bail|sometimes|bool', + 'applies_to' => 'bail|sometimes|bool', + 'record_as' => 'bail|sometimes|bool', ]; if (isset($this->currency_id)) diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index 3ccbc9565364..2955bf76796e 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -32,7 +32,12 @@ class UpdateBankTransactionRuleRequest extends Request { /* Ensure we have a client name, and that all emails are unique*/ $rules = [ - 'name' => 'bail|required|string' + 'name' => 'bail|required|string', + 'rules' => 'bail|array', + 'auto_convert' => 'bail|sometimes|bool', + 'matches_on_all' => 'bail|sometimes|bool', + 'applies_to' => 'bail|sometimes|bool', + 'record_as' => 'bail|sometimes|bool', ]; if (isset($this->currency_id)) diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index fa6cccd4cadc..26d9bb68baee 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -36,6 +36,32 @@ class BankTransactionRule extends BaseModel protected $dates = [ ]; + /* Columns to search */ + protected array $search_keys = [ + 'client_id' => 'client', + 'vendor_id' => 'vendor', + 'description' => 'description', + 'transaction_reference' => 'transaction_reference', + 'amount' => 'amount', + ]; + + /* Amount */ + protected array $number_operators = [ + '=', + '>', + '>=', + '<', + '<=' + ]; + + /* Description, Client, Vendor, Reference Number */ + protected array $string_operators = [ + 'is', + 'contains', + 'starts_with', + 'is_empty', + ]; + public function getEntityType() { return self::class; From 229a11e009b50bd206b8daea03f9eb4575317208 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 13 Nov 2022 15:52:57 +1100 Subject: [PATCH 04/23] Padd out rules --- app/Models/BankTransactionRule.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index 26d9bb68baee..03a04372180c 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -62,6 +62,16 @@ class BankTransactionRule extends BaseModel 'is_empty', ]; + + // rule object looks like this: + //[ + // { + // 'search_key': 'client_id', + // 'operator' : 'is', + // 'value' : 'Sparky' + // } + //] + public function getEntityType() { return self::class; From 4f5a74dbd222e544f49605b5d3ad8f1db285403b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 13 Nov 2022 16:46:14 +1100 Subject: [PATCH 05/23] Padd out rules --- app/Models/BankTransactionRule.php | 67 +++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index 03a04372180c..3971bc2d7831 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -62,6 +62,7 @@ class BankTransactionRule extends BaseModel 'is_empty', ]; + private array $search_results = []; // rule object looks like this: //[ @@ -71,7 +72,70 @@ class BankTransactionRule extends BaseModel // 'value' : 'Sparky' // } //] - + + public function processRule(BankTransaction $bank_transaction) + { + foreach($this->rules as $key => $rule) + { + $this->search($rule, $key, $bank_transaction); + } + } + + private function search($rule, $key, $bank_transaction) + { + if($rule->search_key == 'amount') + { + //number search + } + else { + //string search + } + } + + private function findAmount($amount, $bank_transaction) + { + if($bank_transaction->base_type == 'CREDIT'){ + //search invoices + } + else{ + //search expenses + } + + } + + private function searchClient($rule, $bank_transaction) + { + if($bank_transaction->base_type == 'CREDIT'){ + //search invoices + } + else{ + //search expenses + } + + } + + private function searchVendor($rule, $bank_transaction) + { + //search expenses + + + } + + private function searchDescription($rule, $bank_transaction) + { + //search expenses public notes + } + + private function searchReference($rule, $bank_transaction) + { + if($bank_transaction->base_type == 'CREDIT'){ + //search invoices + } + else{ + //search expenses + } + } + public function getEntityType() { return self::class; @@ -101,5 +165,6 @@ class BankTransactionRule extends BaseModel { return $this->belongsTo(ExpenseCategory::class)->withTrashed(); } + } } \ No newline at end of file From b2c314d7c354e854af39a95467ba7cc73466a71e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 14 Nov 2022 08:04:47 +1100 Subject: [PATCH 06/23] Remodelling bank transaction rules --- .../BankTransactionRule/StoreBankTransactionRuleRequest.php | 1 - .../BankTransactionRule/UpdateBankTransactionRuleRequest.php | 1 - app/Models/BankTransactionRule.php | 1 - app/Transformers/BankTransactionRuleTransformer.php | 1 - .../2022_11_13_034143_bank_transaction_rules_table.php | 1 - 5 files changed, 5 deletions(-) diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index 9641bfd1846f..873aae6d8f38 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -38,7 +38,6 @@ class StoreBankTransactionRuleRequest extends Request 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', 'applies_to' => 'bail|sometimes|bool', - 'record_as' => 'bail|sometimes|bool', ]; if (isset($this->currency_id)) diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index 2955bf76796e..51701ffcb13f 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -37,7 +37,6 @@ class UpdateBankTransactionRuleRequest extends Request 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', 'applies_to' => 'bail|sometimes|bool', - 'record_as' => 'bail|sometimes|bool', ]; if (isset($this->currency_id)) diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index 3971bc2d7831..273dc524db5f 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -27,7 +27,6 @@ class BankTransactionRule extends BaseModel 'auto_convert', 'matches_on_all', 'applies_to', - 'record_as', 'client_id', 'vendor_id', 'category_id', diff --git a/app/Transformers/BankTransactionRuleTransformer.php b/app/Transformers/BankTransactionRuleTransformer.php index e2b709121e3c..cb8221c7312e 100644 --- a/app/Transformers/BankTransactionRuleTransformer.php +++ b/app/Transformers/BankTransactionRuleTransformer.php @@ -56,7 +56,6 @@ class BankTransactionRuleTransformer extends EntityTransformer 'auto_convert' => (bool) $bank_transaction_rule->auto_convert, 'matches_on_all' => (bool) $bank_transaction_rule->matches_on_all, 'applies_to' => (string) $bank_transaction_rule->applies_to, - 'record_as' => (string) $bank_transaction_rule->record_as, 'client_id' => $this->encodePrimaryKey($bank_transaction_rule->client_id) ?: '', 'vendor_id' => $this->encodePrimaryKey($bank_transaction_rule->vendor_id) ?: '', 'category_id' => $this->encodePrimaryKey($bank_transaction_rule->category_id) ?: '', diff --git a/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php b/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php index 6201e4e78e6c..7609d1a7d3fd 100644 --- a/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php +++ b/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php @@ -24,7 +24,6 @@ return new class extends Migration $table->boolean('auto_convert')->default(false); //auto convert to match $table->boolean('matches_on_all')->default(false); //match on all rules or just one $table->string('applies_to')->default('CREDIT'); //CREDIT/DEBIT - $table->string('record_as')->default('CREDIT'); //CREDIT/DEBIT $table->unsignedInteger('client_id')->nullable(); $table->unsignedInteger('vendor_id')->nullable(); From 93ecb8790b287738eabd2bfd5e0ca98ffe51f971 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 14 Nov 2022 11:21:05 +1100 Subject: [PATCH 07/23] API Doc Blocks for Bank Transaction Rules --- .../BankTransactionRuleController.php | 12 ++++----- .../Controllers/OpenAPI/BTRulesSchema.php | 10 ++++++++ .../Controllers/OpenAPI/BankTransaction.php | 2 +- .../OpenAPI/BankTransactionRule.php | 25 +++++++++++++++++++ 4 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/OpenAPI/BTRulesSchema.php create mode 100644 app/Http/Controllers/OpenAPI/BankTransactionRule.php diff --git a/app/Http/Controllers/BankTransactionRuleController.php b/app/Http/Controllers/BankTransactionRuleController.php index 1f8ae13b2bd6..5e16a645e7e0 100644 --- a/app/Http/Controllers/BankTransactionRuleController.php +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -83,7 +83,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, @@ -143,7 +143,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, @@ -199,7 +199,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, @@ -255,7 +255,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, @@ -303,7 +303,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, @@ -349,7 +349,7 @@ class BankTransactionRuleController extends BaseController * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * @OA\JsonContent(ref="#/components/schemas/BankTransactionRule"), * ), * @OA\Response( * response=422, diff --git a/app/Http/Controllers/OpenAPI/BTRulesSchema.php b/app/Http/Controllers/OpenAPI/BTRulesSchema.php new file mode 100644 index 000000000000..8c967291aae8 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BTRulesSchema.php @@ -0,0 +1,10 @@ +", description="The operator flag of the search"), + * @OA\Property(property="value", type="string" ,example="bob", description="The value to search for"), + * ) + */ diff --git a/app/Http/Controllers/OpenAPI/BankTransaction.php b/app/Http/Controllers/OpenAPI/BankTransaction.php index c3e5f3d19c13..184c24f30f4b 100644 --- a/app/Http/Controllers/OpenAPI/BankTransaction.php +++ b/app/Http/Controllers/OpenAPI/BankTransaction.php @@ -6,7 +6,7 @@ * @OA\Property(property="id", type="string", example="AS3df3A", description="The bank integration hashed id"), * @OA\Property(property="company_id", type="string", example="AS3df3A", description="The company hashed id"), * @OA\Property(property="user_id", type="string", example="AS3df3A", description="The user hashed id"), - * @OA\Property(property="transaction_id", type="integer", example=343434, description="The id of the transaction"), + * @OA\Property(property="transaction_id", type="integer", example=343434, description="The id of the transaction rule"), * @OA\Property(property="amount", type="number", example=10.00, description="The transaction amount"), * @OA\Property(property="currency_id", type="string", example="1", description="The currency ID of the currency"), * @OA\Property(property="account_type", type="string", example="creditCard", description="The account type"), diff --git a/app/Http/Controllers/OpenAPI/BankTransactionRule.php b/app/Http/Controllers/OpenAPI/BankTransactionRule.php new file mode 100644 index 000000000000..f62e0cf19a91 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BankTransactionRule.php @@ -0,0 +1,25 @@ + Date: Sun, 20 Nov 2022 11:13:46 +1100 Subject: [PATCH 08/23] Minor fixes for request forms --- .../BankTransactionRule/StoreBankTransactionRuleRequest.php | 4 ++-- .../UpdateBankTransactionRuleRequest.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index 873aae6d8f38..5f8e8546d94c 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -40,8 +40,8 @@ class StoreBankTransactionRuleRequest extends Request 'applies_to' => 'bail|sometimes|bool', ]; - if (isset($this->currency_id)) - $rules['category_Id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + if(isset($this->category_id)) + $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; if(isset($this->vendor_id)) $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index 51701ffcb13f..8b45a061e4ae 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -25,7 +25,7 @@ class UpdateBankTransactionRuleRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('edit', $this->bank_transaction); + return auth()->user()->can('edit', $this->bank_transaction_rule); } public function rules() @@ -39,8 +39,8 @@ class UpdateBankTransactionRuleRequest extends Request 'applies_to' => 'bail|sometimes|bool', ]; - if (isset($this->currency_id)) - $rules['category_Id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + if(isset($this->category_id)) + $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; if(isset($this->vendor_id)) $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; From 069568da6eb8b50e67315a3c3a3313524b90d900 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 11:16:38 +1100 Subject: [PATCH 09/23] Minor fixes for request forms --- app/Http/Requests/Request.php | 4 ++++ app/Models/BankTransactionRule.php | 1 - app/Repositories/BankTransactionRuleRepository.php | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 141226755f78..c3ff61b48e61 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -125,6 +125,10 @@ class Request extends FormRequest $input['company_gateway_id'] = $this->decodePrimaryKey($input['company_gateway_id']); } + if (array_key_exists('category_id', $input) && is_string($input['category_id'])) { + $input['category_id'] = $this->decodePrimaryKey($input['category_id']); + } + if (isset($input['client_contacts'])) { foreach ($input['client_contacts'] as $key => $contact) { if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) { diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index 273dc524db5f..2096072c4619 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -164,6 +164,5 @@ class BankTransactionRule extends BaseModel { return $this->belongsTo(ExpenseCategory::class)->withTrashed(); } - } } \ No newline at end of file diff --git a/app/Repositories/BankTransactionRuleRepository.php b/app/Repositories/BankTransactionRuleRepository.php index 52ae23dbdc8c..e5116ca6c0f6 100644 --- a/app/Repositories/BankTransactionRuleRepository.php +++ b/app/Repositories/BankTransactionRuleRepository.php @@ -29,6 +29,7 @@ class BankTransactionRuleRepository extends BaseRepository $bank_transaction_rule->save(); return $bank_transaction_rule; + } } From 3562c3376cba0dd11a5d0f6f9c0e0abe1c7bffe0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 11:25:57 +1100 Subject: [PATCH 10/23] Fixes for CRUD actions on bank transaction rules --- .../OpenAPI/BankTransactionRule.php | 2 +- .../StoreBankTransactionRuleRequest.php | 2 +- .../UpdateBankTransactionRuleRequest.php | 2 +- app/Models/BankTransactionRule.php | 7 ++ tests/Feature/BankTransactionRuleApiTest.php | 85 +++++++++++++++++++ 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/OpenAPI/BankTransactionRule.php b/app/Http/Controllers/OpenAPI/BankTransactionRule.php index f62e0cf19a91..522b1764b265 100644 --- a/app/Http/Controllers/OpenAPI/BankTransactionRule.php +++ b/app/Http/Controllers/OpenAPI/BankTransactionRule.php @@ -17,7 +17,7 @@ * ), * @OA\Property(property="auto_convert", type="boolean", example=true, description="Flags whether the rule converts the transaction automatically"), * @OA\Property(property="matches_on_all", type="boolean", example=true, description="Flags whether all subrules are required for the match"), - * @OA\Property(property="applies_to", type="boolean", example="CREDIT", description="Flags whether the rule applies to a CREDIT or DEBIT"), + * @OA\Property(property="applies_to", type="string", example="CREDIT", description="Flags whether the rule applies to a CREDIT or DEBIT"), * @OA\Property(property="client_id", type="string", example="AS3df3A", description="The client hashed id"), * @OA\Property(property="vendor_id", type="string", example="AS3df3A", description="The vendor hashed id"), * @OA\Property(property="category_id", type="string", example="AS3df3A", description="The category hashed id"), diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index 5f8e8546d94c..f62b7414c710 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -37,7 +37,7 @@ class StoreBankTransactionRuleRequest extends Request 'rules' => 'bail|array', 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', - 'applies_to' => 'bail|sometimes|bool', + 'applies_to' => 'bail|sometimes|string', ]; if(isset($this->category_id)) diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index 8b45a061e4ae..d198a3892eae 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -36,7 +36,7 @@ class UpdateBankTransactionRuleRequest extends Request 'rules' => 'bail|array', 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', - 'applies_to' => 'bail|sometimes|bool', + 'applies_to' => 'bail|sometimes|string', ]; if(isset($this->category_id)) diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index 2096072c4619..f2d1e62d905d 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -32,6 +32,13 @@ class BankTransactionRule extends BaseModel 'category_id', ]; + protected $casts = [ + 'rules' => 'array', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + protected $dates = [ ]; diff --git a/tests/Feature/BankTransactionRuleApiTest.php b/tests/Feature/BankTransactionRuleApiTest.php index 30ff202c0644..e581788c659a 100644 --- a/tests/Feature/BankTransactionRuleApiTest.php +++ b/tests/Feature/BankTransactionRuleApiTest.php @@ -42,6 +42,91 @@ class BankTransactionRuleApiTest extends TestCase Model::reguard(); } +/* +$rules = [ + 'name' => 'bail|required|string', + 'rules' => 'bail|array', + 'auto_convert' => 'bail|sometimes|bool', + 'matches_on_all' => 'bail|sometimes|bool', + 'applies_to' => 'bail|sometimes|bool', +]; + +if(isset($this->category_id)) + $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + +if(isset($this->vendor_id)) + $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + +if(isset($this->client_id)) + $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; +*/ + public function testBankRulePost() + { + + $data = [ + 'name' => 'The First Rule', + 'rules' => [], + 'auto_convert' => false, + 'matches_on_all' => false, + 'applies_to' => 'CREDIT', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transaction_rules/', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals('The First Rule', $arr['data']['name']); + + } + + public function testBankRulePut() + { + + $data = [ + 'name' => 'The First Rule', + 'rules' => [], + 'auto_convert' => false, + 'matches_on_all' => false, + 'applies_to' => 'CREDIT', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transaction_rules/', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals('The First Rule', $arr['data']['name']); + + $data = [ + 'name' => 'A New Name For The First Rule', + 'rules' => [], + 'auto_convert' => false, + 'matches_on_all' => false, + 'applies_to' => 'CREDIT', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/bank_transaction_rules/'. $arr['data']['id'], $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals('A New Name For The First Rule', $arr['data']['name']); + + } + public function testBankTransactionRuleGet() { $response = $this->withHeaders([ From 9412760a25036992e009da7970f591d72d7a044d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 13:12:33 +1100 Subject: [PATCH 11/23] Matching Bank Transactions --- app/Models/BankTransaction.php | 1 + app/Models/BankTransactionRule.php | 112 +++++++------- app/Models/Company.php | 18 +++ app/Services/Bank/BankMatchingService.php | 175 +++++++++++++++++++++- 4 files changed, 241 insertions(+), 65 deletions(-) diff --git a/app/Models/BankTransaction.php b/app/Models/BankTransaction.php index 7092da3f89b1..6ccdd1250f0a 100644 --- a/app/Models/BankTransaction.php +++ b/app/Models/BankTransaction.php @@ -11,6 +11,7 @@ namespace App\Models; +use App\Models\BankTransactionRule; use App\Models\Filterable; use App\Models\Invoice; use App\Services\Bank\BankService; diff --git a/app/Models/BankTransactionRule.php b/app/Models/BankTransactionRule.php index f2d1e62d905d..b2ea0b015b64 100644 --- a/app/Models/BankTransactionRule.php +++ b/app/Models/BankTransactionRule.php @@ -42,13 +42,9 @@ class BankTransactionRule extends BaseModel protected $dates = [ ]; - /* Columns to search */ protected array $search_keys = [ - 'client_id' => 'client', - 'vendor_id' => 'vendor', - 'description' => 'description', - 'transaction_reference' => 'transaction_reference', - 'amount' => 'amount', + 'description' => 'string', + 'amount' => 'number', ]; /* Amount */ @@ -79,68 +75,68 @@ class BankTransactionRule extends BaseModel // } //] - public function processRule(BankTransaction $bank_transaction) - { - foreach($this->rules as $key => $rule) - { - $this->search($rule, $key, $bank_transaction); - } - } + // public function processRule(BankTransaction $bank_transaction) + // { + // foreach($this->rules as $key => $rule) + // { + // $this->search($rule, $key, $bank_transaction); + // } + // } - private function search($rule, $key, $bank_transaction) - { - if($rule->search_key == 'amount') - { - //number search - } - else { - //string search - } - } + // private function search($rule, $key, $bank_transaction) + // { + // if($rule->search_key == 'amount') + // { + // //number search + // } + // else { + // //string search + // } + // } - private function findAmount($amount, $bank_transaction) - { - if($bank_transaction->base_type == 'CREDIT'){ - //search invoices - } - else{ - //search expenses - } + // private function findAmount($amount, $bank_transaction) + // { + // if($bank_transaction->base_type == 'CREDIT'){ + // //search invoices + // } + // else{ + // //search expenses + // } - } + // } - private function searchClient($rule, $bank_transaction) - { - if($bank_transaction->base_type == 'CREDIT'){ - //search invoices - } - else{ - //search expenses - } + // private function searchClient($rule, $bank_transaction) + // { + // if($bank_transaction->base_type == 'CREDIT'){ + // //search invoices + // } + // else{ + // //search expenses + // } - } + // } - private function searchVendor($rule, $bank_transaction) - { - //search expenses + // private function searchVendor($rule, $bank_transaction) + // { + // //search expenses - } + // } - private function searchDescription($rule, $bank_transaction) - { - //search expenses public notes - } + // private function searchDescription($rule, $bank_transaction) + // { + // //search expenses public notes + // } - private function searchReference($rule, $bank_transaction) - { - if($bank_transaction->base_type == 'CREDIT'){ - //search invoices - } - else{ - //search expenses - } - } + // private function searchReference($rule, $bank_transaction) + // { + // if($bank_transaction->base_type == 'CREDIT'){ + // //search invoices + // } + // else{ + // //search expenses + // } + // } public function getEntityType() { diff --git a/app/Models/Company.php b/app/Models/Company.php index d878ee575b47..641add772796 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -13,6 +13,7 @@ namespace App\Models; use App\DataMapper\CompanySettings; use App\Models\BankTransaction; +use App\Models\BankTransactionRule; use App\Models\Language; use App\Models\Presenters\CompanyPresenter; use App\Models\PurchaseOrder; @@ -541,6 +542,23 @@ class Company extends BaseModel return $this->company_users()->withTrashed()->where('is_owner', true)->first()?->user; } + public function credit_rules() + { + return BankTransactionRule::query() + ->where('company_id', $this->id) + ->where('applies_to', 'CREDIT') + ->get(); + } + + public function debit_rules() + { + return BankTransactionRule::query() + ->where('company_id', $this->id) + ->where('applies_to', 'DEBIT') + ->get(); + } + + public function resolveRouteBinding($value, $field = null) { return $this->where('id', $this->decodePrimaryKey($value))->firstOrFail(); diff --git a/app/Services/Bank/BankMatchingService.php b/app/Services/Bank/BankMatchingService.php index b466a95cfcd8..c732a794c006 100644 --- a/app/Services/Bank/BankMatchingService.php +++ b/app/Services/Bank/BankMatchingService.php @@ -11,19 +11,26 @@ namespace App\Services\Bank; +use App\Factory\ExpenseCategoryFactory; +use App\Factory\ExpenseFactory; use App\Libraries\MultiDB; use App\Models\BankTransaction; use App\Models\Company; +use App\Models\ExpenseCategory; use App\Models\Invoice; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Cache; class BankMatchingService implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter; private $company_id; @@ -35,12 +42,23 @@ class BankMatchingService implements ShouldQueue public $deleteWhenMissingModels = true; + protected $credit_rules; + + protected $debit_rules; + + protected $categories; + public function __construct($company_id, $db) { $this->company_id = $company_id; $this->db = $db; } + public function middleware() + { + return [new WithoutOverlapping($this->company->company_key)]; + } + public function handle() { @@ -48,19 +66,162 @@ class BankMatchingService implements ShouldQueue $this->company = Company::find($this->company_id); + $this->categories = collect(Cache::get('bank_categories')); + + $this->matchCredits(); + + $this->matchDebits(); + + } + + private function matchDebits() + { + + $this->debit_rules = $this->company->debit_rules(); + + BankTransaction::where('company_id', $this->company->id) + ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->where('base_type', 'DEBIT') + ->cursor() + ->each(function ($bt){ + + $this->matchDebit($bt); + + }); + + } + + private function matchDebit(BankTransaction $bank_transaction) + { + $matches = 0; + + foreach($this->debit_rules as $rule) + { + $rule_count = count($this->debit_rules); + + if($rule['search_key'] == 'description') + { + + if($this->matchStringOperator($bank_transaction->description, 'description', $rule['operator'])){ + $matches++; + } + + } + + if($rule['search_key'] == 'amount') + { + + if($this->matchNumberOperator($bank_transaction->description, 'amount', $rule['operator'])){ + $matches++; + } + + } + + if(($rule['matches_on_all'] && ($matches == $rule_count)) || (!$rule['matches_on_all'] &&$matches > 0)) + { + + $bank_transaction->client_id = empty($rule['client_id']) ? null : $rule['client_id']; + $bank_transaction->vendor_id = empty($rule['vendor_id']) ? null : $rule['vendor_id']; + $bank_transaction->ninja_category_id = empty($rule['category_id']) ? null : $rule['category_id']; + $bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + $bank_transaction->save(); + + if($rule['auto_convert']) + { + + $expense = ExpenseFactory::create($bank_transaction->company_id, $bank_transaction->user_id); + $expense->category_id = $bank_transaction->ninja_category_id ?: $this->resolveCategory($bank_transaction); + $expense->amount = $bank_transaction->amount; + $expense->number = $this->getNextExpenseNumber($expense); + $expense->currency_id = $bank_transaction->currency_id; + $expense->date = Carbon::parse($bank_transaction->date); + $expense->payment_date = Carbon::parse($bank_transaction->date); + $expense->transaction_reference = $bank_transaction->description; + $expense->transaction_id = $bank_transaction->id; + $expense->vendor_id = $bank_transaction->vendor_id; + $expense->invoice_documents = $this->company->invoice_expense_documents; + $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; + $expense->save(); + + $bank_transaction->expense_id = $expense->id; + $bank_transaction->status_id = BankTransaction::STATUS_CONVERTED; + $bank_transaction->save(); + + break; + + } + + } + + } + + } + + private function resolveCategory(BankTransaction $bank_transaction) + { + $category = $this->categories->firstWhere('highLevelCategoryId', $bank_transaction->category_id); + + $ec = ExpenseCategory::where('company_id', $this->company->id)->where('bank_category_id', $bank_transaction->category_id)->first(); + + if($ec) + return $ec->id; + + if($category) + { + $ec = ExpenseCategoryFactory::create($bank_transaction->company_id, $bank_transaction->user_id); + $ec->bank_category_id = $bank_transaction->category_id; + $ec->name = $category->highLevelCategoryName; + $ec->save(); + + return $ec->id; + } + } + + private function matchNumberOperator($bt_value, $rule_value, $operator) :bool + { + + return match ($operator) { + '>' => floatval($bt_value) > floatval($rule_value), + '>=' => floatval($bt_value) >= floatval($rule_value), + '=' => floatval($bt_value) == floatval($rule_value), + '<' => floatval($bt_value) < floatval($rule_value), + '<=' => floatval($bt_value) <= floatval($rule_value), + default => false, + }; + + } + + private function matchStringOperator($bt_value, $rule_value, $operator) :bool + { + $bt_value = strtolower(str_replace(" ", "", $bt_value)); + $rule_value = strtolower(str_replace(" ", "", $rule_value)); + $rule_length = iconv_strlen($rule_value); + + return match ($operator) { + 'is' => $bt_value == $rule_value, + 'contains' => str_contains($bt_value, $rule_value), + 'starts_with' => substr($bt_value, 0, $rule_length) == $rule_value, + 'is_empty' => empty($bt_value), + default => false, + }; + + } + + + + /* Currently we don't consider rules here, only matching on invoice number*/ + private function matchCredits() + { + $this->credit_rules = $this->company->credit_rules(); + $this->invoices = Invoice::where('company_id', $this->company->id) ->whereIn('status_id', [1,2,3]) ->where('is_deleted', 0) ->get(); - $this->match(); - } - - private function match() - { - BankTransaction::where('company_id', $this->company->id) ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->where('base_type', 'CREDIT') ->cursor() ->each(function ($bt){ From 0efaf80ceef0c85b55af8f77e1789c7c20f3bfd1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 13:55:19 +1100 Subject: [PATCH 12/23] Tests for matching expenses --- .../BankTransactionRepository.php | 10 +- app/Services/Bank/BankMatchingService.php | 175 +------------- app/Services/Bank/BankService.php | 6 +- app/Services/Bank/ProcessBankRule.php | 27 --- app/Services/Bank/ProcessBankRules.php | 214 ++++++++++++++++++ .../Feature/Bank/BankTransactionRuleTest.php | 88 +++++++ 6 files changed, 312 insertions(+), 208 deletions(-) delete mode 100644 app/Services/Bank/ProcessBankRule.php create mode 100644 app/Services/Bank/ProcessBankRules.php create mode 100644 tests/Feature/Bank/BankTransactionRuleTest.php diff --git a/app/Repositories/BankTransactionRepository.php b/app/Repositories/BankTransactionRepository.php index 390b8f56ffb2..1a5bfba1662e 100644 --- a/app/Repositories/BankTransactionRepository.php +++ b/app/Repositories/BankTransactionRepository.php @@ -28,17 +28,11 @@ class BankTransactionRepository extends BaseRepository $bank_transaction->bank_integration_id = $data['bank_integration_id']; $bank_transaction->fill($data); - $bank_transaction->save(); - if($bank_transaction->base_type == 'CREDIT' && $invoice = $bank_transaction->service()->matchInvoiceNumber()) - { - $bank_transaction->invoice_ids = $invoice->hashed_id; - $bank_transaction->status_id = BankTransaction::STATUS_MATCHED; - $bank_transaction->save(); - } + $bank_transaction->service()->processRules(); - return $bank_transaction; + return $bank_transaction->fresh(); } } diff --git a/app/Services/Bank/BankMatchingService.php b/app/Services/Bank/BankMatchingService.php index c732a794c006..bd95296d4fa0 100644 --- a/app/Services/Bank/BankMatchingService.php +++ b/app/Services/Bank/BankMatchingService.php @@ -18,6 +18,7 @@ use App\Models\BankTransaction; use App\Models\Company; use App\Models\ExpenseCategory; use App\Models\Invoice; +use App\Services\Bank\BankService; use App\Utils\Traits\GeneratesCounter; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -42,23 +43,12 @@ class BankMatchingService implements ShouldQueue public $deleteWhenMissingModels = true; - protected $credit_rules; - - protected $debit_rules; - - protected $categories; - public function __construct($company_id, $db) { $this->company_id = $company_id; $this->db = $db; } - public function middleware() - { - return [new WithoutOverlapping($this->company->company_key)]; - } - public function handle() { @@ -66,180 +56,27 @@ class BankMatchingService implements ShouldQueue $this->company = Company::find($this->company_id); - $this->categories = collect(Cache::get('bank_categories')); - - $this->matchCredits(); + $this->matchTransactions(); - $this->matchDebits(); } - private function matchDebits() + private function matchTransactions() { - $this->debit_rules = $this->company->debit_rules(); - BankTransaction::where('company_id', $this->company->id) ->where('status_id', BankTransaction::STATUS_UNMATCHED) - ->where('base_type', 'DEBIT') ->cursor() ->each(function ($bt){ - $this->matchDebit($bt); + (new BankService($bt))->processRules(); }); } - private function matchDebit(BankTransaction $bank_transaction) + public function middleware() { - $matches = 0; - - foreach($this->debit_rules as $rule) - { - $rule_count = count($this->debit_rules); - - if($rule['search_key'] == 'description') - { - - if($this->matchStringOperator($bank_transaction->description, 'description', $rule['operator'])){ - $matches++; - } - - } - - if($rule['search_key'] == 'amount') - { - - if($this->matchNumberOperator($bank_transaction->description, 'amount', $rule['operator'])){ - $matches++; - } - - } - - if(($rule['matches_on_all'] && ($matches == $rule_count)) || (!$rule['matches_on_all'] &&$matches > 0)) - { - - $bank_transaction->client_id = empty($rule['client_id']) ? null : $rule['client_id']; - $bank_transaction->vendor_id = empty($rule['vendor_id']) ? null : $rule['vendor_id']; - $bank_transaction->ninja_category_id = empty($rule['category_id']) ? null : $rule['category_id']; - $bank_transaction->status_id = BankTransaction::STATUS_MATCHED; - $bank_transaction->save(); - - if($rule['auto_convert']) - { - - $expense = ExpenseFactory::create($bank_transaction->company_id, $bank_transaction->user_id); - $expense->category_id = $bank_transaction->ninja_category_id ?: $this->resolveCategory($bank_transaction); - $expense->amount = $bank_transaction->amount; - $expense->number = $this->getNextExpenseNumber($expense); - $expense->currency_id = $bank_transaction->currency_id; - $expense->date = Carbon::parse($bank_transaction->date); - $expense->payment_date = Carbon::parse($bank_transaction->date); - $expense->transaction_reference = $bank_transaction->description; - $expense->transaction_id = $bank_transaction->id; - $expense->vendor_id = $bank_transaction->vendor_id; - $expense->invoice_documents = $this->company->invoice_expense_documents; - $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; - $expense->save(); - - $bank_transaction->expense_id = $expense->id; - $bank_transaction->status_id = BankTransaction::STATUS_CONVERTED; - $bank_transaction->save(); - - break; - - } - - } - - } - + return [new WithoutOverlapping($this->company->company_key)]; } - - private function resolveCategory(BankTransaction $bank_transaction) - { - $category = $this->categories->firstWhere('highLevelCategoryId', $bank_transaction->category_id); - - $ec = ExpenseCategory::where('company_id', $this->company->id)->where('bank_category_id', $bank_transaction->category_id)->first(); - - if($ec) - return $ec->id; - - if($category) - { - $ec = ExpenseCategoryFactory::create($bank_transaction->company_id, $bank_transaction->user_id); - $ec->bank_category_id = $bank_transaction->category_id; - $ec->name = $category->highLevelCategoryName; - $ec->save(); - - return $ec->id; - } - } - - private function matchNumberOperator($bt_value, $rule_value, $operator) :bool - { - - return match ($operator) { - '>' => floatval($bt_value) > floatval($rule_value), - '>=' => floatval($bt_value) >= floatval($rule_value), - '=' => floatval($bt_value) == floatval($rule_value), - '<' => floatval($bt_value) < floatval($rule_value), - '<=' => floatval($bt_value) <= floatval($rule_value), - default => false, - }; - - } - - private function matchStringOperator($bt_value, $rule_value, $operator) :bool - { - $bt_value = strtolower(str_replace(" ", "", $bt_value)); - $rule_value = strtolower(str_replace(" ", "", $rule_value)); - $rule_length = iconv_strlen($rule_value); - - return match ($operator) { - 'is' => $bt_value == $rule_value, - 'contains' => str_contains($bt_value, $rule_value), - 'starts_with' => substr($bt_value, 0, $rule_length) == $rule_value, - 'is_empty' => empty($bt_value), - default => false, - }; - - } - - - - /* Currently we don't consider rules here, only matching on invoice number*/ - private function matchCredits() - { - $this->credit_rules = $this->company->credit_rules(); - - $this->invoices = Invoice::where('company_id', $this->company->id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->get(); - - BankTransaction::where('company_id', $this->company->id) - ->where('status_id', BankTransaction::STATUS_UNMATCHED) - ->where('base_type', 'CREDIT') - ->cursor() - ->each(function ($bt){ - - $invoice = $this->invoices->first(function ($value, $key) use ($bt){ - - return str_contains($bt->description, $value->number); - - }); - - if($invoice) - { - $bt->invoice_ids = $invoice->hashed_id; - $bt->status_id = BankTransaction::STATUS_MATCHED; - $bt->save(); - } - - }); - } - - } diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php index 7a7f50759191..5ff37a1ab6b1 100644 --- a/app/Services/Bank/BankService.php +++ b/app/Services/Bank/BankService.php @@ -40,11 +40,9 @@ class BankService } - public function processRule($rule) + public function processRules() { - (new ProcessBankRule($this->bank_transaction, $rule))->run(); - - return $this; + (new ProcessBankRules($this->bank_transaction))->run(); } } \ No newline at end of file diff --git a/app/Services/Bank/ProcessBankRule.php b/app/Services/Bank/ProcessBankRule.php deleted file mode 100644 index ffeedbb1f1fe..000000000000 --- a/app/Services/Bank/ProcessBankRule.php +++ /dev/null @@ -1,27 +0,0 @@ -bank_transaction->base_type == 'DEBIT') + $this->matchDebit(); + else + $this->matchCredit(); + } + + private function matchCredit() + { + $this->credit_rules = $this->bank_transaction->company->credit_rules(); + + $this->invoices = Invoice::where('company_id', $this->bank_transaction->company_id) + ->whereIn('status_id', [1,2,3]) + ->where('is_deleted', 0) + ->get(); + + $invoice = $this->invoices->first(function ($value, $key){ + + return str_contains($this->bank_transaction, $value->number); + + }); + + if($invoice) + { + $this->bank_transaction->invoice_ids = $invoice->hashed_id; + $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + $this->bank_transaction->save(); + return; + } + + //stub for credit rules + foreach($this->credit_rules as $rule) + { + + } + + } + + private function matchDebit() + { + + $this->debit_rules = $this->bank_transaction->company->debit_rules(); + + $this->categories = collect(Cache::get('bank_categories')); + + foreach($this->debit_rules as $bank_transaction_rule) + { + + $matches = 0; + + foreach($bank_transaction_rule['rules'] as $rule) + { + $rule_count = count($bank_transaction_rule['rules']); + + +nlog($rule_count); +nlog($rule); + + if($rule['search_key'] == 'description') + { + nlog("searching key"); + + if($this->matchStringOperator($this->bank_transaction->description, $rule['value'], $rule['operator'])){ + nlog("found key"); + $matches++; + } + + } + + if($rule['search_key'] == 'amount') + { + + if($this->matchNumberOperator($this->bank_transaction->description, 'amount', $rule['operator'])){ + $matches++; + } + + } + + if(($bank_transaction_rule['matches_on_all'] && ($matches == $rule_count)) || (!$bank_transaction_rule['matches_on_all'] && $matches > 0)) + { + + // $this->bank_transaction->client_id = empty($rule['client_id']) ? null : $rule['client_id']; + $this->bank_transaction->vendor_id = empty($rule['vendor_id']) ? null : $rule['vendor_id']; + $this->bank_transaction->ninja_category_id = empty($rule['category_id']) ? null : $rule['category_id']; + $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + $this->bank_transaction->save(); + + if($bank_transaction_rule['auto_convert']) + { + + $expense = ExpenseFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id); + $expense->category_id = $this->bank_transaction->ninja_category_id ?: $this->resolveCategory(); + $expense->amount = $this->bank_transaction->amount; + $expense->number = $this->getNextExpenseNumber($expense); + $expense->currency_id = $this->bank_transaction->currency_id; + $expense->date = Carbon::parse($this->bank_transaction->date); + $expense->payment_date = Carbon::parse($this->bank_transaction->date); + $expense->transaction_reference = $this->bank_transaction->description; + $expense->transaction_id = $this->bank_transaction->id; + $expense->vendor_id = $this->bank_transaction->vendor_id; + $expense->invoice_documents = $this->bank_transaction->company->invoice_expense_documents; + $expense->should_be_invoiced = $this->bank_transaction->company->mark_expenses_invoiceable; + $expense->save(); + + $this->bank_transaction->expense_id = $expense->id; + $this->bank_transaction->status_id = BankTransaction::STATUS_CONVERTED; + $this->bank_transaction->save(); + + break; + + } + + } + } + + } + + } + + private function resolveCategory() + { + $category = $this->categories->firstWhere('highLevelCategoryId', $this->bank_transaction->category_id); + + $ec = ExpenseCategory::where('company_id', $this->bank_transaction->company_id)->where('bank_category_id', $this->bank_transaction->category_id)->first(); + + if($ec) + return $ec->id; + + if($category) + { + $ec = ExpenseCategoryFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id); + $ec->bank_category_id = $this->bank_transaction->category_id; + $ec->name = $category->highLevelCategoryName; + $ec->save(); + + return $ec->id; + } + } + + private function matchNumberOperator($bt_value, $rule_value, $operator) :bool + { + + return match ($operator) { + '>' => floatval($bt_value) > floatval($rule_value), + '>=' => floatval($bt_value) >= floatval($rule_value), + '=' => floatval($bt_value) == floatval($rule_value), + '<' => floatval($bt_value) < floatval($rule_value), + '<=' => floatval($bt_value) <= floatval($rule_value), + default => false, + }; + + } + + private function matchStringOperator($bt_value, $rule_value, $operator) :bool + { + $bt_value = strtolower(str_replace(" ", "", $bt_value)); + $rule_value = strtolower(str_replace(" ", "", $rule_value)); + $rule_length = iconv_strlen($rule_value); + +nlog($bt_value); +nlog($rule_value); +nlog($rule_length); +nlog($operator); + + return match ($operator) { + 'is' => $bt_value == $rule_value, + 'contains' => str_contains($bt_value, $rule_value), + 'starts_with' => substr($bt_value, 0, $rule_length) == $rule_value, + 'is_empty' => empty($bt_value), + default => false, + }; + + } + + + + +} \ No newline at end of file diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php new file mode 100644 index 000000000000..8981bc0bf0ce --- /dev/null +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -0,0 +1,88 @@ +makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testMatchingBankTransactionExpense() + { + // $this->expense->public_notes = "WaLLaBy"; + // $this->expense->save(); + + // $this->assertEquals('WaLLaBy', $this->expense->public_notes); + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'is', + 'value' => 'wallaby', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'WallABy', + 'base_type' => 'DEBIT', + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + +} \ No newline at end of file From b2dee8dd35dc44dbf80509225e1ae2f04532ebdb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 14:19:35 +1100 Subject: [PATCH 13/23] Tests for rules --- app/Services/Bank/ProcessBankRules.php | 14 +- .../Feature/Bank/BankTransactionRuleTest.php | 272 +++++++++++++++++- 2 files changed, 271 insertions(+), 15 deletions(-) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index 3bfef47086a2..ef21459245ee 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -43,6 +43,7 @@ class ProcessBankRules extends AbstractService private function matchCredit() { + $this->credit_rules = $this->bank_transaction->company->credit_rules(); $this->invoices = Invoice::where('company_id', $this->bank_transaction->company_id) @@ -52,7 +53,7 @@ class ProcessBankRules extends AbstractService $invoice = $this->invoices->first(function ($value, $key){ - return str_contains($this->bank_transaction, $value->number); + return str_contains($this->bank_transaction->description, $value->number); }); @@ -88,10 +89,6 @@ class ProcessBankRules extends AbstractService { $rule_count = count($bank_transaction_rule['rules']); - -nlog($rule_count); -nlog($rule); - if($rule['search_key'] == 'description') { nlog("searching key"); @@ -193,14 +190,9 @@ nlog($rule); $rule_value = strtolower(str_replace(" ", "", $rule_value)); $rule_length = iconv_strlen($rule_value); -nlog($bt_value); -nlog($rule_value); -nlog($rule_length); -nlog($operator); - return match ($operator) { 'is' => $bt_value == $rule_value, - 'contains' => str_contains($bt_value, $rule_value), + 'contains' => stripos($bt_value, $rule_value) !== false, 'starts_with' => substr($bt_value, 0, $rule_length) == $rule_value, 'is_empty' => empty($bt_value), default => false, diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index 8981bc0bf0ce..21567187604e 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -39,12 +39,227 @@ class BankTransactionRuleTest extends TestCase ); } + + public function testMatchingBankTransactionExpenseStartsWithMiss() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'starts_with', + 'value' => 'chesst', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'ChESSSty coughs are terrible', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNull($bt->expense_id); + } + + + + public function testMatchingBankTransactionExpenseStartsWith() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'starts_with', + 'value' => 'chess', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'ChESSSty coughs are terrible', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + + public function testMatchingBankTransactionExpenseContainsMiss() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'contains', + 'value' => 'asdddfd', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'Something asd bizarre', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNull($bt->expense_id); + } + + + public function testMatchingBankTransactionExpenseContains() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'contains', + 'value' => 'asd', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'Something asd bizarre', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + public function testMatchingBankTransactionExpenseMiss() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'is', + 'value' => 'wallaby', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'Wall', + 'base_type' => 'DEBIT', + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNull($bt->expense_id); + } + public function testMatchingBankTransactionExpense() { - // $this->expense->public_notes = "WaLLaBy"; - // $this->expense->save(); - - // $this->assertEquals('WaLLaBy', $this->expense->public_notes); $br = BankTransactionRule::factory()->create([ 'company_id' => $this->company->id, @@ -85,4 +300,53 @@ class BankTransactionRuleTest extends TestCase $this->assertNotNull($bt->expense_id); } + + public function testMatchingBankTransactionInvoice() + { + + $this->invoice->number = "MUHMUH"; + $this->invoice->save(); + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'CREDIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'is', + 'value' => 'MUHMUH', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'MUHMUH', + 'base_type' => 'CREDIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertEquals(BankTransaction::STATUS_MATCHED, $bt->status_id); + } + + + } \ No newline at end of file From 054be4a8acc30e743dd5a344a0711d9fa77ad7c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 14:21:35 +1100 Subject: [PATCH 14/23] Transaction rules tests --- .../Feature/Bank/BankTransactionRuleTest.php | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index 21567187604e..8ac6354eb58f 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -40,6 +40,96 @@ class BankTransactionRuleTest extends TestCase } + + + + public function testMatchingBankTransactionExpenseIsEmpty() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'is_empty', + 'value' => '', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + public function testMatchingBankTransactionExpenseIsEmptyMiss() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'is_empty', + 'value' => '', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'asdadsa', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNull($bt->expense_id); + } + + public function testMatchingBankTransactionExpenseStartsWithMiss() { From 4c72663940f5e109c3b5be96925de433f1fed63e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 14:31:30 +1100 Subject: [PATCH 15/23] Bank Transaction rule tests --- app/Services/Bank/ProcessBankRules.php | 4 +- .../Feature/Bank/BankTransactionRuleTest.php | 214 ++++++++++++++++++ 2 files changed, 215 insertions(+), 3 deletions(-) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index ef21459245ee..bb7591c53a05 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -91,10 +91,8 @@ class ProcessBankRules extends AbstractService if($rule['search_key'] == 'description') { - nlog("searching key"); if($this->matchStringOperator($this->bank_transaction->description, $rule['value'], $rule['operator'])){ - nlog("found key"); $matches++; } @@ -103,7 +101,7 @@ class ProcessBankRules extends AbstractService if($rule['search_key'] == 'amount') { - if($this->matchNumberOperator($this->bank_transaction->description, 'amount', $rule['operator'])){ + if($this->matchNumberOperator($this->bank_transaction->amount, $rule['value'] , $rule['operator'])){ $matches++; } diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index 8ac6354eb58f..f00cecbd20ee 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -39,9 +39,223 @@ class BankTransactionRuleTest extends TestCase ); } + public function testMatchingBankTransactionExpenseAmountLessThanEqualTo() + { + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '<=', + 'value' => 100, + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + + public function testMatchingBankTransactionExpenseAmountLessThan() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '<', + 'value' => 100, + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 99 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + public function testMatchingBankTransactionExpenseAmountGreaterThan() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '>', + 'value' => 100, + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 101 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + + + public function testMatchingBankTransactionExpenseAmountMiss() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '=', + 'value' => 100, + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 101 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNull($bt->expense_id); + } + + public function testMatchingBankTransactionExpenseAmount() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '=', + 'value' => 100, + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => '', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + } + public function testMatchingBankTransactionExpenseIsEmpty() { From d046989e84b08f2438f41cb73011d346ece58339 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 20 Nov 2022 17:00:22 +1100 Subject: [PATCH 16/23] Minor fixes for bank services, executing matching after imports --- app/Import/Providers/Csv.php | 3 +++ app/Services/Bank/BankMatchingService.php | 12 ++---------- app/Services/Bank/BankService.php | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index ca100364f29f..ecc1f15dce7e 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -46,6 +46,7 @@ use App\Repositories\PaymentRepository; use App\Repositories\ProductRepository; use App\Repositories\QuoteRepository; use App\Repositories\VendorRepository; +use App\Services\Bank\BankMatchingService; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Validator; use Symfony\Component\HttpFoundation\ParameterBag; @@ -107,6 +108,8 @@ class Csv extends BaseImport implements ImportInterface $bank_transaction_count = $this->ingest($data, $entity_type); $this->entity_count['bank_transactions'] = $bank_transaction_count; + BankMatchingService::dispatchSync($this->company->id, $this->company->db); + } public function client() diff --git a/app/Services/Bank/BankMatchingService.php b/app/Services/Bank/BankMatchingService.php index bd95296d4fa0..4cdb3415c6af 100644 --- a/app/Services/Bank/BankMatchingService.php +++ b/app/Services/Bank/BankMatchingService.php @@ -33,21 +33,13 @@ class BankMatchingService implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter; - private $company_id; - private Company $company; - private $db; - private $invoices; public $deleteWhenMissingModels = true; - public function __construct($company_id, $db) - { - $this->company_id = $company_id; - $this->db = $db; - } + public function __construct(private int $company_id, private string $db){} public function handle() { @@ -77,6 +69,6 @@ class BankMatchingService implements ShouldQueue public function middleware() { - return [new WithoutOverlapping($this->company->company_key)]; + return [new WithoutOverlapping($this->company_id)]; } } diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php index 5ff37a1ab6b1..3cb00fdb282c 100644 --- a/app/Services/Bank/BankService.php +++ b/app/Services/Bank/BankService.php @@ -13,7 +13,7 @@ namespace App\Services\Bank; use App\Models\BankTransaction; use App\Models\Invoice; -use App\Services\Bank\ProcessBankRule; +use App\Services\Bank\ProcessBankRules; class BankService { From 4b4e023cb345482fae765fef077cf5a9218a4d6d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 21 Nov 2022 21:49:33 +1100 Subject: [PATCH 17/23] Add rules to company transformer --- app/Models/Company.php | 5 +++++ app/Transformers/CompanyTransformer.php | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/Models/Company.php b/app/Models/Company.php index 641add772796..bb705a82841d 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -189,6 +189,11 @@ class Company extends BaseModel return $this->hasMany(BankTransaction::class); } + public function bank_transaction_rules() + { + return $this->hasMany(BankTransactionRule::class); + } + public function getCompanyIdAttribute() { return $this->encodePrimaryKey($this->id); diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 45ef466f87d2..a38aaed32194 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -43,6 +43,7 @@ use App\Models\TaxRate; use App\Models\User; use App\Models\Webhook; use App\Transformers\BankIntegrationTransformer; +use App\Transformers\BankTransactionRuleTransformer; use App\Transformers\BankTransactionTransformer; use App\Transformers\PurchaseOrderTransformer; use App\Transformers\RecurringExpenseTransformer; @@ -104,6 +105,7 @@ class CompanyTransformer extends EntityTransformer 'purchase_orders', 'bank_integrations', 'bank_transactions', + 'bank_transaction_rules', ]; /** @@ -231,6 +233,14 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->bank_transactions, $transformer, BankTransaction::class); } + + public function includeBankTransactionRules(Company $company) + { + $transformer = new BankTransactionRuleTransformer($this->serializer); + + return $this->includeCollection($company->bank_transaction_rules, $transformer, BankTransactionRule::class); + } + public function includeBankIntegrations(Company $company) { $transformer = new BankIntegrationTransformer($this->serializer); From b3fefb3ac8ae27607cc00ef18f54f5f587c052b5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Nov 2022 07:10:17 +1100 Subject: [PATCH 18/23] Minor fixes for validation rules --- .../UpdateBankTransactionRuleRequest.php | 2 +- .../Feature/Bank/BankTransactionRuleTest.php | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index d198a3892eae..d5f7baa327a7 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -40,7 +40,7 @@ class UpdateBankTransactionRuleRequest extends Request ]; if(isset($this->category_id)) - $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; if(isset($this->vendor_id)) $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index f00cecbd20ee..a11b44b96e92 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -39,6 +39,53 @@ class BankTransactionRuleTest extends TestCase ); } + public function testUpdateValidationRules() + { + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'rules' => [ + [ + 'search_key' => 'amount', + 'operator' => '<=', + 'value' => 100, + ] + ] + ]); + + + $data = [ + "applies_to" => "DEBIT", + "archived_at" => 0, + "auto_convert" => False, + "category_id" => $this->expense_category, + "is_deleted" => False, + "isChanged" => True, + "matches_on_all" => True, + "name" => "TEST 22", + "updated_at" => 1669060432, + "vendor_id" => $this->vendor->hashed_id + ]; + + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/bank_transaction_rules/'. $br->hashed_id, $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + } + public function testMatchingBankTransactionExpenseAmountLessThanEqualTo() { From 3fa0373abb5d0a4f0969666add34dbb94ca612f7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Nov 2022 07:14:00 +1100 Subject: [PATCH 19/23] bank rule testS --- .../Feature/Bank/BankTransactionRuleTest.php | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index a11b44b96e92..bd51bdf966f5 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -19,6 +19,7 @@ use App\Models\BankTransaction; use App\Models\BankTransactionRule; use App\Models\Invoice; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Validation\ValidationException; use Tests\MockAccountData; use Tests\TestCase; @@ -37,6 +38,8 @@ class BankTransactionRuleTest extends TestCase $this->withoutMiddleware( ThrottleRequests::class ); + + $this->withoutExceptionHandling(); } public function testUpdateValidationRules() @@ -64,7 +67,7 @@ class BankTransactionRuleTest extends TestCase "applies_to" => "DEBIT", "archived_at" => 0, "auto_convert" => False, - "category_id" => $this->expense_category, + "category_id" => $this->expense_category->hashed_id, "is_deleted" => False, "isChanged" => True, "matches_on_all" => True, @@ -73,16 +76,25 @@ class BankTransactionRuleTest extends TestCase "vendor_id" => $this->vendor->hashed_id ]; + $response = null; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->putJson('/api/v1/bank_transaction_rules/'. $br->hashed_id, $data); + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/bank_transaction_rules/'. $br->hashed_id, $data); - $arr = $response->json(); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($message); + } - $response->assertStatus(200); + if($response){ + $arr = $response->json(); + + $response->assertStatus(200); + } } From 9091951f4191a623fedb39c20a6deb0794d180cf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Nov 2022 08:42:53 +1100 Subject: [PATCH 20/23] Include bank transaction rules to first load --- app/Http/Controllers/BaseController.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 56400e7e1285..068168876a04 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -108,6 +108,7 @@ class BaseController extends Controller 'company.system_logs', 'company.bank_integrations', 'company.bank_transactions', + 'company.bank_transaction_rules', ]; private $mini_load = [ @@ -126,6 +127,7 @@ class BaseController extends Controller 'company.expense_categories', 'company.subscriptions', 'company.bank_integrations', + 'company.bank_transaction_rules', ]; public function __construct() @@ -456,6 +458,13 @@ class BaseController extends Controller $query->where('bank_transactions.user_id', $user->id); } }, + 'company.bank_transaction_rules'=> function ($query) use ($updated_at, $user) { + $query->where('updated_at', '>=', $updated_at); + + if (! $user->isAdmin()) { + $query->where('bank_transaction_rules.user_id', $user->id); + } + }, ] ); @@ -530,6 +539,12 @@ class BaseController extends Controller $query->where('bank_integrations.user_id', $user->id); } }, + 'company.bank_transaction_rules'=> function ($query) use ($updated_at, $user) { + + if (! $user->isAdmin()) { + $query->where('bank_transaction_rules.user_id', $user->id); + } + }, ] ); From 5223757c5256458460c50cc2a145b7bacf0bc2b0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Nov 2022 12:57:03 +1100 Subject: [PATCH 21/23] Add bank rule_id to bank transactions --- app/Services/Bank/ProcessBankRules.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index bb7591c53a05..686ee2cb0dde 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -68,6 +68,7 @@ class ProcessBankRules extends AbstractService //stub for credit rules foreach($this->credit_rules as $rule) { + // $this->bank_transaction->bank_rule_id = $bank_transaction_rule->id; } @@ -114,6 +115,7 @@ class ProcessBankRules extends AbstractService $this->bank_transaction->vendor_id = empty($rule['vendor_id']) ? null : $rule['vendor_id']; $this->bank_transaction->ninja_category_id = empty($rule['category_id']) ? null : $rule['category_id']; $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + $this->bank_transaction->bank_rule_id = $bank_transaction_rule->id; $this->bank_transaction->save(); if($bank_transaction_rule['auto_convert']) From 08630874b87cf32866ad1634b1aa67aa6a28c2cf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Nov 2022 22:14:36 +1100 Subject: [PATCH 22/23] Fixes for rules --- app/Services/Bank/ProcessBankRules.php | 8 +-- .../Feature/Bank/BankTransactionRuleTest.php | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index 686ee2cb0dde..7efb219b589a 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -112,8 +112,8 @@ class ProcessBankRules extends AbstractService { // $this->bank_transaction->client_id = empty($rule['client_id']) ? null : $rule['client_id']; - $this->bank_transaction->vendor_id = empty($rule['vendor_id']) ? null : $rule['vendor_id']; - $this->bank_transaction->ninja_category_id = empty($rule['category_id']) ? null : $rule['category_id']; + $this->bank_transaction->vendor_id = $bank_transaction_rule->vendor_id; + $this->bank_transaction->ninja_category_id = $bank_transaction_rule->category_id; $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; $this->bank_transaction->bank_rule_id = $bank_transaction_rule->id; $this->bank_transaction->save(); @@ -122,7 +122,7 @@ class ProcessBankRules extends AbstractService { $expense = ExpenseFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id); - $expense->category_id = $this->bank_transaction->ninja_category_id ?: $this->resolveCategory(); + $expense->category_id = $bank_transaction_rule->category_id ?: $this->resolveCategory(); $expense->amount = $this->bank_transaction->amount; $expense->number = $this->getNextExpenseNumber($expense); $expense->currency_id = $this->bank_transaction->currency_id; @@ -130,7 +130,7 @@ class ProcessBankRules extends AbstractService $expense->payment_date = Carbon::parse($this->bank_transaction->date); $expense->transaction_reference = $this->bank_transaction->description; $expense->transaction_id = $this->bank_transaction->id; - $expense->vendor_id = $this->bank_transaction->vendor_id; + $expense->vendor_id = $bank_transaction_rule->vendor_id; $expense->invoice_documents = $this->bank_transaction->company->invoice_expense_documents; $expense->should_be_invoiced = $this->bank_transaction->company->mark_expenses_invoiceable; $expense->save(); diff --git a/tests/Feature/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php index bd51bdf966f5..3be7ce68538e 100644 --- a/tests/Feature/Bank/BankTransactionRuleTest.php +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -42,6 +42,57 @@ class BankTransactionRuleTest extends TestCase $this->withoutExceptionHandling(); } + public function testValidationContainsRule() + { + //[{"search_key":"description","operator":"contains","value":"hello"}] + + + $br = BankTransactionRule::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'matches_on_all' => false, + 'auto_convert' => true, + 'applies_to' => 'DEBIT', + 'client_id' => $this->client->id, + 'vendor_id' => $this->vendor->id, + 'category_id' =>$this->expense_category->id, + 'rules' => [ + [ + 'search_key' => 'description', + 'operator' => 'contains', + 'value' => 'hello', + ] + ] + ]); + + $bi = BankIntegration::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'account_id' => $this->account->id, + ]); + + $bt = BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'description' => 'HellO ThErE CowBoY', + 'base_type' => 'DEBIT', + 'amount' => 100 + ]); + + + $bt->service()->processRules(); + + $bt = $bt->fresh(); + + $this->assertNotNull($bt->expense_id); + $this->assertNotNull($bt->expense->category_id); + $this->assertNotNull($bt->expense->vendor_id); + + + } + + public function testUpdateValidationRules() { From d34337edb509c4f6be612d21aec5d2defa364f75 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Nov 2022 08:05:08 +1100 Subject: [PATCH 23/23] Minor fixes for rules --- .../StoreBankTransactionRuleRequest.php | 5 ++- .../UpdateBankTransactionRuleRequest.php | 3 ++ tests/Feature/BankTransactionRuleApiTest.php | 43 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index f62b7414c710..138ca1277ef2 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -35,13 +35,16 @@ class StoreBankTransactionRuleRequest extends Request $rules = [ 'name' => 'bail|required|string', 'rules' => 'bail|array', + 'rules.*.operator' => 'bail|required|nullable', + 'rules.*.search_key' => 'bail|required|nullable', + 'rules.*.value' => 'bail|required|nullable', 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', 'applies_to' => 'bail|sometimes|string', ]; if(isset($this->category_id)) - $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; if(isset($this->vendor_id)) $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; diff --git a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php index d5f7baa327a7..535b75525ecc 100644 --- a/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -34,6 +34,9 @@ class UpdateBankTransactionRuleRequest extends Request $rules = [ 'name' => 'bail|required|string', 'rules' => 'bail|array', + 'rules.*.operator' => 'bail|required|nullable', + 'rules.*.search_key' => 'bail|required|nullable', + 'rules.*.value' => 'bail|required|nullable', 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', 'applies_to' => 'bail|sometimes|string', diff --git a/tests/Feature/BankTransactionRuleApiTest.php b/tests/Feature/BankTransactionRuleApiTest.php index e581788c659a..ec38d470c9fc 100644 --- a/tests/Feature/BankTransactionRuleApiTest.php +++ b/tests/Feature/BankTransactionRuleApiTest.php @@ -60,6 +60,49 @@ if(isset($this->vendor_id)) if(isset($this->client_id)) $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; */ + public function testBankRuleCategoryIdValidation() + { + $data = [ + 'name' => 'The First Rule', + 'rules' => [ + [ + "operator" => "contains", + "search_key" => "description", + "value" => "mobile" + ], + ], + 'assigned_user_id' => null, + 'auto_convert' => false, + 'matches_on_all' => true, + 'applies_to' => 'DEBIT', + 'category_id' => $this->expense_category->hashed_id, + 'vendor_id' => $this->vendor->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transaction_rules/', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals('DEBIT', $arr['data']['applies_to']); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/bank_transaction_rules/'. $arr['data']['id'], $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals('DEBIT', $arr['data']['applies_to']); + } + public function testBankRulePost() {