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..5e16a645e7e0 --- /dev/null +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -0,0 +1,505 @@ +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/BankTransactionRule"), + * ), + * @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/BankTransactionRule"), + * ), + * @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/BankTransactionRule"), + * ), + * @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/BankTransactionRule"), + * ), + * @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/BankTransactionRule"), + * ), + * @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/BankTransactionRule"), + * ), + * @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/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); + } + }, ] ); 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..522b1764b265 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BankTransactionRule.php @@ -0,0 +1,25 @@ +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..138ca1277ef2 --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -0,0 +1,70 @@ +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', + '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,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'; + + 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..535b75525ecc --- /dev/null +++ b/app/Http/Requests/BankTransactionRule/UpdateBankTransactionRuleRequest.php @@ -0,0 +1,67 @@ +user()->can('edit', $this->bank_transaction_rule); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $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,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'; + + 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/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/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/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 new file mode 100644 index 000000000000..b2ea0b015b64 --- /dev/null +++ b/app/Models/BankTransactionRule.php @@ -0,0 +1,171 @@ + 'array', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + + protected $dates = [ + ]; + + protected array $search_keys = [ + 'description' => 'string', + 'amount' => 'number', + ]; + + /* Amount */ + protected array $number_operators = [ + '=', + '>', + '>=', + '<', + '<=' + ]; + + /* Description, Client, Vendor, Reference Number */ + protected array $string_operators = [ + 'is', + 'contains', + 'starts_with', + 'is_empty', + ]; + + private array $search_results = []; + + // rule object looks like this: + //[ + // { + // 'search_key': 'client_id', + // 'operator' : 'is', + // '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; + } + + public function company() + { + return $this->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/Models/Company.php b/app/Models/Company.php index 181f4dcf5ac5..407cdb5ce827 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; @@ -189,6 +190,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); @@ -542,6 +548,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/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/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/Repositories/BankTransactionRuleRepository.php b/app/Repositories/BankTransactionRuleRepository.php new file mode 100644 index 000000000000..e5116ca6c0f6 --- /dev/null +++ b/app/Repositories/BankTransactionRuleRepository.php @@ -0,0 +1,35 @@ +fill($data); + + $bank_transaction_rule->save(); + + return $bank_transaction_rule; + + } + +} diff --git a/app/Services/Bank/BankMatchingService.php b/app/Services/Bank/BankMatchingService.php index b466a95cfcd8..4cdb3415c6af 100644 --- a/app/Services/Bank/BankMatchingService.php +++ b/app/Services/Bank/BankMatchingService.php @@ -11,35 +11,35 @@ 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\Services\Bank\BankService; +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; - - private $company_id; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter; 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() { @@ -48,37 +48,27 @@ class BankMatchingService implements ShouldQueue $this->company = Company::find($this->company_id); - $this->invoices = Invoice::where('company_id', $this->company->id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->get(); + $this->matchTransactions(); + - $this->match(); } - private function match() + private function matchTransactions() { - + BankTransaction::where('company_id', $this->company->id) - ->where('status_id', BankTransaction::STATUS_UNMATCHED) - ->cursor() - ->each(function ($bt){ - - $invoice = $this->invoices->first(function ($value, $key) use ($bt){ + ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->cursor() + ->each(function ($bt){ + + (new BankService($bt))->processRules(); - return str_contains($bt->description, $value->number); - - }); + }); - if($invoice) - { - $bt->invoice_ids = $invoice->hashed_id; - $bt->status_id = BankTransaction::STATUS_MATCHED; - $bt->save(); - } - - }); } - + public function middleware() + { + return [new WithoutOverlapping($this->company_id)]; + } } diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php index 7a7f50759191..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 { @@ -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->description, $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) + { + // $this->bank_transaction->bank_rule_id = $bank_transaction_rule->id; + + } + + } + + 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']); + + if($rule['search_key'] == 'description') + { + + if($this->matchStringOperator($this->bank_transaction->description, $rule['value'], $rule['operator'])){ + $matches++; + } + + } + + if($rule['search_key'] == 'amount') + { + + if($this->matchNumberOperator($this->bank_transaction->amount, $rule['value'] , $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 = $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(); + + if($bank_transaction_rule['auto_convert']) + { + + $expense = ExpenseFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id); + $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; + $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 = $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(); + + $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); + + return match ($operator) { + 'is' => $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, + }; + + } + + + + +} \ No newline at end of file diff --git a/app/Transformers/BankTransactionRuleTransformer.php b/app/Transformers/BankTransactionRuleTransformer.php new file mode 100644 index 000000000000..cb8221c7312e --- /dev/null +++ b/app/Transformers/BankTransactionRuleTransformer.php @@ -0,0 +1,90 @@ + (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, + '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/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 1cf9975f4991..0651bc1eb7c6 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', ]; /** @@ -232,6 +234,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); 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/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..7609d1a7d3fd --- /dev/null +++ b/database/migrations/2022_11_13_034143_bank_transaction_rules_table.php @@ -0,0 +1,52 @@ +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->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() + { + // + } +}; diff --git a/routes/api.php b/routes/api.php index 6eaabb51f1c0..d7d0632a73d1 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/Bank/BankTransactionRuleTest.php b/tests/Feature/Bank/BankTransactionRuleTest.php new file mode 100644 index 000000000000..3be7ce68538e --- /dev/null +++ b/tests/Feature/Bank/BankTransactionRuleTest.php @@ -0,0 +1,766 @@ +makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $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() + { + + $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->hashed_id, + "is_deleted" => False, + "isChanged" => True, + "matches_on_all" => True, + "name" => "TEST 22", + "updated_at" => 1669060432, + "vendor_id" => $this->vendor->hashed_id + ]; + + $response = null; + + + 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); + + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($message); + } + + if($response){ + $arr = $response->json(); + + $response->assertStatus(200); + } + + } + + 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() + { + + $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() + { + + $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() + { + + $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); + } + + + 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 diff --git a/tests/Feature/BankTransactionRuleApiTest.php b/tests/Feature/BankTransactionRuleApiTest.php new file mode 100644 index 000000000000..ec38d470c9fc --- /dev/null +++ b/tests/Feature/BankTransactionRuleApiTest.php @@ -0,0 +1,230 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + 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 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() + { + + $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([ + '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);