diff --git a/app/Casts/ClientSyncCast.php b/app/Casts/ClientSyncCast.php new file mode 100644 index 000000000000..aaeb153680c2 --- /dev/null +++ b/app/Casts/ClientSyncCast.php @@ -0,0 +1,41 @@ +qb_id = $data['qb_id']; + + return $is; + } + + public function set($model, string $key, $value, array $attributes) + { + return [ + $key => json_encode([ + 'qb_id' => $value->qb_id, + ]) + ]; + } +} diff --git a/app/Casts/EncryptedCast.php b/app/Casts/EncryptedCast.php index a8c11f3ad397..5600fa7b7d52 100644 --- a/app/Casts/EncryptedCast.php +++ b/app/Casts/EncryptedCast.php @@ -4,7 +4,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/Casts/InvoiceSyncCast.php b/app/Casts/InvoiceSyncCast.php new file mode 100644 index 000000000000..713322a35181 --- /dev/null +++ b/app/Casts/InvoiceSyncCast.php @@ -0,0 +1,41 @@ +qb_id = $data['qb_id']; + + return $is; + } + + public function set($model, string $key, $value, array $attributes) + { + return [ + $key => json_encode([ + 'qb_id' => $value->qb_id, + ]) + ]; + } +} diff --git a/app/Casts/ProductSyncCast.php b/app/Casts/ProductSyncCast.php new file mode 100644 index 000000000000..e3c491015201 --- /dev/null +++ b/app/Casts/ProductSyncCast.php @@ -0,0 +1,41 @@ +qb_id = $data['qb_id']; + + return $ps; + } + + public function set($model, string $key, $value, array $attributes) + { + return [ + $key => json_encode([ + 'qb_id' => $value->qb_id, + ]) + ]; + } +} diff --git a/app/Casts/QuickbooksSettingsCast.php b/app/Casts/QuickbooksSettingsCast.php index a5027733f180..4de9333564bd 100644 --- a/app/Casts/QuickbooksSettingsCast.php +++ b/app/Casts/QuickbooksSettingsCast.php @@ -4,7 +4,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ @@ -18,33 +18,19 @@ class QuickbooksSettingsCast implements CastsAttributes { public function get($model, string $key, $value, array $attributes) { + if (is_null($value)) + return new QuickbooksSettings(); + $data = json_decode($value, true); - - if(!is_array($data)) - return null; - - $qb = new QuickbooksSettings(); - $qb->accessTokenKey = $data['accessTokenKey']; - $qb->refresh_token = $data['refresh_token']; - $qb->realmID = $data['realmID']; - $qb->accessTokenExpiresAt = $data['accessTokenExpiresAt']; - $qb->refreshTokenExpiresAt = $data['refreshTokenExpiresAt']; - $qb->settings = $data['settings'] ?? []; - - return $qb; + return QuickbooksSettings::fromArray($data); } public function set($model, string $key, $value, array $attributes) { - return [ - $key => json_encode([ - 'accessTokenKey' => $value->accessTokenKey, - 'refresh_token' => $value->refresh_token, - 'realmID' => $value->realmID, - 'accessTokenExpiresAt' => $value->accessTokenExpiresAt, - 'refreshTokenExpiresAt' => $value->refreshTokenExpiresAt, - 'settings' => $value->settings, - ]) - ]; + if ($value instanceof QuickbooksSettings) { + return json_encode(get_object_vars($value)); + } + + return json_encode($value); } } diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 65f74b2e49b8..05ec4984cb72 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -44,6 +44,7 @@ use App\Factory\ClientContactFactory; use App\Factory\VendorContactFactory; use App\Jobs\Company\CreateCompanyToken; use App\Models\RecurringInvoiceInvitation; +use App\Utils\Traits\CleanLineItems; use Symfony\Component\Console\Input\InputOption; /* @@ -80,10 +81,12 @@ Options: */ class CheckData extends Command { + use CleanLineItems; + /** * @var string */ - protected $signature = 'ninja:check-data {--database=} {--fix=} {--portal_url=} {--client_id=} {--vendor_id=} {--paid_to_date=} {--client_balance=} {--ledger_balance=} {--balance_status=} {--bank_transaction=}'; + protected $signature = 'ninja:check-data {--database=} {--fix=} {--portal_url=} {--client_id=} {--vendor_id=} {--paid_to_date=} {--client_balance=} {--ledger_balance=} {--balance_status=} {--bank_transaction=} {--line_items=}'; /** * @var string @@ -146,6 +149,10 @@ class CheckData extends Command $this->fixBankTransactions(); } + if($this->option('line_items')) { + $this->cleanInvoiceLineItems(); + } + $this->logMessage('Done: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE)); $this->logMessage('Total execution time in seconds: ' . (microtime(true) - $time_start)); @@ -1177,4 +1184,21 @@ class CheckData extends Command }); } + + public function cleanInvoiceLineItems() + { + Invoice::withTrashed()->cursor()->each(function ($invoice) { + $invoice->line_items = $this->cleanItems($invoice->line_items); + $invoice->saveQuietly(); + }); + + Credit::withTrashed()->cursor()->each(function ($invoice) { + $invoice->line_items = $this->cleanItems($invoice->line_items); + $invoice->saveQuietly(); + }); + + + } + + } diff --git a/app/DataMapper/ClientSync.php b/app/DataMapper/ClientSync.php new file mode 100644 index 000000000000..b45b2aca45a7 --- /dev/null +++ b/app/DataMapper/ClientSync.php @@ -0,0 +1,42 @@ +qb_id = $attributes['qb_id'] ?? ''; + } + /** + * Get the name of the caster class to use when casting from / to this cast target. + * + * @param array $arguments + */ + public static function castUsing(array $arguments): string + { + return ClientSyncCast::class; + } + + public static function fromArray(array $data): self + { + return new self($data); + } +} diff --git a/app/DataMapper/InvoiceSync.php b/app/DataMapper/InvoiceSync.php new file mode 100644 index 000000000000..b56b1a2239c2 --- /dev/null +++ b/app/DataMapper/InvoiceSync.php @@ -0,0 +1,43 @@ +qb_id = $attributes['qb_id'] ?? ''; + } + + /** + * Get the name of the caster class to use when casting from / to this cast target. + * + * @param array $arguments + */ + public static function castUsing(array $arguments): string + { + return InvoiceSyncCast::class; + } + + public static function fromArray(array $data): self + { + return new self($data); + } +} diff --git a/app/DataMapper/ProductSync.php b/app/DataMapper/ProductSync.php new file mode 100644 index 000000000000..dfc6aada909f --- /dev/null +++ b/app/DataMapper/ProductSync.php @@ -0,0 +1,43 @@ +qb_id = $attributes['qb_id'] ?? ''; + } + + /** + * Get the name of the caster class to use when casting from / to this cast target. + * + * @param array $arguments + */ + public static function castUsing(array $arguments): string + { + return ProductSyncCast::class; + } + + public static function fromArray(array $data): self + { + return new self($data); + } +} diff --git a/app/DataMapper/QuickbooksSettings.php b/app/DataMapper/QuickbooksSettings.php index 1442914e4b5f..87e0805f8b4b 100644 --- a/app/DataMapper/QuickbooksSettings.php +++ b/app/DataMapper/QuickbooksSettings.php @@ -30,34 +30,28 @@ class QuickbooksSettings implements Castable public int $refreshTokenExpiresAt; public string $baseURL; - /** - * entity client,invoice,quote,purchase_order,vendor,payment - * sync true/false - * update_record true/false - * direction push/pull/birectional - * */ - public array $settings = [ - 'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'sales' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - 'expense' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'], - ]; + + public QuickbooksSync $settings; + public function __construct(array $attributes = []) + { + $this->accessTokenKey = $attributes['accessTokenKey'] ?? ''; + $this->refresh_token = $attributes['refresh_token'] ?? ''; + $this->realmID = $attributes['realmID'] ?? ''; + $this->accessTokenExpiresAt = $attributes['accessTokenExpiresAt'] ?? 0; + $this->refreshTokenExpiresAt = $attributes['refreshTokenExpiresAt'] ?? 0; + $this->baseURL = $attributes['baseURL'] ?? ''; + $this->settings = new QuickbooksSync($attributes['settings'] ?? []); + } - /** - * Get the name of the caster class to use when casting from / to this cast target. - * - * @param array $arguments - */ public static function castUsing(array $arguments): string { return QuickbooksSettingsCast::class; } + public static function fromArray(array $data): self + { + return new self($data); + } + } diff --git a/app/DataMapper/QuickbooksSync.php b/app/DataMapper/QuickbooksSync.php new file mode 100644 index 000000000000..c27fc86ab38c --- /dev/null +++ b/app/DataMapper/QuickbooksSync.php @@ -0,0 +1,55 @@ +client = new QuickbooksSyncMap($attributes['client'] ?? []); + $this->vendor = new QuickbooksSyncMap($attributes['vendor'] ?? []); + $this->invoice = new QuickbooksSyncMap($attributes['invoice'] ?? []); + $this->sales = new QuickbooksSyncMap($attributes['sales'] ?? []); + $this->quote = new QuickbooksSyncMap($attributes['quote'] ?? []); + $this->purchase_order = new QuickbooksSyncMap($attributes['purchase_order'] ?? []); + $this->product = new QuickbooksSyncMap($attributes['product'] ?? []); + $this->payment = new QuickbooksSyncMap($attributes['payment'] ?? []); + $this->expense = new QuickbooksSyncMap($attributes['expense'] ?? []); + $this->default_income_account = $attributes['default_income_account'] ?? ''; + $this->default_expense_account = $attributes['default_expense_account'] ?? ''; + } +} \ No newline at end of file diff --git a/app/DataMapper/QuickbooksSyncMap.php b/app/DataMapper/QuickbooksSyncMap.php new file mode 100644 index 000000000000..800b2dfea633 --- /dev/null +++ b/app/DataMapper/QuickbooksSyncMap.php @@ -0,0 +1,30 @@ +direction = isset($attributes['direction']) + ? SyncDirection::from($attributes['direction']) + : SyncDirection::BIDIRECTIONAL; + + } +} diff --git a/app/DataMapper/Tax/TaxModel.php b/app/DataMapper/Tax/TaxModel.php index 42d027adf99e..cbddf270c536 100644 --- a/app/DataMapper/Tax/TaxModel.php +++ b/app/DataMapper/Tax/TaxModel.php @@ -35,6 +35,10 @@ class TaxModel $this->regions = $this->init(); } else { + if(!$model->seller_subregion) { + $this->seller_subregion = ''; + } + //@phpstan-ignore-next-line foreach($model as $key => $value) { $this->{$key} = $value; diff --git a/app/Enum/SyncDirection.php b/app/Enum/SyncDirection.php new file mode 100644 index 000000000000..537f32b9633b --- /dev/null +++ b/app/Enum/SyncDirection.php @@ -0,0 +1,19 @@ +invoice = $invoice; - $this->payment = $payment; - $this->company = $company; - $this->event_vars = $event_vars; } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 878721fbc88e..cf8d2cd44411 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -11,32 +11,33 @@ namespace App\Exceptions; -use App\Utils\Ninja; -use Aws\Exception\CredentialsException; -use GuzzleHttp\Exception\ConnectException; -use Illuminate\Auth\Access\AuthorizationException; -use Illuminate\Auth\AuthenticationException; -use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; -use Illuminate\Database\Eloquent\RelationNotFoundException; -use Illuminate\Encryption\MissingAppKeyException; -use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; -use Illuminate\Http\Exceptions\ThrottleRequestsException; -use Illuminate\Http\Request; -use Illuminate\Queue\MaxAttemptsExceededException; -use Illuminate\Session\TokenMismatchException; -use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Schema; -use Illuminate\Validation\ValidationException; -use InvalidArgumentException; -use League\Flysystem\UnableToCreateDirectory; -use PDOException; -use Sentry\Laravel\Integration; -use Sentry\State\Scope; -use Symfony\Component\Console\Exception\CommandNotFoundException; -use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Process\Exception\RuntimeException; use Throwable; +use PDOException; +use App\Utils\Ninja; +use Sentry\State\Scope; +use Illuminate\Support\Arr; +use Illuminate\Http\Request; +use InvalidArgumentException; +use Sentry\Laravel\Integration; +use Illuminate\Support\Facades\Schema; +use Aws\Exception\CredentialsException; +use Illuminate\Database\QueryException; +use GuzzleHttp\Exception\ConnectException; +use Illuminate\Auth\AuthenticationException; +use League\Flysystem\UnableToCreateDirectory; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Validation\ValidationException; +use Illuminate\Encryption\MissingAppKeyException; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Queue\MaxAttemptsExceededException; +use Illuminate\Http\Exceptions\ThrottleRequestsException; +use Symfony\Component\Process\Exception\RuntimeException; +use Illuminate\Database\Eloquent\RelationNotFoundException; +use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; class Handler extends ExceptionHandler { @@ -52,6 +53,7 @@ class Handler extends ExceptionHandler ValidationException::class, // ModelNotFoundException::class, NotFoundHttpException::class, + RelationNotFoundException::class, ]; protected $selfHostDontReport = [ @@ -65,6 +67,8 @@ class Handler extends ExceptionHandler RuntimeException::class, InvalidArgumentException::class, CredentialsException::class, + RelationNotFoundException::class, + QueryException::class, ]; protected $hostedDontReport = [ @@ -73,6 +77,7 @@ class Handler extends ExceptionHandler ValidationException::class, ModelNotFoundException::class, NotFoundHttpException::class, + RelationNotFoundException::class, ]; /** diff --git a/app/Filters/BankTransactionFilters.php b/app/Filters/BankTransactionFilters.php index 2471f98aaa25..1e05a6d03741 100644 --- a/app/Filters/BankTransactionFilters.php +++ b/app/Filters/BankTransactionFilters.php @@ -68,7 +68,7 @@ class BankTransactionFilters extends QueryFilters */ public function client_status(string $value = ''): Builder { - if (strlen($value ?? '') == 0) { + if (strlen($value) == 0) { return $this->builder; } diff --git a/app/Helpers/Mail/Office365MailTransport.php b/app/Helpers/Mail/Office365MailTransport.php index 4a6f49a70b65..6d345f03968b 100644 --- a/app/Helpers/Mail/Office365MailTransport.php +++ b/app/Helpers/Mail/Office365MailTransport.php @@ -4,7 +4,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 552374bfd2bc..5d25533decc2 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -57,6 +57,7 @@ class CompanyGatewayController extends BaseController private string $forte_key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs'; + private string $cbapowerboard_key = 'b67581d804dbad1743b61c57285142ad'; /** * CompanyGatewayController constructor. @@ -227,17 +228,35 @@ class CompanyGatewayController extends BaseController ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db); - if (in_array($company_gateway->gateway_key, $this->stripe_keys)) { - StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); - } elseif($company_gateway->gateway_key == $this->checkout_key) { - CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); - } elseif($company_gateway->gateway_key == $this->forte_key) { + switch ($company_gateway->gateway_key) { + case in_array($company_gateway->gateway_key, $this->stripe_keys): + StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); + break; - dispatch(function () use ($company_gateway) { - MultiDB::setDb($company_gateway->company->db); - $company_gateway->driver()->updateFees(); - })->afterResponse(); + case $this->checkout_key: + CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); + break; + case $this->forte_key: + dispatch(function () use ($company_gateway) { + MultiDB::setDb($company_gateway->company->db); + $company_gateway->driver()->updateFees(); + })->afterResponse(); + + break; + + case $this->cbapowerboard_key: + dispatch(function () use ($company_gateway) { + MultiDB::setDb($company_gateway->company->db); + $company_gateway->driver()->init()->settings()->updateSettings(); + })->afterResponse(); + + break; + + default: + # code... + break; + } return $this->itemResponse($company_gateway); diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index e1d75589e3ea..3b3cf53b21eb 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -19,28 +19,6 @@ use App\Services\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - // private array $import_entities = [ - // 'client' => 'Customer', - // 'invoice' => 'Invoice', - // 'product' => 'Item', - // 'payment' => 'Payment' - // ]; - - public function onAuthorized(AuthorizedQuickbooksRequest $request) - { - - MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); - $company = $request->getCompany(); - $qb = new QuickbooksService($company); - - $realm = $request->query('realmId'); - $access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm); - $qb->sdk()->saveOAuthToken($access_token_object); - - return redirect(config('ninja.react_url')); - - } - /** * Determine if the user is authorized to make this request. * @@ -59,5 +37,22 @@ class ImportQuickbooksController extends BaseController return redirect()->to($authorizationUrl); } + + public function onAuthorized(AuthorizedQuickbooksRequest $request) + { + + MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); + $company = $request->getCompany(); + $qb = new QuickbooksService($company); + + $realm = $request->query('realmId'); + $access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm); + $qb->sdk()->saveOAuthToken($access_token_object); + + return redirect(config('ninja.react_url')); + + } + + } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index f4d842843c6b..443f5bfc3218 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -11,10 +11,12 @@ namespace App\Http\Controllers; -use App\Http\Requests\Search\GenericSearchRequest; +use App\Models\User; +use App\Utils\Ninja; use App\Models\Client; use App\Models\Invoice; -use App\Models\User; +use Elastic\Elasticsearch\ClientBuilder; +use App\Http\Requests\Search\GenericSearchRequest; class SearchController extends Controller { @@ -26,6 +28,14 @@ class SearchController extends Controller public function __invoke(GenericSearchRequest $request) { + if(config('scount.driver') == 'elastic' && $request->has('search') && $request->input('search') !== '') { + try{ + return $this->search($request->input('search', '')); + } catch(\Exception $e) { + nlog("elk down?"); + } + } + /** @var \App\Models\User $user */ $user = auth()->user(); @@ -41,6 +51,96 @@ class SearchController extends Controller } + public function search(string $search) + { + $user = auth()->user(); + $company = $user->company(); + + $elastic = ClientBuilder::fromConfig(config('elastic.client.connections.default')); + + $params = [ + 'index' => 'clients,invoices,client_contacts', + 'body' => [ + 'query' => [ + 'bool' => [ + 'must' => [ + 'multi_match' => [ + 'query' => $search, + 'fields' => ['*'], + 'fuzziness' => 'AUTO', + ], + ], + 'filter' => [ + 'match' => [ + 'company_key' => $company->company_key, + ], + ], + ], + ], + 'size' => 1000, + ], + ]; + + $results = $elastic->search($params); + + $this->mapResults($results['hits']['hits'] ?? []); + + return response()->json([ + 'clients' => $this->clients, + 'client_contacts' => $this->client_contacts, + 'invoices' => $this->invoices, + 'settings' => $this->settingsMap(), + ], 200); + + } + + private function mapResults(array $results) + { + + foreach($results as $result) { + switch($result['_index']) { + case 'clients': + + if($result['_source']['is_deleted']) //do not return deleted results + break; + + $this->clients[] = [ + 'name' => $result['_source']['name'], + 'type' => '/client', + 'id' => $result['_source']['hashed_id'], + 'path' => "/clients/{$result['_source']['hashed_id']}" + ]; + + break; + case 'invoices': + + if ($result['_source']['is_deleted']) //do not return deleted invoices + break; + + + $this->invoices[] = [ + 'name' => $result['_source']['name'], + 'type' => '/invoice', + 'id' => $result['_source']['hashed_id'], + 'path' => "/invoices/{$result['_source']['hashed_id']}/edit" + ]; + break; + case 'client_contacts': + + if($result['_source']['__soft_deleted']) // do not return deleted contacts + break; + + $this->client_contacts[] = [ + 'name' => $result['_source']['name'], + 'type' => '/client', + 'id' => $result['_source']['hashed_id'], + 'path' => "/clients/{$result['_source']['hashed_id']}" + ]; + break; + } + } + } + private function clientMap(User $user) { @@ -81,20 +181,14 @@ class SearchController extends Controller $invoices = Invoice::query() ->company() ->with('client') - ->where('invoices.is_deleted', 0) - // ->whereHas('client', function ($q) { - // $q->where('is_deleted', 0); - // }) - - ->leftJoin('clients', function ($join) { - $join->on('invoices.client_id', '=', 'clients.id') - ->where('clients.is_deleted', 0); - }) - + ->where('is_deleted', 0) + ->whereHas('client', function ($q) { + $q->where('is_deleted', 0); + }) ->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_invoice'), function ($query) use ($user) { $query->where('invoices.user_id', $user->id); }) - ->orderBy('invoices.id', 'desc') + ->orderBy('id', 'desc') ->take(3000) ->get(); diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index 8c44bbc87b52..05ae6b4bf9e6 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -37,19 +37,6 @@ class StripeConnectController extends BaseController MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); - // $company_gateway = CompanyGateway::query() - // ->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34') - // ->where('company_id', $request->getCompany()->id) - // ->first(); - - // if ($company_gateway) { - // $config = $company_gateway->getConfig(); - - // if (property_exists($config, 'account_id') && strlen($config->account_id) > 5) { - // return view('auth.connect.existing'); - // } - // } - $stripe_client_id = config('ninja.ninja_stripe_client_id'); $redirect_uri = config('ninja.app_url').'/stripe/completed'; $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}"; @@ -98,6 +85,10 @@ class StripeConnectController extends BaseController return view('auth.connect.access_denied'); } + if(!$request->getTokenContent()) { + return view('auth.connect.session_expired'); + } + MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); $company = Company::query()->where('company_key', $request->getTokenContent()['company_key'])->first(); diff --git a/app/Http/Requests/Chart/ShowCalculatedFieldRequest.php b/app/Http/Requests/Chart/ShowCalculatedFieldRequest.php index 5a9945da8f72..3aa6271dc2af 100644 --- a/app/Http/Requests/Chart/ShowCalculatedFieldRequest.php +++ b/app/Http/Requests/Chart/ShowCalculatedFieldRequest.php @@ -37,7 +37,7 @@ class ShowCalculatedFieldRequest extends Request 'date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom', 'start_date' => 'bail|sometimes|date', 'end_date' => 'bail|sometimes|date', - 'field' => 'required|bail|in:active_invoices, outstanding_invoices, completed_payments, refunded_payments, active_quotes, unapproved_quotes, logged_tasks, invoiced_tasks, paid_tasks, logged_expenses, pending_expenses, invoiced_expenses, invoice_paid_expenses', + 'field' => 'required|bail|in:active_invoices,outstanding_invoices,completed_payments,refunded_payments,active_quotes,unapproved_quotes logged_tasks,invoiced_tasks,paid_tasks,logged_expenses,pending_expenses,invoiced_expenses,invoice_paid_expenses', 'calculation' => 'required|bail|in:sum,avg,count', 'period' => 'required|bail|in:current,previous,total', 'format' => 'sometimes|bail|in:time,money', diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 483993951309..6cb455352019 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -390,6 +390,7 @@ class BaseImport try { $invoice_data = $invoice_transformer->transform($raw_invoice); + $invoice_data['user_id'] = $this->company->owner()->id; $invoice_data['line_items'] = $this->cleanItems( $invoice_data['line_items'] ?? [] diff --git a/app/Interfaces/SyncInterface.php b/app/Interfaces/SyncInterface.php new file mode 100644 index 000000000000..17ca4e69c189 --- /dev/null +++ b/app/Interfaces/SyncInterface.php @@ -0,0 +1,21 @@ +file_location)) { - unlink(Storage::path($this->file_location)); - } + if(Storage::exists($this->file_location)) + Storage::delete($this->file_location); + } // diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 2cf7563ea483..855751670a4d 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -154,6 +154,20 @@ class NinjaMailerJob implements ShouldQueue LightLogs::create(new EmailSuccess($this->nmo->company->company_key, $this->nmo->mailable->subject)) ->send(); + } catch(\Symfony\Component\Mailer\Exception\TransportException $e){ + nlog("Mailer failed with a Transport Exception {$e->getMessage()}"); + + if(Ninja::isHosted() && $this->mailer == 'smtp'){ + $settings = $this->nmo->settings; + $settings->email_sending_method = 'default'; + $this->company->settings = $settings; + $this->company->save(); + } + + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); diff --git a/app/Jobs/Mailgun/ProcessMailgunWebhook.php b/app/Jobs/Mailgun/ProcessMailgunWebhook.php index 0f0161209174..3c63d05a3424 100644 --- a/app/Jobs/Mailgun/ProcessMailgunWebhook.php +++ b/app/Jobs/Mailgun/ProcessMailgunWebhook.php @@ -89,7 +89,7 @@ class ProcessMailgunWebhook implements ShouldQueue { nlog($this->request); - if(!$this->request['event-data']['tags'][0]) { + if(!$this->request['event-data']['tags'][0] ?? false) { //@phpstan-ignore-line return; } diff --git a/app/Livewire/BillingPortalPurchasev2.php b/app/Livewire/BillingPortalPurchasev2.php index cf03d07f75c6..0d365261a340 100644 --- a/app/Livewire/BillingPortalPurchasev2.php +++ b/app/Livewire/BillingPortalPurchasev2.php @@ -431,31 +431,31 @@ class BillingPortalPurchasev2 extends Component * @throws PresenterException * @throws InvalidArgumentException */ - private function createClientContact() - { - $company = $this->subscription->company; - $user = $this->subscription->user; - $user->setCompany($company); + // private function createClientContact() + // { + // $company = $this->subscription->company; + // $user = $this->subscription->user; + // $user->setCompany($company); - $client_repo = new ClientRepository(new ClientContactRepository()); - $data = [ - 'name' => '', - 'group_settings_id' => $this->subscription->group_id, - 'contacts' => [ - ['email' => $this->email], - ], - 'client_hash' => Str::random(40), - 'settings' => ClientSettings::defaults(), - ]; + // $client_repo = new ClientRepository(new ClientContactRepository()); + // $data = [ + // 'name' => '', + // 'group_settings_id' => $this->subscription->group_id, + // 'contacts' => [ + // ['email' => $this->email], + // ], + // 'client_hash' => Str::random(40), + // 'settings' => ClientSettings::defaults(), + // ]; - $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); + // $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); - $this->contact = $client->fresh()->contacts()->first(); + // $this->contact = $client->fresh()->contacts()->first(); - Auth::guard('contact')->loginUsingId($this->contact->id, true); + // Auth::guard('contact')->loginUsingId($this->contact->id, true); - return $this; - } + // return $this; + // } /** diff --git a/app/Livewire/Flow2/PaymentMethod.php b/app/Livewire/Flow2/PaymentMethod.php index 19986f73676f..780c51023c8c 100644 --- a/app/Livewire/Flow2/PaymentMethod.php +++ b/app/Livewire/Flow2/PaymentMethod.php @@ -80,7 +80,6 @@ class PaymentMethod extends Component { nlog($e->getMessage()); - $stopPropagation(); } diff --git a/app/Livewire/Flow2/ProcessPayment.php b/app/Livewire/Flow2/ProcessPayment.php index c162ba1ee14f..46efada6c023 100644 --- a/app/Livewire/Flow2/ProcessPayment.php +++ b/app/Livewire/Flow2/ProcessPayment.php @@ -91,9 +91,8 @@ class ProcessPayment extends Component $bag = new \Illuminate\Support\MessageBag(); $bag->add('gateway_error', $e->getMessage()); - session()->put('errors', $errors->put('default', $bag)); - + $invoice_id = $this->getContext()['payable_invoices'][0]['invoice_id']; $this->redirectRoute('client.invoice.show', ['invoice' => $invoice_id]); $stopPropagation(); diff --git a/app/Livewire/Flow2/RequiredFields.php b/app/Livewire/Flow2/RequiredFields.php index 9460aaeeaafe..4f8fa23ba7ee 100644 --- a/app/Livewire/Flow2/RequiredFields.php +++ b/app/Livewire/Flow2/RequiredFields.php @@ -136,4 +136,12 @@ class RequiredFields extends Component ]); } + public function exception($e, $stopPropagation) + { + + nlog($e->getMessage()); + + $stopPropagation(); + + } } diff --git a/app/Mail/Admin/ClientUnsubscribedObject.php b/app/Mail/Admin/ClientUnsubscribedObject.php index 0d1eb20135ca..aea513a6868e 100644 --- a/app/Mail/Admin/ClientUnsubscribedObject.php +++ b/app/Mail/Admin/ClientUnsubscribedObject.php @@ -13,13 +13,14 @@ namespace App\Mail\Admin; use App\Models\ClientContact; use App\Models\Company; +use App\Models\VendorContact; use App\Utils\Ninja; use Illuminate\Support\Facades\App; class ClientUnsubscribedObject { public function __construct( - public ClientContact $contact, + public ClientContact | VendorContact$contact, public Company $company, private bool $use_react_link = false ) { diff --git a/app/Models/Client.php b/app/Models/Client.php index cbb7500daa29..25f6aa512142 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -11,23 +11,25 @@ namespace App\Models; -use App\DataMapper\ClientSettings; -use App\DataMapper\CompanySettings; -use App\DataMapper\FeesAndLimits; -use App\Libraries\Currency\Conversion\CurrencyApi; -use App\Models\Presenters\ClientPresenter; -use App\Models\Traits\Excludable; -use App\Services\Client\ClientService; +use Laravel\Scout\Searchable; use App\Utils\Traits\AppSetup; -use App\Utils\Traits\ClientGroupSettingsSaver; -use App\Utils\Traits\GeneratesCounter; -use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; -use Illuminate\Contracts\Translation\HasLocalePreference; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\SoftDeletes; +use App\Utils\Traits\MakesDates; +use App\DataMapper\FeesAndLimits; +use App\Models\Traits\Excludable; +use App\DataMapper\ClientSettings; +use App\DataMapper\ClientSync; +use App\DataMapper\CompanySettings; +use App\Services\Client\ClientService; +use App\Utils\Traits\GeneratesCounter; use Laracasts\Presenter\PresentableTrait; +use App\Models\Presenters\ClientPresenter; +use Illuminate\Database\Eloquent\SoftDeletes; +use App\Utils\Traits\ClientGroupSettingsSaver; +use App\Libraries\Currency\Conversion\CurrencyApi; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Contracts\Translation\HasLocalePreference; /** * App\Models\Client @@ -69,6 +71,7 @@ use Laracasts\Presenter\PresentableTrait; * @property int|null $shipping_country_id * @property object|null $settings * @property object|null $group_settings + * @property object|null $sync * @property bool $is_deleted * @property int|null $group_settings_id * @property string|null $vat_number @@ -124,6 +127,9 @@ class Client extends BaseModel implements HasLocalePreference use ClientGroupSettingsSaver; use Excludable; + + use Searchable; + protected $presenter = ClientPresenter::class; protected $hidden = [ @@ -186,6 +192,7 @@ class Client extends BaseModel implements HasLocalePreference 'last_login' => 'timestamp', 'tax_data' => 'object', 'e_invoice' => 'object', + 'sync' => ClientSync::class, ]; protected $touches = []; @@ -232,6 +239,38 @@ class Client extends BaseModel implements HasLocalePreference 'custom_value4', ]; + public function toSearchableArray() + { + return [ + 'name' => $this->present()->name(), + 'is_deleted' => $this->is_deleted, + 'hashed_id' => $this->hashed_id, + 'number' => $this->number, + 'id_number' => $this->id_number, + 'vat_number' => $this->vat_number, + 'balance' => $this->balance, + 'paid_to_date' => $this->paid_to_date, + 'phone' => $this->phone, + 'address1' => $this->address1, + 'address2' => $this->address2, + 'city' => $this->city, + 'state' => $this->state, + 'postal_code' => $this->postal_code, + 'website' => $this->website, + 'private_notes' => $this->private_notes, + 'public_notes' => $this->public_notes, + 'shipping_address1' => $this->shipping_address1, + 'shipping_address2' => $this->shipping_address2, + 'shipping_city' => $this->shipping_city, + 'shipping_state' => $this->shipping_state, + 'shipping_postal_code' => $this->shipping_postal_code, + 'custom_value1' => $this->custom_value1, + 'custom_value2' => $this->custom_value2, + 'custom_value3' => $this->custom_value3, + 'custom_value4' => $this->custom_value4, + 'company_key' => $this->company->company_key, + ]; + } public function getEntityType() { diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php index 82941087367b..939dc7140d09 100644 --- a/app/Models/ClientContact.php +++ b/app/Models/ClientContact.php @@ -13,6 +13,7 @@ namespace App\Models; use App\Utils\Ninja; use Illuminate\Support\Str; +use Laravel\Scout\Searchable; use App\Jobs\Mail\NinjaMailer; use App\Utils\Traits\AppSetup; use App\Utils\Traits\MakesHash; @@ -100,6 +101,8 @@ class ClientContact extends Authenticatable implements HasLocalePreference use HasFactory; use AppSetup; + use Searchable; + /* Used to authenticate a contact */ protected $guard = 'contact'; @@ -165,6 +168,23 @@ class ClientContact extends Authenticatable implements HasLocalePreference 'email', ]; + public function toSearchableArray() + { + return [ + 'name' => $this->present()->search_display(), + 'hashed_id' => $this->client->hashed_id, + 'email' => $this->email, + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'phone' => $this->phone, + 'custom_value1' => $this->custom_value1, + 'custom_value2' => $this->custom_value2, + 'custom_value3' => $this->custom_value3, + 'custom_value4' => $this->custom_value4, + 'company_key' => $this->company->company_key, + ]; + } + /* V2 type of scope */ diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index e2d939db2515..adfaad5f00f1 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -489,7 +489,6 @@ class CompanyGateway extends BaseModel public function getSettings() { - // return $this->settings; return $this->settings ?? new \stdClass; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index b51fbf7fef83..1cfd1df3404e 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -11,22 +11,24 @@ namespace App\Models; -use App\Events\Invoice\InvoiceReminderWasEmailed; -use App\Events\Invoice\InvoiceWasEmailed; -use App\Helpers\Invoice\InvoiceSum; -use App\Helpers\Invoice\InvoiceSumInclusive; -use App\Models\Presenters\EntityPresenter; -use App\Services\Invoice\InvoiceService; -use App\Services\Ledger\LedgerService; +use App\DataMapper\InvoiceSync; use App\Utils\Ninja; -use App\Utils\Traits\Invoice\ActionsInvoice; +use Laravel\Scout\Searchable; +use Illuminate\Support\Carbon; use App\Utils\Traits\MakesDates; -use App\Utils\Traits\MakesInvoiceValues; +use App\Helpers\Invoice\InvoiceSum; use App\Utils\Traits\MakesReminders; use App\Utils\Traits\NumberFormatter; -use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Carbon; +use App\Services\Ledger\LedgerService; +use App\Services\Invoice\InvoiceService; +use App\Utils\Traits\MakesInvoiceValues; +use App\Events\Invoice\InvoiceWasEmailed; use Laracasts\Presenter\PresentableTrait; +use App\Models\Presenters\EntityPresenter; +use App\Helpers\Invoice\InvoiceSumInclusive; +use App\Utils\Traits\Invoice\ActionsInvoice; +use Illuminate\Database\Eloquent\SoftDeletes; +use App\Events\Invoice\InvoiceReminderWasEmailed; /** * App\Models\Invoice @@ -52,6 +54,7 @@ use Laracasts\Presenter\PresentableTrait; * @property bool $is_deleted * @property object|array|string $line_items * @property object|null $backup + * @property object|null $sync * @property string|null $footer * @property string|null $public_notes * @property string|null $private_notes @@ -144,6 +147,8 @@ class Invoice extends BaseModel use MakesReminders; use ActionsInvoice; + use Searchable; + protected $presenter = EntityPresenter::class; protected $touches = []; @@ -210,6 +215,8 @@ class Invoice extends BaseModel 'custom_surcharge_tax3' => 'bool', 'custom_surcharge_tax4' => 'bool', 'e_invoice' => 'object', + 'sync' => InvoiceSync::class, + ]; protected $with = []; @@ -235,6 +242,25 @@ class Invoice extends BaseModel public const STATUS_UNPAID = -2; //status < 4 || < 3 && !is_deleted && !trashed() + public function toSearchableArray() + { + return [ + 'name' => $this->client->present()->name() . ' - ' . $this->number, + 'hashed_id' => $this->hashed_id, + 'number' => $this->number, + 'is_deleted' => $this->is_deleted, + 'amount' => (float) $this->amount, + 'balance' => (float) $this->balance, + 'due_date' => $this->due_date, + 'date' => $this->date, + 'custom_value1' => $this->custom_value1, + 'custom_value2' => $this->custom_value2, + 'custom_value3' => $this->custom_value3, + 'custom_value4' => $this->custom_value4, + 'company_key' => $this->company->company_key, + ]; + } + public function getEntityType() { return self::class; @@ -559,7 +585,7 @@ class Invoice extends BaseModel * Filtering logic to determine * whether an invoice is locked * based on the current status of the invoice. - * @return bool [description] + * @return bool */ public function isLocked(): bool { @@ -569,7 +595,7 @@ class Invoice extends BaseModel case 'off': return false; case 'when_sent': - return $this->status_id == self::STATUS_SENT; + return $this->status_id >= self::STATUS_SENT; case 'when_paid': return $this->status_id == self::STATUS_PAID || $this->status_id == self::STATUS_PARTIAL; case 'end_of_month': @@ -739,7 +765,7 @@ class Invoice extends BaseModel $send_email_enabled = ctrans('texts.send_email') . " " .ctrans('texts.enabled'); $send_email_disabled = ctrans('texts.send_email') . " " .ctrans('texts.disabled'); - $sends_email_1 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $sends_email_1 = $settings->enable_reminder1 ? $send_email_enabled : $send_email_disabled; $days_1 = $settings->num_days_reminder1 . " " . ctrans('texts.days'); $schedule_1 = ctrans("texts.{$settings->schedule_reminder1}"); //after due date etc or disabled $label_1 = ctrans('texts.reminder1'); @@ -749,7 +775,7 @@ class Invoice extends BaseModel $schedule_2 = ctrans("texts.{$settings->schedule_reminder2}"); //after due date etc or disabled $label_2 = ctrans('texts.reminder2'); - $sends_email_3 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $sends_email_3 = $settings->enable_reminder3 ? $send_email_enabled : $send_email_disabled; $days_3 = $settings->num_days_reminder3 . " " . ctrans('texts.days'); $schedule_3 = ctrans("texts.{$settings->schedule_reminder3}"); //after due date etc or disabled $label_3 = ctrans('texts.reminder3'); diff --git a/app/Models/Product.php b/app/Models/Product.php index 52390a589e07..1a96b690df4e 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -11,6 +11,7 @@ namespace App\Models; +use App\DataMapper\ProductSync; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; use League\CommonMark\CommonMarkConverter; @@ -43,6 +44,7 @@ use League\CommonMark\CommonMarkConverter; * @property int|null $deleted_at * @property int|null $created_at * @property int|null $updated_at + * @property object|null $sync * @property bool $is_deleted * @property float $in_stock_quantity * @property bool $stock_notification @@ -100,6 +102,13 @@ class Product extends BaseModel 'tax_id', ]; + protected $casts = [ + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + 'sync' => ProductSync::class, + ]; + public array $ubl_tax_map = [ self::PRODUCT_TYPE_REVERSE_TAX => 'AE', // VAT_REVERSE_CHARGE = self::PRODUCT_TYPE_EXEMPT => 'E', // EXEMPT_FROM_TAX = diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 8ea4dc2654cd..197ebddd33b3 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -203,7 +203,6 @@ class VendorContact extends Authenticatable implements HasLocalePreference { return $this ->withTrashed() - // ->company() ->where('id', $this->decodePrimaryKey($value)) ->firstOrFail(); } @@ -219,4 +218,15 @@ class VendorContact extends Authenticatable implements HasLocalePreference return $domain.'/vendor/key_login/'.$this->contact_key; } + + public function getAdminLink($use_react_link = false): string + { + return $use_react_link ? $this->getReactLink() : config('ninja.app_url'); + } + + private function getReactLink(): string + { + return config('ninja.react_url')."/#/vendors/{$this->vendor->hashed_id}"; + } + } diff --git a/app/PaymentDrivers/BTCPayPaymentDriver.php b/app/PaymentDrivers/BTCPayPaymentDriver.php index 4d1c02b273c9..801241e006e0 100644 --- a/app/PaymentDrivers/BTCPayPaymentDriver.php +++ b/app/PaymentDrivers/BTCPayPaymentDriver.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://opensource.org/licenses/AAL */ diff --git a/app/PaymentDrivers/BlockonomicsPaymentDriver.php b/app/PaymentDrivers/BlockonomicsPaymentDriver.php index a53ddc4bb612..05db691d2648 100644 --- a/app/PaymentDrivers/BlockonomicsPaymentDriver.php +++ b/app/PaymentDrivers/BlockonomicsPaymentDriver.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://opensource.org/licenses/AAL */ diff --git a/app/PaymentDrivers/CBAPowerBoard/CreditCard.php b/app/PaymentDrivers/CBAPowerBoard/CreditCard.php index aa0c6e80374a..512e7cb2f6c6 100644 --- a/app/PaymentDrivers/CBAPowerBoard/CreditCard.php +++ b/app/PaymentDrivers/CBAPowerBoard/CreditCard.php @@ -29,7 +29,7 @@ use App\PaymentDrivers\CBAPowerBoard\Models\Gateway; class CreditCard implements LivewireMethodInterface { - private Gateway $cba_gateway; + private ?Gateway $cba_gateway; public function __construct(public CBAPowerBoardPaymentDriver $powerboard) { @@ -39,7 +39,6 @@ class CreditCard implements LivewireMethodInterface public function authorizeView(array $data) { $data['payment_method_id'] = GatewayType::CREDIT_CARD; - $data['threeds'] = $this->powerboard->company_gateway->getConfigField('threeds'); return render('gateways.powerboard.credit_card.authorize', $this->paymentData($data)); } @@ -51,8 +50,6 @@ class CreditCard implements LivewireMethodInterface { $payment_source = $this->storePaymentSource($request); - nlog($payment_source); - $browser_details = json_decode($request->browser_details, true); $payload = [ @@ -221,15 +218,15 @@ class CreditCard implements LivewireMethodInterface { $this->powerboard->init(); - if($this->cba_gateway->verification_status != "completed") - throw new PaymentFailed("This payment method is not configured as yet. Reference Powerboard portal for further information", 400); + // if(!isset($this->cba_gateway->verification_status) || $this->cba_gateway->verification_status != "completed") + // throw new PaymentFailed("This payment method is not configured as yet. Reference Powerboard portal for further information", 400); $merge = [ 'public_key' => $this->powerboard->company_gateway->getConfigField('publicKey'), 'widget_endpoint' => $this->powerboard->widget_endpoint, 'gateway' => $this->powerboard, 'environment' => $this->powerboard->environment, - 'gateway_id' => $this->cba_gateway->_id, + 'gateway_id' => $this->cba_gateway->_id ?? false, ]; return array_merge($data, $merge); @@ -451,7 +448,6 @@ class CreditCard implements LivewireMethodInterface match($error_object->error->code) { "UnfulfilledCondition" => $error_message = $error_object->error->details->messages[0] ?? $error_object->error->message ?? "Unknown error", "GatewayError" => $error_message = $error_object->error->message, - "UnfulfilledCondition" => $error_message = $error_object->error->message, "transaction_declined" => $error_message = $error_object->error->details[0]->status_code_description, default => $error_message = $error_object->error->message ?? "Unknown error", }; diff --git a/app/PaymentDrivers/CBAPowerBoard/Models/Gateway.php b/app/PaymentDrivers/CBAPowerBoard/Models/Gateway.php index d36e176d5ffb..2bf85301ed7d 100644 --- a/app/PaymentDrivers/CBAPowerBoard/Models/Gateway.php +++ b/app/PaymentDrivers/CBAPowerBoard/Models/Gateway.php @@ -13,35 +13,17 @@ namespace App\PaymentDrivers\CBAPowerBoard\Models; class Gateway { - /** @var string */ - public string $_id; - /** @var string */ - public string $name; - /** @var string */ - public string $type; - /** @var string */ - public string $mode; - /** @var string */ - public string $created_at; - /** @var string */ - public string $updated_at; - /** @var bool */ - public bool $archived; - /** @var bool */ - public bool $default; - /** @var string */ - public string $verification_status; public function __construct( - string $_id, - string $name, - string $type, - string $mode, - string $created_at, - string $updated_at, - bool $archived, - bool $default, - string $verification_status + public string $_id, + public string $name, + public string $type, + public string $mode, + public string $created_at, + public string $updated_at, + public bool $archived, + public bool $default, + public string $verification_status = '' ) { $this->_id = $_id; $this->name = $name; diff --git a/app/PaymentDrivers/CBAPowerBoard/Settings.php b/app/PaymentDrivers/CBAPowerBoard/Settings.php index 945a72f48ea7..896e538871a7 100644 --- a/app/PaymentDrivers/CBAPowerBoard/Settings.php +++ b/app/PaymentDrivers/CBAPowerBoard/Settings.php @@ -13,10 +13,11 @@ namespace App\PaymentDrivers\CBAPowerBoard; -use App\PaymentDrivers\CBAPowerBoard\Models\Gateways; +use App\Models\GatewayType; -use App\PaymentDrivers\CBAPowerBoard\Models\Gateway; use App\PaymentDrivers\CBAPowerBoardPaymentDriver; +use App\PaymentDrivers\CBAPowerBoard\Models\Gateway; +use App\PaymentDrivers\CBAPowerBoard\Models\Gateways; class Settings { @@ -28,21 +29,29 @@ class Settings public function __construct(public CBAPowerBoardPaymentDriver $powerboard) { } - - public function getGateways() + + /** + * Returns the API response for the gateways + * + * @return mixed + */ + public function getGateways(): mixed { $r = $this->powerboard->gatewayRequest('/v1/gateways', (\App\Enum\HttpVerb::GET)->value, [], []); if($r->failed()) $r->throw(); - - nlog($r->object()); return (new \App\PaymentDrivers\CBAPowerBoard\Models\Parse())->encode(Gateway::class."[]", $r->object()->resource->data); } - /** We will need to have a process that updates this at intervals */ + /** We will need to have a process that updates this at intervals */ + /** + * updateSettings from the API + * + * @return self + */ public function updateSettings():self { $gateways = $this->getGateways(); @@ -53,12 +62,23 @@ class Settings return $this; } - + + /** + * getSettings + * + * @return mixed + */ public function getSettings(): mixed { return $this->powerboard->company_gateway->getSettings(); } - + + /** + * Entry point for getting the payment gateway configuration + * + * @param int $gateway_type_id + * @return mixed + */ public function getPaymentGatewayConfiguration(int $gateway_type_id): mixed { $type = self::GATEWAY_CBA; @@ -70,25 +90,46 @@ class Settings return $this->getGatewayByType($type); } - + + /** + * Returns the CBA gateway object for a given gateway type + * + * @param string $gateway_type_const + * @return mixed + */ private function getGatewayByType(string $gateway_type_const): mixed { $settings = $this->getSettings(); - if(!property_exists($settings,'gateways')){ + if(!property_exists($settings, 'gateways')){ $this->updateSettings(); $settings = $this->getSettings(); } $gateways = (new \App\PaymentDrivers\CBAPowerBoard\Models\Parse())->encode(Gateway::class."[]", $settings->gateways); + if ($gateway_type_const == self::GATEWAY_CBA && strlen($this->powerboard->company_gateway->getConfigField('gatewayId') ?? '') > 1) { + + return collect($gateways)->first(function (Gateway $gateway) { + return $gateway->_id == $this->powerboard->company_gateway->getConfigField('gatewayId'); + }); + + } + return collect($gateways)->first(function (Gateway $gateway) use ($gateway_type_const){ return $gateway->type == $gateway_type_const; }); } - + + /** + * Returns the CBA gateway ID for a given gateway type + * + * @param int $gateway_type_id + * @return string + */ public function getGatewayId(int $gateway_type_id): string { + $gateway = $this->getPaymentGatewayConfiguration($gateway_type_id); return $gateway->_id; diff --git a/app/PaymentDrivers/CBAPowerBoardPaymentDriver.php b/app/PaymentDrivers/CBAPowerBoardPaymentDriver.php index cf03217b350b..7e0c494af4aa 100644 --- a/app/PaymentDrivers/CBAPowerBoardPaymentDriver.php +++ b/app/PaymentDrivers/CBAPowerBoardPaymentDriver.php @@ -185,16 +185,10 @@ class CBAPowerBoardPaymentDriver extends BaseDriver { $this->init(); - + $this->settings()->updateSettings(); + return true; - // try { - // $this->verifyConnect(); - // return true; - // } catch(\Exception $e) { - // } - - // return false; } diff --git a/app/PaymentDrivers/Factory/ForteCustomerFactory.php b/app/PaymentDrivers/Factory/ForteCustomerFactory.php index e7c535221627..277d536f5c39 100644 --- a/app/PaymentDrivers/Factory/ForteCustomerFactory.php +++ b/app/PaymentDrivers/Factory/ForteCustomerFactory.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php b/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php index 3f05dbf84d06..5308bc4ab6e0 100644 --- a/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php +++ b/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/PaymentDrivers/Factory/SquareCustomerFactory.php b/app/PaymentDrivers/Factory/SquareCustomerFactory.php index c62eac3d8055..00070b93a793 100644 --- a/app/PaymentDrivers/Factory/SquareCustomerFactory.php +++ b/app/PaymentDrivers/Factory/SquareCustomerFactory.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/PaymentDrivers/Forte/ACH.php b/app/PaymentDrivers/Forte/ACH.php index 1fa4c4a2ab07..09071ac39c8d 100644 --- a/app/PaymentDrivers/Forte/ACH.php +++ b/app/PaymentDrivers/Forte/ACH.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/PaymentDrivers/Forte/CreditCard.php b/app/PaymentDrivers/Forte/CreditCard.php index cc4256171f27..9b3bd359c73f 100644 --- a/app/PaymentDrivers/Forte/CreditCard.php +++ b/app/PaymentDrivers/Forte/CreditCard.php @@ -5,7 +5,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license */ diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php index 90efcfa0c18b..3daa71dfefe1 100644 --- a/app/PaymentDrivers/FortePaymentDriver.php +++ b/app/PaymentDrivers/FortePaymentDriver.php @@ -4,7 +4,7 @@ * * @link https://github.com/invoiceninja/invoiceninja source repository * - * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://opensource.org/licenses/AAL */ diff --git a/app/PaymentDrivers/Rotessa/PaymentMethod.php b/app/PaymentDrivers/Rotessa/PaymentMethod.php index 8ccf3346685d..51459d74282b 100755 --- a/app/PaymentDrivers/Rotessa/PaymentMethod.php +++ b/app/PaymentDrivers/Rotessa/PaymentMethod.php @@ -111,7 +111,7 @@ class PaymentMethod implements MethodInterface, LivewireMethodInterface $message = json_decode($e->getMessage(), true); - return redirect()->route('client.payment_methods.index')->withErrors(array_values($message['errors'])); + return redirect()->route('client.payment_methods.index')->withErrors(array_values($message['errors'] ?? [$e->getMessage()])); } diff --git a/app/Services/Chart/ChartCalculations.php b/app/Services/Chart/ChartCalculations.php index 88a817c5bc1c..9db8feefc2f4 100644 --- a/app/Services/Chart/ChartCalculations.php +++ b/app/Services/Chart/ChartCalculations.php @@ -141,8 +141,8 @@ trait ChartCalculations } match ($data['calculation']) { - 'sum' => $result = $q->sum('refunded'), - 'avg' => $result = $q->avg('refunded'), + 'sum' => $result = $q->sum('amount'), + 'avg' => $result = $q->avg('amount'), 'count' => $result = $q->count(), default => $result = 0, }; @@ -287,14 +287,14 @@ trait ChartCalculations return $query->get() ->when($data['currency_id'] == '999', function ($collection) { - $collection->map(function ($e) { + return $collection->map(function ($e) { /** @var \App\Models\Expense $e */ return $e->amount * $e->exchange_rate; }); }) ->when($data['currency_id'] != '999', function ($collection) { - $collection->map(function ($e) { + return $collection->map(function ($e) { /** @var \App\Models\Expense $e */ return $e->amount; diff --git a/app/Services/EDocument/Gateway/Qvalia/Partner.php b/app/Services/EDocument/Gateway/Qvalia/Partner.php index 4cb82f65f52a..a2e19aee7420 100644 --- a/app/Services/EDocument/Gateway/Qvalia/Partner.php +++ b/app/Services/EDocument/Gateway/Qvalia/Partner.php @@ -107,7 +107,7 @@ class Partner { $uri = "/partner/{$this->partner_number}/account/{$accountRegNo}"; - return $this->qvalia->httpClient($uri, (\App\Enum\HttpVerb::DELETE)->value)->object(); + return $this->qvalia->httpClient($uri, (\App\Enum\HttpVerb::DELETE)->value, [])->object(); } /** @@ -138,7 +138,7 @@ class Partner { $uri = "/partner/{$this->partner_number}/account/{$accountRegNo}/peppol/{$peppolId}"; - return $this->qvalia->httpClient($uri, (\App\Enum\HttpVerb::DELETE)->value)->object(); + return $this->qvalia->httpClient($uri, (\App\Enum\HttpVerb::DELETE)->value, [])->object(); } } diff --git a/app/Services/EDocument/Imports/ZugferdEDocument.php b/app/Services/EDocument/Imports/ZugferdEDocument.php index cc70e4cd6694..bcc40624eef6 100644 --- a/app/Services/EDocument/Imports/ZugferdEDocument.php +++ b/app/Services/EDocument/Imports/ZugferdEDocument.php @@ -131,6 +131,7 @@ class ZugferdEDocument extends AbstractService $vendor->postal_code = $postcode; $country = app('countries')->first(function ($c) use ($country) { + /** @var \App\Models\Country $c */ return $c->iso_3166_2 == $country || $c->iso_3166_3 == $country; }); if ($country) diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 2e21bb8b4af7..76103ddd96e5 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -291,6 +291,20 @@ class Email implements ShouldQueue LightLogs::create(new EmailSuccess($this->company->company_key, $this->mailable->subject)) ->send(); + } catch(\Symfony\Component\Mailer\Exception\TransportException $e){ + nlog("Mailer failed with a Transport Exception {$e->getMessage()}"); + + if(Ninja::isHosted() && $this->mailer == 'smtp'){ + $settings = $this->email_object->settings; + $settings->email_sending_method = 'default'; + $this->company->settings = $settings; + $this->company->save(); + } + + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index 2a9791ba7ed3..1ff84d0e59e6 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -47,7 +47,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->settings->schedule_reminder1 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder1); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder1)->addSeconds($offset); if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); @@ -58,7 +58,7 @@ class UpdateReminder extends AbstractService ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder1 == 'before_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder1); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder1)->addSeconds($offset); // nlog("1. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -71,7 +71,7 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder1 == 'after_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder1); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder1)->addSeconds($offset); // nlog("2. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -81,7 +81,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->settings->schedule_reminder2 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder2); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder2)->addSeconds($offset); if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); @@ -93,7 +93,7 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder2 == 'before_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder2); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder2)->addSeconds($offset); // nlog("3. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -106,7 +106,7 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder2 == 'after_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder2); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder2)->addSeconds($offset); // nlog("4. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -116,7 +116,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->settings->schedule_reminder3 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder3); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays((int)$this->settings->num_days_reminder3)->addSeconds($offset); if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); @@ -128,7 +128,7 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder3 == 'before_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder3); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays((int)$this->settings->num_days_reminder3)->addSeconds($offset); // nlog("5. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -141,7 +141,7 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder3 == 'after_due_date') { $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; - $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder3); + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays((int)$this->settings->num_days_reminder3)->addSeconds($offset); // nlog("6. {$reminder_date->format('Y-m-d')}"); if ($reminder_date->gt(now())) { @@ -154,17 +154,15 @@ class UpdateReminder extends AbstractService ($this->invoice->reminder1_sent || $this->settings->schedule_reminder1 == "" || !$this->settings->enable_reminder1) && ($this->invoice->reminder2_sent || $this->settings->schedule_reminder2 == "" || !$this->settings->enable_reminder2) && ($this->invoice->reminder3_sent || $this->settings->schedule_reminder3 == "" || !$this->settings->enable_reminder3)) { - $reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int) $this->settings->endless_reminder_frequency_id); + $reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int) $this->settings->endless_reminder_frequency_id)->addSeconds($offset); - if ($reminder_date) { - if ($reminder_date->gt(now())) { - $date_collection->push($reminder_date); - } + if ($reminder_date && $reminder_date->gt(now())) { + $date_collection->push($reminder_date); } } if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) { - $this->invoice->next_send_date = $date_collection->sort()->first()->addSeconds($offset); + $this->invoice->next_send_date = $date_collection->sort()->first(); } else { $this->invoice->next_send_date = null; } diff --git a/app/Services/Quickbooks/Jobs/QuickbooksImport.php b/app/Services/Quickbooks/Jobs/QuickbooksImport.php new file mode 100644 index 000000000000..3bafdbd046eb --- /dev/null +++ b/app/Services/Quickbooks/Jobs/QuickbooksImport.php @@ -0,0 +1,245 @@ + 'Item', + 'client' => 'Customer', + 'invoice' => 'Invoice', + 'sales' => 'SalesReceipt', + // 'quote' => 'Estimate', + // 'purchase_order' => 'PurchaseOrder', + // 'payment' => 'Payment', + // 'vendor' => 'Vendor', + // 'expense' => 'Purchase', + ]; + + private QuickbooksService $qbs; + + private QuickbooksSync $settings; + + private Company $company; + + public function __construct(public int $company_id, public string $db) + { + } + + /** + * Execute the job. + */ + public function handle() + { + MultiDB::setDb($this->db); + + $this->company = Company::query()->find($this->company_id); + $this->qbs = new QuickbooksService($this->company); + $this->settings = $this->company->quickbooks->settings; + + foreach($this->entities as $key => $entity) { + + if(!$this->qbs->syncable($key, \App\Enum\SyncDirection::PULL)) { + nlog('skipping ' . $key); + continue; + } + + $records = $this->qbs->sdk()->fetchRecords($entity); + + $this->processEntitySync($key, $records); + + } + + } + + /** + * Processes the sync for a given entity + * + * @param string $entity + * @param mixed $records + * @return void + */ + private function processEntitySync(string $entity, $records): void + { + match($entity){ + 'client' => $this->qbs->client->syncToNinja($records), + 'product' => $this->qbs->product->syncToNinja($records), + 'invoice' => $this->qbs->invoice->syncToNinja($records), + 'sales' => $this->qbs->invoice->syncToNinja($records), + // 'vendor' => $this->syncQbToNinjaVendors($records), + // 'quote' => $this->syncInvoices($records), + // 'expense' => $this->syncQbToNinjaExpenses($records), + // 'purchase_order' => $this->syncInvoices($records), + // 'payment' => $this->syncPayment($records), + default => false, + }; + } + + private function syncQbToNinjaInvoices($records): void + { + + + } + + + + private function syncQbToNinjaVendors(array $records): void + { + + $transformer = new VendorTransformer($this->company); + + foreach($records as $record) + { + $ninja_data = $transformer->qbToNinja($record); + + if($vendor = $this->findVendor($ninja_data)) + { + $vendor->fill($ninja_data[0]); + $vendor->saveQuietly(); + + $contact = $vendor->contacts()->where('email', $ninja_data[1]['email'])->first(); + + if(!$contact) + { + $contact = VendorContactFactory::create($this->company->id, $this->company->owner()->id); + $contact->vendor_id = $vendor->id; + $contact->send_email = true; + $contact->is_primary = true; + $contact->fill($ninja_data[1]); + $contact->saveQuietly(); + } + elseif($this->qbs->syncable('vendor', \App\Enum\SyncDirection::PULL)){ + $contact->fill($ninja_data[1]); + $contact->saveQuietly(); + } + + } + + } + } + + private function syncQbToNinjaExpenses(array $records): void + { + + $transformer = new ExpenseTransformer($this->company); + + foreach($records as $record) + { + $ninja_data = $transformer->qbToNinja($record); + + if($expense = $this->findExpense($ninja_data)) + { + $expense->fill($ninja_data); + $expense->saveQuietly(); + } + + } + } + + private function findExpense(array $qb_data): ?Expense + { + $expense = $qb_data; + + $search = Expense::query() + ->withTrashed() + ->where('company_id', $this->company->id) + ->where('number', $expense['number']); + + if($search->count() == 0) { + return ExpenseFactory::create($this->company->id, $this->company->owner()->id); + } + elseif($search->count() == 1) { + return $this->qbs->syncable('expense', \App\Enum\SyncDirection::PULL) ? $search->first() : null; + } + + return null; + } + + private function findVendor(array $qb_data) :?Vendor + { + $vendor = $qb_data[0]; + $contact = $qb_data[1]; + $vendor_meta = $qb_data[2]; + + $search = Vendor::query() + ->withTrashed() + ->where('company_id', $this->company->id) + ->where(function ($q) use ($vendor, $vendor_meta, $contact){ + + $q->where('vendor_hash', $vendor_meta['vendor_hash']) + ->orWhere('number', $vendor['number']) + ->orWhereHas('contacts', function ($q) use ($contact){ + $q->where('email', $contact['email']); + }); + + }); + + if($search->count() == 0) { + //new client + return VendorFactory::create($this->company->id, $this->company->owner()->id); + } + elseif($search->count() == 1) { + + return $this->qbs->syncable('vendor', \App\Enum\SyncDirection::PULL) ? $search->first() : null; + } + + return null; + } + + public function middleware() + { + return [new WithoutOverlapping("qbs-{$this->company_id}-{$this->db}")]; + } + + public function failed($exception) + { + nlog("QuickbooksSync failed => ".$exception->getMessage()); + config(['queue.failed.driver' => null]); + + } +} diff --git a/app/Services/Quickbooks/Jobs/QuickbooksSync.php b/app/Services/Quickbooks/Jobs/QuickbooksSync.php deleted file mode 100644 index 603b2923963f..000000000000 --- a/app/Services/Quickbooks/Jobs/QuickbooksSync.php +++ /dev/null @@ -1,436 +0,0 @@ - 'Item', - 'client' => 'Customer', - 'invoice' => 'Invoice', - 'quote' => 'Estimate', - 'purchase_order' => 'PurchaseOrder', - 'payment' => 'Payment', - 'sales' => 'SalesReceipt', - 'vendor' => 'Vendor', - 'expense' => 'Purchase', - ]; - - private QuickbooksService $qbs; - - private ?array $settings; - - private Company $company; - - public function __construct(public int $company_id, public string $db) - { - } - - /** - * Execute the job. - */ - public function handle() - { - MultiDB::setDb($this->db); - - $this->company = Company::query()->find($this->company_id); - $this->qbs = new QuickbooksService($this->company); - $this->settings = $this->company->quickbooks->settings; - - nlog("here we go!"); - foreach($this->entities as $key => $entity) { - if(!$this->syncGate($key, 'pull')) { - continue; - } - - $records = $this->qbs->sdk()->fetchRecords($entity); - - $this->processEntitySync($key, $records); - - } - - } - - private function syncGate(string $entity, string $direction): bool - { - return (bool) $this->settings[$entity]['sync'] && in_array($this->settings[$entity]['direction'], [$direction,'bidirectional']); - } - - private function updateGate(string $entity): bool - { - return (bool) $this->settings[$entity]['sync'] && $this->settings[$entity]['update_record']; - } - - // private function harvestQbEntityName(string $entity): string - // { - // return $this->entities[$entity]; - // } - - private function processEntitySync(string $entity, $records) - { - match($entity){ - 'client' => $this->syncQbToNinjaClients($records), - 'product' => $this->syncQbToNinjaProducts($records), - 'invoice' => $this->syncQbToNinjaInvoices($records), - 'sales' => $this->syncQbToNinjaInvoices($records), - 'vendor' => $this->syncQbToNinjaVendors($records), - // 'quote' => $this->syncInvoices($records), - 'expense' => $this->syncQbToNinjaExpenses($records), - // 'purchase_order' => $this->syncInvoices($records), - // 'payment' => $this->syncPayment($records), - default => false, - }; - } - - private function syncQbToNinjaInvoices($records): void - { - nlog("invoice sync ". count($records)); - $invoice_transformer = new InvoiceTransformer($this->company); - - foreach($records as $record) - { - nlog($record); - - $ninja_invoice_data = $invoice_transformer->qbToNinja($record); - - nlog($ninja_invoice_data); - - $payment_ids = $ninja_invoice_data['payment_ids'] ?? []; - $client_id = $ninja_invoice_data['client_id'] ?? null; - - if(is_null($client_id)) - continue; - - unset($ninja_invoice_data['payment_ids']); - - if($invoice = $this->findInvoice($ninja_invoice_data)) - { - $invoice->fill($ninja_invoice_data); - $invoice->saveQuietly(); - - $invoice = $invoice->calc()->getInvoice()->service()->markSent()->save(); - - foreach($payment_ids as $payment_id) - { - - $payment = $this->qbs->sdk->FindById('Payment', $payment_id); - - $payment_transformer = new PaymentTransformer($this->company); - - $transformed = $payment_transformer->qbToNinja($payment); - - $ninja_payment = $payment_transformer->buildPayment($payment); - $ninja_payment->service()->applyNumber()->save(); - - $paymentable = new \App\Models\Paymentable(); - $paymentable->payment_id = $ninja_payment->id; - $paymentable->paymentable_id = $invoice->id; - $paymentable->paymentable_type = 'invoices'; - $paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount'); - $paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line - $paymentable->save(); - - $invoice->service()->applyPayment($ninja_payment, $paymentable->amount); - - } - - if($record instanceof IPPSalesReceipt) - { - $invoice->service()->markPaid()->save(); - } - - } - - $ninja_invoice_data = false; - - } - - } - - private function findInvoice(array $ninja_invoice_data): ?Invoice - { - $search = Invoice::query() - ->withTrashed() - ->where('company_id', $this->company->id) - ->where('number', $ninja_invoice_data['number']); - - if($search->count() == 0) { - //new invoice - $invoice = InvoiceFactory::create($this->company->id, $this->company->owner()->id); - $invoice->client_id = $ninja_invoice_data['client_id']; - - return $invoice; - } elseif($search->count() == 1) { - return $this->settings['invoice']['update_record'] ? $search->first() : null; - } - - return null; - - } - - private function syncQbToNinjaClients(array $records): void - { - - $client_transformer = new ClientTransformer($this->company); - - foreach($records as $record) - { - $ninja_client_data = $client_transformer->qbToNinja($record); - - if($client = $this->findClient($ninja_client_data)) - { - $client->fill($ninja_client_data[0]); - $client->saveQuietly(); - - $contact = $client->contacts()->where('email', $ninja_client_data[1]['email'])->first(); - - if(!$contact) - { - $contact = ClientContactFactory::create($this->company->id, $this->company->owner()->id); - $contact->client_id = $client->id; - $contact->send_email = true; - $contact->is_primary = true; - $contact->fill($ninja_client_data[1]); - $contact->saveQuietly(); - } - elseif($this->updateGate('client')){ - $contact->fill($ninja_client_data[1]); - $contact->saveQuietly(); - } - - } - - } - } - - private function syncQbToNinjaVendors(array $records): void - { - - $transformer = new VendorTransformer($this->company); - - foreach($records as $record) - { - $ninja_data = $transformer->qbToNinja($record); - - if($vendor = $this->findVendor($ninja_data)) - { - $vendor->fill($ninja_data[0]); - $vendor->saveQuietly(); - - $contact = $vendor->contacts()->where('email', $ninja_data[1]['email'])->first(); - - if(!$contact) - { - $contact = VendorContactFactory::create($this->company->id, $this->company->owner()->id); - $contact->vendor_id = $vendor->id; - $contact->send_email = true; - $contact->is_primary = true; - $contact->fill($ninja_data[1]); - $contact->saveQuietly(); - } - elseif($this->updateGate('vendor')){ - $contact->fill($ninja_data[1]); - $contact->saveQuietly(); - } - - } - - } - } - - private function syncQbToNinjaExpenses(array $records): void - { - - $transformer = new ExpenseTransformer($this->company); - - foreach($records as $record) - { - $ninja_data = $transformer->qbToNinja($record); - - if($expense = $this->findExpense($ninja_data)) - { - $expense->fill($ninja_data); - $expense->saveQuietly(); - } - - } - } - - - private function syncQbToNinjaProducts($records): void - { - $product_transformer = new ProductTransformer($this->company); - - foreach($records as $record) - { - $ninja_data = $product_transformer->qbToNinja($record); - - if($product = $this->findProduct($ninja_data['hash'])) - { - $product->fill($ninja_data); - $product->save(); - } - } - } - - private function findExpense(array $qb_data): ?Expense - { - $expense = $qb_data; - - $search = Expense::query() - ->withTrashed() - ->where('company_id', $this->company->id) - ->where('number', $expense['number']); - - if($search->count() == 0) { - return ExpenseFactory::create($this->company->id, $this->company->owner()->id); - } - elseif($search->count() == 1) { - return $this->settings['expense']['update_record'] ? $search->first() : null; - } - - return null; - } - - private function findVendor(array $qb_data) :?Vendor - { - $vendor = $qb_data[0]; - $contact = $qb_data[1]; - $vendor_meta = $qb_data[2]; - - $search = Vendor::query() - ->withTrashed() - ->where('company_id', $this->company->id) - ->where(function ($q) use ($vendor, $vendor_meta, $contact){ - - $q->where('vendor_hash', $vendor_meta['vendor_hash']) - ->orWhere('number', $vendor['number']) - ->orWhereHas('contacts', function ($q) use ($contact){ - $q->where('email', $contact['email']); - }); - - }); - - if($search->count() == 0) { - //new client - return VendorFactory::create($this->company->id, $this->company->owner()->id); - } - elseif($search->count() == 1) { - return $this->settings['vendor']['update_record'] ? $search->first() : null; - } - - return null; - } - - private function findClient(array $qb_data) :?Client - { - $client = $qb_data[0]; - $contact = $qb_data[1]; - $client_meta = $qb_data[2]; - - $search = Client::query() - ->withTrashed() - ->where('company_id', $this->company->id) - ->where(function ($q) use ($client, $client_meta, $contact){ - - $q->where('client_hash', $client_meta['client_hash']) - ->orWhere('number', $client['number']) - ->orWhereHas('contacts', function ($q) use ($contact){ - $q->where('email', $contact['email']); - }); - - }); - - if($search->count() == 0) { - //new client - $client = ClientFactory::create($this->company->id, $this->company->owner()->id); - $client->client_hash = $client_meta['client_hash']; - $client->settings = $client_meta['settings']; - - return $client; - } - elseif($search->count() == 1) { - return $this->settings['client']['update_record'] ? $search->first() : null; - } - - return null; - } - - private function findProduct(string $key): ?Product - { - $search = Product::query() - ->withTrashed() - ->where('company_id', $this->company->id) - ->where('hash', $key); - - if($search->count() == 0) { - //new product - $product = ProductFactory::create($this->company->id, $this->company->owner()->id); - $product->hash = $key; - - return $product; - } elseif($search->count() == 1) { - return $this->settings['product']['update_record'] ? $search->first() : null; - } - - return null; - - - } - - public function middleware() - { - return [new WithoutOverlapping("qbs-{$this->company_id}-{$this->db}")]; - } - - public function failed($exception) - { - nlog("QuickbooksSync failed => ".$exception->getMessage()); - config(['queue.failed.driver' => null]); - - } -} diff --git a/app/Services/Quickbooks/Models/QbClient.php b/app/Services/Quickbooks/Models/QbClient.php new file mode 100644 index 000000000000..9ea3f6d90bfe --- /dev/null +++ b/app/Services/Quickbooks/Models/QbClient.php @@ -0,0 +1,116 @@ +service->sdk->FindById('Customer', $id); + } + + public function syncToNinja(array $records): void + { + + $transformer = new ClientTransformer($this->service->company); + + foreach ($records as $record) { + + $ninja_data = $transformer->qbToNinja($record); + + if($ninja_data[0]['terms']){ + + $days = $this->service->findEntityById('Term', $ninja_data[0]['terms']); + + nlog($days); + + if($days){ + $ninja_data[0]['settings']->payment_terms = (string)$days->DueDays; + } + + } + + if ($client = $this->findClient($ninja_data[0]['id'])) { + + $qbc = $this->find($ninja_data[0]['id']); + + $client->fill($ninja_data[0]); + $client->service()->applyNumber()->save(); + + $contact = $client->contacts()->where('email', $ninja_data[1]['email'])->first(); + + if(!$contact) + { + $contact = ClientContactFactory::create($this->service->company->id, $this->service->company->owner()->id); + $contact->client_id = $client->id; + $contact->send_email = true; + $contact->is_primary = true; + $contact->fill($ninja_data[1]); + $contact->saveQuietly(); + } + elseif($this->service->syncable('client', \App\Enum\SyncDirection::PULL)){ + $contact->fill($ninja_data[1]); + $contact->saveQuietly(); + } + + } + } + + } + + public function syncToForeign(array $records): void + { + } + + public function sync(string $id, string $last_updated): void + { + + } + + private function findClient(string $key): ?Client + { + $search = Client::query() + ->withTrashed() + ->where('company_id', $this->service->company->id) + ->where('sync->qb_id', $key); + + if ($search->count() == 0) { + + $client = ClientFactory::create($this->service->company->id, $this->service->company->owner()->id); + + $sync = new ClientSync(); + $sync->qb_id = $key; + $client->sync = $sync; + + return $client; + + } elseif ($search->count() == 1) { + return $this->service->syncable('client', \App\Enum\SyncDirection::PULL) ? $search->first() : null; + } + + return null; + + + } +} diff --git a/app/Services/Quickbooks/Models/QbInvoice.php b/app/Services/Quickbooks/Models/QbInvoice.php new file mode 100644 index 000000000000..52253fccc483 --- /dev/null +++ b/app/Services/Quickbooks/Models/QbInvoice.php @@ -0,0 +1,221 @@ +invoice_transformer = new InvoiceTransformer($this->service->company); + $this->invoice_repository = new InvoiceRepository(); + } + + public function find(string $id): mixed + { + return $this->service->sdk->FindById('Invoice', $id); + } + + public function syncToNinja(array $records): void + { + + foreach ($records as $record) { + + $this->syncNinjaInvoice($record); + + } + + } + + public function syncToForeign(array $records): void + { + + } + + private function qbInvoiceUpdate(array $ninja_invoice_data, Invoice $invoice): void + { + $current_ninja_invoice_balance = $invoice->balance; + $qb_invoice_balance = $ninja_invoice_data['balance']; + + if(floatval($current_ninja_invoice_balance) == floatval($qb_invoice_balance)) + { + nlog('Invoice balance is the same, skipping update of line items'); + unset($ninja_invoice_data['line_items']); + $invoice->fill($ninja_invoice_data); + $invoice->saveQuietly(); + } + else{ + nlog('Invoice balance is different, updating line items'); + $this->invoice_repository->save($ninja_invoice_data, $invoice); + } + } + + private function findInvoice(string $id, ?string $client_id = null): ?Invoice + { + $search = Invoice::query() + ->withTrashed() + ->where('company_id', $this->service->company->id) + ->where('sync->qb_id', $id); + + if($search->count() == 0) { + $invoice = InvoiceFactory::create($this->service->company->id, $this->service->company->owner()->id); + $invoice->client_id = (int)$client_id; + + $sync = new InvoiceSync(); + $sync->qb_id = $id; + $invoice->sync = $sync; + + return $invoice; + } elseif($search->count() == 1) { + return $this->service->syncable('invoice', \App\Enum\SyncDirection::PULL) ? $search->first() : null; + } + + return null; + + } + + public function sync($id, string $last_updated): void + { + + $qb_record = $this->find($id); + + nlog($qb_record); + + if($this->service->syncable('invoice', \App\Enum\SyncDirection::PULL)) + { + + $invoice = $this->findInvoice($id); + + nlog("Comparing QB last updated: " . $last_updated); + nlog("Comparing Ninja last updated: " . $invoice->updated_at); + + if(data_get($qb_record, 'TxnStatus') === 'Voided') + { + $this->delete($id); + return; + } + + if(!$invoice->id){ + $this->syncNinjaInvoice($qb_record); + } + elseif(Carbon::parse($last_updated)->gt(Carbon::parse($invoice->updated_at)) || $qb_record->SyncToken == '0') + { + $ninja_invoice_data = $this->invoice_transformer->qbToNinja($qb_record); + nlog($ninja_invoice_data); + + $this->invoice_repository->save($ninja_invoice_data, $invoice); + + } + + } + } + + /** + * syncNinjaInvoice + * + * @param $record + * @return void + */ + public function syncNinjaInvoice($record): void + { + + $ninja_invoice_data = $this->invoice_transformer->qbToNinja($record); + + nlog($ninja_invoice_data); + + $payment_ids = $ninja_invoice_data['payment_ids'] ?? []; + + $client_id = $ninja_invoice_data['client_id'] ?? null; + + if (is_null($client_id)) { + return; + } + + unset($ninja_invoice_data['payment_ids']); + + if ($invoice = $this->findInvoice($ninja_invoice_data['id'], $ninja_invoice_data['client_id'])) { + + if ($invoice->id) { + $this->qbInvoiceUpdate($ninja_invoice_data, $invoice); + } + + //new invoice scaffold + $invoice->fill($ninja_invoice_data); + $invoice->saveQuietly(); + + $invoice = $invoice->calc()->getInvoice()->service()->markSent()->applyNumber()->createInvitations()->save(); + + foreach ($payment_ids as $payment_id) { + + $payment = $this->service->sdk->FindById('Payment', $payment_id); + + $payment_transformer = new PaymentTransformer($this->service->company); + + $transformed = $payment_transformer->qbToNinja($payment); + + $ninja_payment = $payment_transformer->buildPayment($payment); + $ninja_payment->service()->applyNumber()->save(); + + $paymentable = new \App\Models\Paymentable(); + $paymentable->payment_id = $ninja_payment->id; + $paymentable->paymentable_id = $invoice->id; + $paymentable->paymentable_type = 'invoices'; + $paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount'); + $paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line + $paymentable->save(); + + $invoice->service()->applyPayment($ninja_payment, $paymentable->amount); + + } + + if ($record instanceof \QuickBooksOnline\API\Data\IPPSalesReceipt) { + $invoice->service()->markPaid()->save(); + } + + } + + $ninja_invoice_data = false; + + + } + + /** + * Deletes the invoice from Ninja and sets the sync to null + * + * @param string $id + * @return void + */ + public function delete($id): void + { + $qb_record = $this->find($id); + + if($this->service->syncable('invoice', \App\Enum\SyncDirection::PULL) && $invoice = $this->findInvoice($id)) + { + $invoice->sync = null; + $invoice->saveQuietly(); + $this->invoice_repository->delete($invoice); + } + } +} diff --git a/app/Services/Quickbooks/Models/QbProduct.php b/app/Services/Quickbooks/Models/QbProduct.php new file mode 100644 index 000000000000..085718546231 --- /dev/null +++ b/app/Services/Quickbooks/Models/QbProduct.php @@ -0,0 +1,102 @@ +product_transformer = new ProductTransformer($service->company); + + } + + public function find(string $id): mixed + { + return $this->service->sdk->FindById('Item', $id); + } + + public function syncToNinja(array $records): void + { + + foreach ($records as $record) { + + $ninja_data = $this->product_transformer->qbToNinja($record); + + if ($product = $this->findProduct($ninja_data['id'])) { + $product->fill($ninja_data); + $product->save(); + } + } + + } + + public function syncToForeign(array $records): void + { + } + + private function findProduct(string $key): ?Product + { + $search = Product::query() + ->withTrashed() + ->where('company_id', $this->service->company->id) + ->where('sync->qb_id', $key); + + if($search->count() == 0) { + + $product = ProductFactory::create($this->service->company->id, $this->service->company->owner()->id); + + $sync = new ProductSync(); + $sync->qb_id = $key; + $product->sync = $sync; + + return $product; + + } elseif($search->count() == 1) { + return $this->service->syncable('product', \App\Enum\SyncDirection::PULL) ? $search->first() : null; + } + + return null; + + } + + public function sync(string $id, string $last_updated): void + { + $qb_record = $this->find($id); + + if($this->service->syncable('product', \App\Enum\SyncDirection::PULL) && $ninja_record = $this->findProduct($id)) + { + + if(Carbon::parse($last_updated) > Carbon::parse($ninja_record->updated_at)) + { + $ninja_data = $this->product_transformer->qbToNinja($qb_record); + + $ninja_record->fill($ninja_data); + $ninja_record->save(); + + } + + } + + } +} \ No newline at end of file diff --git a/app/Services/Quickbooks/QuickbooksService.php b/app/Services/Quickbooks/QuickbooksService.php index 9d5b45fdd18f..fc222126a455 100644 --- a/app/Services/Quickbooks/QuickbooksService.php +++ b/app/Services/Quickbooks/QuickbooksService.php @@ -11,17 +11,21 @@ namespace App\Services\Quickbooks; -use App\Factory\ClientContactFactory; -use App\Factory\ClientFactory; -use App\Factory\InvoiceFactory; -use App\Factory\ProductFactory; use App\Models\Client; use App\Models\Company; use App\Models\Invoice; use App\Models\Product; -use App\Services\Quickbooks\Jobs\QuickbooksSync; +use App\Factory\ClientFactory; +use App\Factory\InvoiceFactory; +use App\Factory\ProductFactory; +use App\DataMapper\QuickbooksSync; +use App\Factory\ClientContactFactory; use QuickBooksOnline\API\Core\CoreConstants; +use App\Services\Quickbooks\Models\QbInvoice; +use App\Services\Quickbooks\Models\QbProduct; use QuickBooksOnline\API\DataService\DataService; +use App\Services\Quickbooks\Jobs\QuickbooksImport; +use App\Services\Quickbooks\Models\QbClient; use App\Services\Quickbooks\Transformers\ClientTransformer; use App\Services\Quickbooks\Transformers\InvoiceTransformer; use App\Services\Quickbooks\Transformers\PaymentTransformer; @@ -31,9 +35,19 @@ class QuickbooksService { public DataService $sdk; + public QbInvoice $invoice; + + public QbProduct $product; + + public QbClient $client; + + public QuickbooksSync $settings; + private bool $testMode = true; - public function __construct(private Company $company) + private bool $try_refresh = true; + + public function __construct(public Company $company) { $this->init(); } @@ -46,7 +60,6 @@ class QuickbooksService 'ClientSecret' => config('services.quickbooks.client_secret'), 'auth_mode' => 'oauth2', 'scope' => "com.intuit.quickbooks.accounting", - // 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', 'RedirectURI' => $this->testMode ? 'https://grok.romulus.com.au/quickbooks/authorized' : 'https://invoicing.co/quickbooks/authorized', 'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL, ]; @@ -55,18 +68,85 @@ class QuickbooksService $this->sdk = DataService::Configure($merged); - $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); + // $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); $this->sdk->enableLog(); $this->sdk->setMinorVersion("73"); $this->sdk->throwExceptionOnError(true); + $this->checkToken(); + + $this->invoice = new QbInvoice($this); + + $this->product = new QbProduct($this); + + $this->client = new QbClient($this); + + $this->settings = $this->company->quickbooks->settings; + + $this->checkDefaultAccounts(); + return $this; } + private function checkDefaultAccounts(): self + { + + $accountQuery = "SELECT * FROM Account WHERE AccountType IN ('Income', 'Cost of Goods Sold')"; + + if(strlen($this->settings->default_income_account) == 0 || strlen($this->settings->default_expense_account) == 0){ + + nlog("Checking default accounts for company {$this->company->company_key}"); + $accounts = $this->sdk->Query($accountQuery); + + nlog($accounts); + + $find_income_account = true; + $find_expense_account = true; + + foreach ($accounts as $account) { + if ($account->AccountType->value == 'Income' && $find_income_account) { + $this->settings->default_income_account = $account->Id->value; + $find_income_account = false; + } elseif ($account->AccountType->value == 'Cost of Goods Sold' && $find_expense_account) { + $this->settings->default_expense_account = $account->Id->value; + $find_expense_account = false; + } + } + + nlog($this->settings); + + $this->company->quickbooks->settings = $this->settings; + $this->company->save(); + } + + + return $this; + } + + private function checkToken(): self + { + + if($this->company->quickbooks->accessTokenExpiresAt == 0 || $this->company->quickbooks->accessTokenExpiresAt > time()) + return $this; + + if($this->company->quickbooks->accessTokenExpiresAt && $this->company->quickbooks->accessTokenExpiresAt < time() && $this->try_refresh){ + $this->sdk()->refreshToken($this->company->quickbooks->refresh_token); + $this->company = $this->company->fresh(); + $this->try_refresh = false; + $this->init(); + + return $this; + } + + nlog('Quickbooks token expired and could not be refreshed => ' .$this->company->company_key); + throw new \Exception('Quickbooks token expired and could not be refreshed'); + + } + private function ninjaAccessToken(): array { - return isset($this->company->quickbooks->accessTokenKey) ? [ + return $this->company->quickbooks->accessTokenExpiresAt > 0 ? [ 'accessTokenKey' => $this->company->quickbooks->accessTokenKey, 'refreshTokenKey' => $this->company->quickbooks->refresh_token, 'QBORealmID' => $this->company->quickbooks->realmID, @@ -85,7 +165,24 @@ class QuickbooksService */ public function syncFromQb(): void { - QuickbooksSync::dispatch($this->company->id, $this->company->db); + QuickbooksImport::dispatch($this->company->id, $this->company->db); + } + + public function findEntityById(string $entity, string $id): mixed + { + return $this->sdk->FindById($entity, $id); + } + + /** + * Flag to determine if a sync is allowed in either direction + * + * @param string $entity + * @param \App\Enum\SyncDirection $direction + * @return bool + */ + public function syncable(string $entity, \App\Enum\SyncDirection $direction): bool + { + return $this->settings->{$entity}->direction === $direction || $this->settings->{$entity}->direction === \App\Enum\SyncDirection::BIDIRECTIONAL; } } diff --git a/app/Services/Quickbooks/SdkWrapper.php b/app/Services/Quickbooks/SdkWrapper.php index 2845b747ab89..2fb7e688ac73 100644 --- a/app/Services/Quickbooks/SdkWrapper.php +++ b/app/Services/Quickbooks/SdkWrapper.php @@ -33,7 +33,7 @@ class SdkWrapper private function init(): self { - isset($this->company->quickbooks->accessTokenKey) ? $this->setNinjaAccessToken($this->company->quickbooks) : null; + $this->setNinjaAccessToken($this->company->quickbooks); return $this; @@ -104,16 +104,24 @@ class SdkWrapper $this->setAccessToken($token); - if($token_object->accessTokenExpiresAt < time()){ - $new_token = $this->sdk->getOAuth2LoginHelper()->refreshToken(); - - $this->setAccessToken($new_token); - $this->saveOAuthToken($this->accessToken()); + if($token_object->accessTokenExpiresAt != 0 && $token_object->accessTokenExpiresAt < time()){ + $this->refreshToken($token_object->refresh_token); } return $this; } + + public function refreshToken(string $refresh_token): self + { + $new_token = $this->sdk->getOAuth2LoginHelper()->refreshAccessTokenWithRefreshToken($refresh_token); + + $this->setAccessToken($new_token); + $this->saveOAuthToken($this->accessToken()); + + return $this; + } + /** * SetsAccessToken * diff --git a/app/Services/Quickbooks/Transformers/BaseTransformer.php b/app/Services/Quickbooks/Transformers/BaseTransformer.php index 66f3ff45195f..bc5639438f41 100644 --- a/app/Services/Quickbooks/Transformers/BaseTransformer.php +++ b/app/Services/Quickbooks/Transformers/BaseTransformer.php @@ -66,7 +66,8 @@ class BaseTransformer $client = Client::query() ->withTrashed() ->where('company_id', $this->company->id) - ->where('number', $customer_reference_id) + // ->where('number', $customer_reference_id) + ->where('sync->qb_id', $customer_reference_id) ->first(); return $client ? $client->id : null; diff --git a/app/Services/Quickbooks/Transformers/ClientTransformer.php b/app/Services/Quickbooks/Transformers/ClientTransformer.php index 998f12a1543c..6a320cb70f91 100644 --- a/app/Services/Quickbooks/Transformers/ClientTransformer.php +++ b/app/Services/Quickbooks/Transformers/ClientTransformer.php @@ -31,6 +31,7 @@ class ClientTransformer extends BaseTransformer public function transform(mixed $data): array { + nlog($data); $contact = [ 'first_name' => data_get($data, 'GivenName'), @@ -40,6 +41,7 @@ class ClientTransformer extends BaseTransformer ]; $client = [ + 'id' => data_get($data, 'Id.value', null), 'name' => data_get($data,'CompanyName', ''), 'address1' => data_get($data, 'BillAddr.Line1', ''), 'address2' => data_get($data, 'BillAddr.Line2', ''), @@ -53,16 +55,20 @@ class ClientTransformer extends BaseTransformer 'shipping_country_id' => $this->resolveCountry(data_get($data, 'ShipAddr.Country', '')), 'shipping_state' => data_get($data, 'ShipAddr.CountrySubDivisionCode', ''), 'shipping_postal_code' => data_get($data, 'BillAddr.PostalCode', ''), - 'number' => data_get($data, 'Id.value', ''), + 'client_hash' => data_get($data, 'V4IDPseudonym', \Illuminate\Support\Str::random(32)), + 'vat_number' => data_get($data, 'PrimaryTaxIdentifier', ''), + 'id_number' => data_get($data, 'BusinessNumber', ''), + 'terms' => data_get($data, 'SalesTermRef.value', false), + 'is_tax_exempt' => !data_get($data, 'Taxable', false), + 'private_notes' => data_get($data, 'Notes', ''), ]; $settings = ClientSettings::defaults(); $settings->currency_id = (string) $this->resolveCurrency(data_get($data, 'CurrencyRef.value')); - $new_client_merge = [ - 'client_hash' => data_get($data, 'V4IDPseudonym', \Illuminate\Support\Str::random(32)), - 'settings' => $settings, - ]; + $client['settings'] = $settings; + + $new_client_merge = []; return [$client, $contact, $new_client_merge]; } diff --git a/app/Services/Quickbooks/Transformers/InvoiceTransformer.php b/app/Services/Quickbooks/Transformers/InvoiceTransformer.php index 1ba7cedbfaf4..b48492e66336 100644 --- a/app/Services/Quickbooks/Transformers/InvoiceTransformer.php +++ b/app/Services/Quickbooks/Transformers/InvoiceTransformer.php @@ -38,6 +38,7 @@ class InvoiceTransformer extends BaseTransformer $client_id = $this->getClientId(data_get($qb_data, 'CustomerRef.value', null)); return $client_id ? [ + 'id' => data_get($qb_data, 'Id.value', false), 'client_id' => $client_id, 'number' => data_get($qb_data, 'DocNumber', false), 'date' => data_get($qb_data, 'TxnDate', now()->format('Y-m-d')), @@ -45,15 +46,62 @@ class InvoiceTransformer extends BaseTransformer 'public_notes' => data_get($qb_data, 'CustomerMemo.value', false), 'due_date' => data_get($qb_data, 'DueDate', null), 'po_number' => data_get($qb_data, 'PONumber', ""), - 'partial' => data_get($qb_data, 'Deposit', 0), - 'line_items' => $this->getLineItems(data_get($qb_data, 'Line', [])), + 'partial' => (float)data_get($qb_data, 'Deposit', 0), + 'line_items' => $this->getLineItems(data_get($qb_data, 'Line', []), data_get($qb_data, 'ApplyTaxAfterDiscount', 'true')), 'payment_ids' => $this->getPayments($qb_data), 'status_id' => Invoice::STATUS_SENT, - 'tax_rate1' => $rate = data_get($qb_data,'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0), + 'tax_rate1' => $rate = $this->calculateTotalTax($qb_data), 'tax_name1' => $rate > 0 ? "Sales Tax" : "", + 'custom_surcharge1' => $this->checkIfDiscountAfterTax($qb_data), + ] : false; } + private function checkIfDiscountAfterTax($qb_data) + { + + if($qb_data->ApplyTaxAfterDiscount == 'true'){ + return 0; + } + + foreach(data_get($qb_data, 'Line', []) as $line) + { + + if(data_get($line, 'DetailType.value') == 'DiscountLineDetail') + { + + if(!isset($this->company->custom_fields->surcharge1)) + { + $this->company->custom_fields->surcharge1 = ctrans('texts.discount'); + $this->company->save(); + } + + return (float)data_get($line, 'Amount', 0) * -1; + } + } + + return 0; + } + + private function calculateTotalTax($qb_data) + { + $taxLines = data_get($qb_data, 'TxnTaxDetail.TaxLine', []); + + if (!is_array($taxLines)) { + $taxLines = [$taxLines]; + } + + $totalTaxRate = 0; + + foreach ($taxLines as $taxLine) { + $taxRate = data_get($taxLine, 'TaxLineDetail.TaxPercent', 0); + $totalTaxRate += $taxRate; + } + + return (float)$totalTaxRate; + } + + private function getPayments(mixed $qb_data) { $payments = []; @@ -81,27 +129,45 @@ class InvoiceTransformer extends BaseTransformer } - private function getLineItems(mixed $qb_items) + private function getLineItems(mixed $qb_items, string $include_discount = 'true') { $items = []; foreach($qb_items as $qb_item) { - $item = new InvoiceItem; - $item->product_key = data_get($qb_item, 'SalesItemLineDetail.ItemRef.name', ''); - $item->notes = data_get($qb_item,'Description', ''); - $item->quantity = data_get($qb_item,'SalesItemLineDetail.Qty', 0); - $item->cost = data_get($qb_item, 'SalesItemLineDetail.UnitPrice', 0); - $item->discount = data_get($item,'DiscountRate', data_get($qb_item,'DiscountAmount', 0)); - $item->is_amount_discount = data_get($qb_item,'DiscountAmount', 0) > 0 ? true : false; - $item->type_id = stripos(data_get($qb_item, 'ItemAccountRef.name') ?? '', 'Service') !== false ? '2' : '1'; - $item->tax_id = data_get($qb_item, 'TaxCodeRef.value', '') == 'NON' ? Product::PRODUCT_TYPE_EXEMPT : $item->type_id; - $item->tax_rate1 = data_get($qb_item, 'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0); - $item->tax_name1 = $item->tax_rate1 > 0 ? "Sales Tax" : ""; - $items[] = (object)$item; - } - nlog($items); + if(data_get($qb_item, 'DetailType.value') == 'SalesItemLineDetail') + { + $item = new InvoiceItem; + $item->product_key = data_get($qb_item, 'SalesItemLineDetail.ItemRef.name', ''); + $item->notes = data_get($qb_item,'Description', ''); + $item->quantity = (float)data_get($qb_item,'SalesItemLineDetail.Qty', 0); + $item->cost = (float)data_get($qb_item, 'SalesItemLineDetail.UnitPrice', 0); + $item->discount = (float)data_get($item,'DiscountRate', data_get($qb_item,'DiscountAmount', 0)); + $item->is_amount_discount = data_get($qb_item,'DiscountAmount', 0) > 0 ? true : false; + $item->type_id = stripos(data_get($qb_item, 'ItemAccountRef.name') ?? '', 'Service') !== false ? '2' : '1'; + $item->tax_id = data_get($qb_item, 'TaxCodeRef.value', '') == 'NON' ? Product::PRODUCT_TYPE_EXEMPT : $item->type_id; + $item->tax_rate1 = (float)data_get($qb_item, 'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0); + $item->tax_name1 = $item->tax_rate1 > 0 ? "Sales Tax" : ""; + $items[] = (object)$item; + } + + if(data_get($qb_item, 'DetailType.value') == 'DiscountLineDetail' && $include_discount == 'true') + { + + $item = new InvoiceItem(); + $item->product_key = ctrans('texts.discount'); + $item->notes = ctrans('texts.discount'); + $item->quantity = 1; + $item->cost = (float)data_get($qb_item, 'Amount', 0) * -1; + $item->discount = 0; + $item->is_amount_discount = true; + $item->type_id = '1'; + $item->tax_id = Product::PRODUCT_TYPE_PHYSICAL; + $items[] = (object)$item; + + } + } return $items; diff --git a/app/Services/Quickbooks/Transformers/PaymentTransformer.php b/app/Services/Quickbooks/Transformers/PaymentTransformer.php index e5eb7f38ae28..c29094c25323 100644 --- a/app/Services/Quickbooks/Transformers/PaymentTransformer.php +++ b/app/Services/Quickbooks/Transformers/PaymentTransformer.php @@ -91,7 +91,7 @@ class PaymentTransformer extends BaseTransformer if(!$credit_line) return $payment; - $credit = \App\Factory\CreditFactory::create($this->company->id, $this->company->owner()->id, $payment->client_id); + $credit = \App\Factory\CreditFactory::create($this->company->id, $this->company->owner()->id); $credit->client_id = $payment->client_id; $line = new \App\DataMapper\InvoiceItem(); diff --git a/app/Services/Quickbooks/Transformers/ProductTransformer.php b/app/Services/Quickbooks/Transformers/ProductTransformer.php index 7b638d23e559..8a93d607fe85 100644 --- a/app/Services/Quickbooks/Transformers/ProductTransformer.php +++ b/app/Services/Quickbooks/Transformers/ProductTransformer.php @@ -30,10 +30,9 @@ class ProductTransformer extends BaseTransformer public function transform(mixed $data): array { - nlog(data_get($data, 'Id', null)); - + return [ - 'hash' => data_get($data, 'Id.value', null), + 'id' => data_get($data, 'Id.value', null), 'product_key' => data_get($data, 'Name', data_get($data, 'FullyQualifiedName','')), 'notes' => data_get($data, 'Description', ''), 'cost' => data_get($data, 'PurchaseCost', 0), diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index 415f6f5b7092..c619414a7b98 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -166,6 +166,10 @@ class InvoiceTransformer extends EntityTransformer $data['reminder_schedule'] = (string) $invoice->reminderSchedule(); } + if (request()->has('is_locked') && request()->query('is_locked') == 'true') { + $data['is_locked'] = (bool) $invoice->isLocked(); + } + return $data; } diff --git a/composer.json b/composer.json index f67661f328ed..d188feead9a7 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "authorizenet/authorizenet": "^2.0", "awobaz/compoships": "^2.1", "aws/aws-sdk-php": "^3.319", + "babenkoivan/elastic-scout-driver": "^4.0", "bacon/bacon-qr-code": "^2.0", "beganovich/snappdf": "dev-master", "braintree/braintree_php": "^6.0", @@ -68,6 +69,7 @@ "josemmo/facturae-php": "^1.7", "laracasts/presenter": "^0.2.1", "laravel/framework": "^11.0", + "laravel/scout": "^10.11", "laravel/slack-notification-channel": "^3", "laravel/socialite": "^5", "laravel/tinker": "^2.7", diff --git a/composer.lock b/composer.lock index 3646644d1739..737ab9f6630d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a1fac503f165997d7aa339239e002230", + "content-hash": "79894c80128f463131a8bf601f5fbfcb", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -376,16 +376,16 @@ }, { "name": "authorizenet/authorizenet", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/AuthorizeNet/sdk-php.git", - "reference": "e1acf55c9cb22bef1852b1e494502973ade11cce" + "reference": "8555cc245953dd0ac57f7ea424a5572eae4c7191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AuthorizeNet/sdk-php/zipball/e1acf55c9cb22bef1852b1e494502973ade11cce", - "reference": "e1acf55c9cb22bef1852b1e494502973ade11cce", + "url": "https://api.github.com/repos/AuthorizeNet/sdk-php/zipball/8555cc245953dd0ac57f7ea424a5572eae4c7191", + "reference": "8555cc245953dd0ac57f7ea424a5572eae4c7191", "shasum": "" }, "require": { @@ -413,9 +413,9 @@ ], "support": { "issues": "https://github.com/AuthorizeNet/sdk-php/issues", - "source": "https://github.com/AuthorizeNet/sdk-php/tree/2.0.3" + "source": "https://github.com/AuthorizeNet/sdk-php/tree/2.0.4" }, - "time": "2024-05-29T17:33:13+00:00" + "time": "2024-09-18T06:23:52+00:00" }, { "name": "awobaz/compoships", @@ -535,16 +535,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.321.11", + "version": "3.322.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb" + "reference": "6a329cf111a4e54f2ca0e87ce07dd0b9e0befdad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bbd357d246350ffcd0dd8df30951d2d46c5ddadb", - "reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6a329cf111a4e54f2ca0e87ce07dd0b9e0befdad", + "reference": "6a329cf111a4e54f2ca0e87ce07dd0b9e0befdad", "shasum": "" }, "require": { @@ -627,9 +627,220 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.321.11" + "source": "https://github.com/aws/aws-sdk-php/tree/3.322.2" }, - "time": "2024-09-13T18:05:10+00:00" + "time": "2024-09-20T18:08:53+00:00" + }, + { + "name": "babenkoivan/elastic-adapter", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-adapter.git", + "reference": "3a1fbb2d30c0b9e84c50204c1406cb7e44e6d2a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-adapter/zipball/3a1fbb2d30c0b9e84c50204c1406cb7e44e6d2a1", + "reference": "3a1fbb2d30c0b9e84c50204c1406cb7e44e6d2a1", + "shasum": "" + }, + "require": { + "babenkoivan/elastic-client": "^3.0", + "php": "^8.2" + }, + "require-dev": { + "dg/bypass-finals": "^1.7", + "friendsofphp/php-cs-fixer": "^3.14", + "orchestra/testbench": "^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elastic\\Adapter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Adapter for official PHP Elasticsearch client", + "keywords": [ + "adapter", + "client", + "elastic", + "elasticsearch", + "php" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-adapter/issues", + "source": "https://github.com/babenkoivan/elastic-adapter/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2024-06-18T06:57:10+00:00" + }, + { + "name": "babenkoivan/elastic-client", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-client.git", + "reference": "65f4a4c9dc3b5f6ba4e68e59ad26a68393aae995" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-client/zipball/65f4a4c9dc3b5f6ba4e68e59ad26a68393aae995", + "reference": "65f4a4c9dc3b5f6ba4e68e59ad26a68393aae995", + "shasum": "" + }, + "require": { + "elasticsearch/elasticsearch": "^8.0", + "php": "^8.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14", + "orchestra/testbench": "^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Elastic\\Client\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Elastic\\Client\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "The official PHP Elasticsearch client integrated with Laravel", + "keywords": [ + "client", + "elastic", + "elasticsearch", + "laravel", + "php" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-client/issues", + "source": "https://github.com/babenkoivan/elastic-client/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2024-06-18T06:53:01+00:00" + }, + { + "name": "babenkoivan/elastic-scout-driver", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/babenkoivan/elastic-scout-driver.git", + "reference": "f3791521fb3216850335f491a1461a16738125cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/babenkoivan/elastic-scout-driver/zipball/f3791521fb3216850335f491a1461a16738125cd", + "reference": "f3791521fb3216850335f491a1461a16738125cd", + "shasum": "" + }, + "require": { + "babenkoivan/elastic-adapter": "^4.0", + "php": "^8.2" + }, + "require-dev": { + "babenkoivan/elastic-migrations": "^4.0", + "friendsofphp/php-cs-fixer": "^3.14", + "laravel/legacy-factories": "^1.3", + "laravel/scout": "^10.0", + "orchestra/testbench": "^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Elastic\\ScoutDriver\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Elastic\\ScoutDriver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Babenko", + "email": "babenko.i.a@gmail.com" + } + ], + "description": "Elasticsearch driver for Laravel Scout", + "keywords": [ + "driver", + "elastic", + "elasticsearch", + "laravel", + "php", + "scout" + ], + "support": { + "issues": "https://github.com/babenkoivan/elastic-scout-driver/issues", + "source": "https://github.com/babenkoivan/elastic-scout-driver/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/ivanbabenko", + "type": "ko-fi" + }, + { + "url": "https://paypal.me/babenkoi", + "type": "paypal" + } + ], + "time": "2024-06-18T07:06:48+00:00" }, { "name": "bacon/bacon-qr-code", @@ -743,16 +954,16 @@ }, { "name": "braintree/braintree_php", - "version": "6.19.0", + "version": "6.20.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "f3178632ca098d1f96a429d665aabc4e95346c03" + "reference": "f46d6d570a955561f9474e0c4e5c56ca4cc22c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/f3178632ca098d1f96a429d665aabc4e95346c03", - "reference": "f3178632ca098d1f96a429d665aabc4e95346c03", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/f46d6d570a955561f9474e0c4e5c56ca4cc22c49", + "reference": "f46d6d570a955561f9474e0c4e5c56ca4cc22c49", "shasum": "" }, "require": { @@ -786,9 +997,9 @@ "description": "Braintree PHP Client Library", "support": { "issues": "https://github.com/braintree/braintree_php/issues", - "source": "https://github.com/braintree/braintree_php/tree/6.19.0" + "source": "https://github.com/braintree/braintree_php/tree/6.20.0" }, - "time": "2024-07-23T20:09:58+00:00" + "time": "2024-09-19T21:28:15+00:00" }, { "name": "brick/math", @@ -852,16 +1063,16 @@ }, { "name": "btcpayserver/btcpayserver-greenfield-php", - "version": "v2.7.0", + "version": "v2.7.1", "source": { "type": "git", "url": "https://github.com/btcpayserver/btcpayserver-greenfield-php.git", - "reference": "5e2ba7e3f585fc8e6dc068e22a0efbfdacd9c992" + "reference": "28197bf65fd4a0ba39598fac5651fec4a805b78c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/5e2ba7e3f585fc8e6dc068e22a0efbfdacd9c992", - "reference": "5e2ba7e3f585fc8e6dc068e22a0efbfdacd9c992", + "url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/28197bf65fd4a0ba39598fac5651fec4a805b78c", + "reference": "28197bf65fd4a0ba39598fac5651fec4a805b78c", "shasum": "" }, "require": { @@ -900,9 +1111,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.7.0" + "source": "https://github.com/btcpayserver/btcpayserver-greenfield-php/tree/v2.7.1" }, - "time": "2024-09-13T14:54:13+00:00" + "time": "2024-09-16T21:02:29+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -1950,6 +2161,122 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "elastic/transport", + "version": "v8.10.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elastic-transport-php.git", + "reference": "8be37d679637545e50b1cea9f8ee903888783021" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elastic-transport-php/zipball/8be37d679637545e50b1cea9f8ee903888783021", + "reference": "8be37d679637545e50b1cea9f8ee903888783021", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "open-telemetry/api": "^1.0", + "php": "^7.4 || ^8.0", + "php-http/discovery": "^1.14", + "php-http/httplug": "^2.3", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "nyholm/psr7": "^1.5", + "open-telemetry/sdk": "^1.0", + "php-http/mock-client": "^1.5", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "symfony/http-client": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elastic\\Transport\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "HTTP transport PHP library for Elastic products", + "keywords": [ + "PSR_17", + "elastic", + "http", + "psr-18", + "psr-7", + "transport" + ], + "support": { + "issues": "https://github.com/elastic/elastic-transport-php/issues", + "source": "https://github.com/elastic/elastic-transport-php/tree/v8.10.0" + }, + "time": "2024-08-14T08:55:07+00:00" + }, + { + "name": "elasticsearch/elasticsearch", + "version": "v8.15.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elasticsearch-php.git", + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "shasum": "" + }, + "require": { + "elastic/transport": "^8.10", + "guzzlehttp/guzzle": "^7.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "ext-yaml": "*", + "ext-zip": "*", + "mockery/mockery": "^1.5", + "nyholm/psr7": "^1.5", + "php-http/message-factory": "^1.0", + "php-http/mock-client": "^1.5", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "psr/http-factory": "^1.0", + "symfony/finder": "~4.0", + "symfony/http-client": "^5.0|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elastic\\Elasticsearch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP Client for Elasticsearch", + "keywords": [ + "client", + "elastic", + "elasticsearch", + "search" + ], + "support": { + "issues": "https://github.com/elastic/elasticsearch-php/issues", + "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0" + }, + "time": "2024-08-14T14:32:50+00:00" + }, { "name": "endroid/qr-code", "version": "5.0.7", @@ -4820,6 +5147,84 @@ }, "time": "2024-08-12T22:06:33+00:00" }, + { + "name": "laravel/scout", + "version": "v10.11.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/scout.git", + "reference": "642b4750127b5242a089571c9314037a7453cc0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/scout/zipball/642b4750127b5242a089571c9314037a7453cc0a", + "reference": "642b4750127b5242a089571c9314037a7453cc0a", + "shasum": "" + }, + "require": { + "illuminate/bus": "^9.0|^10.0|^11.0", + "illuminate/contracts": "^9.0|^10.0|^11.0", + "illuminate/database": "^9.0|^10.0|^11.0", + "illuminate/http": "^9.0|^10.0|^11.0", + "illuminate/pagination": "^9.0|^10.0|^11.0", + "illuminate/queue": "^9.0|^10.0|^11.0", + "illuminate/support": "^9.0|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "algolia/algoliasearch-client-php": "^3.2", + "meilisearch/meilisearch-php": "^1.0", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^7.31|^8.11|^9.0", + "php-http/guzzle7-adapter": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3|^10.4", + "typesense/typesense-php": "^4.9.3" + }, + "suggest": { + "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).", + "meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0).", + "typesense/typesense-php": "Required to use the Typesense engine (^4.9)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Scout\\ScoutServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Scout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Scout provides a driver based solution to searching your Eloquent models.", + "keywords": [ + "algolia", + "laravel", + "search" + ], + "support": { + "issues": "https://github.com/laravel/scout/issues", + "source": "https://github.com/laravel/scout" + }, + "time": "2024-09-11T21:32:42+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.3.4", @@ -5819,16 +6224,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -5859,7 +6264,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -5871,7 +6276,7 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { "name": "league/oauth1-client", @@ -5951,16 +6356,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.6", + "version": "v3.5.8", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f" + "reference": "ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/597a2808d8d3001cc3ed5ce89a6ebab00f83b80f", - "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f", + "url": "https://api.github.com/repos/livewire/livewire/zipball/ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae", + "reference": "ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae", "shasum": "" }, "require": { @@ -6015,7 +6420,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.6" + "source": "https://github.com/livewire/livewire/tree/v3.5.8" }, "funding": [ { @@ -6023,7 +6428,7 @@ "type": "github" } ], - "time": "2024-08-19T11:52:18+00:00" + "time": "2024-09-20T19:41:19+00:00" }, { "name": "maennchen/zipstream-php", @@ -6395,16 +6800,16 @@ }, { "name": "mindee/mindee", - "version": "v1.10.0", + "version": "v1.11.1", "source": { "type": "git", "url": "https://github.com/mindee/mindee-api-php.git", - "reference": "40865a03e34bb2416b32e5e1dd4937020e7bcc27" + "reference": "1ffbbdab646202f6b9547d12399841feba75c68e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mindee/mindee-api-php/zipball/40865a03e34bb2416b32e5e1dd4937020e7bcc27", - "reference": "40865a03e34bb2416b32e5e1dd4937020e7bcc27", + "url": "https://api.github.com/repos/mindee/mindee-api-php/zipball/1ffbbdab646202f6b9547d12399841feba75c68e", + "reference": "1ffbbdab646202f6b9547d12399841feba75c68e", "shasum": "" }, "require": { @@ -6414,7 +6819,6 @@ "php": ">=7.4", "setasign/fpdf": "^1.8", "setasign/fpdi": "^2.6", - "spatie/pdf-to-image": "^1.2", "symfony/console": ">=5.4" }, "require-dev": { @@ -6444,9 +6848,9 @@ "description": "Mindee Client Library for PHP", "support": { "issues": "https://github.com/mindee/mindee-api-php/issues", - "source": "https://github.com/mindee/mindee-api-php/tree/v1.10.0" + "source": "https://github.com/mindee/mindee-api-php/tree/v1.11.1" }, - "time": "2024-09-04T15:40:29+00:00" + "time": "2024-09-20T14:46:42+00:00" }, { "name": "mollie/mollie-api-php", @@ -7470,16 +7874,16 @@ }, { "name": "nwidart/laravel-modules", - "version": "v11.1.0", + "version": "v11.1.2", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "2ae13812f055a85d7063e90366884cd327877821" + "reference": "d275a5b9f7c329c505480750d354a7eef69fc42a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/2ae13812f055a85d7063e90366884cd327877821", - "reference": "2ae13812f055a85d7063e90366884cd327877821", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/d275a5b9f7c329c505480750d354a7eef69fc42a", + "reference": "d275a5b9f7c329c505480750d354a7eef69fc42a", "shasum": "" }, "require": { @@ -7541,7 +7945,7 @@ ], "support": { "issues": "https://github.com/nWidart/laravel-modules/issues", - "source": "https://github.com/nWidart/laravel-modules/tree/v11.1.0" + "source": "https://github.com/nWidart/laravel-modules/tree/v11.1.2" }, "funding": [ { @@ -7553,7 +7957,7 @@ "type": "github" } ], - "time": "2024-09-13T19:24:08+00:00" + "time": "2024-09-20T08:45:18+00:00" }, { "name": "nyholm/psr7", @@ -7633,6 +8037,134 @@ ], "time": "2024-09-09T07:06:30+00:00" }, + { + "name": "open-telemetry/api", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "87de95d926f46262885d0d390060c095af13e2e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/87de95d926f46262885d0d390060c095af13e2e5", + "reference": "87de95d926f46262885d0d390060c095af13e2e5", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.0", + "php": "^7.4 || ^8.0", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php80": "^1.26", + "symfony/polyfill-php81": "^1.26", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-02-06T01:32:25+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", + "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1.26", + "symfony/polyfill-php81": "^1.26", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-01-13T05:50:44+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.0.0", @@ -10317,16 +10849,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "4.8.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7" + "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2bbcb7e81097993cf64d5b296eaa6d396cddd5a7", - "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/73078e1f26d57f7a10e3bee2a2f543a02f6493c3", + "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3", "shasum": "" }, "require": { @@ -10390,7 +10922,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.8.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.9.0" }, "funding": [ { @@ -10402,7 +10934,7 @@ "type": "custom" } ], - "time": "2024-08-15T19:03:01+00:00" + "time": "2024-09-19T12:58:53+00:00" }, { "name": "setasign/fpdf", @@ -10772,66 +11304,6 @@ }, "time": "2024-07-12T02:43:55+00:00" }, - { - "name": "spatie/pdf-to-image", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/spatie/pdf-to-image.git", - "reference": "9a5cb264a99e87e010c65d4ece03b51f821d55bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/pdf-to-image/zipball/9a5cb264a99e87e010c65d4ece03b51f821d55bd", - "reference": "9a5cb264a99e87e010c65d4ece03b51f821d55bd", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "scrutinizer/ocular": "~1.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Spatie\\PdfToImage\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "Convert a pdf to an image", - "homepage": "https://github.com/spatie/pdf-to-image", - "keywords": [ - "convert", - "image", - "pdf", - "pdf-to-image", - "spatie" - ], - "support": { - "issues": "https://github.com/spatie/pdf-to-image/issues", - "source": "https://github.com/spatie/pdf-to-image/tree/1.2.2" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2016-12-14T15:37:00+00:00" - }, { "name": "sprain/swiss-qr-bill", "version": "v4.14", @@ -11161,16 +11633,16 @@ }, { "name": "symfony/console", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", "shasum": "" }, "require": { @@ -11234,7 +11706,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.4" + "source": "https://github.com/symfony/console/tree/v7.1.5" }, "funding": [ { @@ -11250,7 +11722,7 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:53+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/css-selector", @@ -11617,16 +12089,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", "shasum": "" }, "require": { @@ -11663,7 +12135,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" }, "funding": [ { @@ -11679,7 +12151,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/finder", @@ -11747,16 +12219,16 @@ }, { "name": "symfony/http-client", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65" + "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4c92046bb788648ff1098cc66da69aa7eac8cb65", - "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65", + "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", + "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", "shasum": "" }, "require": { @@ -11820,7 +12292,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.11" + "source": "https://github.com/symfony/http-client/tree/v6.4.12" }, "funding": [ { @@ -11836,7 +12308,7 @@ "type": "tidelift" } ], - "time": "2024-08-26T06:30:21+00:00" + "time": "2024-09-20T08:21:33+00:00" }, { "name": "symfony/http-client-contracts", @@ -11918,16 +12390,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.1.3", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" + "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e30ef73b1e44eea7eb37ba69600a354e553f694b", + "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b", "shasum": "" }, "require": { @@ -11975,7 +12447,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.5" }, "funding": [ { @@ -11991,20 +12463,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" + "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", - "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/44204d96150a9df1fc57601ec933d23fefc2d65b", + "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b", "shasum": "" }, "require": { @@ -12089,7 +12561,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.5" }, "funding": [ { @@ -12105,20 +12577,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T17:02:28+00:00" + "time": "2024-09-21T06:09:21+00:00" }, { "name": "symfony/intl", - "version": "v7.1.1", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c" + "reference": "a0ba7a400e4c915500762c998355bea219a32d6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", + "url": "https://api.github.com/repos/symfony/intl/zipball/a0ba7a400e4c915500762c998355bea219a32d6b", + "reference": "a0ba7a400e4c915500762c998355bea219a32d6b", "shasum": "" }, "require": { @@ -12175,7 +12647,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.1.1" + "source": "https://github.com/symfony/intl/tree/v7.1.5" }, "funding": [ { @@ -12191,20 +12663,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee" + "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee", - "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee", + "url": "https://api.github.com/repos/symfony/mailer/zipball/bbf21460c56f29810da3df3e206e38dfbb01e80b", + "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b", "shasum": "" }, "require": { @@ -12255,7 +12727,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.2" + "source": "https://github.com/symfony/mailer/tree/v7.1.5" }, "funding": [ { @@ -12271,7 +12743,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T08:00:31+00:00" + "time": "2024-09-08T12:32:26+00:00" }, { "name": "symfony/mailgun-mailer", @@ -12344,16 +12816,16 @@ }, { "name": "symfony/mime", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ccaa6c2503db867f472a587291e764d6a1e58758" + "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758", - "reference": "ccaa6c2503db867f472a587291e764d6a1e58758", + "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff", + "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff", "shasum": "" }, "require": { @@ -12408,7 +12880,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.4" + "source": "https://github.com/symfony/mime/tree/v7.1.5" }, "funding": [ { @@ -12424,7 +12896,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/options-resolver", @@ -13134,6 +13606,82 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php82", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php83", "version": "v1.31.0", @@ -13361,16 +13909,16 @@ }, { "name": "symfony/process", - "version": "v7.1.3", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + "reference": "5c03ee6369281177f07f7c68252a280beccba847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", + "reference": "5c03ee6369281177f07f7c68252a280beccba847", "shasum": "" }, "require": { @@ -13402,7 +13950,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.3" + "source": "https://github.com/symfony/process/tree/v7.1.5" }, "funding": [ { @@ -13418,7 +13966,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:44:47+00:00" + "time": "2024-09-19T21:48:23+00:00" }, { "name": "symfony/property-access", @@ -13746,16 +14294,16 @@ }, { "name": "symfony/serializer", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "0158b0e91b7cf7e744a6fb9acaeb613d1ca40dbb" + "reference": "71d6e1f70f00752d1469d0f5e83b0a51716f288b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/0158b0e91b7cf7e744a6fb9acaeb613d1ca40dbb", - "reference": "0158b0e91b7cf7e744a6fb9acaeb613d1ca40dbb", + "url": "https://api.github.com/repos/symfony/serializer/zipball/71d6e1f70f00752d1469d0f5e83b0a51716f288b", + "reference": "71d6e1f70f00752d1469d0f5e83b0a51716f288b", "shasum": "" }, "require": { @@ -13769,12 +14317,14 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", + "symfony/type-info": "<7.1.5", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0", "seld/jsonlint": "^1.10", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", @@ -13790,7 +14340,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.5", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -13823,7 +14373,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.1.4" + "source": "https://github.com/symfony/serializer/tree/v7.1.5" }, "funding": [ { @@ -13839,7 +14389,7 @@ "type": "tidelift" } ], - "time": "2024-08-22T09:39:57+00:00" + "time": "2024-09-20T12:13:15+00:00" }, { "name": "symfony/service-contracts", @@ -13926,16 +14476,16 @@ }, { "name": "symfony/string", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -13993,7 +14543,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.4" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -14009,20 +14559,20 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/translation", - "version": "v7.1.3", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" + "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", - "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", + "url": "https://api.github.com/repos/symfony/translation/zipball/235535e3f84f3dfbdbde0208ede6ca75c3a489ea", + "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea", "shasum": "" }, "require": { @@ -14087,7 +14637,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.3" + "source": "https://github.com/symfony/translation/tree/v7.1.5" }, "funding": [ { @@ -14103,7 +14653,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-09-16T06:30:38+00:00" }, { "name": "symfony/translation-contracts", @@ -14185,16 +14735,16 @@ }, { "name": "symfony/type-info", - "version": "v7.1.1", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc" + "reference": "9f6094aa900d2c06bd61576a6f279d4ac441515f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/60b28eb733f1453287f1263ed305b96091e0d1dc", - "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc", + "url": "https://api.github.com/repos/symfony/type-info/zipball/9f6094aa900d2c06bd61576a6f279d4ac441515f", + "reference": "9f6094aa900d2c06bd61576a6f279d4ac441515f", "shasum": "" }, "require": { @@ -14247,7 +14797,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.1.1" + "source": "https://github.com/symfony/type-info/tree/v7.1.5" }, "funding": [ { @@ -14263,20 +14813,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:59:31+00:00" + "time": "2024-09-19T21:48:23+00:00" }, { "name": "symfony/uid", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "82177535395109075cdb45a70533aa3d7a521cdf" + "reference": "8c7bb8acb933964055215d89f9a9871df0239317" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf", - "reference": "82177535395109075cdb45a70533aa3d7a521cdf", + "url": "https://api.github.com/repos/symfony/uid/zipball/8c7bb8acb933964055215d89f9a9871df0239317", + "reference": "8c7bb8acb933964055215d89f9a9871df0239317", "shasum": "" }, "require": { @@ -14321,7 +14871,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.4" + "source": "https://github.com/symfony/uid/tree/v7.1.5" }, "funding": [ { @@ -14337,20 +14887,20 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/validator", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "0d7e0dfd41702d6b9356214b76110421c1e74368" + "reference": "e57592782dc2a86997477f28164c51af53512ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/0d7e0dfd41702d6b9356214b76110421c1e74368", - "reference": "0d7e0dfd41702d6b9356214b76110421c1e74368", + "url": "https://api.github.com/repos/symfony/validator/zipball/e57592782dc2a86997477f28164c51af53512ad8", + "reference": "e57592782dc2a86997477f28164c51af53512ad8", "shasum": "" }, "require": { @@ -14418,7 +14968,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.1.4" + "source": "https://github.com/symfony/validator/tree/v7.1.5" }, "funding": [ { @@ -14434,20 +14984,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T15:58:06+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" + "reference": "e20e03889539fd4e4211e14d2179226c513c010d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", - "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e20e03889539fd4e4211e14d2179226c513c010d", + "reference": "e20e03889539fd4e4211e14d2179226c513c010d", "shasum": "" }, "require": { @@ -14501,7 +15051,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.5" }, "funding": [ { @@ -14517,20 +15067,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T16:12:47+00:00" + "time": "2024-09-16T10:07:02+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "be37e7f13195e05ab84ca5269365591edd240335" + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335", - "reference": "be37e7f13195e05ab84ca5269365591edd240335", + "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", "shasum": "" }, "require": { @@ -14573,7 +15123,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.11" + "source": "https://github.com/symfony/yaml/tree/v6.4.12" }, "funding": [ { @@ -14589,7 +15139,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:55:28+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -15216,23 +15766,23 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.13.5", + "version": "v3.14.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07" + "reference": "16a13cc5221aee90ae20aa59083ced2211e714eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/16a13cc5221aee90ae20aa59083ced2211e714eb", + "reference": "16a13cc5221aee90ae20aa59083ced2211e714eb", "shasum": "" }, "require": { "illuminate/routing": "^9|^10|^11", "illuminate/session": "^9|^10|^11", "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.22.0", + "maximebf/debugbar": "~1.23.0", "php": "^8.0", "symfony/finder": "^6|^7" }, @@ -15245,7 +15795,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.13-dev" + "dev-master": "3.14-dev" }, "laravel": { "providers": [ @@ -15284,7 +15834,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.0" }, "funding": [ { @@ -15296,7 +15846,7 @@ "type": "github" } ], - "time": "2024-04-12T11:20:37+00:00" + "time": "2024-09-20T12:16:37+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -15446,16 +15996,16 @@ }, { "name": "brianium/paratest", - "version": "v7.5.4", + "version": "v7.5.5", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "c490591cc9c2f4830633b905547d30d5eb609c88" + "reference": "f29c7d671afc5c4e1140bd7b9f2749e827902a1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/c490591cc9c2f4830633b905547d30d5eb609c88", - "reference": "c490591cc9c2f4830633b905547d30d5eb609c88", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/f29c7d671afc5c4e1140bd7b9f2749e827902a1e", + "reference": "f29c7d671afc5c4e1140bd7b9f2749e827902a1e", "shasum": "" }, "require": { @@ -15469,7 +16019,7 @@ "phpunit/php-code-coverage": "^11.0.6", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.3.3", + "phpunit/phpunit": "^11.3.6", "sebastian/environment": "^7.2.0", "symfony/console": "^6.4.11 || ^7.1.4", "symfony/process": "^6.4.8 || ^7.1.3" @@ -15479,11 +16029,11 @@ "ext-pcov": "*", "ext-posix": "*", "infection/infection": "^0.29.6", - "phpstan/phpstan": "^1.12.1", - "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan": "^1.12.4", + "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", - "squizlabs/php_codesniffer": "^3.10.2", + "squizlabs/php_codesniffer": "^3.10.3", "symfony/filesystem": "^6.4.9 || ^7.1.2" }, "bin": [ @@ -15524,7 +16074,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.5.4" + "source": "https://github.com/paratestphp/paratest/tree/v7.5.5" }, "funding": [ { @@ -15536,7 +16086,7 @@ "type": "paypal" } ], - "time": "2024-09-04T21:15:27+00:00" + "time": "2024-09-20T12:57:46+00:00" }, { "name": "clue/ndjson-react", @@ -15756,24 +16306,24 @@ }, { "name": "composer/semver", - "version": "3.4.2", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -15817,7 +16367,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.2" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -15833,7 +16383,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T11:35:52+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", @@ -16397,16 +16947,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.22.5", + "version": "v1.23.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989" + "reference": "689720d724c771ac4add859056744b7b3f2406da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1b5cabe0ce013134cf595bfa427bbf2f6abcd989", - "reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da", + "reference": "689720d724c771ac4add859056744b7b3f2406da", "shasum": "" }, "require": { @@ -16428,7 +16978,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.22-dev" + "dev-master": "1.23-dev" } }, "autoload": { @@ -16459,9 +17009,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.5" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2" }, - "time": "2024-09-09T08:05:55+00:00" + "time": "2024-09-16T11:23:09+00:00" }, { "name": "mockery/mockery", @@ -16850,16 +17400,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.3", + "version": "1.12.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa517cb918591b93acc9b95c0bebdcd0e4538bd", + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd", "shasum": "" }, "require": { @@ -16904,7 +17454,7 @@ "type": "github" } ], - "time": "2024-09-09T08:10:35+00:00" + "time": "2024-09-19T07:58:01+00:00" }, { "name": "phpunit/php-code-coverage", @@ -17231,16 +17781,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.3.5", + "version": "11.3.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4dc07a589a68f8f2d5132ac0849146d122e08347" + "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4dc07a589a68f8f2d5132ac0849146d122e08347", - "reference": "4dc07a589a68f8f2d5132ac0849146d122e08347", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d62c45a19c665bb872c2a47023a0baf41a98bb2b", + "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b", "shasum": "" }, "require": { @@ -17267,7 +17817,7 @@ "sebastian/exporter": "^6.1.3", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.0.1", + "sebastian/type": "^5.1.0", "sebastian/version": "^5.0.1" }, "suggest": { @@ -17311,7 +17861,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.6" }, "funding": [ { @@ -17327,7 +17877,7 @@ "type": "tidelift" } ], - "time": "2024-09-13T05:22:17+00:00" + "time": "2024-09-19T10:54:28+00:00" }, { "name": "react/cache", @@ -18673,28 +19223,28 @@ }, { "name": "sebastian/type", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -18718,7 +19268,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" }, "funding": [ { @@ -18726,7 +19276,7 @@ "type": "github" } ], - "time": "2024-07-03T05:11:49+00:00" + "time": "2024-09-17T13:12:04+00:00" }, { "name": "sebastian/version", diff --git a/config/elastic.client.php b/config/elastic.client.php new file mode 100644 index 000000000000..77b5b0e30663 --- /dev/null +++ b/config/elastic.client.php @@ -0,0 +1,24 @@ + env('ELASTIC_CONNECTION', 'default'), + 'connections' => [ + 'default' => [ + 'hosts' => [ + env('ELASTIC_HOST'), + ], + // configure basic authentication + 'basicAuthentication' => [ + env('ELASTIC_USERNAME'), + env('ELASTIC_PASSWORD'), + ], + // configure HTTP client (Guzzle by default) + 'httpClientOptions' => [ + 'timeout' => 2, + 'verify_host' => false, // Disable SSL verification + 'verify_peer' => false, + + ], + ], + ], +]; diff --git a/config/elastic.scout_driver.php b/config/elastic.scout_driver.php new file mode 100644 index 000000000000..a5d123aa8de4 --- /dev/null +++ b/config/elastic.scout_driver.php @@ -0,0 +1,5 @@ + env('ELASTIC_SCOUT_DRIVER_REFRESH_DOCUMENTS', false), +]; diff --git a/config/scout.php b/config/scout.php new file mode 100644 index 000000000000..a65020a46701 --- /dev/null +++ b/config/scout.php @@ -0,0 +1,202 @@ + env('SCOUT_DRIVER', 'algolia'), + + /* + |-------------------------------------------------------------------------- + | Index Prefix + |-------------------------------------------------------------------------- + | + | Here you may specify a prefix that will be applied to all search index + | names used by Scout. This prefix may be useful if you have multiple + | "tenants" or applications sharing the same search infrastructure. + | + */ + + 'prefix' => env('SCOUT_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Queue Data Syncing + |-------------------------------------------------------------------------- + | + | This option allows you to control if the operations that sync your data + | with your search engines are queued. When this is set to "true" then + | all automatic data syncing will get queued for better performance. + | + */ + + 'queue' => env('SCOUT_QUEUE', true), + + /* + |-------------------------------------------------------------------------- + | Database Transactions + |-------------------------------------------------------------------------- + | + | This configuration option determines if your data will only be synced + | with your search indexes after every open database transaction has + | been committed, thus preventing any discarded data from syncing. + | + */ + + 'after_commit' => false, + + /* + |-------------------------------------------------------------------------- + | Chunk Sizes + |-------------------------------------------------------------------------- + | + | These options allow you to control the maximum chunk size when you are + | mass importing data into the search engine. This allows you to fine + | tune each of these chunk sizes based on the power of the servers. + | + */ + + 'chunk' => [ + 'searchable' => 500, + 'unsearchable' => 500, + ], + + /* + |-------------------------------------------------------------------------- + | Soft Deletes + |-------------------------------------------------------------------------- + | + | This option allows to control whether to keep soft deleted records in + | the search indexes. Maintaining soft deleted records can be useful + | if your application still needs to search for the records later. + | + */ + + 'soft_delete' => true, + + /* + |-------------------------------------------------------------------------- + | Identify User + |-------------------------------------------------------------------------- + | + | This option allows you to control whether to notify the search engine + | of the user performing the search. This is sometimes useful if the + | engine supports any analytics based on this application's users. + | + | Supported engines: "algolia" + | + */ + + 'identify' => env('SCOUT_IDENTIFY', false), + + /* + |-------------------------------------------------------------------------- + | Algolia Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Algolia settings. Algolia is a cloud hosted + | search engine which works great with Scout out of the box. Just plug + | in your application ID and admin API key to get started searching. + | + */ + + 'algolia' => [ + 'id' => env('ALGOLIA_APP_ID', ''), + 'secret' => env('ALGOLIA_SECRET', ''), + ], + + /* + |-------------------------------------------------------------------------- + | Meilisearch Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Meilisearch settings. Meilisearch is an open + | source search engine with minimal configuration. Below, you can state + | the host and key information for your own Meilisearch installation. + | + | See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options + | + */ + + 'meilisearch' => [ + 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), + 'key' => env('MEILISEARCH_KEY'), + 'index-settings' => [ + // 'users' => [ + // 'filterableAttributes'=> ['id', 'name', 'email'], + // ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Typesense Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Typesense settings. Typesense is an open + | source search engine using minimal configuration. Below, you will + | state the host, key, and schema configuration for the instance. + | + */ + + 'typesense' => [ + 'client-settings' => [ + 'api_key' => env('TYPESENSE_API_KEY', 'xyz'), + 'nodes' => [ + [ + 'host' => env('TYPESENSE_HOST', 'localhost'), + 'port' => env('TYPESENSE_PORT', '8108'), + 'path' => env('TYPESENSE_PATH', ''), + 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), + ], + ], + 'nearest_node' => [ + 'host' => env('TYPESENSE_HOST', 'localhost'), + 'port' => env('TYPESENSE_PORT', '8108'), + 'path' => env('TYPESENSE_PATH', ''), + 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), + ], + 'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2), + 'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30), + 'num_retries' => env('TYPESENSE_NUM_RETRIES', 3), + 'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1), + ], + 'model-settings' => [ + // User::class => [ + // 'collection-schema' => [ + // 'fields' => [ + // [ + // 'name' => 'id', + // 'type' => 'string', + // ], + // [ + // 'name' => 'name', + // 'type' => 'string', + // ], + // [ + // 'name' => 'created_at', + // 'type' => 'int64', + // ], + // ], + // 'default_sorting_field' => 'created_at', + // ], + // 'search-parameters' => [ + // 'query_by' => 'name' + // ], + // ], + ], + ], + +]; diff --git a/database/migrations/2024_09_06_042040_cba_powerboard.php b/database/migrations/2024_09_06_042040_cba_powerboard.php index 7c993e310d54..255438b06481 100644 --- a/database/migrations/2024_09_06_042040_cba_powerboard.php +++ b/database/migrations/2024_09_06_042040_cba_powerboard.php @@ -21,8 +21,13 @@ return new class extends Migration $fields->publicKey = ''; $fields->secretKey = ''; $fields->testMode = false; - $fields->threeds = false; + $fields->gatewayId = ''; + if($gateway = Gateway::find(64)){ + $gateway->fields = json_encode($fields); + $gateway->save(); + }else{ + $powerboard = new Gateway(); $powerboard->id = 64; $powerboard->name = 'CBA PowerBoard'; @@ -34,7 +39,8 @@ return new class extends Migration $powerboard->fields = json_encode($fields); $powerboard->save(); - + } + Schema::table("company_gateways", function (\Illuminate\Database\Schema\Blueprint $table){ $table->text('settings')->nullable(); }); diff --git a/database/migrations/2024_09_22_084749_2024_09_23_add_sync_column_for_qb.php b/database/migrations/2024_09_22_084749_2024_09_23_add_sync_column_for_qb.php new file mode 100644 index 000000000000..a863a578b83d --- /dev/null +++ b/database/migrations/2024_09_22_084749_2024_09_23_add_sync_column_for_qb.php @@ -0,0 +1,35 @@ +text('sync')->nullable(); + }); + + Schema::table('invoices', function (Blueprint $table) { + $table->text('sync')->nullable(); + }); + + Schema::table('products', function (Blueprint $table) { + $table->text('sync')->nullable(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index 970ad5cecb2e..d4cf01d6966d 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -89,7 +89,7 @@ class PaymentLibrariesSeeder extends Seeder ['id' => 61, 'name' => 'PayPal Platform', 'provider' => 'PayPal_PPCP', 'key' => '80af24a6a691230bbec33e930ab40666', 'fields' => '{"testMode":false}'], ['id' => 62, 'name' => 'BTCPay', 'provider' => 'BTCPay', 'key' => 'vpyfbmdrkqcicpkjqdusgjfluebftuva', 'fields' => '{"btcpayUrl":"", "apiKey":"", "storeId":"", "webhookSecret":""}'], ['id' => 63, 'name' => 'Rotessa', 'is_offsite' => false, 'sort_order' => 22, 'provider' => 'Rotessa', 'key' => '91be24c7b792230bced33e930ac61676', 'fields' => '{"apiKey":"", "testMode":false}'], - ['id' => 64, 'name' => 'CBA PowerBoard', 'is_offsite' => false, 'sort_order' => 26, 'provider' => 'CBAPowerBoard', 'key' => 'b67581d804dbad1743b61c57285142ad', 'fields' => '{"publicKey":"", "secretKey":"", "testMode":false, "Threeds":true}'], + ['id' => 64, 'name' => 'CBA PowerBoard', 'is_offsite' => false, 'sort_order' => 26, 'provider' => 'CBAPowerBoard', 'key' => 'b67581d804dbad1743b61c57285142ad', 'fields' => '{"publicKey":"", "secretKey":"", "testMode":false, "gatewayId":""}'], ['id' => 65, 'name' => 'Blockonomics', 'is_offsite' => false, 'sort_order' => 27, 'provider' => 'Blockonomics', 'key' => 'wbhf02us6owgo7p4nfjd0ymssdshks4d', 'fields' => '{"apiKey":"", "callbackSecret":""}'], ]; diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index 2815c0f02bd4..e022ef5a26f0 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -5331,6 +5331,7 @@ Développe automatiquement la section des notes dans le tableau de produits pour 'country_Melilla' => 'Melilla', 'country_Ceuta' => 'Ceuta', 'country_Canary Islands' => 'Îles Canaries', + 'lang_Vietnamese' => 'Vietnamien', 'invoice_status_changed' => 'Veuillez noter que l\'état de votre facture a été mis à jour. Nous vous recommandons de rafraîchir la page pour afficher la version la plus récente.', 'no_unread_notifications' => 'Vous êtes à jour! Aucune nouvelle notification.', ); diff --git a/lang/vi/texts.php b/lang/vi/texts.php index 8abeb4361f43..9f608378d25e 100644 --- a/lang/vi/texts.php +++ b/lang/vi/texts.php @@ -41,7 +41,7 @@ $lang = array( 'quantity' => 'Số lượng', 'line_total' => 'Tổng', 'subtotal' => 'Thành tiền', - 'net_subtotal' => 'Tịnh', + 'net_subtotal' => 'Tính', 'paid_to_date' => 'Hạn thanh toán', 'balance_due' => 'Số tiền thanh toán', 'invoice_design_id' => 'Thiết kế', @@ -581,8 +581,8 @@ $lang = array( 'pro_plan_call_to_action' => 'Nâng cấp ngay!', 'pro_plan_feature1' => 'Tạo khách hàng không giới hạn', 'pro_plan_feature2' => 'Truy cập vào 10 mẫu thiết kế hóa đơn đẹp', - 'pro_plan_feature3' => 'URL tùy chỉnh - "YourBrand.InvoiceNinja.com"', - 'pro_plan_feature4' => 'Xóa "Được tạo bởi Invoice Ninja"', + 'pro_plan_feature3' => 'URL tùy chỉnh - "YourBrand.InvoiceNinja.com"', + 'pro_plan_feature4' => 'Xóa "Được tạo bởi Invoice Ninja"', 'pro_plan_feature5' => 'Truy cập nhiều người dùng & Theo dõi hoạt động', 'pro_plan_feature6' => 'Tạo báo giá và hóa đơn tạm tính', 'pro_plan_feature7' => 'Tùy chỉnh Tiêu đề và Số hiệu Trường Hóa đơn', @@ -729,7 +729,7 @@ $lang = array( 'invoice_counter' => 'Quầy tính tiền', 'quote_counter' => 'Bộ đếm Báo giá ', 'type' => 'Kiểu', - 'activity_1' => ':user đã tạo ra máy khách :client', + 'activity_1' => ':user đã tạo ra khách hàng :client', 'activity_2' => ':user khách hàng lưu trữ :client', 'activity_3' => ':user đã xóa máy khách :client', 'activity_4' => ':user đã tạo hóa đơn :invoice', @@ -799,8 +799,8 @@ $lang = array( 'archived_token' => 'Đã lưu trữ mã thông báo thành công', 'archive_user' => 'Lưu trữ người dùng', 'archived_user' => 'Đã lưu trữ người dùng thành công', - 'archive_account_gateway' => 'Xóa Cổng Thanh Toán', - 'archived_account_gateway' => 'Cổng lưu trữ thành công', + 'archive_account_gateway' => 'Xóa cổng thanh toán', + 'archived_account_gateway' => 'Lưu trữ cổng thành công', 'archive_recurring_invoice' => 'Lưu trữ hóa đơn định kỳ', 'archived_recurring_invoice' => 'Đã lưu trữ thành công hóa đơn định kỳ', 'delete_recurring_invoice' => 'Xóa hóa đơn định kỳ', @@ -959,7 +959,7 @@ $lang = array( 'quote_message_button' => 'Để xem báo giá cho :amount , hãy nhấp vào nút bên dưới.', 'payment_message_button' => 'Cảm ơn bạn đã thanh toán :amount .', 'payment_type_direct_debit' => 'Ghi nợ trực tiếp', - 'bank_accounts' => 'Thẻ tín dụng và ngân hàng', + 'bank_accounts' => 'Thẻ tín dụng & ngân hàng', 'add_bank_account' => 'Thêm tài khoản ngân hàng', 'setup_account' => 'Thiết lập tài khoản', 'import_expenses' => 'Chi phí nhập khẩu', @@ -1244,9 +1244,9 @@ $lang = array( 'confirm_remove_payment_method' => 'Bạn có chắc chắn muốn xóa phương thức thanh toán này không?', 'remove' => 'Di dời', 'payment_method_removed' => 'Đã xóa phương thức thanh toán.', - 'bank_account_verification_help' => 'Chúng tôi đã thực hiện hai khoản tiền gửi vào tài khoản của bạn với mô tả "XÁC MINH". Các khoản tiền gửi này sẽ mất 1-2 ngày làm việc để xuất hiện trên sao kê của bạn. Vui lòng nhập số tiền bên dưới.', - 'bank_account_verification_next_steps' => 'Chúng tôi đã thực hiện hai khoản tiền gửi vào tài khoản của bạn với mô tả "XÁC MINH". Các khoản tiền gửi này sẽ mất 1-2 ngày làm việc để hiển thị trên sao kê của bạn. - Khi đã có số tiền, hãy quay lại trang phương thức thanh toán này và nhấp vào "Hoàn tất xác minh" bên cạnh tài khoản.', + 'bank_account_verification_help' => 'Chúng tôi đã thực hiện hai khoản tiền gửi vào tài khoản của bạn với mô tả "XÁC MINH". Các khoản tiền gửi này sẽ mất 1-2 ngày làm việc để xuất hiện trên sao kê của bạn. Vui lòng nhập số tiền bên dưới.', + 'bank_account_verification_next_steps' => 'Chúng tôi đã thực hiện hai khoản tiền gửi vào tài khoản của bạn với mô tả "XÁC MINH". Các khoản tiền gửi này sẽ mất 1-2 ngày làm việc để hiển thị trên sao kê của bạn. + Khi đã có số tiền, hãy quay lại trang phương thức thanh toán này và nhấp vào "Hoàn tất xác minh" bên cạnh tài khoản.', 'unknown_bank' => 'Ngân hàng không xác định', 'ach_verification_delay_help' => 'Bạn sẽ có thể sử dụng tài khoản sau khi hoàn tất xác minh. Việc xác minh thường mất 1-2 ngày làm việc.', 'add_credit_card' => 'Thêm thẻ tín dụng', @@ -2096,7 +2096,7 @@ $lang = array( 'gateway_fees' => 'Phí cổng vào', 'fees_disabled' => 'Phí đã bị vô hiệu hóa', 'gateway_fees_help' => 'Tự động thêm phụ phí/chiết khấu khi thanh toán trực tuyến.', - 'gateway' => 'Cổng vào', + 'gateway' => 'Gateway', 'gateway_fee_change_warning' => 'Nếu có hóa đơn chưa thanh toán kèm phí, bạn cần cập nhật thủ công.', 'fees_surcharge_help' => 'Tùy chỉnh phụ phí :link .', 'label_and_taxes' => 'nhãn và thuế', @@ -2612,7 +2612,7 @@ $lang = array( 'signature_on_pdf_help' => 'Hiển thị chữ ký của khách hàng trên hóa đơn/báo giá PDF.', 'expired_white_label' => 'The white label license has expired', 'return_to_login' => 'Quay lại Đăng nhập', - 'convert_products_tip' => 'Lưu ý: thêm :link có tên " :name " để xem tỷ giá hối đoái.', + 'convert_products_tip' => 'Lưu ý: thêm :link có tên " :name " để xem tỷ giá hối đoái.', 'amount_greater_than_balance' => 'Số tiền lớn hơn số dư trên hóa đơn, chúng tôi sẽ tạo khoản tín dụng với số tiền còn lại.', 'custom_fields_tip' => 'Sử dụng Label|Option1,Option2 để hiển thị hộp chọn.', 'client_information' => 'Thông tin khách hàng', @@ -3062,8 +3062,8 @@ $lang = array( 'provider' => 'Nhà cung cấp', 'company_gateway' => 'Cổng thanh toán', 'company_gateways' => 'Cổng thanh toán', - 'new_company_gateway' => 'Cổng mới', - 'edit_company_gateway' => 'Chỉnh sửa Cổng', + 'new_company_gateway' => 'Gateway mới', + 'edit_company_gateway' => 'Chỉnh sửa Gateway', 'created_company_gateway' => 'Đã tạo cổng thành công', 'updated_company_gateway' => 'Đã cập nhật cổng thành công', 'archived_company_gateway' => 'Cổng lưu trữ thành công', @@ -3097,7 +3097,7 @@ $lang = array( 'uploaded_logo' => 'Đã tải logo thành công', 'saved_settings' => 'Đã lưu cài đặt thành công', 'device_settings' => 'Cài đặt thiết bị', - 'credit_cards_and_banks' => 'Thẻ tín dụng và ngân hàng', + 'credit_cards_and_banks' => 'Thẻ tín dụng & ngân hàng', 'price' => 'Giá', 'email_sign_up' => 'Đăng ký Email', 'google_sign_up' => 'Đăng ký Google', @@ -3648,7 +3648,7 @@ $lang = array( 'view_licenses' => 'Xem Giấy phép', 'fullscreen_editor' => 'Biên tập toàn màn hình', 'sidebar_editor' => 'Biên tập thanh bên', - 'please_type_to_confirm' => 'Vui lòng nhập " :value " để xác nhận', + 'please_type_to_confirm' => 'Vui lòng nhập ":value"để xác nhận', 'purge' => 'thanh lọc', 'clone_to' => 'Sao chép vào', 'clone_to_other' => 'Sao chép sang cái khác', @@ -3855,8 +3855,8 @@ $lang = array( 'recommended_in_production' => 'Rất khuyến khích trong sản xuất', 'enable_only_for_development' => 'Chỉ cho phép phát triển', 'test_pdf' => 'Kiểm tra PDF', - 'checkout_authorize_label' => 'Checkout.com có thể được lưu làm phương thức thanh toán để sử dụng sau này, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra "Lưu thông tin thẻ tín dụng" trong quá trình thanh toán.', - 'sofort_authorize_label' => 'Tài khoản ngân hàng (SOFORT) có thể được lưu làm phương thức thanh toán để sử dụng trong tương lai, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra "Lưu chi tiết thanh toán" trong quá trình thanh toán.', + 'checkout_authorize_label' => 'Checkout.com có thể được lưu làm phương thức thanh toán để sử dụng sau này, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra" Lưu thông tin thẻ tín dụng" trong quá trình thanh toán.', + 'sofort_authorize_label' => 'Tài khoản ngân hàng (SOFORT) có thể được lưu làm phương thức thanh toán để sử dụng trong tương lai, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra "Lưu chi tiết thanh toán" trong quá trình thanh toán.', 'node_status' => 'Trạng thái nút', 'npm_status' => 'Trạng thái NPM', 'node_status_not_found' => 'Tôi không tìm thấy Node ở đâu cả. Nó đã được cài đặt chưa?', @@ -3891,7 +3891,7 @@ $lang = array( 'payment_method_saving_failed' => 'Không thể lưu phương thức thanh toán để sử dụng sau này.', 'pay_with' => 'Thanh toán bằng', 'n/a' => 'Không có', - 'by_clicking_next_you_accept_terms' => 'Bằng cách nhấp vào "Tiếp theo", bạn chấp nhận các điều khoản.', + 'by_clicking_next_you_accept_terms' => 'Bằng cách nhấp vào "Tiếp theo", bạn chấp nhận các điều khoản.', 'not_specified' => 'Không xác định', 'before_proceeding_with_payment_warning' => 'Trước khi tiến hành thanh toán, bạn phải điền vào các trường sau', 'after_completing_go_back_to_previous_page' => 'Sau khi hoàn tất, hãy quay lại trang trước.', @@ -4072,7 +4072,7 @@ $lang = array( 'max_companies_desc' => 'Bạn đã đạt đến số lượng công ty tối đa. Xóa các công ty hiện có để di chuyển các công ty mới.', 'migration_already_completed' => 'Công ty đã di chuyển', 'migration_already_completed_desc' => 'Có vẻ như bạn đã di chuyển :company _name sang phiên bản V5 của Invoice Ninja. Trong trường hợp bạn muốn bắt đầu lại, bạn có thể buộc di chuyển để xóa dữ liệu hiện có.', - 'payment_method_cannot_be_authorized_first' => 'Phương thức thanh toán này có thể được lưu lại để sử dụng sau này, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra "Chi tiết cửa hàng" trong quá trình thanh toán.', + 'payment_method_cannot_be_authorized_first' => 'Phương thức thanh toán này có thể được lưu lại để sử dụng sau này, sau khi bạn hoàn tất giao dịch đầu tiên. Đừng quên kiểm tra "Chi tiết cửa hàng" trong quá trình thanh toán.', 'new_account' => 'Tài khoản mới', 'activity_100' => ':user đã tạo hóa đơn định kỳ :recurring_invoice', 'activity_101' => ':user hóa đơn định kỳ đã cập nhật :recurring_invoice', @@ -4110,7 +4110,7 @@ $lang = array( 'one_time_purchases' => 'Mua một lần', 'recurring_purchases' => 'Mua hàng định kỳ', 'you_might_be_interested_in_following' => 'Bạn có thể quan tâm đến những điều sau đây', - 'quotes_with_status_sent_can_be_approved' => 'Chỉ những báo giá có trạng thái "Đã gửi" mới được chấp thuận. Không thể chấp thuận những báo giá đã hết hạn.', + 'quotes_with_status_sent_can_be_approved' => 'Chỉ những báo giá có trạng thái "Đã gửi" mới được chấp thuận. Không thể chấp thuận những báo giá đã hết hạn.', 'no_quotes_available_for_download' => 'Không có báo giá nào có sẵn để tải xuống.', 'copyright' => 'Bản quyền', 'user_created_user' => ':user đã tạo :created_user tại :time', @@ -4287,7 +4287,7 @@ $lang = array( 'migration_not_yet_completed' => 'Việc di chuyển vẫn chưa hoàn tất', 'show_task_end_date' => 'Hiển thị ngày kết thúc nhiệm vụ', 'show_task_end_date_help' => 'Cho phép chỉ định ngày kết thúc nhiệm vụ', - 'gateway_setup' => 'Thiết lập cổng', + 'gateway_setup' => 'Cài đặt Gateway', 'preview_sidebar' => 'Xem trước thanh bên', 'years_data_shown' => 'Dữ liệu năm được hiển thị', 'ended_all_sessions' => 'Đã kết thúc thành công tất cả các phiên', @@ -4394,7 +4394,7 @@ $lang = array( 'document_download_subject' => 'Tài liệu của bạn đã sẵn sàng để tải xuống', 'reminder_message' => 'Nhắc nhở về hóa đơn :number cho :balance', 'gmail_credentials_invalid_subject' => 'Gửi bằng GMail thông tin đăng nhập không hợp lệ', - 'gmail_credentials_invalid_body' => 'Thông tin đăng nhập GMail của bạn không đúng, vui lòng đăng nhập vào cổng thông tin quản trị viên và điều hướng đến Cài đặt > Chi tiết người dùng và ngắt kết nối và kết nối lại tài khoản GMail của bạn. Chúng tôi sẽ gửi cho bạn thông báo này hàng ngày cho đến khi sự cố này được giải quyết', + 'gmail_credentials_invalid_body' => 'Thông tin đăng nhập GMail của bạn không đúng, vui lòng đăng nhập vào cổng thông tin quản trị viên và điều hướng đến Cài đặt > Chi tiết người dùng và ngắt kết nối và kết nối lại tài khoản GMail của bạn. Chúng tôi sẽ gửi cho bạn thông báo này hàng ngày cho đến khi sự cố này được giải quyết', 'total_columns' => 'Tổng số trường', 'view_task' => 'Xem Nhiệm vụ', 'cancel_invoice' => 'Hủy bỏ', @@ -4419,8 +4419,8 @@ $lang = array( 'signed_in_as' => 'Đã đăng nhập như', 'total_results' => 'Tổng kết quả', 'restore_company_gateway' => 'Khôi phục cổng', - 'archive_company_gateway' => 'Cổng lưu trữ', - 'delete_company_gateway' => 'Xóa cổng', + 'archive_company_gateway' => 'Lưu trữ gateway', + 'delete_company_gateway' => 'Xóa gateway', 'exchange_currency' => 'Trao đổi tiền tệ', 'tax_amount1' => 'Số tiền thuế 1', 'tax_amount2' => 'Số tiền thuế 2', @@ -4506,7 +4506,7 @@ $lang = array( 'add' => 'Thêm vào', 'last_sent_template' => 'Mẫu gửi cuối cùng', 'enable_flexible_search' => 'Bật Tìm kiếm linh hoạt', - 'enable_flexible_search_help' => 'Phù hợp với các ký tự không liền kề, ví dụ: "ct" phù hợp với "cat"', + 'enable_flexible_search_help' => 'Phù hợp với các ký tự không liền kề, ví dụ: "ct" phù hợp với "cat"', 'vendor_details' => 'Chi tiết nhà cung cấp', 'purchase_order_details' => 'Chi tiết đơn đặt hàng', 'qr_iban' => 'Mã QR IBAN', @@ -5333,6 +5333,7 @@ $lang = array( 'country_Melilla' => 'Melilla', 'country_Ceuta' => 'Ceuta', 'country_Canary Islands' => 'Quần đảo Canary', + 'lang_Vietnamese' => 'Tiếng Việt', 'invoice_status_changed' => 'Xin lưu ý rằng trạng thái hóa đơn của bạn đã được cập nhật. Chúng tôi khuyên bạn nên làm mới trang để xem phiên bản mới nhất.', 'no_unread_notifications' => 'Bạn đã cập nhật đầy đủ rồi! Không có thông báo mới nào.', ); diff --git a/public/build/assets/app-021b0210.js b/public/build/assets/app-021b0210.js new file mode 100644 index 000000000000..70e3dbcde102 --- /dev/null +++ b/public/build/assets/app-021b0210.js @@ -0,0 +1,109 @@ +import{A as Ol}from"./index-08e160a7.js";import{c as zt,g as Cl}from"./_commonjsHelpers-725317a4.js";var Al={visa:{niceType:"Visa",type:"visa",patterns:[4],gaps:[4,8,12],lengths:[16,18,19],code:{name:"CVV",size:3}},mastercard:{niceType:"Mastercard",type:"mastercard",patterns:[[51,55],[2221,2229],[223,229],[23,26],[270,271],2720],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}},"american-express":{niceType:"American Express",type:"american-express",patterns:[34,37],gaps:[4,10],lengths:[15],code:{name:"CID",size:4}},"diners-club":{niceType:"Diners Club",type:"diners-club",patterns:[[300,305],36,38,39],gaps:[4,10],lengths:[14,16,19],code:{name:"CVV",size:3}},discover:{niceType:"Discover",type:"discover",patterns:[6011,[644,649],65],gaps:[4,8,12],lengths:[16,19],code:{name:"CID",size:3}},jcb:{niceType:"JCB",type:"jcb",patterns:[2131,1800,[3528,3589]],gaps:[4,8,12],lengths:[16,17,18,19],code:{name:"CVV",size:3}},unionpay:{niceType:"UnionPay",type:"unionpay",patterns:[620,[624,626],[62100,62182],[62184,62187],[62185,62197],[62200,62205],[622010,622999],622018,[622019,622999],[62207,62209],[622126,622925],[623,626],6270,6272,6276,[627700,627779],[627781,627799],[6282,6289],6291,6292,810,[8110,8131],[8132,8151],[8152,8163],[8164,8171]],gaps:[4,8,12],lengths:[14,15,16,17,18,19],code:{name:"CVN",size:3}},maestro:{niceType:"Maestro",type:"maestro",patterns:[493698,[5e5,504174],[504176,506698],[506779,508999],[56,59],63,67,6],gaps:[4,8,12],lengths:[12,13,14,15,16,17,18,19],code:{name:"CVC",size:3}},elo:{niceType:"Elo",type:"elo",patterns:[401178,401179,438935,457631,457632,431274,451416,457393,504175,[506699,506778],[509e3,509999],627780,636297,636368,[650031,650033],[650035,650051],[650405,650439],[650485,650538],[650541,650598],[650700,650718],[650720,650727],[650901,650978],[651652,651679],[655e3,655019],[655021,655058]],gaps:[4,8,12],lengths:[16],code:{name:"CVE",size:3}},mir:{niceType:"Mir",type:"mir",patterns:[[2200,2204]],gaps:[4,8,12],lengths:[16,17,18,19],code:{name:"CVP2",size:3}},hiper:{niceType:"Hiper",type:"hiper",patterns:[637095,63737423,63743358,637568,637599,637609,637612],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}},hipercard:{niceType:"Hipercard",type:"hipercard",patterns:[606282],gaps:[4,8,12],lengths:[16],code:{name:"CVC",size:3}}},Tl=Al,ni={},xn={};Object.defineProperty(xn,"__esModule",{value:!0});xn.clone=void 0;function Pl(e){return e?JSON.parse(JSON.stringify(e)):null}xn.clone=Pl;var ii={};Object.defineProperty(ii,"__esModule",{value:!0});ii.matches=void 0;function Rl(e,r,n){var a=String(r).length,s=e.substr(0,a),l=parseInt(s,10);return r=parseInt(String(r).substr(0,s.length),10),n=parseInt(String(n).substr(0,s.length),10),l>=r&&l<=n}function Ml(e,r){return r=String(r),r.substring(0,e.length)===e.substring(0,r.length)}function kl(e,r){return Array.isArray(r)?Rl(e,r[0],r[1]):Ml(e,r)}ii.matches=kl;Object.defineProperty(ni,"__esModule",{value:!0});ni.addMatchingCardsToResults=void 0;var Nl=xn,Ll=ii;function jl(e,r,n){var a,s;for(a=0;a=s&&(v.matchStrength=s),n.push(v);break}}}ni.addMatchingCardsToResults=jl;var ai={};Object.defineProperty(ai,"__esModule",{value:!0});ai.isValidInputType=void 0;function Il(e){return typeof e=="string"||e instanceof String}ai.isValidInputType=Il;var oi={};Object.defineProperty(oi,"__esModule",{value:!0});oi.findBestMatch=void 0;function Dl(e){var r=e.filter(function(n){return n.matchStrength}).length;return r>0&&r===e.length}function $l(e){return Dl(e)?e.reduce(function(r,n){return!r||Number(r.matchStrength)Vl?mn(!1,!1):ql.test(e)?mn(!1,!0):mn(!0,!0)}si.cardholderName=zl;var li={};function Wl(e){for(var r=0,n=!1,a=e.length-1,s;a>=0;)s=parseInt(e.charAt(a),10),n&&(s*=2,s>9&&(s=s%10+1)),n=!n,r+=s,a--;return r%10===0}var Kl=Wl;Object.defineProperty(li,"__esModule",{value:!0});li.cardNumber=void 0;var Jl=Kl,oo=Ho;function yr(e,r,n){return{card:e,isPotentiallyValid:r,isValid:n}}function Gl(e,r){r===void 0&&(r={});var n,a,s;if(typeof e!="string"&&typeof e!="number")return yr(null,!1,!1);var l=String(e).replace(/-|\s/g,"");if(!/^\d*$/.test(l))return yr(null,!1,!1);var v=oo(l);if(v.length===0)return yr(null,!1,!1);if(v.length!==1)return yr(null,!0,!1);var g=v[0];if(r.maxLength&&l.length>r.maxLength)return yr(g,!1,!1);g.type===oo.types.UNIONPAY&&r.luhnValidateUnionPay!==!0?a=!0:a=Jl(l),s=Math.max.apply(null,g.lengths),r.maxLength&&(s=Math.min(r.maxLength,s));for(var R=0;R4)return or(!1,!1);var g=parseInt(e,10),R=Number(String(s).substr(2,2)),U=!1;if(a===2){if(String(s).substr(0,2)===e)return or(!1,!0);n=R===g,U=g>=R&&g<=R+r}else a===4&&(n=s===g,U=g>=s&&g<=s+r);return or(U,U,n)}Qr.expirationYear=Xl;var fi={};Object.defineProperty(fi,"__esModule",{value:!0});fi.isArray=void 0;fi.isArray=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"};Object.defineProperty(ci,"__esModule",{value:!0});ci.parseDate=void 0;var Ql=Qr,Zl=fi;function eu(e){var r=Number(e[0]),n;return r===0?2:r>1||r===1&&Number(e[1])>2?1:r===1?(n=e.substr(1),Ql.expirationYear(n).isPotentiallyValid?1:2):e.length===5?1:e.length>5?2:1}function tu(e){var r;if(/^\d{4}-\d{1,2}$/.test(e)?r=e.split("-").reverse():/\//.test(e)?r=e.split(/\s*\/\s*/g):/\s/.test(e)&&(r=e.split(/ +/g)),Zl.isArray(r))return{month:r[0]||"",year:r.slice(1).join()};var n=eu(e),a=e.substr(0,n);return{month:a,year:e.substr(a.length)}}ci.parseDate=tu;var En={};Object.defineProperty(En,"__esModule",{value:!0});En.expirationMonth=void 0;function vn(e,r,n){return{isValid:e,isPotentiallyValid:r,isValidForThisYear:n||!1}}function ru(e){var r=new Date().getMonth()+1;if(typeof e!="string")return vn(!1,!1);if(e.replace(/\s/g,"")===""||e==="0")return vn(!1,!0);if(!/^\d*$/.test(e))return vn(!1,!1);var n=parseInt(e,10);if(isNaN(Number(e)))return vn(!1,!1);var a=n>0&&n<13;return vn(a,a,a&&n>=r)}En.expirationMonth=ru;var na=zt&&zt.__assign||function(){return na=Object.assign||function(e){for(var r,n=1,a=arguments.length;nr?e[n]:r;return r}function qr(e,r){return{isValid:e,isPotentiallyValid:r}}function uu(e,r){return r===void 0&&(r=qo),r=r instanceof Array?r:[r],typeof e!="string"||!/^\d*$/.test(e)?qr(!1,!1):su(r,e.length)?qr(!0,!0):e.lengthlu(r)?qr(!1,!1):qr(!0,!0)}di.cvv=uu;var pi={};Object.defineProperty(pi,"__esModule",{value:!0});pi.postalCode=void 0;var cu=3;function Gi(e,r){return{isValid:e,isPotentiallyValid:r}}function fu(e,r){r===void 0&&(r={});var n=r.minLength||cu;return typeof e!="string"?Gi(!1,!1):e.lengthfunction(){return r||(0,e[zo(e)[0]])((r={exports:{}}).exports,r),r.exports},Ru=(e,r,n,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of zo(r))!Pu.call(e,s)&&s!==n&&Vo(e,s,{get:()=>r[s],enumerable:!(a=Au(r,s))||a.enumerable});return e},Qe=(e,r,n)=>(n=e!=null?Cu(Tu(e)):{},Ru(r||!e||!e.__esModule?Vo(n,"default",{value:e,enumerable:!0}):n,e)),St=Wt({"../alpine/packages/alpinejs/dist/module.cjs.js"(e,r){var n=Object.create,a=Object.defineProperty,s=Object.getOwnPropertyDescriptor,l=Object.getOwnPropertyNames,v=Object.getPrototypeOf,g=Object.prototype.hasOwnProperty,R=(t,i)=>function(){return i||(0,t[l(t)[0]])((i={exports:{}}).exports,i),i.exports},U=(t,i)=>{for(var o in i)a(t,o,{get:i[o],enumerable:!0})},ne=(t,i,o,f)=>{if(i&&typeof i=="object"||typeof i=="function")for(let d of l(i))!g.call(t,d)&&d!==o&&a(t,d,{get:()=>i[d],enumerable:!(f=s(i,d))||f.enumerable});return t},ie=(t,i,o)=>(o=t!=null?n(v(t)):{},ne(i||!t||!t.__esModule?a(o,"default",{value:t,enumerable:!0}):o,t)),V=t=>ne(a({},"__esModule",{value:!0}),t),G=R({"node_modules/@vue/shared/dist/shared.cjs.js"(t){Object.defineProperty(t,"__esModule",{value:!0});function i(b,K){const re=Object.create(null),fe=b.split(",");for(let He=0;He!!re[He.toLowerCase()]:He=>!!re[He]}var o={1:"TEXT",2:"CLASS",4:"STYLE",8:"PROPS",16:"FULL_PROPS",32:"HYDRATE_EVENTS",64:"STABLE_FRAGMENT",128:"KEYED_FRAGMENT",256:"UNKEYED_FRAGMENT",512:"NEED_PATCH",1024:"DYNAMIC_SLOTS",2048:"DEV_ROOT_FRAGMENT",[-1]:"HOISTED",[-2]:"BAIL"},f={1:"STABLE",2:"DYNAMIC",3:"FORWARDED"},d="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt",p=i(d),m=2;function E(b,K=0,re=b.length){let fe=b.split(/(\r?\n)/);const He=fe.filter((wt,ct)=>ct%2===1);fe=fe.filter((wt,ct)=>ct%2===0);let rt=0;const _t=[];for(let wt=0;wt=K){for(let ct=wt-m;ct<=wt+m||re>rt;ct++){if(ct<0||ct>=fe.length)continue;const hn=ct+1;_t.push(`${hn}${" ".repeat(Math.max(3-String(hn).length,0))}| ${fe[ct]}`);const Ur=fe[ct].length,Zn=He[ct]&&He[ct].length||0;if(ct===wt){const Hr=K-(rt-(Ur+Zn)),Ki=Math.max(1,re>rt?Ur-Hr:re-K);_t.push(" | "+" ".repeat(Hr)+"^".repeat(Ki))}else if(ct>wt){if(re>rt){const Hr=Math.max(Math.min(re-rt,Ur),1);_t.push(" | "+"^".repeat(Hr))}rt+=Ur+Zn}}break}return _t.join(` +`)}var j="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",te=i(j),Me=i(j+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected"),Xe=/[>/="'\u0009\u000a\u000c\u0020]/,Ie={};function Ke(b){if(Ie.hasOwnProperty(b))return Ie[b];const K=Xe.test(b);return K&&console.error(`unsafe attribute name: ${b}`),Ie[b]=!K}var Tt={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},Ut=i("animation-iteration-count,border-image-outset,border-image-slice,border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,stroke-miterlimit,stroke-opacity,stroke-width"),we=i("accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap");function Ve(b){if(Dt(b)){const K={};for(let re=0;re{if(re){const fe=re.split(Ue);fe.length>1&&(K[fe[0].trim()]=fe[1].trim())}}),K}function It(b){let K="";if(!b)return K;for(const re in b){const fe=b[re],He=re.startsWith("--")?re:Xn(re);(hr(fe)||typeof fe=="number"&&Ut(He))&&(K+=`${He}:${fe};`)}return K}function Ht(b){let K="";if(hr(b))K=b;else if(Dt(b))for(let re=0;re]/;function Di(b){const K=""+b,re=Ii.exec(K);if(!re)return K;let fe="",He,rt,_t=0;for(rt=re.index;rt||--!>|kr(re,K))}var Bn=b=>b==null?"":qt(b)?JSON.stringify(b,Bi,2):String(b),Bi=(b,K)=>pr(K)?{[`Map(${K.size})`]:[...K.entries()].reduce((re,[fe,He])=>(re[`${fe} =>`]=He,re),{})}:$t(K)?{[`Set(${K.size})`]:[...K.values()]}:qt(K)&&!Dt(K)&&!Wn(K)?String(K):K,Ui=["bigInt","optionalChaining","nullishCoalescingOperator"],ln=Object.freeze({}),un=Object.freeze([]),cn=()=>{},Nr=()=>!1,Lr=/^on[^a-z]/,jr=b=>Lr.test(b),Ir=b=>b.startsWith("onUpdate:"),Un=Object.assign,Hn=(b,K)=>{const re=b.indexOf(K);re>-1&&b.splice(re,1)},qn=Object.prototype.hasOwnProperty,Vn=(b,K)=>qn.call(b,K),Dt=Array.isArray,pr=b=>gr(b)==="[object Map]",$t=b=>gr(b)==="[object Set]",fn=b=>b instanceof Date,dn=b=>typeof b=="function",hr=b=>typeof b=="string",Hi=b=>typeof b=="symbol",qt=b=>b!==null&&typeof b=="object",Dr=b=>qt(b)&&dn(b.then)&&dn(b.catch),zn=Object.prototype.toString,gr=b=>zn.call(b),qi=b=>gr(b).slice(8,-1),Wn=b=>gr(b)==="[object Object]",Kn=b=>hr(b)&&b!=="NaN"&&b[0]!=="-"&&""+parseInt(b,10)===b,Jn=i(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),mr=b=>{const K=Object.create(null);return re=>K[re]||(K[re]=b(re))},Gn=/-(\w)/g,Yn=mr(b=>b.replace(Gn,(K,re)=>re?re.toUpperCase():"")),Vi=/\B([A-Z])/g,Xn=mr(b=>b.replace(Vi,"-$1").toLowerCase()),vr=mr(b=>b.charAt(0).toUpperCase()+b.slice(1)),zi=mr(b=>b?`on${vr(b)}`:""),pn=(b,K)=>b!==K&&(b===b||K===K),Wi=(b,K)=>{for(let re=0;re{Object.defineProperty(b,K,{configurable:!0,enumerable:!1,value:re})},Fr=b=>{const K=parseFloat(b);return isNaN(K)?b:K},Br,Qn=()=>Br||(Br=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});t.EMPTY_ARR=un,t.EMPTY_OBJ=ln,t.NO=Nr,t.NOOP=cn,t.PatchFlagNames=o,t.babelParserDefaultPlugins=Ui,t.camelize=Yn,t.capitalize=vr,t.def=$r,t.escapeHtml=Di,t.escapeHtmlComment=$i,t.extend=Un,t.generateCodeFrame=E,t.getGlobalThis=Qn,t.hasChanged=pn,t.hasOwn=Vn,t.hyphenate=Xn,t.invokeArrayFns=Wi,t.isArray=Dt,t.isBooleanAttr=Me,t.isDate=fn,t.isFunction=dn,t.isGloballyWhitelisted=p,t.isHTMLTag=Rr,t.isIntegerKey=Kn,t.isKnownAttr=we,t.isMap=pr,t.isModelListener=Ir,t.isNoUnitNumericStyleProp=Ut,t.isObject=qt,t.isOn=jr,t.isPlainObject=Wn,t.isPromise=Dr,t.isReservedProp=Jn,t.isSSRSafeAttrName=Ke,t.isSVGTag=ji,t.isSet=$t,t.isSpecialBooleanAttr=te,t.isString=hr,t.isSymbol=Hi,t.isVoidTag=Mr,t.looseEqual=kr,t.looseIndexOf=Fn,t.makeMap=i,t.normalizeClass=Ht,t.normalizeStyle=Ve,t.objectToString=zn,t.parseStringStyle=bt,t.propsToAttrMap=Tt,t.remove=Hn,t.slotFlagsText=f,t.stringifyStyle=It,t.toDisplayString=Bn,t.toHandlerKey=zi,t.toNumber=Fr,t.toRawType=qi,t.toTypeString=gr}}),C=R({"node_modules/@vue/shared/index.js"(t,i){i.exports=G()}}),y=R({"node_modules/@vue/reactivity/dist/reactivity.cjs.js"(t){Object.defineProperty(t,"__esModule",{value:!0});var i=C(),o=new WeakMap,f=[],d,p=Symbol("iterate"),m=Symbol("Map key iterate");function E(u){return u&&u._isEffect===!0}function j(u,T=i.EMPTY_OBJ){E(u)&&(u=u.raw);const L=Xe(u,T);return T.lazy||L(),L}function te(u){u.active&&(Ie(u),u.options.onStop&&u.options.onStop(),u.active=!1)}var Me=0;function Xe(u,T){const L=function(){if(!L.active)return u();if(!f.includes(L)){Ie(L);try{return we(),f.push(L),d=L,u()}finally{f.pop(),Ve(),d=f[f.length-1]}}};return L.id=Me++,L.allowRecurse=!!T.allowRecurse,L._isEffect=!0,L.active=!0,L.raw=u,L.deps=[],L.options=T,L}function Ie(u){const{deps:T}=u;if(T.length){for(let L=0;L{ht&&ht.forEach(Ft=>{(Ft!==d||Ft.allowRecurse)&&nt.add(Ft)})};if(T==="clear")Le.forEach(xt);else if(L==="length"&&i.isArray(u))Le.forEach((ht,Ft)=>{(Ft==="length"||Ft>=se)&&xt(ht)});else switch(L!==void 0&&xt(Le.get(L)),T){case"add":i.isArray(u)?i.isIntegerKey(L)&&xt(Le.get("length")):(xt(Le.get(p)),i.isMap(u)&&xt(Le.get(m)));break;case"delete":i.isArray(u)||(xt(Le.get(p)),i.isMap(u)&&xt(Le.get(m)));break;case"set":i.isMap(u)&&xt(Le.get(p));break}const gn=ht=>{ht.options.onTrigger&&ht.options.onTrigger({effect:ht,target:u,key:L,type:T,newValue:se,oldValue:J,oldTarget:me}),ht.options.scheduler?ht.options.scheduler(ht):ht()};nt.forEach(gn)}var bt=i.makeMap("__proto__,__v_isRef,__isVue"),It=new Set(Object.getOwnPropertyNames(Symbol).map(u=>Symbol[u]).filter(i.isSymbol)),Ht=Mr(),Pr=Mr(!1,!0),on=Mr(!0),sn=Mr(!0,!0),Rr=ji();function ji(){const u={};return["includes","indexOf","lastIndexOf"].forEach(T=>{u[T]=function(...L){const se=b(this);for(let me=0,Le=this.length;me{u[T]=function(...L){Ut();const se=b(this)[T].apply(this,L);return Ve(),se}}),u}function Mr(u=!1,T=!1){return function(se,J,me){if(J==="__v_isReactive")return!u;if(J==="__v_isReadonly")return u;if(J==="__v_raw"&&me===(u?T?Yn:Gn:T?mr:Jn).get(se))return se;const Le=i.isArray(se);if(!u&&Le&&i.hasOwn(Rr,J))return Reflect.get(Rr,J,me);const nt=Reflect.get(se,J,me);return(i.isSymbol(J)?It.has(J):bt(J))||(u||Ne(se,"get",J),T)?nt:fe(nt)?!Le||!i.isIntegerKey(J)?nt.value:nt:i.isObject(nt)?u?pn(nt):vr(nt):nt}}var Ii=$n(),Di=$n(!0);function $n(u=!1){return function(L,se,J,me){let Le=L[se];if(!u&&(J=b(J),Le=b(Le),!i.isArray(L)&&fe(Le)&&!fe(J)))return Le.value=J,!0;const nt=i.isArray(L)&&i.isIntegerKey(se)?Number(se)i.isObject(u)?vr(u):u,un=u=>i.isObject(u)?pn(u):u,cn=u=>u,Nr=u=>Reflect.getPrototypeOf(u);function Lr(u,T,L=!1,se=!1){u=u.__v_raw;const J=b(u),me=b(T);T!==me&&!L&&Ne(J,"get",T),!L&&Ne(J,"get",me);const{has:Le}=Nr(J),nt=se?cn:L?un:ln;if(Le.call(J,T))return nt(u.get(T));if(Le.call(J,me))return nt(u.get(me));u!==J&&u.get(T)}function jr(u,T=!1){const L=this.__v_raw,se=b(L),J=b(u);return u!==J&&!T&&Ne(se,"has",u),!T&&Ne(se,"has",J),u===J?L.has(u):L.has(u)||L.has(J)}function Ir(u,T=!1){return u=u.__v_raw,!T&&Ne(b(u),"iterate",p),Reflect.get(u,"size",u)}function Un(u){u=b(u);const T=b(this);return Nr(T).has.call(T,u)||(T.add(u),Ue(T,"add",u,u)),this}function Hn(u,T){T=b(T);const L=b(this),{has:se,get:J}=Nr(L);let me=se.call(L,u);me?Kn(L,se,u):(u=b(u),me=se.call(L,u));const Le=J.call(L,u);return L.set(u,T),me?i.hasChanged(T,Le)&&Ue(L,"set",u,T,Le):Ue(L,"add",u,T),this}function qn(u){const T=b(this),{has:L,get:se}=Nr(T);let J=L.call(T,u);J?Kn(T,L,u):(u=b(u),J=L.call(T,u));const me=se?se.call(T,u):void 0,Le=T.delete(u);return J&&Ue(T,"delete",u,void 0,me),Le}function Vn(){const u=b(this),T=u.size!==0,L=i.isMap(u)?new Map(u):new Set(u),se=u.clear();return T&&Ue(u,"clear",void 0,void 0,L),se}function Dt(u,T){return function(se,J){const me=this,Le=me.__v_raw,nt=b(Le),xt=T?cn:u?un:ln;return!u&&Ne(nt,"iterate",p),Le.forEach((gn,ht)=>se.call(J,xt(gn),xt(ht),me))}}function pr(u,T,L){return function(...se){const J=this.__v_raw,me=b(J),Le=i.isMap(me),nt=u==="entries"||u===Symbol.iterator&&Le,xt=u==="keys"&&Le,gn=J[u](...se),ht=L?cn:T?un:ln;return!T&&Ne(me,"iterate",xt?m:p),{next(){const{value:Ft,done:Ji}=gn.next();return Ji?{value:Ft,done:Ji}:{value:nt?[ht(Ft[0]),ht(Ft[1])]:ht(Ft),done:Ji}},[Symbol.iterator](){return this}}}}function $t(u){return function(...T){{const L=T[0]?`on key "${T[0]}" `:"";console.warn(`${i.capitalize(u)} operation ${L}failed: target is readonly.`,b(this))}return u==="delete"?!1:this}}function fn(){const u={get(me){return Lr(this,me)},get size(){return Ir(this)},has:jr,add:Un,set:Hn,delete:qn,clear:Vn,forEach:Dt(!1,!1)},T={get(me){return Lr(this,me,!1,!0)},get size(){return Ir(this)},has:jr,add:Un,set:Hn,delete:qn,clear:Vn,forEach:Dt(!1,!0)},L={get(me){return Lr(this,me,!0)},get size(){return Ir(this,!0)},has(me){return jr.call(this,me,!0)},add:$t("add"),set:$t("set"),delete:$t("delete"),clear:$t("clear"),forEach:Dt(!0,!1)},se={get(me){return Lr(this,me,!0,!0)},get size(){return Ir(this,!0)},has(me){return jr.call(this,me,!0)},add:$t("add"),set:$t("set"),delete:$t("delete"),clear:$t("clear"),forEach:Dt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(me=>{u[me]=pr(me,!1,!1),L[me]=pr(me,!0,!1),T[me]=pr(me,!1,!0),se[me]=pr(me,!0,!0)}),[u,L,T,se]}var[dn,hr,Hi,qt]=fn();function Dr(u,T){const L=T?u?qt:Hi:u?hr:dn;return(se,J,me)=>J==="__v_isReactive"?!u:J==="__v_isReadonly"?u:J==="__v_raw"?se:Reflect.get(i.hasOwn(L,J)&&J in se?L:se,J,me)}var zn={get:Dr(!1,!1)},gr={get:Dr(!1,!0)},qi={get:Dr(!0,!1)},Wn={get:Dr(!0,!0)};function Kn(u,T,L){const se=b(L);if(se!==L&&T.call(u,se)){const J=i.toRawType(u);console.warn(`Reactive ${J} contains both the raw and reactive versions of the same object${J==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var Jn=new WeakMap,mr=new WeakMap,Gn=new WeakMap,Yn=new WeakMap;function Vi(u){switch(u){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Xn(u){return u.__v_skip||!Object.isExtensible(u)?0:Vi(i.toRawType(u))}function vr(u){return u&&u.__v_isReadonly?u:$r(u,!1,Fn,zn,Jn)}function zi(u){return $r(u,!1,Bi,gr,mr)}function pn(u){return $r(u,!0,Bn,qi,Gn)}function Wi(u){return $r(u,!0,Ui,Wn,Yn)}function $r(u,T,L,se,J){if(!i.isObject(u))return console.warn(`value cannot be made reactive: ${String(u)}`),u;if(u.__v_raw&&!(T&&u.__v_isReactive))return u;const me=J.get(u);if(me)return me;const Le=Xn(u);if(Le===0)return u;const nt=new Proxy(u,Le===2?se:L);return J.set(u,nt),nt}function Fr(u){return Br(u)?Fr(u.__v_raw):!!(u&&u.__v_isReactive)}function Br(u){return!!(u&&u.__v_isReadonly)}function Qn(u){return Fr(u)||Br(u)}function b(u){return u&&b(u.__v_raw)||u}function K(u){return i.def(u,"__v_skip",!0),u}var re=u=>i.isObject(u)?vr(u):u;function fe(u){return!!(u&&u.__v_isRef===!0)}function He(u){return wt(u)}function rt(u){return wt(u,!0)}var _t=class{constructor(u,T=!1){this._shallow=T,this.__v_isRef=!0,this._rawValue=T?u:b(u),this._value=T?u:re(u)}get value(){return Ne(b(this),"get","value"),this._value}set value(u){u=this._shallow?u:b(u),i.hasChanged(u,this._rawValue)&&(this._rawValue=u,this._value=this._shallow?u:re(u),Ue(b(this),"set","value",u))}};function wt(u,T=!1){return fe(u)?u:new _t(u,T)}function ct(u){Ue(b(u),"set","value",u.value)}function hn(u){return fe(u)?u.value:u}var Ur={get:(u,T,L)=>hn(Reflect.get(u,T,L)),set:(u,T,L,se)=>{const J=u[T];return fe(J)&&!fe(L)?(J.value=L,!0):Reflect.set(u,T,L,se)}};function Zn(u){return Fr(u)?u:new Proxy(u,Ur)}var Hr=class{constructor(u){this.__v_isRef=!0;const{get:T,set:L}=u(()=>Ne(this,"get","value"),()=>Ue(this,"set","value"));this._get=T,this._set=L}get value(){return this._get()}set value(u){this._set(u)}};function Ki(u){return new Hr(u)}function wl(u){Qn(u)||console.warn("toRefs() expects a reactive object but received a plain one.");const T=i.isArray(u)?new Array(u.length):{};for(const L in u)T[L]=ao(u,L);return T}var xl=class{constructor(u,T){this._object=u,this._key=T,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(u){this._object[this._key]=u}};function ao(u,T){return fe(u[T])?u[T]:new xl(u,T)}var Sl=class{constructor(u,T,L){this._setter=T,this._dirty=!0,this.__v_isRef=!0,this.effect=j(u,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,Ue(b(this),"set","value"))}}),this.__v_isReadonly=L}get value(){const u=b(this);return u._dirty&&(u._value=this.effect(),u._dirty=!1),Ne(u,"get","value"),u._value}set value(u){this._setter(u)}};function El(u){let T,L;return i.isFunction(u)?(T=u,L=()=>{console.warn("Write operation failed: computed value is readonly")}):(T=u.get,L=u.set),new Sl(T,L,i.isFunction(u)||!u.set)}t.ITERATE_KEY=p,t.computed=El,t.customRef=Ki,t.effect=j,t.enableTracking=we,t.isProxy=Qn,t.isReactive=Fr,t.isReadonly=Br,t.isRef=fe,t.markRaw=K,t.pauseTracking=Ut,t.proxyRefs=Zn,t.reactive=vr,t.readonly=pn,t.ref=He,t.resetTracking=Ve,t.shallowReactive=zi,t.shallowReadonly=Wi,t.shallowRef=rt,t.stop=te,t.toRaw=b,t.toRef=ao,t.toRefs=wl,t.track=Ne,t.trigger=Ue,t.triggerRef=ct,t.unref=hn}}),w=R({"node_modules/@vue/reactivity/index.js"(t,i){i.exports=y()}}),_={};U(_,{Alpine:()=>io,default:()=>_l}),r.exports=V(_);var S=!1,P=!1,I=[],de=-1;function D(t){A(t)}function A(t){I.includes(t)||I.push(t),ee()}function N(t){let i=I.indexOf(t);i!==-1&&i>de&&I.splice(i,1)}function ee(){!P&&!S&&(S=!0,queueMicrotask(ye))}function ye(){S=!1,P=!0;for(let t=0;tt.effect(i,{scheduler:o=>{Ge?D(o):o()}}),Je=t.raw}function dt(t){X=t}function mt(t){let i=()=>{};return[f=>{let d=X(f);return t._x_effects||(t._x_effects=new Set,t._x_runEffects=()=>{t._x_effects.forEach(p=>p())}),t._x_effects.add(d),i=()=>{d!==void 0&&(t._x_effects.delete(d),Te(d))},d},()=>{i()}]}function Et(t,i){let o=!0,f,d=X(()=>{let p=t();JSON.stringify(p),o?f=p:queueMicrotask(()=>{i(p,f),f=p}),o=!1});return()=>Te(d)}var Oe=[],_e=[],Ce=[];function Ee(t){Ce.push(t)}function pe(t,i){typeof i=="function"?(t._x_cleanups||(t._x_cleanups=[]),t._x_cleanups.push(i)):(i=t,_e.push(i))}function Z(t){Oe.push(t)}function qe(t,i,o){t._x_attributeCleanups||(t._x_attributeCleanups={}),t._x_attributeCleanups[i]||(t._x_attributeCleanups[i]=[]),t._x_attributeCleanups[i].push(o)}function W(t,i){t._x_attributeCleanups&&Object.entries(t._x_attributeCleanups).forEach(([o,f])=>{(i===void 0||i.includes(o))&&(f.forEach(d=>d()),delete t._x_attributeCleanups[o])})}function ae(t){var i,o;for((i=t._x_effects)==null||i.forEach(N);(o=t._x_cleanups)!=null&&o.length;)t._x_cleanups.pop()()}var be=new MutationObserver(We),De=!1;function ve(){be.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),De=!0}function le(){Ze(),be.disconnect(),De=!1}var ut=[];function Ze(){let t=be.takeRecords();ut.push(()=>t.length>0&&We(t));let i=ut.length;queueMicrotask(()=>{if(ut.length===i)for(;ut.length>0;)ut.shift()()})}function Q(t){if(!De)return t();le();let i=t();return ve(),i}var k=!1,$=[];function ge(){k=!0}function z(){k=!1,We($),$=[]}function We(t){if(k){$=$.concat(t);return}let i=new Set,o=new Set,f=new Map,d=new Map;for(let p=0;pm.nodeType===1&&i.add(m)),t[p].removedNodes.forEach(m=>m.nodeType===1&&o.add(m))),t[p].type==="attributes")){let m=t[p].target,E=t[p].attributeName,j=t[p].oldValue,te=()=>{f.has(m)||f.set(m,[]),f.get(m).push({name:E,value:m.getAttribute(E)})},Me=()=>{d.has(m)||d.set(m,[]),d.get(m).push(E)};m.hasAttribute(E)&&j===null?te():m.hasAttribute(E)?(Me(),te()):Me()}d.forEach((p,m)=>{W(m,p)}),f.forEach((p,m)=>{Oe.forEach(E=>E(m,p))});for(let p of o)i.has(p)||_e.forEach(m=>m(p));i.forEach(p=>{p._x_ignoreSelf=!0,p._x_ignore=!0});for(let p of i)o.has(p)||p.isConnected&&(delete p._x_ignoreSelf,delete p._x_ignore,Ce.forEach(m=>m(p)),p._x_ignore=!0,p._x_ignoreSelf=!0);i.forEach(p=>{delete p._x_ignoreSelf,delete p._x_ignore}),i=null,o=null,f=null,d=null}function he(t){return ce(q(t))}function B(t,i,o){return t._x_dataStack=[i,...q(o||t)],()=>{t._x_dataStack=t._x_dataStack.filter(f=>f!==i)}}function q(t){return t._x_dataStack?t._x_dataStack:typeof ShadowRoot=="function"&&t instanceof ShadowRoot?q(t.host):t.parentNode?q(t.parentNode):[]}function ce(t){return new Proxy({objects:t},Be)}var Be={ownKeys({objects:t}){return Array.from(new Set(t.flatMap(i=>Object.keys(i))))},has({objects:t},i){return i==Symbol.unscopables?!1:t.some(o=>Object.prototype.hasOwnProperty.call(o,i)||Reflect.has(o,i))},get({objects:t},i,o){return i=="toJSON"?Ae:Reflect.get(t.find(f=>Reflect.has(f,i))||{},i,o)},set({objects:t},i,o,f){const d=t.find(m=>Object.prototype.hasOwnProperty.call(m,i))||t[t.length-1],p=Object.getOwnPropertyDescriptor(d,i);return p!=null&&p.set&&(p!=null&&p.get)?p.set.call(f,o)||!0:Reflect.set(d,i,o)}};function Ae(){return Reflect.ownKeys(this).reduce((i,o)=>(i[o]=Reflect.get(this,o),i),{})}function st(t){let i=f=>typeof f=="object"&&!Array.isArray(f)&&f!==null,o=(f,d="")=>{Object.entries(Object.getOwnPropertyDescriptors(f)).forEach(([p,{value:m,enumerable:E}])=>{if(E===!1||m===void 0||typeof m=="object"&&m!==null&&m.__v_skip)return;let j=d===""?p:`${d}.${p}`;typeof m=="object"&&m!==null&&m._x_interceptor?f[p]=m.initialize(t,j,p):i(m)&&m!==f&&!(m instanceof Element)&&o(m,j)})};return o(t)}function it(t,i=()=>{}){let o={initialValue:void 0,_x_interceptor:!0,initialize(f,d,p){return t(this.initialValue,()=>Rt(f,d),m=>Nt(f,d,m),d,p)}};return i(o),f=>{if(typeof f=="object"&&f!==null&&f._x_interceptor){let d=o.initialize.bind(o);o.initialize=(p,m,E)=>{let j=f.initialize(p,m,E);return o.initialValue=j,d(p,m,E)}}else o.initialValue=f;return o}}function Rt(t,i){return i.split(".").reduce((o,f)=>o[f],t)}function Nt(t,i,o){if(typeof i=="string"&&(i=i.split(".")),i.length===1)t[i[0]]=o;else{if(i.length===0)throw error;return t[i[0]]||(t[i[0]]={}),Nt(t[i[0]],i.slice(1),o)}}var cr={};function Ot(t,i){cr[t]=i}function Kt(t,i){let o=fr(i);return Object.entries(cr).forEach(([f,d])=>{Object.defineProperty(t,`$${f}`,{get(){return d(i,o)},enumerable:!1})}),t}function fr(t){let[i,o]=oe(t),f={interceptor:it,...i};return pe(t,o),f}function Cn(t,i,o,...f){try{return o(...f)}catch(d){tr(d,t,i)}}function tr(t,i,o=void 0){t=Object.assign(t??{message:"No error message given."},{el:i,expression:o}),console.warn(`Alpine Expression Error: ${t.message} + +${o?'Expression: "'+o+`" + +`:""}`,i),setTimeout(()=>{throw t},0)}var Sr=!0;function An(t){let i=Sr;Sr=!1;let o=t();return Sr=i,o}function Jt(t,i,o={}){let f;return vt(t,i)(d=>f=d,o),f}function vt(...t){return Zr(...t)}var Zr=Pn;function Tn(t){Zr=t}function Pn(t,i){let o={};Kt(o,t);let f=[o,...q(t)],d=typeof i=="function"?yi(f,i):_i(f,i,t);return Cn.bind(null,t,i,d)}function yi(t,i){return(o=()=>{},{scope:f={},params:d=[]}={})=>{let p=i.apply(ce([f,...t]),d);Er(o,p)}}var en={};function bi(t,i){if(en[t])return en[t];let o=Object.getPrototypeOf(async function(){}).constructor,f=/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(async()=>{ ${t} })()`:t,p=(()=>{try{let m=new o(["__self","scope"],`with (scope) { __self.result = ${f} }; __self.finished = true; return __self.result;`);return Object.defineProperty(m,"name",{value:`[Alpine] ${t}`}),m}catch(m){return tr(m,i,t),Promise.resolve()}})();return en[t]=p,p}function _i(t,i,o){let f=bi(i,o);return(d=()=>{},{scope:p={},params:m=[]}={})=>{f.result=void 0,f.finished=!1;let E=ce([p,...t]);if(typeof f=="function"){let j=f(f,E).catch(te=>tr(te,o,i));f.finished?(Er(d,f.result,E,m,o),f.result=void 0):j.then(te=>{Er(d,te,E,m,o)}).catch(te=>tr(te,o,i)).finally(()=>f.result=void 0)}}}function Er(t,i,o,f,d){if(Sr&&typeof i=="function"){let p=i.apply(o,f);p instanceof Promise?p.then(m=>Er(t,m,o,f)).catch(m=>tr(m,d,i)):t(p)}else typeof i=="object"&&i instanceof Promise?i.then(p=>t(p)):t(i)}var Or="x-";function Gt(t=""){return Or+t}function Rn(t){Or=t}var Cr={};function c(t,i){return Cr[t]=i,{before(o){if(!Cr[o]){console.warn(String.raw`Cannot find directive \`${o}\`. \`${t}\` will use the default order of execution`);return}const f=Ye.indexOf(o);Ye.splice(f>=0?f:Ye.indexOf("DEFAULT"),0,t)}}}function h(t){return Object.keys(Cr).includes(t)}function x(t,i,o){if(i=Array.from(i),t._x_virtualDirectives){let p=Object.entries(t._x_virtualDirectives).map(([E,j])=>({name:E,value:j})),m=O(p);p=p.map(E=>m.find(j=>j.name===E.name)?{name:`x-bind:${E.name}`,value:`"${E.value}"`}:E),i=i.concat(p)}let f={};return i.map($e((p,m)=>f[p]=m)).filter(je).map(Fe(f,o)).sort(At).map(p=>ue(t,p))}function O(t){return Array.from(t).map($e()).filter(i=>!je(i))}var M=!1,F=new Map,H=Symbol();function Y(t){M=!0;let i=Symbol();H=i,F.set(i,[]);let o=()=>{for(;F.get(i).length;)F.get(i).shift()();F.delete(i)},f=()=>{M=!1,o()};t(o),f()}function oe(t){let i=[],o=E=>i.push(E),[f,d]=mt(t);return i.push(d),[{Alpine:an,effect:f,cleanup:o,evaluateLater:vt.bind(vt,t),evaluate:Jt.bind(Jt,t)},()=>i.forEach(E=>E())]}function ue(t,i){let o=()=>{},f=Cr[i.type]||o,[d,p]=oe(t);qe(t,i.original,p);let m=()=>{t._x_ignore||t._x_ignoreSelf||(f.inline&&f.inline(t,i,d),f=f.bind(f,t,i,d),M?F.get(H).push(f):f())};return m.runCleanups=p,m}var ke=(t,i)=>({name:o,value:f})=>(o.startsWith(t)&&(o=o.replace(t,i)),{name:o,value:f}),Pe=t=>t;function $e(t=()=>{}){return({name:i,value:o})=>{let{name:f,value:d}=Se.reduce((p,m)=>m(p),{name:i,value:o});return f!==i&&t(f,i),{name:f,value:d}}}var Se=[];function Re(t){Se.push(t)}function je({name:t}){return et().test(t)}var et=()=>new RegExp(`^${Or}([^:^.]+)\\b`);function Fe(t,i){return({name:o,value:f})=>{let d=o.match(et()),p=o.match(/:([a-zA-Z0-9\-_:]+)/),m=o.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],E=i||t[o]||o;return{type:d?d[1]:null,value:p?p[1]:null,modifiers:m.map(j=>j.replace(".","")),expression:f,original:E}}}var tt="DEFAULT",Ye=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",tt,"teleport"];function At(t,i){let o=Ye.indexOf(t.type)===-1?tt:t.type,f=Ye.indexOf(i.type)===-1?tt:i.type;return Ye.indexOf(o)-Ye.indexOf(f)}function at(t,i,o={}){t.dispatchEvent(new CustomEvent(i,{detail:o,bubbles:!0,composed:!0,cancelable:!0}))}function Ct(t,i){if(typeof ShadowRoot=="function"&&t instanceof ShadowRoot){Array.from(t.children).forEach(d=>Ct(d,i));return}let o=!1;if(i(t,()=>o=!0),o)return;let f=t.firstElementChild;for(;f;)Ct(f,i),f=f.nextElementSibling}function yt(t,...i){console.warn(`Alpine Warning: ${t}`,...i)}var rr=!1;function Mn(){rr&&yt("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),rr=!0,document.body||yt("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `