Merge pull request #11276 from turbo124/v5-develop

v5.12.27
This commit is contained in:
David Bomba 2025-09-06 16:38:06 +10:00 committed by GitHub
commit 876a025e7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 548 additions and 92 deletions

View File

@ -1 +1 @@
5.12.26 5.12.27

View File

@ -86,8 +86,8 @@ class AccountController extends BaseController
} }
if ($request->has('hash') && config('ninja.cloudflare.turnstile.secret')) { //@todo once all platforms are implemented, we disable access to the rest of this route without a success response. if ($request->has('hash') && config('ninja.cloudflare.turnstile.secret')) {
if (Secure::decrypt($request->input('hash')) !== $request->input('email')) { if (Secure::decrypt($request->input('hash')) !== $request->input('email')) {
return response()->json(['message' => 'Invalid Signup Payload'], 400); return response()->json(['message' => 'Invalid Signup Payload'], 400);
} }

View File

@ -84,6 +84,24 @@ class ResetPasswordController extends Controller
*/ */
public function reset(Request $request) public function reset(Request $request)
{ {
// Safely decode URL-encoded token and email before validation
if ($request->has('token')) {
$token = $request->input('token');
// Only decode if it contains URL encoding characters
if (strpos($token, '%') !== false) {
$request->merge(['token' => urldecode($token)]);
}
}
if ($request->has('email')) {
$email = $request->input('email');
// Only decode if it contains URL encoding characters
if (strpos($email, '%') !== false) {
$request->merge(['email' => urldecode($email)]);
}
}
$request->validate($this->rules(), $this->validationErrorMessages()); $request->validate($this->rules(), $this->validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful we // Here we will attempt to reset the user's password. If it is successful we

View File

@ -111,7 +111,7 @@ class ConnectedAccountController extends BaseController
nlog("microsoft"); nlog("microsoft");
nlog($email); nlog($email);
if (auth()->user()->email != $email && MultiDB::checkUserEmailExists($email)) { if (strtolower(auth()->user()->email) != strtolower($email) && MultiDB::checkUserEmailExists(strtolower($email))) {
return response()->json(['message' => ctrans('texts.email_already_register')], 400); return response()->json(['message' => ctrans('texts.email_already_register')], 400);
} }

View File

@ -514,11 +514,13 @@ class TaskController extends BaseController
$tasks = Task::withTrashed()->whereIn('id', $this->transformKeys($ids))->company(); $tasks = Task::withTrashed()->whereIn('id', $this->transformKeys($ids))->company();
if ($request->action == 'bulk_update' && $user->can('edit', $tasks->first())) { $_tasks = (clone $tasks);
if ($request->action == 'bulk_update' && $user->can('edit', $_tasks->first())) {
$this->task_repo->bulkUpdate($tasks, $request->column, $request->new_value); $this->task_repo->bulkUpdate($tasks, $request->column, $request->new_value);
return $this->listResponse(Task::withTrashed()->whereIn('id', $this->transformKeys($ids))); return $this->listResponse(Task::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
} }

View File

@ -62,7 +62,7 @@ class CreateAccountRequest extends Request
public function prepareForValidation() public function prepareForValidation()
{ {
nlog(array_merge(['signup' => 'true', 'ipaddy' => request()->ip()], $this->all())); nlog(array_merge(['signup' => 'true', 'ipaddy' => request()->ip(), 'headers' => request()->headers->all()], $this->all()));
$input = $this->all(); $input = $this->all();

View File

@ -16,8 +16,12 @@ use App\Utils\Ninja;
use App\Models\Account; use App\Models\Account;
use App\Models\Company; use App\Models\Company;
use App\Utils\TempFile; use App\Utils\TempFile;
use App\Services\Email\Email;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use App\Services\Email\EmailObject;
use Illuminate\Support\Facades\App;
use App\Utils\Traits\SavesDocuments; use App\Utils\Traits\SavesDocuments;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -36,6 +40,8 @@ class EInvoicePullDocs implements ShouldQueue
public $tries = 1; public $tries = 1;
private int $einvoice_received_count = 0;
public function __construct() public function __construct()
{ {
} }
@ -64,6 +70,8 @@ class EInvoicePullDocs implements ShouldQueue
}) })
->each(function ($company) { ->each(function ($company) {
$this->einvoice_received_count = 0;
$response = \Illuminate\Support\Facades\Http::baseUrl(config('ninja.hosted_ninja_url')) $response = \Illuminate\Support\Facades\Http::baseUrl(config('ninja.hosted_ninja_url'))
->withHeaders([ ->withHeaders([
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
@ -86,6 +94,24 @@ class EInvoicePullDocs implements ShouldQueue
nlog($response->body()); nlog($response->body());
} }
if($this->einvoice_received_count > 0) {
App::setLocale($company->getLocale());
$mo = new EmailObject();
$mo->subject = ctrans('texts.einvoice_received_subject');
$mo->body = ctrans('texts.einvoice_received_body', ['count' => $this->einvoice_received_count]);
$mo->text_body = ctrans('texts.einvoice_received_body', ['count' => $this->einvoice_received_count]);
$mo->company_key = $company->company_key;
$mo->html_template = 'email.template.admin';
$mo->to = [new Address($company->owner()->email, $company->owner()->present()->name())];
// $mo->email_template_body = 'einvoice_received_body';
// $mo->email_template_subject = 'einvoice_received_subject';
Email::dispatch($mo, $company);
}
}); });
}); });
@ -96,6 +122,8 @@ class EInvoicePullDocs implements ShouldQueue
$storecove = new Storecove(); $storecove = new Storecove();
$mail_payload = [];
foreach ($received_documents as $document) { foreach ($received_documents as $document) {
nlog($document); nlog($document);
$storecove_invoice = $storecove->expense->getStorecoveInvoice(json_encode($document['document']['invoice'])); $storecove_invoice = $storecove->expense->getStorecoveInvoice(json_encode($document['document']['invoice']));
@ -125,6 +153,7 @@ class EInvoicePullDocs implements ShouldQueue
} }
$this->einvoice_received_count++;
} }
@ -145,6 +174,8 @@ class EInvoicePullDocs implements ShouldQueue
if ($response->successful()) { if ($response->successful()) {
} }
} }
public function failed(\Throwable $exception) public function failed(\Throwable $exception)

