mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 00:07:31 -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\Ninja\CompanySizeCheck; | ||||||
| use App\Jobs\Util\VersionCheck; | use App\Jobs\Util\VersionCheck; | ||||||
| use App\Models\Account; | use App\Models\Account; | ||||||
|  | use App\Models\BankIntegration; | ||||||
|  | use App\Models\BankTransaction; | ||||||
| use App\Models\Client; | use App\Models\Client; | ||||||
| use App\Models\ClientContact; | use App\Models\ClientContact; | ||||||
| use App\Models\Company; | use App\Models\Company; | ||||||
| @ -223,6 +225,18 @@ class DemoMode extends Command | |||||||
|             'company_id' => $company->id, |             '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'); |         $this->info('Creating '.$this->count.' clients'); | ||||||
| 
 | 
 | ||||||
|         for ($x = 0; $x < $this->count; $x++) { |         for ($x = 0; $x < $this->count; $x++) { | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ class S3Cleanup extends Command | |||||||
|      */ |      */ | ||||||
|     public function handle() |     public function handle() | ||||||
|     { |     { | ||||||
|         if (! Ninja::isHosted()) { |         if (!Ninja::isHosted()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -182,7 +182,7 @@ class Handler extends ExceptionHandler | |||||||
|         } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) { |         } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) { | ||||||
|             return response()->json(['message'=>'Fatal error'], 500); |             return response()->json(['message'=>'Fatal error'], 500); | ||||||
|         } elseif ($exception instanceof AuthorizationException) { |         } 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) { |         } elseif ($exception instanceof TokenMismatchException) { | ||||||
|             return redirect() |             return redirect() | ||||||
|                     ->back() |                     ->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; | namespace App\Filters; | ||||||
| 
 | 
 | ||||||
| use App\Models\User; | use App\Models\User; | ||||||
|  | use App\Utils\Traits\MakesHash; | ||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -19,6 +20,8 @@ use Illuminate\Database\Eloquent\Builder; | |||||||
|  */ |  */ | ||||||
| class TaskFilters extends QueryFilters | class TaskFilters extends QueryFilters | ||||||
| { | { | ||||||
|  |     use MakesHash; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Filter based on search text. |      * 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. |      * Sorts the list based on $sort. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -76,7 +76,7 @@ class EpcQrGenerator | |||||||
|             $this->formatMoney($this->amount), |             $this->formatMoney($this->amount), | ||||||
|             $this->sepa['purpose'], |             $this->sepa['purpose'], | ||||||
|             substr($this->invoice->number,0,34), |             substr($this->invoice->number,0,34), | ||||||
|             substr($this->invoice->public_notes,0,139), |             '', | ||||||
|             '' |             '' | ||||||
|         )), "\n"); |         )), "\n"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -110,6 +110,8 @@ class ActivityController extends BaseController | |||||||
|                     'vendor' => $activity->vendor ? $activity->vendor : '', |                     'vendor' => $activity->vendor ? $activity->vendor : '', | ||||||
|                     'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '', |                     'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '', | ||||||
|                     'purchase_order' => $activity->purchase_order ? $activity->purchase_order : '', |                     '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()); |                 return array_merge($arr, $activity->toArray()); | ||||||
|  | |||||||
| @ -481,19 +481,32 @@ class BankTransactionController extends BaseController | |||||||
|     { |     { | ||||||
|         $action = request()->input('action'); |         $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); |             return response()->json(['message' => 'Unsupported action.'], 400); | ||||||
| 
 | 
 | ||||||
|         $ids = request()->input('ids'); |         $ids = request()->input('ids'); | ||||||
|              |              | ||||||
|         $bank_transactions = BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); |         $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) { |             $bank_transactions->each(function ($bank_transaction, $key) use ($action) { | ||||||
|                 if (auth()->user()->can('edit', $bank_transaction)) { |                 if (auth()->user()->can('edit', $bank_transaction)) { | ||||||
|                     $this->bank_transaction_repo->{$action}($bank_transaction); |                     $this->bank_transaction_repo->{$action}($bank_transaction); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|  |         } | ||||||
|  |          | ||||||
|         /* Need to understand which permission are required for the given bulk action ie. view / edit */ |         /* 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()); |         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.system_logs', | ||||||
|           'company.bank_integrations', |           'company.bank_integrations', | ||||||
|           'company.bank_transactions', |           'company.bank_transactions', | ||||||
|  |           'company.bank_transaction_rules', | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|     private $mini_load = [ |     private $mini_load = [ | ||||||
| @ -126,6 +127,7 @@ class BaseController extends Controller | |||||||
|         'company.expense_categories', |         'company.expense_categories', | ||||||
|         'company.subscriptions', |         'company.subscriptions', | ||||||
|         'company.bank_integrations', |         'company.bank_integrations', | ||||||
|  |         'company.bank_transaction_rules', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function __construct() |     public function __construct() | ||||||
| @ -456,6 +458,13 @@ class BaseController extends Controller | |||||||
|                         $query->where('bank_transactions.user_id', $user->id); |                         $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); |                         $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. |      * Set locale for incoming request. | ||||||
|      * |      * | ||||||
| @ -56,4 +75,7 @@ class SubscriptionPurchaseController extends Controller | |||||||
|             App::setLocale($record->locale); |             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\UpdateInvoiceRequest; | ||||||
| use App\Http\Requests\Invoice\UpdateReminderRequest; | use App\Http\Requests\Invoice\UpdateReminderRequest; | ||||||
| use App\Http\Requests\Invoice\UploadInvoiceRequest; | use App\Http\Requests\Invoice\UploadInvoiceRequest; | ||||||
|  | use App\Jobs\Cron\AutoBill; | ||||||
| use App\Jobs\Entity\EmailEntity; | use App\Jobs\Entity\EmailEntity; | ||||||
| use App\Jobs\Invoice\BulkInvoiceJob; | use App\Jobs\Invoice\BulkInvoiceJob; | ||||||
| use App\Jobs\Invoice\StoreInvoice; | 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 */ |         /*If we are using bulk actions, we don't want to return anything */ | ||||||
|         switch ($action) { |         switch ($action) { | ||||||
|  |             case 'auto_bill': | ||||||
|  |                 AutoBill::dispatch($invoice->id, $invoice->company->db); | ||||||
|  |                 return $this->itemResponse($invoice); | ||||||
|  | 
 | ||||||
|             case 'clone_to_invoice': |             case 'clone_to_invoice': | ||||||
|                 $invoice = CloneInvoiceFactory::create($invoice, auth()->user()->id); |                 $invoice = CloneInvoiceFactory::create($invoice, auth()->user()->id); | ||||||
| 
 |  | ||||||
|                 return $this->itemResponse($invoice); |                 return $this->itemResponse($invoice); | ||||||
|                 break; |                  | ||||||
|             case 'clone_to_quote': |             case 'clone_to_quote': | ||||||
|                 $quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id); |                 $quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id); | ||||||
| 
 | 
 | ||||||
| @ -767,7 +771,7 @@ class InvoiceController extends BaseController | |||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case 'cancel': |             case 'cancel': | ||||||
|                 $invoice = $invoice->service()->handleCancellation()->deletePdf()->touchPdf()->save(); |                 $invoice = $invoice->service()->handleCancellation()->touchPdf()->save(); | ||||||
| 
 | 
 | ||||||
|                 if (! $bulk) { |                 if (! $bulk) { | ||||||
|                     $this->itemResponse($invoice); |                     $this->itemResponse($invoice); | ||||||
| @ -777,7 +781,7 @@ class InvoiceController extends BaseController | |||||||
|             case 'email': |             case 'email': | ||||||
|                 //check query parameter for email_type and set the template else use calculateTemplate
 |                 //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')); |                     $this->reminder_template = $invoice->client->getSetting(request()->input('email_type')); | ||||||
|                 } else { |                 } else { | ||||||
|                     $this->reminder_template = $invoice->calculateTemplate('invoice'); |                     $this->reminder_template = $invoice->calculateTemplate('invoice'); | ||||||
|  | |||||||
| @ -181,7 +181,10 @@ class MigrationController extends BaseController | |||||||
|         $company->tasks()->forceDelete(); |         $company->tasks()->forceDelete(); | ||||||
|         $company->vendors()->forceDelete(); |         $company->vendors()->forceDelete(); | ||||||
|         $company->expenses()->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(); |         $company->all_activities()->forceDelete(); | ||||||
| 
 | 
 | ||||||
|         $settings = $company->settings; |         $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="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="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="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="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="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"), |  *       @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(); |             $request->session()->invalidate(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         //magic links survive for 1 hour
 | ||||||
|         if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { |         if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { | ||||||
|             $payload = Cache::get($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')) { |         } elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) { | ||||||
|             if (MultiDB::findAndSetDbByContactKey($request->segment(3))) { |             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)) { |                     if (empty($client_contact->email)) { | ||||||
|                         $client_contact->email = Str::random(6).'@example.com'; |                         $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)) { |         } 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)) { |                 if (empty($client_contact->email)) { | ||||||
|                     $client_contact->email = Str::random(6).'@example.com'; |                     $client_contact->email = Str::random(6).'@example.com'; | ||||||
|                     $client_contact->save(); |                     $client_contact->save(); | ||||||
| @ -125,7 +134,11 @@ class ContactKeyLogin | |||||||
|                 return redirect($this->setRedirectPath()); |                 return redirect($this->setRedirectPath()); | ||||||
|             } |             } | ||||||
|         } elseif ($request->segment(3)) { |         } 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)) { |                 if (empty($client_contact->email)) { | ||||||
|                     $client_contact->email = Str::random(6).'@example.com'; |                     $client_contact->email = Str::random(6).'@example.com'; | ||||||
|                     $client_contact->save(); |                     $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(); |         $input = $this->all(); | ||||||
| 
 | 
 | ||||||
|  |         if(!isset($input['name'])) | ||||||
|  |             $input['name'] = 'Untitled Company'; | ||||||
|  | 
 | ||||||
|         if (array_key_exists('google_analytics_url', $input)) { |         if (array_key_exists('google_analytics_url', $input)) { | ||||||
|             $input['google_analytics_key'] = $input['google_analytics_url']; |             $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)) { |         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); |         $this->replace($input); | ||||||
|  | |||||||
| @ -74,9 +74,9 @@ class UpdateCompanyRequest extends Request | |||||||
|      |      | ||||||
|         $input = $this->all(); |         $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'] = $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)) { |         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()) { |         if (! $account->isFreeHostedClient()) { | ||||||
|             return $settings; |             return $settings; | ||||||
| @ -126,10 +126,12 @@ class UpdateCompanyRequest extends Request | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function addScheme($url, $scheme = 'https://') |     private function addScheme($url, $scheme = 'https://') | ||||||
|  |     { | ||||||
|  |         if(Ninja::isHosted()) | ||||||
|         { |         { | ||||||
|             $url = str_replace('http://', '', $url); |             $url = str_replace('http://', '', $url); | ||||||
| 
 |  | ||||||
|             $url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url; |             $url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return rtrim($url, '/'); |         return rtrim($url, '/'); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -125,6 +125,10 @@ class Request extends FormRequest | |||||||
|             $input['company_gateway_id'] = $this->decodePrimaryKey($input['company_gateway_id']); |             $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'])) { |         if (isset($input['client_contacts'])) { | ||||||
|             foreach ($input['client_contacts'] as $key => $contact) { |             foreach ($input['client_contacts'] as $key => $contact) { | ||||||
|                 if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) { |                 if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) { | ||||||
|  | |||||||
| @ -35,26 +35,34 @@ class StoreSubscriptionRequest extends Request | |||||||
|     public function rules() |     public function rules() | ||||||
|     { |     { | ||||||
|         $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)], |             '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); |         return $this->globalRules($rules); | ||||||
|  | |||||||
| @ -37,26 +37,34 @@ class UpdateSubscriptionRequest extends Request | |||||||
|     public function rules() |     public function rules() | ||||||
|     { |     { | ||||||
|         $rules = [ |         $rules = [ | ||||||
|             'product_ids' => ['sometimes'], |             'name' => ['bail','sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)], | ||||||
|             'recurring_product_ids' => ['sometimes'], |             'group_id' => ['bail','sometimes', 'nullable', Rule::exists('group_settings','id')->where('company_id', auth()->user()->company()->id)], | ||||||
|             'assigned_user_id' => ['sometimes'], |             'assigned_user_id' => ['bail','sometimes', 'nullable', Rule::exists('users','id')->where('account_id', auth()->user()->account_id)], | ||||||
|             'is_recurring' => ['sometimes'], |             'product_ids' => 'bail|sometimes|nullable|string', | ||||||
|             'frequency_id' => ['required_with:recurring_product_ids'], |             'recurring_product_ids' => 'bail|sometimes|nullable|string', | ||||||
|             'auto_bill' => ['sometimes'], |             'is_recurring' => 'bail|sometimes|bool', | ||||||
|             'promo_code' => ['sometimes'], |             'frequency_id' => 'bail|required_with:recurring_product_ids', | ||||||
|             'promo_discount' => ['sometimes'], |             'auto_bill' => 'bail|sometimes|nullable|string', | ||||||
|             'is_amount_discount' => ['sometimes'], |             'promo_code' => 'bail|sometimes|nullable|string', | ||||||
|             'allow_cancellation' => ['sometimes'], |             'promo_discount' => 'bail|sometimes|numeric', | ||||||
|             'per_set_enabled' => ['sometimes'], |             'is_amount_discount' => 'bail|sometimes|bool', | ||||||
|             'min_seats_limit' => ['sometimes'], |             'allow_cancellation' => 'bail|sometimes|bool', | ||||||
|             'max_seats_limit' => ['sometimes'], |             'per_set_enabled' => 'bail|sometimes|bool', | ||||||
|             'trial_enabled' => ['sometimes'], |             'min_seats_limit' => 'bail|sometimes|numeric', | ||||||
|             'trial_duration' => ['sometimes'], |             'max_seats_limit' => 'bail|sometimes|numeric', | ||||||
|             'allow_query_overrides' => ['sometimes'], |             'trial_enabled' => 'bail|sometimes|bool', | ||||||
|             'allow_plan_changes' => ['sometimes'], |             'trial_duration' => 'bail|sometimes|numeric', | ||||||
|             'refund_period' => ['sometimes'], |             'allow_query_overrides' => 'bail|sometimes|bool', | ||||||
|             'webhook_configuration' => ['array'], |             'allow_plan_changes' => 'bail|sometimes|bool', | ||||||
|             'name' => ['sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)], |             '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); |         return $this->globalRules($rules); | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ use App\Http\Requests\Request; | |||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use App\Utils\Traits\ChecksEntityStatus; | use App\Utils\Traits\ChecksEntityStatus; | ||||||
| use App\Utils\Traits\MakesHash; | use App\Utils\Traits\MakesHash; | ||||||
|  | use Illuminate\Auth\Access\AuthorizationException; | ||||||
| use Illuminate\Validation\Rule; | use Illuminate\Validation\Rule; | ||||||
| 
 | 
 | ||||||
| class UpdateTaskRequest extends Request | class UpdateTaskRequest extends Request | ||||||
| @ -29,6 +30,10 @@ class UpdateTaskRequest extends Request | |||||||
|      */ |      */ | ||||||
|     public function authorize() : bool |     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); |         return auth()->user()->can('edit', $this->task); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -87,4 +92,11 @@ class UpdateTaskRequest extends Request | |||||||
| 
 | 
 | ||||||
|         $this->replace($input); |         $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 |     public function authorize() : bool | ||||||
|     { |     { | ||||||
|         return auth()->user()->isAdmin(); |         return auth()->user(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ class ValidCompanyQuantity implements Rule | |||||||
|      */ |      */ | ||||||
|     public function passes($attribute, $value) |     public function passes($attribute, $value) | ||||||
|     { |     { | ||||||
|  |          | ||||||
|         if (Ninja::isSelfHost()) { |         if (Ninja::isSelfHost()) { | ||||||
|             return auth()->user()->company()->account->companies->count() < 10; |             return auth()->user()->company()->account->companies->count() < 10; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -46,6 +46,7 @@ use App\Repositories\PaymentRepository; | |||||||
| use App\Repositories\ProductRepository; | use App\Repositories\ProductRepository; | ||||||
| use App\Repositories\QuoteRepository; | use App\Repositories\QuoteRepository; | ||||||
| use App\Repositories\VendorRepository; | use App\Repositories\VendorRepository; | ||||||
|  | use App\Services\Bank\BankMatchingService; | ||||||
| use App\Utils\Traits\MakesHash; | use App\Utils\Traits\MakesHash; | ||||||
| use Illuminate\Support\Facades\Validator; | use Illuminate\Support\Facades\Validator; | ||||||
| use Symfony\Component\HttpFoundation\ParameterBag; | use Symfony\Component\HttpFoundation\ParameterBag; | ||||||
| @ -107,6 +108,8 @@ class Csv extends BaseImport implements ImportInterface | |||||||
|         $bank_transaction_count = $this->ingest($data, $entity_type); |         $bank_transaction_count = $this->ingest($data, $entity_type); | ||||||
|         $this->entity_count['bank_transactions'] = $bank_transaction_count; |         $this->entity_count['bank_transactions'] = $bank_transaction_count; | ||||||
| 
 | 
 | ||||||
|  |         BankMatchingService::dispatchSync($this->company->id, $this->company->db); | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function client() |     public function client() | ||||||
|  | |||||||
| @ -196,13 +196,19 @@ class MatchBankTransactions implements ShouldQueue | |||||||
|         $expense->payment_date = Carbon::parse($this->bt->date); |         $expense->payment_date = Carbon::parse($this->bt->date); | ||||||
|         $expense->transaction_reference = $this->bt->description; |         $expense->transaction_reference = $this->bt->description; | ||||||
|         $expense->transaction_id = $this->bt->id; |         $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->invoice_documents = $this->company->invoice_expense_documents; | ||||||
|         $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; |         $expense->should_be_invoiced = $this->company->mark_expenses_invoiceable; | ||||||
|         $expense->save(); |         $expense->save(); | ||||||
| 
 | 
 | ||||||
|         $this->bt->expense_id = $expense->id; |         $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->status_id = BankTransaction::STATUS_CONVERTED; | ||||||
|         $this->bt->save(); |         $this->bt->save(); | ||||||
| 
 | 
 | ||||||
| @ -254,6 +260,9 @@ class MatchBankTransactions implements ShouldQueue | |||||||
| 
 | 
 | ||||||
|         }, 1); |         }, 1); | ||||||
| 
 | 
 | ||||||
|  |         if(!$this->invoice) | ||||||
|  |             return; | ||||||
|  |          | ||||||
|         /* Create Payment */ |         /* Create Payment */ | ||||||
|         $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); |         $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -50,11 +50,20 @@ class ReminderJob implements ShouldQueue | |||||||
|             $this->processReminders(); |             $this->processReminders(); | ||||||
|         } else { |         } else { | ||||||
|             //multiDB environment, need to
 |             //multiDB environment, need to
 | ||||||
|  |             /* | ||||||
|             foreach (MultiDB::$dbs as $db) { |             foreach (MultiDB::$dbs as $db) { | ||||||
|                 MultiDB::setDB($db); |                 MultiDB::setDB($db); | ||||||
|                 nlog("set db {$db}"); |                 nlog("set db {$db}"); | ||||||
|                 $this->processReminders(); |                 $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')); |         nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s')); | ||||||
| 
 | 
 | ||||||
|  |         set_time_limit(0); | ||||||
|  | 
 | ||||||
|         Invoice::query() |         Invoice::query() | ||||||
|              ->where('is_deleted', 0) |              ->where('is_deleted', 0) | ||||||
|              ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) |              ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||||
| @ -77,16 +88,21 @@ class ReminderJob implements ShouldQueue | |||||||
|              }) |              }) | ||||||
|              ->with('invitations')->cursor()->each(function ($invoice) { |              ->with('invitations')->cursor()->each(function ($invoice) { | ||||||
|                  if ($invoice->isPayable()) { |                  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'); |                      $reminder_template = $invoice->calculateTemplate('invoice'); | ||||||
|                      nlog("reminder template = {$reminder_template}"); |                      nlog("reminder template = {$reminder_template}"); | ||||||
|                          $invoice->service()->touchReminder($reminder_template)->save(); |  | ||||||
|                      $invoice = $this->calcLateFee($invoice, $reminder_template); |                      $invoice = $this->calcLateFee($invoice, $reminder_template); | ||||||
| 
 |                      $invoice->service()->touchReminder($reminder_template)->save(); | ||||||
|                          $invoice->service()->touchPdf(); |                      $invoice->service()->touchPdf(true); | ||||||
| 
 | 
 | ||||||
|                      //20-04-2022 fixes for endless reminders - generic template naming was wrong
 |                      //20-04-2022 fixes for endless reminders - generic template naming was wrong
 | ||||||
|                      $enabled_reminder = 'enable_'.$reminder_template; |                      $enabled_reminder = 'enable_'.$reminder_template; | ||||||
| 
 |  | ||||||
|                      if ($reminder_template == 'endless_reminder') { |                      if ($reminder_template == 'endless_reminder') { | ||||||
|                          $enabled_reminder = 'enable_reminder_endless'; |                          $enabled_reminder = 'enable_reminder_endless'; | ||||||
|                      } |                      } | ||||||
| @ -99,7 +115,7 @@ class ReminderJob implements ShouldQueue | |||||||
|                 (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { |                 (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { | ||||||
| 
 | 
 | ||||||
|                          $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { |                          $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}"); |                              nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); | ||||||
|                          }); |                          }); | ||||||
| 
 | 
 | ||||||
| @ -112,6 +128,7 @@ class ReminderJob implements ShouldQueue | |||||||
|                      $invoice->next_send_date = null; |                      $invoice->next_send_date = null; | ||||||
|                      $invoice->save(); |                      $invoice->save(); | ||||||
|                  } |                  } | ||||||
|  | 
 | ||||||
|              }); |              }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -196,22 +213,17 @@ class ReminderJob implements ShouldQueue | |||||||
|         $invoice->line_items = $invoice_items; |         $invoice->line_items = $invoice_items; | ||||||
| 
 | 
 | ||||||
|         /**Refresh Invoice values*/ |         /**Refresh Invoice values*/ | ||||||
|         $invoice->calc()->getInvoice()->save(); |         $invoice = $invoice->calc()->getInvoice(); | ||||||
|         $invoice->fresh(); |         // $invoice->service()->deletePdf(); 24-11-2022 no need to delete here because we regenerate later anyway
 | ||||||
|         $invoice->service()->deletePdf(); |  | ||||||
| 
 |  | ||||||
|         /* Refresh the client here to ensure the balance is fresh */ |  | ||||||
|         $client = $invoice->client; |  | ||||||
|         $client = $client->fresh(); |  | ||||||
| 
 | 
 | ||||||
|         nlog('adjusting client balance and invoice balance by #'.$invoice->number.' '.($invoice->balance - $temp_invoice_balance)); |         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}"); |         $invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}"); | ||||||
| 
 | 
 | ||||||
|         $transaction = [ |         $transaction = [ | ||||||
|             'invoice' => $invoice->transaction_event(), |             'invoice' => $invoice->transaction_event(), | ||||||
|             'payment' => [], |             'payment' => [], | ||||||
|             'client' => $client->transaction_event(), |             'client' => $invoice->client->transaction_event(), | ||||||
|             'credit' => [], |             'credit' => [], | ||||||
|             'metadata' => ['setLateFee'], |             'metadata' => ['setLateFee'], | ||||||
|         ]; |         ]; | ||||||
|  | |||||||
| @ -292,6 +292,14 @@ class Activity extends StaticModel | |||||||
|         return $this->belongsTo(Quote::class)->withTrashed(); |         return $this->belongsTo(Quote::class)->withTrashed(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @return mixed | ||||||
|  |      */ | ||||||
|  |     public function subscription() | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(Subscription::class)->withTrashed(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @return mixed |      * @return mixed | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\BankTransactionRule; | ||||||
| use App\Models\Filterable; | use App\Models\Filterable; | ||||||
| use App\Models\Invoice; | use App\Models\Invoice; | ||||||
| use App\Services\Bank\BankService; | 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\DataMapper\CompanySettings; | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
|  | use App\Models\BankTransactionRule; | ||||||
| use App\Models\Language; | use App\Models\Language; | ||||||
| use App\Models\Presenters\CompanyPresenter; | use App\Models\Presenters\CompanyPresenter; | ||||||
| use App\Models\PurchaseOrder; | use App\Models\PurchaseOrder; | ||||||
| @ -123,6 +124,8 @@ class Company extends BaseModel | |||||||
|         'enabled_expense_tax_rates', |         'enabled_expense_tax_rates', | ||||||
|         'invoice_task_project', |         'invoice_task_project', | ||||||
|         'report_include_deleted', |         'report_include_deleted', | ||||||
|  |         'invoice_task_lock', | ||||||
|  |         'use_vendor_currency', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $hidden = [ |     protected $hidden = [ | ||||||
| @ -188,6 +191,11 @@ class Company extends BaseModel | |||||||
|         return $this->hasMany(BankTransaction::class); |         return $this->hasMany(BankTransaction::class); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function bank_transaction_rules() | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(BankTransactionRule::class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function getCompanyIdAttribute() |     public function getCompanyIdAttribute() | ||||||
|     { |     { | ||||||
|         return $this->encodePrimaryKey($this->id); |         return $this->encodePrimaryKey($this->id); | ||||||
| @ -541,6 +549,23 @@ class Company extends BaseModel | |||||||
|         return $this->company_users()->withTrashed()->where('is_owner', true)->first()?->user; |         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) |     public function resolveRouteBinding($value, $field = null) | ||||||
|     { |     { | ||||||
|         return $this->where('id', $this->decodePrimaryKey($value))->firstOrFail(); |         return $this->where('id', $this->decodePrimaryKey($value))->firstOrFail(); | ||||||
|  | |||||||
| @ -79,7 +79,8 @@ class PurchaseOrder extends BaseModel | |||||||
|         'partial', |         'partial', | ||||||
|         'paid_to_date', |         'paid_to_date', | ||||||
|         'vendor_id', |         'vendor_id', | ||||||
|         'last_viewed' |         'last_viewed', | ||||||
|  |         'currency_id', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|  | |||||||
| @ -54,6 +54,10 @@ class Subscription extends BaseModel | |||||||
|         'price', |         'price', | ||||||
|         'name', |         'name', | ||||||
|         'currency_id', |         'currency_id', | ||||||
|  |         'registration_required', | ||||||
|  |         'optional_product_ids', | ||||||
|  |         'optional_recurring_product_ids', | ||||||
|  |         'use_inventory_management', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|  | |||||||
| @ -40,6 +40,7 @@ class Task extends BaseModel | |||||||
|         'number', |         'number', | ||||||
|         'is_date_based', |         'is_date_based', | ||||||
|         'status_order', |         'status_order', | ||||||
|  |         'invoice_lock' | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $touches = []; |     protected $touches = []; | ||||||
|  | |||||||
| @ -64,6 +64,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference | |||||||
|         'email', |         'email', | ||||||
|         'is_primary', |         'is_primary', | ||||||
|         'vendor_id', |         'vendor_id', | ||||||
|  |         'send_email', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function avatar() |     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\Bank; | ||||||
| use App\Models\BankIntegration; | use App\Models\BankIntegration; | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
|  | use App\Models\BankTransactionRule; | ||||||
| use App\Models\Client; | use App\Models\Client; | ||||||
| use App\Models\Company; | use App\Models\Company; | ||||||
| use App\Models\CompanyGateway; | use App\Models\CompanyGateway; | ||||||
| @ -45,6 +46,7 @@ use App\Models\Webhook; | |||||||
| use App\Policies\ActivityPolicy; | use App\Policies\ActivityPolicy; | ||||||
| use App\Policies\BankIntegrationPolicy; | use App\Policies\BankIntegrationPolicy; | ||||||
| use App\Policies\BankTransactionPolicy; | use App\Policies\BankTransactionPolicy; | ||||||
|  | use App\Policies\BankTransactionRulePolicy; | ||||||
| use App\Policies\ClientPolicy; | use App\Policies\ClientPolicy; | ||||||
| use App\Policies\CompanyGatewayPolicy; | use App\Policies\CompanyGatewayPolicy; | ||||||
| use App\Policies\CompanyPolicy; | use App\Policies\CompanyPolicy; | ||||||
| @ -86,6 +88,7 @@ class AuthServiceProvider extends ServiceProvider | |||||||
|         Activity::class => ActivityPolicy::class, |         Activity::class => ActivityPolicy::class, | ||||||
|         BankIntegration::class => BankIntegrationPolicy::class, |         BankIntegration::class => BankIntegrationPolicy::class, | ||||||
|         BankTransaction::class => BankTransactionPolicy::class, |         BankTransaction::class => BankTransactionPolicy::class, | ||||||
|  |         BankTransactionRule::class => BankTransactionRulePolicy::class, | ||||||
|         Client::class => ClientPolicy::class, |         Client::class => ClientPolicy::class, | ||||||
|         Company::class => CompanyPolicy::class, |         Company::class => CompanyPolicy::class, | ||||||
|         CompanyToken::class => CompanyTokenPolicy::class, |         CompanyToken::class => CompanyTokenPolicy::class, | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Repositories; | namespace App\Repositories; | ||||||
| 
 | 
 | ||||||
|  | use App\Jobs\Bank\MatchBankTransactions; | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
| use App\Models\Task; | use App\Models\Task; | ||||||
| use App\Models\TaskStatus; | use App\Models\TaskStatus; | ||||||
| @ -28,17 +29,25 @@ class BankTransactionRepository extends BaseRepository | |||||||
|             $bank_transaction->bank_integration_id = $data['bank_integration_id']; |             $bank_transaction->bank_integration_id = $data['bank_integration_id']; | ||||||
| 
 | 
 | ||||||
|         $bank_transaction->fill($data); |         $bank_transaction->fill($data); | ||||||
| 
 |  | ||||||
|         $bank_transaction->save(); |         $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; |         $data['transactions'] = $bank_transactions->map(function ($bt){ | ||||||
|              $bank_transaction->save();    |             return ['id' => $bt->id, 'invoice_ids' => $bt->invoice_ids]; | ||||||
|  | 
 | ||||||
|  |         })->toArray(); | ||||||
|  | 
 | ||||||
|  |         $bts = (new MatchBankTransactions(auth()->user()->company()->id, auth()->user()->company()->db, $data))->handle(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         return $bank_transaction; |  | ||||||
|     } |  | ||||||
|      |      | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										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; | namespace App\Services\Bank; | ||||||
| 
 | 
 | ||||||
|  | use App\Factory\ExpenseCategoryFactory; | ||||||
|  | use App\Factory\ExpenseFactory; | ||||||
| use App\Libraries\MultiDB; | use App\Libraries\MultiDB; | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
| use App\Models\Company; | use App\Models\Company; | ||||||
|  | use App\Models\ExpenseCategory; | ||||||
| use App\Models\Invoice; | use App\Models\Invoice; | ||||||
|  | use App\Services\Bank\BankService; | ||||||
|  | use App\Utils\Traits\GeneratesCounter; | ||||||
| use Illuminate\Bus\Queueable; | use Illuminate\Bus\Queueable; | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| use Illuminate\Foundation\Bus\Dispatchable; | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
| use Illuminate\Queue\InteractsWithQueue; | use Illuminate\Queue\InteractsWithQueue; | ||||||
|  | use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Facades\Cache; | ||||||
| 
 | 
 | ||||||
| class BankMatchingService implements ShouldQueue | class BankMatchingService implements ShouldQueue | ||||||
| { | { | ||||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; |     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter; | ||||||
| 
 |  | ||||||
|     private $company_id; |  | ||||||
| 
 | 
 | ||||||
|     private Company $company; |     private Company $company; | ||||||
| 
 | 
 | ||||||
|     private $db; |  | ||||||
| 
 |  | ||||||
|     private $invoices; |     private $invoices; | ||||||
| 
 | 
 | ||||||
|     public $deleteWhenMissingModels = true; |     public $deleteWhenMissingModels = true; | ||||||
| 
 | 
 | ||||||
|     public function __construct($company_id, $db) |     public function __construct(private int $company_id, private string $db){} | ||||||
|     { |  | ||||||
|         $this->company_id = $company_id; |  | ||||||
|         $this->db = $db; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public function handle() |     public function handle() | ||||||
|     { |     { | ||||||
| @ -48,15 +48,12 @@ class BankMatchingService implements ShouldQueue | |||||||
| 
 | 
 | ||||||
|         $this->company = Company::find($this->company_id); |         $this->company = Company::find($this->company_id); | ||||||
| 
 | 
 | ||||||
|         $this->invoices = Invoice::where('company_id', $this->company->id) |         $this->matchTransactions(); | ||||||
|                                 ->whereIn('status_id', [1,2,3]) |      | ||||||
|                                 ->where('is_deleted', 0) |  | ||||||
|                                 ->get(); |  | ||||||
| 
 | 
 | ||||||
|         $this->match(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function match() |     private function matchTransactions() | ||||||
|     { |     { | ||||||
|          |          | ||||||
|         BankTransaction::where('company_id', $this->company->id) |         BankTransaction::where('company_id', $this->company->id) | ||||||
| @ -64,21 +61,14 @@ class BankMatchingService implements ShouldQueue | |||||||
|            ->cursor() |            ->cursor() | ||||||
|            ->each(function ($bt){ |            ->each(function ($bt){ | ||||||
|              |              | ||||||
|                             $invoice = $this->invoices->first(function ($value, $key) use ($bt){ |                (new BankService($bt))->processRules(); | ||||||
| 
 |  | ||||||
|                                     return str_contains($bt->description, $value->number); |  | ||||||
| 
 | 
 | ||||||
|            }); |            }); | ||||||
| 
 | 
 | ||||||
|                             if($invoice) |     } | ||||||
|  | 
 | ||||||
|  |     public function middleware() | ||||||
|     { |     { | ||||||
|                                 $bt->invoice_ids = $invoice->hashed_id; |         return [new WithoutOverlapping($this->company_id)]; | ||||||
|                                 $bt->status_id = BankTransaction::STATUS_MATCHED; |  | ||||||
|                                 $bt->save();    |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|                        }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ namespace App\Services\Bank; | |||||||
| 
 | 
 | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
| use App\Models\Invoice; | use App\Models\Invoice; | ||||||
| use App\Services\Bank\ProcessBankRule; | use App\Services\Bank\ProcessBankRules; | ||||||
| 
 | 
 | ||||||
| class BankService | class BankService | ||||||
| { | { | ||||||
| @ -40,11 +40,9 @@ class BankService | |||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function processRule($rule) |     public function processRules() | ||||||
|     { |     { | ||||||
|         (new ProcessBankRule($this->bank_transaction, $rule))->run(); |         (new ProcessBankRules($this->bank_transaction))->run(); | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -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) |     public function updateBalance(float $amount) | ||||||
|     { |     { | ||||||
|         // $this->client->balance += $amount;
 |  | ||||||
| 
 | 
 | ||||||
|  |         try { | ||||||
|             \DB::connection(config('database.default'))->transaction(function () use($amount) { |             \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 = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); | ||||||
|                 $this->client->balance += $amount; |                 $this->client->balance += $amount; | ||||||
|                 $this->client->save(); |                 $this->client->save(); | ||||||
| 
 | 
 | ||||||
|             }, 2); |             }, 2); | ||||||
|  |         } | ||||||
|  |         catch (\Throwable $throwable) { | ||||||
|  |             nlog("DB ERROR " . $throwable->getMessage()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return $this; |         return $this; | ||||||
|  |          | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function updateBalanceAndPaidToDate(float $balance, float $paid_to_date) |     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) { |             \DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) { | ||||||
| 
 | 
 | ||||||
|                 $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); |                 $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); | ||||||
| @ -55,6 +61,12 @@ class ClientService | |||||||
|                 $this->client->save(); |                 $this->client->save(); | ||||||
| 
 | 
 | ||||||
|             }, 2); |             }, 2); | ||||||
|  |         } | ||||||
|  |         catch (\Throwable $throwable) { | ||||||
|  |             nlog("DB ERROR " . $throwable->getMessage()); | ||||||
|  |         } | ||||||
|  |     | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -147,7 +147,7 @@ class ApplyPayment | |||||||
|         event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); |         event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||||
| 
 | 
 | ||||||
|         if ((int) $this->invoice->balance == 0) { |         if ((int) $this->invoice->balance == 0) { | ||||||
|             $this->invoice->service()->deletePdf(); |             $this->invoice->service()->touchPdf(); | ||||||
|             $this->invoice = $this->invoice->fresh(); |             $this->invoice = $this->invoice->fresh(); | ||||||
|             event(new InvoiceWasPaid($this->invoice, $this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); |             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 = [ |         $transaction = [ | ||||||
|             'invoice' => $this->invoice->transaction_event(), |             'invoice' => $this->invoice->transaction_event(), | ||||||
|  | |||||||
| @ -83,7 +83,7 @@ class ApplyPaymentAmount extends AbstractService | |||||||
|                 ->updatePaidToDate($payment->amount) |                 ->updatePaidToDate($payment->amount) | ||||||
|                 ->setCalculatedStatus() |                 ->setCalculatedStatus() | ||||||
|                 ->applyNumber() |                 ->applyNumber() | ||||||
|                 ->deletePdf() |                 ->touchPdf() | ||||||
|                 ->save(); |                 ->save(); | ||||||
| 
 | 
 | ||||||
|         $this->invoice |         $this->invoice | ||||||
|  | |||||||
| @ -112,10 +112,12 @@ class InvoiceService | |||||||
|      * @param  Payment $payment        The Payment |      * @param  Payment $payment        The Payment | ||||||
|      * @param  float   $payment_amount The Payment amount |      * @param  float   $payment_amount The Payment amount | ||||||
|      * @return InvoiceService          Parent class object |      * @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) |     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(); |         $this->invoice = (new ApplyPayment($this->invoice, $payment, $payment_amount))->run(); | ||||||
| 
 | 
 | ||||||
| @ -218,7 +220,6 @@ class InvoiceService | |||||||
|     public function markDeleted() |     public function markDeleted() | ||||||
|     { |     { | ||||||
|         $this->removeUnpaidGatewayFees(); |         $this->removeUnpaidGatewayFees(); | ||||||
|         $this->deletePdf(); |  | ||||||
| 
 | 
 | ||||||
|         $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); |         $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); | ||||||
| 
 | 
 | ||||||
| @ -378,6 +379,7 @@ class InvoiceService | |||||||
|                                      })->toArray(); |                                      })->toArray(); | ||||||
| 
 | 
 | ||||||
|         $this->invoice = $this->invoice->calc()->getInvoice(); |         $this->invoice = $this->invoice->calc()->getInvoice(); | ||||||
|  |         $this->invoice->service()->touchPdf(); | ||||||
| 
 | 
 | ||||||
|         /* 24-03-2022 */ |         /* 24-03-2022 */ | ||||||
|         $new_balance = $this->invoice->balance; |         $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 ?: '', |             'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '', | ||||||
|             'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '', |             'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '', | ||||||
|             'vendor_id'=> (string) $this->encodePrimaryKey($bank_transaction->vendor_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, |             'is_deleted' => (bool) $bank_transaction->is_deleted, | ||||||
|             'created_at' => (int) $bank_transaction->created_at, |             'created_at' => (int) $bank_transaction->created_at, | ||||||
|             'updated_at' => (int) $bank_transaction->updated_at, |             'updated_at' => (int) $bank_transaction->updated_at, | ||||||
|  | |||||||
| @ -43,6 +43,7 @@ use App\Models\TaxRate; | |||||||
| use App\Models\User; | use App\Models\User; | ||||||
| use App\Models\Webhook; | use App\Models\Webhook; | ||||||
| use App\Transformers\BankIntegrationTransformer; | use App\Transformers\BankIntegrationTransformer; | ||||||
|  | use App\Transformers\BankTransactionRuleTransformer; | ||||||
| use App\Transformers\BankTransactionTransformer; | use App\Transformers\BankTransactionTransformer; | ||||||
| use App\Transformers\PurchaseOrderTransformer; | use App\Transformers\PurchaseOrderTransformer; | ||||||
| use App\Transformers\RecurringExpenseTransformer; | use App\Transformers\RecurringExpenseTransformer; | ||||||
| @ -104,6 +105,7 @@ class CompanyTransformer extends EntityTransformer | |||||||
|         'purchase_orders', |         'purchase_orders', | ||||||
|         'bank_integrations', |         'bank_integrations', | ||||||
|         'bank_transactions', |         'bank_transactions', | ||||||
|  |         'bank_transaction_rules', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -186,6 +188,8 @@ class CompanyTransformer extends EntityTransformer | |||||||
|             'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates, |             'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates, | ||||||
|             'invoice_task_project' => (bool) $company->invoice_task_project, |             'invoice_task_project' => (bool) $company->invoice_task_project, | ||||||
|             'report_include_deleted' => (bool) $company->report_include_deleted, |             '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); |         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) |     public function includeBankIntegrations(Company $company) | ||||||
|     { |     { | ||||||
|         $transformer = new BankIntegrationTransformer($this->serializer); |         $transformer = new BankIntegrationTransformer($this->serializer); | ||||||
|  | |||||||
| @ -132,6 +132,7 @@ class PurchaseOrderTransformer extends EntityTransformer | |||||||
|             'paid_to_date' => (float)$purchase_order->paid_to_date, |             'paid_to_date' => (float)$purchase_order->paid_to_date, | ||||||
|             'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id), |             'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id), | ||||||
|             'expense_id' => $this->encodePrimaryKey($purchase_order->expense_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, |             'updated_at' => (int) $subscription->updated_at, | ||||||
|             'archived_at' => (int) $subscription->deleted_at, |             'archived_at' => (int) $subscription->deleted_at, | ||||||
|             'plan_map' => '', //@deprecated 03/04/2021
 |             '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), |             'user_id' => (string) $this->encodePrimaryKey($task->user_id), | ||||||
|             'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id), |             'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id), | ||||||
|             'number' => (string) $task->number ?: '', |             'number' => (string) $task->number ?: '', | ||||||
|             // 'start_time' => (int) $task->start_time,
 |  | ||||||
|             'description' => (string) $task->description ?: '', |             'description' => (string) $task->description ?: '', | ||||||
|             'duration' => (int) $task->duration ?: 0, |             'duration' => (int) $task->duration ?: 0, | ||||||
|             'rate' => (float) $task->rate ?: 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
 |             'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
 | ||||||
|             'is_date_based' => (bool) $task->is_date_based, |             'is_date_based' => (bool) $task->is_date_based, | ||||||
|             'status_order' => is_null($task->status_order) ? null : (int) $task->status_order, |             '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); |         $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'>
 |         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>";
 |           <rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
 | ||||||
| @ -80,7 +80,7 @@ trait Inviteable | |||||||
|         if (Ninja::isHosted()) { |         if (Ninja::isHosted()) { | ||||||
|             $domain = $this->company->domain(); |             $domain = $this->company->domain(); | ||||||
|         } else { |         } 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())); |         $entity_type = Str::snake(class_basename($this->entityType())); | ||||||
| @ -95,7 +95,7 @@ trait Inviteable | |||||||
|         if (Ninja::isHosted()) { |         if (Ninja::isHosted()) { | ||||||
|             $domain = $this->company->domain(); |             $domain = $this->company->domain(); | ||||||
|         } else { |         } 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) { |         switch ($this->company->portal_mode) { | ||||||
| @ -121,7 +121,7 @@ trait Inviteable | |||||||
|         if (Ninja::isHosted()) { |         if (Ninja::isHosted()) { | ||||||
|             $domain = $this->company->domain(); |             $domain = $this->company->domain(); | ||||||
|         } else { |         } 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) { |         switch ($this->company->portal_mode) { | ||||||
|  | |||||||
| @ -211,4 +211,5 @@ return [ | |||||||
|         'dev_mode' => env("YODLEE_DEV_MODE", false), |         'dev_mode' => env("YODLEE_DEV_MODE", false), | ||||||
|         'config_name' => env("YODLEE_CONFIG_NAME", 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' => 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' => 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' => 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) { |         foreach ($currencies as $currency) { | ||||||
|  | |||||||
| @ -4843,6 +4843,11 @@ $LANG = array( | |||||||
|     'refresh_accounts' => 'Refresh Accounts', |     'refresh_accounts' => 'Refresh Accounts', | ||||||
|     'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account', |     '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', |     '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; | return $LANG; | ||||||
|  | |||||||
							
								
								
									
										65
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										65
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -5,6 +5,8 @@ | |||||||
|     "packages": { |     "packages": { | ||||||
|         "": { |         "": { | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|  |                 "@tailwindcss/forms": "^0.3.4", | ||||||
|  |                 "@tailwindcss/line-clamp": "^0.3.1", | ||||||
|                 "autoprefixer": "^10.3.7", |                 "autoprefixer": "^10.3.7", | ||||||
|                 "axios": "^0.25", |                 "axios": "^0.25", | ||||||
|                 "card-js": "^1.0.13", |                 "card-js": "^1.0.13", | ||||||
| @ -25,6 +27,7 @@ | |||||||
|             "devDependencies": { |             "devDependencies": { | ||||||
|                 "@babel/compat-data": "7.15.0", |                 "@babel/compat-data": "7.15.0", | ||||||
|                 "@babel/plugin-proposal-class-properties": "^7.14.5", |                 "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||||
|  |                 "@tailwindcss/aspect-ratio": "^0.4.2", | ||||||
|                 "laravel-mix-purgecss": "^6.0.0", |                 "laravel-mix-purgecss": "^6.0.0", | ||||||
|                 "vue-template-compiler": "^2.6.14" |                 "vue-template-compiler": "^2.6.14" | ||||||
|             } |             } | ||||||
| @ -1682,6 +1685,34 @@ | |||||||
|                 "node": ">= 8" |                 "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": { |         "node_modules/@trysound/sax": { | ||||||
|             "version": "0.2.0", |             "version": "0.2.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", |             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", | ||||||
| @ -5742,6 +5773,14 @@ | |||||||
|                 "url": "https://opencollective.com/webpack" |                 "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": { |         "node_modules/minimalistic-assert": { | ||||||
|             "version": "1.0.1", |             "version": "1.0.1", | ||||||
|             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", |             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", | ||||||
| @ -10285,6 +10324,27 @@ | |||||||
|                 "fastq": "^1.6.0" |                 "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": { |         "@trysound/sax": { | ||||||
|             "version": "0.2.0", |             "version": "0.2.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", |             "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": { |         "minimalistic-assert": { | ||||||
|             "version": "1.0.1", |             "version": "1.0.1", | ||||||
|             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", |             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", | ||||||
|  | |||||||
| @ -11,10 +11,13 @@ | |||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@babel/compat-data": "7.15.0", |         "@babel/compat-data": "7.15.0", | ||||||
|         "@babel/plugin-proposal-class-properties": "^7.14.5", |         "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||||
|  |         "@tailwindcss/aspect-ratio": "^0.4.2", | ||||||
|         "laravel-mix-purgecss": "^6.0.0", |         "laravel-mix-purgecss": "^6.0.0", | ||||||
|         "vue-template-compiler": "^2.6.14" |         "vue-template-compiler": "^2.6.14" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|  |         "@tailwindcss/line-clamp": "^0.3.1", | ||||||
|  |         "@tailwindcss/forms": "^0.3.4", | ||||||
|         "autoprefixer": "^10.3.7", |         "autoprefixer": "^10.3.7", | ||||||
|         "axios": "^0.25", |         "axios": "^0.25", | ||||||
|         "card-js": "^1.0.13", |         "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/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/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", |     "/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/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/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/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/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/pdf.js": "/js/clients/shared/pdf.js?id=be5307abc990bb44f2f92628103b1d98", | ||||||
|     "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=c2caa29f753ad1f3a12ca45acddacd72", |     "/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-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-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=db71055862995fd6ae21becfc587a3de", | ||||||
|     "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=914a6846ad1e5584635e7430fef76875", |     "/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", |     "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c", | ||||||
|     "/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d" |     "/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; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -306,6 +306,16 @@ | |||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -293,6 +293,18 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -287,6 +287,17 @@ | |||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -258,6 +258,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -263,6 +263,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -280,6 +280,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -307,6 +307,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |       /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|       /** Hide company logo **/ |       /** Hide company logo **/ | ||||||
|  | |||||||
| @ -250,6 +250,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -324,6 +324,17 @@ | |||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** Hide company logo **/ | ||||||
|  | |||||||
| @ -287,6 +287,17 @@ | |||||||
|         z-index:200 !important; |         z-index:200 !important; | ||||||
|         position:  fixed; |         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. **/ |     /** Useful snippets, uncomment to enable. **/ | ||||||
| 
 | 
 | ||||||
|     /** Hide company logo **/ |     /** 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\ActivityController; | ||||||
| use App\Http\Controllers\Auth\ForgotPasswordController; | use App\Http\Controllers\Auth\ForgotPasswordController; | ||||||
| use App\Http\Controllers\Auth\LoginController; | use App\Http\Controllers\Auth\LoginController; | ||||||
| use App\Http\Controllers\Bank\YodleeController; |  | ||||||
| use App\Http\Controllers\BankIntegrationController; | use App\Http\Controllers\BankIntegrationController; | ||||||
| use App\Http\Controllers\BankTransactionController; | 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\BaseController; | ||||||
| use App\Http\Controllers\ChartController; | use App\Http\Controllers\ChartController; | ||||||
| use App\Http\Controllers\ClientController; | 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/bulk', [BankTransactionController::class, 'bulk'])->name('bank_transactions.bulk'); | ||||||
|     Route::post('bank_transactions/match', [BankTransactionController::class, 'match'])->name('bank_transactions.match'); |     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::post('check_subdomain', [SubdomainController::class, 'index'])->name('check_subdomain'); | ||||||
|     Route::get('ping', [PingController::class, 'index'])->name('ping'); |     Route::get('ping', [PingController::class, 'index'])->name('ping'); | ||||||
|     Route::get('health_check', [PingController::class, 'health'])->name('health_check'); |     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('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', [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 () { | Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () { | ||||||
|     /*Invitation catches*/ |     /*Invitation catches*/ | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								tailwind.config.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								tailwind.config.js
									
									
									
									
										vendored
									
									
								
							| @ -17,5 +17,9 @@ module.exports = { | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     variants: {}, |     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([ |         $response = $this->withHeaders([ | ||||||
|             'X-API-SECRET' => config('ninja.api_secret'), |             'X-API-SECRET' => config('ninja.api_secret'), | ||||||
|             'X-API-TOKEN' => $this->token, |             '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);
 |         // nlog($response);
 | ||||||
|         $response->assertStatus(200); |         $response->assertStatus(200); | ||||||
| @ -98,7 +98,7 @@ class SubscriptionApiTest extends TestCase | |||||||
| 
 | 
 | ||||||
|         $response1 = $this |         $response1 = $this | ||||||
|             ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token]) |             ->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) |             ->assertStatus(200) | ||||||
|             ->json(); |             ->json(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Tests\Feature; | namespace Tests\Feature; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\Task; | ||||||
| use App\Utils\Traits\MakesHash; | use App\Utils\Traits\MakesHash; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||||
| @ -42,6 +43,90 @@ class TaskApiTest extends TestCase | |||||||
|         Model::reguard(); |         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() |     public function testTimeLogValidation() | ||||||
|     { |     { | ||||||
|         $data = [ |         $data = [ | ||||||
| @ -75,9 +160,10 @@ class TaskApiTest extends TestCase | |||||||
|         $arr = $response->json(); |         $arr = $response->json(); | ||||||
|         $response->assertStatus(200); |         $response->assertStatus(200); | ||||||
|          |          | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     public function testTimeLogValidation2() |     public function testTimeLogValidation2() | ||||||
|     { |     { | ||||||
|         $data = [ |         $data = [ | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ use App\Jobs\Company\CreateCompanyTaskStatuses; | |||||||
| use App\Models\Account; | use App\Models\Account; | ||||||
| use App\Models\BankIntegration; | use App\Models\BankIntegration; | ||||||
| use App\Models\BankTransaction; | use App\Models\BankTransaction; | ||||||
|  | use App\Models\BankTransactionRule; | ||||||
| use App\Models\Client; | use App\Models\Client; | ||||||
| use App\Models\ClientContact; | use App\Models\ClientContact; | ||||||
| use App\Models\Company; | use App\Models\Company; | ||||||
| @ -153,6 +154,11 @@ trait MockAccountData | |||||||
|      */ |      */ | ||||||
|     public $bank_transaction; |     public $bank_transaction; | ||||||
|      |      | ||||||
|  |     /** | ||||||
|  |      * @var | ||||||
|  |      */ | ||||||
|  |     public $bank_transaction_rule; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @var |      * @var | ||||||
|      */ |      */ | ||||||
| @ -572,6 +578,11 @@ trait MockAccountData | |||||||
|             'bank_integration_id' => $this->bank_integration->id, |             '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) |         $invitations = CreditInvitation::whereCompanyId($this->credit->company_id) | ||||||
|                                         ->whereCreditId($this->credit->id); |                                         ->whereCreditId($this->credit->id); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user