mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 08:57:34 -04:00 
			
		
		
		
	Merge pull request #7977 from turbo124/v5-develop
Bank Transaction Rules
This commit is contained in:
		
						commit
						cec5bfafe0
					
				| @ -22,6 +22,8 @@ use App\Jobs\Company\CreateCompanyTaskStatuses; | ||||
| use App\Jobs\Ninja\CompanySizeCheck; | ||||
| use App\Jobs\Util\VersionCheck; | ||||
| use App\Models\Account; | ||||
| use App\Models\BankIntegration; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\Client; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Company; | ||||
| @ -223,6 +225,18 @@ class DemoMode extends Command | ||||
|             'company_id' => $company->id, | ||||
|         ]); | ||||
| 
 | ||||
|         $bi = BankIntegration::factory()->create([ | ||||
|             'account_id' => $account->id, | ||||
|             'company_id' => $company->id, | ||||
|             'user_id' => $user->id, | ||||
|         ]); | ||||
| 
 | ||||
|         BankTransaction::factory()->count(50)->create([ | ||||
|             'bank_integration_id' => $bi->id, | ||||
|             'user_id' => $user->id, | ||||
|             'company_id' => $company->id, | ||||
|         ]); | ||||
| 
 | ||||
|         $this->info('Creating '.$this->count.' clients'); | ||||
| 
 | ||||
