diff --git a/VERSION.txt b/VERSION.txt index 91e000a3e7f1..25780661c1e4 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.32 \ No newline at end of file +5.5.33 \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4ee1a351dda1..23a824d22505 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -17,6 +17,7 @@ use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Cron\SubscriptionCron; use App\Jobs\Ledger\LedgerBalanceUpdate; use App\Jobs\Ninja\AdjustEmailQuota; +use App\Jobs\Ninja\BankTransactionSync; use App\Jobs\Ninja\CompanySizeCheck; use App\Jobs\Ninja\QueueSize; use App\Jobs\Ninja\SystemMaintenance; @@ -42,40 +43,59 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { + /* Check for the latest version of Invoice Ninja */ $schedule->job(new VersionCheck)->daily(); + /* Checks and cleans redundant files */ $schedule->job(new DiskCleanup)->daily()->withoutOverlapping(); + /* Send reminders */ $schedule->job(new ReminderJob)->hourly()->withoutOverlapping(); + /* Returns the number of jobs in the queue */ $schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping(); + /* Checks for large companies and marked them as is_large */ $schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping(); + /* Pulls in the latest exchange rates */ $schedule->job(new UpdateExchangeRates)->daily()->withoutOverlapping(); + /* Runs cleanup code for subscriptions */ $schedule->job(new SubscriptionCron)->daily()->withoutOverlapping(); + /* Sends recurring invoices*/ $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); + /* Sends recurring invoices*/ $schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping(); + /* Performs auto billing */ $schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping(); + /* Checks the status of the scheduler */ $schedule->job(new SchedulerCheck)->daily()->withoutOverlapping(); + /* Checks for scheduled tasks */ $schedule->job(new TaskScheduler())->daily()->withoutOverlapping(); + /* Performs system maintenance such as pruning the backup table */ $schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping(); + /* Pulls in bank transactions from third party services */ + $schedule->job(new BankTransactionSync)->dailyAt('04:00')->withoutOverlapping(); + if (Ninja::isSelfHost()) { + $schedule->call(function () { Account::whereNotNull('id')->update(['is_scheduler_running' => true]); })->everyFiveMinutes(); + } /* Run hosted specific jobs */ if (Ninja::isHosted()) { + $schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping(); $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping(); @@ -85,12 +105,15 @@ class Kernel extends ConsoleKernel $schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:05')->withoutOverlapping(); $schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping(); + } if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && ! config('ninja.is_docker')) { + $schedule->command('queue:work database --stop-when-empty --memory=256')->everyMinute()->withoutOverlapping(); $schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping(); + } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 9bfbc70930c5..f13d26ee64a1 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -21,6 +21,7 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Database\Eloquent\RelationNotFoundException; +use Illuminate\Database\QueryException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\Exceptions\ThrottleRequestsException; use Illuminate\Http\Request; @@ -204,7 +205,11 @@ class Handler extends ExceptionHandler return response()->json(['message' => $exception->getMessage()], 400); } elseif ($exception instanceof StripeConnectFailure) { return response()->json(['message' => $exception->getMessage()], 400); - } + } elseif ($exception instanceof QueryException) { + return response()->json(['message' => 'We had a problem executing this query. Please retry.'], 500); + } + + return parent::render($request, $exception); } diff --git a/app/Exceptions/YodleeApiException.php b/app/Exceptions/YodleeApiException.php new file mode 100644 index 000000000000..c5c4458e5e3d --- /dev/null +++ b/app/Exceptions/YodleeApiException.php @@ -0,0 +1,41 @@ +getMessage() && strlen($this->getMessage()) >= 1) { + $msg = $this->getMessage(); + } + + return response()->json([ + 'message' => $msg, + ], 400); + } +} diff --git a/app/Factory/BankIntegrationFactory.php b/app/Factory/BankIntegrationFactory.php new file mode 100644 index 000000000000..cdcac8340857 --- /dev/null +++ b/app/Factory/BankIntegrationFactory.php @@ -0,0 +1,37 @@ +account_id = $account_id; + $bank_integration->user_id = $user_id; + $bank_integration->company_id = $company_id; + + $bank_integration->provider_name = ''; + $bank_integration->bank_account_id = ''; + $bank_integration->bank_account_name = ''; + $bank_integration->bank_account_number = ''; + $bank_integration->bank_account_status = ''; + $bank_integration->bank_account_type = ''; + $bank_integration->balance = 0; + $bank_integration->currency = ''; + + return $bank_integration; + } +} diff --git a/app/Factory/BankTransactionFactory.php b/app/Factory/BankTransactionFactory.php new file mode 100644 index 000000000000..4fff5906bc93 --- /dev/null +++ b/app/Factory/BankTransactionFactory.php @@ -0,0 +1,35 @@ +user_id = $user_id; + $bank_transaction->company_id = $company_id; + + $bank_transaction->amount = 0; + $bank_transaction->currency_id = 1; + $bank_transaction->account_type = ''; + $bank_transaction->category_type = ''; + $bank_transaction->date = now()->format('Y-m-d'); + $bank_transaction->description = ''; + $bank_transaction->status_id = 1; + + return $bank_transaction; + } +} diff --git a/app/Helpers/Bank/AccountTransformerInterface.php b/app/Helpers/Bank/AccountTransformerInterface.php new file mode 100644 index 000000000000..1fd5ebf4685c --- /dev/null +++ b/app/Helpers/Bank/AccountTransformerInterface.php @@ -0,0 +1,17 @@ + stdClass Object +( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => My CD - 8878 + [accountStatus] => ACTIVE + [accountNumber] => xxxx8878 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [id] => 12331861 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [accountType] => CD + [displayedName] => LORETTA + [createdDate] => 2022-07-28T06:55:33Z + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) + + ) + +) + ) + */ + + +class AccountTransformer implements AccountTransformerInterface +{ + + public function transform($yodlee_account) + { + + $data = []; + + if(!property_exists($yodlee_account, 'account')) + return $data; + + foreach($yodlee_account->account as $account) + { + $data[] = $this->transformAccount($account); + } + + return $data; + } + + public function transformAccount($account) + { + + return [ + 'id' => $account->id, + 'account_type' => $account->CONTAINER, + 'account_name' => $account->accountName, + 'account_status' => $account->accountStatus, + 'account_number' => $account->accountNumber, + 'provider_account_id' => $account->providerAccountId, + 'provider_id' => $account->providerId, + 'provider_name' => $account->providerName, + 'nickname' => property_exists($account, 'nickname') ? $account->nickname : '', + 'current_balance' => property_exists($account, 'currentBalance') ? $account->currentBalance->amount : 0, + 'account_currency' => property_exists($account, 'currency') ? $account->currentBalance->currency : '', + ]; + } +} + + diff --git a/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php new file mode 100644 index 000000000000..7868fb8f5baf --- /dev/null +++ b/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php @@ -0,0 +1,80 @@ + bank +[id] => 103953585 +[amount] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + +[categoryType] => UNCATEGORIZE +[categoryId] => 1 +[category] => Uncategorized +[categorySource] => SYSTEM +[highLevelCategoryId] => 10000017 +[createdDate] => 2022-08-04T21:50:17Z +[lastUpdated] => 2022-08-04T21:50:17Z +[description] => stdClass Object + ( + [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK + ) + +[isManual] => +[sourceType] => AGGREGATED +[date] => 2022-08-03 +[transactionDate] => 2022-08-03 +[postDate] => 2022-08-03 +[status] => POSTED +[accountId] => 12331794 +[runningBalance] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + +[checkNumber] => 998 +) +*/ + +class IncomeTransformer implements BankRevenueInterface +{ + use AppSetup; + + public function transform($transaction) + { + + $data = []; + + if(!property_exists($transaction, 'transaction')) + return $data; + + foreach($transaction->transaction as $transaction) + { + $data[] = $this->transformTransaction($transaction); + } + + return $data; + } + + public function transformTransaction($transaction) + { + + return [ + 'transaction_id' => $transaction->id, + 'amount' => $transaction->amount->amount, + 'currency_id' => $this->convertCurrency($transaction->amount->currency), + 'account_type' => $transaction->CONTAINER, + 'category_id' => $transaction->highLevelCategoryId, + 'category_type' => $transaction->categoryType, + 'date' => $transaction->date, + 'bank_account_id' => $transaction->accountId, + 'description' => $transaction->description->original, + 'base_type' => property_exists($transaction, 'baseType') ? $transaction->baseType : $this->calculateBaseType($transaction), + ]; + } + + private function calculateBaseType($transaction) + { + //CREDIT / DEBIT + + if(property_exists($transaction, 'highLevelCategoryId') && $transaction->highLevelCategoryId == 10000012) + return 'CREDIT'; + + return 'DEBIT'; + + } + + private function convertCurrency(string $code) + { + + $currencies = Cache::get('currencies'); + + if (! $currencies) { + $this->buildCache(true); + } + + $currency = $currencies->filter(function ($item) use($code){ + return $item->code == $code; + })->first(); + + if($currency) + return $currency->id; + + return 1; + + } + + +} + + diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php new file mode 100644 index 000000000000..6a84fd850de7 --- /dev/null +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -0,0 +1,284 @@ +bank_account_id = $bank_account_id; + + $this->client_id = config('ninja.yodlee.client_id'); + + $this->client_secret = config('ninja.yodlee.client_secret'); + + $this->admin_name = config('ninja.yodlee.admin_name'); + + $this->test_mode = config('ninja.yodlee.test_mode'); + + config('ninja.yodlee.dev_mode') ? $this->setDevUrl() : null; + + } + + public function getFastTrackUrl() + { + if(config('ninja.yodlee.dev_mode')) + return $this->dev_fast_track_url; + + return $this->test_mode ? $this->test_fast_track_url : $this->production_track_url; + } + + public function setTestMode() + { + $this->test_mode = true; + + return $this; + } + + public function setDevUrl() + { + $this->test_api_endpoint = $this->dev_api_endpoint; + + $this->api_endpoint = $this->dev_api_endpoint; + + return $this; + } + + public function getEndpoint() + { + return $this->test_mode ? $this->test_api_endpoint : $this->api_endpoint; + } + + /** + * If we do not pass in a user + * we pass in the admin username instead + */ + public function getAccessToken($is_admin = false) + { + if($is_admin) + $user = $this->admin_name; + else + $user = $this->bank_account_id ?: $this->admin_name; + + $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); + + return $response->token->accessToken; + } + + + public function createUser($company) + { + + $token = $this->getAccessToken(true); + + $user['user'] = [ + 'loginName' => Str::uuid(), + ]; + +/* +{ + "user": { + "preferences": { + "dateFormat": "string", + "timeZone": "string", + "currency": "USD", + "locale": "en_US" + }, + "address": { + "zip": "string", + "country": "string", + "address3": "string", + "address2": "string", + "city": "string", + "address1": "string", + "state": "string" + }, + "loginName": "string", + "name": { + "middle": "string", + "last": "string", + "fullName": "string", + "first": "string" + }, + "email": "string", + "segmentName": "string" + } +} +*/ + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->post($this->getEndpoint(). "/user/register", $user); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + public function getAccounts($params = []) + { + + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params); + + if($response->successful()){ + + $at = new AccountTransformer(); + return $at->transform($response->object()); + + } + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + public function deleteAccount($account_id) + { + + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->delete($this->getEndpoint(). "/accounts/{$account_id}", []); + + if($response->successful()){ + + return true; + + } + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + + public function getTransactions($params = []) + { + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions", $params); + + if($response->successful()){ + // return $response->object(); + $it = new IncomeTransformer(); + return $it->transform($response->object()); + + } + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + public function getTransactionCount($params = []) + { + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions/count", $params); + + if($response->successful()){ + + return $response->object(); + + } + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + public function getTransactionCategories($params = []) + { + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions/categories", $params); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + private function bankFormRequest(string $uri, string $verb, array $data, array $headers) + { + + $response = Http::withHeaders($this->getFormHeaders($headers))->asForm()->{$verb}($this->getEndpoint() . $uri, $this->buildBody()); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + throw new YodleeApiException($response->body()); + + } + + private function getHeaders($data = []) + { + return array_merge($data, [ + 'Api-Version' => '1.1', + 'ContentType' => 'application/json' + ]); + } + + + private function getFormHeaders($data = []) + { + return array_merge($data, [ + 'Api-Version' => '1.1', + ]); + } + + private function buildBody() + { + + return [ + 'clientId' => $this->client_id, + 'secret' => $this->client_secret, + ]; + + } + +} diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php new file mode 100644 index 000000000000..ddf8b2100d79 --- /dev/null +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -0,0 +1,118 @@ +getCompany(); + + + if($company->account->bank_integration_account_id){ + + $flow = 'edit'; + + $token = $company->account->bank_integration_account_id; + + } + else{ + + $flow = 'add'; + + $response = $yodlee->createUser($company); + + $token = $response->user->loginName; + + $company->account->bank_integration_account_id = $token; + + $company->push(); + + } + + $yodlee = new Yodlee($token); + + if($request->has('window_closed') && $request->input("window_closed") == "true") + $this->getAccounts($company, $token); + + $data = [ + 'access_token' => $yodlee->getAccessToken(), + 'fasttrack_url' => $yodlee->getFastTrackUrl(), + 'config_name' => config('ninja.yodlee.config_name'), + 'flow' => $flow, + 'company' => $company, + 'account' => $company->account, + 'completed' => $request->has('window_closed') ? true : false, + ]; + + return view('bank.yodlee.auth', $data); + + } + + private function getAccounts($company, $token) + { + $yodlee = new Yodlee($token); + + $accounts = $yodlee->getAccounts(); + + foreach($accounts as $account) + { + + if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) + { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = $company->id; + $bank_integration->account_id = $company->account_id; + $bank_integration->user_id = $company->owner()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + $bank_integration->from_date = now()->subYear(); + + $bank_integration->save(); + } + + } + + + $company->account->bank_integrations->each(function ($bank_integration) use ($company){ + + ProcessBankTransactions::dispatch($company->account->bank_integration_account_id, $bank_integration); + + }); + + + } + +} diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php new file mode 100644 index 000000000000..c38f31245fa6 --- /dev/null +++ b/app/Http/Controllers/BankIntegrationController.php @@ -0,0 +1,683 @@ +bank_integration_repo = $bank_integration_repo; + } + + /** + * @OA\Get( + * path="/api/v1/bank_integrations", + * operationId="getBankIntegrations", + * tags={"bank_integrations"}, + * summary="Gets a list of bank_integrations", + * description="Lists all bank integrations", + * @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/BankIntegration"), + * ), + * @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 Request $request + * @return Response|mixed + */ + public function index(Request $request) + { + + $bank_integrations = BankIntegration::query()->company(); + + return $this->listResponse($bank_integrations); + + } + + /** + * Display the specified resource. + * + * @param ShowBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/{id}", + * operationId="showBankIntegration", + * tags={"bank_integrations"}, + * summary="Shows a bank_integration", + * description="Displays a bank_integration 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 BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration 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/BankIntegration"), + * ), + * @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(ShowBankIntegrationRequest $request, BankIntegration $bank_integration) + { + return $this->itemResponse($bank_integration); + } + + + /** + * Show the form for editing the specified resource. + * + * @param EditBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/{id}/edit", + * operationId="editBankIntegration", + * tags={"bank_integrations"}, + * summary="Shows a bank_integration for editing", + * description="Displays a bank_integration 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 BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration 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/BankIntegration"), + * ), + * @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(EditBankIntegrationRequest $request, BankIntegration $bank_integration) + { + return $this->itemResponse($bank_integration); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * + * @OA\Put( + * path="/api/v1/bank_integrations/{id}", + * operationId="updateBankIntegration", + * tags={"bank_integrations"}, + * summary="Updates a bank_integration", + * description="Handles the updating of a bank_integration 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 BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration 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/BankIntegration"), + * ), + * @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(UpdateBankIntegrationRequest $request, BankIntegration $bank_integration) + { + + //stubs for updating the model + $bank_integration = $this->bank_integration_repo->save($request->all(), $bank_integration); + + return $this->itemResponse($bank_integration->fresh()); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateBankIntegrationRequest $request + * @return Response + * + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/create", + * operationId="getBankIntegrationsCreate", + * tags={"bank_integrations"}, + * summary="Gets a new blank bank_integration 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_integration 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/BankIntegration"), + * ), + * @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(CreateBankIntegrationRequest $request) + { + $bank_integration = BankIntegrationFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + return $this->itemResponse($bank_integration); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreBankIntegrationRequest $request + * @return Response + * + * + * + * @OA\Post( + * path="/api/v1/bank_integrations", + * operationId="storeBankIntegration", + * tags={"bank_integrations"}, + * summary="Adds a bank_integration", + * description="Adds an bank_integration 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_integration 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/BankIntegration"), + * ), + * @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(StoreBankIntegrationRequest $request) + { + //stub to store the model + $bank_integration = $this->bank_integration_repo->save($request->all(), BankIntegrationFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id)); + + return $this->itemResponse($bank_integration); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/bank_integrations/{id}", + * operationId="deleteBankIntegration", + * tags={"bank_integrations"}, + * summary="Deletes a bank_integration", + * description="Handles the deletion of a bank_integration 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 BankIntegration 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(DestroyBankIntegrationRequest $request, BankIntegration $bank_integration) + { + $this->bank_integration_repo->delete($bank_integration); + + return $this->itemResponse($bank_integration->fresh()); + } + + + /** + * Perform bulk actions on the list view. + * + * @return Collection + * + * @OA\Post( + * path="/api/v1/bank_integrations/bulk", + * operationId="bulkBankIntegrations", + * tags={"bank_integrations"}, + * summary="Performs bulk actions on an array of bank_integrations", + * 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_integrations = BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); + + $bank_integrations->each(function ($bank_integration, $key) use ($action) { + if (auth()->user()->can('edit', $bank_integration)) { + $this->bank_integration_repo->{$action}($bank_integration); + } + }); + + /* Need to understand which permission are required for the given bulk action ie. view / edit */ + + return $this->listResponse(BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); + } + + + /** + * Return the remote list of accounts stored on the third party provider. + * + * @return Response + * + * @OA\Post( + * path="/api/v1/bank_integrations/refresh_accounts", + * operationId="getRefreshAccounts", + * tags={"bank_integrations"}, + * summary="Gets the list of accounts from the remote server", + * description="Adds an bank_integration 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_integration 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/BankIntegration"), + * ), + * @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 refreshAccounts(AdminBankIntegrationRequest $request) + { + // As yodlee is the first integration we don't need to perform switches yet, however + // if we add additional providers we can reuse this class + + $bank_account_id = auth()->user()->account->bank_integration_account_id; + + if(!$bank_account_id) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + $yodlee = new Yodlee($bank_account_id); + + $accounts = $yodlee->getAccounts(); + + foreach($accounts as $account) + { + + if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', auth()->user()->company()->id)->exists()) + { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = auth()->user()->company()->id; + $bank_integration->account_id = auth()->user()->account_id; + $bank_integration->user_id = auth()->user()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + + $bank_integration->save(); + } + } + + + return response()->json(BankIntegration::query()->company(), 200); + } + + /** + * Return the remote list of accounts stored on the third party provider + * and update our local cache. + * + * @return Response + * + * @OA\Post( + * path="/api/v1/bank_integrations/remove_account/account_id", + * operationId="getRemoveAccount", + * tags={"bank_integrations"}, + * summary="Removes an account from the integration", + * description="Removes an account from the integration", + * @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 bank_integration 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/BankIntegration"), + * ), + * @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 removeAccount(AdminBankIntegrationRequest $request, $acc_id) + { + + $bank_account_id = auth()->user()->account->bank_integration_account_id; + + if(!$bank_account_id) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + $bi = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->where('company_id', auth()->user()->company()->id)->firstOrFail(); + + $yodlee = new Yodlee($bank_account_id); + $res = $yodlee->deleteAccount($acc_id); + + $this->bank_integration_repo->delete($bi); + + return $this->itemResponse($bi->fresh()); + + } + + + /** + * Return the remote list of accounts stored on the third party provider + * and update our local cache. + * + * @return Response + * + * @OA\Post( + * path="/api/v1/bank_integrations/get_transactions/account_id", + * operationId="getAccountTransactions", + * tags={"bank_integrations"}, + * summary="Retrieve transactions for a account", + * description="Retrieve transactions for a account", + * @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="Retrieve transactions for a account", + * @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/BankIntegration"), + * ), + * @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 getTransactions(AdminBankIntegrationRequest $request) + { + + auth()->user()->account->bank_integrations->each(function ($bank_integration) { + + ProcessBankTransactions::dispatchSync(auth()->user()->account->bank_integration_account_id, $bank_integration); + + }); + + return response()->json(['message' => 'Fetching transactions....'], 200); + + } +} \ No newline at end of file diff --git a/app/Http/Controllers/BankTransactionController.php b/app/Http/Controllers/BankTransactionController.php new file mode 100644 index 000000000000..3f63ce04e0e3 --- /dev/null +++ b/app/Http/Controllers/BankTransactionController.php @@ -0,0 +1,563 @@ +bank_transaction_repo = $bank_transaction_repo; + } + + /** + * @OA\Get( + * path="/api/v1/bank_transactions", + * operationId="getBankTransactions", + * tags={"bank_transactions"}, + * summary="Gets a list of bank_transactions", + * description="Lists all bank integrations", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\Parameter( + * name="rows", + * in="query", + * description="The number of bank integrations to return", + * example="50", + * required=false, + * @OA\Schema( + * type="number", + * format="integer", + * ), + * ), + * @OA\Response( + * response=200, + * description="A list of bank integrations", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * @param Request $request + * @return Response|mixed + */ + public function index(Request $request) + { + + $bank_transactions = BankTransaction::query()->company(); + + return $this->listResponse($bank_transactions); + + } + + /** + * Display the specified resource. + * + * @param ShowBankTransactionRequest $request + * @param BankTransaction $bank_transaction + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_transactions/{id}", + * operationId="showBankTransaction", + * tags={"bank_transactions"}, + * 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 BankTransaction Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function show(ShowBankTransactionRequest $request, BankTransaction $bank_transaction) + { + return $this->itemResponse($bank_transaction); + } + + + /** + * Show the form for editing the specified resource. + * + * @param EditBankTransactionRequest $request + * @param BankTransaction $bank_transaction + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_transactions/{id}/edit", + * operationId="editBankTransaction", + * tags={"bank_transactions"}, + * 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 BankTransaction Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function edit(EditBankTransactionRequest $request, BankTransaction $bank_transaction) + { + return $this->itemResponse($bank_transaction); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateBankTransactionRequest $request + * @param BankTransaction $bank_transaction + * @return Response + * + * + * + * @OA\Put( + * path="/api/v1/bank_transactions/{id}", + * operationId="updateBankTransaction", + * tags={"bank_transactions"}, + * summary="Updates a bank_transaction", + * description="Handles the updating of 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 BankTransaction Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_transaction object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function update(UpdateBankTransactionRequest $request, BankTransaction $bank_transaction) + { + + //stubs for updating the model + $bank_transaction = $this->bank_transaction_repo->save($request->all(), $bank_transaction); + + return $this->itemResponse($bank_transaction->fresh()); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateBankTransactionRequest $request + * @return Response + * + * + * + * @OA\Get( + * path="/api/v1/bank_transactions/create", + * operationId="getBankTransactionsCreate", + * tags={"bank_transactions"}, + * summary="Gets a new blank bank_transaction 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 object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function create(CreateBankTransactionRequest $request) + { + $bank_transaction = BankTransactionFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + return $this->itemResponse($bank_transaction); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreBankTransactionRequest $request + * @return Response + * + * + * + * @OA\Post( + * path="/api/v1/bank_transactions", + * operationId="storeBankTransaction", + * tags={"bank_transactions"}, + * summary="Adds a bank_transaction", + * 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 object", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/BankTransaction"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function store(StoreBankTransactionRequest $request) + { + //stub to store the model + $bank_transaction = $this->bank_transaction_repo->save($request->all(), BankTransactionFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id)); + + return $this->itemResponse($bank_transaction); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyBankTransactionRequest $request + * @param BankTransaction $bank_transaction + * @return Response + * + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/bank_transactions/{id}", + * operationId="deleteBankTransaction", + * tags={"bank_transactions"}, + * summary="Deletes a bank_transaction", + * description="Handles the deletion of 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 BankTransaction 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(DestroyBankTransactionRequest $request, BankTransaction $bank_transaction) + { + $this->bank_transaction_repo->delete($bank_transaction); + + return $this->itemResponse($bank_transaction->fresh()); + } + + + /** + * Perform bulk actions on the list view. + * + * @return Collection + * + * @OA\Post( + * path="/api/v1/bank_transations/bulk", + * operationId="bulkBankTransactions", + * tags={"bank_transactions"}, + * summary="Performs bulk actions on an array of bank_transations", + * 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_transactions = BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); + + $bank_transactions->each(function ($bank_transaction, $key) use ($action) { + if (auth()->user()->can('edit', $bank_transaction)) { + $this->bank_transaction_repo->{$action}($bank_transaction); + } + }); + + /* Need to understand which permission are required for the given bulk action ie. view / edit */ + + return $this->listResponse(BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); + } + + /** + * Perform bulk actions on the list view. + * + * @return Collection + * + * @OA\Post( + * path="/api/v1/bank_transations/match", + * operationId="matchBankTransactions", + * tags={"bank_transactions"}, + * summary="Performs match actions on an array of bank_transactions", + * 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 match(MatchBankTransactionRequest $request) + { + + // MatchBankTransactions::dispatch(auth()->user()->company()->id, auth()->user()->company()->db, $request->all()); + + $bts = (new MatchBankTransactions(auth()->user()->company()->id, auth()->user()->company()->db, $request->all()))->handle(); + + return $this->listResponse($bts); + + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index b004034e5d40..cc98b0e71dfd 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -105,6 +105,8 @@ class BaseController extends Controller 'company.vendors.documents', 'company.webhooks', 'company.system_logs', + 'company.bank_integrations', + 'company.bank_transactions', ]; private $mini_load = [ @@ -122,6 +124,7 @@ class BaseController extends Controller 'company.designs.company', 'company.expense_categories', 'company.subscriptions', + 'company.bank_integrations', ]; public function __construct() @@ -438,6 +441,20 @@ class BaseController extends Controller $query->where('subscriptions.user_id', $user->id); } }, + 'company.bank_integrations'=> function ($query) use ($updated_at, $user) { + $query->whereNotNull('updated_at'); + + if (! $user->isAdmin()) { + $query->where('bank_integrations.user_id', $user->id); + } + }, + 'company.bank_transactions'=> function ($query) use ($updated_at, $user) { + $query->where('updated_at', '>=', $updated_at); + + if (! $user->isAdmin()) { + $query->where('bank_transactions.user_id', $user->id); + } + }, ] ); @@ -497,6 +514,12 @@ class BaseController extends Controller $query->where('activities.user_id', $user->id); } }, + 'company.bank_integrations'=> function ($query) use ($created_at, $user) { + + if (! $user->isAdmin()) { + $query->where('bank_integrations.user_id', $user->id); + } + }, ] ); @@ -741,6 +764,20 @@ class BaseController extends Controller } }, + 'company.bank_integrations'=> function ($query) use ($created_at, $user) { + $query->where('created_at', '>=', $created_at); + + if (! $user->isAdmin()) { + $query->where('bank_integrations.user_id', $user->id); + } + }, + 'company.bank_transactions'=> function ($query) use ($created_at, $user) { + $query->where('created_at', '>=', $created_at); + + if (! $user->isAdmin()) { + $query->where('bank_transactions.user_id', $user->id); + } + }, ] ); diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 800037c8b11b..60a16d22423a 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -669,7 +669,7 @@ class ClientController extends BaseController * * * @OA\Post( - * path="/api/v1/clients/{id}/{mergaeble_client_hashed_id}/merge", + * path="/api/v1/clients/{id}/{mergeable_client_hashed_id}/merge", * operationId="mergeClient", * tags={"clients"}, * summary="Merges two clients", @@ -690,7 +690,7 @@ class ClientController extends BaseController * ), * ), * @OA\Parameter( - * name="mergeable_client_hashedid", + * name="mergeable_client_hashed_id", * in="path", * description="The Mergeable Client Hashed ID", * example="D2J234DFA", diff --git a/app/Http/Controllers/OpenAPI/ActivitySchema.php b/app/Http/Controllers/OpenAPI/ActivitySchema.php index 2c14067a2fa2..e53877852857 100644 --- a/app/Http/Controllers/OpenAPI/ActivitySchema.php +++ b/app/Http/Controllers/OpenAPI/ActivitySchema.php @@ -3,20 +3,21 @@ * @OA\Schema( * schema="Activity", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="activity_type_id", type="string", example="2", description="______"), - * @OA\Property(property="client_id", type="string", example="2", description="______"), - * @OA\Property(property="company_id", type="string", example="2", description="______"), - * @OA\Property(property="user_id", type="string", example="2", description="______"), - * @OA\Property(property="invoice_id", type="string", example="2", description="______"), - * @OA\Property(property="payment_id", type="string", example="2", description="______"), - * @OA\Property(property="credit_id", type="string", example="2", description="______"), - * @OA\Property(property="updated_at", type="string", example="2", description="______"), - * @OA\Property(property="expense_id", type="string", example="2", description="______"), - * @OA\Property(property="is_system", type="boolean", example=true, description="______"), - * @OA\Property(property="contact_id", type="string", example="2", description="______"), - * @OA\Property(property="task_id", type="string", example="2", description="______"), - * @OA\Property(property="notes", type="string", example="2", description="______"), - * @OA\Property(property="ip", type="string", example="2", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The id field of the activity"), + * @OA\Property(property="activity_type_id", type="string", example="Opnel5aKBz", description="The activity type id"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The client hashed id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The company hashed id"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), + * @OA\Property(property="invoice_id", type="string", example="Opnel5aKBz", description="The invoice hashed id"), + * @OA\Property(property="payment_id", type="string", example="Opnel5aKBz", description="The payment hashed id"), + * @OA\Property(property="credit_id", type="string", example="Opnel5aKBz", description="The credit hashed id"), + * @OA\Property(property="updated_at", type="integer", example="343421434", description="Unixtimestamp the last time the record was updated"), + * @OA\Property(property="expense_id", type="string", example="Opnel5aKBz", description="The expense hashed id"), + * @OA\Property(property="is_system", type="boolean", example=true, description="Defines is the activity was performed by the system"), + * @OA\Property(property="contact_id", type="string", example="Opnel5aKBz", description="The contact hashed id"), + * @OA\Property(property="task_id", type="string", example="Opnel5aKBz", description="The task hashed id"), + * @OA\Property(property="notes", type="string", example="Opnel5aKBz", description="Activity Notes"), + * @OA\Property(property="token_id", type="string", example="Opnel5aKBz", description="The hashed ID of the token who performed the action"), + * @OA\Property(property="ip", type="string", example="192.168.1.252", description="The IP Address of the user who performed the action"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/BankIntegration.php b/app/Http/Controllers/OpenAPI/BankIntegration.php new file mode 100644 index 000000000000..4731d13e1938 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BankIntegration.php @@ -0,0 +1,18 @@ +", description="The custom template"), * @OA\Property(property="counter_number_applied", type="string", example="when_sent", description="enum when the invoice number counter is set, ie when_saved, when_sent, when_paid"), * @OA\Property(property="quote_number_applied", type="string", example="when_sent", description="enum when the quote number counter is set, ie when_saved, when_sent"), - * @OA\Property(property="custom_message_dashboard", type="string", example="Please pay invoices immediately", description="____________"), - * @OA\Property(property="custom_message_unpaid_invoice", type="string", example="Please pay invoices immediately", description="____________"), - * @OA\Property(property="custom_message_paid_invoice", type="string", example="Thanks for paying this invoice!", description="____________"), - * @OA\Property(property="custom_message_unapproved_quote", type="string", example="Please approve quote", description="____________"), - * @OA\Property(property="lock_invoices", type="boolean", example=true, description="____________"), - * @OA\Property(property="auto_archive_invoice", type="boolean", example=true, description="____________"), - * @OA\Property(property="auto_archive_quote", type="boolean", example=true, description="____________"), - * @OA\Property(property="auto_convert_quote", type="boolean", example=true, description="____________"), - * @OA\Property(property="inclusive_taxes", type="boolean", example=true, description="____________"), + * @OA\Property(property="custom_message_dashboard", type="string", example="Please pay invoices immediately", description="A custom message which is displayed on the dashboard"), + * @OA\Property(property="custom_message_unpaid_invoice", type="string", example="Please pay invoices immediately", description="A custom message which is displayed in the client portal when a client is viewing a unpaid invoice."), + * @OA\Property(property="custom_message_paid_invoice", type="string", example="Thanks for paying this invoice!", description="A custom message which is displayed in the client portal when a client is viewing a paid invoice."), + * @OA\Property(property="custom_message_unapproved_quote", type="string", example="Please approve quote", description="A custom message which is displayed in the client portal when a client is viewing a unapproved quote."), + * @OA\Property(property="lock_invoices", type="boolean", example=true, description="Toggles whether invoices are locked once sent and cannot be modified further"), + * @OA\Property(property="auto_archive_invoice", type="boolean", example=true, description="Toggles whether a invoice is archived immediately following payment"), + * @OA\Property(property="auto_archive_quote", type="boolean", example=true, description="Toggles whether a quote is archived after being converted to a invoice"), + * @OA\Property(property="auto_convert_quote", type="boolean", example=true, description="Toggles whether a quote is converted to a invoice when approved"), + * @OA\Property(property="inclusive_taxes", type="boolean", example=true, description="Boolean flag determining whether inclusive or exclusive taxes are used"), * @OA\Property(property="translations", type="object", example="", description="JSON payload of customized translations"), * @OA\Property(property="task_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the task number pattern"), - * @OA\Property(property="task_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="task_number_counter", type="integer", example="1", description="The incrementing counter for tasks"), * @OA\Property(property="reminder_send_time", type="integer", example="32400", description="Time from UTC +0 when the email will be sent to the client"), * @OA\Property(property="expense_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the expense number pattern"), - * @OA\Property(property="expense_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="expense_number_counter", type="integer", example="1", description="The incrementing counter for expenses"), * @OA\Property(property="vendor_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the vendor number pattern"), - * @OA\Property(property="vendor_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="vendor_number_counter", type="integer", example="1", description="The incrementing counter for vendors"), * @OA\Property(property="ticket_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the ticket number pattern"), - * @OA\Property(property="ticket_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="ticket_number_counter", type="integer", example="1", description="The incrementing counter for tickets"), * @OA\Property(property="payment_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the payment number pattern"), - * @OA\Property(property="payment_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="payment_number_counter", type="integer", example="1", description="The incrementing counter for payments"), * @OA\Property(property="invoice_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the invoice number pattern"), - * @OA\Property(property="invoice_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="invoice_number_counter", type="integer", example="1", description="The incrementing counter for invoices"), * @OA\Property(property="quote_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the quote number pattern"), - * @OA\Property(property="quote_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="quote_number_counter", type="integer", example="1", description="The incrementing counter for quotes"), * @OA\Property(property="client_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the client number pattern"), - * @OA\Property(property="client_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="client_number_counter", type="integer", example="1", description="The incrementing counter for clients"), * @OA\Property(property="credit_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the credit number pattern"), - * @OA\Property(property="credit_number_counter", type="integer", example="1", description="____________"), + * @OA\Property(property="credit_number_counter", type="integer", example="1", description="The incrementing counter for credits"), * @OA\Property(property="recurring_invoice_number_prefix", type="string", example="R", description="This string is prepended to the recurring invoice number"), * @OA\Property(property="reset_counter_frequency_id", type="integer", example="1", description="CONSTANT which is used to apply the frequency which the counters are reset"), * @OA\Property(property="reset_counter_date", type="string", example="2019-01-01", description="The explicit date which is used to reset counters"), @@ -103,52 +103,52 @@ * @OA\Property(property="name", type="string", example="Acme Co", description="The company name"), * @OA\Property(property="company_logo", type="object", example="logo.png", description="The company logo file"), * @OA\Property(property="website", type="string", example="www.acme.com", description="The company website URL"), - * @OA\Property(property="address1", type="string", example="Suite 888", description="____________"), - * @OA\Property(property="address2", type="string", example="5 Jimbo Way", description="____________"), - * @OA\Property(property="city", type="string", example="Sydney", description="____________"), - * @OA\Property(property="state", type="string", example="Florisa", description="____________"), - * @OA\Property(property="postal_code", type="string", example="90210", description="____________"), - * @OA\Property(property="phone", type="string", example="555-213-3948", description="____________"), - * @OA\Property(property="email", type="string", example="joe@acme.co", description="____________"), + * @OA\Property(property="address1", type="string", example="Suite 888", description="The company address line 1"), + * @OA\Property(property="address2", type="string", example="5 Jimbo Way", description="The company address line 2"), + * @OA\Property(property="city", type="string", example="Sydney", description="The company city"), + * @OA\Property(property="state", type="string", example="Florisa", description="The company state"), + * @OA\Property(property="postal_code", type="string", example="90210", description="The company zip/postal code"), + * @OA\Property(property="phone", type="string", example="555-213-3948", description="The company phone"), + * @OA\Property(property="email", type="string", example="joe@acme.co", description="The company email"), * @OA\Property(property="country_id", type="string", example="1", description="The country ID"), - * @OA\Property(property="vat_number", type="string", example="32 120 377 720", description="____________"), + * @OA\Property(property="vat_number", type="string", example="32 120 377 720", description="The company VAT/TAX ID number"), * @OA\Property(property="page_size", type="string", example="A4", description="The default page size"), * @OA\Property(property="font_size", type="number", example="9", description="The font size"), * @OA\Property(property="primary_font", type="string", example="roboto", description="The primary font"), * @OA\Property(property="secondary_font", type="string", example="roboto", description="The secondary font"), - * @OA\Property(property="hide_paid_to_date", type="boolean", example=false, description="____________"), - * @OA\Property(property="embed_documents", type="boolean", example=false, description="____________"), - * @OA\Property(property="all_pages_header", type="boolean", example=false, description="____________"), - * @OA\Property(property="all_pages_footer", type="boolean", example=false, description="____________"), - * @OA\Property(property="document_email_attachment", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_client_portal_password", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_email_markup", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_client_portal_dashboard", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_client_portal", type="boolean", example=false, description="____________"), - * @OA\Property(property="email_template_statement", type="string", example="template matter", description="____________"), - * @OA\Property(property="email_subject_statement", type="string", example="subject matter", description="____________"), - * @OA\Property(property="signature_on_pdf", type="boolean", example=false, description="____________"), - * @OA\Property(property="quote_footer", type="string", example="the quote footer", description="____________"), - * @OA\Property(property="email_subject_custom1", type="string", example="Custom Subject 1", description="____________"), - * @OA\Property(property="email_subject_custom2", type="string", example="Custom Subject 2", description="____________"), - * @OA\Property(property="email_subject_custom3", type="string", example="Custom Subject 3", description="____________"), - * @OA\Property(property="email_template_custom1", type="string", example="", description="____________"), - * @OA\Property(property="email_template_custom2", type="string", example="", description="____________"), - * @OA\Property(property="email_template_custom3", type="string", example="", description="____________"), - * @OA\Property(property="enable_reminder1", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_reminder2", type="boolean", example=false, description="____________"), - * @OA\Property(property="enable_reminder3", type="boolean", example=false, description="____________"), + * @OA\Property(property="hide_paid_to_date", type="boolean", example=false, description="Flags whether to hide the paid to date field"), + * @OA\Property(property="embed_documents", type="boolean", example=false, description="Toggled whether to embed documents in the PDF"), + * @OA\Property(property="all_pages_header", type="boolean", example=false, description="The header for the PDF"), + * @OA\Property(property="all_pages_footer", type="boolean", example=false, description="The footer for the PDF"), + * @OA\Property(property="document_email_attachment", type="boolean", example=false, description="Toggles whether to attach documents in the email"), + * @OA\Property(property="enable_client_portal_password", type="boolean", example=false, description="Toggles password protection of the client portal"), + * @OA\Property(property="enable_email_markup", type="boolean", example=false, description="Toggles the use of markdown in emails"), + * @OA\Property(property="enable_client_portal_dashboard", type="boolean", example=false, description="Toggles whether the client dashboard is shown in the client portal"), + * @OA\Property(property="enable_client_portal", type="boolean", example=false, description="Toggles whether the entire client portal is displayed to the client, or only the context"), + * @OA\Property(property="email_template_statement", type="string", example="template matter", description="The body of the email for statements"), + * @OA\Property(property="email_subject_statement", type="string", example="subject matter", description="The subject of the email for statements"), + * @OA\Property(property="signature_on_pdf", type="boolean", example=false, description="Toggles whether the signature (if available) is displayed on the PDF"), + * @OA\Property(property="quote_footer", type="string", example="the quote footer", description="The default quote footer"), + * @OA\Property(property="email_subject_custom1", type="string", example="Custom Subject 1", description="Custom reminder template subject"), + * @OA\Property(property="email_subject_custom2", type="string", example="Custom Subject 2", description="Custom reminder template subject"), + * @OA\Property(property="email_subject_custom3", type="string", example="Custom Subject 3", description="Custom reminder template subject"), + * @OA\Property(property="email_template_custom1", type="string", example="", description="Custom reminder template body"), + * @OA\Property(property="email_template_custom2", type="string", example="", description="Custom reminder template body"), + * @OA\Property(property="email_template_custom3", type="string", example="", description="Custom reminder template body"), + * @OA\Property(property="enable_reminder1", type="boolean", example=false, description="Toggles whether this reminder is enabled"), + * @OA\Property(property="enable_reminder2", type="boolean", example=false, description="Toggles whether this reminder is enabled"), + * @OA\Property(property="enable_reminder3", type="boolean", example=false, description="Toggles whether this reminder is enabled"), * @OA\Property(property="num_days_reminder1", type="number", example="9", description="The Reminder interval"), * @OA\Property(property="num_days_reminder2", type="number", example="9", description="The Reminder interval"), * @OA\Property(property="num_days_reminder3", type="number", example="9", description="The Reminder interval"), * @OA\Property(property="schedule_reminder1", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), * @OA\Property(property="schedule_reminder2", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), * @OA\Property(property="schedule_reminder3", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), - * @OA\Property(property="late_fee_amount1", type="number", example=10.00, description="____________"), - * @OA\Property(property="late_fee_amount2", type="number", example=20.00, description="____________"), - * @OA\Property(property="late_fee_amount3", type="number", example=100.00, description="____________"), - * @OA\Property(property="endless_reminder_frequency_id", type="string", example="1", description="____________"), - * @OA\Property(property="client_online_payment_notification", type="boolean", example=false, description="____________"), - * @OA\Property(property="client_manual_payment_notification", type="boolean", example=false, description="____________"), + * @OA\Property(property="late_fee_amount1", type="number", example=10.00, description="The late fee amount for reminder 1"), + * @OA\Property(property="late_fee_amount2", type="number", example=20.00, description="The late fee amount for reminder 2"), + * @OA\Property(property="late_fee_amount3", type="number", example=100.00, description="The late fee amount for reminder 2"), + * @OA\Property(property="endless_reminder_frequency_id", type="string", example="1", description="The frequency id of the endless reminder"), + * @OA\Property(property="client_online_payment_notification", type="boolean", example=false, description="Determines if a client should receive the notification for a online payment"), + * @OA\Property(property="client_manual_payment_notification", type="boolean", example=false, description="Determines if a client should receive the notification for a manually entered payment"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/CreditPaymentableSchema.php b/app/Http/Controllers/OpenAPI/CreditPaymentableSchema.php index e05ff50a8552..598b12073993 100644 --- a/app/Http/Controllers/OpenAPI/CreditPaymentableSchema.php +++ b/app/Http/Controllers/OpenAPI/CreditPaymentableSchema.php @@ -3,8 +3,8 @@ * @OA\Schema( * schema="CreditPaymentable", * type="object", - * @OA\Property(property="credit_id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="amount", type="string", example="2", description="______"), + * @OA\Property(property="credit_id", type="string", example="Opnel5aKBz", description="The credit hashed id"), + * @OA\Property(property="amount", type="string", example="2", description="The credit amount"), * * ) */ diff --git a/app/Http/Controllers/OpenAPI/CreditSchema.php b/app/Http/Controllers/OpenAPI/CreditSchema.php index 9f7ff64e86a7..1ea9ecb164bf 100644 --- a/app/Http/Controllers/OpenAPI/CreditSchema.php +++ b/app/Http/Controllers/OpenAPI/CreditSchema.php @@ -3,33 +3,33 @@ * @OA\Schema( * schema="Credit", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="status_id", type="string", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The credit hashed id"), + * @OA\Property(property="user_id", type="string", example="", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="", description="The client hashed id"), + * @OA\Property(property="status_id", type="string", example="", description="The status field id infors of the current status of the credit"), * @OA\Property(property="invoice_id", type="string", example="", description="The linked invoice this credit is applied to"), * @OA\Property(property="number", type="string", example="QUOTE_101", description="The credit number - is a unique alpha numeric number per credit per company"), - * @OA\Property(property="po_number", type="string", example="", description="________"), - * @OA\Property(property="terms", type="string", example="", description="________"), - * @OA\Property(property="public_notes", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="footer", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="tax_name1", type="string", example="", description="________"), - * @OA\Property(property="tax_name2", type="string", example="", description="________"), - * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_name3", type="string", example="", description="________"), - * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"), + * @OA\Property(property="po_number", type="string", example="", description="The purchase order number this credit refers to"), + * @OA\Property(property="terms", type="string", example="", description="The credit terms field"), + * @OA\Property(property="public_notes", type="string", example="", description="The public notes field of the credit"), + * @OA\Property(property="private_notes", type="string", example="", description="The private notes field of the credit"), + * @OA\Property(property="footer", type="string", example="", description="The credit footer text"), + * @OA\Property(property="custom_value1", type="string", example="", description="A Custom value"), + * @OA\Property(property="custom_value2", type="string", example="", description="A Custom value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A Custom value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A Custom value"), + * @OA\Property(property="tax_name1", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="", description="The tax rate"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax name"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), * @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the credit"), - * @OA\Property(property="line_items", type="object", example="", description="_________"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), + * @OA\Property(property="line_items", type="object", example="", description="The line items array containing the line items of the credit"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The total credit amount"), + * @OA\Property(property="balance", type="number", format="float", example="10.00", description="The credit balance"), * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), diff --git a/app/Http/Controllers/OpenAPI/DocumentSchema.php b/app/Http/Controllers/OpenAPI/DocumentSchema.php index 12e0372441af..20f68e7ee627 100644 --- a/app/Http/Controllers/OpenAPI/DocumentSchema.php +++ b/app/Http/Controllers/OpenAPI/DocumentSchema.php @@ -3,18 +3,18 @@ * @OA\Schema( * schema="Document", * type="object", - * @OA\Property(property="id", type="string", example="AS3df3A", description="The design hashed id"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="project_id", type="string", example="", description="__________"), - * @OA\Property(property="vendor_id", type="string", example="", description="__________"), - * @OA\Property(property="name", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="url", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="preview", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="type", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="disk", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="hash", type="string", example="Beauty", description="The design name"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="Flag to determine if the design is deleted"), + * @OA\Property(property="id", type="string", example="AS3df3A", description="The document hashed id"), + * @OA\Property(property="user_id", type="string", example="", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="", description="The assigned user hashed id"), + * @OA\Property(property="project_id", type="string", example="", description="The project associated with this document"), + * @OA\Property(property="vendor_id", type="string", example="", description="The vendor associated with this documents"), + * @OA\Property(property="name", type="string", example="Beauty", description="The document name"), + * @OA\Property(property="url", type="string", example="Beauty", description="The document url"), + * @OA\Property(property="preview", type="string", example="Beauty", description="The document preview url"), + * @OA\Property(property="type", type="string", example="Beauty", description="The document type"), + * @OA\Property(property="disk", type="string", example="Beauty", description="The document disk"), + * @OA\Property(property="hash", type="string", example="Beauty", description="The document hashed"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Flag to determine if the document is deleted"), * @OA\Property(property="is_default", type="boolean", example=true, description="Flag to determine if the document is a default doc"), * @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/ExpenseCategorySchema.php b/app/Http/Controllers/OpenAPI/ExpenseCategorySchema.php index f42ce8143905..5d0541097f0f 100644 --- a/app/Http/Controllers/OpenAPI/ExpenseCategorySchema.php +++ b/app/Http/Controllers/OpenAPI/ExpenseCategorySchema.php @@ -3,11 +3,11 @@ * @OA\Schema( * schema="ExpenseCategory", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="name", type="string", example="Accounting", description="______"), - * @OA\Property(property="user_id", type="string", example="XS987sD", description="______"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="______"), - * @OA\Property(property="updated_at", type="string", example="2", description="______"), - * @OA\Property(property="created_at", type="string", example="2", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The expense hashed id"), + * @OA\Property(property="name", type="string", example="Accounting", description="The expense category name"), + * @OA\Property(property="user_id", type="string", example="XS987sD", description="The user hashed id"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Flag determining whether the expense category has been deleted"), + * @OA\Property(property="updated_at", type="integer", example="2", description="The updated at timestamp"), + * @OA\Property(property="created_at", type="integer", example="2", description="The created at timestamp"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/ExpenseSchema.php b/app/Http/Controllers/OpenAPI/ExpenseSchema.php index f92d3faa546e..32d56b1aa57c 100644 --- a/app/Http/Controllers/OpenAPI/ExpenseSchema.php +++ b/app/Http/Controllers/OpenAPI/ExpenseSchema.php @@ -3,40 +3,40 @@ * @OA\Schema( * schema="Expense", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_id", type="string", example="", description="________"), - * @OA\Property(property="bank_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_currency_id", type="string", example="", description="________"), - * @OA\Property(property="expense_currency_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_category_id", type="string", example="", description="________"), - * @OA\Property(property="payment_type_id", type="string", example="", description="________"), - * @OA\Property(property="recurring_expense_id", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="public_notes", type="string", example="", description="________"), - * @OA\Property(property="transaction_reference", type="string", example="", description="________"), - * @OA\Property(property="transcation_id", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="tax_name1", type="string", example="", description="________"), - * @OA\Property(property="tax_name2", type="string", example="", description="________"), - * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_name3", type="string", example="", description="________"), - * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="foreign_amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="exchange_rate", type="number", format="float", example="0.80", description="_________"), - * @OA\Property(property="date", type="string", example="", description="________"), - * @OA\Property(property="payment_date", type="string", example="", description="________"), - * @OA\Property(property="should_be_invoiced", type="boolean", example=true, description="_________"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"), - * @OA\Property(property="invoice_documents", type="boolean", example=true, description=""), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The expense hashed id"), + * @OA\Property(property="user_id", type="string", example="", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="", description="The client hashed id"), + * @OA\Property(property="invoice_id", type="string", example="", description="The related invoice hashed id"), + * @OA\Property(property="bank_id", type="string", example="", description="The bank id related to this expense"), + * @OA\Property(property="invoice_currency_id", type="string", example="", description="The currency id of the related invoice"), + * @OA\Property(property="expense_currency_id", type="string", example="", description="The currency id of the expense"), + * @OA\Property(property="invoice_category_id", type="string", example="", description="The invoice category id"), + * @OA\Property(property="payment_type_id", type="string", example="", description="The payment type id"), + * @OA\Property(property="recurring_expense_id", type="string", example="", description="The related recurring expense this expense was created from"), + * @OA\Property(property="private_notes", type="string", example="", description="The private notes of the expense"), + * @OA\Property(property="public_notes", type="string", example="", description="The public notes of the expense"), + * @OA\Property(property="transaction_reference", type="string", example="", description="The transaction references of the expense"), + * @OA\Property(property="transcation_id", type="string", example="", description="The transaction id of the expense"), + * @OA\Property(property="custom_value1", type="string", example="", description="A custom value"), + * @OA\Property(property="custom_value2", type="string", example="", description="A custom value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A custom value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A custom value"), + * @OA\Property(property="tax_name1", type="string", example="", description="Tax name"), + * @OA\Property(property="tax_name2", type="string", example="", description="Tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="Tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="Tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="Tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="Tax rate"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The total expense amont"), + * @OA\Property(property="foreign_amount", type="number", format="float", example="10.00", description="The total foreign amount of the expense"), + * @OA\Property(property="exchange_rate", type="number", format="float", example="0.80", description="The exchange rate at the time of the expense"), + * @OA\Property(property="date", type="string", example="2022-12-01", description="The expense date formate Y-m-d"), + * @OA\Property(property="payment_date", type="string", example="", description="The date of payment for the expense, format Y-m-d"), + * @OA\Property(property="should_be_invoiced", type="boolean", example=true, description="Flag whether the expense should be invoiced"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Boolean determining whether the expense has been deleted"), + * @OA\Property(property="invoice_documents", type="boolean", example=true, description="Passing the expense documents over to the invoice"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), * ) diff --git a/app/Http/Controllers/OpenAPI/FeesAndLimitsSchema.php b/app/Http/Controllers/OpenAPI/FeesAndLimitsSchema.php index eaeaec5ca708..96ea5cc48edf 100644 --- a/app/Http/Controllers/OpenAPI/FeesAndLimitsSchema.php +++ b/app/Http/Controllers/OpenAPI/FeesAndLimitsSchema.php @@ -3,17 +3,17 @@ * @OA\Schema( * schema="FeesAndLimits", * type="object", - * @OA\Property(property="min_limit", type="string", example="2", description="______"), - * @OA\Property(property="max_limit", type="string", example="2", description="______"), - * @OA\Property(property="fee_amount", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="fee_percent", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="fee_tax_name1", type="string", example="2", description="______"), - * @OA\Property(property="fee_tax_name2", type="string", example="2", description="______"), - * @OA\Property(property="fee_tax_name3", type="string", example="2", description="______"), - * @OA\Property(property="fee_tax_rate1", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="fee_tax_rate2", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="fee_tax_rate3", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="fee_cap", type="number", format="float", example="2.0", description="______"), - * @OA\Property(property="adjust_fee_percent", type="boolean", example=true, description="______"), + * @OA\Property(property="min_limit", type="string", example="2", description="The minimum amount accepted for this gateway"), + * @OA\Property(property="max_limit", type="string", example="2", description="The maximum amount accepted for this gateway"), + * @OA\Property(property="fee_amount", type="number", format="float", example="2.0", description="The gateway fee amount"), + * @OA\Property(property="fee_percent", type="number", format="float", example="2.0", description="The gateway fee percentage"), + * @OA\Property(property="fee_tax_name1", type="string", example="GST", description="Fee tax name"), + * @OA\Property(property="fee_tax_name2", type="string", example="VAT", description="Fee tax name"), + * @OA\Property(property="fee_tax_name3", type="string", example="CA Sales Tax", description="Fee tax name"), + * @OA\Property(property="fee_tax_rate1", type="number", format="float", example="10.0", description="The tax rate"), + * @OA\Property(property="fee_tax_rate2", type="number", format="float", example="17.5", description="The tax rate"), + * @OA\Property(property="fee_tax_rate3", type="number", format="float", example="25.0", description="The tax rate"), + * @OA\Property(property="fee_cap", type="number", format="float", example="2.0", description="If set the fee amount will be no higher than this amount"), + * @OA\Property(property="adjust_fee_percent", type="boolean", example=true, description="Adjusts the fee to match the exact gateway fee."), * ) */ diff --git a/app/Http/Controllers/OpenAPI/GroupSettingSchema.php b/app/Http/Controllers/OpenAPI/GroupSettingSchema.php index 2da966cd3c24..febf878e989c 100644 --- a/app/Http/Controllers/OpenAPI/GroupSettingSchema.php +++ b/app/Http/Controllers/OpenAPI/GroupSettingSchema.php @@ -3,10 +3,10 @@ * @OA\Schema( * schema="GroupSetting", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="name", type="string", example="", description="________"), - * @OA\Property(property="settings", type="object", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The group setting hashed id"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The company hashed id"), + * @OA\Property(property="name", type="string", example="A groupies group", description="The name of the group"), + * @OA\Property(property="settings", type="object", example="", description="The settings object"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/InvoiceSchema.php b/app/Http/Controllers/OpenAPI/InvoiceSchema.php index 4376403c971e..1e9fef4a3d7b 100644 --- a/app/Http/Controllers/OpenAPI/InvoiceSchema.php +++ b/app/Http/Controllers/OpenAPI/InvoiceSchema.php @@ -3,43 +3,43 @@ * @OA\Schema( * schema="Invoice", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="status_id", type="string", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The invoice hashed id"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The client hashed id"), + * @OA\Property(property="status_id", type="string", example="4", description="The invoice status variable"), * @OA\Property(property="number", type="string", example="INV_101", description="The invoice number - is a unique alpha numeric number per invoice per company"), - * @OA\Property(property="po_number", type="string", example="", description="________"), - * @OA\Property(property="terms", type="string", example="", description="________"), - * @OA\Property(property="public_notes", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="footer", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="tax_name1", type="string", example="", description="________"), - * @OA\Property(property="tax_name2", type="string", example="", description="________"), - * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_name3", type="string", example="", description="________"), - * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"), + * @OA\Property(property="po_number", type="string", example="PO-1234", description="The purchase order associated with this invoice"), + * @OA\Property(property="terms", type="string", example="These are invoice terms", description="The invoice terms"), + * @OA\Property(property="public_notes", type="string", example="These are some public notes", description="The public notes of the invoice"), + * @OA\Property(property="private_notes", type="string", example="These are some private notes", description="The private notes of the invoice"), + * @OA\Property(property="footer", type="string", example="", description="The invoice footer notes"), + * @OA\Property(property="custom_value1", type="string", example="2022-10-01", description="A custom field value"), + * @OA\Property(property="custom_value2", type="string", example="Something custom", description="A custom field value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A custom field value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A custom field value"), + * @OA\Property(property="tax_name1", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), * @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the invoice"), - * @OA\Property(property="line_items", type="object", example="", description="_________"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"), + * @OA\Property(property="line_items", type="object", example="", description="An array of objects which define the line items of the invoice"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The invoice amount"), + * @OA\Property(property="balance", type="number", format="float", example="10.00", description="The invoice balance"), + * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="The amount paid on the invoice to date"), + * @OA\Property(property="discount", type="number", format="float", example="10.00", description="The invoice discount, can be an amount or a percentage"), + * @OA\Property(property="partial", type="number", format="float", example="10.00", description="The deposit/partial amount"), + * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="Flag determining if the discount is an amount or a percentage"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Defines if the invoice has been deleted"), * @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"), * @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The Invoice Date"), * @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The last date the invoice was sent out"), * @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"), - * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="_________"), - * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="_________"), + * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="The due date for the deposit/partial amount"), + * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="The due date of the invoice"), * @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"), * @OA\Property(property="last_viewed", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/PaymentSchema.php b/app/Http/Controllers/OpenAPI/PaymentSchema.php index 98b681a625d4..a8d23744904b 100644 --- a/app/Http/Controllers/OpenAPI/PaymentSchema.php +++ b/app/Http/Controllers/OpenAPI/PaymentSchema.php @@ -3,18 +3,18 @@ * @OA\Schema( * schema="Payment", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="invitation_id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="client_contact_id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The payment hashed id"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The client hashed id"), + * @OA\Property(property="invitation_id", type="string", example="Opnel5aKBz", description="The invitation hashed id"), + * @OA\Property(property="client_contact_id", type="string", example="Opnel5aKBz", description="The client contact hashed id"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), * @OA\Property(property="type_id", type="string", example="1", description="The Payment Type ID"), * @OA\Property(property="date", type="string", example="1-1-2014", description="The Payment date"), * @OA\Property(property="transaction_reference", type="string", example="xcsSxcs124asd", description="The transaction reference as defined by the payment gateway"), - * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="private_notes", type="string", example="The payment was refunded due to error", description="______"), - * @OA\Property(property="is_manual", type="boolean", example=true, description="______"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="______"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The assigned user hashed id"), + * @OA\Property(property="private_notes", type="string", example="The payment was refunded due to error", description="The private notes of the payment"), + * @OA\Property(property="is_manual", type="boolean", example=true, description="Flags whether the payment was made manually or processed via a gateway"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Defines if the payment has been deleted"), * @OA\Property(property="amount", type="number", example=10.00, description="The amount of this payment"), * @OA\Property(property="refunded", type="number", example=10.00, description="The refunded amount of this payment"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/PaymentableSchema.php b/app/Http/Controllers/OpenAPI/PaymentableSchema.php index 357dc40c5fac..46a55570c382 100644 --- a/app/Http/Controllers/OpenAPI/PaymentableSchema.php +++ b/app/Http/Controllers/OpenAPI/PaymentableSchema.php @@ -6,8 +6,8 @@ * @OA\Property(property="id", type="string", example="AS3df3A", description="The paymentable hashed id"), * @OA\Property(property="invoice_id", type="string", example="AS3df3A", description="The invoice hashed id"), * @OA\Property(property="credit_id", type="string", example="AS3df3A", description="The credit hashed id"), - * @OA\Property(property="refunded", type="number", format="float", example="10.00", description="______"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="______"), + * @OA\Property(property="refunded", type="number", format="float", example="10.00", description="The amount that has been refunded for this payment"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The amount that has been applied to the payment"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="created_at", type="number", format="integer", example="1434342123", description="Timestamp"),* * ) diff --git a/app/Http/Controllers/OpenAPI/ProductSchema.php b/app/Http/Controllers/OpenAPI/ProductSchema.php index 506fd381f4e2..dd203aa3791d 100644 --- a/app/Http/Controllers/OpenAPI/ProductSchema.php +++ b/app/Http/Controllers/OpenAPI/ProductSchema.php @@ -3,6 +3,6 @@ * @OA\Schema( * schema="Product", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The product hashed id"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/ProjectSchema.php b/app/Http/Controllers/OpenAPI/ProjectSchema.php index a3455a16b0bf..3eafd3a04c49 100644 --- a/app/Http/Controllers/OpenAPI/ProjectSchema.php +++ b/app/Http/Controllers/OpenAPI/ProjectSchema.php @@ -3,7 +3,7 @@ * @OA\Schema( * schema="Project", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="name", type="string", example="New Project", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The project hashed id"), + * @OA\Property(property="name", type="string", example="New Project", description="The project name"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/QuoteSchema.php b/app/Http/Controllers/OpenAPI/QuoteSchema.php index 5ccbd71f08d8..d05264bd4411 100644 --- a/app/Http/Controllers/OpenAPI/QuoteSchema.php +++ b/app/Http/Controllers/OpenAPI/QuoteSchema.php @@ -3,43 +3,43 @@ * @OA\Schema( * schema="Quote", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="status_id", type="string", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The quote hashed id"), + * @OA\Property(property="user_id", type="string", example="", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="", description="The client hashed id"), + * @OA\Property(property="status_id", type="string", example="", description="The status of the quote"), * @OA\Property(property="number", type="string", example="QUOTE_101", description="The quote number - is a unique alpha numeric number per quote per company"), - * @OA\Property(property="po_number", type="string", example="", description="________"), - * @OA\Property(property="terms", type="string", example="", description="________"), - * @OA\Property(property="public_notes", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="footer", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="tax_name1", type="string", example="", description="________"), - * @OA\Property(property="tax_name2", type="string", example="", description="________"), - * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_name3", type="string", example="", description="________"), - * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"), + * @OA\Property(property="po_number", type="string", example="PO-1234", description="The purchase order number associated with this quote"), + * @OA\Property(property="terms", type="string", example="These are some quote terms. Valid for 14 days.", description="The quote terms"), + * @OA\Property(property="public_notes", type="string", example="These are public notes which the client may see", description="Public notes for the quote"), + * @OA\Property(property="private_notes", type="string", example="These are private notes, not to be disclosed to the client", description="Private notes for the quote"), + * @OA\Property(property="footer", type="string", example="The text goes in the footer of the quote", description="Footer text of quote"), + * @OA\Property(property="custom_value1", type="string", example="A custom value", description="Custom value field"), + * @OA\Property(property="custom_value2", type="string", example="A custom value", description="Custom value field"), + * @OA\Property(property="custom_value3", type="string", example="A custom value", description="Custom value field"), + * @OA\Property(property="custom_value4", type="string", example="A custom value", description="Custom value field"), + * @OA\Property(property="tax_name1", type="string", example="GST", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="VAT", description="The tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), * @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the quote"), - * @OA\Property(property="line_items", type="object", example="", description="_________"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"), + * @OA\Property(property="line_items", type="object", example="[{"product_key":"test", "unit_cost":10},{"product_key":"test", "unit_cost":10}]", description="An array of line items of the quote"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The total amount of the quote"), + * @OA\Property(property="balance", type="number", format="float", example="10.00", description="The balance due of the quote"), + * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="The amount that has been paid to date on the quote"), + * @OA\Property(property="discount", type="number", format="float", example="10.00", description="The quote discount"), + * @OA\Property(property="partial", type="number", format="float", example="10.00", description="The partial/deposit amount"), + * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="Boolean flag determining if the quote is an amount or percentage"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Boolean flag determining if the quote has been deleted"), * @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"), * @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The Quote Date"), * @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The last date the quote was sent out"), * @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"), - * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="_________"), - * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="_________"), + * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="The date when the partial/deposit is due"), + * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="The date the quote is valid until"), * @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"), * @OA\Property(property="last_viewed", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/RecurringExpense.php b/app/Http/Controllers/OpenAPI/RecurringExpense.php index f4059674e053..db3df83cbe40 100644 --- a/app/Http/Controllers/OpenAPI/RecurringExpense.php +++ b/app/Http/Controllers/OpenAPI/RecurringExpense.php @@ -3,44 +3,43 @@ * @OA\Schema( * schema="RecurringExpense", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_id", type="string", example="", description="________"), - * @OA\Property(property="bank_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_currency_id", type="string", example="", description="________"), - * @OA\Property(property="expense_currency_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_category_id", type="string", example="", description="________"), - * @OA\Property(property="payment_type_id", type="string", example="", description="________"), - * @OA\Property(property="recurring_expense_id", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="public_notes", type="string", example="", description="________"), - * @OA\Property(property="transaction_reference", type="string", example="", description="________"), - * @OA\Property(property="transcation_id", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="tax_name1", type="string", example="", description="________"), - * @OA\Property(property="tax_name2", type="string", example="", description="________"), - * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="tax_name3", type="string", example="", description="________"), - * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="frequency_id", type="number", format="int", example="1", description="_________"), - * @OA\Property(property="remaining_cycles", type="number", format="int", example="1", description="_________"), - * @OA\Property(property="foreign_amount", type="number", format="float", example="10.00", description="_________"), - * @OA\Property(property="exchange_rate", type="number", format="float", example="0.80", description="_________"), - * @OA\Property(property="date", type="string", example="", description="________"), - * @OA\Property(property="payment_date", type="string", example="", description="________"), - * @OA\Property(property="should_be_invoiced", type="boolean", example=true, description="_________"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the recurring expense"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The hashed id of the user who created the recurring expense"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The hashed id of the user assigned to this recurring expense"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The hashed id of the company"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The hashed id of the client"), + * @OA\Property(property="invoice_id", type="string", example="Opnel5aKBz", description="The hashed id of the invoice"), + * @OA\Property(property="bank_id", type="string", example="22", description="The id of the bank associated with this recurring expense"), + * @OA\Property(property="invoice_currency_id", type="string", example="1", description="The currency id of the invoice associated with this recurring expense"), + * @OA\Property(property="expense_currency_id", type="string", example="1", description="The currency id of the expense associated with this recurring expense"), + * @OA\Property(property="invoice_category_id", type="string", example="1", description="The category id of the invoice"), + * @OA\Property(property="payment_type_id", type="string", example="1", description="The payment type id"), + * @OA\Property(property="private_notes", type="string", example="Private and confidential", description="The recurring expense private notes"), + * @OA\Property(property="public_notes", type="string", example="This is the best client in the world", description="The recurring expense public notes"), + * @OA\Property(property="transaction_reference", type="string", example="EXP-1223-2333", description="The recurring expense transaction reference"), + * @OA\Property(property="transcation_id", type="string", example="1233312312", description="The transaction id of the recurring expense"), + * @OA\Property(property="custom_value1", type="string", example="$1000", description="Custom value field"), + * @OA\Property(property="custom_value2", type="string", example="2022-10-10", description="Custom value field"), + * @OA\Property(property="custom_value3", type="string", example="short text", description="Custom value field"), + * @OA\Property(property="custom_value4", type="string", example="very long text", description="Custom value field"), + * @OA\Property(property="tax_name1", type="string", example="GST", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="VAT", description="The tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The total amount of the recurring expense"), + * @OA\Property(property="frequency_id", type="number", format="int", example="1", description="The frequency this recurring expense fires"), + * @OA\Property(property="remaining_cycles", type="number", format="int", example="1", description="The number of remaining cycles for this recurring expense"), + * @OA\Property(property="foreign_amount", type="number", format="float", example="10.00", description="The foreign currency amount of the recurring expense"), + * @OA\Property(property="exchange_rate", type="number", format="float", example="0.80", description="The exchange rate for the expernse"), + * @OA\Property(property="date", type="string", example="", description="The date of the expense"), + * @OA\Property(property="payment_date", type="string", example="", description="The date the expense was paid"), + * @OA\Property(property="should_be_invoiced", type="boolean", example=true, description="Boolean flag determining if the expense should be invoiced"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Boolean flag determining if the recurring expense is deleted"), * @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The Date it was sent last"), * @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The next send date"), - * @OA\Property(property="invoice_documents", type="boolean", example=true, description=""), + * @OA\Property(property="invoice_documents", type="boolean", example=true, description="Boolean flag determining if the documents associated with this expense should be passed onto the invoice if it is converted to an invoice"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), * ) diff --git a/app/Http/Controllers/OpenAPI/RecurringInvoice.php b/app/Http/Controllers/OpenAPI/RecurringInvoice.php index 81e71de1c54c..075a0b9f973d 100644 --- a/app/Http/Controllers/OpenAPI/RecurringInvoice.php +++ b/app/Http/Controllers/OpenAPI/RecurringInvoice.php @@ -3,6 +3,56 @@ * @OA\Schema( * schema="RecurringInvoice", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the recurring invoice"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The client hashed id"), + * @OA\Property(property="status_id", type="string", example="4", description="The invoice status variable"), + * @OA\Property(property="frequency_id", type="number", example="4", description="The recurring invoice frequency"), + * @OA\Property(property="remaining_cycles", type="number", example="4", description="The number of invoices left to be generated"), + * @OA\Property(property="number", type="string", example="INV_101", description="The recurringinvoice number - is a unique alpha numeric number per invoice per company"), + * @OA\Property(property="po_number", type="string", example="PO-1234", description="The purchase order associated with this recurring invoice"), + * @OA\Property(property="terms", type="string", example="These are invoice terms", description="The invoice terms"), + * @OA\Property(property="public_notes", type="string", example="These are some public notes", description="The public notes of the invoice"), + * @OA\Property(property="private_notes", type="string", example="These are some private notes", description="The private notes of the invoice"), + * @OA\Property(property="footer", type="string", example="", description="The invoice footer notes"), + * @OA\Property(property="custom_value1", type="string", example="2022-10-01", description="A custom field value"), + * @OA\Property(property="custom_value2", type="string", example="Something custom", description="A custom field value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A custom field value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A custom field value"), + * @OA\Property(property="tax_name1", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the invoice"), + * @OA\Property(property="line_items", type="object", example="", description="An array of objects which define the line items of the invoice"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The invoice amount"), + * @OA\Property(property="balance", type="number", format="float", example="10.00", description="The invoice balance"), + * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="The amount paid on the invoice to date"), + * @OA\Property(property="discount", type="number", format="float", example="10.00", description="The invoice discount, can be an amount or a percentage"), + * @OA\Property(property="partial", type="number", format="float", example="10.00", description="The deposit/partial amount"), + * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="Flag determining if the discount is an amount or a percentage"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Defines if the invoice has been deleted"), + * @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"), + * @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The Invoice Date"), + * @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The last date the invoice was sent out"), + * @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"), + * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="The due date for the deposit/partial amount"), + * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="The due date of the invoice"), + * @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"), + * @OA\Property(property="last_viewed", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="custom_surcharge1", type="number", format="float", example="10.00", description="First Custom Surcharge"), + * @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"), + * @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"), + * @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"), + * @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/RecurringQuote.php b/app/Http/Controllers/OpenAPI/RecurringQuote.php index 7d752a184844..63ff79967c40 100644 --- a/app/Http/Controllers/OpenAPI/RecurringQuote.php +++ b/app/Http/Controllers/OpenAPI/RecurringQuote.php @@ -3,6 +3,56 @@ * @OA\Schema( * schema="RecurringQuote", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the recurring quote"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The user hashed id"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The assigned user hashed id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The company hashed id"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The client hashed id"), + * @OA\Property(property="status_id", type="string", example="4", description="The quote status variable"), + * @OA\Property(property="frequency_id", type="number", example="4", description="The recurring quote frequency"), + * @OA\Property(property="remaining_cycles", type="number", example="4", description="The number of quotes left to be generated"), + * @OA\Property(property="number", type="string", example="INV_101", description="The recurringquote number - is a unique alpha numeric number per quote per company"), + * @OA\Property(property="po_number", type="string", example="PO-1234", description="The purchase order associated with this recurring quote"), + * @OA\Property(property="terms", type="string", example="These are quote terms", description="The quote terms"), + * @OA\Property(property="public_notes", type="string", example="These are some public notes", description="The public notes of the quote"), + * @OA\Property(property="private_notes", type="string", example="These are some private notes", description="The private notes of the quote"), + * @OA\Property(property="footer", type="string", example="", description="The quote footer notes"), + * @OA\Property(property="custom_value1", type="string", example="2022-10-01", description="A custom field value"), + * @OA\Property(property="custom_value2", type="string", example="Something custom", description="A custom field value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A custom field value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A custom field value"), + * @OA\Property(property="tax_name1", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_name2", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="tax_name3", type="string", example="", description="The tax name"), + * @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="The tax rate"), + * @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the quote"), + * @OA\Property(property="line_items", type="object", example="", description="An array of objects which define the line items of the quote"), + * @OA\Property(property="amount", type="number", format="float", example="10.00", description="The quote amount"), + * @OA\Property(property="balance", type="number", format="float", example="10.00", description="The quote balance"), + * @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="The amount paid on the quote to date"), + * @OA\Property(property="discount", type="number", format="float", example="10.00", description="The quote discount, can be an amount or a percentage"), + * @OA\Property(property="partial", type="number", format="float", example="10.00", description="The deposit/partial amount"), + * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="Flag determining if the discount is an amount or a percentage"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Defines if the quote has been deleted"), + * @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"), + * @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The quote Date"), + * @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The last date the quote was sent out"), + * @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"), + * @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="The due date for the deposit/partial amount"), + * @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="The due date of the quote"), + * @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"), + * @OA\Property(property="last_viewed", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), + * @OA\Property(property="custom_surcharge1", type="number", format="float", example="10.00", description="First Custom Surcharge"), + * @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"), + * @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"), + * @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"), + * @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), + * @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/SystemLogSchema.php b/app/Http/Controllers/OpenAPI/SystemLogSchema.php index 56b002cb029d..708dc070282b 100644 --- a/app/Http/Controllers/OpenAPI/SystemLogSchema.php +++ b/app/Http/Controllers/OpenAPI/SystemLogSchema.php @@ -11,7 +11,7 @@ * @OA\Property(property="category_id", type="integer", example=1, description="The Category Type ID"), * @OA\Property(property="type_id", type="integer", example=1, description="The Type Type ID"), * @OA\Property(property="log", type="object", example="{'key':'value'}", description="The json object of the error"), - * @OA\Property(property="updated_at", type="string", example="2", description="______"), - * @OA\Property(property="created_at", type="string", example="2", description="______"), + * @OA\Property(property="updated_at", type="string", example="2", description="Timestamp"), + * @OA\Property(property="created_at", type="string", example="2", description="Timestamp"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/TaskSchema.php b/app/Http/Controllers/OpenAPI/TaskSchema.php index 0bdf9f4c960e..0fcef8b6f38c 100644 --- a/app/Http/Controllers/OpenAPI/TaskSchema.php +++ b/app/Http/Controllers/OpenAPI/TaskSchema.php @@ -3,25 +3,25 @@ * @OA\Schema( * schema="Task", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), - * @OA\Property(property="invoice_id", type="string", example="", description="________"), - * @OA\Property(property="project_id", type="string", example="", description="________"), - * @OA\Property(property="number", type="string", example="", description="________"), - * @OA\Property(property="time_log", type="string", example="", description="________"), - * @OA\Property(property="is_running", type="boolean", example=true, description="________"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="________"), - * @OA\Property(property="task_status_id", type="string", example="", description="________"), - * @OA\Property(property="description", type="string", example="", description="________"), - * @OA\Property(property="duration", type="integer", example="", description="________"), - * @OA\Property(property="task_status_order", type="integer", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the task"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The hashed id of the user who created the task"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The assigned user of the task"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The hashed id of the company"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The hashed if of the client"), + * @OA\Property(property="invoice_id", type="string", example="Opnel5aKBz", description="The hashed id of the invoice associated with the task"), + * @OA\Property(property="project_id", type="string", example="Opnel5aKBz", description="The hashed id of the project associated with the task"), + * @OA\Property(property="number", type="string", example="TASK-123", description="The number of the task"), + * @OA\Property(property="time_log", type="string", example="[[1,2],[3,4]]", description="An array of unix time stamps defining the start and end times of the task"), + * @OA\Property(property="is_running", type="boolean", example=true, description="Determines if the task is still running"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Boolean flag determining if the task has been deleted"), + * @OA\Property(property="task_status_id", type="string", example="Opnel5aKBz", description="The hashed id of the task status"), + * @OA\Property(property="description", type="string", example="A wonder task to work on", description="The task description"), + * @OA\Property(property="duration", type="integer", example="", description="The task duration"), + * @OA\Property(property="task_status_order", type="integer", example="4", description="The order of the task"), + * @OA\Property(property="custom_value1", type="string", example="2022-10-10", description="A custom value"), + * @OA\Property(property="custom_value2", type="string", example="$1100", description="A custom value"), + * @OA\Property(property="custom_value3", type="string", example="I need help", description="A custom value"), + * @OA\Property(property="custom_value4", type="string", example="INV-3343", description="A custom value"), * @OA\Property(property="created_at", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"), * @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/TaskStatusSchema.php b/app/Http/Controllers/OpenAPI/TaskStatusSchema.php index 6cfaa811f979..c69e017e0f7c 100644 --- a/app/Http/Controllers/OpenAPI/TaskStatusSchema.php +++ b/app/Http/Controllers/OpenAPI/TaskStatusSchema.php @@ -5,7 +5,7 @@ * type="object", * @OA\Property(property="name", type="string", example="Backlog", description="The task status name"), * @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="______"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="A boolean flag determining if the task status has been deleted"), * @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="archived_at", type="number", format="integer", example="134341234234", description="Timestamp"), * ) diff --git a/app/Http/Controllers/OpenAPI/TaxRateSchema.php b/app/Http/Controllers/OpenAPI/TaxRateSchema.php index 5e0b7f882896..3ea4fd15b715 100644 --- a/app/Http/Controllers/OpenAPI/TaxRateSchema.php +++ b/app/Http/Controllers/OpenAPI/TaxRateSchema.php @@ -3,9 +3,9 @@ * @OA\Schema( * schema="TaxRate", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"), - * @OA\Property(property="name", type="string", example="GST", description="______"), - * @OA\Property(property="rate", type="number", example="10", description="______"), - * @OA\Property(property="is_deleted", type="boolean", example=true, description="______"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="Thie hashed id of the tax"), + * @OA\Property(property="name", type="string", example="GST", description="The tax name"), + * @OA\Property(property="rate", type="number", example="10", description="The tax rate"), + * @OA\Property(property="is_deleted", type="boolean", example=true, description="Boolean flag determining if the tax has been deleted"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/UserSchema.php b/app/Http/Controllers/OpenAPI/UserSchema.php index fd0a571d8b3f..d9f749feeaa1 100644 --- a/app/Http/Controllers/OpenAPI/UserSchema.php +++ b/app/Http/Controllers/OpenAPI/UserSchema.php @@ -3,15 +3,15 @@ * @OA\Schema( * schema="User", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="__________"), - * @OA\Property(property="first_name", type="string", example="The users first name", description="________"), - * @OA\Property(property="last_name", type="string", example="The users last name", description="_________"), - * @OA\Property(property="email", type="string", example="", description="_________"), - * @OA\Property(property="phone", type="string", example="555-1233-23232", description="_________"), - * @OA\Property(property="signature", type="string", example="A users text signature", description="_________"), - * @OA\Property(property="avatar", type="string", example="https://url.to.your/avatar.png", description="_________"), - * @OA\Property(property="accepted_terms_version", type="string", example="1.0.1", description="_________"), - * @OA\Property(property="oauth_user_id", type="string", example="jkhasdf789as6f675sdf768sdfs", description="_________"), - * @OA\Property(property="oauth_provider_id", type="string", example="google", description="_________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the user"), + * @OA\Property(property="first_name", type="string", example="Brad", description="The first name of the user"), + * @OA\Property(property="last_name", type="string", example="Pitt", description="The last name of the user"), + * @OA\Property(property="email", type="string", example="brad@pitt.com", description="The users email address"), + * @OA\Property(property="phone", type="string", example="555-1233-23232", description="The users phone number"), + * @OA\Property(property="signature", type="string", example="Have a nice day!", description="The users sign off signature"), + * @OA\Property(property="avatar", type="string", example="https://url.to.your/avatar.png", description="The users avatar"), + * @OA\Property(property="accepted_terms_version", type="string", example="1.0.1", description="The version of the invoice ninja terms that has been accepted by the user"), + * @OA\Property(property="oauth_user_id", type="string", example="jkhasdf789as6f675sdf768sdfs", description="The provider id of the oauth entity"), + * @OA\Property(property="oauth_provider_id", type="string", example="google", description="The oauth entity id"), * ) */ diff --git a/app/Http/Controllers/OpenAPI/VendorContact.php b/app/Http/Controllers/OpenAPI/VendorContact.php index f078087f9ccc..01837ce31910 100644 --- a/app/Http/Controllers/OpenAPI/VendorContact.php +++ b/app/Http/Controllers/OpenAPI/VendorContact.php @@ -3,19 +3,19 @@ * @OA\Schema( * schema="VendorContact", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="vendor_id", type="string", example="", description="________"), - * @OA\Property(property="first_name", type="string", example="", description="________"), - * @OA\Property(property="last_name", type="string", example="", description="________"), - * @OA\Property(property="phone", type="string", example="", description="________"), - * @OA\Property(property="custom_value1", type="string", example="", description="________"), - * @OA\Property(property="custom_value2", type="string", example="", description="________"), - * @OA\Property(property="custom_value3", type="string", example="", description="________"), - * @OA\Property(property="custom_value4", type="string", example="", description="________"), - * @OA\Property(property="email", type="string", example="", description="________"), - * @OA\Property(property="is_primary", type="boolean", example=true, description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the vendor contact"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The hashed id of the user id"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The hashed id of the company"), + * @OA\Property(property="vendor_id", type="string", example="Opnel5aKBz", description="The hashed id of the vendor"), + * @OA\Property(property="first_name", type="string", example="Harry", description="The first name of the contact"), + * @OA\Property(property="last_name", type="string", example="Windsor", description="The last name of the contact"), + * @OA\Property(property="phone", type="string", example="555-123-1234", description="The contacts phone number"), + * @OA\Property(property="custom_value1", type="string", example="2022-10-10", description="A custom value"), + * @OA\Property(property="custom_value2", type="string", example="$1000", description="A custom value"), + * @OA\Property(property="custom_value3", type="string", example="", description="A custom value"), + * @OA\Property(property="custom_value4", type="string", example="", description="A custom value"), + * @OA\Property(property="email", type="string", example="harry@windsor.com", description="The contact email address"), + * @OA\Property(property="is_primary", type="boolean", example=true, description="Boolean flag determining if the contact is the primary contact for the vendor"), * @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="deleted_at", type="number", format="integer", example="134341234234", description="Timestamp"), diff --git a/app/Http/Controllers/OpenAPI/VendorSchema.php b/app/Http/Controllers/OpenAPI/VendorSchema.php index fc91d2728012..8cf71c4ca9ea 100644 --- a/app/Http/Controllers/OpenAPI/VendorSchema.php +++ b/app/Http/Controllers/OpenAPI/VendorSchema.php @@ -3,11 +3,11 @@ * @OA\Schema( * schema="Vendor", * type="object", - * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), - * @OA\Property(property="user_id", type="string", example="", description="__________"), - * @OA\Property(property="assigned_user_id", type="string", example="", description="__________"), - * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), + * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="The hashed id of the vendor"), + * @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="The hashed id of the user who created the vendor"), + * @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="The hashed id of the assigned user to this vendor"), + * @OA\Property(property="company_id", type="string", example="Opnel5aKBz", description="The hashed id of the company"), + * @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="The hashed id of the client"), * @OA\Property( * property="contacts", * type="array", @@ -16,10 +16,10 @@ * ref="#/components/schemas/VendorContact", * ), * ), - * @OA\Property(property="name", type="string", example="", description="________"), - * @OA\Property(property="website", type="string", example="", description="________"), - * @OA\Property(property="private_notes", type="string", example="", description="________"), - * @OA\Property(property="industry_id", type="string", example="", description="________"), + * @OA\Property(property="name", type="string", example="Harry's cafe de wheels", description="The vendor name"), + * @OA\Property(property="website", type="string", example="www.harry.com", description="The website of the vendor"), + * @OA\Property(property="private_notes", type="string", example="Shhh, don't tell the vendor", description="The private notes of the vendor"), + * @OA\Property(property="industry_id", type="string", example="1", description="The industry id of the vendor"), * @OA\Property(property="size_id", type="string", example="", description="________"), * @OA\Property(property="address1", type="string", example="", description="________"), * @OA\Property(property="address2", type="string", example="", description="________"), diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index 8d7b11a8a5ee..e84e74f3a1c8 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -52,6 +52,7 @@ class QueryLogging // nlog("Query count = {$count}"); // nlog($queries); + // nlog($request->url()); if ($count > 175) { nlog("Query count = {$count}"); diff --git a/app/Http/Middleware/VendorContactKeyLogin.php b/app/Http/Middleware/VendorContactKeyLogin.php index 334f6609c72f..0573a3389122 100644 --- a/app/Http/Middleware/VendorContactKeyLogin.php +++ b/app/Http/Middleware/VendorContactKeyLogin.php @@ -65,7 +65,7 @@ class VendorContactKeyLogin return redirect($this->setRedirectPath()); } } elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) { - if (MultiDB::findAndSetDbByContactKey($request->segment(3))) { + if (MultiDB::findAndSetDbByVendorContactKey($request->segment(3))) { if ($vendor_contact = VendorContact::where('contact_key', $request->segment(3))->first()) { if (empty($vendor_contact->email)) { $vendor_contact->email = Str::random(6).'@example.com'; diff --git a/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php new file mode 100644 index 000000000000..b3a7aab38d81 --- /dev/null +++ b/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/BankIntegration/CreateBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/CreateBankIntegrationRequest.php new file mode 100644 index 000000000000..627fd9745f69 --- /dev/null +++ b/app/Http/Requests/BankIntegration/CreateBankIntegrationRequest.php @@ -0,0 +1,28 @@ +user()->can('create', BankIntegration::class); + } +} diff --git a/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php new file mode 100644 index 000000000000..b2550cd7dabe --- /dev/null +++ b/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php new file mode 100644 index 000000000000..6c5b668e4eaa --- /dev/null +++ b/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php new file mode 100644 index 000000000000..9d08c25f1fa8 --- /dev/null +++ b/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('view', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php new file mode 100644 index 000000000000..67e209731b63 --- /dev/null +++ b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php @@ -0,0 +1,57 @@ +user()->can('create', BankIntegration::class); + } + + public function rules() + { + + $rules = [ + 'bank_account_name' => 'required|min:3' + ]; + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + if(!array_key_exists('provider_name', $input) || strlen($input['provider_name']) == 0) + $input['provider_name'] = $input['bank_account_name']; + + $this->replace($input); + } + + public function messages() + { + return []; + } + +} diff --git a/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php new file mode 100644 index 000000000000..5bb712be1164 --- /dev/null +++ b/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php @@ -0,0 +1,51 @@ +user()->can('edit', $this->bank_integration); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $rules = []; + + return $rules; + } + + public function messages() + { + return [ ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } + +} diff --git a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php new file mode 100644 index 000000000000..b9354432c455 --- /dev/null +++ b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php @@ -0,0 +1,38 @@ +user()->can('edit', $this->bank_integration); + } + + public function rules() + { + $rules = []; + + if ($this->input('documents')) { + $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + } + + return $rules; + } +} diff --git a/app/Http/Requests/BankTransaction/AdminBankTransactionRequest.php b/app/Http/Requests/BankTransaction/AdminBankTransactionRequest.php new file mode 100644 index 000000000000..4522a8f34f2f --- /dev/null +++ b/app/Http/Requests/BankTransaction/AdminBankTransactionRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/BankTransaction/CreateBankTransactionRequest.php b/app/Http/Requests/BankTransaction/CreateBankTransactionRequest.php new file mode 100644 index 000000000000..21844aaca774 --- /dev/null +++ b/app/Http/Requests/BankTransaction/CreateBankTransactionRequest.php @@ -0,0 +1,28 @@ +user()->can('create', BankTransaction::class); + } +} diff --git a/app/Http/Requests/BankTransaction/DestroyBankTransactionRequest.php b/app/Http/Requests/BankTransaction/DestroyBankTransactionRequest.php new file mode 100644 index 000000000000..35f33c7ec78d --- /dev/null +++ b/app/Http/Requests/BankTransaction/DestroyBankTransactionRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_transaction); + } +} diff --git a/app/Http/Requests/BankTransaction/EditBankTransactionRequest.php b/app/Http/Requests/BankTransaction/EditBankTransactionRequest.php new file mode 100644 index 000000000000..b2413ffabbcd --- /dev/null +++ b/app/Http/Requests/BankTransaction/EditBankTransactionRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_transaction); + } +} diff --git a/app/Http/Requests/BankTransaction/ImportBankTransactionsRequest.php b/app/Http/Requests/BankTransaction/ImportBankTransactionsRequest.php new file mode 100644 index 000000000000..3f2801ad4329 --- /dev/null +++ b/app/Http/Requests/BankTransaction/ImportBankTransactionsRequest.php @@ -0,0 +1,67 @@ +user()->isAdmin(); + } + + public function rules() + { + + $rules = [ + 'transactions' => 'bail|array', + 'transactions.*.id' => 'bail|required', + 'transactions.*.invoice_ids' => 'nullable|string|sometimes', + 'transactions.*.ninja_category_id' => 'nullable|string|sometimes' + ]; + + $rules['transactions.*.vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + return $rules; + + } + + public function prepareForValidation() + { + $inputs = $this->all(); + + foreach($inputs['transactions'] as $key => $input) + { + + if(array_key_exists('id', $inputs['transactions'][$key])) + $inputs['transactions'][$key]['id'] = $this->decodePrimaryKey($input['id']); + + if(array_key_exists('ninja_category_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['ninja_category_id']) >= 1) + $inputs['transactions'][$key]['ninja_category_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['ninja_category_id']); + + if(array_key_exists('vendor_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['vendor_id']) >= 1) + $inputs['transactions'][$key]['vendor_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['vendor_id']); + + // $input = $this->decodePrimaryKeys($input); + } + + $this->replace($inputs); + + } +} diff --git a/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php new file mode 100644 index 000000000000..9c5869fabce3 --- /dev/null +++ b/app/Http/Requests/BankTransaction/MatchBankTransactionRequest.php @@ -0,0 +1,67 @@ +user()->isAdmin(); + } + + public function rules() + { + + $rules = [ + 'transactions' => 'bail|array', + 'transactions.*.id' => 'bail|required', + 'transactions.*.invoice_ids' => 'nullable|string|sometimes', + 'transactions.*.ninja_category_id' => 'nullable|string|sometimes' + ]; + + $rules['transactions.*.vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + return $rules; + + } + + public function prepareForValidation() + { + $inputs = $this->all(); + + foreach($inputs['transactions'] as $key => $input) + { + + if(array_key_exists('id', $inputs['transactions'][$key])) + $inputs['transactions'][$key]['id'] = $this->decodePrimaryKey($input['id']); + + if(array_key_exists('ninja_category_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['ninja_category_id']) >= 1) + $inputs['transactions'][$key]['ninja_category_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['ninja_category_id']); + + if(array_key_exists('vendor_id', $inputs['transactions'][$key]) && strlen($inputs['transactions'][$key]['vendor_id']) >= 1) + $inputs['transactions'][$key]['vendor_id'] = $this->decodePrimaryKey($inputs['transactions'][$key]['vendor_id']); + + // $input = $this->decodePrimaryKeys($input); + } + + $this->replace($inputs); + + } +} diff --git a/app/Http/Requests/BankTransaction/ShowBankTransactionRequest.php b/app/Http/Requests/BankTransaction/ShowBankTransactionRequest.php new file mode 100644 index 000000000000..f41b9a28ab8e --- /dev/null +++ b/app/Http/Requests/BankTransaction/ShowBankTransactionRequest.php @@ -0,0 +1,27 @@ +user()->can('view', $this->bank_transaction); + } +} diff --git a/app/Http/Requests/BankTransaction/StoreBankTransactionRequest.php b/app/Http/Requests/BankTransaction/StoreBankTransactionRequest.php new file mode 100644 index 000000000000..b292453df8c5 --- /dev/null +++ b/app/Http/Requests/BankTransaction/StoreBankTransactionRequest.php @@ -0,0 +1,51 @@ +user()->can('create', BankTransaction::class); + } + + public function rules() + { + + $rules = []; + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1) + $input['bank_integration_id'] = $this->decodePrimaryKey($input['bank_integration_id']); + + $this->replace($input); + } + + +} diff --git a/app/Http/Requests/BankTransaction/UpdateBankTransactionRequest.php b/app/Http/Requests/BankTransaction/UpdateBankTransactionRequest.php new file mode 100644 index 000000000000..fb5927590d5c --- /dev/null +++ b/app/Http/Requests/BankTransaction/UpdateBankTransactionRequest.php @@ -0,0 +1,76 @@ +user()->can('edit', $this->bank_transaction); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $rules = [ + 'date' => 'bail|required|date', + 'description' => 'bail|sometimes|string', + 'amount' => 'numeric|required', + ]; + + if (isset($this->currency_id)) + $rules['currency_id'] = 'sometimes|exists:currencies,id'; + + if(isset($this->vendor_id)) + $rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + if(isset($this->expense_id)) + $rules['expense_id'] = 'bail|required|exists:expenses,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + + if(array_key_exists('baseType', $input) && strlen($input['baseType']) > 1) + $input['base_type'] = $input['baseType']; //== 'deposit' ? 'CREDIT' : 'DEBIT'; + + if(array_key_exists('vendor_id', $input) && strlen($input['vendor_id']) > 1) + $input['vendor_id'] = $this->decodePrimaryKey($input['vendor_id']); + + if(array_key_exists('expense_id', $input) && strlen($input['expense_id']) > 1) + $input['expense_id'] = $this->decodePrimaryKey($input['expense_id']); + + if(array_key_exists('ninja_category_id', $input) && strlen($input['ninja_category_id']) > 1) + $input['ninja_category_id'] = $this->decodePrimaryKey($input['ninja_category_id']); + + if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1) + $input['bank_integration_id'] = $this->decodePrimaryKey($input['bank_integration_id']); + + $this->replace($input); + } + +} diff --git a/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php new file mode 100644 index 000000000000..d0c45260ebbf --- /dev/null +++ b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php @@ -0,0 +1,38 @@ +user()->can('edit', $this->bank_transaction); + } + + public function rules() + { + $rules = []; + + if ($this->input('documents')) { + $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Report/ProfitLossRequest.php b/app/Http/Requests/Report/ProfitLossRequest.php index 2886b4eb1944..3b3e7802d010 100644 --- a/app/Http/Requests/Report/ProfitLossRequest.php +++ b/app/Http/Requests/Report/ProfitLossRequest.php @@ -31,7 +31,7 @@ class ProfitLossRequest extends Request 'start_date' => 'string|date', 'end_date' => 'string|date', 'is_income_billed' => 'required|bail|bool', - 'is_expense_billed' => 'required|bail|bool', + 'is_expense_billed' => 'bool', 'include_tax' => 'required|bail|bool', 'date_range' => 'sometimes|string', 'send_email' => 'bool', diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index b9fcf766e29d..141226755f78 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -109,6 +109,10 @@ class Request extends FormRequest $input['invoice_id'] = $this->decodePrimaryKey($input['invoice_id']); } + if (array_key_exists('expense_id', $input) && is_string($input['expense_id'])) { + $input['expense_id'] = $this->decodePrimaryKey($input['expense_id']); + } + if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { $input['design_id'] = $this->decodePrimaryKey($input['design_id']); } diff --git a/app/Http/Requests/Yodlee/YodleeAdminRequest.php b/app/Http/Requests/Yodlee/YodleeAdminRequest.php new file mode 100644 index 000000000000..8b361a0d8459 --- /dev/null +++ b/app/Http/Requests/Yodlee/YodleeAdminRequest.php @@ -0,0 +1,39 @@ +user()->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return []; + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Yodlee/YodleeAuthRequest.php b/app/Http/Requests/Yodlee/YodleeAuthRequest.php new file mode 100644 index 000000000000..bda7259e4e06 --- /dev/null +++ b/app/Http/Requests/Yodlee/YodleeAuthRequest.php @@ -0,0 +1,70 @@ +state) { + $this->token = $this->state; + } + + $data = Cache::get($this->token); + + return $data; + } + + public function getContact() + { + MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); + + return User::findOrFail($this->getTokenContent()['user_id']); + + } + + public function getCompany() + { + + MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); + + return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail(); + + } +} \ No newline at end of file diff --git a/app/Import/Definitions/BankTransactionMap.php b/app/Import/Definitions/BankTransactionMap.php new file mode 100644 index 000000000000..bf2c2927ddcb --- /dev/null +++ b/app/Import/Definitions/BankTransactionMap.php @@ -0,0 +1,47 @@ + 'bank.transaction_id', + 1 => 'bank.amount', + 2 => 'bank.currency', + 3 => 'bank.account_type', + 4 => 'bank.category_id', + 5 => 'bank.category_type', + 6 => 'bank.date', + 7 => 'bank.bank_account_id', + 8 => 'bank.description', + 9 => 'bank.base_type', + ]; + } + + public static function import_keys() + { + return [ + 0 => 'texts.transaction_id', + 1 => 'texts.amount', + 2 => 'texts.currency', + 3 => 'texts.account_type', + 4 => 'texts.category_id', + 5 => 'texts.category_type', + 6 => 'texts.date', + 7 => 'texts.bank_account_id', + 8 => 'texts.description', + 9 => 'texts.type', + ]; + } +} \ No newline at end of file diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 365ee46b37cc..348cb7baf835 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -187,7 +187,6 @@ class BaseImport try { $entity = $this->transformer->transform($record); - // $validator = $this->request_name::runFormRequest($entity); $validator = $this->runValidation($entity); if ($validator->fails()) { diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index fcca60949591..5dd23683c40f 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -11,6 +11,7 @@ namespace App\Import\Providers; +use App\Factory\BankTransactionFactory; use App\Factory\ClientFactory; use App\Factory\ExpenseFactory; use App\Factory\InvoiceFactory; @@ -18,6 +19,7 @@ use App\Factory\PaymentFactory; use App\Factory\ProductFactory; use App\Factory\QuoteFactory; use App\Factory\VendorFactory; +use App\Http\Requests\BankTransaction\StoreBankTransactionRequest; use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; @@ -35,6 +37,8 @@ use App\Import\Transformer\Csv\PaymentTransformer; use App\Import\Transformer\Csv\ProductTransformer; use App\Import\Transformer\Csv\QuoteTransformer; use App\Import\Transformer\Csv\VendorTransformer; +use App\Import\Transformers\Bank\BankTransformer; +use App\Repositories\BankTransactionRepository; use App\Repositories\ClientRepository; use App\Repositories\ExpenseRepository; use App\Repositories\InvoiceRepository; @@ -60,12 +64,50 @@ class Csv extends BaseImport implements ImportInterface 'vendor', 'expense', 'quote', + 'bank_transaction', ]) ) { $this->{$entity}(); } } + public function bank_transaction() + { + $entity_type = 'bank_transaction'; + + $data = $this->getCsvData($entity_type); + + if (is_array($data)) { + $data = $this->preTransformCsv($data, $entity_type); + + + if(array_key_exists('bank_integration_id', $this->request)){ + + foreach($data as $key => $value) + { + $data['bank_integration_id'][$key] = $this->request['bank_integration_id']; + } + + } + + } + + if (empty($data)) { + $this->entity_count['bank_transactions'] = 0; + + return; + } + + $this->request_name = StoreBankTransactionRequest::class; + $this->repository_name = BankTransactionRepository::class; + $this->factory_name = BankTransactionFactory::class; + + $this->transformer = new BankTransformer($this->company); + $bank_transaction_count = $this->ingest($data, $entity_type); + $this->entity_count['bank_transactions'] = $bank_transaction_count; + + } + public function client() { $entity_type = 'client'; diff --git a/app/Import/Transformers/Bank/BankTransformer.php b/app/Import/Transformers/Bank/BankTransformer.php new file mode 100644 index 000000000000..071ac7711074 --- /dev/null +++ b/app/Import/Transformers/Bank/BankTransformer.php @@ -0,0 +1,81 @@ + $this->bank_integration->id, + 'transaction_id' => $this->getNumber($transaction,'bank.transaction_id'), + 'amount' => abs($this->getFloat($transaction, 'bank.amount')), + 'currency_id' => $this->getCurrencyByCode($transaction, 'bank.currency'), + 'account_type' => strlen($this->getString($transaction, 'bank.account_type')) > 1 ? $this->getString($transaction, 'bank.account_type') : 'bank', + 'category_id' => $this->getNumber($transaction, 'bank.category_id') > 0 ? $this->getNumber($transaction, 'bank.category_id') : null, + 'category_type' => $this->getString($transaction, 'category_type'), + 'date' => array_key_exists('date', $transaction) ? date('Y-m-d', strtotime(str_replace("/","-",$transaction['date']))) + : now()->format('Y-m-d'), + 'bank_account_id' => array_key_exists('bank_account_id', $transaction) ? $transaction['bank_account_id'] : 0, + 'description' => array_key_exists('description', $transaction)? $transaction['description'] : '', + 'base_type' => $this->calculateType($transaction), + 'created_at' => $now, + 'updated_at' => $now, + 'company_id' => $this->company->id, + 'user_id' => $this->company->owner()->id, + ]; + + return $transformed; + } + + + private function calculateType($transaction) + { + + if(array_key_exists('base_type', $transaction) && $transaction['base_type'] == 'CREDIT') + return 'CREDIT'; + + if(array_key_exists('base_type', $transaction) && $transaction['base_type'] == 'DEBIT') + return 'DEBIT'; + + if(array_key_exists('category_id',$transaction)) + return 'DEBIT'; + + if(array_key_exists('category_type', $transaction) && $transaction['category_type'] == 'Income') + return 'CREDIT'; + + if(array_key_exists('category_type', $transaction)) + return 'DEBIT'; + + if(array_key_exists('amount', $transaction) && is_numeric($transaction['amount']) && $transaction['amount'] > 0) + return 'CREDIT'; + + return 'DEBIT'; + + } + +} \ No newline at end of file diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php new file mode 100644 index 000000000000..666c6ac43cdc --- /dev/null +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -0,0 +1,365 @@ +company_id = $company_id; + $this->db = $db; + $this->input = $input['transactions']; + $this->categories = collect(); + $this->bts = collect(); + + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + + MultiDB::setDb($this->db); + + $this->company = Company::find($this->company_id); + + $yodlee = new Yodlee($this->company->account->bank_integration_account_id); + + $bank_categories = Cache::get('bank_categories'); + + if(!$bank_categories){ + $_categories = $yodlee->getTransactionCategories(); + $this->categories = collect($_categories->transactionCategory); + Cache::forever('bank_categories', $this->categories); + } + else { + $this->categories = collect($bank_categories); + } + + foreach($this->input as $input) + { + if(array_key_exists('invoice_ids', $input) && strlen($input['invoice_ids']) > 1) + $this->matchInvoicePayment($input); + else + $this->matchExpense($input); + } + + return BankTransaction::whereIn('id', $this->bts); + + } + + private function getInvoices(string $invoice_hashed_ids) + { + $collection = collect(); + + $invoices = explode(",", $invoice_hashed_ids); + + if(count($invoices) >= 1) + { + + foreach($invoices as $invoice){ + + if(is_string($invoice) && strlen($invoice) > 1) + $collection->push($this->decodePrimaryKey($invoice)); + } + + } + + return $collection; + } + + private function checkPayable($invoices) :bool + { + + foreach($invoices as $invoice){ + + $invoice->service()->markSent(); + + if(!$invoice->isPayable()) + return false; + + } + + return true; + + } + + private function matchInvoicePayment($input) :self + { + $this->bt = BankTransaction::find($input['id']); + + $_invoices = Invoice::withTrashed()->find($this->getInvoices($input['invoice_ids'])); + + $amount = $this->bt->amount; + + if($_invoices && $this->checkPayable($_invoices)){ + + $this->createPayment($_invoices, $amount); + + } + + $this->bts->push($this->bt->id); + + return $this; + } + + private function matchExpense($input) :self + { + //if there is a category id, pull it from Yodlee and insert - or just reuse!! + $this->bt = BankTransaction::find($input['id']); + + $expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id); + $expense->category_id = $this->resolveCategory($input); + $expense->amount = $this->bt->amount; + $expense->number = $this->getNextExpenseNumber($expense); + $expense->currency_id = $this->bt->currency_id; + $expense->date = Carbon::parse($this->bt->date); + $expense->payment_date = Carbon::parse($this->bt->date); + $expense->transaction_reference = $this->bt->description; + $expense->transaction_id = $this->bt->id; + $expense->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null; + $expense->save(); + + $this->bt->expense_id = $expense->id; + $this->bt->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null; + $this->bt->status_id = BankTransaction::STATUS_CONVERTED; + $this->bt->save(); + + $this->bts->push($this->bt->id); + + return $this; + } + + private function createPayment($invoices, float $amount) :void + { + $this->available_balance = $amount; + + \DB::connection(config('database.default'))->transaction(function () use($invoices) { + + $invoices->each(function ($invoice) use ($invoices){ + + $this->invoice = Invoice::withTrashed()->where('id', $invoice->id)->lockForUpdate()->first(); + + if($invoices->count() == 1){ + $_amount = $this->available_balance; + } + elseif($invoices->count() > 1 && floatval($this->invoice->balance) < floatval($this->available_balance) && $this->available_balance > 0) + { + $_amount = $this->invoice->balance; + $this->available_balance = $this->available_balance - $this->invoice->balance; + } + elseif($invoices->count() > 1 && floatval($this->invoice->balance) > floatval($this->available_balance) && $this->available_balance > 0) + { + $_amount = $this->available_balance; + $this->available_balance = 0; + } + + $this->attachable_invoices[] = ['id' => $this->invoice->id, 'amount' => $_amount]; + + $this->invoice + ->service() + ->setExchangeRate() + ->updateBalance($_amount * -1) + ->updatePaidToDate($_amount) + ->setCalculatedStatus() + ->save(); + + }); + + }, 1); + + /* Create Payment */ + $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); + + $payment->amount = $amount; + $payment->applied = $amount; + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->client_id = $this->invoice->client_id; + $payment->transaction_reference = $this->bt->description; + $payment->transaction_id = $this->bt->id; + $payment->currency_id = $this->bt->currency_id; + $payment->is_manual = false; + $payment->date = $this->bt->date ? Carbon::parse($this->bt->date) : now(); + + + /* Bank Transfer! */ + $payment_type_id = 1; + + $payment->saveQuietly(); + + $payment->service()->applyNumber()->save(); + + if($payment->client->getSetting('send_email_on_mark_paid')) + $payment->service()->sendEmail(); + + $this->setExchangeRate($payment); + + /* Create a payment relationship to the invoice entity */ + foreach($this->attachable_invoices as $attachable_invoice) + { + + $payment->invoices()->attach($attachable_invoice['id'], [ + 'amount' => $attachable_invoice['amount'], + ]); + + } + + event('eloquent.created: App\Models\Payment', $payment); + + $this->invoice->next_send_date = null; + + $this->invoice + ->service() + ->applyNumber() + ->touchPdf() + ->save(); + + $payment->ledger() + ->updatePaymentBalance($amount * -1); + + $this->invoice + ->client + ->service() + ->updateBalanceAndPaidToDate($amount*-1, $amount) + ->save(); + + $this->invoice = $this->invoice + ->service() + ->workFlow() + ->save(); + + /* Update Invoice balance */ + event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + + $this->bt->invoice_ids = collect($invoices)->pluck('hashed_id')->implode(','); + $this->bt->status_id = BankTransaction::STATUS_CONVERTED; + $this->bt->save(); + } + + private function resolveCategory($input) :?int + { + if(array_key_exists('ninja_category_id', $input) && (int)$input['ninja_category_id'] > 1){ + $this->bt->ninja_category_id = $input['ninja_category_id']; + $this->bt->save(); + + return (int)$input['ninja_category_id']; + } + + $category = $this->categories->firstWhere('highLevelCategoryId', $this->bt->category_id); + + $ec = ExpenseCategory::where('company_id', $this->bt->company_id)->where('bank_category_id', $this->bt->category_id)->first(); + + if($ec) + return $ec->id; + + if($category) + { + $ec = ExpenseCategoryFactory::create($this->bt->company_id, $this->bt->user_id); + $ec->bank_category_id = $this->bt->category_id; + $ec->name = $category->highLevelCategoryName; + $ec->save(); + + return $ec->id; + } + + + return null; + } + + private function setExchangeRate(Payment $payment) + { + if ($payment->exchange_rate != 1) { + return; + } + + $client_currency = $payment->client->getSetting('currency_id'); + $company_currency = $payment->client->company->settings->currency_id; + + if ($company_currency != $client_currency) { + $exchange_rate = new CurrencyApi(); + + $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); + $payment->exchange_currency_id = $company_currency; + + $payment->saveQuietly(); + } + } + + public function middleware() + { + return [new WithoutOverlapping($this->company_id)]; + } + + + + + +} \ No newline at end of file diff --git a/app/Jobs/Bank/ProcessBankTransactions.php b/app/Jobs/Bank/ProcessBankTransactions.php new file mode 100644 index 000000000000..7e7c85083e9e --- /dev/null +++ b/app/Jobs/Bank/ProcessBankTransactions.php @@ -0,0 +1,155 @@ +bank_integration_account_id = $bank_integration_account_id; + $this->bank_integration = $bank_integration; + $this->from_date = $bank_integration->from_date; + $this->company = $this->bank_integration->company; + + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + + set_time_limit(0); + + //Loop through everything until we are up to date + $this->from_date = $this->from_date ?: '2021-01-01'; + + do{ + + $this->processTransactions(); + + } + while($this->stop_loop); + + BankService::dispatch($this->company->id, $this->company->db); + + } + + + private function processTransactions() + { + + $yodlee = new Yodlee($this->bank_integration_account_id); + + $data = [ + 'top' => 500, + 'fromDate' => $this->from_date, + 'accountId' => $this->bank_integration->bank_account_id, + 'skip' => $this->skip, + ]; + + //Get transaction count object + $transaction_count = $yodlee->getTransactionCount($data); + + //Get int count + $count = $transaction_count->transaction->TOTAL->count; + + //get transactions array + $transactions = $yodlee->getTransactions($data); + + //if no transactions, update the from_date and move on + if(count($transactions) == 0){ + + $this->bank_integration->from_date = now(); + $this->bank_integration->save(); + $this->stop_loop = false; + return; + } + + //Harvest the company + + MultiDB::setDb($this->company->db); + + /*Get the user */ + $user_id = $this->company->owner()->id; + + /* Unguard the model to perform batch inserts */ + BankTransaction::unguard(); + + $now = now(); + + foreach($transactions as $transaction) + { + + if(BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) + continue; + + //this should be much faster to insert than using ::create() + $bt = \DB::table('bank_transactions')->insert( + array_merge($transaction,[ + 'company_id' => $this->company->id, + 'user_id' => $user_id, + 'bank_integration_id' => $this->bank_integration->id, + 'created_at' => $now, + 'updated_at' => $now, + ]) + ); + + } + + + $this->skip = $this->skip + 500; + + if($count < 500){ + $this->stop_loop = false; + + $this->bank_integration->from_date = now(); + $this->bank_integration->save(); + + } + + } + +} \ No newline at end of file diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index fbcfe2677605..a8ec2da2d0bc 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -77,6 +77,7 @@ class CompanyExport implements ShouldQueue set_time_limit(0); $this->export_data['app_version'] = config('ninja.app_version'); + $this->export_data['storage_url'] = Storage::url(''); $this->export_data['activities'] = $this->company->all_activities->map(function ($activity){ @@ -167,7 +168,8 @@ class CompanyExport implements ShouldQueue $this->export_data['company'] = $this->company->toArray(); - + $this->export_data['company']['company_key'] = $this->createHash(); + $this->export_data['company_gateways'] = $this->company->company_gateways()->withTrashed()->cursor()->map(function ($company_gateway){ $company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']); diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index f62aa6b7268a..8cac0d230176 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -1075,7 +1075,6 @@ class CompanyImport implements ShouldQueue private function import_documents() { - // foreach($this->backup_file->documents as $document) foreach((object)$this->getObject("documents") as $document) { @@ -1105,6 +1104,29 @@ class CompanyImport implements ShouldQueue $new_document->save(['timestamps' => false]); + + $storage_url = (object)$this->getObject('storage_url', true); + + if(!Storage::exists($new_document->url)){ + + $url = $storage_url . $new_document->url; + + $file = @file_get_contents($url); + + if($file) + { + try{ + Storage::disk(config('filesystems.default'))->put($new_document->url, $file); + } + catch(\Exception $e) + { + nlog($e->getMessage()); + nlog("I could not upload {$new_document->url}"); + } + } + + } + } return $this; diff --git a/app/Jobs/Document/ZipDocuments.php b/app/Jobs/Document/ZipDocuments.php index aa05eb1f7d17..7cb8a3e2f3ed 100644 --- a/app/Jobs/Document/ZipDocuments.php +++ b/app/Jobs/Document/ZipDocuments.php @@ -126,6 +126,12 @@ class ZipDocuments implements ShouldQueue $number = '_'.$document->documentable->number; } - return "{$date}_{$document->documentable->translate_entity()}{$number}_{$filename}"; + $entity = ctrans('texts.document'); + + if(isset($document->documentable)){ + $entity = $document->documentable->translate_entity(); + } + + return "{$date}_{$entity}{$number}_{$filename}"; } } diff --git a/app/Jobs/Import/CSVIngest.php b/app/Jobs/Import/CSVIngest.php index 8a61d6ca30d8..d19691825cf0 100644 --- a/app/Jobs/Import/CSVIngest.php +++ b/app/Jobs/Import/CSVIngest.php @@ -74,7 +74,7 @@ class CSVIngest implements ShouldQueue $engine = $this->bootEngine(); - foreach (['client', 'product', 'invoice', 'payment', 'vendor', 'expense', 'quote'] as $entity) { + foreach (['client', 'product', 'invoice', 'payment', 'vendor', 'expense', 'quote', 'bank_transaction'] as $entity) { $engine->import($entity); } diff --git a/app/Jobs/Mail/NinjaMailer.php b/app/Jobs/Mail/NinjaMailer.php index 726327dba855..0a0edbd21a2b 100644 --- a/app/Jobs/Mail/NinjaMailer.php +++ b/app/Jobs/Mail/NinjaMailer.php @@ -42,10 +42,7 @@ class NinjaMailer extends Mailable $ninja_mailable = $this->from(config('mail.from.address'), $from_name) ->subject($this->mail_obj->subject) - ->view($this->mail_obj->markdown, $this->mail_obj->data) - ->withSymfonyMessage(function ($message) { - $message->getHeaders()->addTextHeader('Tag', $this->mail_obj->tag); - }); + ->view($this->mail_obj->markdown, $this->mail_obj->data); if (property_exists($this->mail_obj, 'text_view')) { $ninja_mailable->text($this->mail_obj->text_view, $this->mail_obj->data); diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index a04e9dbcd950..1b6d50cd902c 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -128,12 +128,19 @@ class NinjaMailerJob implements ShouldQueue /* Count the amount of emails sent across all the users accounts */ Cache::increment($this->company->account->key); - } catch (\Exception | \RuntimeException $e) { + } catch (\Exception | \RuntimeException | \Google\Service\Exception $e) { nlog("error failed with {$e->getMessage()}"); $message = $e->getMessage(); + if($e instanceof \Google\Service\Exception){ + + if(($e->getCode() == 429) && ($this->nmo->to_user instanceof ClientContact)) + $this->logMailError("Google rate limiter hit, we will retry in 30 seconds.", $this->nmo->to_user->client); + + } + /** * Post mark buries the proper message in a a guzzle response * this merges a text string with a json object diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php new file mode 100644 index 000000000000..d657f4c66843 --- /dev/null +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -0,0 +1,81 @@ +syncTransactions(); + } + } + + public function syncTransactions() + { + $a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){ + + // $queue = Ninja::isHosted() ? 'bank' : 'default'; + + // if($account->isPaid()) + // { + + $account->bank_integrations->each(function ($bank_integration) use ($account){ + + ProcessBankTransactions::dispatchSync($account->bank_integration_account_id, $bank_integration); + + }); + + // } + + }); + } + +} diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 6df012026b58..97a038a8ffc5 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -100,7 +100,7 @@ class ReminderJob implements ShouldQueue $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); - nlog("Firing reminder email for invoice {$invoice->number}"); + nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); }); if ($invoice->invitations->count() > 0) { diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index dabc5fc647d2..30fbddd44b2b 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -18,6 +18,7 @@ use App\Models\Company; use App\Models\CompanyToken; use App\Models\Document; use App\Models\User; +use App\Models\VendorContact; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; @@ -364,6 +365,23 @@ class MultiDB return false; } + public static function findAndSetDbByVendorContactKey($contact_key) :bool + { + $current_db = config('database.default'); + + foreach (self::$dbs as $db) { + if (VendorContact::on($db)->where('contact_key', $contact_key)->exists()) { + self::setDb($db); + + return true; + } + } + + self::setDB($current_db); + + return false; + } + public static function findAndSetDbByClientHash($client_hash) :bool { $current_db = config('database.default'); diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index b62f402ec0dc..000d2f72748f 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -100,7 +100,6 @@ class InvoiceEmailEngine extends BaseEmailEngine $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); } else { $subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->client->locale()); - // $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); } if (iconv_strlen($subject_template) == 0) { diff --git a/app/Models/Account.php b/app/Models/Account.php index 6bed655af7fe..afe84061211e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -140,6 +140,11 @@ class Account extends BaseModel return $this->hasMany(Company::class); } + public function bank_integrations() + { + return $this->hasMany(BankIntegration::class); + } + public function company_users() { return $this->hasMany(CompanyUser::class); @@ -359,9 +364,9 @@ class Account extends BaseModel 'plan_price' => $price, 'trial' => false, 'plan' => $plan, - 'started' => DateTime::createFromFormat('Y-m-d', $this->plan_started), + 'started' => $this->plan_started ? DateTime::createFromFormat('Y-m-d', $this->plan_started) : false, 'expires' => $plan_expires, - 'paid' => DateTime::createFromFormat('Y-m-d', $this->plan_paid), + 'paid' => $this->plan_paid ? DateTime::createFromFormat('Y-m-d', $this->plan_paid) : false, 'term' => $this->plan_term, 'active' => $plan_active, ]; diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php new file mode 100644 index 000000000000..dce8b2b36708 --- /dev/null +++ b/app/Models/BankIntegration.php @@ -0,0 +1,60 @@ +belongsTo(Company::class); + } + + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + + public function account() + { + return $this->belongsTo(Account::class); + } + + public function transactions() + { + return $this->hasMany(BankTransaction::class)->withTrashed(); + } + +} \ No newline at end of file diff --git a/app/Models/BankTransaction.php b/app/Models/BankTransaction.php new file mode 100644 index 000000000000..c603fae0b089 --- /dev/null +++ b/app/Models/BankTransaction.php @@ -0,0 +1,98 @@ +invoice_ids); + + if(count($invoices) >= 1) + { + + foreach($invoices as $invoice){ + + if(is_string($invoice) && strlen($invoice) > 1) + $collection->push($this->decodePrimaryKey($invoice)); + } + + } + + return $collection; + } + + public function getEntityType() + { + return self::class; + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function vendor() + { + return $this->belongsTo(Vendor::class); + } + + public function expense() + { + return $this->belongsTo(Expense::class); + } + + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + + public function bank_integration() + { + return $this->belongsTo(BankIntegration::class)->withTrashed(); + } + + public function account() + { + return $this->belongsTo(Account::class)->withTrashed(); + } + +} \ No newline at end of file diff --git a/app/Models/Company.php b/app/Models/Company.php index 3581c42f510f..d878ee575b47 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -12,6 +12,7 @@ namespace App\Models; use App\DataMapper\CompanySettings; +use App\Models\BankTransaction; use App\Models\Language; use App\Models\Presenters\CompanyPresenter; use App\Models\PurchaseOrder; @@ -177,6 +178,16 @@ class Company extends BaseModel return $this->hasMany(CompanyLedger::class); } + public function bank_integrations() + { + return $this->hasMany(BankIntegration::class); + } + + public function bank_transactions() + { + return $this->hasMany(BankTransaction::class); + } + public function getCompanyIdAttribute() { return $this->encodePrimaryKey($this->id); diff --git a/app/PaymentDrivers/Braintree/CreditCard.php b/app/PaymentDrivers/Braintree/CreditCard.php index 001fc5f33f95..daf14090a37f 100644 --- a/app/PaymentDrivers/Braintree/CreditCard.php +++ b/app/PaymentDrivers/Braintree/CreditCard.php @@ -126,6 +126,12 @@ class CreditCard ], ]; + // uses the same auth id twice when this is enabled. + + // if($state['server_response']?->threeDSecureInfo){ + // $data['threeDSecureAuthenticationId'] = $state['server_response']?->threeDSecureInfo?->threeDSecureAuthenticationId; + // } + if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) { /** https://developer.paypal.com/braintree/docs/reference/request/transaction/sale/php#full-example */ $data['merchantAccountId'] = $this->braintree->company_gateway->getConfigField('merchantAccountId'); @@ -224,7 +230,8 @@ class CreditCard */ private function processUnsuccessfulPayment($response) { - $this->braintree->sendFailureMail($response->transaction->additionalProcessorResponse); + + $this->braintree->sendFailureMail($response?->transaction?->additionalProcessorResponse); $message = [ 'server_response' => $response, @@ -240,7 +247,7 @@ class CreditCard $this->braintree->client->company, ); - throw new PaymentFailed($response->transaction->additionalProcessorResponse, $response->transaction->processorResponseCode); + throw new PaymentFailed($response?->transaction?->additionalProcessorResponse ?: 'Unhandled error, please contact merchant', $response?->transaction?->processorResponseCode ?: 500); } private function storePaymentMethod($method, $customer_reference) diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 6e298ff1d276..ffe18c0165b5 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -93,6 +93,9 @@ class CreditCard implements MethodInterface $gateway_response = \json_decode($request->gateway_response); $customerRequest = $this->checkout->getCustomer(); + + nlog($customerRequest); + $request = $this->bootRequest($gateway_response->token); $request->capture = false; $request->reference = '$1 payment for authorization.'; diff --git a/app/Policies/BankIntegrationPolicy.php b/app/Policies/BankIntegrationPolicy.php new file mode 100644 index 000000000000..c2dfb135c07d --- /dev/null +++ b/app/Policies/BankIntegrationPolicy.php @@ -0,0 +1,31 @@ +isAdmin(); + } +} diff --git a/app/Policies/BankTransactionPolicy.php b/app/Policies/BankTransactionPolicy.php new file mode 100644 index 000000000000..00b57861aa6d --- /dev/null +++ b/app/Policies/BankTransactionPolicy.php @@ -0,0 +1,31 @@ +isAdmin(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 670d21b75d7e..9b9f779eeb9d 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,6 +12,9 @@ namespace App\Providers; use App\Models\Activity; +use App\Models\Bank; +use App\Models\BankIntegration; +use App\Models\BankTransaction; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; @@ -40,6 +43,8 @@ use App\Models\User; use App\Models\Vendor; use App\Models\Webhook; use App\Policies\ActivityPolicy; +use App\Policies\BankIntegrationPolicy; +use App\Policies\BankTransactionPolicy; use App\Policies\ClientPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; @@ -79,7 +84,8 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Activity::class => ActivityPolicy::class, - Subscription::class => SubscriptionPolicy::class, + BankIntegration::class => BankIntegrationPolicy::class, + BankTransaction::class => BankTransactionPolicy::class, Client::class => ClientPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, @@ -95,17 +101,18 @@ class AuthServiceProvider extends ServiceProvider PaymentTerm::class => PaymentTermPolicy::class, Product::class => ProductPolicy::class, Project::class => ProjectPolicy::class, + PurchaseOrder::class => PurchaseOrderPolicy::class, Quote::class => QuotePolicy::class, RecurringExpense::class => RecurringExpensePolicy::class, RecurringInvoice::class => RecurringInvoicePolicy::class, RecurringQuote::class => RecurringQuotePolicy::class, - Webhook::class => WebhookPolicy::class, + Subscription::class => SubscriptionPolicy::class, Task::class => TaskPolicy::class, TaskStatus::class => TaskStatusPolicy::class, TaxRate::class => TaxRatePolicy::class, User::class => UserPolicy::class, Vendor::class => VendorPolicy::class, - PurchaseOrder::class => PurchaseOrderPolicy::class, + Webhook::class => WebhookPolicy::class, ]; /** diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 59deeb53f077..623fd33f29a7 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -84,7 +84,6 @@ class ActivityRepository extends BaseRepository $entity->load('client'); $contact = $entity->client->primary_contact()->first(); $backup->html_backup = ''; - // $backup->html_backup = $this->generateHtml($entity); $backup->amount = $entity->amount; $backup->activity_id = $activity->id; $backup->json_backup = ''; diff --git a/app/Repositories/BankIntegrationRepository.php b/app/Repositories/BankIntegrationRepository.php new file mode 100644 index 000000000000..aca7ac150b30 --- /dev/null +++ b/app/Repositories/BankIntegrationRepository.php @@ -0,0 +1,34 @@ +fill($data); + $bank_integration->save(); + + return $bank_integration->fresh(); + } + +} diff --git a/app/Repositories/BankTransactionRepository.php b/app/Repositories/BankTransactionRepository.php new file mode 100644 index 000000000000..0d48177101a0 --- /dev/null +++ b/app/Repositories/BankTransactionRepository.php @@ -0,0 +1,37 @@ +bank_integration_id) && array_key_exists('bank_integration_id', $data)) + $bank_transaction->bank_integration_id = $data['bank_integration_id']; + + $bank_transaction->fill($data); + + $bank_transaction->save(); + + return $bank_transaction; + } + +} diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 59fcfaf0c6f2..1595b422eda5 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -118,7 +118,7 @@ class BaseRepository $invitation_class = sprintf('App\\Models\\%sInvitation', $resource); - $invitation = $invitation_class::where('key', $invitation['key'])->first(); + $invitation = $invitation_class::with('company')->where('key', $invitation['key'])->first(); return $invitation; } @@ -152,7 +152,7 @@ class BaseRepository if(array_key_exists('client_id', $data)) $model->client_id = $data['client_id']; - $client = Client::where('id', $model->client_id)->withTrashed()->firstOrFail(); + $client = Client::with('group_settings')->where('id', $model->client_id)->withTrashed()->firstOrFail(); $state = []; diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php new file mode 100644 index 000000000000..ad15ddd21352 --- /dev/null +++ b/app/Services/Bank/BankService.php @@ -0,0 +1,84 @@ +company_id = $company_id; + $this->db = $db; + } + + public function handle() + { + + MultiDB::setDb($this->db); + + $this->company = Company::find($this->company_id); + + $this->invoices = Invoice::where('company_id', $this->company->id) + ->whereIn('status_id', [1,2,3]) + ->where('is_deleted', 0) + ->get(); + + $this->match(); + } + + private function match() + { + + BankTransaction::where('company_id', $this->company->id) + ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->cursor() + ->each(function ($bt){ + + $invoice = $this->invoices->first(function ($value, $key) use ($bt){ + + return str_contains($bt->description, $value->number); + + }); + + if($invoice) + { + $bt->invoice_ids = $invoice->hashed_id; + $bt->status_id = BankTransaction::STATUS_MATCHED; + $bt->save(); + } + + }); + } + + +} diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 19586b19d779..6ee15254ccba 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -317,7 +317,7 @@ class InvoiceService } elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) { $this->invoice->status_id = Invoice::STATUS_PARTIAL; } - elseif ($this->invoice->balance > 0) { + elseif ($this->invoice->balance < 0) { $this->invoice->status_id = Invoice::STATUS_SENT; } diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index 24a4409b5e32..42e43f47d4e0 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -52,7 +52,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->settings->schedule_reminder1 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -62,7 +62,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->invoice->due_date && $this->settings->schedule_reminder1 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -72,7 +72,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->invoice->due_date && $this->settings->schedule_reminder1 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -81,7 +81,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->settings->schedule_reminder2 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -91,7 +91,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->invoice->due_date && $this->settings->schedule_reminder2 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -101,7 +101,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->invoice->due_date && $this->settings->schedule_reminder2 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -110,7 +110,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->settings->schedule_reminder3 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -120,7 +120,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->invoice->due_date && $this->settings->schedule_reminder3 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -130,7 +130,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->invoice->due_date && $this->settings->schedule_reminder3 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -151,7 +151,7 @@ class UpdateReminder extends AbstractService } if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) { - $this->invoice->next_send_date = $date_collection->sort()->first(); + $this->invoice->next_send_date = $date_collection->sort()->first()->addSeconds($offset); } else { $this->invoice->next_send_date = null; } diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 2b4b36df758f..17391f96bc81 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -277,6 +277,9 @@ class RefundPayment $invoice->service()->setStatus(Invoice::STATUS_PARTIAL); } + //26-10-2022 - disable autobill to prevent future billings; + $invoice->auto_bill_enabled = false; + $invoice->saveQuietly(); //06-09-2022 @@ -285,10 +288,6 @@ class RefundPayment ->updateBalance($refunded_invoice['amount']) ->save(); - // $client = $invoice->client; - // $client->balance += $refunded_invoice['amount']; - // $client->save(); - $transaction = [ 'invoice' => $invoice->transaction_event(), 'payment' => [], diff --git a/app/Transformers/BankIntegrationTransformer.php b/app/Transformers/BankIntegrationTransformer.php new file mode 100644 index 000000000000..131259f498a6 --- /dev/null +++ b/app/Transformers/BankIntegrationTransformer.php @@ -0,0 +1,90 @@ + (string) $this->encodePrimaryKey($bank_integration->id), + 'provider_name' => (string)$bank_integration->provider_name ?: '', + 'provider_id' => (int) $bank_integration->provider_id ?: 0, + 'bank_account_id' => (int) $bank_integration->bank_account_id ?: 0, + 'bank_account_name' => (string) $bank_integration->bank_account_name ?: '', + 'bank_account_number' => (string) $bank_integration->bank_account_number ?: '', + 'bank_account_status' => (string)$bank_integration->bank_account_status ?: '', + 'bank_account_type' => (string)$bank_integration->bank_account_type ?: '', + 'balance' => (float)$bank_integration->balance ?: 0, + 'currency' => (string)$bank_integration->currency ?: '', + 'nickname' => (string)$bank_integration->nickname ?: '', + 'from_date' => (string)$bank_integration->from_date ?: '', + 'is_deleted' => (bool) $bank_integration->is_deleted, + 'created_at' => (int) $bank_integration->created_at, + 'updated_at' => (int) $bank_integration->updated_at, + 'archived_at' => (int) $bank_integration->deleted_at, + ]; + } + + public function includeAccount(BankIntegration $bank_integration) + { + $transformer = new AccountTransformer($this->serializer); + + return $this->includeItem($bank_integration->account, $transformer, Account::class); + } + + public function includeCompany(BankIntegration $bank_integration) + { + $transformer = new CompanyTransformer($this->serializer); + + return $this->includeItem($bank_integration->company, $transformer, Company::class); + } + + public function includeBankTransactions(BankIntegration $bank_integration) + { + $transformer = new BankTransactionTransformer($this->serializer); + + return $this->includeCollection($bank_integration->transactions, $transformer, BankTransaction::class); + } + +} diff --git a/app/Transformers/BankTransactionTransformer.php b/app/Transformers/BankTransactionTransformer.php new file mode 100644 index 000000000000..ccd0de45687a --- /dev/null +++ b/app/Transformers/BankTransactionTransformer.php @@ -0,0 +1,105 @@ + (string) $this->encodePrimaryKey($bank_transaction->id), + 'bank_integration_id' => (string) $this->encodePrimaryKey($bank_transaction->bank_integration_id), + 'transaction_id' => (int) $bank_transaction->transaction_id, + 'amount' => (float) $bank_transaction->amount ?: 0, + 'currency_id' => (string) $bank_transaction->currency_id ?: '1', + 'account_type' => (string) $bank_transaction->account_type ?: '', + 'category_id' => (int) $bank_transaction->category_id, + 'ninja_category_id' => (string) $this->encodePrimaryKey($bank_transaction->ninja_category_id) ?: '', + 'category_type' => (string) $bank_transaction->category_type ?: '', + 'date' => (string) $bank_transaction->date ?: '', + 'bank_account_id' => (int) $bank_transaction->bank_account_id, + 'status_id' => (string) $bank_transaction->status_id, + 'description' => (string) $bank_transaction->description ?: '', + 'base_type' => (string) $bank_transaction->base_type ?: '', + 'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '', + 'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '', + 'vendor_id'=> (string) $this->encodePrimaryKey($bank_transaction->vendor_id) ?: '', + 'is_deleted' => (bool) $bank_transaction->is_deleted, + 'created_at' => (int) $bank_transaction->created_at, + 'updated_at' => (int) $bank_transaction->updated_at, + 'archived_at' => (int) $bank_transaction->deleted_at, + ]; + } + + public function includeAccount(BankTransaction $bank_transaction) + { + $transformer = new AccountTransformer($this->serializer); + + return $this->includeItem($bank_transaction->account, $transformer, Account::class); + } + + public function includeCompany(BankTransaction $bank_transaction) + { + $transformer = new CompanyTransformer($this->serializer); + + return $this->includeItem($bank_transaction->company, $transformer, Company::class); + } + + public function includeExpense(BankTransaction $bank_transaction) + { + $transformer = new ExpenseTransformer($this->serializer); + + return $this->includeItem($bank_transaction->expense, $transformer, Expense::class); + } + + public function includeVendor(BankTransaction $bank_transaction) + { + $transformer = new VendorTransformer($this->serializer); + + return $this->includeItem($bank_transaction->vendor, $transformer, Vendor::class); + } + +} diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 459625082a54..45ef466f87d2 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -13,6 +13,8 @@ namespace App\Transformers; use App\Models\Account; use App\Models\Activity; +use App\Models\BankIntegration; +use App\Models\BankTransaction; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; @@ -40,6 +42,8 @@ use App\Models\TaskStatus; use App\Models\TaxRate; use App\Models\User; use App\Models\Webhook; +use App\Transformers\BankIntegrationTransformer; +use App\Transformers\BankTransactionTransformer; use App\Transformers\PurchaseOrderTransformer; use App\Transformers\RecurringExpenseTransformer; use App\Utils\Traits\MakesHash; @@ -98,6 +102,8 @@ class CompanyTransformer extends EntityTransformer 'subscriptions', 'recurring_expenses', 'purchase_orders', + 'bank_integrations', + 'bank_transactions', ]; /** @@ -218,6 +224,20 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->tokens, $transformer, CompanyToken::class); } + public function includeBankTransactions(Company $company) + { + $transformer = new BankTransactionTransformer($this->serializer); + + return $this->includeCollection($company->bank_transactions, $transformer, BankTransaction::class); + } + + public function includeBankIntegrations(Company $company) + { + $transformer = new BankIntegrationTransformer($this->serializer); + + return $this->includeCollection($company->bank_integrations, $transformer, BankIntegration::class); + } + public function includeTokensHashed(Company $company) { $transformer = new CompanyTokenHashedTransformer($this->serializer); diff --git a/app/Transformers/ExpenseTransformer.php b/app/Transformers/ExpenseTransformer.php index 13c8d9b7481c..3d390cfcf97c 100644 --- a/app/Transformers/ExpenseTransformer.php +++ b/app/Transformers/ExpenseTransformer.php @@ -103,7 +103,7 @@ class ExpenseTransformer extends EntityTransformer 'private_notes' => (string) $expense->private_notes ?: '', 'public_notes' => (string) $expense->public_notes ?: '', 'transaction_reference' => (string) $expense->transaction_reference ?: '', - 'transaction_id' => (string) $expense->transaction_id ?: '', + 'transaction_id' => (string) $this->encodePrimaryKey($expense->transaction_id) ?: '', 'date' => $expense->date ?: '', 'number' => (string)$expense->number ?: '', 'payment_date' => $expense->payment_date ?: '', diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index e25762b6f4b9..77607f2b1497 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -97,7 +97,7 @@ class InvoiceTransformer extends EntityTransformer 'balance' => (float) $invoice->balance, 'client_id' => (string) $this->encodePrimaryKey($invoice->client_id), 'vendor_id' => (string) $this->encodePrimaryKey($invoice->vendor_id), - 'status_id' => (string) ($invoice->status_id ?: 1), + 'status_id' => (string) ($invoice->status_id ?: '1'), 'design_id' => (string) $this->encodePrimaryKey($invoice->design_id), 'recurring_id' => (string) $this->encodePrimaryKey($invoice->recurring_id), 'created_at' => (int) $invoice->created_at, diff --git a/app/Transformers/PaymentTransformer.php b/app/Transformers/PaymentTransformer.php index 1c4c3b6f0d55..5a9e54c265d5 100644 --- a/app/Transformers/PaymentTransformer.php +++ b/app/Transformers/PaymentTransformer.php @@ -79,6 +79,7 @@ class PaymentTransformer extends EntityTransformer 'refunded' => (float) $payment->refunded, 'applied' => (float) $payment->applied, 'transaction_reference' => $payment->transaction_reference ?: '', + 'transaction_id' => $this->encodePrimaryKey($payment->transaction_id) ?: '', 'date' => $payment->date ?: '', 'is_manual' => (bool) $payment->is_manual, 'created_at' => (int) $payment->created_at, diff --git a/config/ninja.php b/config/ninja.php index 7fe3ff05f171..2a75163c4eb0 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.32', - 'app_tag' => '5.5.32', + 'app_version' => '5.5.33', + 'app_tag' => '5.5.33', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), @@ -200,5 +200,12 @@ return [ 'twilio_account_sid' => env('TWILIO_ACCOUNT_SID',false), 'twilio_auth_token' => env('TWILIO_AUTH_TOKEN',false), 'twilio_verify_sid' => env('TWILIO_VERIFY_SID',false), - + 'yodlee' => [ + 'client_id' => env('YODLEE_CLIENT_ID',false), + 'client_secret' => env('YODLEE_CLIENT_SECRET', false), + 'admin_name' => env('YODLEE_LOGIN_ADMIN_NAME', false), + 'test_mode' => env("YODLEE_TEST_MODE", false), + 'dev_mode' => env("YODLEE_DEV_MODE", false), + 'config_name' => env("YODLEE_CONFIG_NAME", false), + ], ]; diff --git a/database/factories/BankIntegrationFactory.php b/database/factories/BankIntegrationFactory.php new file mode 100644 index 000000000000..ad588f872a84 --- /dev/null +++ b/database/factories/BankIntegrationFactory.php @@ -0,0 +1,41 @@ + $this->faker->company(), + 'provider_id' => 1, + 'bank_account_name' => $this->faker->catchPhrase(), + 'bank_account_id' => 1, + 'bank_account_number' => $this->faker->randomNumber(9, true), + 'bank_account_status' => 'active', + 'bank_account_type' => 'creditCard', + 'balance' => $this->faker->randomFloat(2, 10, 10000), + 'currency' => 'USD', + 'nickname' => $this->faker->word(), + 'is_deleted' => false, + ]; + } +} \ No newline at end of file diff --git a/database/factories/BankTransactionFactory.php b/database/factories/BankTransactionFactory.php new file mode 100644 index 000000000000..ddba2b428d2c --- /dev/null +++ b/database/factories/BankTransactionFactory.php @@ -0,0 +1,40 @@ + $this->faker->randomNumber(9, true) , + 'amount' => $this->faker->randomFloat(2,10,10000) , + 'currency_id' => '1', + 'account_type' => 'creditCard', + 'category_id' => 1, + 'category_type' => 'Random' , + 'date' => $this->faker->date('Y-m-d') , + 'bank_account_id' => 1 , + 'description' =>$this->faker->words(5, true) , + 'status_id'=> 1 + ]; + } +} diff --git a/database/migrations/2022_08_05_023357_bank_integration.php b/database/migrations/2022_08_05_023357_bank_integration.php new file mode 100644 index 000000000000..1375b50af314 --- /dev/null +++ b/database/migrations/2022_08_05_023357_bank_integration.php @@ -0,0 +1,104 @@ +id(); + $table->unsignedInteger('account_id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + + $table->text('provider_name'); //providerName ie Chase + $table->bigInteger('provider_id'); //id of the bank + $table->bigInteger('bank_account_id'); //id + $table->text('bank_account_name')->nullable(); //accountName + $table->text('bank_account_number')->nullable(); //accountNumber + $table->text('bank_account_status')->nullable(); //accountStatus + $table->text('bank_account_type')->nullable(); //CONTAINER + $table->decimal('balance', 20, 6)->default(0); //currentBalance.amount + $table->text('currency')->nullable(); //currentBalance.currency + $table->text('nickname')->default(''); //accountName + $table->date('from_date')->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('account_id')->references('id')->on('accounts')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->text('bank_integration_account_id')->nullable(); + }); + + Schema::create('bank_transactions', function (Blueprint $table){ + $table->id(); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + $table->unsignedBigInteger('bank_integration_id'); + $table->unsignedBigInteger('transaction_id')->index(); + $table->decimal('amount', 20, 6)->default(0); + $table->string('currency_code')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->string('account_type')->nullable(); + $table->unsignedInteger('category_id')->nullable(); + $table->unsignedInteger('ninja_category_id')->nullable(); + $table->string('category_type')->index(); + $table->string('base_type')->index(); + $table->date('date')->nullable(); + $table->unsignedBigInteger('bank_account_id'); + $table->text('description')->nullable(); + $table->text('invoice_ids')->default(''); + $table->unsignedInteger('expense_id')->nullable(); + $table->unsignedInteger('vendor_id')->nullable(); + $table->unsignedInteger('status_id')->default(1); //unmatched / matched / converted + $table->boolean('is_deleted')->default(0); + + $table->timestamps(6); + + $table->softDeletes('deleted_at', 6); + $table->foreign('bank_integration_id')->references('id')->on('bank_integrations')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + + }); + + Schema::table('expense_categories', function (Blueprint $table) { + $table->unsignedInteger('bank_category_id')->nullable(); + }); + + Schema::table('payments', function (Blueprint $table) { + $table->unsignedBigInteger('transaction_id')->nullable(); + }); + + Schema::table('expenses', function (Illuminate\Database\Schema\Blueprint $table) { + $table->unsignedBigInteger('transaction_id')->nullable()->change(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +}; diff --git a/public/js/clients/payments/braintree-credit-card.js b/public/js/clients/payments/braintree-credit-card.js index 974c09213e62..307751827ef4 100644 --- a/public/js/clients/payments/braintree-credit-card.js +++ b/public/js/clients/payments/braintree-credit-card.js @@ -1,2 +1,2 @@ /*! For license information please see braintree-credit-card.js.LICENSE.txt */ -(()=>{function e(e,t){for(var n=0;n{function e(e,t){for(var n=0;n { dropinInstance.requestPaymentMethod({ threeDSecure: { + challengeRequested:true, amount: params.amount, email: params.email, billingAddress: { diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php new file mode 100644 index 000000000000..81b2ab9fe73f --- /dev/null +++ b/resources/views/bank/yodlee/auth.blade.php @@ -0,0 +1,145 @@ +@extends('layouts.ninja') +@section('meta_title', ctrans('texts.new_bank_account')) + +@push('head') + + + + + +@endpush + +@section('body') + + + + +@endsection + +@push('footer') + + + +@endpush \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 41a10a732eeb..0c52e5fcb8e2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,6 +14,8 @@ use App\Http\Controllers\AccountController; use App\Http\Controllers\ActivityController; use App\Http\Controllers\Auth\ForgotPasswordController; use App\Http\Controllers\Auth\LoginController; +use App\Http\Controllers\BankIntegrationController; +use App\Http\Controllers\BankTransactionController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ChartController; use App\Http\Controllers\ClientController; @@ -105,6 +107,17 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { Route::put('accounts/{account}', [AccountController::class, 'update'])->name('account.update'); + Route::resource('bank_integrations', BankIntegrationController::class); // name = (clients. index / create / show / update / destroy / edit + Route::post('bank_integrations/refresh_accounts', [BankIntegrationController::class, 'refreshAccounts'])->name('bank_integrations.refresh_accounts'); + Route::post('bank_integrations/remove_account/{acc_id}', [BankIntegrationController::class, 'removeAccount'])->name('bank_integrations.remove_account'); + Route::post('bank_integrations/get_transactions/{acc_id}', [BankIntegrationController::class, 'getTransactions'])->name('bank_integrations.transactions')->middleware('throttle:1,1'); + + Route::post('bank_integrations/bulk', [BankIntegrationController::class, 'bulk'])->name('bank_integrations.bulk'); + + Route::resource('bank_transactions', BankTransactionController::class); // name = (clients. index / create / show / update / destroy / edit + 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('check_subdomain', [SubdomainController::class, 'index'])->name('check_subdomain'); Route::get('ping', [PingController::class, 'index'])->name('ping'); Route::get('health_check', [PingController::class, 'health'])->name('health_check'); diff --git a/routes/web.php b/routes/web.php index 83ad812b4f1b..fc04c0a72f0f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Auth\ForgotPasswordController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Auth\ResetPasswordController; +use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; use App\Http\Controllers\Gateways\Checkout3dsController; @@ -52,6 +53,8 @@ Route::middleware('url_db')->group(function () { Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize'])->name('stripe_connect.initialization'); Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); +Route::get('yodlee/onboard/{token}', [YodleeController::class, 'auth'])->name('yodlee.auth'); + Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Checkout3dsController::class, 'index'])->middleware('domain_db')->name('checkout.3ds_redirect'); Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mollie3dsController::class, 'index'])->middleware('domain_db')->name('mollie.3ds_redirect'); Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect'); diff --git a/tests/Feature/Bank/BankTransactionTest.php b/tests/Feature/Bank/BankTransactionTest.php new file mode 100644 index 000000000000..6e78a5c45581 --- /dev/null +++ b/tests/Feature/Bank/BankTransactionTest.php @@ -0,0 +1,71 @@ +makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testMatchBankTransactionsValidationShouldFail() + { + $data = []; + + $data['transactions'][] = [ + 'bad_key' => 10, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(422); + } + + + public function testMatchBankTransactionValidationShouldPass() + { + $data = []; + + $data['transactions'][] = [ + 'id' => $this->bank_transaction->hashed_id, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/bank_transactions/match', $data); + + $response->assertStatus(200); + } + +} \ No newline at end of file diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php new file mode 100644 index 000000000000..a21349b6ad27 --- /dev/null +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -0,0 +1,660 @@ +markTestSkipped('Skip test no Yodlee API credentials found'); + + $this->makeTestData(); + + } + + public function testExpenseGenerationFromBankFeed() + { + + $bi = BankIntegrationFactory::create($this->company->id, $this->user->id, $this->account->id); + $bi->save(); + + $bt = BankTransactionFactory::create($this->company->id, $this->user->id); + $bt->bank_integration_id = $bi->id; + $bt->description = 'Fuel'; + $bt->amount = 10; + $bt->currency_code = $this->client->currency()->code; + $bt->date = now()->format('Y-m-d'); + $bt->transaction_id = 1234567890; + $bt->category_id = 10000003; + $bt->base_type = 'DEBIT'; + $bt->save(); + + $data = []; + + $data['transactions'][] = [ + 'id' => $bt->id, + ]; + + MatchBankTransactions::dispatchSync($this->company->id, $this->company->db, $data); + + $expense = Expense::where('transaction_reference', 'Fuel')->first(); + + $this->assertNotNull($expense); + $this->assertEquals(10, (int)$expense->amount); + + } + + public function testIncomeMatchingAndPaymentGeneration() + { + $this->account->bank_integration_account_id = 'sbMem62e1e69547bfb2'; + $this->account->save(); + + $invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); + $invoice->status_id = Invoice::STATUS_DRAFT; + + $invoice->line_items = $this->buildLineItems(); + $invoice->uses_inclusive_taxes = false; + $invoice->tax_rate1 = 0; + $invoice->tax_rate2 = 0; + $invoice->tax_rate3 = 0; + $invoice->discount = 0; + $invoice->number = 'TESTMATCHING'; + $invoice->date = now()->format('Y-m-d'); + + $invoice->save(); + + $invoice_calc = new InvoiceSum($invoice); + $invoice_calc->build(); + + $invoice = $invoice_calc->getInvoice(); + $invoice->save(); + $invoice = $invoice->service()->markSent()->save(); + + $this->assertEquals(Invoice::STATUS_SENT, $invoice->status_id); + $this->assertEquals(10, $invoice->amount); + $this->assertEquals(10, $invoice->balance); + + $bi = BankIntegrationFactory::create($this->company->id, $this->user->id, $this->account->id); + $bi->save(); + + $bt = BankTransactionFactory::create($this->company->id, $this->user->id); + $bt->bank_integration_id = $bi->id; + $bt->description = $invoice->number; + $bt->amount = 10; + $bt->currency_code = $this->client->currency()->code; + $bt->date = now()->format('Y-m-d'); + $bt->transaction_id = 123456; + $bt->save(); + + $data['transactions'][] = [ + 'id' => $bt->id, + 'invoice_ids' => $invoice->hashed_id + ]; + + MatchBankTransactions::dispatchSync($this->company->id, $this->company->db, $data); + + $payment = Payment::where('transaction_reference', $bt->description)->first(); + $payment_count = Payment::where('transaction_reference', $bt->description)->count(); + + $this->assertNotNull($payment); + + $this->assertEquals(10, (int)$payment->amount); + $this->assertEquals(4, $payment->status_id); + $this->assertEquals(1, $payment->invoices()->count()); + + $invoice = $invoice->fresh(); + + $this->assertEquals(Invoice::STATUS_PAID, $invoice->status_id); + $this->assertEquals(0, $invoice->balance); + $this->assertEquals(1, $payment_count); + } + + + public function testCategoryPropertyExists() + { + $yodlee = new Yodlee('sbMem62e1e69547bfb2'); + + $transactions = $yodlee->getTransactionCategories(); + + $this->assertTrue(property_exists($transactions,'transactionCategory')); + + $t = collect($transactions->transactionCategory); + + $x = $t->firstWhere('highLevelCategoryId', 10000003); + + $this->assertNotNull($x); + + $this->assertEquals('Automotive Expenses', $x->highLevelCategoryName); + + } + + public function testFunctionalMatching() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + + $accounts = $yodlee->getAccounts(); + + foreach($accounts as $account) + { + + if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $this->company->id)->exists()) + { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = $this->company->id; + $bank_integration->account_id = $this->company->account_id; + $bank_integration->user_id = $this->user->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + + $bank_integration->save(); + + ProcessBankTransactions::dispatchSync('sbMem62e1e69547bfb1', $bank_integration); + + } + } + + $this->assertGreaterThan(0, BankIntegration::count()); + $this->assertGreaterThan(0, BankTransaction::count()); + + $this->invoice->number = "XXXXXX8501"; + $this->invoice->save(); + + BankService::dispatchSync($this->company->id, $this->company->db); + + $bt = BankTransaction::where('invoice_ids', $this->invoice->hashed_id)->first(); + + $this->assertNotNull($bt); + + $this->assertEquals(BankTransaction::STATUS_MATCHED, $bt->status_id); + + } + + + public function testDataMatching() + { + + $transaction = collect([ + (object)[ + 'description' => 'tinkertonkton' + ], + (object)[ + 'description' => 'spud' + ], + ]); + + $this->assertEquals(2, $transaction->count()); + + $hit = $transaction->where('description', 'spud')->first(); + + $this->assertNotNull($hit); + + $hit = $transaction->where('description', 'tinkertonkton')->first(); + + $this->assertNotNull($hit); + + $hit = $transaction->contains('description', 'tinkertonkton'); + + $this->assertTrue($hit); + + + $transaction = collect([ + (object)[ + 'description' => 'tinker and spice' + ], + (object)[ + 'description' => 'spud with water' + ], + ]); + + $hit = $transaction->contains('description', 'tinker and spice'); + + $this->assertTrue($hit); + + + $invoice = $transaction->first(function ($value, $key) { + + return str_contains($value->description, 'tinker'); + + }); + + $this->assertNotNull($invoice); + + + } + + public function testYodleeInstance() + { + + $yodlee = new Yodlee(); + + $this->assertNotNull($yodlee); + + $this->assertInstanceOf(Yodlee::class, $yodlee); + } + + public function testAccessTokenGeneration() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + + $access_token = $yodlee->getAccessToken(); + + $this->assertNotNull($access_token); + } + +/** + + [transactionCategory] => Array + ( + [0] => stdClass Object + ( + [id] => 1 + [source] => SYSTEM + [classification] => PERSONAL + [category] => Uncategorized + [type] => UNCATEGORIZE + [highLevelCategoryId] => 10000017 + [highLevelCategoryName] => Uncategorized + [defaultCategoryName] => Uncategorized + [defaultHighLevelCategoryName] => Uncategorized + ) + + [1] => stdClass Object + ( + [id] => 2 + [source] => SYSTEM + [classification] => PERSONAL + [category] => Automotive/Fuel + [type] => EXPENSE + [detailCategory] => Array + ( + [0] => stdClass Object + ( + [id] => 1041 + [name] => Registration/Licensing + ) + + [1] => stdClass Object + ( + [id] => 1145 + [name] => Automotive + ) + + [2] => stdClass Object + ( + [id] => 1218 + [name] => Auto Fees/Penalties + ) + + [3] => stdClass Object + ( + [id] => 1260 + [name] => Car Appraisers + ) + + [4] => stdClass Object + ( + [id] => 1261 + [name] => Car Dealers + ) + + [5] => stdClass Object + ( + [id] => 1262 + [name] => Car Dealers and Leasing + ) + + [6] => stdClass Object + ( + [id] => 1263 + [name] => Car Parts and Accessories + ) + + [7] => stdClass Object + ( + [id] => 1264 + [name] => Car Wash and Detail + ) + + [8] => stdClass Object + ( + [id] => 1265 + [name] => Classic and Antique Car + ) + + [9] => stdClass Object + ( + [id] => 1267 + [name] => Maintenance and Repair + ) + + [10] => stdClass Object + ( + [id] => 1268 + [name] => Motorcycles/Mopeds/Scooters + ) + + [11] => stdClass Object + ( + [id] => 1269 + [name] => Oil and Lube + ) + + [12] => stdClass Object + ( + [id] => 1270 + [name] => Motorcycle Repair + ) + + [13] => stdClass Object + ( + [id] => 1271 + [name] => RVs and Motor Homes + ) + + [14] => stdClass Object + ( + [id] => 1272 + [name] => Motorcycle Sales + ) + + [15] => stdClass Object + ( + [id] => 1273 + [name] => Salvage Yards + ) + + [16] => stdClass Object + ( + [id] => 1274 + [name] => Smog Check + ) + + [17] => stdClass Object + ( + [id] => 1275 + [name] => Tires + ) + + [18] => stdClass Object + ( + [id] => 1276 + [name] => Towing + ) + + [19] => stdClass Object + ( + [id] => 1277 + [name] => Transmissions + ) + + [20] => stdClass Object + ( + [id] => 1278 + [name] => Used Cars + ) + + [21] => stdClass Object + ( + [id] => 1240 + [name] => e-Charging + ) + + [22] => stdClass Object + ( + [id] => 1266 + [name] => Gas Stations + ) + + ) + + [highLevelCategoryId] => 10000003 + [highLevelCategoryName] => Automotive Expenses + [defaultCategoryName] => Automotive Expenses + [defaultHighLevelCategoryName] => Automotive Expenses + ) + +*/ + + + public function testGetCategories() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb2'); + + $transactions = $yodlee->getTransactionCategories(); + + $this->assertIsArray($transactions->transactionCategory); + + } + + +/** +[2022-08-05 01:29:45] local.INFO: stdClass Object +( + [account] => Array + ( + [0] => stdClass Object + ( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => My CD - 8878 + [accountStatus] => ACTIVE + [accountNumber] => xxxx8878 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [id] => 12331861 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [accountType] => CD + [displayedName] => LORETTA + [createdDate] => 2022-07-28T06:55:33Z + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) + + ) + + ) + [1] => stdClass Object + ( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => Joint Savings - 7159 + [accountStatus] => ACTIVE + [accountNumber] => xxxx7159 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) + + [id] => 12331860 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [availableBalance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) + + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) + + [accountType] => SAVINGS + [displayedName] => LYDIA + [createdDate] => 2022-07-28T06:55:33Z + [classification] => PERSONAL + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) + + ) + + ) +*/ + public function testGetAccounts() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + + $accounts = $yodlee->getAccounts(); + + $this->assertIsArray($accounts); + } + + +/** +[2022-08-05 01:36:34] local.INFO: stdClass Object +( + [transaction] => Array + ( + [0] => stdClass Object + ( + [CONTAINER] => bank + [id] => 103953585 + [amount] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + + [categoryType] => UNCATEGORIZE + [categoryId] => 1 + [category] => Uncategorized + [categorySource] => SYSTEM + [highLevelCategoryId] => 10000017 + [createdDate] => 2022-08-04T21:50:17Z + [lastUpdated] => 2022-08-04T21:50:17Z + [description] => stdClass Object + ( + [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK + ) + + [isManual] => + [sourceType] => AGGREGATED + [date] => 2022-08-03 + [transactionDate] => 2022-08-03 + [postDate] => 2022-08-03 + [status] => POSTED + [accountId] => 12331794 + [runningBalance] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + + [checkNumber] => 998 + ) + + + */ + + public function testGetTransactions() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + + $transactions = $yodlee->getTransactions(['categoryId' => 2, 'fromDate' => '2000-01-01']); + + $this->assertIsArray($transactions); + + } + + public function testGetTransactionsWithParams() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + + $data = [ + 'basetype' => 'DEBIT', //CREDIT + 'CONTAINER' => 'bank', + 'top' => 500, + 'fromDate' => '2000-10-10', /// YYYY-MM-DD + ]; + + $accounts = $yodlee->getTransactions($data); + + $this->assertIsArray($accounts); + + } + +} diff --git a/tests/Feature/Bank/YodleeBankTransactionTest.php b/tests/Feature/Bank/YodleeBankTransactionTest.php new file mode 100644 index 000000000000..dbd56ebffcb9 --- /dev/null +++ b/tests/Feature/Bank/YodleeBankTransactionTest.php @@ -0,0 +1,124 @@ +markTestSkipped('Skip test no Yodlee API credentials found'); + + $this->makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testDataMatching1() + { + + $this->invoice->number = "super-funk-1234"; + $this->invoice->save(); + + $bank_transaction = BankTransaction::where('company_id', $this->company->id)->first(); + $bank_transaction->description = "super-funk-1234"; + $bank_transaction->save(); + + $this->assertNotNull($this->invoice); + $this->assertNotNull($bank_transaction); + + $invoices = Invoice::where('company_id', $this->company->id)->get(); + + BankTransaction::where('company_id', $this->company->id) + ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->cursor() + ->each(function ($bt) use($invoices){ + + $invoice = $invoices->first(function ($value, $key) use ($bt){ + + return str_contains($value->number, $bt->description); + + }); + + if($invoice) + { + $bt->invoice_ids = $invoice->hashed_id; + $bt->status_id = BankTransaction::STATUS_MATCHED; + $bt->save(); + } + + }); + + + $this->assertTrue(BankTransaction::where('invoice_ids', $this->invoice->hashed_id)->exists()); + + } + + + public function testDataMatching2() + { + + $this->invoice->number = "super-funk-1234"; + $this->invoice->save(); + + $bank_transaction = BankTransaction::where('company_id', $this->company->id)->first(); + $bank_transaction->description = "super-funk-123"; + $bank_transaction->save(); + + $this->assertNotNull($this->invoice); + $this->assertNotNull($bank_transaction); + + $invoices = Invoice::where('company_id', $this->company->id)->get(); + + BankTransaction::where('company_id', $this->company->id) + ->where('status_id', BankTransaction::STATUS_UNMATCHED) + ->cursor() + ->each(function ($bt) use($invoices){ + + $invoice = $invoices->first(function ($value, $key) use ($bt){ + + return str_contains($value->number, $bt->description); + + }); + + if($invoice) + { + $bt->invoice_ids = $invoice->hashed_id; + $bt->status_id = BankTransaction::STATUS_MATCHED; + $bt->save(); + } + + }); + + + $this->assertTrue(BankTransaction::where('invoice_ids', $this->invoice->hashed_id)->exists()); + + } + + + +} \ No newline at end of file diff --git a/tests/Feature/BankIntegrationApiTest.php b/tests/Feature/BankIntegrationApiTest.php new file mode 100644 index 000000000000..2b4f711b0ad3 --- /dev/null +++ b/tests/Feature/BankIntegrationApiTest.php @@ -0,0 +1,102 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + } + + public function testBankIntegrationGet() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/bank_integrations/'.$this->encodePrimaryKey($this->bank_integration->id)); + + $response->assertStatus(200); + } + + public function testBankIntegrationArchived() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_integration->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_integrations/bulk?action=archive', $data); + + $arr = $response->json(); + + $this->assertNotNull($arr['data'][0]['archived_at']); + } + + public function testBankIntegrationRestored() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_integration->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_integrations/bulk?action=restore', $data); + + $arr = $response->json(); + + $this->assertEquals(0, $arr['data'][0]['archived_at']); + } + + public function testBankIntegrationDeleted() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_integration->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_integrations/bulk?action=delete', $data); + + $arr = $response->json(); + + $this->assertTrue($arr['data'][0]['is_deleted']); + } +} diff --git a/tests/Feature/BankTransactionApiTest.php b/tests/Feature/BankTransactionApiTest.php new file mode 100644 index 000000000000..b5210c87bfdc --- /dev/null +++ b/tests/Feature/BankTransactionApiTest.php @@ -0,0 +1,102 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + } + + public function testBankTransactionGet() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/bank_transactions/'.$this->encodePrimaryKey($this->bank_transaction->id)); + + $response->assertStatus(200); + } + + public function testBankTransactionArchived() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transactions/bulk?action=archive', $data); + + $arr = $response->json(); + + $this->assertNotNull($arr['data'][0]['archived_at']); + } + + public function testBankTransactionRestored() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transactions/bulk?action=restore', $data); + + $arr = $response->json(); + + $this->assertEquals(0, $arr['data'][0]['archived_at']); + } + + public function testBankTransactionDeleted() + { + $data = [ + 'ids' => [$this->encodePrimaryKey($this->bank_transaction->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/bank_transactions/bulk?action=delete', $data); + + $arr = $response->json(); + + $this->assertTrue($arr['data'][0]['is_deleted']); + } +} diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index db38a80a05a4..0ccb86849bc7 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -24,6 +24,8 @@ use App\Factory\PurchaseOrderFactory; use App\Helpers\Invoice\InvoiceSum; use App\Jobs\Company\CreateCompanyTaskStatuses; use App\Models\Account; +use App\Models\BankIntegration; +use App\Models\BankTransaction; use App\Models\Client; use App\Models\ClientContact; use App\Models\Company; @@ -141,6 +143,16 @@ trait MockAccountData */ public $cu; + /** + * @var + */ + public $bank_integration; + + /** + * @var + */ + public $bank_transaction; + /** * @var */ @@ -524,6 +536,42 @@ trait MockAccountData 'credit_id' => $this->credit->id, ]); + $this->bank_integration = BankIntegration::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'account_id' => $this->account->id, + ]); + + $this->bank_transaction = BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'bank_integration_id' => $this->bank_integration->id, + ]); + + BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'bank_integration_id' => $this->bank_integration->id, + ]); + + BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'bank_integration_id' => $this->bank_integration->id, + ]); + + BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'bank_integration_id' => $this->bank_integration->id, + ]); + + BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'bank_integration_id' => $this->bank_integration->id, + ]); + $invitations = CreditInvitation::whereCompanyId($this->credit->company_id) ->whereCreditId($this->credit->id); diff --git a/tests/Unit/CompareCollectionTest.php b/tests/Unit/CompareCollectionTest.php index f699b2c002fc..d51f55b606b4 100644 --- a/tests/Unit/CompareCollectionTest.php +++ b/tests/Unit/CompareCollectionTest.php @@ -11,6 +11,7 @@ namespace Tests\Unit; +use App\Utils\Traits\MakesHash; use Tests\TestCase; /** @@ -19,6 +20,8 @@ use Tests\TestCase; */ class CompareCollectionTest extends TestCase { + use MakesHash; + protected function setUp() :void { parent::setUp(); @@ -42,6 +45,29 @@ class CompareCollectionTest extends TestCase $this->is_not_admin = false; } + public function testCollectionCreation() + { + $collection = collect(); + + $invoice_ids = ''; + + $invoices = explode(",", $invoice_ids); + + if(count($invoices) >= 1) + { + + foreach($invoices as $invoice){ + + if(is_string($invoice) && strlen($invoice) > 1) + $collection->push($this->decodePrimaryKey($invoice)); + } + + } + + $this->assertEquals(0, $collection->count()); + + } + public function testCompareResultOfComparison() { $this->assertEquals(7, $this->map->count());