diff --git a/VERSION.txt b/VERSION.txt index 7620216932..f5ac77f2d0 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.12.26 \ No newline at end of file +5.12.27 \ No newline at end of file diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 993e733175..c5820ba29d 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -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')) { return response()->json(['message' => 'Invalid Signup Payload'], 400); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index bb84d17abb..2a42b720a1 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -84,6 +84,24 @@ class ResetPasswordController extends Controller */ 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()); // Here we will attempt to reset the user's password. If it is successful we diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index 2748b6d6b2..d08372a158 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -111,7 +111,7 @@ class ConnectedAccountController extends BaseController nlog("microsoft"); 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); } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 1cc28e55ee..f62913fc6b 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -514,11 +514,13 @@ class TaskController extends BaseController $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); - return $this->listResponse(Task::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(Task::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); } diff --git a/app/Http/Requests/Account/CreateAccountRequest.php b/app/Http/Requests/Account/CreateAccountRequest.php index 2c39856c21..ac488dd0b5 100644 --- a/app/Http/Requests/Account/CreateAccountRequest.php +++ b/app/Http/Requests/Account/CreateAccountRequest.php @@ -62,7 +62,7 @@ class CreateAccountRequest extends Request 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(); diff --git a/app/Jobs/EDocument/EInvoicePullDocs.php b/app/Jobs/EDocument/EInvoicePullDocs.php index 26c2808dbe..2afaab0359 100644 --- a/app/Jobs/EDocument/EInvoicePullDocs.php +++ b/app/Jobs/EDocument/EInvoicePullDocs.php @@ -16,8 +16,12 @@ use App\Utils\Ninja; use App\Models\Account; use App\Models\Company; use App\Utils\TempFile; +use App\Services\Email\Email; use Illuminate\Bus\Queueable; +use App\Services\Email\EmailObject; +use Illuminate\Support\Facades\App; use App\Utils\Traits\SavesDocuments; +use Illuminate\Mail\Mailables\Address; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; @@ -36,6 +40,8 @@ class EInvoicePullDocs implements ShouldQueue public $tries = 1; + private int $einvoice_received_count = 0; + public function __construct() { } @@ -64,6 +70,8 @@ class EInvoicePullDocs implements ShouldQueue }) ->each(function ($company) { + $this->einvoice_received_count = 0; + $response = \Illuminate\Support\Facades\Http::baseUrl(config('ninja.hosted_ninja_url')) ->withHeaders([ 'Content-Type' => 'application/json', @@ -86,6 +94,24 @@ class EInvoicePullDocs implements ShouldQueue 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(); + $mail_payload = []; + foreach ($received_documents as $document) { nlog($document); $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()) { } + + } public function failed(\Throwable $exception) diff --git a/app/Jobs/EDocument/MergeEDocument.php b/app/Jobs/EDocument/MergeEDocument.php index 77881736c9..8c7a3654cc 100644 --- a/app/Jobs/EDocument/MergeEDocument.php +++ b/app/Jobs/EDocument/MergeEDocument.php @@ -31,6 +31,7 @@ class MergeEDocument implements ShouldQueue */ public function handle(): string { + nlog("MergeEDocument:: handle"); $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"; diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php index 93c31d0131..e34ac8723a 100644 --- a/app/Repositories/TaskRepository.php +++ b/app/Repositories/TaskRepository.php @@ -16,6 +16,7 @@ use App\Models\Task; use App\Models\Project; use App\Factory\TaskFactory; use App\Jobs\Task\TaskAssigned; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\QueryException; @@ -25,6 +26,7 @@ use Illuminate\Database\QueryException; class TaskRepository extends BaseRepository { use GeneratesCounter; + use MakesHash; 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 { + // First, filter out tasks that have been invoiced $models->whereNull('invoice_id'); - + + if(stripos($column, '_id') !== false) { + $new_value = $this->decodePrimaryKey($new_value); + } + if ($column === 'project_id') { // Handle project_id updates with client_id synchronization $project = Project::withTrashed() @@ -449,7 +456,7 @@ class TaskRepository extends BaseRepository '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! $models->update([$column => $new_value, 'project_id' => null]); } diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 624059f0e3..7ed1f14d78 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -45,28 +45,31 @@ class ClientService public function calculateBalance(?Invoice $invoice = null) { - $balance = Invoice::withTrashed() - ->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; + // $pre_client_balance = $this->client->balance; 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) { $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client->balance = $balance; $this->client->saveQuietly(); }, 2); + + } catch (\Throwable $throwable) { nlog("DB ERROR " . $throwable->getMessage()); } - if ($invoice && floatval($this->client->balance) != floatval($pre_client_balance)) { - $diff = $this->client->balance - $pre_client_balance; - $invoice->ledger()->insertInvoiceBalance($diff, $this->client->balance, "Update Adjustment Invoice # {$invoice->number} => {$diff}"); - } + // if ($invoice && floatval($this->client->balance) != floatval($pre_client_balance)) { + // $diff = $this->client->balance - $pre_client_balance; + // $invoice->ledger()->insertInvoiceBalance($diff, $this->client->balance, "Update Adjustment Invoice # {$invoice->number} => {$diff}"); + // } return $this; } @@ -116,14 +119,16 @@ class ClientService 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->payment_balance = $amount; $this->client->saveQuietly(); diff --git a/app/Services/EDocument/Standards/ZugferdEDocument.php b/app/Services/EDocument/Standards/ZugferdEDocument.php index 1a5eac1222..13c4770ca8 100644 --- a/app/Services/EDocument/Standards/ZugferdEDocument.php +++ b/app/Services/EDocument/Standards/ZugferdEDocument.php @@ -92,8 +92,8 @@ class ZugferdEDocument extends AbstractService ->setPaymentTerms() // 3. Then payment terms ->setLineItems() // 4. Then line items ->setCustomSurcharges() // 4a. Surcharges - ->setDocumentSummation() // 5. Finally document summation - ->setAdditionalReferencedDocument(); // 6. Additional referenced document + ->setDocumentSummation(); // 5. Finally document summation + // ->setAdditionalReferencedDocument(); // 6. Additional referenced document return $this; @@ -127,7 +127,17 @@ class ZugferdEDocument extends AbstractService 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 { if($this->document->client->getSetting('merge_e_invoice_to_pdf')) { diff --git a/app/Services/Email/AdminEmailMailable.php b/app/Services/Email/AdminEmailMailable.php index 94842e495c..8a1370f5db 100644 --- a/app/Services/Email/AdminEmailMailable.php +++ b/app/Services/Email/AdminEmailMailable.php @@ -56,13 +56,12 @@ class AdminEmailMailable extends Mailable */ public function content() { - return new Content( view: 'email.admin.generic', text: 'email.admin.generic_text', with: [ 'title' => $this->email_object->subject, - 'message' => $this->email_object->body, + 'content' => $this->email_object->body, 'url' => $this->email_object->url ?? null, 'button' => $this->email_object->button ?? null, 'signature' => $this->email_object->company->owner()->signature, diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 401fd70ce9..1246c113a5 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -154,7 +154,7 @@ class DeletePayment if (abs(floatval($paymentable_invoice->balance) - floatval($paymentable_invoice->amount)) < 0.005) { $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(); } else { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); @@ -255,4 +255,4 @@ class DeletePayment return $this->payment; } -} +} \ No newline at end of file diff --git a/app/Utils/BcMath.php b/app/Utils/BcMath.php new file mode 100644 index 0000000000..2b68052c5c --- /dev/null +++ b/app/Utils/BcMath.php @@ -0,0 +1,375 @@ += 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; + } +} diff --git a/app/Utils/Statics.php b/app/Utils/Statics.php index c82d7ec44e..dbbe3d3b79 100644 --- a/app/Utils/Statics.php +++ b/app/Utils/Statics.php @@ -127,6 +127,7 @@ class Statics 'client' => \App\Models\Client::$bulk_update_columns, 'expense' => \App\Models\Expense::$bulk_update_columns, 'recurring_invoice' => \App\Models\RecurringInvoice::$bulk_update_columns, + 'task' => \App\Models\Task::$bulk_update_columns, ]; return $data; diff --git a/composer.lock b/composer.lock index 08eb077d3e..2c7dfe7bef 100644 --- a/composer.lock +++ b/composer.lock @@ -1119,25 +1119,25 @@ }, { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -1167,7 +1167,7 @@ ], "support": { "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": [ { @@ -1175,7 +1175,7 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-08-29T12:40:03+00:00" }, { "name": "btcpayserver/btcpayserver-greenfield-php", @@ -8518,16 +8518,16 @@ }, { "name": "open-telemetry/api", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7" + "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", - "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5", + "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5", "shasum": "" }, "require": { @@ -8584,7 +8584,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "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", @@ -9521,16 +9521,16 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "3a3cad86101a77019eb2fc693aab1a8c11b18b94" + "reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a3cad86101a77019eb2fc693aab1a8c11b18b94", - "reference": "3a3cad86101a77019eb2fc693aab1a8c11b18b94", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/096ae6faf94b49b2cf53e92a0073133c941e1f57", + "reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57", "shasum": "" }, "require": { @@ -9620,9 +9620,9 @@ ], "support": { "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", @@ -10752,20 +10752,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "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", "ramsey/collection": "^1.2 || ^2.0" }, @@ -10824,9 +10824,9 @@ ], "support": { "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", @@ -11266,23 +11266,23 @@ }, { "name": "sentry/sentry-laravel", - "version": "4.15.1", + "version": "4.15.3", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "7e0675e8e06d1ec5cb623792892920000a3aedb5" + "reference": "c3f71a83e8b3a1451e811199d145e864519cecc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/7e0675e8e06d1ec5cb623792892920000a3aedb5", - "reference": "7e0675e8e06d1ec5cb623792892920000a3aedb5", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/c3f71a83e8b3a1451e811199d145e864519cecc1", + "reference": "c3f71a83e8b3a1451e811199d145e864519cecc1", "shasum": "" }, "require": { "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", "nyholm/psr7": "^1.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" }, "require-dev": { @@ -11339,7 +11339,7 @@ ], "support": { "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": [ { @@ -11351,7 +11351,7 @@ "type": "custom" } ], - "time": "2025-06-24T12:39:03+00:00" + "time": "2025-09-04T14:37:41+00:00" }, { "name": "setasign/fpdf", @@ -18084,16 +18084,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.87.0", + "version": "v3.87.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "50a13c4c5f25d2c6894e30e92c051474cf0e115a" + "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/50a13c4c5f25d2c6894e30e92c051474cf0e115a", - "reference": "50a13c4c5f25d2c6894e30e92c051474cf0e115a", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2f5170365e2a422d0c5421f9c8818b2c078105f6", + "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6", "shasum": "" }, "require": { @@ -18126,7 +18126,7 @@ "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.7", "infection/infection": "^0.29.14", - "justinrainbow/json-schema": "^6.4", + "justinrainbow/json-schema": "^6.5", "keradus/cli-executor": "^2.2", "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.8", @@ -18176,7 +18176,7 @@ ], "support": { "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": [ { @@ -18184,7 +18184,7 @@ "type": "github" } ], - "time": "2025-09-02T10:58:35+00:00" + "time": "2025-09-02T15:27:36+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -19194,16 +19194,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.35", + "version": "11.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91" + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d341ee94ee5007b286fc7907b383aae6b5b3cc91", - "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264a87c7ef68b1ab9af7172357740dc266df5957", + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957", "shasum": "" }, "require": { @@ -19275,7 +19275,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "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": [ { @@ -19299,7 +19299,7 @@ "type": "tidelift" } ], - "time": "2025-08-28T05:13:54+00:00" + "time": "2025-09-03T06:24:17+00:00" }, { "name": "react/cache", diff --git a/config/ninja.php b/config/ninja.php index 282acdac63..4c0508de02 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -17,7 +17,7 @@ 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.12.26'), + 'app_version' => env('APP_VERSION', '5.12.27'), 'app_tag' => env('APP_TAG', '5.12.26'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', diff --git a/lang/en/texts.php b/lang/en/texts.php index 582a6abb28..733146f182 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5631,7 +5631,10 @@ $lang = array( 'replaced' => 'Replaced', 'ses_from_address' => 'SES From Address', '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.

Login to view.', + ); return $lang; diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php index 664ef7838a..b22748f253 100644 --- a/resources/views/auth/passwords/reset.blade.php +++ b/resources/views/auth/passwords/reset.blade.php @@ -12,7 +12,7 @@
@csrf - +

trans('texts.change_password')

diff --git a/routes/api.php b/routes/api.php index 788e46fc79..fdf16e49a2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -514,3 +514,7 @@ Route::get('/health', function () { 'message' => 'API is healthy', ]); })->middleware('throttle:20,1'); + +Route::get('/api/v1/signup/protect', function () { + return response()->json(['status' => 'ok']); +})->middleware('throttle:10,1'); \ No newline at end of file diff --git a/tests/Unit/TaskRepositoryBulkUpdateTest.php b/tests/Unit/TaskRepositoryBulkUpdateTest.php index 1153dc2eae..53779af14a 100644 --- a/tests/Unit/TaskRepositoryBulkUpdateTest.php +++ b/tests/Unit/TaskRepositoryBulkUpdateTest.php @@ -102,7 +102,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::whereIn('id', [$task1->id, $task2->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 $task1->refresh(); @@ -164,7 +164,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::where('id', $task->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 $task->refresh(); @@ -197,7 +197,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::whereIn('id', [$task1->id, $task2->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 $task1->refresh(); @@ -238,7 +238,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::whereIn('id', [$invoicedTask->id, $regularTask->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 $invoicedTask->refresh(); @@ -269,7 +269,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::where('id', $task->id); // 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 $task->refresh(); @@ -324,7 +324,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $startTime = microtime(true); // 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); $executionTime = $endTime - $startTime; @@ -345,7 +345,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::where('id', 99999); // 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 $this->assertTrue(true); @@ -369,7 +369,7 @@ class TaskRepositoryBulkUpdateTest extends TestCase $models = Task::where('id', $task->id); // 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 $task->refresh();