Merge pull request #9403 from turbo124/v5-develop

v5.8.38
This commit is contained in:
David Bomba 2024-03-25 15:20:30 +11:00 committed by GitHub
commit f7094ecff7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 256 additions and 79 deletions

View File

@ -1 +1 @@
5.8.37
5.8.38

View File

@ -169,26 +169,21 @@ class CheckData extends Command
private function checkCompanyTokens()
{
// CompanyUser::whereDoesntHave('token', function ($query){
// return $query->where('is_system', 1);
// })->cursor()->each(function ($cu){
// if ($cu->user) {
// $this->logMessage("Creating missing company token for user # {$cu->user->id} for company id # {$cu->company->id}");
// (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle();
// } else {
// $this->logMessage("Dangling User ID # {$cu->id}");
// }
// });
CompanyUser::query()->cursor()->each(function ($cu) {
if (CompanyToken::where('user_id', $cu->user_id)->where('company_id', $cu->company_id)->where('is_system', 1)->doesntExist()) {
$this->logMessage("Creating missing company token for user # {$cu->user_id} for company id # {$cu->company_id}");
if ($cu->company && $cu->user) {
$this->logMessage("Creating missing company token for user # {$cu->user_id} for company id # {$cu->company_id}");
(new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle();
} else {
// $cu->forceDelete();
}
if (!$cu->user) {
$this->logMessage("No user found for company user - removing company user");
$cu->forceDelete();
}
}
});
}
@ -482,6 +477,14 @@ class CheckData extends Command
}
} else {
$this->logMessage("No contact present, so cannot add invitation for {$entity_key} - {$entity->id}");
try{
$entity->service()->createInvitations()->save();
}
catch(\Exception $e){
}
}
try {

View File

@ -246,6 +246,8 @@ class InvoiceSum
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
if ($this->invoice->amount != $this->invoice->balance) {
// $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward.
} else {
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision);

View File

@ -278,7 +278,7 @@ class InvitationController extends Controller
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$invoice = $invitation->invoice;
$invoice = $invitation->invoice->service()->removeUnpaidGatewayFees()->save();
if ($invoice->partial > 0) {
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision);

View File

@ -72,7 +72,7 @@ class InvoiceController extends Controller
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [
'invoice' => $invoice,
'invoice' => $invoice->service()->removeUnpaidGatewayFees()->save(),
'invitation' => $invitation ?: $invoice->invitations->first(),
'key' => $invitation ? $invitation->key : false,
'hash' => $hash,

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\DataMapper\Analytics\LivePreview;
use App\Http\Requests\Preview\DesignPreviewRequest;
use App\Http\Requests\Preview\PreviewInvoiceRequest;
use App\Http\Requests\Preview\ShowPreviewRequest;
use App\Jobs\Util\PreviewPdf;
use App\Models\Client;
use App\Models\ClientContact;
@ -131,9 +132,9 @@ class PreviewController extends BaseController
* Used in the Custom Designer to preview design changes
* @return mixed
*/
public function show()
public function show(ShowPreviewRequest $request)
{
if(request()->has('template')) {
if($request->input('design.is_template')) {
return $this->template();
}
@ -238,7 +239,6 @@ class PreviewController extends BaseController
private function liveTemplate(array $request_data)
{
nlog($request_data['entity_type']);
/** @var \App\Models\User $user */
$user = auth()->user();
@ -292,8 +292,6 @@ class PreviewController extends BaseController
->setTemplate($design_object)
->mock();
} catch(SyntaxError $e) {
// return response()->json(['message' => 'Twig syntax is invalid.', 'errors' => new \stdClass], 422);
}
if (request()->query('html') == 'true') {

View File

@ -0,0 +1,45 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Preview;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
class ShowPreviewRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
public function rules()
{
$rules = [
];
return $rules;
}
public function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -11,10 +11,14 @@
namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class GenericReportRequest extends Request
{
private string $error_message = '';
/**
* Determine if the user is authorized to make this request.
*
@ -22,11 +26,7 @@ class GenericReportRequest extends Request
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin() || $user->hasPermission('view_reports');
return $this->checkAuthority();
}
public function rules()
@ -70,4 +70,25 @@ class GenericReportRequest extends Request
$this->replace($input);
}
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
}

View File

@ -11,13 +11,17 @@
namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Auth\Access\AuthorizationException;
class ProductSalesReportRequest extends Request
{
use MakesHash;
private string $error_message = '';
/**
* Determine if the user is authorized to make this request.
*
@ -25,18 +29,22 @@ class ProductSalesReportRequest extends Request
*/
public function authorize(): bool
{
return auth()->user()->isAdmin();
return $this->checkAuthority();
}
public function rules()
{
/** @var \App\Models\User $user */
$user = auth()->user();
return [
'date_range' => 'bail|required|string',
'end_date' => 'bail|required_if:date_range,custom|nullable|date',
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
'report_keys' => 'bail|present|array',
'send_email' => 'bail|required|bool',
'client_id' => 'bail|nullable|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0',
'client_id' => 'bail|nullable|sometimes|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0',
];
}
@ -67,4 +75,26 @@ class ProductSalesReportRequest extends Request
$this->replace($input);
}
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
}

View File

@ -11,10 +11,15 @@
namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class ProfitLossRequest extends Request
{
private string $error_message = '';
/**
* Determine if the user is authorized to make this request.
*
@ -22,10 +27,7 @@ class ProfitLossRequest extends Request
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
return $this->checkAuthority();
}
public function rules()
@ -51,4 +53,26 @@ class ProfitLossRequest extends Request
$this->replace($input);
}
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
}

View File

@ -11,10 +11,14 @@
namespace App\Http\Requests\Report;
use App\Utils\Ninja;
use App\Http\Requests\Request;
use Illuminate\Auth\Access\AuthorizationException;
class ReportPreviewRequest extends Request
{
private string $error_message = '';
/**
* Determine if the user is authorized to make this request.
*
@ -22,11 +26,7 @@ class ReportPreviewRequest extends Request
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin() || $user->hasPermission('view_reports');
return $this->checkAuthority();
}
public function rules()
@ -38,4 +38,26 @@ class ReportPreviewRequest extends Request
public function prepareForValidation()
{
}
private function checkAuthority()
{
$this->error_message = ctrans('texts.authorization_failure');
/** @var \App\Models\User $user */
$user = auth()->user();
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
$this->error_message = ctrans('texts.upgrade_to_view_reports');
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
}
protected function failedAuthorization()
{
throw new AuthorizationException($this->error_message);
}
}

View File

@ -59,15 +59,12 @@ class CleanStaleInvoiceOrder implements ShouldQueue
Invoice::query()
->withTrashed()
->where('status_id', Invoice::STATUS_SENT)
->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(30)])
->where('created_at', '<', now()->subMinutes(30))
->where('balance', '>', 0)
->whereJsonContains('line_items', ['type_id' => '3'])
->cursor()
->each(function ($invoice) {
if (collect($invoice->line_items)->contains('type_id', 3)) {
$invoice->service()->removeUnpaidGatewayFees();
}
$invoice->service()->removeUnpaidGatewayFees();
});
return;
@ -86,6 +83,18 @@ class CleanStaleInvoiceOrder implements ShouldQueue
$invoice->is_proforma = false;
$repo->delete($invoice);
});
Invoice::query()
->withTrashed()
->where('status_id', Invoice::STATUS_SENT)
->where('created_at', '<', now()->subMinutes(30))
->where('balance', '>', 0)
->whereJsonContains('line_items', ['type_id' => '3'])
->cursor()
->each(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
});
}
}