|         for ($x = 0; $x < $this->count; $x++) { | ||||
|  | ||||
| @ -182,7 +182,7 @@ class Handler extends ExceptionHandler | ||||
|         } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) { | ||||
|             return response()->json(['message'=>'Fatal error'], 500); | ||||
|         } elseif ($exception instanceof AuthorizationException) { | ||||
|             return response()->json(['message'=>'You are not authorized to view or perform this action'], 401); | ||||
|             return response()->json(['message'=> $exception->getMessage()], 401); | ||||
|         } elseif ($exception instanceof TokenMismatchException) { | ||||
|             return redirect() | ||||
|                     ->back() | ||||
|  | ||||
							
								
								
									
										30
									
								
								app/Factory/BankTransactionRuleFactory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/Factory/BankTransactionRuleFactory.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Factory; | ||||
| 
 | ||||
| use App\Models\BankTransactionRule; | ||||
| use Illuminate\Support\Str; | ||||
| 
 | ||||
| class BankTransactionRuleFactory | ||||
| { | ||||
|     public static function create(int $company_id, int $user_id) :BankTransactionRule | ||||
|     { | ||||
|         $bank_transaction_rule = new BankTransactionRule; | ||||
|         $bank_transaction_rule->user_id = $user_id; | ||||
|         $bank_transaction_rule->company_id = $company_id; | ||||
| 
 | ||||
|         $bank_transaction_rule->name = ''; | ||||
|         $bank_transaction_rule->rules = []; | ||||
|          | ||||
|         return $bank_transaction_rule; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										133
									
								
								app/Filters/BankTransactionRuleFilters.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								app/Filters/BankTransactionRuleFilters.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\BankTransactionRule; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * BankTransactionRuleilters. | ||||
|  */ | ||||
| class BankTransactionRuleFilters extends QueryFilters | ||||
| { | ||||
|     /** | ||||
|      * Filter by name. | ||||
|      * | ||||
|      * @param string $name | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function name(string $name = ''): Builder | ||||
|     { | ||||
|         if(strlen($name) >=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(); | ||||
|     } | ||||
| } | ||||
| @ -12,6 +12,7 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -19,6 +20,8 @@ use Illuminate\Database\Eloquent\Builder; | ||||
|  */ | ||||
| class TaskFilters extends QueryFilters | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     /** | ||||
|      * Filter based on search text. | ||||
|      * | ||||
| @ -111,6 +114,16 @@ class TaskFilters extends QueryFilters | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public function project_tasks($project) | ||||
|     { | ||||
| 
 | ||||
|         if (strlen($project) == 0) { | ||||
|             return $this->builder; | ||||
|         } | ||||
| 
 | ||||
|         return $this->builder->where('project_id', $this->decodePrimaryKey($project)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sorts the list based on $sort. | ||||
|      * | ||||
|  | ||||
| @ -76,7 +76,7 @@ class EpcQrGenerator | ||||
|             $this->formatMoney($this->amount), | ||||
|             $this->sepa['purpose'], | ||||
|             substr($this->invoice->number,0,34), | ||||
|             substr($this->invoice->public_notes,0,139), | ||||
|             '', | ||||
|             '' | ||||
|         )), "\n"); | ||||
| 
 | ||||
|  | ||||
| @ -110,6 +110,8 @@ class ActivityController extends BaseController | ||||
|                     'vendor' => $activity->vendor ? $activity->vendor : '', | ||||
|                     'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '', | ||||
|                     'purchase_order' => $activity->purchase_order ? $activity->purchase_order : '', | ||||
|                     'subscription' => $activity->subscription ? $activity->subscription : '', | ||||
|                     'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '', | ||||
|                 ]; | ||||
| 
 | ||||
|                 return array_merge($arr, $activity->toArray()); | ||||
|  | ||||
| @ -481,19 +481,32 @@ class BankTransactionController extends BaseController | ||||
|     { | ||||
|         $action = request()->input('action'); | ||||
| 
 | ||||
|         if(!in_array($action, ['archive', 'restore', 'delete'])) | ||||
|         if(!in_array($action, ['archive', 'restore', 'delete', 'convert_matched'])) | ||||
|             return response()->json(['message' => 'Unsupported action.'], 400); | ||||
| 
 | ||||
|         $ids = request()->input('ids'); | ||||
|              | ||||
|         $bank_transactions = BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); | ||||
| 
 | ||||
|         if($action == 'convert_matched') //catch this action
 | ||||
|         { | ||||
|             if(auth()->user()->isAdmin()) | ||||
|             { | ||||
|                 $this->bank_transaction_repo->convert_matched($bank_transactions); | ||||
|             } | ||||
|             else  | ||||
|                 return; | ||||
|         } | ||||
|         else { | ||||
| 
 | ||||
|             $bank_transactions->each(function ($bank_transaction, $key) use ($action) { | ||||
|                 if (auth()->user()->can('edit', $bank_transaction)) { | ||||
|                     $this->bank_transaction_repo->{$action}($bank_transaction); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|         } | ||||
|          | ||||
|         /* Need to understand which permission are required for the given bulk action ie. view / edit */ | ||||
| 
 | ||||
|         return $this->listResponse(BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); | ||||
|  | ||||
							
								
								
									
										505
									
								
								app/Http/Controllers/BankTransactionRuleController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								app/Http/Controllers/BankTransactionRuleController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,505 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Factory\BankTransactionFactory; | ||||
| use App\Factory\BankTransactionRuleFactory; | ||||
| use App\Filters\BankTransactionFilters; | ||||
| use App\Filters\BankTransactionRuleFilters; | ||||
| use App\Helpers\Bank\Yodlee\Yodlee; | ||||
| use App\Http\Requests\BankTransactionRule\CreateBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransactionRule\DestroyBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransactionRule\EditBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransactionRule\ShowBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransactionRule\StoreBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransactionRule\UpdateBankTransactionRuleRequest; | ||||
| use App\Http\Requests\BankTransaction\AdminBankTransactionRuleRequest; | ||||
| use App\Http\Requests\Import\PreImportRequest; | ||||
| use App\Jobs\Bank\MatchBankTransactionRules; | ||||
| use App\Models\BankTransaction; | ||||
| 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; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Support\Str; | ||||
| 
 | ||||
| class BankTransactionRuleController extends BaseController | ||||
| { | ||||
|     use MakesHash; | ||||
|      | ||||
|     protected $entity_type = BankTransactionRule::class; | ||||
| 
 | ||||
|     protected $entity_transformer = BankTransactionRuleTransformer::class; | ||||
| 
 | ||||
|     protected $bank_transaction_repo; | ||||
| 
 | ||||
|     public function __construct(BankTransactionRuleRepository $bank_transaction_repo) | ||||
|     { | ||||
|         parent::__construct(); | ||||
| 
 | ||||
|         $this->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()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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); | ||||
|                     } | ||||
|                 }, | ||||
|             ] | ||||
|         ); | ||||
| 
 | ||||
|  | ||||
| @ -41,6 +41,25 @@ class SubscriptionPurchaseController extends Controller | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function upgrade(Subscription $subscription, Request $request) | ||||
|     { | ||||
|         /* Make sure the contact is logged into the correct company for this subscription */ | ||||
|         if (auth()->guard('contact')->user() && auth()->guard('contact')->user()->company_id != $subscription->company_id) { | ||||
|             auth()->guard('contact')->logout(); | ||||
|             $request->session()->invalidate(); | ||||
|         } | ||||
| 
 | ||||
|         if ($request->has('locale')) { | ||||
|             $this->setLocale($request->query('locale')); | ||||
|         } | ||||
| 
 | ||||
|         return view('billing-portal.purchasev2', [ | ||||
|             'subscription' => $subscription, | ||||
|             'hash' => Str::uuid()->toString(), | ||||
|             'request_data' => $request->all(), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set locale for incoming request. | ||||
|      * | ||||
| @ -56,4 +75,7 @@ class SubscriptionPurchaseController extends Controller | ||||
|             App::setLocale($record->locale); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -28,6 +28,7 @@ use App\Http\Requests\Invoice\StoreInvoiceRequest; | ||||
| use App\Http\Requests\Invoice\UpdateInvoiceRequest; | ||||
| use App\Http\Requests\Invoice\UpdateReminderRequest; | ||||
| use App\Http\Requests\Invoice\UploadInvoiceRequest; | ||||
| use App\Jobs\Cron\AutoBill; | ||||
| use App\Jobs\Entity\EmailEntity; | ||||
| use App\Jobs\Invoice\BulkInvoiceJob; | ||||
| use App\Jobs\Invoice\StoreInvoice; | ||||
| @ -696,11 +697,14 @@ class InvoiceController extends BaseController | ||||
|     { | ||||
|         /*If we are using bulk actions, we don't want to return anything */ | ||||
|         switch ($action) { | ||||
|             case 'auto_bill': | ||||
|                 AutoBill::dispatch($invoice->id, $invoice->company->db); | ||||
|                 return $this->itemResponse($invoice); | ||||
| 
 | ||||
|             case 'clone_to_invoice': | ||||
|                 $invoice = CloneInvoiceFactory::create($invoice, auth()->user()->id); | ||||
| 
 | ||||
|                 return $this->itemResponse($invoice); | ||||
|                 break; | ||||
|                  | ||||
|             case 'clone_to_quote': | ||||
|                 $quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id); | ||||
| 
 | ||||
| @ -767,7 +771,7 @@ class InvoiceController extends BaseController | ||||
|                 } | ||||
|                 break; | ||||
|             case 'cancel': | ||||
|                 $invoice = $invoice->service()->handleCancellation()->deletePdf()->touchPdf()->save(); | ||||
|                 $invoice = $invoice->service()->handleCancellation()->touchPdf()->save(); | ||||
| 
 | ||||
|                 if (! $bulk) { | ||||
|                     $this->itemResponse($invoice); | ||||
| @ -777,7 +781,7 @@ class InvoiceController extends BaseController | ||||
|             case 'email': | ||||
|                 //check query parameter for email_type and set the template else use calculateTemplate
 | ||||
| 
 | ||||
|                 if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) { | ||||
|                 if (request()->has('email_type') && in_array(request()->input('email_type'), ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'custom1', 'custom2', 'custom3'])) { | ||||
|                     $this->reminder_template = $invoice->client->getSetting(request()->input('email_type')); | ||||
|                 } else { | ||||
|                     $this->reminder_template = $invoice->calculateTemplate('invoice'); | ||||
|  | ||||
| @ -181,7 +181,10 @@ class MigrationController extends BaseController | ||||
|         $company->tasks()->forceDelete(); | ||||
|         $company->vendors()->forceDelete(); | ||||
|         $company->expenses()->forceDelete(); | ||||
|         $company->purchase_orders()->forceDelete(); | ||||
|         $company->bank_transaction_rules()->forceDelete(); | ||||
|         $company->bank_transactions()->forceDelete(); | ||||
|         $company->bank_integrations()->forceDelete(); | ||||
| 
 | ||||
|         $company->all_activities()->forceDelete(); | ||||
| 
 | ||||
|         $settings = $company->settings; | ||||
|  | ||||
							
								
								
									
										10
									
								
								app/Http/Controllers/OpenAPI/BTRulesSchema.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Http/Controllers/OpenAPI/BTRulesSchema.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @OA\Schema( | ||||
|  *   schema="BTRules", | ||||
|  *   type="object", | ||||
|  *       @OA\Property(property="data_key", type="string", example="description,amount", description="The key to search"), | ||||
|  *       @OA\Property(property="operator", type="string", example=">", description="The operator flag of the search"), | ||||
|  *       @OA\Property(property="value", type="string" ,example="bob", description="The value to search for"), | ||||
|  * ) | ||||
|  */ | ||||
| @ -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"), | ||||
|  | ||||
							
								
								
									
										25
									
								
								app/Http/Controllers/OpenAPI/BankTransactionRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Http/Controllers/OpenAPI/BankTransactionRule.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @OA\Schema( | ||||
|  *   schema="BankTransactionRule", | ||||
|  *   type="object", | ||||
|  *       @OA\Property(property="id", type="string", example="AS3df3A", description="The bank transaction rules 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="name", type="string", example="Rule 1", description="The name of the transaction"), | ||||
|  *       @OA\Property( | ||||
|  *          property="rules", | ||||
|  *          type="array", | ||||
|  *          description="A mapped collection of the sub rules for the BankTransactionRule", | ||||
|  *          @OA\Items( | ||||
|  *              ref="#/components/schemas/BTRules", | ||||
|  *          ), | ||||
|  *       ), | ||||
|  *       @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="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"), | ||||
|  * ) | ||||
|  */ | ||||
							
								
								
									
										528
									
								
								app/Http/Livewire/BillingPortalPurchasev2.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										528
									
								
								app/Http/Livewire/BillingPortalPurchasev2.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,528 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Livewire; | ||||
| 
 | ||||
| use App\Factory\ClientFactory; | ||||
| use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Mail\ContactPasswordlessLogin; | ||||
| use App\Models\Client; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\Subscription; | ||||
| use App\Repositories\ClientContactRepository; | ||||
| use App\Repositories\ClientRepository; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Str; | ||||
| use App\DataMapper\ClientSettings; | ||||
| use Livewire\Component; | ||||
| 
 | ||||
| class BillingPortalPurchasev2 extends Component | ||||
| { | ||||
|     /** | ||||
|      * Random hash generated by backend to handle the tracking of state. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     public $hash; | ||||
| 
 | ||||
|     /** | ||||
|      * Top level text on the left side of billing page. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     public $heading_text; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * E-mail address model for user input. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     public $email; | ||||
| 
 | ||||
|     /** | ||||
|      * Password model for user input. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     public $password; | ||||
| 
 | ||||
|     /** | ||||
|      * Instance of subscription. | ||||
|      * | ||||
|      * @var Subscription | ||||
|      */ | ||||
|     public $subscription; | ||||
| 
 | ||||
|     /** | ||||
|      * Instance of client contact. | ||||
|      * | ||||
|      * @var null|ClientContact | ||||
|      */ | ||||
|     public $contact; | ||||
| 
 | ||||
|     /** | ||||
|      * Rules for validating the form. | ||||
|      * | ||||
|      * @var \string[][] | ||||
|      */ | ||||
|     protected $rules = [ | ||||
|         'email' => ['required', 'email'], | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * Id for CompanyGateway record. | ||||
|      * | ||||
|      * @var string|integer | ||||
|      */ | ||||
|     public $company_gateway_id; | ||||
| 
 | ||||
|     /** | ||||
|      * Id for GatewayType. | ||||
|      * | ||||
|      * @var string|integer | ||||
|      */ | ||||
|     public $payment_method_id; | ||||
| 
 | ||||
|     private $user_coupon; | ||||
| 
 | ||||
|     /** | ||||
|      * List of steps that frontend form follows. | ||||
|      * | ||||
|      * @var array | ||||
|      */ | ||||
|     public $steps = [ | ||||
|         'passed_email' => false, | ||||
|         'existing_user' => false, | ||||
|         'fetched_payment_methods' => false, | ||||
|         'fetched_client' => false, | ||||
|         'show_start_trial' => false, | ||||
|         'passwordless_login_sent' => false, | ||||
|         'started_payment' => false, | ||||
|         'discount_applied' => false, | ||||
|         'show_loading_bar' => false, | ||||
|         'not_eligible' => null, | ||||
|         'not_eligible_message' => null, | ||||
|         'payment_required' => true, | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * List of payment methods fetched from client. | ||||
|      * | ||||
|      * @var array | ||||
|      */ | ||||
|     public $methods = []; | ||||
| 
 | ||||
|     /** | ||||
|      * Instance of \App\Models\Invoice | ||||
|      * | ||||
|      * @var Invoice | ||||
|      */ | ||||
|     public $invoice; | ||||
| 
 | ||||
|     /** | ||||
|      * Coupon model for user input | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     public $coupon; | ||||
| 
 | ||||
|     /** | ||||
|      * Quantity for seats | ||||
|      * | ||||
|      * @var int | ||||
|      */ | ||||
|     public $quantity; | ||||
| 
 | ||||
|     /** | ||||
|      * First-hit request data (queries, locales...). | ||||
|      * | ||||
|      * @var array | ||||
|      */ | ||||
|     public $request_data; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     public $price; | ||||
| 
 | ||||
|     /** | ||||
|      * Disabled state of passwordless login button. | ||||
|      * | ||||
|      * @var bool | ||||
|      */ | ||||
|     public $passwordless_login_btn = false; | ||||
| 
 | ||||
|     /** | ||||
|      * Instance of company. | ||||
|      * | ||||
|      * @var Company | ||||
|      */ | ||||
|     public $company; | ||||
| 
 | ||||
|     /** | ||||
|      * Campaign reference. | ||||
|      * | ||||
|      * @var string|null | ||||
|      */ | ||||
|     public $campaign; | ||||
| 
 | ||||
|     public function mount() | ||||
|     { | ||||
|         MultiDB::setDb($this->company->db); | ||||
| 
 | ||||
|         $this->quantity = 1; | ||||
| 
 | ||||
|         $this->price = $this->subscription->price; | ||||
| 
 | ||||
|         if (request()->query('coupon')) { | ||||
|             $this->coupon = request()->query('coupon'); | ||||
|             $this->handleCoupon(); | ||||
|         } | ||||
|         elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){ | ||||
|             $this->price = $this->subscription->promo_price; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle user authentication | ||||
|      * | ||||
|      * @return $this|bool|void | ||||
|      */ | ||||
|     public function authenticate() | ||||
|     { | ||||
|         $this->validate(); | ||||
| 
 | ||||
|         $contact = ClientContact::where('email', $this->email) | ||||
|             ->where('company_id', $this->subscription->company_id) | ||||
|             ->first(); | ||||
| 
 | ||||
|         if ($contact && $this->steps['existing_user'] === false) { | ||||
|             return $this->steps['existing_user'] = true; | ||||
|         } | ||||
| 
 | ||||
|         if ($contact && $this->steps['existing_user']) { | ||||
|             $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]); | ||||
| 
 | ||||
|             return $attempt | ||||
|                 ? $this->getPaymentMethods($contact) | ||||
|                 : session()->flash('message', 'These credentials do not match our records.'); | ||||
|         } | ||||
| 
 | ||||
|         $this->steps['existing_user'] = false; | ||||
| 
 | ||||
|         $contact = $this->createBlankClient(); | ||||
| 
 | ||||
|         if ($contact && $contact instanceof ClientContact) { | ||||
|             $this->getPaymentMethods($contact); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a blank client. Used for new customers purchasing. | ||||
|      * | ||||
|      * @return mixed | ||||
|      * @throws \Laracasts\Presenter\Exceptions\PresenterException | ||||
|      */ | ||||
|     protected function createBlankClient() | ||||
|     { | ||||
|         $company = $this->subscription->company; | ||||
|         $user = $this->subscription->user; | ||||
|         $user->setCompany($company); | ||||
|          | ||||
|         $client_repo = new ClientRepository(new ClientContactRepository()); | ||||
| 
 | ||||
|         $data = [ | ||||
|             'name' => '', | ||||
|             'contacts' => [ | ||||
|                 ['email' => $this->email], | ||||
|             ], | ||||
|             'client_hash' => Str::random(40), | ||||
|             'settings' => ClientSettings::defaults(), | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($this->request_data as $field => $value) { | ||||
|             if (in_array($field, Client::$subscriptions_fillable)) { | ||||
|                 $data[$field] = $value; | ||||
|             } | ||||
| 
 | ||||
|             if (in_array($field, ClientContact::$subscription_fillable)) { | ||||
|                 $data['contacts'][0][$field] = $value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| // nlog($this->subscription->group_settings->settings);
 | ||||
| // nlog($this->subscription->group_settings->settings->currency_id);
 | ||||
| 
 | ||||
|         if(array_key_exists('currency_id', $this->request_data)) { | ||||
| 
 | ||||
|             $currency = Cache::get('currencies')->filter(function ($item){ | ||||
|                 return $item->id == $this->request_data['currency_id']; | ||||
|             })->first(); | ||||
| 
 | ||||
|             if($currency) | ||||
|                 $data['settings']->currency_id = $currency->id; | ||||
| 
 | ||||
|         } | ||||
|         elseif($this->subscription->group_settings && property_exists($this->subscription->group_settings->settings, 'currency_id')) { | ||||
| 
 | ||||
|             $currency = Cache::get('currencies')->filter(function ($item){ | ||||
|                 return $item->id == $this->subscription->group_settings->settings->currency_id; | ||||
|             })->first(); | ||||
| 
 | ||||
|             if($currency) | ||||
|                 $data['settings']->currency_id = $currency->id; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (array_key_exists('locale', $this->request_data)) { | ||||
|             $request = $this->request_data; | ||||
| 
 | ||||
|             $record = Cache::get('languages')->filter(function ($item) use ($request) { | ||||
|                 return $item->locale == $request['locale']; | ||||
|             })->first(); | ||||
| 
 | ||||
|             if ($record) { | ||||
|                 $data['settings']['language_id'] = (string)$record->id; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); | ||||
| 
 | ||||
|         return $client->fresh()->contacts->first(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fetching payment methods from the client. | ||||
|      * | ||||
|      * @param ClientContact $contact | ||||
|      * @return $this | ||||
|      */ | ||||
|     protected function getPaymentMethods(ClientContact $contact): self | ||||
|     { | ||||
|         Auth::guard('contact')->loginUsingId($contact->id, true); | ||||
| 
 | ||||
|         $this->contact = $contact; | ||||
| 
 | ||||
|         if ($this->subscription->trial_enabled) { | ||||
|             $this->heading_text = ctrans('texts.plan_trial'); | ||||
|             $this->steps['show_start_trial'] = true; | ||||
| 
 | ||||
|             return $this; | ||||
|         } | ||||
| 
 | ||||
|         if ((int)$this->price == 0) | ||||
|             $this->steps['payment_required'] = false; | ||||
|         else | ||||
|             $this->steps['fetched_payment_methods'] = true; | ||||
| 
 | ||||
|         $this->methods = $contact->client->service()->getPaymentMethods($this->price); | ||||
| 
 | ||||
|         $this->heading_text = ctrans('texts.payment_methods'); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Middle method between selecting payment method & | ||||
|      * submitting the from to the backend. | ||||
|      * | ||||
|      * @param $company_gateway_id | ||||
|      * @param $gateway_type_id | ||||
|      */ | ||||
|     public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id) | ||||
|     { | ||||
|         $this->company_gateway_id = $company_gateway_id; | ||||
|         $this->payment_method_id = $gateway_type_id; | ||||
| 
 | ||||
|         $this->handleBeforePaymentEvents(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Method to handle events before payments. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function handleBeforePaymentEvents() | ||||
|     { | ||||
|         $this->steps['started_payment'] = true; | ||||
|         $this->steps['show_loading_bar'] = true; | ||||
| 
 | ||||
|         $data = [ | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'date' => now()->format('Y-m-d'), | ||||
|             'invitations' => [[ | ||||
|                 'key' => '', | ||||
|                 'client_contact_id' => $this->contact->hashed_id, | ||||
|             ]], | ||||
|             'user_input_promo_code' => $this->coupon, | ||||
|             'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon, | ||||
|             'quantity' => $this->quantity, | ||||
|         ]; | ||||
| 
 | ||||
|         $is_eligible = $this->subscription->service()->isEligible($this->contact); | ||||
| 
 | ||||
|         if (is_array($is_eligible) && $is_eligible['message'] != 'Success') { | ||||
|             $this->steps['not_eligible'] = true; | ||||
|             $this->steps['not_eligible_message'] = $is_eligible['message']; | ||||
|             $this->steps['show_loading_bar'] = false; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $this->invoice = $this->subscription | ||||
|             ->service() | ||||
|             ->createInvoice($data, $this->quantity) | ||||
|             ->service() | ||||
|             ->markSent() | ||||
|             ->fillDefaults() | ||||
|             ->adjustInventory() | ||||
|             ->save(); | ||||
| 
 | ||||
|         Cache::put($this->hash, [ | ||||
|             'subscription_id' => $this->subscription->id, | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'invoice_id' => $this->invoice->id, | ||||
|             'context' => 'purchase', | ||||
|             'campaign' => $this->campaign, | ||||
|         ], now()->addMinutes(60)); | ||||
| 
 | ||||
|         $this->emit('beforePaymentEventsCompleted'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Proxy method for starting the trial. | ||||
|      * | ||||
|      * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||||
|      */ | ||||
|     public function handleTrial() | ||||
|     { | ||||
|         return $this->subscription->service()->startTrial([ | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'quantity' => $this->quantity, | ||||
|             'contact_id' => $this->contact->id, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function handlePaymentNotRequired() | ||||
|     { | ||||
| 
 | ||||
|         $is_eligible = $this->subscription->service()->isEligible($this->contact); | ||||
|          | ||||
|         if ($is_eligible['status_code'] != 200) { | ||||
|             $this->steps['not_eligible'] = true; | ||||
|             $this->steps['not_eligible_message'] = $is_eligible['message']; | ||||
|             $this->steps['show_loading_bar'] = false; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return $this->subscription->service()->handleNoPaymentRequired([ | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'quantity' => $this->quantity, | ||||
|             'contact_id' => $this->contact->id, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'coupon' => $this->coupon, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update quantity property. | ||||
|      * | ||||
|      * @param string $option | ||||
|      * @return int | ||||
|      */ | ||||
|     public function updateQuantity(string $option): int | ||||
|     { | ||||
|         $this->handleCoupon(); | ||||
| 
 | ||||
|         if ($this->quantity == 1 && $option == 'decrement') { | ||||
|             $this->price = $this->price * 1; | ||||
|             return $this->quantity; | ||||
|         } | ||||
| 
 | ||||
|         if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') { | ||||
|             $this->price = $this->price * $this->subscription->max_seats_limit; | ||||
|             return $this->quantity; | ||||
|         } | ||||
| 
 | ||||
|         if ($option == 'increment') { | ||||
|             $this->quantity++; | ||||
|             $this->price = $this->price * $this->quantity; | ||||
|             return $this->quantity; | ||||
|         } | ||||
| 
 | ||||
|             $this->quantity--; | ||||
|             $this->price = $this->price * $this->quantity; | ||||
| 
 | ||||
|             return $this->quantity; | ||||
|     } | ||||
| 
 | ||||
|     public function handleCoupon() | ||||
|     { | ||||
| 
 | ||||
|         if($this->steps['discount_applied']){ | ||||
|             $this->price = $this->subscription->promo_price; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if ($this->coupon == $this->subscription->promo_code) { | ||||
|             $this->price = $this->subscription->promo_price; | ||||
|             $this->quantity = 1; | ||||
|             $this->steps['discount_applied'] = true; | ||||
|         } | ||||
|         else | ||||
|             $this->price = $this->subscription->price; | ||||
|     } | ||||
| 
 | ||||
|     public function passwordlessLogin() | ||||
|     { | ||||
|         $this->passwordless_login_btn = true; | ||||
| 
 | ||||
|         $contact = ClientContact::query() | ||||
|             ->where('email', $this->email) | ||||
|             ->where('company_id', $this->subscription->company_id) | ||||
|             ->first(); | ||||
| 
 | ||||
|         $mailer = new NinjaMailerObject(); | ||||
|         $mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon); | ||||
|         $mailer->company = $this->subscription->company; | ||||
|         $mailer->settings = $this->subscription->company->settings; | ||||
|         $mailer->to_user = $contact; | ||||
| 
 | ||||
|         NinjaMailerJob::dispatch($mailer); | ||||
| 
 | ||||
|         $this->steps['passwordless_login_sent'] = true; | ||||
|         $this->passwordless_login_btn = false; | ||||
|     } | ||||
| 
 | ||||
|     public function render() | ||||
|     { | ||||
|         if (array_key_exists('email', $this->request_data)) { | ||||
|             $this->email = $this->request_data['email']; | ||||
|         } | ||||
| 
 | ||||
|         if ($this->contact instanceof ClientContact) { | ||||
|             $this->getPaymentMethods($this->contact); | ||||
|         } | ||||
| 
 | ||||
|         return render('components.livewire.billing-portal-purchasev2'); | ||||
|     } | ||||
| } | ||||
| @ -41,6 +41,7 @@ class ContactKeyLogin | ||||
|             $request->session()->invalidate(); | ||||
|         } | ||||
| 
 | ||||
|         //magic links survive for 1 hour
 | ||||
|         if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { | ||||
|             $payload = Cache::get($request->segment(3)); | ||||
| 
 | ||||
| @ -66,7 +67,11 @@ class ContactKeyLogin | ||||
|             } | ||||
|         } elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) { | ||||
|             if (MultiDB::findAndSetDbByContactKey($request->segment(3))) { | ||||
|                 if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) { | ||||
|                 if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) { | ||||
| 
 | ||||
|                     if($client_contact->company->settings->enable_client_portal_password) | ||||
|                         return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]); | ||||
| 
 | ||||
|                     if (empty($client_contact->email)) { | ||||
|                         $client_contact->email = Str::random(6).'@example.com'; | ||||
|                     } | ||||
| @ -82,7 +87,11 @@ class ContactKeyLogin | ||||
|                 } | ||||
|             } | ||||
|         } elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) { | ||||
|             if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) { | ||||
|             if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) { | ||||
| 
 | ||||
|                 if($client_contact->company->settings->enable_client_portal_password) | ||||
|                     return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]); | ||||
| 
 | ||||
|                 if (empty($client_contact->email)) { | ||||
|                     $client_contact->email = Str::random(6).'@example.com'; | ||||
|                     $client_contact->save(); | ||||
| @ -125,7 +134,11 @@ class ContactKeyLogin | ||||
|                 return redirect($this->setRedirectPath()); | ||||
|             } | ||||
|         } elseif ($request->segment(3)) { | ||||
|             if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) { | ||||
|             if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) { | ||||
| 
 | ||||
|                 if($client_contact->company->settings->enable_client_portal_password) | ||||
|                     return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]); | ||||
| 
 | ||||
|                 if (empty($client_contact->email)) { | ||||
|                     $client_contact->email = Str::random(6).'@example.com'; | ||||
|                     $client_contact->save(); | ||||
|  | ||||
| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Models\BankTransactionRule; | ||||
| 
 | ||||
| class CreateBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->can('create', BankTransactionRule::class); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
| class DestroyBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->can('edit', $this->bank_transaction_rule); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
| class EditBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->can('edit', $this->bank_transaction_rule); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
| class ShowBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->can('view', $this->bank_transaction_rule); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,70 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Models\BankTransactionRule; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| class StoreBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->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); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\BankTransactionRule; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| class UpdateBankTransactionRuleRequest extends Request | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -61,22 +61,15 @@ class StoreCompanyRequest extends Request | ||||
|     { | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         if(!isset($input['name'])) | ||||
|             $input['name'] = 'Untitled Company'; | ||||
| 
 | ||||
|         if (array_key_exists('google_analytics_url', $input)) { | ||||
|             $input['google_analytics_key'] = $input['google_analytics_url']; | ||||
|         } | ||||
| 
 | ||||
|         // $company_settings = CompanySettings::defaults();
 | ||||
| 
 | ||||
|         //@todo this code doesn't make sense as we never return $company_settings anywhere
 | ||||
|         //@deprecated???
 | ||||
|         // if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
 | ||||
|         //     foreach ($input['settings'] as $key => $value) {
 | ||||
|         //         $company_settings->{$key} = $value;
 | ||||
|         //     }
 | ||||
|         // }
 | ||||
| 
 | ||||
|         if (array_key_exists('portal_domain', $input)) { | ||||
|             $input['portal_domain'] = strtolower($input['portal_domain']); | ||||
|             $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); | ||||
|         } | ||||
| 
 | ||||
|         $this->replace($input); | ||||
|  | ||||
| @ -74,9 +74,9 @@ class UpdateCompanyRequest extends Request | ||||
|      | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         if (Ninja::isHosted() && array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) { | ||||
|         if (array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) { | ||||
|             $input['portal_domain'] = $this->addScheme($input['portal_domain']); | ||||
|             $input['portal_domain'] = strtolower($input['portal_domain']); | ||||
|             $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); | ||||
|         } | ||||
| 
 | ||||
|         if (array_key_exists('settings', $input)) { | ||||
| @ -108,7 +108,7 @@ class UpdateCompanyRequest extends Request | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|             $settings['email_style_custom'] = str_replace("{{", "", $settings['email_style_custom']); | ||||
|         $settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']); | ||||
| 
 | ||||
|         if (! $account->isFreeHostedClient()) { | ||||
|             return $settings; | ||||
| @ -126,10 +126,12 @@ class UpdateCompanyRequest extends Request | ||||
|     } | ||||
| 
 | ||||
|     private function addScheme($url, $scheme = 'https://') | ||||
|     { | ||||
|         if(Ninja::isHosted()) | ||||
|         { | ||||
|             $url = str_replace('http://', '', $url); | ||||
| 
 | ||||
|             $url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url; | ||||
|         } | ||||
| 
 | ||||
|         return rtrim($url, '/'); | ||||
|     } | ||||
|  | ||||
| @ -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)) { | ||||
|  | ||||
| @ -35,26 +35,34 @@ class StoreSubscriptionRequest extends Request | ||||
|     public function rules() | ||||
|     { | ||||
|         $rules = [ | ||||
|             'product_ids' => ['sometimes'], | ||||
|             'recurring_product_ids' => ['sometimes'], | ||||
|             'assigned_user_id' => ['sometimes'], | ||||
|             'is_recurring' => ['sometimes'], | ||||
|             'frequency_id' => ['required_with:recurring_product_ids'], | ||||
|             'auto_bill' => ['sometimes'], | ||||
|             'promo_code' => ['sometimes'], | ||||
|             'promo_discount' => ['sometimes'], | ||||
|             'is_amount_discount' => ['sometimes'], | ||||
|             'allow_cancellation' => ['sometimes'], | ||||
|             'per_set_enabled' => ['sometimes'], | ||||
|             'min_seats_limit' => ['sometimes'], | ||||
|             'max_seats_limit' => ['sometimes'], | ||||
|             'trial_enabled' => ['sometimes'], | ||||
|             'trial_duration' => ['sometimes'], | ||||
|             'allow_query_overrides' => ['sometimes'], | ||||
|             'allow_plan_changes' => ['sometimes'], | ||||
|             'refund_period' => ['sometimes'], | ||||
|             'webhook_configuration' => ['array'], | ||||
|             'name' => ['required', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)], | ||||
|             'group_id' => ['bail','sometimes', 'nullable', Rule::exists('group_settings','id')->where('company_id', auth()->user()->company()->id)], | ||||
|             'assigned_user_id' => ['bail','sometimes', 'nullable', Rule::exists('users','id')->where('account_id', auth()->user()->account_id)], | ||||
|             'product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'recurring_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'is_recurring' => 'bail|sometimes|bool', | ||||
|             'frequency_id' => 'bail|required_with:recurring_product_ids', | ||||
|             'auto_bill' => 'bail|sometimes|nullable|string', | ||||
|             'promo_code' => 'bail|sometimes|nullable|string', | ||||
|             'promo_discount' => 'bail|sometimes|numeric', | ||||
|             'is_amount_discount' => 'bail|sometimes|bool', | ||||
|             'allow_cancellation' => 'bail|sometimes|bool', | ||||
|             'per_set_enabled' => 'bail|sometimes|bool', | ||||
|             'min_seats_limit' => 'bail|sometimes|numeric', | ||||
|             'max_seats_limit' => 'bail|sometimes|numeric', | ||||
|             'trial_enabled' => 'bail|sometimes|bool', | ||||
|             'trial_duration' => 'bail|sometimes|numeric', | ||||
|             'allow_query_overrides' => 'bail|sometimes|bool', | ||||
|             'allow_plan_changes' => 'bail|sometimes|bool', | ||||
|             'refund_period' => 'bail|sometimes|numeric', | ||||
|             'webhook_configuration' => 'bail|array', | ||||
|             'webhook_configuration.post_purchase_url' => 'bail|sometimes|nullable|string', | ||||
|             'webhook_configuration.post_purchase_rest_method' => 'bail|sometimes|nullable|string', | ||||
|             'webhook_configuration.post_purchase_headers' => 'bail|sometimes|array', | ||||
|             'registration_required' => 'bail|sometimes|bool', | ||||
|             'optional_recurring_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'optional_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'use_inventory_management' => 'bail|sometimes|bool' | ||||
|         ]; | ||||
| 
 | ||||
|         return $this->globalRules($rules); | ||||
|  | ||||
| @ -37,26 +37,34 @@ class UpdateSubscriptionRequest extends Request | ||||
|     public function rules() | ||||
|     { | ||||
|         $rules = [ | ||||
|             'product_ids' => ['sometimes'], | ||||
|             'recurring_product_ids' => ['sometimes'], | ||||
|             'assigned_user_id' => ['sometimes'], | ||||
|             'is_recurring' => ['sometimes'], | ||||
|             'frequency_id' => ['required_with:recurring_product_ids'], | ||||
|             'auto_bill' => ['sometimes'], | ||||
|             'promo_code' => ['sometimes'], | ||||
|             'promo_discount' => ['sometimes'], | ||||
|             'is_amount_discount' => ['sometimes'], | ||||
|             'allow_cancellation' => ['sometimes'], | ||||
|             'per_set_enabled' => ['sometimes'], | ||||
|             'min_seats_limit' => ['sometimes'], | ||||
|             'max_seats_limit' => ['sometimes'], | ||||
|             'trial_enabled' => ['sometimes'], | ||||
|             'trial_duration' => ['sometimes'], | ||||
|             'allow_query_overrides' => ['sometimes'], | ||||
|             'allow_plan_changes' => ['sometimes'], | ||||
|             'refund_period' => ['sometimes'], | ||||
|             'webhook_configuration' => ['array'], | ||||
|             'name' => ['sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)], | ||||
|             'name' => ['bail','sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)], | ||||
|             'group_id' => ['bail','sometimes', 'nullable', Rule::exists('group_settings','id')->where('company_id', auth()->user()->company()->id)], | ||||
|             'assigned_user_id' => ['bail','sometimes', 'nullable', Rule::exists('users','id')->where('account_id', auth()->user()->account_id)], | ||||
|             'product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'recurring_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'is_recurring' => 'bail|sometimes|bool', | ||||
|             'frequency_id' => 'bail|required_with:recurring_product_ids', | ||||
|             'auto_bill' => 'bail|sometimes|nullable|string', | ||||
|             'promo_code' => 'bail|sometimes|nullable|string', | ||||
|             'promo_discount' => 'bail|sometimes|numeric', | ||||
|             'is_amount_discount' => 'bail|sometimes|bool', | ||||
|             'allow_cancellation' => 'bail|sometimes|bool', | ||||
|             'per_set_enabled' => 'bail|sometimes|bool', | ||||
|             'min_seats_limit' => 'bail|sometimes|numeric', | ||||
|             'max_seats_limit' => 'bail|sometimes|numeric', | ||||
|             'trial_enabled' => 'bail|sometimes|bool', | ||||
|             'trial_duration' => 'bail|sometimes|numeric', | ||||
|             'allow_query_overrides' => 'bail|sometimes|bool', | ||||
|             'allow_plan_changes' => 'bail|sometimes|bool', | ||||
|             'refund_period' => 'bail|sometimes|numeric', | ||||
|             'webhook_configuration' => 'bail|array', | ||||
|             'webhook_configuration.post_purchase_url' => 'bail|sometimes|nullable|string', | ||||
|             'webhook_configuration.post_purchase_rest_method' => 'bail|sometimes|nullable|string', | ||||
|             'webhook_configuration.post_purchase_headers' => 'bail|sometimes|array', | ||||
|             'registration_required' => 'bail|sometimes|bool', | ||||
|             'optional_recurring_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'optional_product_ids' => 'bail|sometimes|nullable|string', | ||||
|             'use_inventory_management' => 'bail|sometimes|bool', | ||||
|         ]; | ||||
| 
 | ||||
|         return $this->globalRules($rules); | ||||
|  | ||||
| @ -15,6 +15,7 @@ use App\Http\Requests\Request; | ||||
| use App\Models\Project; | ||||
| use App\Utils\Traits\ChecksEntityStatus; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Auth\Access\AuthorizationException; | ||||
| use Illuminate\Validation\Rule; | ||||
| 
 | ||||
| class UpdateTaskRequest extends Request | ||||
| @ -29,6 +30,10 @@ class UpdateTaskRequest extends Request | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         //prevent locked tasks from updating
 | ||||
|         if($this->task->invoice_lock && $this->task->invoice_id) | ||||
|             return false; | ||||
| 
 | ||||
|         return auth()->user()->can('edit', $this->task); | ||||
|     } | ||||
| 
 | ||||
| @ -87,4 +92,11 @@ class UpdateTaskRequest extends Request | ||||
| 
 | ||||
|         $this->replace($input); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     protected function failedAuthorization() | ||||
|     { | ||||
|         throw new AuthorizationException(ctrans('texts.task_update_authorization_error')); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -24,7 +24,7 @@ class GenerateSmsRequest extends Request | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->isAdmin(); | ||||
|         return auth()->user(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ class ValidCompanyQuantity implements Rule | ||||
|      */ | ||||
|     public function passes($attribute, $value) | ||||
|     { | ||||
|          | ||||
|         if (Ninja::isSelfHost()) { | ||||
|             return auth()->user()->company()->account->companies->count() < 10; | ||||
|         } | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -196,13 +196,19 @@ class MatchBankTransactions implements ShouldQueue | ||||
|         $expense->payment_date = Carbon::parse($this->bt->date); | ||||
|         $expense->transaction_reference = $this->bt->description; | ||||
|         $expense->transaction_id = $this->bt->id; | ||||
|         $expense->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null; | ||||
| 
 | ||||
|         if(array_key_exists('vendor_id', $input)) | ||||
|             $expense->vendor_id = $input['vendor_id']; | ||||
| 
 | ||||
|         $expense->invoice_documents = $this->company->invoice_expense_documents; | ||||
|         $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; | ||||
|         $expense->save(); | ||||
| 
 | ||||
|         $this->bt->expense_id = $expense->id; | ||||
|         $this->bt->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null; | ||||
| 
 | ||||
|         if(array_key_exists('vendor_id', $input)) | ||||
|             $this->bt->vendor_id = $input['vendor_id']; | ||||
|          | ||||
|         $this->bt->status_id = BankTransaction::STATUS_CONVERTED; | ||||
|         $this->bt->save(); | ||||
| 
 | ||||
| @ -254,6 +260,9 @@ class MatchBankTransactions implements ShouldQueue | ||||
| 
 | ||||
|         }, 1); | ||||
| 
 | ||||
|         if(!$this->invoice) | ||||
|             return; | ||||
|          | ||||
|         /* Create Payment */ | ||||
|         $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); | ||||
| 
 | ||||
|  | ||||
| @ -50,11 +50,20 @@ class ReminderJob implements ShouldQueue | ||||
|             $this->processReminders(); | ||||
|         } else { | ||||
|             //multiDB environment, need to
 | ||||
|             /* | ||||
|             foreach (MultiDB::$dbs as $db) { | ||||
|                 MultiDB::setDB($db); | ||||
|                 nlog("set db {$db}"); | ||||
|                 $this->processReminders(); | ||||
|             } | ||||
|             */ | ||||
|             //24-11-2022 fix for potential memory leak during a long running process, the second reminder may run twice
 | ||||
|             foreach (config('ninja.dbs') as $db) { | ||||
|                 MultiDB::setDB($db); | ||||
|                 nlog("set db {$db}"); | ||||
|                 $this->processReminders(); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -62,6 +71,8 @@ class ReminderJob implements ShouldQueue | ||||
|     { | ||||
|         nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s')); | ||||
| 
 | ||||
|         set_time_limit(0); | ||||
| 
 | ||||
|         Invoice::query() | ||||
|              ->where('is_deleted', 0) | ||||
|              ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||
| @ -77,16 +88,21 @@ class ReminderJob implements ShouldQueue | ||||
|              }) | ||||
|              ->with('invitations')->cursor()->each(function ($invoice) { | ||||
|                  if ($invoice->isPayable()) { | ||||
| 
 | ||||
|                     //Attempts to prevent duplicates from sending
 | ||||
|                     if($invoice->reminder_last_sent && Carbon::parse($invoice->reminder_last_sent)->startOfDay()->eq(now()->startOfDay())){ | ||||
|                         nlog("caught a duplicate reminder for invoice {$invoice->number}"); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                      $reminder_template = $invoice->calculateTemplate('invoice'); | ||||
|                      nlog("reminder template = {$reminder_template}"); | ||||
|                          $invoice->service()->touchReminder($reminder_template)->save(); | ||||
|                      $invoice = $this->calcLateFee($invoice, $reminder_template); | ||||
| 
 | ||||
|                          $invoice->service()->touchPdf(); | ||||
|                      $invoice->service()->touchReminder($reminder_template)->save(); | ||||
|                      $invoice->service()->touchPdf(true); | ||||
| 
 | ||||
|                      //20-04-2022 fixes for endless reminders - generic template naming was wrong
 | ||||
|                      $enabled_reminder = 'enable_'.$reminder_template; | ||||
| 
 | ||||
|                      if ($reminder_template == 'endless_reminder') { | ||||
|                          $enabled_reminder = 'enable_reminder_endless'; | ||||
|                      } | ||||
| @ -99,7 +115,7 @@ class ReminderJob implements ShouldQueue | ||||
|                 (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { | ||||
| 
 | ||||
|                          $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { | ||||
|                                  EmailEntity::dispatchSync($invitation, $invitation->company, $reminder_template); | ||||
|                              EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); | ||||
|                              nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); | ||||
|                          }); | ||||
| 
 | ||||
| @ -112,6 +128,7 @@ class ReminderJob implements ShouldQueue | ||||
|                      $invoice->next_send_date = null; | ||||
|                      $invoice->save(); | ||||
|                  } | ||||
| 
 | ||||
|              }); | ||||
|     } | ||||
| 
 | ||||
| @ -196,22 +213,17 @@ class ReminderJob implements ShouldQueue | ||||
|         $invoice->line_items = $invoice_items; | ||||
| 
 | ||||
|         /**Refresh Invoice values*/ | ||||
|         $invoice->calc()->getInvoice()->save(); | ||||
|         $invoice->fresh(); | ||||
|         $invoice->service()->deletePdf(); | ||||
| 
 | ||||
|         /* Refresh the client here to ensure the balance is fresh */ | ||||
|         $client = $invoice->client; | ||||
|         $client = $client->fresh(); | ||||
|         $invoice = $invoice->calc()->getInvoice(); | ||||
|         // $invoice->service()->deletePdf(); 24-11-2022 no need to delete here because we regenerate later anyway
 | ||||
| 
 | ||||
|         nlog('adjusting client balance and invoice balance by #'.$invoice->number.' '.($invoice->balance - $temp_invoice_balance)); | ||||
|         $client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save(); | ||||
|         $invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance); | ||||
|         $invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}"); | ||||
| 
 | ||||
|         $transaction = [ | ||||
|             'invoice' => $invoice->transaction_event(), | ||||
|             'payment' => [], | ||||
|             'client' => $client->transaction_event(), | ||||
|             'client' => $invoice->client->transaction_event(), | ||||
|             'credit' => [], | ||||
|             'metadata' => ['setLateFee'], | ||||
|         ]; | ||||
|  | ||||
| @ -292,6 +292,14 @@ class Activity extends StaticModel | ||||
|         return $this->belongsTo(Quote::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function subscription() | ||||
|     { | ||||
|         return $this->belongsTo(Subscription::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use App\Models\BankTransactionRule; | ||||
| use App\Models\Filterable; | ||||
| use App\Models\Invoice; | ||||
| use App\Services\Bank\BankService; | ||||
|  | ||||
							
								
								
									
										171
									
								
								app/Models/BankTransactionRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								app/Models/BankTransactionRule.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use App\Models\Filterable; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| class BankTransactionRule extends BaseModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use MakesHash; | ||||
|     use Filterable; | ||||
|      | ||||
|     protected $fillable = [ | ||||
|         'name', | ||||
|         'rules', | ||||
|         'auto_convert', | ||||
|         'matches_on_all', | ||||
|         'applies_to', | ||||
|         'client_id', | ||||
|         'vendor_id', | ||||
|         'category_id', | ||||
|     ]; | ||||
| 
 | ||||
|     protected $casts = [ | ||||
|         'rules' => '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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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; | ||||
| @ -123,6 +124,8 @@ class Company extends BaseModel | ||||
|         'enabled_expense_tax_rates', | ||||
|         'invoice_task_project', | ||||
|         'report_include_deleted', | ||||
|         'invoice_task_lock', | ||||
|         'use_vendor_currency', | ||||
|     ]; | ||||
| 
 | ||||
|     protected $hidden = [ | ||||
| @ -188,6 +191,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); | ||||
| @ -541,6 +549,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(); | ||||
|  | ||||
| @ -79,7 +79,8 @@ class PurchaseOrder extends BaseModel | ||||
|         'partial', | ||||
|         'paid_to_date', | ||||
|         'vendor_id', | ||||
|         'last_viewed' | ||||
|         'last_viewed', | ||||
|         'currency_id', | ||||
|     ]; | ||||
| 
 | ||||
|     protected $casts = [ | ||||
|  | ||||
| @ -54,6 +54,10 @@ class Subscription extends BaseModel | ||||
|         'price', | ||||
|         'name', | ||||
|         'currency_id', | ||||
|         'registration_required', | ||||
|         'optional_product_ids', | ||||
|         'optional_recurring_product_ids', | ||||
|         'use_inventory_management', | ||||
|     ]; | ||||
| 
 | ||||
|     protected $casts = [ | ||||
|  | ||||
| @ -40,6 +40,7 @@ class Task extends BaseModel | ||||
|         'number', | ||||
|         'is_date_based', | ||||
|         'status_order', | ||||
|         'invoice_lock' | ||||
|     ]; | ||||
| 
 | ||||
|     protected $touches = []; | ||||
|  | ||||
| @ -64,6 +64,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference | ||||
|         'email', | ||||
|         'is_primary', | ||||
|         'vendor_id', | ||||
|         'send_email', | ||||
|     ]; | ||||
| 
 | ||||
|     public function avatar() | ||||
|  | ||||
							
								
								
									
										31
									
								
								app/Policies/BankTransactionRulePolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/Policies/BankTransactionRulePolicy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Policies; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| 
 | ||||
| /** | ||||
|  * Class BankTransactionPolicy. | ||||
|  */ | ||||
| class BankTransactionRulePolicy extends EntityPolicy | ||||
| { | ||||
|     /** | ||||
|      *  Checks if the user has create permissions. | ||||
|      * | ||||
|      * @param  User $user | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function create(User $user) : bool | ||||
|     { | ||||
|         return $user->isAdmin(); | ||||
|     } | ||||
| } | ||||
| @ -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, | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Repositories; | ||||
| 
 | ||||
| use App\Jobs\Bank\MatchBankTransactions; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\Task; | ||||
| use App\Models\TaskStatus; | ||||
| @ -28,17 +29,25 @@ 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->service()->processRules(); | ||||
| 
 | ||||
|         return $bank_transaction->fresh(); | ||||
|     } | ||||
| 
 | ||||
|     public function convert_matched($bank_transactions) | ||||
|     { | ||||
|              $bank_transaction->invoice_ids = $invoice->hashed_id; | ||||
|              $bank_transaction->status_id = BankTransaction::STATUS_MATCHED; | ||||
|              $bank_transaction->save();    | ||||
|         } | ||||
| 
 | ||||
|         return $bank_transaction; | ||||
|     } | ||||
|         $data['transactions'] = $bank_transactions->map(function ($bt){ | ||||
|             return ['id' => $bt->id, 'invoice_ids' => $bt->invoice_ids]; | ||||
| 
 | ||||
|         })->toArray(); | ||||
| 
 | ||||
|         $bts = (new MatchBankTransactions(auth()->user()->company()->id, auth()->user()->company()->db, $data))->handle(); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								app/Repositories/BankTransactionRuleRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Repositories/BankTransactionRuleRepository.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Repositories; | ||||
| 
 | ||||
| use App\Models\BankTransactionRule; | ||||
| use App\Models\Task; | ||||
| use App\Models\TaskStatus; | ||||
| 
 | ||||
| /** | ||||
|  * Class for bank transaction rule repository. | ||||
|  */ | ||||
| class BankTransactionRuleRepository extends BaseRepository | ||||
| { | ||||
| 
 | ||||
|     public function save($data, BankTransactionRule $bank_transaction_rule) | ||||
|     { | ||||
| 
 | ||||
|         $bank_transaction_rule->fill($data); | ||||
| 
 | ||||
|         $bank_transaction_rule->save(); | ||||
| 
 | ||||
|         return $bank_transaction_rule; | ||||
|          | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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,15 +48,12 @@ 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) | ||||
| @ -64,21 +61,14 @@ class BankMatchingService implements ShouldQueue | ||||
|            ->cursor() | ||||
|            ->each(function ($bt){ | ||||
|              | ||||
|                             $invoice = $this->invoices->first(function ($value, $key) use ($bt){ | ||||
| 
 | ||||
|                                     return str_contains($bt->description, $value->number); | ||||
|                (new BankService($bt))->processRules(); | ||||
| 
 | ||||
|            }); | ||||
| 
 | ||||
|                             if($invoice) | ||||
|     } | ||||
| 
 | ||||
|     public function middleware() | ||||
|     { | ||||
|                                 $bt->invoice_ids = $invoice->hashed_id; | ||||
|                                 $bt->status_id = BankTransaction::STATUS_MATCHED; | ||||
|                                 $bt->save();    | ||||
|         return [new WithoutOverlapping($this->company_id)]; | ||||
|     } | ||||
| 
 | ||||
|                        }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Services\Bank; | ||||
| 
 | ||||
| use App\Models\BankTransaction; | ||||
| use App\Services\AbstractService; | ||||
| 
 | ||||
| class ProcessBankRule extends AbstractService | ||||
| { | ||||
| 
 | ||||
|     public function __construct(private BankTransaction $bank_transaction, $rule){} | ||||
| 
 | ||||
|     public function run() : void | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|      | ||||
| } | ||||
							
								
								
									
										206
									
								
								app/Services/Bank/ProcessBankRules.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								app/Services/Bank/ProcessBankRules.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,206 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Services\Bank; | ||||
| 
 | ||||
| use App\Factory\ExpenseCategoryFactory; | ||||
| use App\Factory\ExpenseFactory; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\ExpenseCategory; | ||||
| use App\Models\Invoice; | ||||
| use App\Services\AbstractService; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use Illuminate\Support\Carbon; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| 
 | ||||
| class ProcessBankRules extends AbstractService | ||||
| { | ||||
|     use GeneratesCounter; | ||||
| 
 | ||||
|     protected $credit_rules; | ||||
| 
 | ||||
|     protected $debit_rules; | ||||
| 
 | ||||
|     protected $categories; | ||||
| 
 | ||||
|     public function __construct(public BankTransaction $bank_transaction){} | ||||
| 
 | ||||
|     public function run() | ||||
|     { | ||||
|         if($this->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_transaction_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_transaction_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, | ||||
|         }; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -29,24 +29,30 @@ class ClientService | ||||
| 
 | ||||
|     public function updateBalance(float $amount) | ||||
|     { | ||||
|         // $this->client->balance += $amount;
 | ||||
| 
 | ||||
|         try { | ||||
|             \DB::connection(config('database.default'))->transaction(function () use($amount) { | ||||
| 
 | ||||
|                 nlog("inside transaction - updating balance by {$amount}"); | ||||
| 
 | ||||
|                 $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); | ||||
|                 $this->client->balance += $amount; | ||||
|                 $this->client->save(); | ||||
| 
 | ||||
|             }, 2); | ||||
|         } | ||||
|         catch (\Throwable $throwable) { | ||||
|             nlog("DB ERROR " . $throwable->getMessage()); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     public function updateBalanceAndPaidToDate(float $balance, float $paid_to_date) | ||||
|     { | ||||
|         // $this->client->balance += $amount;
 | ||||
|         // $this->client->paid_to_date += $amount;
 | ||||
| 
 | ||||
|         try { | ||||
|             \DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) { | ||||
| 
 | ||||
|                 $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); | ||||
| @ -55,6 +61,12 @@ class ClientService | ||||
|                 $this->client->save(); | ||||
| 
 | ||||
|             }, 2); | ||||
|         } | ||||
|         catch (\Throwable $throwable) { | ||||
|             nlog("DB ERROR " . $throwable->getMessage()); | ||||
|         } | ||||
|     | ||||
| 
 | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| @ -147,7 +147,7 @@ class ApplyPayment | ||||
|         event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
| 
 | ||||
|         if ((int) $this->invoice->balance == 0) { | ||||
|             $this->invoice->service()->deletePdf(); | ||||
|             $this->invoice->service()->touchPdf(); | ||||
|             $this->invoice = $this->invoice->fresh(); | ||||
|             event(new InvoiceWasPaid($this->invoice, $this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
|         } | ||||
|  | ||||
| @ -103,7 +103,7 @@ class ApplyPayment extends AbstractService | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         $this->invoice->service()->applyNumber()->workFlow()->save(); | ||||
|         $this->invoice->service()->applyNumber()->workFlow()->touchPdf()->save(); | ||||
| 
 | ||||
|         $transaction = [ | ||||
|             'invoice' => $this->invoice->transaction_event(), | ||||
|  | ||||
| @ -83,7 +83,7 @@ class ApplyPaymentAmount extends AbstractService | ||||
|                 ->updatePaidToDate($payment->amount) | ||||
|                 ->setCalculatedStatus() | ||||
|                 ->applyNumber() | ||||
|                 ->deletePdf() | ||||
|                 ->touchPdf() | ||||
|                 ->save(); | ||||
| 
 | ||||
|         $this->invoice | ||||
|  | ||||
| @ -112,10 +112,12 @@ class InvoiceService | ||||
|      * @param  Payment $payment        The Payment | ||||
|      * @param  float   $payment_amount The Payment amount | ||||
|      * @return InvoiceService          Parent class object | ||||
|      * @deprecated 24-11-2022 - cannot find any references to this method anywhere | ||||
|      */ | ||||
|     public function applyPayment(Payment $payment, float $payment_amount) | ||||
|     { | ||||
|         $this->deletePdf(); | ||||
|         // $this->deletePdf();
 | ||||
|         $this->invoice = $this->markSent()->save(); | ||||
| 
 | ||||
|         $this->invoice = (new ApplyPayment($this->invoice, $payment, $payment_amount))->run(); | ||||
| 
 | ||||
| @ -218,7 +220,6 @@ class InvoiceService | ||||
|     public function markDeleted() | ||||
|     { | ||||
|         $this->removeUnpaidGatewayFees(); | ||||
|         $this->deletePdf(); | ||||
| 
 | ||||
|         $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); | ||||
| 
 | ||||
| @ -378,6 +379,7 @@ class InvoiceService | ||||
|                                      })->toArray(); | ||||
| 
 | ||||
|         $this->invoice = $this->invoice->calc()->getInvoice(); | ||||
|         $this->invoice->service()->touchPdf(); | ||||
| 
 | ||||
|         /* 24-03-2022 */ | ||||
|         $new_balance = $this->invoice->balance; | ||||
|  | ||||
							
								
								
									
										90
									
								
								app/Transformers/BankTransactionRuleTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								app/Transformers/BankTransactionRuleTransformer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Transformers; | ||||
| 
 | ||||
| use App\Models\Account; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\BankTransactionRule; | ||||
| use App\Models\Company; | ||||
| use App\Models\Expense; | ||||
| use App\Models\Invoice; | ||||
| use App\Transformers\VendorTransformer; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| /** | ||||
|  * Class BankTransactionRuleTransformer. | ||||
|  */ | ||||
| class BankTransactionRuleTransformer extends EntityTransformer | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $defaultIncludes = [ | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $availableIncludes = [ | ||||
|         'company', | ||||
|         'vendor', | ||||
|         'client', | ||||
|         'expense_category', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @param BankTransaction $bank_integration | ||||
|      * @return array | ||||
|      */ | ||||
|     public function transform(BankTransactionRule $bank_transaction_rule) | ||||
|     { | ||||
|         return [ | ||||
|             'id' => (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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -67,6 +67,7 @@ class BankTransactionTransformer extends EntityTransformer | ||||
|             'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '', | ||||
|             'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '', | ||||
|             'vendor_id'=> (string) $this->encodePrimaryKey($bank_transaction->vendor_id) ?: '', | ||||
|             'bank_transaction_rule_id' => (string) $this->encodePrimaryKey($bank_transaction->bank_transaction_rule_id) ?: '', | ||||
|             'is_deleted' => (bool) $bank_transaction->is_deleted, | ||||
|             'created_at' => (int) $bank_transaction->created_at, | ||||
|             'updated_at' => (int) $bank_transaction->updated_at, | ||||
|  | ||||
| @ -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', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
| @ -186,6 +188,8 @@ class CompanyTransformer extends EntityTransformer | ||||
|             'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates, | ||||
|             'invoice_task_project' => (bool) $company->invoice_task_project, | ||||
|             'report_include_deleted' => (bool) $company->report_include_deleted, | ||||
|             'invoice_task_lock' => (bool) $company->invoice_task_lock, | ||||
|             'use_vendor_currency' => (bool) $company->use_vendor_currency, | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
| @ -231,6 +235,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); | ||||
|  | ||||
| @ -132,6 +132,7 @@ class PurchaseOrderTransformer extends EntityTransformer | ||||
|             'paid_to_date' => (float)$purchase_order->paid_to_date, | ||||
|             'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id), | ||||
|             'expense_id' => $this->encodePrimaryKey($purchase_order->expense_id), | ||||
|             'currency_id' => $purchase_order->currency_id ? (string) $purchase_order->currency_id : '', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -68,6 +68,10 @@ class SubscriptionTransformer extends EntityTransformer | ||||
|             'updated_at' => (int) $subscription->updated_at, | ||||
|             'archived_at' => (int) $subscription->deleted_at, | ||||
|             'plan_map' => '', //@deprecated 03/04/2021
 | ||||
|             'use_inventory_management' => (bool) $subscription->use_inventory_management, | ||||
|             'optional_recurring_product_ids' =>(string)$subscription->optional_recurring_product_ids, | ||||
|             'optional_product_ids' => (string) $subscription->optional_product_ids, | ||||
|             'registration_required' => (bool) $subscription->registration_required, | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -72,7 +72,6 @@ class TaskTransformer extends EntityTransformer | ||||
|             'user_id' => (string) $this->encodePrimaryKey($task->user_id), | ||||
|             'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id), | ||||
|             'number' => (string) $task->number ?: '', | ||||
|             // 'start_time' => (int) $task->start_time,
 | ||||
|             'description' => (string) $task->description ?: '', | ||||
|             'duration' => (int) $task->duration ?: 0, | ||||
|             'rate' => (float) $task->rate ?: 0, | ||||
| @ -93,6 +92,7 @@ class TaskTransformer extends EntityTransformer | ||||
|             'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
 | ||||
|             'is_date_based' => (bool) $task->is_date_based, | ||||
|             'status_order' => is_null($task->status_order) ? null : (int) $task->status_order, | ||||
|             'invoice_lock' => (bool) $task->invoice_lock, | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -68,7 +68,7 @@ trait Inviteable | ||||
|         ); | ||||
|         $writer = new Writer($renderer); | ||||
| 
 | ||||
|         $qr = $writer->writeString($this->getPaymentLink()); | ||||
|         $qr = $writer->writeString($this->getPaymentLink(), 'utf-8'); | ||||
| 
 | ||||
|         return "<svg viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
 | ||||
|           <rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
 | ||||
| @ -80,7 +80,7 @@ trait Inviteable | ||||
|         if (Ninja::isHosted()) { | ||||
|             $domain = $this->company->domain(); | ||||
|         } else { | ||||
|             $domain = config('ninja.app_url'); | ||||
|             $domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url'); | ||||
|         } | ||||
| 
 | ||||
|         $entity_type = Str::snake(class_basename($this->entityType())); | ||||
| @ -95,7 +95,7 @@ trait Inviteable | ||||
|         if (Ninja::isHosted()) { | ||||
|             $domain = $this->company->domain(); | ||||
|         } else { | ||||
|             $domain = config('ninja.app_url'); | ||||
|             $domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url'); | ||||
|         } | ||||
| 
 | ||||
|         switch ($this->company->portal_mode) { | ||||
| @ -121,7 +121,7 @@ trait Inviteable | ||||
|         if (Ninja::isHosted()) { | ||||
|             $domain = $this->company->domain(); | ||||
|         } else { | ||||
|             $domain = config('ninja.app_url'); | ||||
|             $domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url'); | ||||
|         } | ||||
| 
 | ||||
|         switch ($this->company->portal_mode) { | ||||
|  | ||||
| @ -211,4 +211,5 @@ return [ | ||||
|         'dev_mode' => env("YODLEE_DEV_MODE", false), | ||||
|         'config_name' => env("YODLEE_CONFIG_NAME", false), | ||||
|     ], | ||||
|     'dbs' => ['db-ninja-01','db-ninja-02'] | ||||
| ]; | ||||
|  | ||||
							
								
								
									
										31
									
								
								database/factories/BankTransactionRuleFactory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								database/factories/BankTransactionRuleFactory.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace Database\Factories; | ||||
| 
 | ||||
| use App\Models\Account; | ||||
| use Illuminate\Database\Eloquent\Factories\Factory; | ||||
| use Illuminate\Support\Str; | ||||
| 
 | ||||
| class BankTransactionRuleFactory extends Factory | ||||
| { | ||||
|     /** | ||||
|      * Define the model's default state. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function definition() | ||||
|     { | ||||
|         return [ | ||||
|             'name' =>$this->faker->name(), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function up() | ||||
|     { | ||||
|          | ||||
|         Schema::create('bank_transaction_rules', function (Blueprint $table) { | ||||
|             $table->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() | ||||
|     { | ||||
|         //
 | ||||
|     } | ||||
| }; | ||||
| @ -0,0 +1,83 @@ | ||||
| <?php | ||||
| 
 | ||||
| use App\Models\Currency; | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function up() | ||||
|     { | ||||
|         Schema::table('tasks', function (Blueprint $table) | ||||
|         { | ||||
|             $table->boolean('invoice_lock')->default(false); | ||||
|         }); | ||||
| 
 | ||||
|         Schema::table('companies', function (Blueprint $table) | ||||
|         { | ||||
|             $table->boolean('invoice_task_lock')->default(false); | ||||
|             $table->boolean('use_vendor_currency')->default(false); | ||||
|         }); | ||||
| 
 | ||||
|         Schema::table('purchase_orders', function (Blueprint $table) | ||||
|         { | ||||
|             $table->unsignedInteger('currency_id')->nullable(); | ||||
|         }); | ||||
| 
 | ||||
|         Schema::table('bank_transactions', function (Blueprint $table) | ||||
|         { | ||||
|             $table->bigInteger('bank_transaction_rule_id')->nullable(); | ||||
|         }); | ||||
| 
 | ||||
|         Schema::table('subscriptions', function (Blueprint $table) | ||||
|         { | ||||
|             $table->boolean('registration_required')->default(false); | ||||
|             $table->boolean('use_inventory_management')->default(false); | ||||
|             $table->text('optional_product_ids')->nullable(); | ||||
|             $table->text('optional_recurring_product_ids')->nullable(); | ||||
|              | ||||
|         }); | ||||
| 
 | ||||
|         $currencies = [ | ||||
| 
 | ||||
|             ['id' => 113, 'name' => 'Swazi lilangeni', 'code' => 'SZL', 'symbol' => 'E', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
| 
 | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($currencies as $currency) { | ||||
|             $record = Currency::whereCode($currency['code'])->first(); | ||||
|             if ($record) { | ||||
|                 $record->name = $currency['name']; | ||||
|                 $record->symbol = $currency['symbol']; | ||||
|                 $record->precision = $currency['precision']; | ||||
|                 $record->thousand_separator = $currency['thousand_separator']; | ||||
|                 $record->decimal_separator = $currency['decimal_separator']; | ||||
|                 if (isset($currency['swap_currency_symbol'])) { | ||||
|                     $record->swap_currency_symbol = $currency['swap_currency_symbol']; | ||||
|                 } | ||||
|                 $record->save(); | ||||
|             } else { | ||||
|                 Currency::create($currency); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         \Illuminate\Support\Facades\Artisan::call('ninja:design-update'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function down() | ||||
|     { | ||||
|         //
 | ||||
|     } | ||||
| }; | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -127,6 +127,15 @@ class CurrenciesSeeder extends Seeder | ||||
|             ['id' => 102, 'name' => 'Moldovan Leu', 'code' => 'MDL', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 103, 'name' => 'Kazakhstani Tenge', 'code' => 'KZT', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 104, 'name' => 'Ethiopian Birr', 'code' => 'ETB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 105, 'name' => 'Gambia Dalasi', 'code' => 'GMD', 'symbol' => 'D', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 106, 'name' => 'Paraguayan Guarani', 'code' => 'PYG', 'symbol' => '₲', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 107, 'name' => 'Malawi Kwacha', 'code' => 'MWK', 'symbol' => 'MK', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 108, 'name' => 'Zimbabwean Dollar', 'code' => 'ZWL', 'symbol' => 'Z$', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 109, 'name' => 'Cambodian Riel', 'code' => 'KHR', 'symbol' => '៛', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 110, 'name' => 'Vanuatu Vatu', 'code' => 'VUV', 'symbol' => 'VT', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 111, 'name' => 'Cuban Peso', 'code' => 'CUP', 'symbol' => '₱', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 112, 'name' => 'Cayman Island Dollar', 'code' => 'KYD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|             ['id' => 113, 'name' => 'Swazi lilangeni', 'code' => 'SZL', 'symbol' => 'E', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($currencies as $currency) { | ||||
|  | ||||
| @ -4843,6 +4843,11 @@ $LANG = array( | ||||
|     'refresh_accounts' => 'Refresh Accounts', | ||||
|     'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account', | ||||
|     'click_here_to_connect_bank_account' => 'Click here to connect your bank account', | ||||
|     'task_update_authorization_error' => 'Insufficient permissions, or task may be locked', | ||||
|     'cash_vs_accrual' => 'Accrual accounting', | ||||
|     'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.', | ||||
|     'expense_paid_report' => 'Expensed reporting', | ||||
|     'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses', | ||||
| ); | ||||
| 
 | ||||
| return $LANG; | ||||
|  | ||||
							
								
								
									
										65
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										65
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -5,6 +5,8 @@ | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "dependencies": { | ||||
|                 "@tailwindcss/forms": "^0.3.4", | ||||
|                 "@tailwindcss/line-clamp": "^0.3.1", | ||||
|                 "autoprefixer": "^10.3.7", | ||||
|                 "axios": "^0.25", | ||||
|                 "card-js": "^1.0.13", | ||||
| @ -25,6 +27,7 @@ | ||||
|             "devDependencies": { | ||||
|                 "@babel/compat-data": "7.15.0", | ||||
|                 "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||
|                 "@tailwindcss/aspect-ratio": "^0.4.2", | ||||
|                 "laravel-mix-purgecss": "^6.0.0", | ||||
|                 "vue-template-compiler": "^2.6.14" | ||||
|             } | ||||
| @ -1682,6 +1685,34 @@ | ||||
|                 "node": ">= 8" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@tailwindcss/aspect-ratio": { | ||||
|             "version": "0.4.2", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz", | ||||
|             "integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==", | ||||
|             "dev": true, | ||||
|             "peerDependencies": { | ||||
|                 "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@tailwindcss/forms": { | ||||
|             "version": "0.3.4", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz", | ||||
|             "integrity": "sha512-vlAoBifNJUkagB+PAdW4aHMe4pKmSLroH398UPgIogBFc91D2VlHUxe4pjxQhiJl0Nfw53sHSJSQBSTQBZP3vA==", | ||||
|             "dependencies": { | ||||
|                 "mini-svg-data-uri": "^1.2.3" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "tailwindcss": ">=2.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@tailwindcss/line-clamp": { | ||||
|             "version": "0.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.3.1.tgz", | ||||
|             "integrity": "sha512-pNr0T8LAc3TUx/gxCfQZRe9NB2dPEo/cedPHzUGIPxqDMhgjwNm6jYxww4W5l0zAsAddxr+XfZcqttGiFDgrGg==", | ||||
|             "peerDependencies": { | ||||
|                 "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@trysound/sax": { | ||||
|             "version": "0.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", | ||||
| @ -5742,6 +5773,14 @@ | ||||
|                 "url": "https://opencollective.com/webpack" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/mini-svg-data-uri": { | ||||
|             "version": "1.4.4", | ||||
|             "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", | ||||
|             "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", | ||||
|             "bin": { | ||||
|                 "mini-svg-data-uri": "cli.js" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/minimalistic-assert": { | ||||
|             "version": "1.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", | ||||
| @ -10285,6 +10324,27 @@ | ||||
|                 "fastq": "^1.6.0" | ||||
|             } | ||||
|         }, | ||||
|         "@tailwindcss/aspect-ratio": { | ||||
|             "version": "0.4.2", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz", | ||||
|             "integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==", | ||||
|             "dev": true, | ||||
|             "requires": {} | ||||
|         }, | ||||
|         "@tailwindcss/forms": { | ||||
|             "version": "0.3.4", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz", | ||||
|             "integrity": "sha512-vlAoBifNJUkagB+PAdW4aHMe4pKmSLroH398UPgIogBFc91D2VlHUxe4pjxQhiJl0Nfw53sHSJSQBSTQBZP3vA==", | ||||
|             "requires": { | ||||
|                 "mini-svg-data-uri": "^1.2.3" | ||||
|             } | ||||
|         }, | ||||
|         "@tailwindcss/line-clamp": { | ||||
|             "version": "0.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.3.1.tgz", | ||||
|             "integrity": "sha512-pNr0T8LAc3TUx/gxCfQZRe9NB2dPEo/cedPHzUGIPxqDMhgjwNm6jYxww4W5l0zAsAddxr+XfZcqttGiFDgrGg==", | ||||
|             "requires": {} | ||||
|         }, | ||||
|         "@trysound/sax": { | ||||
|             "version": "0.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", | ||||
| @ -13399,6 +13459,11 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "mini-svg-data-uri": { | ||||
|             "version": "1.4.4", | ||||
|             "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", | ||||
|             "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==" | ||||
|         }, | ||||
|         "minimalistic-assert": { | ||||
|             "version": "1.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", | ||||
|  | ||||
| @ -11,10 +11,13 @@ | ||||
|     "devDependencies": { | ||||
|         "@babel/compat-data": "7.15.0", | ||||
|         "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||
|         "@tailwindcss/aspect-ratio": "^0.4.2", | ||||
|         "laravel-mix-purgecss": "^6.0.0", | ||||
|         "vue-template-compiler": "^2.6.14" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@tailwindcss/line-clamp": "^0.3.1", | ||||
|         "@tailwindcss/forms": "^0.3.4", | ||||
|         "autoprefixer": "^10.3.7", | ||||
|         "axios": "^0.25", | ||||
|         "card-js": "^1.0.13", | ||||
|  | ||||
							
								
								
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								public/js/app.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/js/app.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								public/js/setup/setup.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/js/setup/setup.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,5 +1,5 @@ | ||||
| { | ||||
|     "/js/app.js": "/js/app.js?id=384185bf9d293949134d09b890c81369", | ||||
|     "/js/app.js": "/js/app.js?id=19300612c6880925e8043b61e8d49632", | ||||
|     "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=9fb77e87fe0f85a367050e08f79ec9df", | ||||
|     "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=803182f668c39d631ca5c55437876da4", | ||||
|     "/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js?id=6e9f466c5504d3753f9b4ffc6f947095", | ||||
| @ -15,7 +15,7 @@ | ||||
|     "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=6fb63bae43d077b5061f4dadfe8dffc8", | ||||
|     "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=cdc76607aaf0b47a5a4e554e4177713d", | ||||
|     "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=809de47258a681f0ffebe787dd6a9a93", | ||||
|     "/js/setup/setup.js": "/js/setup/setup.js?id=87367cce4927b42a92defdbae7a64711", | ||||
|     "/js/setup/setup.js": "/js/setup/setup.js?id=27560b012f166f8b9417ced2188aab70", | ||||
|     "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=8ce33c3deae058ad314fb8357e5be63b", | ||||
|     "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=be5307abc990bb44f2f92628103b1d98", | ||||
|     "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=c2caa29f753ad1f3a12ca45acddacd72", | ||||
| @ -42,7 +42,7 @@ | ||||
|     "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=3d53d2f7d0291d9f92cf7414dd2d351c", | ||||
|     "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=db71055862995fd6ae21becfc587a3de", | ||||
|     "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=914a6846ad1e5584635e7430fef76875", | ||||
|     "/css/app.css": "/css/app.css?id=6bafb560444b3b12f8d1ce59bd7fd703", | ||||
|     "/css/app.css": "/css/app.css?id=2c1ff2517e9909ca83760beb295535be", | ||||
|     "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c", | ||||
|     "/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d" | ||||
| } | ||||
|  | ||||
							
								
								
									
										17
									
								
								resources/views/billing-portal/purchasev2.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								resources/views/billing-portal/purchasev2.blade.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| @extends('portal.ninja2020.layout.clean') | ||||
| @section('meta_title', ctrans('texts.purchase')) | ||||
| 
 | ||||
| @section('body') | ||||
|     @livewire('billing-portal-purchasev2', ['subscription' => $subscription, 'company' => $subscription->company, 'contact' => auth()->guard('contact')->user(), 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null]) | ||||
| @stop | ||||
| 
 | ||||
| @push('footer') | ||||
|     <script> | ||||
|         function updateGatewayFields(companyGatewayId, paymentMethodId) { | ||||
|             document.getElementById('company_gateway_id').value = companyGatewayId; | ||||
|             document.getElementById('payment_method_id').value = paymentMethodId; | ||||
|         } | ||||
| 
 | ||||
|         Livewire.on('beforePaymentEventsCompleted', () => document.getElementById('payment-method-form').submit()); | ||||
|     </script> | ||||
| @endpush | ||||
| @ -321,6 +321,17 @@ | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
| 
 | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -306,6 +306,16 @@ | ||||
|         position:  fixed; | ||||
|     }  | ||||
|      | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -293,6 +293,18 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|      | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -287,6 +287,17 @@ | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|      | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -258,6 +258,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -263,6 +263,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }      | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -280,6 +280,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -307,6 +307,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|       /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|       /** Hide company logo **/ | ||||
|  | ||||
| @ -250,6 +250,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     } | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -324,6 +324,17 @@ | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|      | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -287,6 +287,17 @@ | ||||
|         z-index:200 !important; | ||||
|         position:  fixed; | ||||
|     }  | ||||
| 
 | ||||
|     .project-header { | ||||
|         font-size: 1.2em; | ||||
|         margin-top: 0.1em; | ||||
|         margin-bottom: 0; | ||||
|         padding-bottom: 0; | ||||
|         margin-left: 0; | ||||
|         margin-right: 0; | ||||
|         font-weight: bold; | ||||
|         color: #505050; | ||||
|     }  | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
|     /** Hide company logo **/ | ||||
|  | ||||
| @ -0,0 +1,152 @@ | ||||
| <style type="text/css"> | ||||
|      | ||||
| </style> | ||||
| 
 | ||||
| <div class="grid grid-cols-12"> | ||||
|     <div class="col-span-12 xl:col-span-8 bg-gray-50 flex flex-col max-h-100px items-center h-screen"> | ||||
|         <div class="w-full p-8 md:max-w-3xl"> | ||||
|             <img class="object-scale-down" style="max-height: 100px;"src="{{ $subscription->company->present()->logo }}" alt="{{ $subscription->company->present()->name }}"> | ||||
| 
 | ||||
|             <h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide mt-6"> | ||||
|             {{ $subscription->name }} | ||||
|             </h1> | ||||
|         </div> | ||||
|         <div class="w-full p-4 md:max-w-3xl"> | ||||
|             @if(!empty($subscription->recurring_product_ids)) | ||||
|                 <p | ||||
|                     class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium"> | ||||
|                     {{ ctrans('texts.recurring_purchases') }} | ||||
|                 </p> | ||||
|               <ul role="list" class="divide-y divide-gray-200 bg-white"> | ||||
|                 @foreach($subscription->service()->recurring_products() as $product) | ||||
|                 <li> | ||||
|                   <a href="#" class="block hover:bg-gray-50"> | ||||
|                     <div class="px-4 py-4 sm:px-6"> | ||||
|                       <div class="flex items-center justify-between"> | ||||
|                         <div class="ml-2 flex flex-shrink-0"> | ||||
|                           <p class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800"></p> | ||||
|                         </div> | ||||
|                       </div> | ||||
|                       <div class="mt-0 sm:flex sm:justify-between"> | ||||
|                         <div class="sm:flex"> | ||||
|                           <p class="text-sm font-medium text-gray-900 mt-0">{!! nl2br($product->notes) !!}</p> | ||||
|                         </div> | ||||
|                         <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0"> | ||||
|                             <span data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }} / {{ App\Models\RecurringInvoice::frequencyForKey($subscription->frequency_id) }}</span> | ||||
|                         </div> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </a> | ||||
|                 </li> | ||||
|                 @endforeach | ||||
|             </ul> | ||||
|             @endif | ||||
|         </div> | ||||
|         <div class="w-full p-4 md:max-w-3xl"> | ||||
| 
 | ||||
|             @if(!empty($subscription->product_ids)) | ||||
|                 <p | ||||
|                     class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium"> | ||||
|                     {{ ctrans('texts.one_time_purchases') }} | ||||
|                 </p> | ||||
|                 <ul role="list" class="divide-y divide-gray-200 bg-white"> | ||||
|                     @foreach($subscription->service()->products() as $product) | ||||
|                     <li> | ||||
|                       <a href="#" class="block hover:bg-gray-50"> | ||||
|                         <div class="px-4 py-4 sm:px-6"> | ||||
|                           <div class="flex items-center justify-between"> | ||||
|                             <p class="truncate text-sm font-medium text-gray-600"></p> | ||||
|                             <div class="ml-2 flex flex-shrink-0"> | ||||
|                               <p class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800"></p> | ||||
|                             </div> | ||||
|                           </div> | ||||
|                           <div class="mt-2 sm:flex sm:justify-between"> | ||||
|                             <div class="sm:flex"> | ||||
|                               <p class="text-sm font-medium text-gray-900 mt-2">{!! nl2br($product->notes) !!}</p> | ||||
|                             </div> | ||||
|                             <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0"> | ||||
|                                 <span data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span> | ||||
|                             </div> | ||||
|                           </div> | ||||
|                         </div> | ||||
|                       </a> | ||||
|                     </li> | ||||
|                     @endforeach | ||||
|                 </ul> | ||||
|             @endif | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="relative mt-8"> | ||||
|             <div class="absolute inset-0 flex items-center"> | ||||
|                 <div class="w-full border-t border-gray-300"></div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="relative flex justify-center text-sm leading-5"> | ||||
|                 <h1 class="text-2xl font-bold tracking-wide bg-gray-50 px-6 py-0">Optional products</h1> | ||||
|             </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="w-full p-4 md:max-w-3xl"> | ||||
|             @if(!empty($subscription->recurring_product_ids)) | ||||
|                 @foreach($subscription->service()->recurring_products() as $product) | ||||
|                 <div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border"> | ||||
|                     <div class="text-sm">{!! nl2br($product->notes) !!}</div> | ||||
|                     <div data-ref="price-and-quantity-container"> | ||||
|                         <span data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }} / {{ App\Models\RecurringInvoice::frequencyForKey($subscription->frequency_id) }}</span> | ||||
|                         {{--                                <span data-ref="quantity" class="text-sm">(1x)</span>--}} | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 @endforeach | ||||
|             @endif | ||||
|         </div> | ||||
|         <div class="w-full p-4 md:max-w-3xl"> | ||||
| 
 | ||||
|             @if(!empty($subscription->product_ids)) | ||||
|                 @foreach($subscription->service()->products() as $product) | ||||
|                     <div class="flex items-center justify-between mb-4 bg-white rounded px-6 py-4 shadow-sm border"> | ||||
|                         <div class="text-sm">{!! nl2br($product->notes) !!}</div> | ||||
|                         <div data-ref="price-and-quantity-container"> | ||||
|                             <span | ||||
|                                 data-ref="price">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span> | ||||
|                             {{--                                <span data-ref="quantity" class="text-sm">(1x)</span>--}} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 @endforeach | ||||
|             @endif | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|     <div class="col-span-12 xl:col-span-4 bg-blue-500 flex flex-col item-center "> | ||||
|         <div class="w-full p-4 md:max-w-3xl"> | ||||
|             <div id="summary" class="w-1/4 px-8 text-white"> | ||||
|                 <h1 class="font-semibold text-2xl border-b pb-8 text-white">Order Summary</h1> | ||||
|                 <div class="flex justify-between mt-10 mb-5"> | ||||
|                   <span class="font-semibold text-sm uppercase">Items 3</span> | ||||
|                   <span class="font-semibold text-sm">590$</span> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                   <label class="font-medium inline-block mb-3 text-sm uppercase">Shipping</label> | ||||
|                   <select class="block p-2 text-white w-full text-sm"> | ||||
|                     <option>Standard shipping - $10.00</option> | ||||
|                   </select> | ||||
|                 </div> | ||||
|                 <div class="py-10"> | ||||
|                   <label for="promo" class="font-semibold inline-block mb-3 text-sm uppercase">Promo Code</label> | ||||
|                   <input type="text" id="promo" placeholder="Enter your code" class="p-2 text-sm w-full"> | ||||
|                 </div> | ||||
|                 <button class="bg-white hover:bg-gray-600 px-5 py-2 text-sm text-blue-500 uppercase">Apply</button> | ||||
|                 <div class="border-t mt-8"> | ||||
|                   <div class="flex font-semibold justify-between py-6 text-sm uppercase"> | ||||
|                     <span>Total cost</span> | ||||
|                     <span>$600</span> | ||||
|                   </div> | ||||
|                   <button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| @ -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'); | ||||
|  | ||||
| @ -115,6 +115,8 @@ Route::post('payments/process/response', [App\Http\Controllers\ClientPortal\Paym | ||||
| Route::get('payments/process/response', [App\Http\Controllers\ClientPortal\PaymentController::class, 'response'])->name('client.payments.response.get')->middleware(['locale', 'domain_db', 'verify_hash']); | ||||
| 
 | ||||
| Route::get('client/subscriptions/{subscription}/purchase', [App\Http\Controllers\ClientPortal\SubscriptionPurchaseController::class, 'index'])->name('client.subscription.purchase')->middleware('domain_db'); | ||||
| Route::get('client/subscriptions/{subscription}/purchase/v2', [App\Http\Controllers\ClientPortal\SubscriptionPurchaseController::class, 'upgrade'])->name('client.subscription.upgrade')->middleware('domain_db'); | ||||
| 
 | ||||
| 
 | ||||
| Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () { | ||||
|     /*Invitation catches*/ | ||||
|  | ||||
							
								
								
									
										6
									
								
								tailwind.config.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								tailwind.config.js
									
									
									
									
										vendored
									
									
								
							| @ -17,5 +17,9 @@ module.exports = { | ||||
|         } | ||||
|     }, | ||||
|     variants: {}, | ||||
|     plugins: [] | ||||
|     plugins: [ | ||||
|         require('@tailwindcss/line-clamp'), | ||||
|         require('@tailwindcss/forms') | ||||
|     ] | ||||
| 
 | ||||
| }; | ||||
|  | ||||
							
								
								
									
										766
									
								
								tests/Feature/Bank/BankTransactionRuleTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										766
									
								
								tests/Feature/Bank/BankTransactionRuleTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,766 @@ | ||||
| <?php | ||||
| 
 | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace Tests\Feature\Bank; | ||||
| 
 | ||||
| use App\Factory\BankIntegrationFactory; | ||||
| use App\Factory\BankTransactionFactory; | ||||
| use App\Models\BankIntegration; | ||||
| 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; | ||||
| 
 | ||||
| class BankTransactionRuleTest extends TestCase | ||||
| { | ||||
| 
 | ||||
|     use DatabaseTransactions; | ||||
|     use MockAccountData; | ||||
| 
 | ||||
|     protected function setUp() :void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         $this->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); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										230
									
								
								tests/Feature/BankTransactionRuleApiTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								tests/Feature/BankTransactionRuleApiTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,230 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace Tests\Feature; | ||||
| 
 | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
| use Illuminate\Support\Facades\Session; | ||||
| use Illuminate\Validation\ValidationException; | ||||
| use Tests\MockAccountData; | ||||
| use Tests\TestCase; | ||||
| 
 | ||||
| /** | ||||
|  * @test | ||||
|  * @covers App\Http\Controllers\BankTransactionRuleController | ||||
|  */ | ||||
| class BankTransactionRuleApiTest extends TestCase | ||||
| { | ||||
|     use MakesHash; | ||||
|     use DatabaseTransactions; | ||||
|     use MockAccountData; | ||||
| 
 | ||||
|     protected function setUp() :void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         $this->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']); | ||||
|     } | ||||
| } | ||||
| @ -83,7 +83,7 @@ class SubscriptionApiTest extends TestCase | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->post('/api/v1/subscriptions', ['product_ids' => $product->id, 'allow_cancellation' => true, 'name' => Str::random(5)]); | ||||
|         ])->post('/api/v1/subscriptions', ['product_ids' => $product->hashed_id, 'allow_cancellation' => true, 'name' => Str::random(5)]); | ||||
| 
 | ||||
|         // nlog($response);
 | ||||
|         $response->assertStatus(200); | ||||
| @ -98,7 +98,7 @@ class SubscriptionApiTest extends TestCase | ||||
| 
 | ||||
|         $response1 = $this | ||||
|             ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token]) | ||||
|             ->post('/api/v1/subscriptions', ['product_ids' => $product->id, 'name' => Str::random(5)]) | ||||
|             ->post('/api/v1/subscriptions', ['product_ids' => $product->hashed_id, 'name' => Str::random(5)]) | ||||
|             ->assertStatus(200) | ||||
|             ->json(); | ||||
| 
 | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| namespace Tests\Feature; | ||||
| 
 | ||||
| use App\Models\Task; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
| @ -42,6 +43,90 @@ class TaskApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testTaskLockingGate() | ||||
|     { | ||||
|         $data = [ | ||||
|             'timelog' => [[1,2],[3,4]], | ||||
|         ]; | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->post('/api/v1/tasks', $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
|         $response->assertStatus(200); | ||||
|          | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
| 
 | ||||
|         $task = Task::find($this->decodePrimaryKey($arr['data']['id'])); | ||||
|         $task->invoice_id = $this->invoice->id; | ||||
|         $task->save(); | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
| 
 | ||||
|         $task = Task::find($this->decodePrimaryKey($arr['data']['id'])); | ||||
|         $task->invoice_lock =true; | ||||
|         $task->invoice_id = $this->invoice->id; | ||||
|         $task->save(); | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
| 
 | ||||
|         $response->assertStatus(401); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testTaskLocking() | ||||
|     { | ||||
|         $data = [ | ||||
|             'timelog' => [[1,2],[3,4]], | ||||
|             'invoice_lock' => true | ||||
|         ]; | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->post('/api/v1/tasks', $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
|         $response->assertStatus(200); | ||||
|          | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data); | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public function testTimeLogValidation() | ||||
|     { | ||||
|         $data = [ | ||||
| @ -75,9 +160,10 @@ class TaskApiTest extends TestCase | ||||
|         $arr = $response->json(); | ||||
|         $response->assertStatus(200); | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public function testTimeLogValidation2() | ||||
|     { | ||||
|         $data = [ | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user