diff --git a/VERSION.txt b/VERSION.txt
index ca69410c4af0..67fd02cd7eea 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-5.7.10
\ No newline at end of file
+5.7.11
\ No newline at end of file
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index b87ea25c7a77..9da20b1020c9 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -107,7 +107,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping();
/* Pulls in bank transactions from third party services */
- $schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
+ $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
$schedule->command('ninja:check-data --database=db-ninja-01')->dailyAt('02:10')->withoutOverlapping()->name('check-data-db-1-job')->onOneServer();
diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php
index efca1de3573a..36635429a6d7 100644
--- a/app/DataMapper/Tax/BaseRule.php
+++ b/app/DataMapper/Tax/BaseRule.php
@@ -363,7 +363,15 @@ class BaseRule implements RuleInterface
public function override($item): self
{
+ $this->tax_rate1 = $item->tax_rate1;
+ $this->tax_name1 = $item->tax_name1;
+ $this->tax_rate2 = $item->tax_rate2;
+ $this->tax_name2 = $item->tax_name2;
+ $this->tax_rate3 = $item->tax_rate3;
+ $this->tax_name3 = $item->tax_name3;
+
return $this;
+
}
public function calculateRates(): self
diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php
index 469d16c74a6f..e880027201ea 100644
--- a/app/DataMapper/Tax/US/Rule.php
+++ b/app/DataMapper/Tax/US/Rule.php
@@ -49,6 +49,10 @@ class Rule extends BaseRule implements RuleInterface
$this->tax_rate1 = $item->tax_rate1;
$this->tax_name1 = $item->tax_name1;
+ $this->tax_rate2 = $item->tax_rate2;
+ $this->tax_name2 = $item->tax_name2;
+ $this->tax_rate3 = $item->tax_rate3;
+ $this->tax_name3 = $item->tax_name3;
return $this;
diff --git a/app/Http/Controllers/ClientPortal/SwitchCompanyController.php b/app/Http/Controllers/ClientPortal/SwitchCompanyController.php
index 8f6dc09366dd..ec0f15541f7a 100644
--- a/app/Http/Controllers/ClientPortal/SwitchCompanyController.php
+++ b/app/Http/Controllers/ClientPortal/SwitchCompanyController.php
@@ -22,9 +22,10 @@ class SwitchCompanyController extends Controller
public function __invoke(string $contact)
{
- $client_contact = ClientContact::where('email', auth()->user()->email)
- ->where('id', $this->transformKeys($contact))
- ->first();
+ $client_contact = ClientContact::query()
+ ->where('email', auth()->user()->email)
+ ->where('id', $this->transformKeys($contact))
+ ->firstOrFail();
auth()->guard('contact')->loginUsingId($client_contact->id, true);
diff --git a/app/Http/Controllers/CompanyUserController.php b/app/Http/Controllers/CompanyUserController.php
index c3d1c6ed45b1..69de122d5f4b 100644
--- a/app/Http/Controllers/CompanyUserController.php
+++ b/app/Http/Controllers/CompanyUserController.php
@@ -115,7 +115,7 @@ class CompanyUserController extends BaseController
$auth_user = auth()->user();
$company = $auth_user->company();
- $company_user = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first();
+ $company_user = CompanyUser::query()->where('user_id', $user->id)->where('company_id',$company->id)->first();
if (! $company_user) {
throw new ModelNotFoundException(ctrans('texts.company_user_not_found'));
@@ -128,6 +128,11 @@ class CompanyUserController extends BaseController
} else {
$company_user->settings = $request->input('company_user')['settings'];
$company_user->notifications = $request->input('company_user')['notifications'];
+
+ if(isset($request->input('company_user')['react_settings'])) {
+ $company_user->react_settings = $request->input('company_user')['react_settings'];
+ }
+
}
$company_user->save();
diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php
index f6ae18029457..2871bc41e7b5 100644
--- a/app/Http/Controllers/ImportController.php
+++ b/app/Http/Controllers/ImportController.php
@@ -120,36 +120,45 @@ class ImportController extends Controller
foreach($headers as $key => $value) {
-
- $hit = false;
- $unsetKey = false;
- // array_multisort(array_column($translated_keys, 'label'), SORT_ASC, $translated_keys);
foreach($translated_keys as $tkey => $tvalue)
{
if($this->testMatch($value, $tvalue['label'])) {
- $hit = $available_keys[$tvalue['key']];
- $unsetKey = $tkey;
+ $hit = $tvalue['key'];
+ $hints[$key] = $hit;
+ unset($translated_keys[$tkey]);
+ break;
+ }
+ else {
+ $hints[$key] = null;
}
- // elseif($this->testMatch($value, $tvalue['index'])) {
- // $hit = $available_keys[$tvalue['key']];
- // $unsetKey = $tkey;
- // }
}
- if($hit) {
- $hints[$key] = $hit;
- unset($translated_keys[$unsetKey]);
- } else {
- $hints[$key] = null;
- }
-
}
- nlog($translated_keys);
+ //second pass using the index of the translation here
+ foreach($headers as $key => $value)
+ {
+ if(isset($hints[$key])) {
+ continue;
+ }
+
+ foreach($translated_keys as $tkey => $tvalue)
+ {
+ if($this->testMatch($value, $tvalue['index'])) {
+ $hit = $tvalue['key'];
+ $hints[$key] = $hit;
+ unset($translated_keys[$tkey]);
+ break;
+ } else {
+ $hints[$key] = null;
+ }
+ }
+
+ }
return $hints;
}
diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php
index 4d06e8bc5ca6..301307372ae6 100644
--- a/app/Http/Controllers/QuoteController.php
+++ b/app/Http/Controllers/QuoteController.php
@@ -16,6 +16,7 @@ use App\Models\Quote;
use App\Models\Client;
use App\Models\Account;
use App\Models\Invoice;
+use App\Models\Project;
use Illuminate\Http\Request;
use App\Factory\QuoteFactory;
use App\Filters\QuoteFilters;
@@ -33,6 +34,7 @@ use App\Transformers\QuoteTransformer;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Facades\Storage;
use App\Transformers\InvoiceTransformer;
+use App\Transformers\ProjectTransformer;
use App\Factory\CloneQuoteToInvoiceFactory;
use App\Factory\CloneQuoteToProjectFactory;
use App\Http\Requests\Quote\EditQuoteRequest;
@@ -555,7 +557,7 @@ class QuoteController extends BaseController
}
});
- return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
+ return $this->listResponse(Quote::query()->withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
if ($action == 'bulk_print' && $user->can('view', $quotes->first())) {
@@ -585,7 +587,7 @@ class QuoteController extends BaseController
}
});
- return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
+ return $this->listResponse(Quote::query()->withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
/*
@@ -683,7 +685,14 @@ class QuoteController extends BaseController
private function performAction(Quote $quote, $action, $bulk = false)
{
switch ($action) {
- case 'convert':
+ case 'convert_to_project':
+
+ $this->entity_type = Project::class;
+ $this->entity_transformer = ProjectTransformer::class;
+
+ return $this->itemResponse($quote->service()->convertToProject());
+
+ case 'convert':
case 'convert_to_invoice':
$this->entity_type = Invoice::class;
@@ -691,8 +700,6 @@ class QuoteController extends BaseController
return $this->itemResponse($quote->service()->convertToInvoice());
- break;
-
case 'clone_to_invoice':
$this->entity_type = Invoice::class;
@@ -701,19 +708,19 @@ class QuoteController extends BaseController
$invoice = CloneQuoteToInvoiceFactory::create($quote, auth()->user()->id);
return $this->itemResponse($invoice);
- break;
+
case 'clone_to_quote':
$quote = CloneQuoteFactory::create($quote, auth()->user()->id);
return $this->itemResponse($quote);
- break;
+
case 'approve':
if (! in_array($quote->status_id, [Quote::STATUS_SENT, Quote::STATUS_DRAFT])) {
return response()->json(['message' => ctrans('texts.quote_unapprovable')], 400);
}
return $this->itemResponse($quote->service()->approveWithNoCoversion()->save());
- break;
+
case 'history':
// code...
break;
@@ -725,16 +732,14 @@ class QuoteController extends BaseController
echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']);
-
- break;
case 'restore':
$this->quote_repo->restore($quote);
if (! $bulk) {
return $this->itemResponse($quote);
}
-
break;
+
case 'archive':
$this->quote_repo->archive($quote);
@@ -752,16 +757,11 @@ class QuoteController extends BaseController
break;
case 'email':
- $quote->service()->sendEmail();
-
- return response()->json(['message'=> ctrans('texts.sent_message')], 200);
- break;
-
case 'send_email':
+
$quote->service()->sendEmail();
return response()->json(['message'=> ctrans('texts.sent_message')], 200);
- break;
case 'mark_sent':
$quote->service()->markSent()->save();
diff --git a/app/Http/Requests/CompanyUser/UpdateCompanyUserRequest.php b/app/Http/Requests/CompanyUser/UpdateCompanyUserRequest.php
index 8db3228d423a..380d063ea347 100644
--- a/app/Http/Requests/CompanyUser/UpdateCompanyUserRequest.php
+++ b/app/Http/Requests/CompanyUser/UpdateCompanyUserRequest.php
@@ -25,7 +25,10 @@ class UpdateCompanyUserRequest extends Request
*/
public function authorize() : bool
{
- return auth()->user()->isAdmin() || (auth()->user()->id == $this->user->id);
+ /** @var \App\Models\User $auth_user */
+ $auth_user = auth()->user();
+
+ return $auth_user->isAdmin() || ($auth_user->id == $this->user->id);
}
public function rules()
diff --git a/app/Http/Requests/Quote/BulkActionQuoteRequest.php b/app/Http/Requests/Quote/BulkActionQuoteRequest.php
index b3fb8857f3bc..c9f41313a568 100644
--- a/app/Http/Requests/Quote/BulkActionQuoteRequest.php
+++ b/app/Http/Requests/Quote/BulkActionQuoteRequest.php
@@ -30,9 +30,11 @@ class BulkActionQuoteRequest extends Request
{
$input = $this->all();
- $rules = [];
+ $rules = [
+ 'action' => 'sometimes|in:convert_to_invoice,convert_to_project,email,bulk_download,bulk_print,clone_to_invoice,approve,download,restore,archive,delete,send_email,mark_sent',
+ ];
- if ($input['action'] == 'convert_to_invoice') {
+ if (in_array($input['action'], ['convert,convert_to_invoice']) ) {
$rules['action'] = [new ConvertableQuoteRule()];
}
diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php
index 6dab0aa252c7..52d331d3ab92 100644
--- a/app/Http/Requests/Task/StoreTaskRequest.php
+++ b/app/Http/Requests/Task/StoreTaskRequest.php
@@ -28,23 +28,30 @@ class StoreTaskRequest extends Request
*/
public function authorize() : bool
{
- return auth()->user()->can('create', Task::class);
+ /** @var \App\Models\User $user */
+ $user = auth()->user();
+
+ return $user->can('create', Task::class);
}
public function rules()
{
+
+ /** @var \App\Models\User $user */
+ $user = auth()->user();
+
$rules = [];
if (isset($this->number)) {
- $rules['number'] = Rule::unique('tasks')->where('company_id', auth()->user()->company()->id);
+ $rules['number'] = Rule::unique('tasks')->where('company_id', $user->company()->id);
}
if (isset($this->client_id)) {
- $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
+ $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0';
}
if (isset($this->project_id)) {
- $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
+ $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0';
}
$rules['timelog'] = ['bail','array',function ($attribute, $values, $fail) {
@@ -77,9 +84,9 @@ class StoreTaskRequest extends Request
public function prepareForValidation()
{
- $input = $this->all();
- $input = $this->decodePrimaryKeys($this->all());
+ $input = $this->decodePrimaryKeys($this->all());
+
if (array_key_exists('status_id', $input) && is_string($input['status_id'])) {
$input['status_id'] = $this->decodePrimaryKey($input['status_id']);
}
diff --git a/app/Mail/Admin/EntitySentObject.php b/app/Mail/Admin/EntitySentObject.php
index da2c1030fbc1..e1ae21c90de9 100644
--- a/app/Mail/Admin/EntitySentObject.php
+++ b/app/Mail/Admin/EntitySentObject.php
@@ -73,7 +73,7 @@ class EntitySentObject
);
$mail_obj->data = [
'title' => $mail_obj->subject,
- 'message' => ctrans(
+ 'content' => ctrans(
$this->template_body,
[
'amount' => $mail_obj->amount,
@@ -98,7 +98,7 @@ class EntitySentObject
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
}
-
+ nlog($mail_obj);
return $mail_obj;
}
@@ -186,7 +186,7 @@ class EntitySentObject
return [
'title' => $this->getSubject(),
- 'message' => $this->getMessage(),
+ 'content' => $this->getMessage(),
'url' => $this->invitation->getAdminLink($this->use_react_url),
'button' => ctrans("texts.view_{$this->entity_type}"),
'signature' => $settings->email_signature,
diff --git a/app/Models/Task.php b/app/Models/Task.php
index caee4ca8a374..be0c86135e8a 100644
--- a/app/Models/Task.php
+++ b/app/Models/Task.php
@@ -226,4 +226,15 @@ class Task extends BaseModel
{
return ctrans('texts.task');
}
+
+ public function getRate(): float
+ {
+ if($this->project && $this->project->task_rate > 0)
+ return $this->project->task_rate;
+
+ if($this->client)
+ return $this->client->getSetting('default_task_rate');
+
+ return $this->company->settings->default_task_rate ?? 0;
+ }
}
diff --git a/app/PaymentDrivers/Authorize/RefundTransaction.php b/app/PaymentDrivers/Authorize/RefundTransaction.php
index 462d4f4236cc..998ffbad33a9 100644
--- a/app/PaymentDrivers/Authorize/RefundTransaction.php
+++ b/app/PaymentDrivers/Authorize/RefundTransaction.php
@@ -12,14 +12,16 @@
namespace App\PaymentDrivers\Authorize;
-use App\Jobs\Util\SystemLogger;
use App\Models\Payment;
use App\Models\SystemLog;
+use App\Jobs\Util\SystemLogger;
use App\PaymentDrivers\AuthorizePaymentDriver;
-use net\authorize\api\contract\v1\CreateTransactionRequest;
-use net\authorize\api\contract\v1\CustomerProfilePaymentType;
+use net\authorize\api\contract\v1\PaymentType;
+use net\authorize\api\contract\v1\CreditCardType;
use net\authorize\api\contract\v1\PaymentProfileType;
use net\authorize\api\contract\v1\TransactionRequestType;
+use net\authorize\api\contract\v1\CreateTransactionRequest;
+use net\authorize\api\contract\v1\CustomerProfilePaymentType;
use net\authorize\api\controller\CreateTransactionController;
/**
@@ -43,24 +45,42 @@ class RefundTransaction
$transaction_details = $this->authorize_transaction->getTransactionDetails($payment->transaction_reference);
+ $creditCard = $transaction_details->getTransaction()->getPayment()->getCreditCard();
+ $creditCardNumber = $creditCard->getCardNumber();
+ $creditCardExpiry = $creditCard->getExpirationDate();
+ $transaction_status = $transaction_details->getTransaction()->getTransactionStatus();
+
+ $transaction_type = $transaction_status == 'capturedPendingSettlement' ? 'voidTransaction' : 'refundTransaction';
+
+ if($transaction_type == 'voidTransaction') {
+ $amount = $transaction_details->getTransaction()->getAuthAmount();
+ }
+
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref'.time();
- $paymentProfile = new PaymentProfileType();
- $paymentProfile->setPaymentProfileId($transaction_details->getTransaction()->getProfile()->getCustomerPaymentProfileId());
+ // $paymentProfile = new PaymentProfileType();
+ // $paymentProfile->setPaymentProfileId($transaction_details->getTransaction()->getProfile()->getCustomerPaymentProfileId());
- // set customer profile
- $customerProfile = new CustomerProfilePaymentType();
- $customerProfile->setCustomerProfileId($transaction_details->getTransaction()->getProfile()->getCustomerProfileId());
- $customerProfile->setPaymentProfile($paymentProfile);
+ // // // set customer profile
+ // $customerProfile = new CustomerProfilePaymentType();
+ // $customerProfile->setCustomerProfileId($transaction_details->getTransaction()->getProfile()->getCustomerProfileId());
+ // $customerProfile->setPaymentProfile($paymentProfile);
+
+ $creditCard = new CreditCardType();
+ $creditCard->setCardNumber($creditCardNumber);
+ $creditCard->setExpirationDate($creditCardExpiry);
+ $paymentOne = new PaymentType();
+ $paymentOne->setCreditCard($creditCard);
//create a transaction
$transactionRequest = new TransactionRequestType();
- $transactionRequest->setTransactionType('refundTransaction');
+ $transactionRequest->setTransactionType($transaction_type);
$transactionRequest->setAmount($amount);
- $transactionRequest->setProfile($customerProfile);
+ // $transactionRequest->setProfile($customerProfile);
+ $transactionRequest->setPayment($paymentOne);
$transactionRequest->setRefTransId($payment->transaction_reference);
$request = new CreateTransactionRequest();
@@ -83,6 +103,7 @@ class RefundTransaction
'transaction_response' => $tresponse->getResponseCode(),
'payment_id' => $payment->id,
'amount' => $amount,
+ 'voided' => $transaction_status == 'capturedPendingSettlement' ? true : false,
];
SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company);
@@ -166,4 +187,5 @@ class RefundTransaction
SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company);
}
+
}
diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php
index f3f5642dc198..dbb954d35a60 100644
--- a/app/Repositories/TaskRepository.php
+++ b/app/Repositories/TaskRepository.php
@@ -45,7 +45,11 @@ class TaskRepository extends BaseRepository
$task->saveQuietly();
if ($this->new_task && ! $task->status_id) {
- $this->setDefaultStatus($task);
+ $task->status_id = $this->setDefaultStatus($task);
+ }
+
+ if($this->new_task && (!$task->rate || $task->rate <= 0)) {
+ $task->rate = $task->getRate();
}
$task->number = empty($task->number) || ! array_key_exists('number', $data) ? $this->trySaving($task) : $data['number'];
diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php
index 40ce3240f438..f17f5f902dc7 100644
--- a/app/Services/Credit/SendEmail.php
+++ b/app/Services/Credit/SendEmail.php
@@ -11,8 +11,10 @@
namespace App\Services\Credit;
-use App\Jobs\Entity\EmailEntity;
+use App\Utils\Ninja;
use App\Models\ClientContact;
+use App\Jobs\Entity\EmailEntity;
+use App\Events\Credit\CreditWasEmailed;
class SendEmail
{
@@ -40,12 +42,17 @@ class SendEmail
$this->reminder_template = $this->credit->calculateTemplate('credit');
}
+ $this->credit->service()->markSent()->save();
+
$this->credit->invitations->each(function ($invitation) {
if (! $invitation->contact->trashed() && $invitation->contact->email) {
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template)->delay(2);
}
});
- $this->credit->service()->markSent()->save();
+ if ($this->credit->invitations->count() >= 1) {
+ event(new CreditWasEmailed($this->credit->invitations->first(), $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'credit'));
+ }
+
}
}
diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php
index 5cf05dd6a3d9..7cbc7a0cc0ee 100644
--- a/app/Services/Invoice/SendEmail.php
+++ b/app/Services/Invoice/SendEmail.php
@@ -11,10 +11,12 @@
namespace App\Services\Invoice;
-use App\Jobs\Entity\EmailEntity;
-use App\Models\ClientContact;
+use App\Utils\Ninja;
use App\Models\Invoice;
+use App\Models\ClientContact;
+use App\Jobs\Entity\EmailEntity;
use App\Services\AbstractService;
+use App\Events\Invoice\InvoiceWasEmailed;
class SendEmail extends AbstractService
{
@@ -36,5 +38,10 @@ class SendEmail extends AbstractService
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template)->delay(10);
}
});
+
+ if ($this->invoice->invitations->count() >= 1) {
+ event(new InvoiceWasEmailed($this->invoice->invitations->first(), $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), $this->reminder_template ?? 'invoice'));
+ }
+
}
}
diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php
index fe56ef089145..007f1df6efbf 100644
--- a/app/Services/Payment/RefundPayment.php
+++ b/app/Services/Payment/RefundPayment.php
@@ -35,6 +35,10 @@ class RefundPayment
private $activity_repository;
+ private bool $refund_failed = false;
+
+ private string $refund_failed_message = '';
+
public function __construct($payment, $refund_data)
{
$this->payment = $payment;
@@ -50,12 +54,14 @@ class RefundPayment
public function run()
{
- $this->payment = $this->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment)
+ $this->payment = $this
+ ->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment)
+ ->processGatewayRefund() //process the gateway refund if needed
->setStatus() //sets status of payment
->updateCreditables() //return the credits first
->updatePaymentables() //update the paymentable items
->adjustInvoices()
- ->processGatewayRefund() //process the gateway refund if needed
+ ->finalize()
->save();
if (array_key_exists('email_receipt', $this->refund_data) && $this->refund_data['email_receipt'] == 'true') {
@@ -71,9 +77,28 @@ class RefundPayment
return $this->payment;
}
+ private function finalize(): self
+ {
+ if($this->refund_failed)
+ throw new PaymentRefundFailed($this->refund_failed_message);
+
+ return $this;
+ }
+
/**
* Process the refund through the gateway.
*
+ * @var array $response
+ * [
+ * 'transaction_reference' => (string),
+ * 'transaction_response' => (string),
+ * 'success' => (bool),
+ * 'description' => (string),
+ * 'code' => (string),
+ * 'payment_id' => (int),
+ * 'amount' => (float),
+ * ];
+ *
* @return $this
* @throws PaymentRefundFailed
*/
@@ -83,16 +108,27 @@ class RefundPayment
if ($this->payment->company_gateway) {
$response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund);
+ if($response['amount'] ?? false)
+ $this->total_refund = $response['amount'];
+
+ if($response['voided'] ?? false)
+ {
+ //When a transaction is voided - all invoices attached to the payment need to be reversed, this
+ //block prevents the edge case where a partial refund was attempted.
+ $this->refund_data['invoices'] = $this->payment->invoices->map(function ($invoice){
+ return [
+ 'invoice_id' => $invoice->id,
+ 'amount' => $invoice->pivot->amount,
+ ];
+ })->toArray();
+ }
+
$this->payment->refunded += $this->total_refund;
if ($response['success'] == false) {
$this->payment->save();
-
- if (array_key_exists('description', $response)) {
- throw new PaymentRefundFailed($response['description']);
- } else {
- throw new PaymentRefundFailed();
- }
+ $this->refund_failed = true;
+ $this->refund_failed_message = $response['description'] ?? '';
}
}
} else {
diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php
index c55fadf26c47..d0e54a9b1891 100644
--- a/app/Services/PdfMaker/Design.php
+++ b/app/Services/PdfMaker/Design.php
@@ -932,7 +932,9 @@ class Design extends BaseDesign
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
- $visible = intval($this->entity->{$_variable}) != 0;
+ //07/09/2023 don't show custom values if they are empty
+ // $visible = intval($this->entity->{$_variable}) != 0;
+ $visible = intval(str_replace(['0','.'],'', $this->entity->{$_variable})) != 0;
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
diff --git a/app/Services/Quote/ConvertQuoteToProject.php b/app/Services/Quote/ConvertQuoteToProject.php
new file mode 100644
index 000000000000..d74b05f49ebd
--- /dev/null
+++ b/app/Services/Quote/ConvertQuoteToProject.php
@@ -0,0 +1,76 @@
+quote->line_items)->filter(function ($item){
+ return $item->type_id == '2';
+ });
+
+ $project = ProjectFactory::create($this->quote->company_id, $this->quote->user_id);
+ $project->name = ctrans('texts.quote_number_short'). " " . $this->quote->number . "[{$this->quote->client->present()->name()}]";
+ $project->client_id = $this->quote->client_id;
+ $project->public_notes = $this->quote->public_notes;
+ $project->private_notes = $this->quote->private_notes;
+ $project->budgeted_hours = $quote_items->sum('quantity') ?? 0;
+ $project->task_rate = $this->quote->client->getSetting('default_task_rate');
+ $project->saveQuietly();
+ $project->number = $this->getNextProjectNumber($project);
+ $project->saveQuietly();
+
+ $this->quote->project_id = $project->id;
+ $this->quote->saveQuietly();
+
+ $task_status = $this->quote->company->task_statuses()
+ ->whereNull('deleted_at')
+ ->orderBy('id', 'asc')
+ ->first();
+
+
+ $task_repo = new TaskRepository();
+
+ $quote_items->each(function($item) use($task_repo, $task_status){
+
+ $task = TaskFactory::create($this->quote->company_id, $this->quote->user_id);
+ $task->client_id = $this->quote->client_id;
+ $task->project_id = $this->quote->project_id;
+ $task->description = $item->notes;
+ $task->status_id = $task_status->id;
+ $task->rate = $item->unit_cost;
+ $task_repo->save([], $task);
+
+ });
+
+ event('eloquent.created: App\Models\Project', $project);
+
+ return $project->fresh();
+ }
+}
\ No newline at end of file
diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php
index 908cb257c151..e12168af52e1 100644
--- a/app/Services/Quote/QuoteService.php
+++ b/app/Services/Quote/QuoteService.php
@@ -13,13 +13,14 @@ namespace App\Services\Quote;
use App\Utils\Ninja;
use App\Models\Quote;
-use App\Jobs\Util\UnlinkFile;
+use App\Models\Project;
use App\Utils\Traits\MakesHash;
use App\Exceptions\QuoteConversion;
use App\Jobs\Entity\CreateEntityPdf;
use App\Repositories\QuoteRepository;
use App\Events\Quote\QuoteWasApproved;
use Illuminate\Support\Facades\Storage;
+use App\Services\Quote\ConvertQuoteToProject;
class QuoteService
{
@@ -41,6 +42,13 @@ class QuoteService
return $this;
}
+ public function convertToProject(): Project
+ {
+ $project = (new ConvertQuoteToProject($this->quote))->run();
+
+ return $project;
+ }
+
public function convert() :self
{
if ($this->quote->invoice_id) {
diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php
index 445d321ff97c..d157792aa0c6 100644
--- a/app/Services/Quote/SendEmail.php
+++ b/app/Services/Quote/SendEmail.php
@@ -11,8 +11,10 @@
namespace App\Services\Quote;
-use App\Jobs\Entity\EmailEntity;
+use App\Utils\Ninja;
use App\Models\ClientContact;
+use App\Jobs\Entity\EmailEntity;
+use App\Events\Quote\QuoteWasEmailed;
class SendEmail
{
@@ -42,15 +44,15 @@ class SendEmail
$this->reminder_template = $this->quote->calculateTemplate('quote');
}
-
$this->quote->service()->markSent()->save();
$this->quote->invitations->each(function ($invitation) {
if (! $invitation->contact->trashed() && $invitation->contact->email) {
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template);
-
- // MailEntity::dispatch($invitation, $invitation->company->db, $mo);
}
});
+
+
+
}
}
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index 97b2e7ea46f5..058f9ed36731 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -663,6 +663,12 @@ class HtmlEngine
$data['$payment.custom3'] = ['value' => '', 'label' => ctrans('texts.payment')];
$data['$payment.custom4'] = ['value' => '', 'label' => ctrans('texts.payment')];
+ $data['$payment.amount'] = ['value' => '', 'label' => ctrans('texts.payment')];
+ $data['$payment.date'] = ['value' => '', 'label' => ctrans('texts.payment_date')];
+ $data['$payment.number'] = ['value' => '', 'label' => ctrans('texts.payment_number')];
+ $data['$payment.transaction_reference'] = ['value' => '', 'label' => ctrans('texts.transaction_reference')];
+
+
if ($this->entity_string == 'invoice' && $this->entity->payments()->exists()) {
$payment_list = '
';
@@ -672,7 +678,6 @@ class HtmlEngine
$data['$payments'] = ['value' => $payment_list, 'label' => ctrans('texts.payments')];
-
$payment = $this->entity->payments()->first();
$data['$payment.custom1'] = ['value' => $payment->custom_value1, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')];
@@ -680,6 +685,11 @@ class HtmlEngine
$data['$payment.custom3'] = ['value' => $payment->custom_value3, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment3')];
$data['$payment.custom4'] = ['value' => $payment->custom_value4, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment4')];
+ $data['$payment.amount'] = ['value' => Number::formatMoney($payment->amount, $this->client), 'label' => ctrans('texts.payment')];
+ $data['$payment.date'] = ['value' => $this->formatDate($payment->date, $this->client->date_format()), 'label' => ctrans('texts.payment_date')];
+ $data['$payment.number'] = ['value' => $payment->number, 'label' => ctrans('texts.payment_number')];
+ $data['$payment.transaction_reference'] = ['value' => $payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')];
+
}
if (($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') && isset($this->company?->custom_fields?->company1)) {
diff --git a/app/Utils/Traits/Pdf/PdfMaker.php b/app/Utils/Traits/Pdf/PdfMaker.php
index 99f5038d9505..ebca89f47246 100644
--- a/app/Utils/Traits/Pdf/PdfMaker.php
+++ b/app/Utils/Traits/Pdf/PdfMaker.php
@@ -39,6 +39,8 @@ trait PdfMaker
$pdf->addChromiumArguments(config('ninja.snappdf_chromium_arguments'));
}
+ $html = str_replace(['file:/', 'iframe', '<object', '