View File

@ -31,6 +31,7 @@ class MergeEDocument implements ShouldQueue
*/ */
public function handle(): string public function handle(): string
{ {
nlog("MergeEDocument:: handle");
$settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client; $settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client;
$e_document_type = strlen($settings_entity->getSetting('e_invoice_type')) > 2 ? $settings_entity->getSetting('e_invoice_type') : "XInvoice_3_0"; $e_document_type = strlen($settings_entity->getSetting('e_invoice_type')) > 2 ? $settings_entity->getSetting('e_invoice_type') : "XInvoice_3_0";

View File

@ -16,6 +16,7 @@ use App\Models\Task;
use App\Models\Project; use App\Models\Project;
use App\Factory\TaskFactory; use App\Factory\TaskFactory;
use App\Jobs\Task\TaskAssigned; use App\Jobs\Task\TaskAssigned;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
@ -25,6 +26,7 @@ use Illuminate\Database\QueryException;
class TaskRepository extends BaseRepository class TaskRepository extends BaseRepository
{ {
use GeneratesCounter; use GeneratesCounter;
use MakesHash;
public $new_task = true; public $new_task = true;
@ -432,9 +434,14 @@ class TaskRepository extends BaseRepository
public function bulkUpdate(\Illuminate\Database\Eloquent\Builder $models, string $column, mixed $new_value): void public function bulkUpdate(\Illuminate\Database\Eloquent\Builder $models, string $column, mixed $new_value): void
{ {
// First, filter out tasks that have been invoiced // First, filter out tasks that have been invoiced
$models->whereNull('invoice_id'); $models->whereNull('invoice_id');
if(stripos($column, '_id') !== false) {
$new_value = $this->decodePrimaryKey($new_value);
}
if ($column === 'project_id') { if ($column === 'project_id') {
// Handle project_id updates with client_id synchronization // Handle project_id updates with client_id synchronization
$project = Project::withTrashed() $project = Project::withTrashed()
@ -449,7 +456,7 @@ class TaskRepository extends BaseRepository
'client_id' => $project->client_id, 'client_id' => $project->client_id,
]); ]);
} }
} elseif ($column === 'client_id') { } elseif ($column === 'client_id') {
// If you are updating the client - we will unset the project id! // If you are updating the client - we will unset the project id!
$models->update([$column => $new_value, 'project_id' => null]); $models->update([$column => $new_value, 'project_id' => null]);
} }

