diff --git a/app/Exceptions/PaymentRefundFailed.php b/app/Exceptions/PaymentRefundFailed.php index a5dee9cfdd8a..94a6fe455e01 100644 --- a/app/Exceptions/PaymentRefundFailed.php +++ b/app/Exceptions/PaymentRefundFailed.php @@ -26,8 +26,16 @@ class PaymentRefundFailed extends Exception */ public function render($request) { + + // $msg = 'Unable to refund the transaction'; + $msg = ctrans('texts.warning_local_refund'); + + if($this->getMessage() && strlen($this->getMessage()) >=1 ) + $msg = $this->getMessage(); + return response()->json([ - 'message' => 'Unable to refund the transaction', + 'message' => $msg, ], 401); + } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 8bfed78cf0a3..4d29e66e1518 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -44,61 +44,6 @@ class ForgotPasswordController extends Controller } /** - * Password Reset. - * - * - * @OA\Post( - * path="/api/v1/reset_password", - * operationId="reset_password", - * tags={"reset_password"}, - * summary="Attempts to reset the users password", - * description="Resets a users email password", - * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\RequestBody( - * description="Password reset email", - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * type="object", - * @OA\Property( - * property="email", - * description="The user email address", - * type="string", - * ) - * ) - * ) - * ), - * @OA\Response( - * response=201, - * description="The Reset 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\JsonContent( - * @OA\Items( - * type="string", - * example="Reset link send to your email.", - * ) - * ), - * ), - * @OA\Response( - * response=401, - * description="Validation error", - * @OA\JsonContent( - * @OA\Items( - * type="string", - * example="Unable to send password reset link", - * ), - * ), - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) * @param Request $request * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse * @throws \Illuminate\Validation\ValidationException diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 9d3d272e65bd..7f52eedb2968 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -585,64 +585,5 @@ class ClientController extends BaseController } -/** - * Update the specified resource in storage. - * - * @param UploadClientRequest $request - * @param Client $client - * @return Response - * - * - * - * @OA\Put( - * path="/api/v1/clients/{id}/adjust_ledger", - * operationId="adjustLedger", - * tags={"clients"}, - * summary="Adjust the client ledger to rebalance", - * description="Adjust the client ledger to rebalance", - * @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 Client Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the client 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/Client"), - * ), - * @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"), - * ), - * ) - */ - //@deprecated - not available - public function adjustLedger(AdjustClientLedgerRequest $request, Client $client) - { - // $adjustment = $request->input('adjustment'); - // $notes = $request->input('notes'); - - // $client->service()->updateBalance - } } diff --git a/app/Http/Controllers/ClientStatementController.php b/app/Http/Controllers/ClientStatementController.php index 5d9dfb758a18..6acf4d6d89bc 100644 --- a/app/Http/Controllers/ClientStatementController.php +++ b/app/Http/Controllers/ClientStatementController.php @@ -19,6 +19,7 @@ use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; +use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\MakesHash; use App\Utils\Traits\Pdf\PdfMaker; @@ -34,6 +35,79 @@ class ClientStatementController extends BaseController parent::__construct(); } + /** + * Update the specified resource in storage. + * + * @param CreateStatementRequest $request + * @return Response + * + * @OA\Post( + * path="/api/v1/client_statement", + * operationId="clientStatement", + * tags={"clients"}, + * summary="Return a PDF of the client statement", + * description="Return a PDF of the client statement", + * @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\RequestBody( + * description="Statment Options", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="object", + * @OA\Property( + * property="start_date", + * description="The start date of the statement period - format Y-m-d", + * type="string", + * ), + * @OA\Property( + * property="end_date", + * description="The start date of the statement period - format Y-m-d", + * type="string", + * ), + * @OA\Property( + * property="client_id", + * description="The hashed ID of the client", + * type="string", + * ), + * @OA\Property( + * property="show_payments_table", + * description="Flag which determines if the payments table is shown", + * type="boolean", + * ), + * @OA\Property( + * property="show_aging_table", + * description="Flag which determines if the aging table is shown", + * type="boolean", + * ) + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="Returns the client 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/Client"), + * ), + * @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 statement(CreateStatementRequest $request) { $pdf = $this->createStatement($request); @@ -49,21 +123,23 @@ class ClientStatementController extends BaseController protected function createStatement(CreateStatementRequest $request): ?string { - $invitation = InvoiceInvitation::first(); + $invitation = false; - if (count($request->getInvoices()) >= 1) { + if ($request->getInvoices()->count() >= 1) { $this->entity = $request->getInvoices()->first(); + $invitation = $this->entity->invitations->first(); } - - if (count($request->getPayments()) >= 1) { - $this->entity = $request->getPayments()->first(); + else if ($request->getPayments()->count() >= 1) { + $this->entity = $request->getPayments()->first()->invoices->first()->invitations->first(); + $invitation = $this->entity->invitations->first(); } $entity_design_id = 1; $entity_design_id = $this->entity->design_id ? $this->entity->design_id - : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id)); + : $this->decodePrimaryKey($this->entity->client->getSetting('invoice_design_id')); + $design = Design::find($entity_design_id); @@ -114,7 +190,10 @@ class ClientStatementController extends BaseController $pdf = null; try { - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + } + else if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); } else { $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); diff --git a/app/Http/Requests/Statements/CreateStatementRequest.php b/app/Http/Requests/Statements/CreateStatementRequest.php index a8577c538367..9a75794a550d 100644 --- a/app/Http/Requests/Statements/CreateStatementRequest.php +++ b/app/Http/Requests/Statements/CreateStatementRequest.php @@ -2,12 +2,17 @@ namespace App\Http\Requests\Statements; +use App\Http\Requests\Request; +use App\Models\Client; use App\Models\Invoice; use App\Models\Payment; -use Illuminate\Foundation\Http\FormRequest; +use App\Utils\Number; +use App\Utils\Traits\MakesHash; +use Carbon\Carbon; -class CreateStatementRequest extends FormRequest +class CreateStatementRequest extends Request { + use MakesHash; /** * Determine if the user is authorized to make this request. * @@ -26,11 +31,21 @@ class CreateStatementRequest extends FormRequest public function rules() { return [ - 'start_date' => ['required'], - 'end_date' => ['required'], + 'start_date' => 'required|date_format:Y-m-d', + 'end_date' => 'required|date_format:Y-m-d', + 'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id, + 'show_payments_table' => 'boolean', + 'show_aging_table' => 'boolean', ]; } + protected function prepareForValidation() + { + $input = $this->all(); + $input = $this->decodePrimaryKeys($input); + + $this->replace($input); + } /** * The collection of invoices for the statement. * @@ -38,9 +53,19 @@ class CreateStatementRequest extends FormRequest */ public function getInvoices() { - // $this->request->start_date & $this->request->end_date are available. + $input = $this->all(); - return Invoice::all(); + // $input['start_date & $input['end_date are available. + $client = Client::where('id', $input['client_id'])->first(); + + $from = Carbon::parse($input['start_date']); + $to = Carbon::parse($input['end_date']); + + return Invoice::where('company_id', auth()->user()->company()->id) + ->where('client_id', $client->id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]) + ->whereBetween('date',[$from, $to]) + ->get(); } /** @@ -50,22 +75,95 @@ class CreateStatementRequest extends FormRequest */ public function getPayments() { - // $this->request->start_date & $this->request->end_date are available. + // $input['start_date & $input['end_date are available. + $input = $this->all(); + + $client = Client::where('id', $input['client_id'])->first(); + + $from = Carbon::parse($input['start_date']); + $to = Carbon::parse($input['end_date']); + + return Payment::where('company_id', auth()->user()->company()->id) + ->where('client_id', $client->id) + ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) + ->whereBetween('date',[$from, $to]) + ->get(); - return Payment::all(); } + /** * The array of aging data. */ public function getAging(): array { return [ - '0-30' => 1000, - '30-60' => 2000, - '60-90' => 3000, - '90-120' => 4000, - '120+' => 5000, + '0-30' => $this->getAgingAmount('30'), + '30-60' => $this->getAgingAmount('60'), + '60-90' => $this->getAgingAmount('90'), + '90-120' => $this->getAgingAmount('120'), + '120+' => $this->getAgingAmount('120+'), ]; } + + private function getAgingAmount($range) + { + $input = $this->all(); + + $ranges = $this->calculateDateRanges($range); + + $from = $ranges[0]; + $to = $ranges[1]; + + $client = Client::where('id', $input['client_id'])->first(); + + $amount = Invoice::where('company_id', auth()->user()->company()->id) + ->where('client_id', $client->id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('balance', '>', 0) + ->whereBetween('date',[$from, $to]) + ->sum('balance'); + + return Number::formatMoney($amount, $client); + } + + private function calculateDateRanges($range) + { + + $ranges = []; + + switch ($range) { + case '30': + $ranges[0] = now(); + $ranges[1] = now()->subDays(30); + return $ranges; + break; + case '60': + $ranges[0] = now()->subDays(30); + $ranges[1] = now()->subDays(60); + return $ranges; + break; + case '90': + $ranges[0] = now()->subDays(60); + $ranges[1] = now()->subDays(90); + return $ranges; + break; + case '120': + $ranges[0] = now()->subDays(90); + $ranges[1] = now()->subDays(120); + return $ranges; + break; + case '120+': + $ranges[0] = now()->subDays(120); + $ranges[1] = now()->subYears(40); + return $ranges; + break; + default: + $ranges[0] = now()->subDays(0); + $ranges[1] = now()->subDays(30); + return $ranges; + break; + } + + } } diff --git a/app/PaymentDrivers/BraintreePaymentDriver.php b/app/PaymentDrivers/BraintreePaymentDriver.php index d36f11a23eba..7c0cdd278685 100644 --- a/app/PaymentDrivers/BraintreePaymentDriver.php +++ b/app/PaymentDrivers/BraintreePaymentDriver.php @@ -124,24 +124,57 @@ class BraintreePaymentDriver extends BaseDriver { $this->init(); - try { + try{ + $response = $this->gateway->transaction()->refund($payment->transaction_reference, $amount); - - return [ - 'transaction_reference' => $response->id, - 'transaction_response' => json_encode($response), - 'success' => (bool)$response->success, - 'description' => $response->status, - 'code' => 0, - ]; + } catch (Exception $e) { - return [ + + $data = [ 'transaction_reference' => null, 'transaction_response' => json_encode($e->getMessage()), 'success' => false, 'description' => $e->getMessage(), 'code' => $e->getCode(), ]; + + SystemLogger::dispatch(['server_response' => null, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); + + return $data; + } + + if($response->success) + { + + $data = [ + 'transaction_reference' => $response->id, + 'transaction_response' => json_encode($response), + 'success' => (bool)$response->success, + 'description' => $response->status, + 'code' => 0, + ]; + + SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); + + return $data; + + } + else{ + + $error = $response->errors->deepAll()[0]; + + $data = [ + 'transaction_reference' => null, + 'transaction_response' => $response->errors->deepAll(), + 'success' => false, + 'description' => $error->message, + 'code' => $error->code, + ]; + + SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); + + return $data; + } } diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 61ecfe7dd953..a1d733311e1e 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -81,7 +81,7 @@ class RefundPayment if ($response['success'] == false) { $this->payment->save(); - throw new PaymentRefundFailed(); + throw new PaymentRefundFailed($response['description']); } } } else { diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 88ee420bf970..818307c806ef 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -361,8 +361,8 @@ class Design extends BaseDesign $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) ?: ' ']; $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->calc()->getTotal(), $invoice->client) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->partial, $invoice->client) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $invoice->client) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $invoice->client) ?: ' ']; $tbody[] = $element; } @@ -379,6 +379,8 @@ class Design extends BaseDesign return []; } + $outstanding = $this->invoices->sum('amount'); + return [ ['element' => 'p', 'content' => '$outstanding_label: $outstanding'], ]; @@ -395,7 +397,7 @@ class Design extends BaseDesign return []; } - if (\array_key_exists('show_payment_table', $this->options) && $this->options['show_payment_table'] === false) { + if (\array_key_exists('show_payments_table', $this->options) && $this->options['show_payments_table'] === false) { return []; } @@ -407,7 +409,7 @@ class Design extends BaseDesign $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $payment->client->date_format(), $payment->client->locale()) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => GatewayType::getAlias($payment->gateway_type_id) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->amount, $payment->client) ?: ' ']; $tbody[] = $element; @@ -422,12 +424,14 @@ class Design extends BaseDesign public function statementPaymentTableTotals(): array { - if ($this->type !== self::STATEMENT) { + if ($this->type !== self::STATEMENT || !$this->payments->first()) { return []; } + $payment = $this->payments->first(); + return [ - ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), 1000)], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payments->sum('amount'), $payment->client))], ]; }