diff --git a/VERSION.txt b/VERSION.txt index 86c5a84a4d..13ee93ba38 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.12.48 \ No newline at end of file +5.12.49 \ No newline at end of file diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 4fca7fdfe6..442e123e6c 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -200,6 +200,40 @@ class LoginController extends BaseController } } + public function refreshReact(Request $request) + { + $truth = app()->make(TruthSource::class); + + if ($truth->getCompanyToken()) { + $company_token = $truth->getCompanyToken(); + } else { + $company_token = CompanyToken::where('token', $request->header('X-API-TOKEN'))->first(); + } + + $cu = CompanyUser::query() + ->where('user_id', $company_token->user_id); + + if ($cu->count() == 0) { + return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); + } + + $cu->first()->account->companies->each(function ($company) use ($cu, $request) { + if ($company->tokens()->where('is_system', true)->count() == 0) { + (new CreateCompanyToken($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')))->handle(); + } + }); + + if ($request->has('current_company') && $request->input('current_company') == 'true') { + $cu->where('company_id', $company_token->company_id); + } + + if (Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterprisePaidClient()) { + return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); + } + + return $this->refreshReactResponse($cu); + } + /** * Refreshes the data feed with the current Company User. * diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 3f71ca67fe..0cf9c3cf77 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -297,6 +297,44 @@ class BaseController extends Controller return response()->make($error, $httpErrorCode, $headers); } + /** + * Heavily reduced refresh query to reduce DB burden + * + * @param Builder $query + * @return Response| \Illuminate\Http\JsonResponse + */ + protected function refreshReactResponse($query) + { + $this->manager->parseIncludes([ + 'account', + 'user.company_user', + 'token', + 'company.tax_rates', + ]); + + $this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY; + + if ($this->serializer === EntityTransformer::API_SERIALIZER_JSON) { + $this->manager->setSerializer(new JsonApiSerializer()); + } else { + $this->manager->setSerializer(new ArraySerializer()); + } + + $transformer = new $this->entity_transformer($this->serializer); + + $limit = $this->resolveQueryLimit(); + + $paginator = $query->paginate($limit); + + /** @phpstan-ignore-next-line */ + $query = $paginator->getCollection(); // @phpstan-ignore-line + + $resource = new Collection($query, $transformer, $this->entity_type); + + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return $this->response($this->manager->createData($resource)->toArray()); + } /** * Refresh API response with latest cahnges * @@ -681,7 +719,10 @@ class BaseController extends Controller // Set created_at to current time to filter out all existing related records // (designs, documents, groups, etc.) for a minimal response payload request()->merge(['created_at' => time()]); - return $this->miniLoadResponse($query); + + //2026-01-23: Improve Login Performance for react. + return $this->refreshReactResponse($query); + // return $this->miniLoadResponse($query); } elseif ($user->getCompany()->is_large) { $this->manager->parseIncludes($this->mini_load); diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 6277825cae..25a069e96c 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -607,7 +607,7 @@ class ProjectController extends BaseController $this->entity_transformer = InvoiceTransformer::class; $this->entity_type = Invoice::class; - $invoice = $this->project_repo->invoice($project); + $invoice = $this->project_repo->invoice(collect([$project])); return $this->itemResponse($invoice); } diff --git a/app/Jobs/Cron/InvoiceTaxSummary.php b/app/Jobs/Cron/InvoiceTaxSummary.php index e8eadf47de..58500424a4 100644 --- a/app/Jobs/Cron/InvoiceTaxSummary.php +++ b/app/Jobs/Cron/InvoiceTaxSummary.php @@ -44,6 +44,9 @@ class InvoiceTaxSummary implements ShouldQueue public function handle() { + nlog("InvoiceTaxSummary:: Starting job @ " . now()->toDateTimeString()); + $start = now(); + $currentUtcHour = now()->hour; $transitioningTimezones = $this->getTransitioningTimezones($currentUtcHour); @@ -56,6 +59,8 @@ class InvoiceTaxSummary implements ShouldQueue $this->processCompanyTaxSummary($company); } } + + nlog("InvoiceTaxSummary:: Job completed in " . now()->diffInSeconds($start) . " seconds"); } private function getTransitioningTimezones($utcHour) @@ -117,7 +122,11 @@ class InvoiceTaxSummary implements ShouldQueue } // Get companies that have timezone_id in their JSON settings matching the transitioning timezones - return Company::whereRaw("JSON_EXTRACT(settings, '$.timezone_id') IN (" . implode(',', $timezoneIds) . ")")->get(); + $companies = Company::whereRaw("JSON_EXTRACT(settings, '$.timezone_id') IN (" . implode(',', $timezoneIds) . ")")->get(); + + nlog("InvoiceTaxSummary:: Found " . $companies->count() . " companies in timezones: " . implode(',', $timezoneIds)); + + return $companies; } private function processCompanyTaxSummary($company) diff --git a/app/Jobs/Ninja/TaskScheduler.php b/app/Jobs/Ninja/TaskScheduler.php index 960b531978..37a9908d9e 100644 --- a/app/Jobs/Ninja/TaskScheduler.php +++ b/app/Jobs/Ninja/TaskScheduler.php @@ -64,7 +64,12 @@ class TaskScheduler implements ShouldQueue //@var \App\Models\Schedule $scheduler $scheduler->service()->runTask(); } catch (\Throwable $e) { + nlog("Exception:: TaskScheduler:: Doing job :: {$scheduler->id} :: {$scheduler->name}" . $e->getMessage()); + + if (app()->bound('sentry')) { + app('sentry')->captureException($e); + } } }); diff --git a/app/Mail/Admin/EntityFailedSendObject.php b/app/Mail/Admin/EntityFailedSendObject.php index 124263f3ab..cc06af31cc 100644 --- a/app/Mail/Admin/EntityFailedSendObject.php +++ b/app/Mail/Admin/EntityFailedSendObject.php @@ -46,6 +46,31 @@ class EntityFailedSendObject { $this->invitation = $invitation; $this->entity_type = $entity_type; + + // Load relationships if they're not already loaded (e.g., when withoutRelations() was called) + if (!$invitation->relationLoaded('contact')) { + $invitation->load('contact'); + } + if (!$invitation->relationLoaded('company')) { + $invitation->load('company.account'); + } else { + // If company is loaded, ensure account is also loaded + if ($invitation->company && !$invitation->company->relationLoaded('account')) { + $invitation->company->load('account'); + } + } + if (!$invitation->relationLoaded($entity_type)) { + $invitation->load([$entity_type => function ($query) { + $query->with('client'); + }]); + } else { + // If entity is loaded, ensure client is also loaded + $entity = $invitation->{$entity_type}; + if ($entity && !$entity->relationLoaded('client')) { + $entity->load('client'); + } + } + $this->entity = $invitation->{$entity_type}; $this->contact = $invitation->contact; $this->company = $invitation->company; diff --git a/app/Repositories/ProjectRepository.php b/app/Repositories/ProjectRepository.php index b11c65b5b5..fdc6fb459d 100644 --- a/app/Repositories/ProjectRepository.php +++ b/app/Repositories/ProjectRepository.php @@ -21,13 +21,23 @@ use App\Models\Project; * Class for project repository. */ class ProjectRepository extends BaseRepository -{ +{ + /** + * Invoices a collection of projects into a single invoice. + * + * @param mixed $projects + * @return App\Models\Invoice + */ public function invoice(mixed $projects) { $_project = $projects->first(); $invoice = InvoiceFactory::create($_project->company_id, $_project->user_id); $invoice->client_id = $_project->client_id; + + if(count($projects) == 1) { + $invoice->project_id = $_project->id; + } // $invoice->project_id = $project->id; $lines = []; diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 7903205af7..5dbd159884 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -178,7 +178,7 @@ class ClientService $credits = Credit::withTrashed()->where('client_id', $this->client->id) ->where('is_deleted', false) ->where(function ($query) { - $query->whereDate('due_date', '<=', now()->format('Y-m-d')) + $query->where('due_date', '>=', now()->format('Y-m-d')) ->orWhereNull('due_date'); }) ->orderBy('created_at', 'ASC'); @@ -192,7 +192,7 @@ class ClientService ->where('is_deleted', false) ->where('balance', '>', 0) ->where(function ($query) { - $query->whereDate('due_date', '<=', now()->format('Y-m-d')) + $query->where('due_date', '>=', now()->format('Y-m-d')) ->orWhereNull('due_date'); }) ->orderBy('created_at', 'ASC')->get(); diff --git a/app/Services/EDocument/Standards/Peppol.php b/app/Services/EDocument/Standards/Peppol.php index 7197ff4ef2..1aaaf89730 100644 --- a/app/Services/EDocument/Standards/Peppol.php +++ b/app/Services/EDocument/Standards/Peppol.php @@ -1407,11 +1407,13 @@ class Peppol extends AbstractService $party->PartyIdentification[] = $pi; - $pts = new \InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme(); + //// If this is intracommunity supply, ensure that the country prefix is on the party tax scheme + $pts = new \InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme(); $companyID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CompanyID(); - $companyID->value = preg_replace("/[^a-zA-Z0-9]/", "", $this->invoice->client->vat_number); + $companyID->value = $this->ensureVatNumberPrefix($this->invoice->client->vat_number, $this->invoice->client->country->iso_3166_2); $pts->CompanyID = $companyID; + //// If this is intracommunity supply, ensure that the country prefix is on the party tax scheme $ts = new TaxScheme(); $id = new ID(); @@ -1512,8 +1514,13 @@ class Peppol extends AbstractService $location->Address = $address; $delivery->DeliveryLocation = $location; - if (isset($this->invoice->e_invoice->Invoice->Delivery[0]->ActualDeliveryDate->date)) { - $delivery->ActualDeliveryDate = new \DateTime($this->invoice->e_invoice->Invoice->Delivery[0]->ActualDeliveryDate->date); + // Safely extract delivery date using data_get to handle missing properties + $delivery_date = data_get($this->invoice->e_invoice, 'Invoice.Delivery.0.ActualDeliveryDate.date') + ?? data_get($this->invoice->e_invoice, 'Invoice.Delivery.0.ActualDeliveryDate') + ?? null; + + if ($delivery_date) { + $delivery->ActualDeliveryDate = new \DateTime($delivery_date); } return [$delivery]; @@ -1915,6 +1922,31 @@ class Peppol extends AbstractService return '0037'; } + /** + * Ensures the VAT number has the correct country code prefix. + * + * @param string $vatNumber The raw VAT number. + * @param string $countryCode The 2-letter ISO country code. + * @return string The formatted VAT number with prefix. + */ + private function ensureVatNumberPrefix(string $vatNumber, string $countryCode): string + { + // Handle Greece special case + $prefix = ($countryCode === 'GR') ? 'EL' : $countryCode; + + // Clean the VAT number by removing non-alphanumeric characters + $cleanedVat = preg_replace("/[^a-zA-Z0-9]/", "", $vatNumber); + + // Check if the VAT number already starts with the country prefix + // If it does, return it as-is (preserving any check digits like "AA" in "FRAA123456789") + if (str_starts_with(strtoupper($cleanedVat), strtoupper($prefix))) { + return $cleanedVat; + } + + // If the prefix is missing, clean and prepend it + return $prefix . $cleanedVat; + } + public function getErrors(): array { return $this->errors; diff --git a/app/Services/Scheduler/EmailReport.php b/app/Services/Scheduler/EmailReport.php index eded156c03..3aa60a3fc3 100644 --- a/app/Services/Scheduler/EmailReport.php +++ b/app/Services/Scheduler/EmailReport.php @@ -58,6 +58,10 @@ class EmailReport $start_end_dates = $this->calculateStartAndEndDates($this->scheduler->parameters, $this->scheduler->company); $data = $this->scheduler->parameters; + + if(!isset($data['user_id'])) { + $data['user_id'] = $this->scheduler->user_id; + } $data['start_date'] = $start_end_dates[0]; $data['end_date'] = $start_end_dates[1]; diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 6391d59e7c..77772bd60d 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -161,7 +161,7 @@ class TemplateService $this->twig->addFilter($filter); $allowedTags = ['if', 'for', 'set', 'filter']; - $allowedFilters = ['default', 'groupBy','capitalize', 'abs', 'date_modify', 'keys', 'join', 'reduce', 'format_date','json_decode','date_modify','trim','round','format_spellout_number','split', 'reduce','replace', 'escape', 'e', 'reverse', 'shuffle', 'slice', 'batch', 'title', 'sort', 'split', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format','nl2br','striptags','markdown_to_html']; + $allowedFilters = ['url_encode','default', 'groupBy','capitalize', 'abs', 'date_modify', 'keys', 'join', 'reduce', 'format_date','json_decode','date_modify','trim','round','format_spellout_number','split', 'reduce','replace', 'escape', 'e', 'reverse', 'shuffle', 'slice', 'batch', 'title', 'sort', 'split', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format','nl2br','striptags','markdown_to_html']; $allowedFunctions = ['range', 'cycle', 'constant', 'date','img','t']; $allowedProperties = ['type_id']; // $allowedMethods = ['img','t']; diff --git a/composer.lock b/composer.lock index 5c7413a6b6..277bf022c2 100644 --- a/composer.lock +++ b/composer.lock @@ -497,16 +497,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.15", + "version": "3.369.19", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7c62f41fb0460c3e5d5c1f70e93e726f1daa75f5" + "reference": "32fee3a25290186724ede9ca177d5090f7c5a837" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7c62f41fb0460c3e5d5c1f70e93e726f1daa75f5", - "reference": "7c62f41fb0460c3e5d5c1f70e93e726f1daa75f5", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/32fee3a25290186724ede9ca177d5090f7c5a837", + "reference": "32fee3a25290186724ede9ca177d5090f7c5a837", "shasum": "" }, "require": { @@ -588,9 +588,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.15" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.19" }, - "time": "2026-01-16T19:18:57+00:00" + "time": "2026-01-23T19:05:51+00:00" }, { "name": "babenkoivan/elastic-adapter", @@ -1179,16 +1179,16 @@ }, { "name": "btcpayserver/btcpayserver-greenfield-php", - "version": "v2.8.1", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/btcpayserver/btcpayserver-greenfield-php.git", - "reference": "3118f9e4e04590f53b2560866238af463153b2cf" + "reference": "60e6be57f9cd08dbe8f851d056358d1df9f07968" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/3118f9e4e04590f53b2560866238af463153b2cf", - "reference": "3118f9e4e04590f53b2560866238af463153b2cf", + "url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/60e6be57f9cd08dbe8f851d056358d1df9f07968", + "reference": "60e6be57f9cd08dbe8f851d056358d1df9f07968", "shasum": "" }, "require": { @@ -1227,9 +1227,9 @@ "description": "BTCPay Server Greenfield API PHP client library.", "support": { "issues": "https://github.com/btcpayserver/btcpayserver-greenfield-php/issues", - "source": "https://github.com/btcpayserver/btcpayserver-greenfield-php/tree/v2.8.1" + "source": "https://github.com/btcpayserver/btcpayserver-greenfield-php/tree/v2.9.0" }, - "time": "2024-11-22T16:34:09+00:00" + "time": "2026-01-21T11:31:48+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -2124,16 +2124,16 @@ }, { "name": "dompdf/php-font-lib", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", "shasum": "" }, "require": { @@ -2141,7 +2141,7 @@ "php": "^7.1 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -2163,9 +2163,9 @@ "homepage": "https://github.com/dompdf/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" }, - "time": "2024-12-02T14:37:59+00:00" + "time": "2026-01-20T14:10:26+00:00" }, { "name": "dompdf/php-svg-lib", @@ -3036,16 +3036,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.428.0", + "version": "v0.429.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "94a3c50a80a36cafb76e32fb76b8007e9f572deb" + "reference": "9dd334c8e6d3f25f91efcab86454c6bc0bc928c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/94a3c50a80a36cafb76e32fb76b8007e9f572deb", - "reference": "94a3c50a80a36cafb76e32fb76b8007e9f572deb", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9dd334c8e6d3f25f91efcab86454c6bc0bc928c1", + "reference": "9dd334c8e6d3f25f91efcab86454c6bc0bc928c1", "shasum": "" }, "require": { @@ -3074,9 +3074,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.428.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.429.0" }, - "time": "2026-01-12T00:58:26+00:00" + "time": "2026-01-19T01:08:26+00:00" }, { "name": "google/auth", @@ -5085,16 +5085,16 @@ }, { "name": "laravel/framework", - "version": "v11.47.0", + "version": "v11.48.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5" + "reference": "5b23ab29087dbcb13077e5c049c431ec4b82f236" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/86693ffa1ba32f56f8c44e31416c6665095a62c5", - "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5", + "url": "https://api.github.com/repos/laravel/framework/zipball/5b23ab29087dbcb13077e5c049c431ec4b82f236", + "reference": "5b23ab29087dbcb13077e5c049c431ec4b82f236", "shasum": "" }, "require": { @@ -5296,7 +5296,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-28T18:20:11+00:00" + "time": "2026-01-20T15:26:20+00:00" }, { "name": "laravel/octane", @@ -6273,16 +6273,16 @@ }, { "name": "league/flysystem", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { @@ -6350,22 +6350,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2025-11-10T17:13:11+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.30.1", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5" + "reference": "e36a2bc60b06332c92e4435047797ded352b446f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d286e896083bed3190574b8b088b557b59eb66f5", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/e36a2bc60b06332c92e4435047797ded352b446f", + "reference": "e36a2bc60b06332c92e4435047797ded352b446f", "shasum": "" }, "require": { @@ -6405,22 +6405,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.30.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.31.0" }, - "time": "2025-10-20T15:27:33+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/flysystem-local", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { @@ -6454,9 +6454,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-11-10T11:23:37+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/fractal", @@ -6844,16 +6844,16 @@ }, { "name": "livewire/livewire", - "version": "v3.7.4", + "version": "v3.7.6", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "5a8dffd4c0ab357ff7ed5b39e7c2453d962a68e0" + "reference": "276ac156f6ae414990784854a2673e3d23c68b24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/5a8dffd4c0ab357ff7ed5b39e7c2453d962a68e0", - "reference": "5a8dffd4c0ab357ff7ed5b39e7c2453d962a68e0", + "url": "https://api.github.com/repos/livewire/livewire/zipball/276ac156f6ae414990784854a2673e3d23c68b24", + "reference": "276ac156f6ae414990784854a2673e3d23c68b24", "shasum": "" }, "require": { @@ -6908,7 +6908,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.7.4" + "source": "https://github.com/livewire/livewire/tree/v3.7.6" }, "funding": [ { @@ -6916,7 +6916,7 @@ "type": "github" } ], - "time": "2026-01-13T09:37:21+00:00" + "time": "2026-01-23T05:41:38+00:00" }, { "name": "maennchen/zipstream-php", @@ -16978,16 +16978,16 @@ }, { "name": "twig/extra-bundle", - "version": "v3.22.2", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e" + "reference": "7a27e784dc56eddfef5e9295829b290ce06f1682" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", - "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/7a27e784dc56eddfef5e9295829b290ce06f1682", + "reference": "7a27e784dc56eddfef5e9295829b290ce06f1682", "shasum": "" }, "require": { @@ -17036,7 +17036,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.2" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.23.0" }, "funding": [ { @@ -17048,20 +17048,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T08:51:53+00:00" + "time": "2025-12-18T20:46:15+00:00" }, { "name": "twig/intl-extra", - "version": "v3.22.1", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1" + "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", - "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", + "reference": "32f15a38d45a8d0ec11bc8a3d97d3ac2a261499f", "shasum": "" }, "require": { @@ -17100,7 +17100,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.22.1" + "source": "https://github.com/twigphp/intl-extra/tree/v3.23.0" }, "funding": [ { @@ -17112,20 +17112,20 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2026-01-17T13:57:47+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.22.0", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb" + "reference": "faf069b259e2d3930c73c2f53e2dec8440bd90a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/fb6f952082e3a7d62a75c8be2c8c47242d3925fb", - "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/faf069b259e2d3930c73c2f53e2dec8440bd90a2", + "reference": "faf069b259e2d3930c73c2f53e2dec8440bd90a2", "shasum": "" }, "require": { @@ -17172,7 +17172,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.22.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.23.0" }, "funding": [ { @@ -17184,20 +17184,20 @@ "type": "tidelift" } ], - "time": "2025-09-15T05:57:37+00:00" + "time": "2025-12-02T14:45:16+00:00" }, { "name": "twig/twig", - "version": "v3.22.2", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2" + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2", - "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", "shasum": "" }, "require": { @@ -17251,7 +17251,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.22.2" + "source": "https://github.com/twigphp/Twig/tree/v3.23.0" }, "funding": [ { @@ -17263,7 +17263,7 @@ "type": "tidelift" } ], - "time": "2025-12-14T11:28:47+00:00" + "time": "2026-01-23T21:00:41+00:00" }, { "name": "twilio/sdk", @@ -17632,16 +17632,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.16.3", + "version": "v3.16.5", "source": { "type": "git", "url": "https://github.com/fruitcake/laravel-debugbar.git", - "reference": "c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e" + "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e", - "reference": "c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e", + "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/e85c0a8464da67e5b4a53a42796d46a43fc06c9a", + "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a", "shasum": "" }, "require": { @@ -17701,7 +17701,7 @@ ], "support": { "issues": "https://github.com/fruitcake/laravel-debugbar/issues", - "source": "https://github.com/fruitcake/laravel-debugbar/tree/v3.16.3" + "source": "https://github.com/fruitcake/laravel-debugbar/tree/v3.16.5" }, "funding": [ { @@ -17713,7 +17713,7 @@ "type": "github" } ], - "time": "2025-12-23T17:37:00+00:00" + "time": "2026-01-23T15:03:22+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -18411,16 +18411,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.92.5", + "version": "v3.93.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58" + "reference": "50895a07cface1385082e4caa6a6786c4e033468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58", - "reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/50895a07cface1385082e4caa6a6786c4e033468", + "reference": "50895a07cface1385082e4caa6a6786c4e033468", "shasum": "" }, "require": { @@ -18452,14 +18452,14 @@ }, "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.7", - "infection/infection": "^0.31", + "infection/infection": "^0.32", "justinrainbow/json-schema": "^6.6", "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.46", + "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48", "symfony/polyfill-php85": "^1.33", "symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0", "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0" @@ -18503,7 +18503,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.5" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.0" }, "funding": [ { @@ -18511,7 +18511,7 @@ "type": "github" } ], - "time": "2026-01-08T21:57:37+00:00" + "time": "2026-01-23T17:33:21+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -18607,7 +18607,7 @@ }, { "name": "illuminate/json-schema", - "version": "v12.47.0", + "version": "v12.48.1", "source": { "type": "git", "url": "https://github.com/illuminate/json-schema.git", @@ -21675,38 +21675,39 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.9.1", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "1baee07216d6748ebd3a65ba97381b051838707a" + "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a", - "reference": "1baee07216d6748ebd3a65ba97381b051838707a", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2abefdcca6074a9155f90b4ccb3345af8889d5f5", + "reference": "2abefdcca6074a9155f90b4ccb3345af8889d5f5", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^10.0|^11.0|^12.0", - "php": "^8.1", - "spatie/ignition": "^1.15", - "symfony/console": "^6.2.3|^7.0", - "symfony/var-dumper": "^6.2.3|^7.0" + "illuminate/support": "^11.0|^12.0", + "nesbot/carbon": "^2.72|^3.0", + "php": "^8.2", + "spatie/ignition": "^1.15.1", + "symfony/console": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "require-dev": { - "livewire/livewire": "^2.11|^3.3.5", - "mockery/mockery": "^1.5.1", - "openai-php/client": "^0.8.1|^0.10", - "orchestra/testbench": "8.22.3|^9.0|^10.0", - "pestphp/pest": "^2.34|^3.7", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", - "phpstan/phpstan-phpunit": "^1.3.16|^2.0", - "vlucas/phpdotenv": "^5.5" + "livewire/livewire": "^3.7.0|^4.0", + "mockery/mockery": "^1.6.12", + "openai-php/client": "^0.10.3", + "orchestra/testbench": "^v9.16.0|^10.6", + "pestphp/pest": "^3.7|^4.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.8", + "vlucas/phpdotenv": "^5.6.2" }, "suggest": { "openai-php/client": "Require get solutions from OpenAI", @@ -21762,7 +21763,7 @@ "type": "github" } ], - "time": "2025-02-20T13:13:55+00:00" + "time": "2026-01-20T13:16:11+00:00" }, { "name": "spaze/phpstan-stripe", diff --git a/config/ninja.php b/config/ninja.php index 55ff879f20..da8a268256 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -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.12.48'), - 'app_tag' => env('APP_TAG', '5.12.48'), + 'app_version' => env('APP_VERSION', '5.12.49'), + 'app_tag' => env('APP_TAG', '5.12.49'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index d717ecff38..80fe92c669 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -5689,6 +5689,7 @@ Développe automatiquement la section des notes dans le tableau de produits pour 'peppol_sending_failed' => 'Problème technique de livraison. Réessai impossible.', 'peppol_sending_success' => 'La facture électronique a été envoyée!', 'auto_generate' => 'Auto générer', + 'mollie_payment_pending' => 'Votre paiement est en attente. Veuillez patienter pendant son traitement. Nous vous enverrons un e-mail une fois qu\'il sera terminé.', ); return $lang; diff --git a/lang/vi/texts.php b/lang/vi/texts.php index 084a9e3cd4..dec5f6ec97 100644 --- a/lang/vi/texts.php +++ b/lang/vi/texts.php @@ -5690,6 +5690,7 @@ $lang = array( 'peppol_sending_failed' => 'Sự cố giao hàng kỹ thuật. Không thể thử lại', 'peppol_sending_success' => 'E- Hóa đơn gửi Thành công !', 'auto_generate' => 'Tự động tạo', + 'mollie_payment_pending' => 'Sự chi trả của bạn đang chờ xử lý. Vui lòng chờ đến xử lý. Chúng tôi sẽ email cho bạn khi hoàn tất.', ); return $lang; diff --git a/lang/zh_TW/texts.php b/lang/zh_TW/texts.php index a1cd23f1f7..f791e973d3 100644 --- a/lang/zh_TW/texts.php +++ b/lang/zh_TW/texts.php @@ -60,8 +60,8 @@ $lang = array( 'download_pdf' => '下載 PDF', 'pay_now' => '立即付款', 'save_invoice' => '儲存發票', - 'clone_invoice' => '克隆至發票', - 'archive_invoice' => '存檔發票', + 'clone_invoice' => '複製至發票', + 'archive_invoice' => '歸檔發票', 'delete_invoice' => '刪除發票', 'email_invoice' => '電郵發票', 'enter_payment' => '輸入付款', @@ -75,7 +75,7 @@ $lang = array( 'clients' => '客戶', 'invoices' => '發票', 'payments' => '付款', - 'credits' => '貸款', + 'credits' => '信用', 'history' => '歷史', 'search' => '搜尋', 'sign_up' => '報名', @@ -88,14 +88,14 @@ $lang = array( 'save' => '儲存', 'create' => '創造', 'upload' => '上傳', - 'import' => '進口', + 'import' => '匯入', 'download' => '下載', 'cancel' => '取消', 'close' => '關閉', 'provide_email' => '請提供有效的電子郵件地址', 'powered_by' => '供電', 'no_items' => '沒有項目', - 'recurring_invoices' => '定期發票', + 'recurring_invoices' => '週期發票', 'recurring_help' => '

每週、每兩個月、每月、每季或每年自動向客戶發送相同的發票。

使用:MONTH 、 :QUARTER或:YEAR取得動態日期。基本數學也同樣有效,例如:MONTH -1。

動態發票變數的範例:

@@ -104,7 +104,7 @@ $lang = array(
  • “ :YEAR +1 年度訂閱”>>“2015 年度訂閱”
  • “ :QUARTER +1 的預付款” >> “第二季的預付款”
  • ', - 'recurring_quotes' => '重複報價', + 'recurring_quotes' => '週期報價', 'in_total_revenue' => '總收入', 'billed_client' => '帳單客戶', 'billed_clients' => '向客戶開立帳單', @@ -113,20 +113,20 @@ $lang = array( 'invoices_past_due' => '發票逾期', 'upcoming_invoices' => '即將開立的發票', 'average_invoice' => '平均發票', - 'archive' => '檔案', + 'archive' => '歸檔', 'delete' => '刪除', - 'archive_client' => '存檔客戶端', + 'archive_client' => '歸檔客戶端', 'delete_client' => '刪除客戶端', - 'archive_payment' => '存檔付款', + 'archive_payment' => '歸檔付款', 'delete_payment' => '刪除付款', - 'archive_credit' => '檔案信用', + 'archive_credit' => '歸檔信用', 'delete_credit' => '刪除信用', 'show_archived_deleted' => '顯示已歸檔/已刪除', 'filter' => '篩選', 'new_client' => '新客戶', 'new_invoice' => '新發票', 'new_payment' => '輸入付款', - 'new_credit' => '輸入貸款資料', + 'new_credit' => '輸入信用資料', 'contact' => '聯絡人', 'date_created' => '建立日期', 'last_login' => '上次登入', @@ -142,19 +142,19 @@ $lang = array( 'method' => '方式', 'payment_amount' => '付款金額', 'payment_date' => '付款日期', - 'credit_amount' => '貸款金額', - 'credit_balance' => '貸款餘額', - 'credit_date' => '貸款日期', + 'credit_amount' => '信用金額', + 'credit_balance' => '信用餘額', + 'credit_date' => '信用日期', 'empty_table' => '資料表中無此資料', 'select' => '選擇', 'edit_client' => '編輯用戶', 'edit_invoice' => '編輯發票', 'create_invoice' => '建立發票', - 'enter_credit' => '輸入貸款資料', + 'enter_credit' => '輸入信用資料', 'last_logged_in' => '上次登入於', 'details' => '詳細資料', 'standing' => '資格', - 'credit' => '貸款', + 'credit' => '信用', 'activity' => '活動', 'date' => '日期', 'message' => '訊息', @@ -224,11 +224,11 @@ $lang = array( 'deleted_payment' => '刪除付款資料成功', 'deleted_payments' => '刪除 :count 筆付款資料成功', 'applied_payment' => '完成套用的付款資料', - 'created_credit' => '建立貸款資料完成', - 'archived_credit' => '歸檔貸款資料成功', - 'archived_credits' => '歸檔 :count 筆貸款資料成功', - 'deleted_credit' => '刪除貸款資料成功', - 'deleted_credits' => '刪除 :count 筆貸款資料成功', + 'created_credit' => '建立信用資料完成', + 'archived_credit' => '歸檔信用資料成功', + 'archived_credits' => '歸檔 :count 筆信用資料成功', + 'deleted_credit' => '刪除信用資料成功', + 'deleted_credits' => '刪除 :count 信用款資料成功', 'imported_file' => '匯入檔案成功', 'updated_vendor' => '更新供應商資料成功', 'created_vendor' => '建立供應商資料成功', @@ -311,7 +311,7 @@ $lang = array( 'quote_total' => '報價單總計', 'your_quote' => '您的報價單', 'total' => '總計', - 'clone' => '再製', + 'clone' => '複製', 'new_quote' => '新報價單', 'create_quote' => '建立報價單', 'edit_quote' => '編輯報價單', @@ -319,7 +319,7 @@ $lang = array( 'delete_quote' => '刪除報價單', 'save_quote' => '儲存報價單', 'email_quote' => '以電子郵件傳送報價單', - 'clone_quote' => '再製到報價單', + 'clone_quote' => '複製到報價單', 'convert_to_invoice' => '轉換至發票', 'view_invoice' => '檢視發票', 'view_client' => '檢視用戶資料', @@ -401,13 +401,13 @@ $lang = array( 'restore_invoice' => '復原發票', 'restore_quote' => '復原報價單', 'restore_client' => '復原用戶', - 'restore_credit' => '復原貸款資料', + 'restore_credit' => '復原信用資料', 'restore_payment' => '復原付款資料', 'restored_invoice' => '復原發票成功', 'restored_quote' => '復原報價單成功', 'restored_client' => '復原用戶資料成功', 'restored_payment' => '復原付款資料成功', - 'restored_credit' => '復原貸款資料成功', + 'restored_credit' => '復原信用資料成功', 'reason_for_canceling' => '告訴我們您為什麼離開,幫助我們改善我們的網站。', 'discount_percent' => '百分比', 'discount_amount' => '金額', @@ -742,10 +742,10 @@ $lang = array( 'activity_11' => ':user 已更新付款資料 :payment', 'activity_12' => ':user 已將付款資料 :payment 歸檔', 'activity_13' => ':user 已刪除付款資料 :payment', - 'activity_14' => ':user 已輸入貸款資料 :credit', - 'activity_15' => ':user 更新貸款 :credit', - 'activity_16' => ':user 已將 :credit 貸款資料歸檔', - 'activity_17' => ':user 已刪除 :credit 貸款資料', + 'activity_14' => ':user 已輸入信用資料 :credit', + 'activity_15' => ':user 更新信用 :credit', + 'activity_16' => ':user 已將 :credit 信用資料歸檔', + 'activity_17' => ':user 已刪除 :credit 信用資料', 'activity_18' => ':user 已建立報價單 :quote', 'activity_19' => ':user 已更新報價單 :quote', 'activity_20' => ':user emailed quote :quote for :client to :contact', @@ -756,7 +756,7 @@ $lang = array( 'activity_25' => ':user 已復原發票 :invoice', 'activity_26' => ':user 已復原用戶 :client 資料', 'activity_27' => ':user 已復原付款資料 :payment', - 'activity_28' => ':user 已復原 :credit 貸款資料', + 'activity_28' => ':user 已復原 :credit 信用資料', 'activity_29' => ':contact approved quote :quote for :client', 'activity_30' => ':user 已建立供應商 :vendor', 'activity_31' => ':user 已將供應商 :vendor 歸檔', @@ -793,14 +793,14 @@ $lang = array( 'quote_footer' => '報價單頁尾', 'free' => '免費', 'quote_is_approved' => '已獲同意', - 'apply_credit' => '套用貸款', + 'apply_credit' => '套用信用', 'system_settings' => '系統設定', 'archive_token' => '歸檔安全代碼', 'archived_token' => '歸檔安全代碼成功', 'archive_user' => '歸檔使用者資料', 'archived_user' => '歸檔使用者資料成功', 'archive_account_gateway' => '刪除閘道資料', - 'archived_account_gateway' => '封存閘道資料成功', + 'archived_account_gateway' => '歸檔閘道資料成功', 'archive_recurring_invoice' => '歸檔週期性發票', 'archived_recurring_invoice' => '歸檔週期性發票成功', 'delete_recurring_invoice' => '刪除週期性發票', @@ -833,7 +833,7 @@ $lang = array( 'task_file' => '任務檔案', 'no_mapper' => '此檔案的對應無效', 'invalid_csv_header' => '無效的 CSV 標頭', - 'client_portal' => '用戶門戶頁面', + 'client_portal' => '客戶端頁面', 'admin' => '管理者', 'disabled' => '已停用', 'show_archived_users' => '顯示歸檔的使用者資料', @@ -1005,7 +1005,7 @@ $lang = array( 'trial_call_to_action' => '開始免費試用', 'trial_success' => '啟用兩星期的專業版免費試用成功', 'overdue' => '逾期未付', - 'white_label_text' => '購買一年的白牌授權 $:price,從發票和用戶門戶頁面中移除 Invoice Ninja 品牌。', + 'white_label_text' => '購買一年的白牌授權 $:price,從發票和客戶端中移除 Invoice Ninja 品牌。', 'user_email_footer' => '欲調整您的電子郵件通知設定。請造訪 :link', 'reset_password_footer' => '若您未提出這項重設密碼的要求,請寫電子郵件給我們的客服: :email', 'limit_users' => '抱歉,這將超過 :limit 名使用者的限制', @@ -1033,7 +1033,7 @@ $lang = array( 'list_expenses' => '列出所有支出', 'list_recurring_invoices' => '列出週期性發票', 'list_payments' => '列出所有付款資料', - 'list_credits' => '列出貸款資料', + 'list_credits' => '列出信用資料', 'tax_name' => '稅名', 'report_settings' => '報告設定', 'new_user' => '新使用者', @@ -1116,10 +1116,10 @@ $lang = array( 'document_date' => '文件日期', 'document_size' => '大小', - 'enable_client_portal' => '用戶門戶頁面', - 'enable_client_portal_help' => '顯示/隱藏用戶門戶頁面。', + 'enable_client_portal' => '客戶端頁面', + 'enable_client_portal_help' => '顯示/隱藏客戶端。', 'enable_client_portal_dashboard' => '儀表板', - 'enable_client_portal_dashboard_help' => '在用戶門戶頁面顯示/隱藏儀表板頁。', + 'enable_client_portal_dashboard_help' => '在客戶端顯示/隱藏儀表板頁。', // Plans 'account_management' => '帳號管理', @@ -1162,8 +1162,8 @@ $lang = array( 'enterprise_plan_product' => '企業方案', 'enterprise_plan_year_description' => '訂用 Invoice Ninja 企業版一年方案。', 'enterprise_plan_month_description' => '訂用 Invoice Ninja 企業版一個月方案。', - 'plan_credit_product' => '貸款', - 'plan_credit_description' => '未使用時間的貸款', + 'plan_credit_product' => '信用', + 'plan_credit_description' => '未使用時間的信用', 'plan_pending_monthly' => '將於 :date 切換至月租型', 'plan_refunded' => '已發送一筆退款。', @@ -1387,7 +1387,7 @@ $lang = array( 'freq_two_years' => '兩年', // Payment types - 'payment_type_Apply Credit' => '套用貸款', + 'payment_type_Apply Credit' => '套用信用', 'payment_type_Bank Transfer' => '銀行轉帳', 'payment_type_Cash' => '現金', 'payment_type_Debit' => '簽帳卡', @@ -1740,8 +1740,8 @@ $lang = array( 'industry_Other' => '其他', 'industry_Photography' => '攝影', - 'view_client_portal' => '檢視用戶入口頁面', - 'view_portal' => '檢視入口頁面', + 'view_client_portal' => '檢視客戶端', + 'view_portal' => '檢視客戶端', 'vendor_contacts' => '供應商連絡人', 'all' => '全部', 'selected' => '已選的', @@ -1929,9 +1929,9 @@ $lang = array( 'deleted_product' => '已成功刪除產品資料', 'deleted_products' => '刪除 :count 筆產品資料成功', 'restored_product' => '復原產品資料成功', - 'update_credit' => '更新貸款資料', - 'updated_credit' => '更新貸款資料成功', - 'edit_credit' => '編輯貸款資料', + 'update_credit' => '更新信用資料', + 'updated_credit' => '更新信用資料成功', + 'edit_credit' => '編輯信用資料', 'realtime_preview' => 'Realtime Preview', 'realtime_preview_help' => 'Realtime refresh PDF preview on the invoice page when editing invoice.
    Disable this to improve performance when editing invoices.', 'live_preview_help' => 'Display a live PDF preview on the invoice page.', @@ -1940,7 +1940,7 @@ $lang = array( 'redirect_url' => '重新導向 URL', 'redirect_url_help' => '可選擇性地指定一個在付款完成後進行重新導向的網址。', 'save_draft' => '儲存草稿', - 'refunded_credit_payment' => '已退款之貸款支付', + 'refunded_credit_payment' => '已退款之信用支付', 'keyboard_shortcuts' => '鍵盤快速鍵', 'toggle_menu' => '切換選單', 'new_...' => '新增 ...', @@ -1998,7 +1998,7 @@ $lang = array( 'emailed_quotes' => '以電子郵件寄出報價單成功', 'website_url' => '網站網址', 'domain' => '網域', - 'domain_help' => '在用戶門戶頁面和傳送電子郵件時使用。', + 'domain_help' => '在客戶端和傳送電子郵件時使用。', 'domain_help_website' => '用於寄送電子郵件時。', 'import_invoices' => '匯入發票', 'new_report' => '新報告', @@ -2067,9 +2067,9 @@ $lang = array( 'updated_payment_term' => '更新付款條款成功', 'archived_payment_term' => '歸檔付款條款成功', 'resend_invite' => '重寄邀請函', - 'credit_created_by' => '由 :transaction_reference付款所建立的貸款', - 'created_payment_and_credit' => '已成功建立付款與貸款資料', - 'created_payment_and_credit_emailed_client' => '成功建立付款和貸款,並透過電子郵件傳送給用戶', + 'credit_created_by' => '由 :transaction_reference付款所建立的信用', + 'created_payment_and_credit' => '已成功建立付款與信用資料', + 'created_payment_and_credit_emailed_client' => '成功建立付款和信用,並透過電子郵件傳送給用戶', 'create_project' => '建立專案', 'create_vendor' => '建立供應商', 'create_expense_category' => '建立類別', @@ -2115,12 +2115,12 @@ $lang = array( 'fees_disabled_for_gateway' => '費用資料功能在此閘道停用。', 'logout_and_delete' => '登出/刪除帳號', 'tax_rate_type_help' => '選取專用稅率時調整行項目成本。
    只有專用稅率可以用作預設值。', - 'credit_note' => '貸款註記', + 'credit_note' => '信用註記', 'credit_issued_to' => '放款給', - 'credit_to' => '貸款給', - 'your_credit' => '您的貸款', - 'credit_number' => '貸款編號', - 'create_credit_note' => '建立貸款註記', + 'credit_to' => '信用給', + 'your_credit' => '您的信用', + 'credit_number' => '信用編號', + 'create_credit_note' => '建立信用註記', 'menu' => '選單', 'error_incorrect_gateway_ids' => '錯誤: 閘道表有錯誤的帳號名稱。', 'purge_data' => '清除資料', @@ -2148,7 +2148,7 @@ $lang = array( 'contact_fields' => '聯絡人欄位', 'custom_contact_fields_help' => '於建立聯絡人資料時增加欄位,且可選擇在 PDF 檔案顯示欄位名稱與值。', 'datatable_info' => '顯示 :total 個項目的 :start 至 :end 項', - 'credit_total' => '貸款總額', + 'credit_total' => '信用總額', 'mark_billable' => '標記計費', 'billed' => '已開立帳單', 'company_variables' => '公司變項', @@ -2215,14 +2215,14 @@ $lang = array( 'restore_recurring_expense' => '復原週期性支出', 'restored_recurring_expense' => '復原週期性支出成功', 'delete_recurring_expense' => '刪除週期性支出', - 'deleted_recurring_expense' => '已成功刪除經常性費用', + 'deleted_recurring_expense' => '已成功刪除週期性費用', 'view_recurring_expense' => '檢視週期性支出', 'taxes_and_fees' => '稅金與費用', 'import_failed' => '匯入失敗', 'recurring_prefix' => '用以標示週期性的前置符號', 'options' => '選項', - 'credit_number_help' => '設定一個前置符號或使用自訂型態,以動態地為欠款發票設定貸款號碼。', - 'next_credit_number' => '下一個貸款號碼是 :number。', + 'credit_number_help' => '設定一個前置符號或使用自訂型態,以動態地為欠款發票設定信用號碼。', + 'next_credit_number' => '下一個信用號碼是 :number。', 'padding_help' => '補齊數字所使用的零的個數。', 'import_warning_invalid_date' => '警告: 日期格式顯然無效。', 'product_notes' => '產品註記', @@ -2239,7 +2239,7 @@ $lang = array( 'downloaded_quote' => '將會寄出一封附有發票的 PDF 檔案之報價單', 'downloaded_invoices' => '將會寄出一封附有發票的 PDF 檔案之電子郵件', 'downloaded_quotes' => '將會寄出一封附有發票的 PDF 檔案之報價單', - 'clone_expense' => '再製', + 'clone_expense' => '複製', 'default_documents' => '預設的文件', 'send_email_to_client' => '向用戶傳送電子郵件', 'refund_subject' => '已辦理退款', @@ -2514,7 +2514,7 @@ $lang = array( 'show_shipping_address_help' => '需要使用者提供其送貨地址', 'ship_to_billing_address' => '寄送至帳單地址', 'delivery_note' => '寄送註記', - 'show_tasks_in_portal' => '在用戶門戶頁面顯示任務', + 'show_tasks_in_portal' => '在客戶端顯示任務', 'cancel_schedule' => '取消排程', 'scheduled_report' => '排程報告', 'scheduled_report_help' => '以電子郵件按照 :format 寄送 :report 報告給 :email', @@ -2566,14 +2566,14 @@ $lang = array( 'project_error_multiple_clients' => '專案不能屬於不同的用戶', 'invoice_project' => '發票專案', 'module_recurring_invoice' => '週期性發票', - 'module_credit' => '貸款', + 'module_credit' => '信用', 'module_quote' => '報價單與提案', 'module_task' => '任務與專案', 'module_expense' => '支出 & 供應商', 'module_ticket' => '票證', 'reminders' => '提醒通知', 'send_client_reminders' => '以電子郵件寄送提醒通知', - 'can_view_tasks' => '任務顯示於入口頁面', + 'can_view_tasks' => '任務顯示於客戶端', 'is_not_sent_reminders' => '提醒通知未寄送', 'promotion_footer' => '您的優惠即將到期,立即以 :link 進行升級。', 'unable_to_delete_primary' => '注意: 欲刪除這項公司資料,先刪除所有相連結的公司。', @@ -2600,7 +2600,7 @@ $lang = array( 'new_status' => '新的狀態', 'convert_products' => '轉換產品', 'convert_products_help' => '自動將產品價格轉換為用戶的貨幣', - 'improve_client_portal_link' => '設定子域名以縮短用戶門戶頁面連結。', + 'improve_client_portal_link' => '設定子域名以縮短客戶端連結。', 'budgeted_hours' => '列入預算的小時', 'progress' => '進度', 'view_project' => '檢視專案', @@ -2612,7 +2612,7 @@ $lang = array( 'expired_white_label' => '白牌授權已過期', 'return_to_login' => '回到登入頁面', 'convert_products_tip' => '注意: 加入名為「:name」的 :link 以查看匯率。', - 'amount_greater_than_balance' => '此金額大於發票餘額,一筆貸款將與剩餘金額一起建立。', + 'amount_greater_than_balance' => '此金額大於發票餘額,一筆信用將與剩餘金額一起建立。', 'custom_fields_tip' => '使用 Label|Option1,Option2 以顯示選取方塊。', 'client_information' => '用戶資訊', 'updated_client_details' => '更新用戶詳細資料成功', @@ -2687,7 +2687,7 @@ $lang = array( 'icon' => '圖示', 'proposal_not_found' => '無法提供查詢的提案', 'create_proposal_category' => '建立類別', - 'clone_proposal_template' => '再製範本', + 'clone_proposal_template' => '複製範本', 'proposal_email' => '提案的電子郵件', 'proposal_subject' => ':account 的新提案 :number', 'proposal_message' => '若要檢視您的 :amount 之提案,按一下以下連結。', @@ -2738,7 +2738,7 @@ $lang = array( 'invalid_url' => '無效的 URL', 'workflow_settings' => '工作流程設定', 'auto_email_invoice' => '自動電子郵件', - 'auto_email_invoice_help' => '建立後自動透過電子郵件發送定期發票。', + 'auto_email_invoice_help' => '建立後自動透過電子郵件發送週期發票。', 'auto_archive_invoice' => '自動歸檔', 'auto_archive_invoice_help' => '付款後自動存檔發票。', 'auto_archive_quote' => '自動歸檔', @@ -2753,11 +2753,11 @@ $lang = array( 'purge_client' => '清除用戶', 'purged_client' => '清除用戶成功', 'purge_client_warning' => '所有相關的紀錄 (發票、任務、支出、文件等等) 也將會刪除。', - 'clone_product' => '再製產品資料', + 'clone_product' => '複製產品資料', 'item_details' => '項目詳細資料', 'send_item_details_help' => '將單項產品的詳細資料傳送到付款主頁面。', 'view_proposal' => '檢視提案', - 'view_in_portal' => '在入口頁面檢視', + 'view_in_portal' => '在客戶端檢視', 'cookie_message' => '本網站使用 cookies 以確保您能在此得到最佳的使用經驗。', 'got_it' => '瞭解了!', 'vendor_will_create' => '將建立供應商', @@ -2960,8 +2960,8 @@ $lang = array( 'previous_year' => '上一年度', 'compare_to' => '比較', 'last_week' => '上個星期', - 'clone_to_invoice' => '再製到發票', - 'clone_to_quote' => '再製到報價單', + 'clone_to_invoice' => '複製到發票', + 'clone_to_quote' => '複製到報價單', 'convert' => '轉換', 'last7_days' => '最近 7 天', 'last30_days' => '最近 30 天', @@ -2997,7 +2997,7 @@ $lang = array( 'edit_document' => '編輯文件', 'uploaded_document' => '已成功上載文件', 'updated_document' => '已成功更新文件', - 'archived_document' => '已成功封存文件', + 'archived_document' => '已成功歸檔文件', 'deleted_document' => '已成功刪除文件', 'restored_document' => '已成功還原文件', 'no_history' => '無歷史記錄', @@ -3043,7 +3043,7 @@ $lang = array( 'number_counter' => '數字計數器', 'number_pattern' => '數字模式', 'custom_javascript' => '自訂 JavaScript', - 'portal_mode' => '入口網站模式', + 'portal_mode' => '客戶端模式', 'attach_pdf' => '附上PDF', 'attach_documents' => '附加文件', 'attach_ubl' => '附上 UBL/電子發票', @@ -3065,7 +3065,7 @@ $lang = array( 'edit_company_gateway' => '編輯閘道', 'created_company_gateway' => '建立閘道資料成功', 'updated_company_gateway' => '更新閘道資料成功', - 'archived_company_gateway' => '封存閘道資料成功', + 'archived_company_gateway' => '歸檔閘道資料成功', 'deleted_company_gateway' => '刪除閘道資料成功', 'restored_company_gateway' => '復原閘道成功', 'continue_editing' => '繼續編輯', @@ -3089,7 +3089,7 @@ $lang = array( 'edit_group' => '編輯群組', 'created_group' => '已成功建立群組', 'updated_group' => '已成功更新群組', - 'archived_group' => '已成功封存群組', + 'archived_group' => '已成功歸檔群組', 'deleted_group' => '已成功刪除群組', 'restored_group' => '已成功還原群組', 'upload_logo' => '上傳您的公司徽標', @@ -3188,7 +3188,7 @@ $lang = array( 'domain_url' => '網域網址', 'password_is_too_easy' => '密碼必須包含大寫字元和數字', 'client_portal_tasks' => '客戶端任務', - 'client_portal_dashboard' => '客戶入口網站儀表板', + 'client_portal_dashboard' => '客戶端儀表板', 'please_enter_a_value' => '請輸入一個值', 'deleted_logo' => '成功刪除標誌', 'generate_number' => '產生號碼', @@ -3290,7 +3290,7 @@ $lang = array( 'tax_rate1' => '稅率1', 'tax_rate2' => '稅率2', 'tax_rate3' => '稅率3', - 'archived_at' => '存檔於', + 'archived_at' => '歸檔於', 'has_expenses' => '有費用', 'custom_taxes1' => '關稅 1', 'custom_taxes2' => '關稅 2', @@ -3361,14 +3361,14 @@ $lang = array( 'slack_webhook_url' => 'Slack Webhook URL', 'partial_payment' => '部分付款', 'partial_payment_email' => '部分付款電子郵件', - 'clone_to_credit' => '克隆到信用', + 'clone_to_credit' => '複製到信用', 'emailed_credit' => '已成功透過電子郵件發送信用證', 'marked_credit_as_sent' => '已成功將信用標記為已發送', 'email_subject_payment_partial' => '電子郵件部分付款主題', 'is_approved' => '被批准', 'migration_went_wrong' => '哎呀!出事了!在開始遷移之前,請確保您已設定 Invoice Ninja v5 實例。', 'cross_migration_message' => '不允許跨帳戶遷移。請在此閱讀更多相關資訊: https ://invoiceninja.github.io/docs/migration/#troubleshooting', - 'email_credit' => '電子郵件信用', + 'email_credit' => '電郵信用', 'client_email_not_set' => '客戶沒有設定電子郵件地址', 'ledger' => '分類帳', 'view_pdf' => '查看PDF', @@ -3514,7 +3514,7 @@ $lang = array( 'removed_token' => '成功刪除令牌', 'restored_token' => '成功恢復令牌', 'client_registration' => '客戶註冊', - 'client_registration_help' => '使客戶能夠在入口網站中自行註冊', + 'client_registration_help' => '使客戶能夠在客戶端中自行註冊', 'customize_and_preview' => '客製化和預覽', 'search_document' => '搜尋 1 個文檔', 'search_design' => '搜尋 1 設計', @@ -3544,7 +3544,7 @@ $lang = array( 'add_documents_to_invoice_help' => '使文件對客戶可見', 'convert_currency_help' => '設定匯率', 'expense_settings' => '費用設定', - 'clone_to_recurring' => '克隆到重複', + 'clone_to_recurring' => '複製到重複', 'crypto' => '加密貨幣', 'user_field' => '使用者欄位', 'variables' => '變數', @@ -3556,9 +3556,9 @@ $lang = array( 'total_taxes' => '總稅金', 'line_taxes' => '行稅', 'total_fields' => '總字段數', - 'stopped_recurring_invoice' => '已成功停止定期發票', - 'started_recurring_invoice' => '已成功開始定期發票', - 'resumed_recurring_invoice' => '已成功恢復定期發票', + 'stopped_recurring_invoice' => '已成功停止週期發票', + 'started_recurring_invoice' => '已成功開始週期發票', + 'resumed_recurring_invoice' => '已成功恢復週期發票', 'gateway_refund' => '網關退款', 'gateway_refund_help' => '透過支付網關處理退款', 'due_date_days' => '到期日', @@ -3570,11 +3570,11 @@ $lang = array( 'endless' => '無盡', 'next_send_date' => '下次發送日期', 'remaining_cycles' => '剩餘週期', - 'created_recurring_invoice' => '已成功建立定期發票', - 'updated_recurring_invoice' => '已成功更新定期發票', + 'created_recurring_invoice' => '已成功建立週期發票', + 'updated_recurring_invoice' => '已成功更新週期發票', 'removed_recurring_invoice' => '已成功刪除定期發票', - 'search_recurring_invoice' => '搜尋 1 份經常性發票', - 'search_recurring_invoices' => '搜尋:count經常性發票', + 'search_recurring_invoice' => '搜尋 1 份週期性發票', + 'search_recurring_invoices' => '搜尋:count週期性發票', 'send_date' => '發送日期', 'auto_bill_on' => '自動計費開啟', 'minimum_under_payment_amount' => '最低付款金額', @@ -3649,8 +3649,8 @@ $lang = array( 'sidebar_editor' => '側邊欄編輯器', 'please_type_to_confirm' => '請輸入「 :value 」確認', 'purge' => '清除', - 'clone_to' => '克隆到', - 'clone_to_other' => '克隆到其他', + 'clone_to' => '複製到', + 'clone_to_other' => '複製到其他', 'labels' => '標籤', 'add_custom' => '新增自訂', 'payment_tax' => '繳稅', @@ -3659,26 +3659,26 @@ $lang = array( 'paid_invoices_are_locked' => '已付款發票已鎖定', 'source_code' => '原始碼', 'app_platforms' => '應用程式平台', - 'archived_task_statuses' => '已成功存檔:value任務狀態', + 'archived_task_statuses' => '已成功歸檔:value任務狀態', 'deleted_task_statuses' => '已成功刪除:value任務狀態', 'restored_task_statuses' => '成功恢復:value任務狀態', 'deleted_expense_categories' => '已成功刪除費用:value類別', 'restored_expense_categories' => '成功恢復費用:value類別', - 'archived_recurring_invoices' => '已成功存檔定期:value發票', - 'deleted_recurring_invoices' => '已成功刪除定期:value發票', - 'restored_recurring_invoices' => '已成功恢復定期:value發票', - 'archived_webhooks' => '已成功存檔:value webhooks', + 'archived_recurring_invoices' => '已成功歸檔週期:value發票', + 'deleted_recurring_invoices' => '已成功刪除週期:value發票', + 'restored_recurring_invoices' => '已成功恢復週期:value發票', + 'archived_webhooks' => '已成功歸檔:value webhooks', 'deleted_webhooks' => '已成功刪除:value webhooks', 'removed_webhooks' => '已成功刪除:value webhooks', 'restored_webhooks' => '已成功恢復:value webhooks', 'api_docs' => 'API文件', - 'archived_tokens' => '已成功存檔:value令牌', + 'archived_tokens' => '已成功歸檔:value令牌', 'deleted_tokens' => '已成功刪除:value令牌', 'restored_tokens' => '已成功恢復:value令牌', - 'archived_payment_terms' => '已成功存檔:value付款條件', + 'archived_payment_terms' => '已成功歸檔:value付款條件', 'deleted_payment_terms' => '已成功刪除:value付款條件', 'restored_payment_terms' => '已成功恢復:value付款條件', - 'archived_designs' => '成功存檔:value設計', + 'archived_designs' => '成功歸檔:value設計', 'deleted_designs' => '成功刪除:value設計', 'restored_designs' => '成功恢復:value設計', 'restored_credits' => '已成功恢復:value積分', @@ -3686,13 +3686,13 @@ $lang = array( 'deleted_users' => '已成功刪除:value用戶', 'removed_users' => '已成功刪除:value用戶', 'restored_users' => '成功恢復:value用戶', - 'archived_tax_rates' => '成功存檔:value稅率', + 'archived_tax_rates' => '成功歸檔:value稅率', 'deleted_tax_rates' => '已成功刪除:value稅率', 'restored_tax_rates' => '成功恢復:value稅率', - 'archived_company_gateways' => '已成功存檔:value網關', + 'archived_company_gateways' => '已成功歸檔:value網關', 'deleted_company_gateways' => '已成功刪除:value網關', 'restored_company_gateways' => '成功恢復:value網關', - 'archived_groups' => '已成功存檔:value組', + 'archived_groups' => '已成功歸檔:value組', 'deleted_groups' => '已成功刪除:value組', 'restored_groups' => '成功恢復:value組', 'archived_documents' => '已成功歸檔:value文檔', @@ -3799,10 +3799,10 @@ $lang = array( 'list_of_invoices' => '發票清單', 'with_selected' => '與選定的', 'invoice_still_unpaid' => '該發票仍未支付。點擊按鈕完成付款', - 'list_of_recurring_invoices' => '經常性發票清單', - 'details_of_recurring_invoice' => '以下是有關定期發票的一些詳細信息', + 'list_of_recurring_invoices' => '週期性發票清單', + 'details_of_recurring_invoice' => '以下是有關週期發票的一些詳細信息', 'cancellation' => '消除', - 'about_cancellation' => '如果您想停止定期發票,請點選請求取消。', + 'about_cancellation' => '如果您想停止週期發票,請點選請求取消。', 'cancellation_warning' => '警告!您請求取消此服務。您的服務可能會被取消,恕不另行通知。', 'cancellation_pending' => '取消待定,我們會聯絡您!', 'list_of_payments' => '付款清單', @@ -3880,7 +3880,7 @@ $lang = array( 'under_payments_disabled' => '本公司不支援少付款。', 'over_payments_disabled' => '本公司不支援超額付款。', 'saved_at' => '保存於:time', - 'credit_payment' => '貸記應用於發票:invoice _number', + 'credit_payment' => '信用應用於發票:invoice _number', 'credit_subject' => '來自:account的新信用:number', 'credit_message' => '要查看:amount的積分,請點擊下面的連結。', 'payment_type_Crypto' => '加密貨幣', @@ -3936,7 +3936,7 @@ $lang = array( 'max_refundable_credit' => '嘗試退款超過允許的信用額:credit ,最大可退款金額為:amount', 'project_client_do_not_match' => '專案客戶與實體客戶不匹配', 'quote_number_taken' => '報價單號碼已被佔用', - 'recurring_invoice_number_taken' => '經常性發票編號:number已使用', + 'recurring_invoice_number_taken' => '週期性發票編號:number已使用', 'user_not_associated_with_account' => '使用者未與此帳戶關聯', 'amounts_do_not_balance' => '金額未正確平衡。', 'insufficient_applied_amount_remaining' => '剩餘申請金額不足以支付付款。', @@ -3951,7 +3951,7 @@ $lang = array( 'large_account_update_parameter' => '如果沒有 update_at 參數,則無法載入大型帳戶', 'no_backup_exists' => '此活動不存在備份', 'company_user_not_found' => '未找到公司用戶記錄', - 'no_credits_found' => '沒有找到學分。', + 'no_credits_found' => '沒有找到信用。', 'action_unavailable' => '請求的操作:action不可用。', 'no_documents_found' => '沒有找到文件', 'no_group_settings_found' => '未找到組設定', @@ -3979,14 +3979,14 @@ $lang = array( 'start_multiselect' => '開始多選', 'email_sent_to_confirm_email' => '已發送一封電子郵件以確認電子郵件地址', 'converted_paid_to_date' => '轉換為付費日期', - 'converted_credit_balance' => '轉換後的貸方餘額', + 'converted_credit_balance' => '轉換後的信用方餘額', 'converted_total' => '換算總計', 'reply_to_name' => '回覆名稱', 'payment_status_-2' => '部分未應用', 'color_theme' => '顏色主題', 'start_migration' => '開始遷移', - 'recurring_cancellation_request' => ':contact請求取消定期發票', - 'recurring_cancellation_request_body' => '客戶:contact請求取消經常:client發票:invoice', + 'recurring_cancellation_request' => ':contact請求取消週期發票', + 'recurring_cancellation_request_body' => '客戶:contact請求取消週期:client發票:invoice', 'hello' => '你好', 'group_documents' => '集團文件', 'quote_approval_confirmation_label' => '您確定要批准此報價嗎?', @@ -4015,7 +4015,7 @@ $lang = array( 'billing_coupon_notice' => '您的折扣將在結帳時套用。', 'use_last_email' => '使用最後的電子郵件', 'activate_company' => '啟動公司', - 'activate_company_help' => '啟用電子郵件、定期發票和通知', + 'activate_company_help' => '啟用電子郵件、週期發票和通知', 'an_error_occurred_try_again' => '發生錯誤,請重試', 'please_first_set_a_password' => '請先設定密碼', 'changing_phone_disables_two_factor' => '警告:更改您的電話號碼將停用 2FA', @@ -4036,7 +4036,7 @@ $lang = array( 'invoice_task_datelog' => '發票任務日期日誌', 'invoice_task_datelog_help' => '將日期詳細資料新增至發票行項目', 'promo_code' => '促銷代碼', - 'recurring_invoice_issued_to' => '定期發票開立至', + 'recurring_invoice_issued_to' => '週期發票開立至', 'subscription' => '訂閱', 'new_subscription' => '新訂閱', 'deleted_subscription' => '已成功刪除訂閱', @@ -4056,7 +4056,7 @@ $lang = array( 'shared_invoice_credit_counter' => '共享發票/信用櫃檯', 'activity_80' => ':user建立訂閱:subscription', 'activity_81' => ':user更新訂閱:subscription', - 'activity_82' => ':user存檔訂閱:subscription', + 'activity_82' => ':user歸檔訂閱:subscription', 'activity_83' => ':user已刪除訂閱:subscription', 'activity_84' => ':user恢復訂閱:subscription', 'amount_greater_than_balance_v5' => '金額大於發票餘額。您不能多付發票費用。', @@ -4073,11 +4073,11 @@ $lang = array( 'migration_already_completed_desc' => '看起來您已經將:company _name遷移到 Invoice Ninja 的 V5 版本了。如果您想重新開始,可以強制遷移以擦除現有資料。', 'payment_method_cannot_be_authorized_first' => '完成第一筆交易後,可以儲存此付款方式以供將來使用。不要忘記在付款過程中檢查“商店詳細資料”。', 'new_account' => '新帳戶', - 'activity_100' => ':user創建了定期發票:recurring_invoice', - 'activity_101' => ':user更新的經常性發票:recurring_invoice', - 'activity_102' => ':user存檔的經常性發票:recurring_invoice', - 'activity_103' => ':user刪除定期發票:recurring_invoice', - 'activity_104' => ':user恢復定期發票:recurring_invoice', + 'activity_100' => ':user創建了週期發票:recurring_invoice', + 'activity_101' => ':user更新的週期性發票:recurring_invoice', + 'activity_102' => ':user歸檔的週期性發票:recurring_invoice', + 'activity_103' => ':user刪除週期發票:recurring_invoice', + 'activity_104' => ':user恢復週期發票:recurring_invoice', 'new_login_detected' => '偵測到您的帳戶有新的登入資訊。', 'new_login_description' => '您最近從新位置或裝置登入了您的 Invoice Ninja 帳戶:

    IP: :ip
    時間: :time
    電子郵件: :email', 'contact_details' => '聯絡方式', @@ -4107,7 +4107,7 @@ $lang = array( 'login_without_password' => '無需密碼登入', 'email_sent' => '當發票寄出後,以電子郵件通知我', 'one_time_purchases' => '一次性購買', - 'recurring_purchases' => '經常性購買', + 'recurring_purchases' => '週期性購買', 'you_might_be_interested_in_following' => '您可能對以下內容感興趣', 'quotes_with_status_sent_can_be_approved' => '只有狀態為「已發送」的報價才能獲得批准。過期報價無法獲得批准。', 'no_quotes_available_for_download' => '沒有可供下載的報價。', @@ -4126,7 +4126,7 @@ $lang = array( 'lang_Latvian' => '拉脫維亞語', 'expiry_date' => '到期日', 'cardholder_name' => '持卡人姓名', - 'recurring_quote_number_taken' => '重複報價編號:number已被佔用', + 'recurring_quote_number_taken' => '週期報價編號:number已被佔用', 'account_type' => '帳戶類型', 'locality' => '地點', 'checking' => '檢查', @@ -4154,7 +4154,7 @@ $lang = array( 'becs_mandate' => '提供您的銀行帳戶詳細信息,即表示您同意本直接借記請求和直接借記請求服務協議,並授權 Stripe Payments Australia Pty Ltd ACN 160 180 343 直接借記用戶 ID 號 507156(「Stripe」)透過代表:company (「商家」)的大量電子清算系統 (BECS) 處理商家單獨向您傳達的任何金額。您證明您是上述帳戶的帳戶持有人或授權簽署人。', 'you_need_to_accept_the_terms_before_proceeding' => '您需要先接受條款才能繼續。', 'direct_debit' => '直接借記', - 'clone_to_expense' => '克隆到費用', + 'clone_to_expense' => '複製到費用', 'checkout' => '查看', 'acss' => 'ACSS 金融卡', 'invalid_amount' => '金額無效。僅限數字/小數值。', @@ -4216,10 +4216,10 @@ $lang = array( 'for_best_performance' => '為了獲得最佳性能,請下載:app應用程式', 'bulk_email_invoice' => '電子郵件發票', 'bulk_email_quote' => '電子郵件報價', - 'bulk_email_credit' => '電子郵件信用', - 'removed_recurring_expense' => '成功消除經常性費用', - 'search_recurring_expense' => '搜尋經常性費用', - 'search_recurring_expenses' => '搜尋經常性費用', + 'bulk_email_credit' => '電郵信用', + 'removed_recurring_expense' => '成功消除週期性費用', + 'search_recurring_expense' => '搜尋週期性費用', + 'search_recurring_expenses' => '搜尋週期性費用', 'last_sent_date' => '最後發送日期', 'include_drafts' => '包括草稿', 'include_drafts_help' => '在報告中包含草稿記錄', @@ -4260,10 +4260,10 @@ $lang = array( 'free_trial_ends_in_days' => 'Pro 計畫試用期將於:count天後結束,點選升級。', 'free_trial_ends_today' => '今天是Pro計畫試用的最後一天,點選升級。', 'change_email' => '更改電子郵件', - 'client_portal_domain_hint' => '可以選擇配置單獨的客戶端入口網站網域', - 'tasks_shown_in_portal' => '門戶中顯示的任務', + 'client_portal_domain_hint' => '可以選擇配置單獨的客戶端網域', + 'tasks_shown_in_portal' => '客戶端中顯示的任務', 'uninvoiced' => '未開發票', - 'subdomain_guide' => '子網域在客戶端入口網站中用於個性化連結以匹配您的品牌。即,https://your-brand.invoicing.co', + 'subdomain_guide' => '子網域在客戶端中用於個性化連結以匹配您的品牌。即,https://your-brand.invoicing.co', 'send_time' => '發送時間', 'import_settings' => '導入設定', 'json_file_missing' => '請提供 JSON 文件', @@ -4273,7 +4273,7 @@ $lang = array( 'wait_for_data' => '請等待資料載入完成', 'net_total' => '淨總值', 'has_taxes' => '有稅', - 'import_customers' => '進口客戶', + 'import_customers' => '匯入客戶', 'imported_customers' => '成功開始導入客戶', 'login_success' => '登入成功', 'login_failure' => '登入失敗', @@ -4326,7 +4326,7 @@ $lang = array( 'rest_method' => '休息法', 'header_key' => '標題鍵', 'header_value' => '標頭值', - 'recurring_products' => '重複產品', + 'recurring_products' => '週期產品', 'promo_discount' => '促銷折扣', 'allow_cancellation' => '允許取消', 'per_seat_enabled' => '每個席位已啟用', @@ -4354,17 +4354,17 @@ $lang = array( 'load_color_theme' => '載入顏色主題', 'lang_Estonian' => '愛沙尼亞語', 'marked_credit_as_paid' => '已成功將信用標記為已付款', - 'marked_credits_as_paid' => '已成功將積分標記為已付款', + 'marked_credits_as_paid' => '已成功將信用標記為已付款', 'wait_for_loading' => '資料載入 - 請等待完成', 'wait_for_saving' => '資料保存 - 請等待完成', 'html_preview_warning' => '注意:此處所做的更改只能預覽,必須在上面的選項卡中應用才能保存', 'remaining' => '其餘的', 'invoice_paid' => '發票已付', - 'activity_120' => ':user創建經常性費用:recurring_expense', - 'activity_121' => ':user更新經常性費用:recurring_expense', - 'activity_122' => ':user存檔的經常性費用:recurring_expense', - 'activity_123' => ':user刪除經常性費用:recurring_expense', - 'activity_124' => ':user恢復經常性費用:recurring_expense', + 'activity_120' => ':user創建週期性費用:recurring_expense', + 'activity_121' => ':user更新週期性費用:recurring_expense', + 'activity_122' => ':user歸檔的週期性費用:recurring_expense', + 'activity_123' => ':user刪除週期性費用:recurring_expense', + 'activity_124' => ':user恢復週期性費用:recurring_expense', 'fpx' => "FPX", 'to_view_entity_set_password' => '要查看:entity您需要設定密碼。', 'unsubscribe' => '退訂', @@ -4389,11 +4389,11 @@ $lang = array( 'file_saved_in_downloads_folder' => '該文件已保存在下載資料夾中', 'small' => '小的', 'quotes_backup_subject' => '您的報價可供下載', - 'credits_backup_subject' => '您的積分已準備好下載', + 'credits_backup_subject' => '您的信用已準備好下載', 'document_download_subject' => '您的文件已可供下載', 'reminder_message' => ':balance的發票:number提醒', 'gmail_credentials_invalid_subject' => '使用 GMail 傳送無效憑證', - 'gmail_credentials_invalid_body' => '您的 GMail 憑證不正確,請登入管理員入口網站並導航至“設定”>“使用者詳細資料”,然後中斷並重新連接您的 GMail 帳戶。我們將每天向您發送此通知,直到此問題解決', + 'gmail_credentials_invalid_body' => '您的 GMail 憑證不正確,請登入管理員客戶端並導航至“設定”>“使用者詳細資料”,然後中斷並重新連接您的 GMail 帳戶。我們將每天向您發送此通知,直到此問題解決', 'total_columns' => '總計字段', 'view_task' => '查看任務', 'cancel_invoice' => '取消', @@ -4418,21 +4418,21 @@ $lang = array( 'signed_in_as' => '登入身份', 'total_results' => '總結果', 'restore_company_gateway' => '恢復網關', - 'archive_company_gateway' => '存檔網關', + 'archive_company_gateway' => '歸檔網關', 'delete_company_gateway' => '刪除網關', 'exchange_currency' => '貨幣兌換', 'tax_amount1' => '稅額1', 'tax_amount2' => '稅額2', 'tax_amount3' => '稅額3', 'update_project' => '更新項目', - 'auto_archive_invoice_cancelled' => '自動存檔已取消的發票', - 'auto_archive_invoice_cancelled_help' => '取消時自動存檔發票', + 'auto_archive_invoice_cancelled' => '自動歸檔已取消的發票', + 'auto_archive_invoice_cancelled_help' => '取消時自動歸檔發票', 'no_invoices_found' => '沒有找到發票', 'created_record' => '記錄創建成功', - 'auto_archive_paid_invoices' => '自動存檔付費', - 'auto_archive_paid_invoices_help' => '付款後自動存檔發票。', - 'auto_archive_cancelled_invoices' => '自動存檔已取消', - 'auto_archive_cancelled_invoices_help' => '取消時自動存檔發票。', + 'auto_archive_paid_invoices' => '自動歸檔付費', + 'auto_archive_paid_invoices_help' => '付款後自動歸檔發票。', + 'auto_archive_cancelled_invoices' => '自動歸檔已取消', + 'auto_archive_cancelled_invoices_help' => '取消時自動歸檔發票。', 'alternate_pdf_viewer' => '備用 PDF 檢視器', 'alternate_pdf_viewer_help' => '改進 PDF 預覽的滾動功能 [BETA]', 'currency_cayman_island_dollar' => '開曼群島元', @@ -4452,7 +4452,7 @@ $lang = array( 'export_format' => '導出格式', 'export_type' => '出口類型', 'stop_on_unpaid' => '停止未付款', - 'stop_on_unpaid_help' => '如果最後一張發票未付款,請停止建立定期發票。', + 'stop_on_unpaid_help' => '如果最後一張發票未付款,請停止建立週期發票。', 'use_quote_terms' => '使用報價條款', 'use_quote_terms_help' => '將報價單轉換為發票時', 'add_country' => '新增國家/地區', @@ -4510,7 +4510,7 @@ $lang = array( 'purchase_order_details' => '採購訂單詳細信息', 'qr_iban' => 'QR 圖碼 IBAN', 'besr_id' => 'BESR ID', - 'clone_to_purchase_order' => '克隆到 PO', + 'clone_to_purchase_order' => '複製到 PO', 'vendor_email_not_set' => '供應商沒有設定電子郵件地址', 'bulk_send_email' => '發電子郵件', 'marked_purchase_order_as_sent' => '已成功將採購訂單標記為已發送', @@ -4599,7 +4599,7 @@ $lang = array( 'total_pending_expenses' => '待處理費用', 'total_invoiced_expenses' => '已開立發票的費用', 'total_invoice_paid_expenses' => '發票支付費用', - 'vendor_portal' => '供應商入口網站', + 'vendor_portal' => '供應商客戶端', 'send_code' => '傳送代碼', 'save_to_upload_documents' => '儲存上傳文檔記錄', 'expense_tax_rates' => '費用稅率', @@ -4618,7 +4618,7 @@ $lang = array( 'bulk_email_purchase_orders' => '透過電子郵件發送採購訂單', 'bulk_email_invoices' => '電子郵件發票', 'bulk_email_quotes' => '電子郵件報價', - 'bulk_email_credits' => '電子郵件積分', + 'bulk_email_credits' => '電郵信用', 'archive_purchase_order' => '存檔採購訂單', 'restore_purchase_order' => '恢復採購訂單', 'delete_purchase_order' => '刪除採購訂單', @@ -4673,7 +4673,7 @@ $lang = array( 'edit_transaction' => '編輯交易', 'created_transaction' => '交易創建成功', 'updated_transaction' => '交易更新成功', - 'archived_transaction' => '已成功存檔交易', + 'archived_transaction' => '已成功歸檔交易', 'deleted_transaction' => '成功刪除交易', 'removed_transaction' => '成功刪除交易', 'restored_transaction' => '交易恢復成功', @@ -4688,9 +4688,11 @@ $lang = array( 'verify_phone_number_2fa_help' => '請驗證您的電話號碼以進行 2FA 備份', 'enable_applying_payments_later' => '啟用稍後申請付款', 'line_item_tax_rates' => '行項目稅率', - 'show_tasks_in_client_portal' => '在客戶端入口網站中顯示任務', + 'show_tasks_in_client_portal' => '在客戶端中顯示任務', 'notification_quote_expired_subject' => ':client的報價:invoice已過期', 'notification_quote_expired' => '以下針對客戶:client和:amount的報價:invoice現已過期。', + 'notification_invoice_overdue_subject' => 'Invoice :invoice is overdue for :client', + 'notification_invoice_overdue' => 'The following Invoice :invoice for client :client and :amount is now overdue.', 'auto_sync' => '自動同步', 'refresh_accounts' => '刷新帳戶', 'upgrade_to_connect_bank_account' => '升級至企業版以連結您的銀行帳戶', @@ -4714,7 +4716,7 @@ $lang = array( 'use_inventory_management' => '使用庫存管理', 'use_inventory_management_help' => '要求產品有庫存', 'optional_products' => '可選產品', - 'optional_recurring_products' => '可選的經常性產品', + 'optional_recurring_products' => '可選的週期性產品', 'convert_matched' => '轉變', 'auto_billed_invoice' => '已成功排隊發票以自動計費', 'auto_billed_invoices' => '已成功將發票排隊以自動計費', @@ -4735,7 +4737,7 @@ $lang = array( 'edit_transaction_rule' => '編輯交易規則', 'created_transaction_rule' => '規則創建成功', 'updated_transaction_rule' => '交易規則更新成功', - 'archived_transaction_rule' => '交易規則存檔成功', + 'archived_transaction_rule' => '交易規則歸檔成功', 'deleted_transaction_rule' => '成功刪除交易規則', 'removed_transaction_rule' => '成功刪除交易規則', 'restored_transaction_rule' => '交易規則恢復成功', @@ -4743,7 +4745,7 @@ $lang = array( 'search_transaction_rules' => '搜尋交易規則', 'payment_type_Interac E-Transfer' => 'Interac 電子轉賬', 'delete_bank_account' => '刪除銀行帳戶', - 'archive_transaction' => '存檔交易', + 'archive_transaction' => '歸檔交易', 'delete_transaction' => '刪除交易', 'otp_code_message' => '我們已將代碼發送至:email輸入此代碼以繼續。', 'otp_code_subject' => '您的一次性密碼', @@ -4775,14 +4777,14 @@ $lang = array( 'import_completed' => '導入完成', 'client_statement_body' => '附上您從:start _date 到:end _date 的聲明。', 'email_queued' => '電子郵件已排隊', - 'clone_to_recurring_invoice' => '克隆到定期發票', + 'clone_to_recurring_invoice' => '複製到週期發票', 'inventory_threshold' => '庫存閾值', 'emailed_statement' => '已成功排隊要傳送的語句', 'show_email_footer' => '顯示電子郵件頁腳', 'invoice_task_hours' => '發票任務時間', 'invoice_task_hours_help' => '將小時數加入發票行項目', 'auto_bill_standard_invoices' => '自動帳單標準發票', - 'auto_bill_recurring_invoices' => '自動開立定期發票', + 'auto_bill_recurring_invoices' => '自動開立週期發票', 'email_alignment' => '電子郵件對齊', 'pdf_preview_location' => 'PDF 預覽位置', 'mailgun' => '郵件槍', @@ -4814,7 +4816,7 @@ $lang = array( 'edit_schedule' => '編輯日程', 'created_schedule' => '已成功建立時間表', 'updated_schedule' => '已成功更新時間表', - 'archived_schedule' => '已成功存檔時間表', + 'archived_schedule' => '已成功歸檔時間表', 'deleted_schedule' => '已成功刪除行程', 'removed_schedule' => '已成功刪除行程', 'restored_schedule' => '已成功復原行程', @@ -4880,7 +4882,7 @@ $lang = array( 'record_not_found' => '找不到記錄', 'minimum_payment_amount' => '最低付款金額', 'client_initiated_payments' => '客戶發起的付款', - 'client_initiated_payments_help' => '支援在客戶入口網站中進行無發票付款', + 'client_initiated_payments_help' => '支援在客戶端中進行無發票付款', 'share_invoice_quote_columns' => '共享發票/報價欄', 'cc_email' => '副本電子郵件', 'payment_balance' => '付款餘額', @@ -4896,7 +4898,7 @@ $lang = array( 'edit_payment_link' => '編輯付款連結', 'created_payment_link' => '付款連結建立成功', 'updated_payment_link' => '付款連結已成功更新', - 'archived_payment_link' => '付款連結已成功存檔', + 'archived_payment_link' => '付款連結已成功歸檔', 'deleted_payment_link' => '已成功刪除付款鏈接', 'removed_payment_link' => '已成功刪除付款鏈接', 'restored_payment_link' => '付款連結已成功恢復', @@ -5014,7 +5016,7 @@ $lang = array( 'buy_price' => '購買價格', 'country_Macedonia' => '馬其頓', 'admin_initiated_payments' => '管理員發起付款', - 'admin_initiated_payments_help' => '支援在管理入口網站中輸入付款而無需發票', + 'admin_initiated_payments_help' => '支援在客戶端中輸入付款而無需發票', 'paid_date' => '支付日期', 'downloaded_entities' => '電子郵件將與 PDF 一起發送', 'lang_French - Swiss' => '法語 - 瑞士語', @@ -5053,7 +5055,7 @@ $lang = array( 'unlinked_transactions' => '已成功取消連結:count事務', 'unlinked_transaction' => '成功取消關聯交易', 'view_dashboard_permission' => '允許使用者存取儀表板,資料僅限於可用權限', - 'marked_sent_credits' => '已成功標記已發送的積分', + 'marked_sent_credits' => '已成功標記已發送的信用', 'show_document_preview' => '顯示文件預覽', 'cash_accounting' => '收付實現制會計', 'click_or_drop_files_here' => '點擊或將檔案拖放到此處', @@ -5202,7 +5204,7 @@ $lang = array( 'checkout_only_for_existing_customers' => '僅對現有客戶啟用結帳。請使用現有帳戶登入結帳。', 'checkout_only_for_new_customers' => '僅對新客戶啟用結帳功能。請註冊一個新帳戶以結帳。', 'auto_bill_standard_invoices_help' => '在到期日自動開立標準發票', - 'auto_bill_on_help' => '在發送日期或到期日自動計費(定期發票)', + 'auto_bill_on_help' => '在發送日期或到期日自動計費(週期發票)', 'use_available_credits_help' => '在透過付款方式收費之前,將所有貸方餘額應用於付款', 'use_unapplied_payments' => '使用未使用的付款', 'use_unapplied_payments_help' => '在透過付款方式收費之前應用所有付款餘額', @@ -5219,7 +5221,7 @@ $lang = array( 'all_payment_gateways' => '查看所有支付網關', 'product_cost' => '產品成本', 'duration_words' => '文字持續時間', - 'upcoming_recurring_invoices' => '即將開立的經常性發票', + 'upcoming_recurring_invoices' => '即將開立的週期性發票', 'shipping_country_id' => '運送國家', 'show_table_footer' => '顯示表頁腳', 'show_table_footer_help' => '在表格的頁尾中顯示總計', @@ -5410,7 +5412,7 @@ $lang = array( 'peppol_token_description' => '令牌用作確保發票安全發送的另一個步驟。與白標許可證不同,令牌可以隨時輪換,無需等待 Invoice Ninja 支援。', 'peppol_token_warning' => '您需要產生令牌才能繼續。', 'generate_token' => '產生令牌', - 'total_credits_amount' => '學分金額', + 'total_credits_amount' => '信用金額', 'sales_above_threshold' => '銷售額超過門檻', 'changing_vat_and_id_number_note' => 'PEPPOL 設定後,您將無法變更增值稅號或 ID 號。', 'iban_help' => '完整的 IBAN 號碼', @@ -5468,9 +5470,9 @@ $lang = array( 'configure' => '配置', 'new_identifier' => '新增值稅號', 'notification_credits_low' => '警告!您的信用餘額很低。', - 'notification_credits_low_text' => '請為您的帳戶添加積分,以避免服務中斷。', + 'notification_credits_low_text' => '請為您的帳戶添加信用,以避免服務中斷。', 'notification_no_credits' => '警告!您的信用餘額為空。', - 'notification_no_credits_text' => '請為您的帳戶添加積分,以避免服務中斷。', + 'notification_no_credits_text' => '請為您的帳戶添加信用,以避免服務中斷。', 'saved_comment' => '評論已儲存', 'acts_as_must_be_true' => '必須選擇「發送電子發票」或「接收電子發票」(或兩者)。', 'delete_identifier' => '刪除標識符', @@ -5635,7 +5637,6 @@ $lang = array( 'einvoice_received_subject' => 'E-Invoice/s Received', 'einvoice_received_body' => 'You have received :count new E-Invoice/s.

    Login to view.', 'download_files_too_large' => 'Some files were too large to attach directly to the email. Please use the links below to download these individually.', - 'restore_disabled_verifactu' => 'You cannot restore an invoice once it has been deleted', 'delete_disabled_verifactu' => 'You cannot delete an invoice once it has been cancelled or modified', 'rectify' => 'Rectificar', @@ -5644,10 +5645,6 @@ $lang = array( 'verifactu_cancellation_send_success' => 'Invoice cancellation for :invoice sent to AEAT successfully', 'verifactu_cancellation_send_failure' => 'Invoice cancellation for :invoice failed to send to AEAT :notes', 'verifactu' => 'Verifactu', - 'activity_150' => 'Account deleted :notes', - 'activity_151' => 'Client :notes merged into :client by :user', - 'activity_152' => 'Vendor :notes merged into :vendor by :user', - 'activity_153' => 'Client :notes purged by :user', 'justify' => 'Justify', 'outdent' => 'Outdent', 'indent' => 'Indent', @@ -5668,6 +5665,32 @@ $lang = array( 'use_legacy_editor_help' => 'Use the TinyMCE editor.', 'enable_e_invoice_received_notification' => 'Enable E-Invoice Received Notification', 'enable_e_invoice_received_notification_help' => 'Receive an email notification when a new E-Invoice is received.', + 'price_changes' => 'Plan Price Changes from January 1st 2026', + 'notification_quote_rejected_subject' => 'Quote :quote was rejected by :client', + 'notification_quote_rejected' => 'The following client :client rejected Quote :quote for :amount :notes.', + 'activity_150' => 'Account deleted :notes', + 'activity_151' => 'Client :notes merged into :client by :user', + 'activity_152' => 'Vendor :notes merged into :vendor by :user', + 'activity_153' => 'Client :notes purged by :user', + 'activity_154' => 'E-Invoice :invoice for :client sent to AEAT successfully', + 'activity_155' => 'E-Invoice :invoice for :client failed to send to AEAT :notes', + 'activity_156' => 'Invoice cancellation for :invoice sent to AEAT successfully', + 'activity_157' => 'Invoice cancellation for :invoice failed to send to AEAT :notes', + 'activity_158' => 'Quote :quote was rejected by :client :notes', + 'quotes_with_status_sent_can_be_rejected' => 'Only quotes with "Sent" status can be rejected.', + 'reject' => 'Reject', + 'rejected' => 'Rejected', + 'reject_quote' => 'Reject Quote', + 'reject_quote_confirmation' => 'Are you sure you want to reject this quote?', + 'reason' => 'Reason', + 'enter_reason' => 'Enter a reason...', + 'notification_invoice_overdue_summary_subject' => 'Invoice Overdue Summary: :date', + 'notification_invoice_overdue_summary' => 'The following invoices are overdue:', + 'purge_user_confirmation' => 'Warning! This action will reassign all entities to the account owner and permanently delete the user across all companies and accounts. Are you sure you want to proceed?', + 'peppol_sending_failed' => '技術交付問題。無法重試', + 'peppol_sending_success' => 'E-Invoice sent successfully!', + 'auto_generate' => 'Auto Generate', + 'mollie_payment_pending' => 'Your payment is pending. Please wait for it to be processed. We will email you when it is completed.', ); return $lang; diff --git a/routes/api.php b/routes/api.php index f55db58e6d..2ad89b0998 100644 --- a/routes/api.php +++ b/routes/api.php @@ -348,6 +348,7 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local Route::put('recurring_quotes/{recurring_quote}/upload', [RecurringQuoteController::class, 'upload']); Route::post('refresh', [LoginController::class, 'refresh'])->middleware('throttle:refresh'); + Route::post('refresh_react', [LoginController::class, 'refreshReact'])->middleware('throttle:refresh'); Route::post('reports/clients', ClientReportController::class)->middleware('throttle:20,1'); Route::post('reports/activities', ActivityReportController::class)->middleware('throttle:20,1'); diff --git a/tests/Feature/EInvoice/PeppolTest.php b/tests/Feature/EInvoice/PeppolTest.php index 635690fc49..27e16758ce 100644 --- a/tests/Feature/EInvoice/PeppolTest.php +++ b/tests/Feature/EInvoice/PeppolTest.php @@ -36,6 +36,7 @@ use App\Services\EDocument\Gateway\Storecove\Storecove; use Illuminate\Foundation\Testing\DatabaseTransactions; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronica; use App\Services\EDocument\Standards\Validation\Peppol\InvoiceLevel; +use App\Services\EDocument\Standards\Validation\Peppol\EntityLevel; use App\Services\EDocument\Standards\Validation\XsltDocumentValidator; use InvoiceNinja\EInvoice\Models\Peppol\BranchType\FinancialInstitutionBranch; use InvoiceNinja\EInvoice\Models\Peppol\FinancialAccountType\PayeeFinancialAccount; @@ -79,6 +80,7 @@ class PeppolTest extends TestCase $settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id; $settings->email = $this->faker->safeEmail(); $settings->currency_id = '3'; + $settings->e_invoice_type = 'PEPPOL'; // Required for validation endpoint to run EntityLevel validation $tax_data = new TaxModel(); $tax_data->regions->EU->has_sales_above_threshold = $params['over_threshold'] ?? false; @@ -581,20 +583,71 @@ class PeppolTest extends TestCase $client->city = ''; $client->save(); + // Reload the client to ensure changes are persisted + $client = $client->refresh(); + + // Direct EntityLevel test to debug validation + $entityLevel = new EntityLevel(); + $directResult = $entityLevel->checkClient($client); + + // Assert direct validation fails + $this->assertFalse($directResult['passes'], 'Direct EntityLevel validation should fail when address1 and city are empty'); + $this->assertNotEmpty($directResult['client'], 'Direct EntityLevel should have client validation errors'); + $data = [ 'entity' => 'clients', 'entity_id' => $client->hashed_id ]; + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->postJson('/api/v1/einvoice/validateEntity', $data); + // Log the response for debugging + $response->assertStatus(422); } + public function testEntityLevelDirectlyValidatesClientWithMissingAddress() + { + $scenario = [ + 'company_vat' => 'DE923356489', + 'company_country' => 'DE', + 'client_country' => 'FR', + 'client_vat' => 'FRAA123456789', + 'client_id_number' => '123456789', + 'classification' => 'business', + 'has_valid_vat' => true, + 'over_threshold' => true, + 'legal_entity_id' => 290868, + 'is_tax_exempt' => false, + ]; + + $entity_data = $this->setupTestData($scenario); + $client = $entity_data['client']; + + // Clear required address fields + $client->address1 = ''; + $client->city = ''; + $client->save(); + + // Directly instantiate and test EntityLevel + $entityLevel = new EntityLevel(); + $result = $entityLevel->checkClient($client); + + // Assert validation fails + $this->assertFalse($result['passes'], 'Validation should fail when address1 and city are empty'); + $this->assertNotEmpty($result['client'], 'Should have client validation errors'); + + // Check that address errors are present + $errorFields = array_column($result['client'], 'field'); + $this->assertContains('address1', $errorFields, 'Should have address1 error'); + $this->assertContains('city', $errorFields, 'Should have city error'); + } + public function testEntityValidationFailsForClientViaInvoice() { $scenario = [ diff --git a/tests/Feature/EInvoice/PeppolXmlValidationTest.php b/tests/Feature/EInvoice/PeppolXmlValidationTest.php new file mode 100644 index 0000000000..41c1a64ed1 --- /dev/null +++ b/tests/Feature/EInvoice/PeppolXmlValidationTest.php @@ -0,0 +1,378 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + INV-20xx-0001 + 2026-01-23 + 2026-02-23 + 380 + Autoliquidation Following art. + EUR + REF-12345/001/0001 + + REF-12345/001/0001 + + + Invoice_INV-20xx-0001.pdf + + + + BE0123456789 + + BE0123456789 + + + Example Company S.A. + + + Example Street 123 + Brussels + 1000 + + BE + + + + BE0123456789 + + VAT + + + + Example Company S.A. + + + John Doe + +31 2 123 45 67 + contact@example.com + + + + + + 987654321 + + 987654321 + + + Customer Company GmbH + + + Customer Street 456 + Berlin + 10115 + + DE + + + + DE987654321 + + VAT + + + + Customer Company GmbH + + + contact@customer.com + + + + + 2026-01-21 + + + + BE + + + + + + 1 + + + 30 Days + + + 0 + + 10000 + 0 + + K + 0 + vatex-eu-ic + Intra-Community supply + + VAT + + + + + + 10000 + 10000 + 10000 + 0 + 0 + 10000 + + + 1 + 10 + 1000 + + Service Support Package A + SVC-001 + + K + 0 + + VAT + + + + + 100 + + + + 2 + 5 + 500 + + Service Support Package B + SVC-002 + + K + 0 + + VAT + + + + + 100 + + + + 3 + 20 + 2000 + + Service Support Package C + SVC-003 + + K + 0 + + VAT + + + + + 100 + + + + 4 + 8 + 800 + + Service Support D + SVC-004 + + K + 0 + + VAT + + + + + 100 + + + + 5 + 8 + 800 + + Service Support Package E + SVC-005 + + K + 0 + + VAT + + + + + 100 + + + + 6 + 2 + 1000 + + Software License A + SW-001 + + K + 0 + + VAT + + + + + 500 + + + + 7 + 2 + 1000 + + Software License B + SW-002 + + K + 0 + + VAT + + + + + 500 + + + + 8 + 5 + 1000 + + Software License C + SW-003 + + K + 0 + + VAT + + + + + 200 + + + + 9 + 10 + 500 + + Additional Service Package + SVC-006 + + K + 0 + + VAT + + + + + 50 + + + + 10 + 2 + 500 + + Additional Feature Package + SVC-007 + + K + 0 + + VAT + + + + + 250 + + + + 11 + 3 + 900 + + Professional Services - Remote + PS-001 + + K + 0 + + VAT + + + + + 300 + + + +'; + + +public function setUp(): void +{ + parent::setUp(); + + try { + $processor = new \Saxon\SaxonProcessor(); + } catch (\Throwable $e) { + $this->markTestSkipped('saxon not installed'); + } + +} + +public function testPeppolXmlValidation() +{ + + try { + $processor = new \Saxon\SaxonProcessor(); + } catch (\Throwable $e) { + $this->markTestSkipped('saxon not installed'); + } + + $validator = new XsltDocumentValidator($this->xml); + $validator->validate(); + + if (count($validator->getErrors()) > 0) { + // nlog($this->xml); + nlog($validator->getErrors()); + } + + $this->assertCount(0, $validator->getErrors()); + } +} \ No newline at end of file diff --git a/tests/Unit/CreditBalanceTest.php b/tests/Unit/CreditBalanceTest.php index a36db69a7b..0bd8ca362c 100644 --- a/tests/Unit/CreditBalanceTest.php +++ b/tests/Unit/CreditBalanceTest.php @@ -43,6 +43,7 @@ class CreditBalanceTest extends TestCase 'company_id' => $this->company->id, 'client_id' => $this->client->id, 'balance' => 10, + 'due_date' => null, 'number' => 'testing-number-01', 'status_id' => Credit::STATUS_SENT, ]); @@ -50,6 +51,22 @@ class CreditBalanceTest extends TestCase $this->assertEquals($this->client->service()->getCreditBalance(), 10); } + public function testCreditBalance2() + { + $credit = Credit::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + 'balance' => 10, + 'due_date' => now()->addDays(10), + 'number' => 'testing-number-01', + 'status_id' => Credit::STATUS_SENT, + ]); + + $this->assertEquals($this->client->service()->getCreditBalance(), 10); + } + + public function testExpiredCreditBalance() { $credit = Credit::factory()->create([ @@ -57,7 +74,7 @@ class CreditBalanceTest extends TestCase 'company_id' => $this->company->id, 'client_id' => $this->client->id, 'balance' => 10, - 'due_date' => now()->addDays(5), + 'due_date' => now()->subDays(5), 'number' => 'testing-number-02', 'status_id' => Credit::STATUS_SENT, ]); diff --git a/tests/Unit/Storecove/DocumentSubmissionExtractUblTest.php b/tests/Unit/Storecove/DocumentSubmissionExtractUblTest.php new file mode 100644 index 0000000000..71ab63f232 --- /dev/null +++ b/tests/Unit/Storecove/DocumentSubmissionExtractUblTest.php @@ -0,0 +1,326 @@ +markTestSkipped('DocumentSubmission class does not exist'); + } + } + /** + * Test extracting CreditNote from StandardBusinessDocument wrapper + */ + public function testExtractCreditNoteFromSbdWrapper(): void + { + $xml = '1.00208:12345678900208:0987654321urn:oasis:names:specification:ubl:schema:xsd:CreditNote-22.1aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeeeCreditNote2026-01-22T15:53:41.44ZDOCUMENTIDurn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1busdox-docid-qnsPROCESSIDurn:fdc:peppol.eu:2017:poacc:billing:01:1.0cenbii-procid-ublCOUNTRY_C1BE + 2.1 + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + TEST/2026/0001 + 2026-01-22 + 381 + EUR + + TEST/2026/0001 + + + test20260001 + + + + 1234567890 + + 1234567890 + + + Test Supplier Company + + + 123 Test Street + Test City + 1000 + Test Region + + BE + + + + BE1234567890 + + VAT + + + + Test Supplier Company + 1234567890 + + + Test Contact + supplier@example.com + + + + + + 0987654321 + + Test Customer Company + + + 456 Customer Street + Customer City + 2000 + + BE + + + + BE0987654321 + + VAT + + + + Test Customer Company + + + 123456789 + customer@example.com + + + + + 30 + TEST/2026/0001 + + BE12345678901234 + TEST SUPPLIER COMPANY + + + + 0.00 + + 49.50 + 0.00 + + E + 0.0 + Exempt + + VAT + + + + + + 49.50 + 49.50 + 49.50 + 49.50 + + + 1 + 1.000000 + 22.00 + + Credit note on TEST/2025/0001 + DOMAIN .COM/.NET/.ORG + + E + 0.0 + + VAT + + + + + 22.00 + 1.0 + + + + 2 + 0.250000 + 27.50 + + Credit note on TEST/2025/0001 + PRESTATION DE SERVICES + + E + 0.0 + + VAT + + + + + 110.00 + 1.0 + + +'; + + $job = new DocumentSubmission([]); + $reflection = new ReflectionClass($job); + $method = $reflection->getMethod('extractInvoiceUbl'); + $method->setAccessible(true); + + $result = $method->invoke($job, $xml); + + // Assert that the result is valid XML + $this->assertNotEmpty($result); + + // Assert that the result contains CreditNote + $this->assertStringContainsString('assertStringContainsString('TEST/2026/0001', $result); + + // Assert that the result does NOT contain the SBD wrapper + $this->assertStringNotContainsString('StandardBusinessDocument', $result); + $this->assertStringNotContainsString('StandardBusinessDocumentHeader', $result); + + // Assert that the result is valid XML that can be parsed + $dom = new \DOMDocument(); + $this->assertTrue($dom->loadXML($result), 'Extracted XML should be valid'); + + // Assert that the root element is CreditNote + $this->assertEquals('CreditNote', $dom->documentElement->localName); + $this->assertEquals('urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2', $dom->documentElement->namespaceURI); + } + + /** + * Test extracting Invoice from StandardBusinessDocument wrapper + */ + public function testExtractInvoiceFromSbdWrapper(): void + { + $xml = '1.00208:07698670260208:0821894064urn:oasis:names:specification:ubl:schema:xsd:Invoice-22.1507dcfe6-7f6e-473a-bd20-f1c8dce2e2c8Invoice2026-01-22T15:53:41.44Z + 2.1 + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + INV/2026/0001 + 2026-01-22 + EUR +'; + + $job = new DocumentSubmission([]); + $reflection = new ReflectionClass($job); + $method = $reflection->getMethod('extractInvoiceUbl'); + $method->setAccessible(true); + + $result = $method->invoke($job, $xml); + + // Assert that the result is valid XML + $this->assertNotEmpty($result); + + // Assert that the result contains Invoice + $this->assertStringContainsString('assertStringContainsString('INV/2026/0001', $result); + + // Assert that the result does NOT contain the SBD wrapper + $this->assertStringNotContainsString('StandardBusinessDocument', $result); + + // Assert that the result is valid XML that can be parsed + $dom = new \DOMDocument(); + $this->assertTrue($dom->loadXML($result), 'Extracted XML should be valid'); + + // Assert that the root element is Invoice + $this->assertEquals('Invoice', $dom->documentElement->localName); + $this->assertEquals('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', $dom->documentElement->namespaceURI); + } + + /** + * Test that exception is thrown when neither Invoice nor CreditNote is found + */ + public function testThrowsExceptionWhenNoInvoiceOrCreditNoteFound(): void + { + $xml = '1.0Test'; + + $job = new DocumentSubmission([]); + $reflection = new ReflectionClass($job); + $method = $reflection->getMethod('extractInvoiceUbl'); + $method->setAccessible(true); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('No Invoice or CreditNote tag found in XML'); + + $method->invoke($job, $xml); + } + + /** + * Test that method handles XML without SBD wrapper (direct Invoice) + */ + public function testExtractDirectInvoiceWithoutWrapper(): void + { + $xml = ' + 2.1 + DIRECT/2026/0001 + 2026-01-22 +'; + + $job = new DocumentSubmission([]); + $reflection = new ReflectionClass($job); + $method = $reflection->getMethod('extractInvoiceUbl'); + $method->setAccessible(true); + + $result = $method->invoke($job, $xml); + + // Assert that the result is valid XML + $this->assertNotEmpty($result); + $this->assertStringContainsString('assertStringContainsString('DIRECT/2026/0001', $result); + + // Assert that the result is valid XML that can be parsed + $dom = new \DOMDocument(); + $this->assertTrue($dom->loadXML($result), 'Extracted XML should be valid'); + $this->assertEquals('Invoice', $dom->documentElement->localName); + } + + /** + * Test that method handles XML without SBD wrapper (direct CreditNote) + */ + public function testExtractDirectCreditNoteWithoutWrapper(): void + { + $xml = ' + 2.1 + CN/2026/0001 + 2026-01-22 +'; + + $job = new DocumentSubmission([]); + $reflection = new ReflectionClass($job); + $method = $reflection->getMethod('extractInvoiceUbl'); + $method->setAccessible(true); + + $result = $method->invoke($job, $xml); + + // Assert that the result is valid XML + $this->assertNotEmpty($result); + $this->assertStringContainsString('assertStringContainsString('CN/2026/0001', $result); + + // Assert that the result is valid XML that can be parsed + $dom = new \DOMDocument(); + $this->assertTrue($dom->loadXML($result), 'Extracted XML should be valid'); + $this->assertEquals('CreditNote', $dom->documentElement->localName); + } +}