View File

@ -45,28 +45,31 @@ class ClientService
public function calculateBalance(?Invoice $invoice = null) public function calculateBalance(?Invoice $invoice = null)
{ {
$balance = Invoice::withTrashed() // $pre_client_balance = $this->client->balance;
->where('client_id', $this->client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('is_deleted', false)
->sum('balance');
$pre_client_balance = $this->client->balance;
try { try {
$balance = Invoice::withTrashed()
->where('client_id', $this->client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('is_deleted', false)
->sum('balance');
DB::connection(config('database.default'))->transaction(function () use ($balance) { DB::connection(config('database.default'))->transaction(function () use ($balance) {
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
$this->client->balance = $balance; $this->client->balance = $balance;
$this->client->saveQuietly(); $this->client->saveQuietly();
}, 2); }, 2);
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
nlog("DB ERROR " . $throwable->getMessage()); nlog("DB ERROR " . $throwable->getMessage());
} }
if ($invoice && floatval($this->client->balance) != floatval($pre_client_balance)) { // if ($invoice && floatval($this->client->balance) != floatval($pre_client_balance)) {
$diff = $this->client->balance - $pre_client_balance; // $diff = $this->client->balance - $pre_client_balance;
$invoice->ledger()->insertInvoiceBalance($diff, $this->client->balance, "Update Adjustment Invoice # {$invoice->number} => {$diff}"); // $invoice->ledger()->insertInvoiceBalance($diff, $this->client->balance, "Update Adjustment Invoice # {$invoice->number} => {$diff}");
} // }
return $this; return $this;
} }
@ -116,14 +119,16 @@ class ClientService
public function updatePaymentBalance() public function updatePaymentBalance()
{ {
$amount = Payment::query()
->withTrashed()
->where('client_id', $this->client->id)
->where('is_deleted', 0)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->selectRaw('SUM(payments.amount - payments.applied) as amount')->first()->amount ?? 0;
DB::connection(config('database.default'))->transaction(function () use ($amount) { DB::connection(config('database.default'))->transaction(function () {
$amount = Payment::query()
->withTrashed()
->where('client_id', $this->client->id)
->where('is_deleted', 0)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->selectRaw('SUM(payments.amount - payments.applied) as amount')->first()->amount ?? 0;
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
$this->client->payment_balance = $amount; $this->client->payment_balance = $amount;
$this->client->saveQuietly(); $this->client->saveQuietly();

View File

@ -92,8 +92,8 @@ class ZugferdEDocument extends AbstractService
->setPaymentTerms() // 3. Then payment terms ->setPaymentTerms() // 3. Then payment terms
->setLineItems() // 4. Then line items ->setLineItems() // 4. Then line items
->setCustomSurcharges() // 4a. Surcharges ->setCustomSurcharges() // 4a. Surcharges
->setDocumentSummation() // 5. Finally document summation ->setDocumentSummation(); // 5. Finally document summation
->setAdditionalReferencedDocument(); // 6. Additional referenced document // ->setAdditionalReferencedDocument(); // 6. Additional referenced document
return $this; return $this;
@ -127,7 +127,17 @@ class ZugferdEDocument extends AbstractService
return $this; return $this;
} }
/**
* setAdditionalReferencedDocument
*
* circular reference causing the file to never be created.
* PDF => xml => PDF => xml
*
* Need to abstract the insertion of the base64 document into the XML.
*
* @return self
*/
private function setAdditionalReferencedDocument(): self private function setAdditionalReferencedDocument(): self
{ {
if($this->document->client->getSetting('merge_e_invoice_to_pdf')) { if($this->document->client->getSetting('merge_e_invoice_to_pdf')) {

View File

@ -56,13 +56,12 @@ class AdminEmailMailable extends Mailable
*/ */
public function content() public function content()
{ {
return new Content( return new Content(
view: 'email.admin.generic', view: 'email.admin.generic',
text: 'email.admin.generic_text', text: 'email.admin.generic_text',
with: [ with: [
'title' => $this->email_object->subject, 'title' => $this->email_object->subject,
'message' => $this->email_object->body, 'content' => $this->email_object->body,
'url' => $this->email_object->url ?? null, 'url' => $this->email_object->url ?? null,
'button' => $this->email_object->button ?? null, 'button' => $this->email_object->button ?? null,
'signature' => $this->email_object->company->owner()->signature, 'signature' => $this->email_object->company->owner()->signature,

View File

@ -154,7 +154,7 @@ class DeletePayment
if (abs(floatval($paymentable_invoice->balance) - floatval($paymentable_invoice->amount)) < 0.005) { if (abs(floatval($paymentable_invoice->balance) - floatval($paymentable_invoice->amount)) < 0.005) {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
} elseif (floatval($paymentable_invoice->balance) == 0) { } elseif (abs(floatval($paymentable_invoice->balance)) < 0.005) {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); $paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save();
} else { } else {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
@ -255,4 +255,4 @@ class DeletePayment
return $this->payment; return $this->payment;
} }
} }

375
app/Utils/BcMath.php Normal file
View File

@ -0,0 +1,375 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Utils;
/**
* BcMath utility class for precise financial calculations
*
* This class provides static methods to replace float arithmetic and comparisons
* with bcmath equivalents for consistent and accurate monetary calculations.
*
* All methods use a default scale of 2 decimal places for currency calculations.
* You can override the scale for specific calculations if needed.
*/
class BcMath
{
/**
* Default scale for currency calculations (2 decimal places)
*/
private const DEFAULT_SCALE = 2;
/**
* Add two numbers using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return string
*/
public static function add($left, $right, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcadd((string)$left, (string)$right, $scale);
}
/**
* Subtract two numbers using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return string
*/
public static function sub($left, $right, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcsub((string)$left, (string)$right, $scale);
}
/**
* Multiply two numbers using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return string
*/
public static function mul($left, $right, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcmul((string)$left, (string)$right, $scale);
}
/**
* Divide two numbers using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return string
*/
public static function div($left, $right, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcdiv((string)$left, (string)$right, $scale);
}
/**
* Calculate modulo using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return string
*/
public static function mod($left, $right, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcmod((string)$left, (string)$right, $scale);
}
/**
* Calculate power using bcmath
*
* @param string|float|int $base
* @param string|float|int $exponent
* @param int|null $scale
* @return string
*/
public static function pow($base, $exponent, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcpow((string)$base, (string)$exponent, $scale);
}
/**
* Calculate square root using bcmath
*
* @param string|float|int $number
* @param int|null $scale
* @return string
*/
public static function sqrt($number, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bcsqrt((string)$number, $scale);
}
/**
* Round a number to specified decimal places using bcmath
*
* @param string|float|int $number
* @param int $precision
* @return string
*/
public static function round($number, int $precision = self::DEFAULT_SCALE): string
{
$number = (string)$number;
$scale = $precision + 1; // Add one extra decimal for rounding
// Multiply by 10^scale, add 0.5, floor, then divide by 10^scale
$multiplier = bcpow('10', (string)$scale, 0);
$rounded = bcadd(bcmul($number, $multiplier, 0), '0.5', 0);
$result = bcdiv($rounded, $multiplier, $precision);
return $result;
}
/**
* Compare two numbers using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return int Returns -1, 0, or 1 if left is less than, equal to, or greater than right
*/
public static function comp($left, $right, ?int $scale = null): int
{
$scale = $scale ?? self::DEFAULT_SCALE;
return bccomp((string)$left, (string)$right, $scale);
}
/**
* Check if two numbers are equal using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return bool
*/
public static function equal($left, $right, ?int $scale = null): bool
{
return self::comp($left, $right, $scale) === 0;
}
/**
* Check if left number is greater than right number using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return bool
*/
public static function greaterThan($left, $right, ?int $scale = null): bool
{
return self::comp($left, $right, $scale) === 1;
}
/**
* Check if left number is greater than or equal to right number using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return bool
*/
public static function greaterThanOrEqual($left, $right, ?int $scale = null): bool
{
return self::comp($left, $right, $scale) >= 0;
}
/**
* Check if left number is less than right number using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return bool
*/
public static function lessThan($left, $right, ?int $scale = null): bool
{
return self::comp($left, $right, $scale) === -1;
}
/**
* Check if left number is less than or equal to right number using bcmath
*
* @param string|float|int $left
* @param string|float|int $right
* @param int|null $scale
* @return bool
*/
public static function lessThanOrEqual($left, $right, ?int $scale = null): bool
{
return self::comp($left, $right, $scale) <= 0;
}
/**
* Check if a number is zero using bcmath
*
* @param string|float|int $number
* @param int|null $scale
* @return bool
*/
public static function isZero($number, ?int $scale = null): bool
{
return self::equal($number, '0', $scale);
}
/**
* Check if a number is positive using bcmath
*
* @param string|float|int $number
* @param int|null $scale
* @return bool
*/
public static function isPositive($number, ?int $scale = null): bool
{
return self::greaterThan($number, '0', $scale);
}
/**
* Check if a number is negative using bcmath
*
* @param string|float|int $number
* @param int|null $scale
* @return bool
*/
public static function isNegative($number, ?int $scale = null): bool
{
return self::lessThan($number, '0', $scale);
}
/**
* Get the absolute value using bcmath
*
* @param string|float|int $number
* @param int|null $scale
* @return string
*/
public static function abs($number, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
$number = (string)$number;
if (self::isNegative($number, $scale)) {
return self::mul($number, '-1', $scale);
}
return $number;
}
/**
* Calculate percentage using bcmath
*
* @param string|float|int $part
* @param string|float|int $total
* @param int|null $scale
* @return string
*/
public static function percentage($part, $total, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
if (self::isZero($total, $scale)) {
return '0';
}
return self::mul(self::div($part, $total, $scale + 2), '100', $scale);
}
/**
* Calculate sum of an array of numbers using bcmath
*
* @param array $numbers
* @param int|null $scale
* @return string
*/
public static function sum(array $numbers, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
$result = '0';
foreach ($numbers as $number) {
$result = self::add($result, $number, $scale);
}
return $result;
}
/**
* Calculate average of an array of numbers using bcmath
*
* @param array $numbers
* @param int|null $scale
* @return string
*/
public static function avg(array $numbers, ?int $scale = null): string
{
$scale = $scale ?? self::DEFAULT_SCALE;
$count = count($numbers);
if ($count === 0) {
return '0';
}
$sum = self::sum($numbers, $scale);
return self::div($sum, (string)$count, $scale);
}
/**
* Format a number as currency string with proper precision
*
* @param string|float|int $number
* @param int $precision
* @return string
*/
public static function formatCurrency($number, int $precision = self::DEFAULT_SCALE): string
{
$rounded = self::round($number, $precision);
return number_format((float)$rounded, $precision, '.', '');
}
/**
* Convert a number to float for compatibility with existing code
* Use this sparingly and only when you need to pass values to functions that expect floats
*
* @param string|float|int $number
* @return float
*/
public static function toFloat($number): float
{
return (float)$number;
}
/**
* Convert a number to string for consistent handling
*
* @param string|float|int $number
* @return string
*/
public static function toString($number): string
{
return (string)$number;
}
}

View File

@ -127,6 +127,7 @@ class Statics
'client' => \App\Models\Client::$bulk_update_columns, 'client' => \App\Models\Client::$bulk_update_columns,
'expense' => \App\Models\Expense::$bulk_update_columns, 'expense' => \App\Models\Expense::$bulk_update_columns,
'recurring_invoice' => \App\Models\RecurringInvoice::$bulk_update_columns, 'recurring_invoice' => \App\Models\RecurringInvoice::$bulk_update_columns,
'task' => \App\Models\Task::$bulk_update_columns,
]; ];
return $data; return $data;

94
composer.lock generated
View File

@ -1119,25 +1119,25 @@
}, },
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.13.1", "version": "0.14.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/brick/math.git", "url": "https://github.com/brick/math.git",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^8.1" "php": "^8.2"
}, },
"require-dev": { "require-dev": {
"php-coveralls/php-coveralls": "^2.2", "php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1", "phpstan/phpstan": "2.1.22",
"vimeo/psalm": "6.8.8" "phpunit/phpunit": "^11.5"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -1167,7 +1167,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/brick/math/issues", "issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.13.1" "source": "https://github.com/brick/math/tree/0.14.0"
}, },
"funding": [ "funding": [
{ {
@ -1175,7 +1175,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-03-29T13:50:30+00:00" "time": "2025-08-29T12:40:03+00:00"
}, },
{ {
"name": "btcpayserver/btcpayserver-greenfield-php", "name": "btcpayserver/btcpayserver-greenfield-php",
@ -8518,16 +8518,16 @@
}, },
{ {
"name": "open-telemetry/api", "name": "open-telemetry/api",
"version": "1.4.0", "version": "1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/opentelemetry-php/api.git", "url": "https://github.com/opentelemetry-php/api.git",
"reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7" "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5",
"reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8584,7 +8584,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php" "source": "https://github.com/open-telemetry/opentelemetry-php"
}, },
"time": "2025-06-19T23:36:51+00:00" "time": "2025-08-07T23:07:38+00:00"
}, },
{ {
"name": "open-telemetry/context", "name": "open-telemetry/context",
@ -9521,16 +9521,16 @@
}, },
{ {
"name": "phpoffice/phpspreadsheet", "name": "phpoffice/phpspreadsheet",
"version": "2.4.0", "version": "2.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "3a3cad86101a77019eb2fc693aab1a8c11b18b94" "reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a3cad86101a77019eb2fc693aab1a8c11b18b94", "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/096ae6faf94b49b2cf53e92a0073133c941e1f57",
"reference": "3a3cad86101a77019eb2fc693aab1a8c11b18b94", "reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9620,9 +9620,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.0" "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.1"
}, },
"time": "2025-08-10T06:45:13+00:00" "time": "2025-09-01T18:41:37+00:00"
}, },
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
@ -10752,20 +10752,20 @@
}, },
{ {
"name": "ramsey/uuid", "name": "ramsey/uuid",
"version": "4.9.0", "version": "4.9.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/ramsey/uuid.git", "url": "https://github.com/ramsey/uuid.git",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"php": "^8.0", "php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0" "ramsey/collection": "^1.2 || ^2.0"
}, },
@ -10824,9 +10824,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/ramsey/uuid/issues", "issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.0" "source": "https://github.com/ramsey/uuid/tree/4.9.1"
}, },
"time": "2025-06-25T14:20:11+00:00" "time": "2025-09-04T20:59:21+00:00"
}, },
{ {
"name": "razorpay/razorpay", "name": "razorpay/razorpay",
@ -11266,23 +11266,23 @@
}, },
{ {
"name": "sentry/sentry-laravel", "name": "sentry/sentry-laravel",
"version": "4.15.1", "version": "4.15.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git", "url": "https://github.com/getsentry/sentry-laravel.git",
"reference": "7e0675e8e06d1ec5cb623792892920000a3aedb5" "reference": "c3f71a83e8b3a1451e811199d145e864519cecc1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/7e0675e8e06d1ec5cb623792892920000a3aedb5", "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/c3f71a83e8b3a1451e811199d145e864519cecc1",
"reference": "7e0675e8e06d1ec5cb623792892920000a3aedb5", "reference": "c3f71a83e8b3a1451e811199d145e864519cecc1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0",
"nyholm/psr7": "^1.0", "nyholm/psr7": "^1.0",
"php": "^7.2 | ^8.0", "php": "^7.2 | ^8.0",
"sentry/sentry": "^4.14.1", "sentry/sentry": "^4.15.2",
"symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0" "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0"
}, },
"require-dev": { "require-dev": {
@ -11339,7 +11339,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues", "issues": "https://github.com/getsentry/sentry-laravel/issues",
"source": "https://github.com/getsentry/sentry-laravel/tree/4.15.1" "source": "https://github.com/getsentry/sentry-laravel/tree/4.15.3"
}, },
"funding": [ "funding": [
{ {
@ -11351,7 +11351,7 @@
"type": "custom" "type": "custom"
} }
], ],
"time": "2025-06-24T12:39:03+00:00" "time": "2025-09-04T14:37:41+00:00"
}, },
{ {
"name": "setasign/fpdf", "name": "setasign/fpdf",
@ -18084,16 +18084,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.87.0", "version": "v3.87.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "50a13c4c5f25d2c6894e30e92c051474cf0e115a" "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/50a13c4c5f25d2c6894e30e92c051474cf0e115a", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2f5170365e2a422d0c5421f9c8818b2c078105f6",
"reference": "50a13c4c5f25d2c6894e30e92c051474cf0e115a", "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -18126,7 +18126,7 @@
"require-dev": { "require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7", "facile-it/paraunit": "^1.3.1 || ^2.7",
"infection/infection": "^0.29.14", "infection/infection": "^0.29.14",
"justinrainbow/json-schema": "^6.4", "justinrainbow/json-schema": "^6.5",
"keradus/cli-executor": "^2.2", "keradus/cli-executor": "^2.2",
"mikey179/vfsstream": "^1.6.12", "mikey179/vfsstream": "^1.6.12",
"php-coveralls/php-coveralls": "^2.8", "php-coveralls/php-coveralls": "^2.8",
@ -18176,7 +18176,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v97773{PHP_CS_FIXER_VERSION}" "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.87.1"
}, },
"funding": [ "funding": [
{ {
@ -18184,7 +18184,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-09-02T10:58:35+00:00" "time": "2025-09-02T15:27:36+00:00"
}, },
{ {
"name": "hamcrest/hamcrest-php", "name": "hamcrest/hamcrest-php",
@ -19194,16 +19194,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "11.5.35", "version": "11.5.36",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91" "reference": "264a87c7ef68b1ab9af7172357740dc266df5957"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d341ee94ee5007b286fc7907b383aae6b5b3cc91", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264a87c7ef68b1ab9af7172357740dc266df5957",
"reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91", "reference": "264a87c7ef68b1ab9af7172357740dc266df5957",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -19275,7 +19275,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.35" "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.36"
}, },
"funding": [ "funding": [
{ {
@ -19299,7 +19299,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-28T05:13:54+00:00" "time": "2025-09-03T06:24:17+00:00"
}, },
{ {
"name": "react/cache", "name": "react/cache",

View File

@ -17,7 +17,7 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.12.26'), 'app_version' => env('APP_VERSION', '5.12.27'),
'app_tag' => env('APP_TAG', '5.12.26'), 'app_tag' => env('APP_TAG', '5.12.26'),
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',

View File

@ -5631,7 +5631,10 @@ $lang = array(
'replaced' => 'Replaced', 'replaced' => 'Replaced',
'ses_from_address' => 'SES From Address', 'ses_from_address' => 'SES From Address',
'ses_from_address_help' => 'The Sending Email Address, must be verified in AWS', 'ses_from_address_help' => 'The Sending Email Address, must be verified in AWS',
'unauthorized_action' => 'You are not authorized to perform this action', 'unauthorized_action' => 'You are not authorized to perform this action',
'einvoice_received_subject' => 'E-Invoice/s Received',
'einvoice_received_body' => 'You have received :count new E-Invoice/s.<br><br>Login to view.',
); );
return $lang; return $lang;

View File

@ -12,7 +12,7 @@
<form method="POST" action="{{ route('password.update') }}"> <form method="POST" action="{{ route('password.update') }}">
@csrf @csrf
<input type="hidden" name="token" value="{{ $token }}"> <input type="hidden" name="token" value="{{ $token }}">
<h1>trans('texts.change_password')</h1> <h1>trans('texts.change_password')</h1>
<p class="text-muted"></p> <p class="text-muted"></p>
<div class="input-group mb-3"> <div class="input-group mb-3">

View File

@ -514,3 +514,7 @@ Route::get('/health', function () {
'message' => 'API is healthy', 'message' => 'API is healthy',
]); ]);
})->middleware('throttle:20,1'); })->middleware('throttle:20,1');
Route::get('/api/v1/signup/protect', function () {
return response()->json(['status' => 'ok']);
})->middleware('throttle:10,1');