View File

@ -126,14 +126,9 @@ class BaseDriver extends AbstractPaymentDriver
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
}
// if ($this->company_gateway->require_contact_name) {
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
// }
// if ($this->company_gateway->require_contact_email) {
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
// }
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
if ($this->company_gateway->require_client_phone) {
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
@ -166,12 +161,10 @@ class BaseDriver extends AbstractPaymentDriver
$fields[] = ['name' => 'client_custom_value2', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client2'), 'type' => 'text', 'validation' => 'required'];
}
if ($this->company_gateway->require_custom_value3) {
$fields[] = ['name' => 'client_custom_value3', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client3'), 'type' => 'text', 'validation' => 'required'];
}
if ($this->company_gateway->require_custom_value4) {
$fields[] = ['name' => 'client_custom_value4', 'label' => $this->helpers->makeCustomField($this->client->company->custom_fields, 'client4'), 'type' => 'text', 'validation' => 'required'];
}

View File

@ -287,6 +287,27 @@ class BraintreePaymentDriver extends BaseDriver
}
}
/**
* Required fields for client to fill, to proceed with gateway actions.
*
* @return array[]
*/
public function getClientRequiredFields(): array
{
$fields = [];
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
return $fields;
}
public function processWebhookRequest($request)
{
$validator = Validator::make($request->all(), [

View File

@ -61,9 +61,9 @@ class CreditCard
'State' => $this->eway_driver->client->state,
'PostalCode' => $this->eway_driver->client->postal_code,
'Country' => $this->eway_driver->client->country->iso_3166_2,
'Phone' => $this->eway_driver->client->phone,
'Email' => $this->eway_driver->client->contacts()->first()->email,
'Url' => $this->eway_driver->client->website,
'Phone' => $this->eway_driver->client->phone ?? '',
'Email' => $this->eway_driver->client->contacts()->first()->email ?? '',
'Url' => $this->eway_driver->client->website ?? '',
'Method' => \Eway\Rapid\Enum\PaymentMethod::CREATE_TOKEN_CUSTOMER,
'SecuredCardData' => $securefieldcode,
];

View File

@ -453,12 +453,6 @@ class InvoiceService
if ((int) $pre_count != (int) $post_count) {
$adjustment = $balance - $new_balance;
// $this->invoice
// ->client
// ->service()
// ->updateBalance($adjustment * -1)
// ->save();
$this->invoice
->ledger()
->updateInvoiceBalance($adjustment * -1, 'Adjustment for removing gateway fee');

View File

@ -80,18 +80,29 @@ class PdfMaker
$replacements = [];
$contents = $this->document->getElementsByTagName('ninja');
$ts = new TemplateService();
$ts = new TemplateService();
if(isset($this->data['template']['entity'])) {
if(isset($this->options['client'])) {
$client = $this->options['client'];
try {
$entity = $this->data['template']['entity'];
$ts->setCompany($entity->company);
$ts->setCompany($client->company);
$ts->addGlobal(['currency_code' => $client->company->currency()->code]);
} catch(\Exception $e) {
nlog($e->getMessage());
}
}
if(isset($this->options['vendor'])) {
$vendor = $this->options['vendor'];
try {
$ts->setCompany($vendor->company);
$ts->addGlobal(['currency_code' => $vendor->company->currency()->code]);
} catch(\Exception $e) {
nlog($e->getMessage());
}
}
$data = $ts->processData($this->options)->getData();
$data = $ts->processData($this->options)->setGlobals()->getData();
$twig = $ts->twig;
foreach ($contents as $content) {

View File

@ -79,7 +79,7 @@ class TemplateAction implements ShouldQueue
*/
public function handle()
{
// nlog("inside template action");
nlog("inside template action");
MultiDB::setDb($this->db);
@ -108,7 +108,14 @@ class TemplateAction implements ShouldQueue
->where('company_id', $this->company->id)
->get();
// nlog($result->toArray());
/** Set a global currency_code */
$first_entity = $result->first();
if($first_entity->client)
$currency_code = $first_entity->client->currency()->code;
elseif($first_entity instanceof Client)
$currency_code = $first_entity->currency()->code;
else
$currency_code = $this->company->currency()->code;
if($result->count() <= 1) {
$data[$key] = collect($result);
@ -118,10 +125,9 @@ class TemplateAction implements ShouldQueue
$ts = $template_service
->setCompany($this->company)
->addGlobal(['currency_code' => $currency_code])
->build($data);
// nlog($ts->getHtml());
if($this->send_email) {
$pdf = $ts->getPdf();
$this->sendEmail($pdf, $template);

View File

@ -157,9 +157,9 @@ class TemplateService
return $this;
}
private function setGlobals(): self
public function setGlobals(): self
{
foreach($this->global_vars as $key => $value) {
$this->twig->addGlobal($key, $value);
}
@ -241,8 +241,6 @@ class TemplateService
public function getPdf(): string
{
// nlog($this->getHtml());
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($this->compiled_html);
} else {

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.37'),
'app_tag' => env('APP_TAG', '5.8.37'),
'app_version' => env('APP_VERSION', '5.8.38'),
'app_tag' => env('APP_TAG', '5.8.38'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),