View File

@ -102,7 +102,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::whereIn('id', [$task1->id, $task2->id]); $models = Task::whereIn('id', [$task1->id, $task2->id]);
// Bulk update project_id // Bulk update project_id
$this->taskRepository->bulkUpdate($models, 'project_id', $otherProject->id); $this->taskRepository->bulkUpdate($models, 'project_id', $otherProject->hashed_id);
// Refresh models from database // Refresh models from database
$task1->refresh(); $task1->refresh();
@ -164,7 +164,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::where('id', $task->id); $models = Task::where('id', $task->id);
// Bulk update client_id // Bulk update client_id
$this->taskRepository->bulkUpdate($models, 'client_id', $newClient->id); $this->taskRepository->bulkUpdate($models, 'client_id', $newClient->hashed_id);
// Refresh model from database // Refresh model from database
$task->refresh(); $task->refresh();
@ -197,7 +197,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::whereIn('id', [$task1->id, $task2->id]); $models = Task::whereIn('id', [$task1->id, $task2->id]);
// Bulk update assigned_user_id // Bulk update assigned_user_id
$this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->id); $this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->hashed_id);
// Refresh models from database // Refresh models from database
$task1->refresh(); $task1->refresh();
@ -238,7 +238,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::whereIn('id', [$invoicedTask->id, $regularTask->id]); $models = Task::whereIn('id', [$invoicedTask->id, $regularTask->id]);
// Bulk update assigned_user_id // Bulk update assigned_user_id
$this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->id); $this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->hashed_id);
// Refresh models from database // Refresh models from database
$invoicedTask->refresh(); $invoicedTask->refresh();
@ -269,7 +269,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::where('id', $task->id); $models = Task::where('id', $task->id);
// Bulk update project_id (should work with soft deleted project) // Bulk update project_id (should work with soft deleted project)
$this->taskRepository->bulkUpdate($models, 'project_id', $this->testProject->id); $this->taskRepository->bulkUpdate($models, 'project_id', $this->testProject->hashed_id);
// Refresh model from database // Refresh model from database
$task->refresh(); $task->refresh();
@ -324,7 +324,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$startTime = microtime(true); $startTime = microtime(true);
// Bulk update assigned_user_id // Bulk update assigned_user_id
$this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->id); $this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->hashed_id);
$endTime = microtime(true); $endTime = microtime(true);
$executionTime = $endTime - $startTime; $executionTime = $endTime - $startTime;
@ -345,7 +345,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::where('id', 99999); $models = Task::where('id', 99999);
// This should not throw an error // This should not throw an error
$this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->id); $this->taskRepository->bulkUpdate($models, 'assigned_user_id', $this->testUser->hashed_id);
// No assertions needed - just ensuring no exceptions are thrown // No assertions needed - just ensuring no exceptions are thrown
$this->assertTrue(true); $this->assertTrue(true);
@ -369,7 +369,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase
$models = Task::where('id', $task->id); $models = Task::where('id', $task->id);
// Bulk update project_id (should work with soft deleted project) // Bulk update project_id (should work with soft deleted project)
$this->taskRepository->bulkUpdate($models, 'project_id', $this->testProject->id); $this->taskRepository->bulkUpdate($models, 'project_id', $this->testProject->hashed_id);
// Refresh model from database // Refresh model from database
$task->refresh(); $task->refresh();