diff --git a/VERSION.txt b/VERSION.txt index 96cf4cd38b12..75d1c3cf3216 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.89 \ No newline at end of file +5.5.90 \ No newline at end of file diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 01a5e9e06e7e..d2548e27e414 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -12,35 +12,36 @@ namespace App\Console\Commands; use App; +use Exception; +use App\Models\User; +use App\Utils\Ninja; +use App\Models\Quote; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; +use App\Models\Contact; +use App\Models\Invoice; +use App\Models\Payment; +use App\Models\CompanyUser; +use Illuminate\Support\Str; +use App\Models\CompanyToken; +use App\Models\ClientContact; +use App\Models\CompanyLedger; +use App\Models\PurchaseOrder; +use App\Models\VendorContact; +use App\Models\QuoteInvitation; +use Illuminate\Console\Command; +use App\Models\CreditInvitation; +use App\Models\InvoiceInvitation; use App\DataMapper\ClientSettings; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; use App\Factory\ClientContactFactory; use App\Factory\VendorContactFactory; use App\Jobs\Company\CreateCompanyToken; -use App\Models\Account; -use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyLedger; -use App\Models\CompanyUser; -use App\Models\Contact; -use App\Models\Credit; -use App\Models\CreditInvitation; -use App\Models\Invoice; -use App\Models\InvoiceInvitation; -use App\Models\Payment; -use App\Models\PurchaseOrder; -use App\Models\Quote; -use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; -use App\Models\User; -use App\Models\Vendor; -use App\Models\VendorContact; -use App\Utils\Ninja; -use Exception; -use Illuminate\Console\Command; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Str; use Symfony\Component\Console\Input\InputOption; /* @@ -160,16 +161,33 @@ class CheckData extends Command private function checkCompanyTokens() { - CompanyUser::doesnthave('token')->cursor()->each(function ($cu) { - if ($cu->user) { + // CompanyUser::whereDoesntHave('token', function ($query){ + // return $query->where('is_system', 1); + // })->cursor()->each(function ($cu){ + // if ($cu->user) { + // $this->logMessage("Creating missing company token for user # {$cu->user->id} for company id # {$cu->company->id}"); + // (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); + // } else { + // $this->logMessage("Dangling User ID # {$cu->id}"); + // } + // }); + + CompanyUser::query()->cursor()->each(function ($cu) { + if (CompanyToken::where('user_id', $cu->user_id)->where('company_id', $cu->company_id)->where('is_system', 1)->doesntExist()) { $this->logMessage("Creating missing company token for user # {$cu->user->id} for company id # {$cu->company->id}"); - (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); - } else { - $this->logMessage("Dangling User ID # {$cu->id}"); + (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); } }); - } + + + } + + /** + * checkOauthSanity + * + * @return void + */ private function checkOauthSanity() { User::where('oauth_provider_id', '1')->cursor()->each(function ($user) { diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index b5aa38a104b9..d979030514fd 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -467,7 +467,16 @@ class CompanySettings extends BaseSettings public $show_task_item_description = false; + public $client_initiated_payments = false; + + public $client_initiated_payments_minimum = 0; + + public $sync_invoice_quote_columns = true; + public static $casts = [ + 'client_initiated_payments' => 'bool', + 'client_initiated_payments_minimum' => 'float', + 'sync_invoice_quote_columns' => 'bool', 'show_task_item_description' => 'bool', 'allow_billable_task_items' => 'bool', 'accept_client_input_quote_approval' => 'bool', @@ -907,6 +916,15 @@ class CompanySettings extends BaseSettings '$product.tax', '$product.line_total', ], + 'product_quote_columns' => [ + '$product.item', + '$product.description', + '$product.unit_cost', + '$product.quantity', + '$product.discount', + '$product.tax', + '$product.line_total', + ], 'task_columns' =>[ '$task.service', '$task.description', diff --git a/app/Filters/CreditFilters.php b/app/Filters/CreditFilters.php index 7fa39b281c21..7f00792adc0f 100644 --- a/app/Filters/CreditFilters.php +++ b/app/Filters/CreditFilters.php @@ -85,7 +85,10 @@ class CreditFilters extends QueryFilters ->orWhere('credits.custom_value1', 'like', '%'.$filter.'%') ->orWhere('credits.custom_value2', 'like', '%'.$filter.'%') ->orWhere('credits.custom_value3', 'like', '%'.$filter.'%') - ->orWhere('credits.custom_value4', 'like', '%'.$filter.'%'); + ->orWhere('credits.custom_value4', 'like', '%'.$filter.'%') + ->orWhereHas('client', function ($q) use ($filter){ + $q->where('name', 'like', '%'.$filter.'%'); + }); }); } diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index 6ae3da8e6f66..81f3e6db68dd 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -69,6 +69,7 @@ class InvoiceFilters extends QueryFilters if (in_array('overdue', $status_parameters)) { $query->orWhereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('due_date', '<', Carbon::now()) + ->orWhere('due_date', null) ->orWhere('partial_due_date', '<', Carbon::now()); } }); @@ -107,7 +108,10 @@ class InvoiceFilters extends QueryFilters ->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%') - ->orWhere('custom_value4', 'like', '%'.$filter.'%'); + ->orWhere('custom_value4', 'like', '%'.$filter.'%') + ->orWhereHas('client', function ($q) use ($filter){ + $q->where('name', 'like', '%'.$filter.'%'); + }); }); } diff --git a/app/Filters/PurchaseOrderFilters.php b/app/Filters/PurchaseOrderFilters.php index a3ef1709c6f4..61fdbb9956fc 100644 --- a/app/Filters/PurchaseOrderFilters.php +++ b/app/Filters/PurchaseOrderFilters.php @@ -93,7 +93,10 @@ class PurchaseOrderFilters extends QueryFilters ->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%') - ->orWhere('custom_value4', 'like', '%'.$filter.'%'); + ->orWhere('custom_value4', 'like', '%'.$filter.'%') + ->orWhereHas('vendor', function ($q) use ($filter){ + $q->where('name', 'like', '%'.$filter.'%'); + }); }); } diff --git a/app/Filters/QuoteFilters.php b/app/Filters/QuoteFilters.php index ca011386d944..7b4151b7983a 100644 --- a/app/Filters/QuoteFilters.php +++ b/app/Filters/QuoteFilters.php @@ -37,7 +37,10 @@ class QuoteFilters extends QueryFilters ->orwhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%') - ->orWhere('custom_value4', 'like', '%'.$filter.'%'); + ->orWhere('custom_value4', 'like', '%'.$filter.'%') + ->orWhereHas('client', function ($q) use ($filter){ + $q->where('name', 'like', '%'.$filter.'%'); + }); }); } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 6afe275e37b0..268b54e71d9d 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -154,7 +154,7 @@ class AccountController extends BaseController $truth->setUser(auth()->user()); $truth->setCompany($ct->first()->company); - return $this->listResponse($ct); + return $this->listResponse($ct->fresh()); } public function update(UpdateAccountRequest $request, Account $account) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 5d4f4b347947..413bf66a3118 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -1,4 +1,5 @@ 6 characters", - * type="string" - * ) - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="The Company User response", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/CompanyUser"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) */ public function apiLogin(LoginRequest $request) { @@ -175,7 +106,7 @@ class LoginController extends BaseController if ($this->attemptLogin($request)) { LightLogs::create(new LoginSuccess()) ->increment() - ->queue(); + ->batch(); $user = $this->guard()->user(); @@ -221,7 +152,7 @@ class LoginController extends BaseController } else { LightLogs::create(new LoginFailure()) ->increment() - ->queue(); + ->batch(); $this->incrementLoginAttempts($request); @@ -236,39 +167,7 @@ class LoginController extends BaseController * Refreshes the data feed with the current Company User. * * @param Request $request - * @return CompanyUser Refresh Feed. - * - * - * @OA\Post( - * path="/api/v1/refresh", - * operationId="refresh", - * tags={"refresh"}, - * summary="Refreshes the dataset", - * description="Refreshes the dataset", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/include"), - * @OA\Parameter(ref="#/components/parameters/include_static"), - * @OA\Parameter(ref="#/components/parameters/clear_cache"), - * @OA\Response( - * response=200, - * description="The Company User response", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/CompanyUser"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) + * @return CompanyUser Refresh Feed. */ public function refresh(Request $request) { @@ -346,7 +245,7 @@ class LoginController extends BaseController private function handleSocialiteLogin($provider, $token) { $user = $this->getSocialiteUser($provider, $token); - + if ($user) { return $this->loginOrCreateFromSocialite($user, $provider); } @@ -363,7 +262,7 @@ class LoginController extends BaseController 'oauth_user_id' => $user->id, 'oauth_provider_id' => $provider, ]; - + if ($existing_user = MultiDB::hasUser($query)) { if (!$existing_user->account) { return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); @@ -408,7 +307,7 @@ class LoginController extends BaseController return $this->timeConstrainedResponse($cu); } - + nlog("socialite"); nlog($user); @@ -478,7 +377,7 @@ class LoginController extends BaseController if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) { auth()->user()->companies->each(function ($company) { - if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()) { + if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->where('is_system',true)->exists()) { (new CreateCompanyToken($company, auth()->user(), 'Google_O_Auth'))->handle(); } }); @@ -499,7 +398,6 @@ class LoginController extends BaseController return response()->json(['message' => 'Invalid response from oauth server, no access token in response.'], 400); } - $graph = new \Microsoft\Graph\Graph(); $graph->setAccessToken($accessToken); @@ -536,17 +434,22 @@ class LoginController extends BaseController return $this->existingLoginUser($user->getId(), 'microsoft'); } - // Signup! - $new_account = [ - 'first_name' => $user->getGivenName() ?: '', - 'last_name' => $user->getSurname() ?: '', - 'password' => '', - 'email' => $email, - 'oauth_user_id' => $user->getId(), - 'oauth_provider_id' => 'microsoft', - ]; - return $this->createNewAccount($new_account); + // Signup! + if (request()->has('create') && request()->input('create') == 'true') { + $new_account = [ + 'first_name' => $user->getGivenName() ?: '', + 'last_name' => $user->getSurname() ?: '', + 'password' => '', + 'email' => $email, + 'oauth_user_id' => $user->getId(), + 'oauth_provider_id' => 'microsoft', + ]; + + return $this->createNewAccount($new_account); + } + + return response()->json(['message' => 'User not found. If you believe this is an error, please send an email to contact@invoiceninja.com'], 400); } @@ -640,19 +543,23 @@ class LoginController extends BaseController return $this->existingLoginUser($google->harvestSubField($user), 'google'); } - //user not found anywhere - lets sign them up. - $name = OAuth::splitName($google->harvestName($user)); + if (request()->has('create') && request()->input('create') == 'true') { + //user not found anywhere - lets sign them up. + $name = OAuth::splitName($google->harvestName($user)); - $new_account = [ - 'first_name' => $name[0], - 'last_name' => $name[1], - 'password' => '', - 'email' => $google->harvestEmail($user), - 'oauth_user_id' => $google->harvestSubField($user), - 'oauth_provider_id' => 'google', - ]; + $new_account = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'password' => '', + 'email' => $google->harvestEmail($user), + 'oauth_user_id' => $google->harvestSubField($user), + 'oauth_provider_id' => 'google', + ]; - return $this->createNewAccount($new_account); + return $this->createNewAccount($new_account); + } + + return response()->json(['message' => 'User not found. If you believe this is an error, please send an email to contact@invoiceninja.com'], 400); } return response() @@ -700,7 +607,7 @@ class LoginController extends BaseController if ($provider == 'microsoft') { $scopes = ['email', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid']; - $parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url')."/auth/microsoft"]; + $parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url') . "/auth/microsoft"]; } if (request()->has('code')) { diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php deleted file mode 100644 index 2fc7685c525c..000000000000 --- a/app/Http/Controllers/Auth/RegisterController.php +++ /dev/null @@ -1,81 +0,0 @@ -middleware('guest'); - } - - /** - * Get a validator for an incoming registration request. - * - * @param array $data - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - return Validator::make($data, [ - 'first_name' => 'required|string|max:255', - 'email' => 'required|string|email|max:255|unique:users', - 'password' => 'required|string|min:6|confirmed', - ]); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return \App\User - */ - protected function create(array $data) - { - return User::create([ - 'first_name' => $data['first_name'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']), - ]); - } -} diff --git a/app/Http/Controllers/Auth/VendorContactLoginController.php b/app/Http/Controllers/Auth/VendorContactLoginController.php index 91708e065faf..6a50083bbace 100644 --- a/app/Http/Controllers/Auth/VendorContactLoginController.php +++ b/app/Http/Controllers/Auth/VendorContactLoginController.php @@ -28,16 +28,13 @@ class VendorContactLoginController extends Controller public function catch() { - $data = [ - - ]; - return $this->render('purchase_orders.catch'); } public function logout() { Auth::guard('vendor')->logout(); + request()->session()->invalidate(); return redirect('/vendors'); diff --git a/app/Http/Controllers/Auth/VerificationController.php b/app/Http/Controllers/Auth/VerificationController.php deleted file mode 100644 index 33f9856001ae..000000000000 --- a/app/Http/Controllers/Auth/VerificationController.php +++ /dev/null @@ -1,50 +0,0 @@ -middleware('auth'); - $this->middleware('signed')->only('verify'); - $this->middleware('throttle:6,1')->only('verify', 'resend'); - } -} diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 56d76a4c79d8..af73ff04ccff 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -31,6 +31,7 @@ use App\Http\Requests\Email\SendEmailRequest; use App\Jobs\PurchaseOrder\PurchaseOrderEmail; use App\Transformers\PurchaseOrderTransformer; use App\Transformers\RecurringInvoiceTransformer; +use Illuminate\Mail\Mailables\Address; class EmailController extends BaseController { @@ -135,6 +136,8 @@ class EmailController extends BaseController $mo->email_template_body = $request->input('template'); $mo->email_template_subject = str_replace("template", "subject", $request->input('template')); + if($request->has('cc_email')) + $mo->cc[] = new Address($request->cc_email); // if ($entity == 'purchaseOrder' || $entity == 'purchase_order' || $template == 'purchase_order' || $entity == 'App\Models\PurchaseOrder') { // return $this->sendPurchaseOrder($entity_obj, $data, $template); diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index d19830888da4..77078d3a818c 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -11,27 +11,29 @@ namespace App\Http\Controllers; -use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; -use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; +use App\Utils\Ninja; +use App\Models\Account; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Support\Facades\Storage; use App\Factory\RecurringInvoiceFactory; use App\Filters\RecurringInvoiceFilters; -use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest; -use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest; -use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest; +use App\Jobs\RecurringInvoice\UpdateRecurring; +use App\Repositories\RecurringInvoiceRepository; +use App\Transformers\RecurringInvoiceTransformer; +use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; +use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; +use App\Http\Requests\RecurringInvoice\BulkRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\UploadRecurringInvoiceRequest; -use App\Models\Account; -use App\Models\RecurringInvoice; -use App\Repositories\RecurringInvoiceRepository; -use App\Transformers\RecurringInvoiceTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\SavesDocuments; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\Storage; +use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest; /** * Class RecurringInvoiceController. @@ -392,50 +394,6 @@ class RecurringInvoiceController extends BaseController * * @param DestroyRecurringInvoiceRequest $request * @param RecurringInvoice $recurring_invoice - * - * @return Response - * - * - * @throws \Exception - * @OA\Delete( - * path="/api/v1/recurring_invoices/{id}", - * operationId="deleteRecurringInvoice", - * tags={"recurring_invoices"}, - * summary="Deletes a RecurringInvoice", - * description="Handles the deletion of an RecurringInvoice by id", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/include"), - * @OA\Parameter( - * name="id", - * in="path", - * description="The RecurringInvoice Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns a HTTP status", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) */ public function destroy(DestroyRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { @@ -445,195 +403,31 @@ class RecurringInvoiceController extends BaseController } /** - * @OA\Get( - * path="/api/v1/recurring_invoice/{invitation_key}/download", - * operationId="downloadRecurringInvoice", - * tags={"invoices"}, - * summary="Download a specific invoice by invitation key", - * description="Downloads a specific invoice", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/include"), - * @OA\Parameter( - * name="invitation_key", - * in="path", - * description="The Recurring Invoice Invitation Key", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the recurring invoice pdf", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) - * @param $invitation_key - * @return \Symfony\Component\HttpFoundation\BinaryFileResponse */ - public function downloadPdf($invitation_key) + public function bulk(BulkRecurringInvoiceRequest $request) { - $invitation = $this->recurring_invoice_repo->getInvitationByKey($invitation_key); - $contact = $invitation->contact; - $recurring_invoice = $invitation->recurring_invoice; - $file = $recurring_invoice->service()->getInvoicePdf($contact); + $percentage_increase = request()->has('percentage_increase') ? request()->input('percentage_increase') : 0; - return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), ['Content-Type' => 'application/pdf']); - } + if(in_array($request->action, ['increase_prices', 'update_prices'])) { + UpdateRecurring::dispatch($request->ids, auth()->user()->company(), auth()->user(), $request->action, $percentage_increase); - /** - * Perform bulk actions on the list view. - * - * @return Collection - * - * - * @OA\Post( - * path="/api/v1/recurring_invoices/bulk", - * operationId="bulkRecurringInvoices", - * tags={"recurring_invoices"}, - * summary="Performs bulk actions on an array of recurring_invoices", - * description="", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/index"), - * @OA\RequestBody( - * description="Hashed IDs", - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * type="array", - * @OA\Items( - * type="integer", - * description="Array of hashed IDs to be bulk 'actioned", - * example="[0,1,2,3]", - * ), - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="The RecurringInvoice response", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/RecurringInvoice"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + return response()->json(['message' => 'Update in progress.'], 200); + } - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) - */ - public function bulk() - { - $action = request()->input('action'); + $recurring_invoices = RecurringInvoice::withTrashed()->find($request->ids); - $ids = request()->input('ids'); - - $recurring_invoices = RecurringInvoice::withTrashed()->find($this->transformKeys($ids)); - - $recurring_invoices->each(function ($recurring_invoice, $key) use ($action) { + $recurring_invoices->each(function ($recurring_invoice, $key) use($request){ if (auth()->user()->can('edit', $recurring_invoice)) { - $this->performAction($recurring_invoice, $action, true); + $this->performAction($recurring_invoice, $request->action, true); } }); - return $this->listResponse(RecurringInvoice::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(RecurringInvoice::withTrashed()->whereIn('id', $request->ids)); } /** * Recurring Invoice Actions. - * - * - * @OA\Get( - * path="/api/v1/recurring_invoices/{id}/{action}", - * operationId="actionRecurringInvoice", - * tags={"recurring_invoices"}, - * summary="Performs a custom action on an RecurringInvoice", - * description="Performs a custom action on an RecurringInvoice. - - The current range of actions are as follows - - clone_to_RecurringInvoice - - clone_to_quote - - history - - delivery_note - - mark_paid - - download - - archive - - delete - - email", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/include"), - * @OA\Parameter( - * name="id", - * in="path", - * description="The RecurringInvoice Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Parameter( - * name="action", - * in="path", - * description="The action string to be performed", - * example="clone_to_quote", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the RecurringInvoice object", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/RecurringInvoice"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) * @param ActionRecurringInvoiceRequest $request * @param RecurringInvoice $recurring_invoice * @param $action diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index a186d7f26d51..ceb4c154df39 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -592,9 +592,9 @@ class UserController extends BaseController */ public function detach(DetachCompanyUserRequest $request, User $user) { - if ($request->entityIsDeleted($user)) { - return $request->disallowUpdate(); - } + // if ($request->entityIsDeleted($user)) { + // return $request->disallowUpdate(); + // } $company_user = CompanyUser::whereUserId($user->id) ->whereCompanyId(auth()->user()->companyId()) diff --git a/app/Http/Livewire/RequiredClientInfo.php b/app/Http/Livewire/RequiredClientInfo.php index f4d0c1a13f79..70af5590a8dc 100644 --- a/app/Http/Livewire/RequiredClientInfo.php +++ b/app/Http/Livewire/RequiredClientInfo.php @@ -232,8 +232,8 @@ class RequiredClientInfo extends Component if ($cg && $cg->update_details) { $payment_gateway = $cg->driver($this->client)->init(); - // if(method_exists($payment_gateway, "updateCustomer")) - // $payment_gateway->updateCustomer(); + if(method_exists($payment_gateway, "updateCustomer")) + $payment_gateway->updateCustomer(); } return true; diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index 36cd4d2e9854..c6d16d54c604 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -43,6 +43,7 @@ class SendEmailRequest extends Request 'template' => 'bail|required', 'entity' => 'bail|required', 'entity_id' => 'bail|required', + 'cc_email' => 'bail|sometimes|email', ]; } diff --git a/app/Http/Requests/RecurringInvoice/BulkRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/BulkRecurringInvoiceRequest.php new file mode 100644 index 000000000000..e030435eb06b --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/BulkRecurringInvoiceRequest.php @@ -0,0 +1,51 @@ + ['required','bail','array',Rule::exists('recurring_invoices', 'id')->where('company_id', auth()->user()->company()->id)], + 'action' => 'in:archive,restore,delete,increase_prices,update_prices,start,stop,send_now', + 'percentage_increase' => 'required_if:action,increase_prices|numeric|min:0|max:100', + ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + if (isset($input['ids'])) { + $input['ids'] = $this->transformKeys($input['ids']); + } + + $this->replace($input); + } +} diff --git a/app/Http/Requests/User/DetachCompanyUserRequest.php b/app/Http/Requests/User/DetachCompanyUserRequest.php index 72b081f7424d..843b84d98371 100644 --- a/app/Http/Requests/User/DetachCompanyUserRequest.php +++ b/app/Http/Requests/User/DetachCompanyUserRequest.php @@ -12,14 +12,10 @@ namespace App\Http\Requests\User; use App\Http\Requests\Request; -use App\Models\User; -use App\Utils\Traits\ChecksEntityStatus; use App\Utils\Traits\MakesHash; class DetachCompanyUserRequest extends Request { - use MakesHash; - use ChecksEntityStatus; /** * Determine if the user is authorized to make this request. diff --git a/app/Jobs/Account/CreateAccount.php b/app/Jobs/Account/CreateAccount.php index 756c744e3765..a010f8272283 100644 --- a/app/Jobs/Account/CreateAccount.php +++ b/app/Jobs/Account/CreateAccount.php @@ -126,7 +126,7 @@ class CreateAccount NinjaMailerJob::dispatch($nmo, true); - \Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66); + (new \Modules\Admin\Jobs\Account\NinjaUser([], $sp035a66))->handle(); } VersionCheck::dispatch(); diff --git a/app/Jobs/Inventory/AdjustProductInventory.php b/app/Jobs/Inventory/AdjustProductInventory.php index f4772ca9010f..8e5c01e1dcd4 100644 --- a/app/Jobs/Inventory/AdjustProductInventory.php +++ b/app/Jobs/Inventory/AdjustProductInventory.php @@ -31,17 +31,8 @@ class AdjustProductInventory implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies; - public Company $company; - - public Invoice $invoice; - - public array $old_invoice; - - public function __construct(Company $company, Invoice $invoice, $old_invoice = []) + public function __construct(public Company $company, public Invoice $invoice, public $old_invoice = []) { - $this->company = $company; - $this->invoice = $invoice; - $this->old_invoice = $old_invoice; } /** @@ -65,33 +56,64 @@ class AdjustProductInventory implements ShouldQueue { MultiDB::setDb($this->company->db); - foreach ($this->invoice->line_items as $item) { - $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); + // foreach ($this->invoice->line_items as $item) { + // $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); + + // if (! $p) { + // continue; + // } + + // $p->in_stock_quantity += $item->quantity; + + // $p->saveQuietly(); + // } + + collect($this->invoice->line_items)->filter(function ($item){ + return $item->type_id == '1'; + })->each(function ($i){ + + $p = Product::where('product_key', $i->product_key)->where('company_id', $this->company->id)->first(); + + if ($p) { + + $p->in_stock_quantity += $i->quantity; + + $p->saveQuietly(); - if (! $p) { - continue; } - $p->in_stock_quantity += $item->quantity; + }); - $p->saveQuietly(); - } } public function handleRestoredInvoice() { MultiDB::setDb($this->company->db); - foreach ($this->invoice->line_items as $item) { - $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); + // foreach ($this->invoice->line_items as $item) { + // $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); - if (! $p) { - continue; + // if (! $p) { + // continue; + // } + + // $p->in_stock_quantity -= $item->quantity; + // $p->saveQuietly(); + // } + + collect($this->invoice->line_items)->filter(function ($item) { + return $item->type_id == '1'; + })->each(function ($i) { + $p = Product::where('product_key', $i->product_key)->where('company_id', $this->company->id)->first(); + + if ($p) { + $p->in_stock_quantity -= $i->quantity; + + $p->saveQuietly(); } + }); + - $p->in_stock_quantity -= $item->quantity; - $p->saveQuietly(); - } } public function middleware() @@ -101,38 +123,74 @@ class AdjustProductInventory implements ShouldQueue private function newInventoryAdjustment() { - $line_items = $this->invoice->line_items; + // $line_items = $this->invoice->line_items; - foreach ($line_items as $item) { - $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first(); + // foreach ($line_items as $item) { + // $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first(); + + // if (! $p) { + // continue; + // } + + // $p->in_stock_quantity -= $item->quantity; + // $p->saveQuietly(); + + // if ($this->company->stock_notification && $p->stock_notification && $p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold) { + // $this->notifyStockLevels($p, 'product'); + // } elseif ($this->company->stock_notification && $p->stock_notification && $this->company->inventory_notification_threshold && $p->in_stock_quantity <= $this->company->inventory_notification_threshold) { + // $this->notifyStocklevels($p, 'company'); + // } + // } + + collect($this->invoice->line_items)->filter(function ($item) { + return $item->type_id == '1'; + })->each(function ($i) { + $p = Product::where('product_key', $i->product_key)->where('company_id', $this->company->id)->first(); + + if ($p) { + $p->in_stock_quantity -= $i->quantity; + + $p->saveQuietly(); + + if ($this->company->stock_notification && $p->stock_notification && $p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold) { + $this->notifyStockLevels($p, 'product'); + } elseif ($this->company->stock_notification && $p->stock_notification && $this->company->inventory_notification_threshold && $p->in_stock_quantity <= $this->company->inventory_notification_threshold) { + $this->notifyStocklevels($p, 'company'); + } - if (! $p) { - continue; } + }); + - $p->in_stock_quantity -= $item->quantity; - $p->saveQuietly(); - if ($this->company->stock_notification && $p->stock_notification && $p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold) { - $this->notifyStockLevels($p, 'product'); - } elseif ($this->company->stock_notification && $p->stock_notification && $this->company->inventory_notification_threshold && $p->in_stock_quantity <= $this->company->inventory_notification_threshold) { - $this->notifyStocklevels($p, 'company'); - } - } } private function existingInventoryAdjustment() { - foreach ($this->old_invoice as $item) { - $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); + // foreach ($this->old_invoice as $item) { + // $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first(); - if (! $p) { - continue; + // if (! $p) { + // continue; + // } + + // $p->in_stock_quantity += $item->quantity; + // $p->saveQuietly(); + // } + + collect($this->invoice->line_items)->filter(function ($item) { + return $item->type_id == '1'; + })->each(function ($i) { + $p = Product::where('product_key', $i->product_key)->where('company_id', $this->company->id)->first(); + + if ($p) { + $p->in_stock_quantity += $i->quantity; + + $p->saveQuietly(); } + }); + - $p->in_stock_quantity += $item->quantity; - $p->saveQuietly(); - } } private function notifyStocklevels(Product $product, string $notification_level) diff --git a/app/Jobs/Ninja/SendReminders.php b/app/Jobs/Ninja/SendReminders.php index 33a0cdc9f1b1..47754da05b6d 100644 --- a/app/Jobs/Ninja/SendReminders.php +++ b/app/Jobs/Ninja/SendReminders.php @@ -214,13 +214,11 @@ class SendReminders implements ShouldQueue nlog('firing email'); EmailEntity::dispatch($invitation, $invitation->company, $template)->delay(10); + event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $template)); + } }); - if ($this->checkSendSetting($invoice, $template)) { - event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $template)); - } - $invoice->last_sent_date = now(); $invoice->next_send_date = $this->calculateNextSendDate($invoice); diff --git a/app/Jobs/RecurringInvoice/UpdateRecurring.php b/app/Jobs/RecurringInvoice/UpdateRecurring.php new file mode 100644 index 000000000000..5c124922b059 --- /dev/null +++ b/app/Jobs/RecurringInvoice/UpdateRecurring.php @@ -0,0 +1,61 @@ +company->db); + + RecurringInvoice::where('company_id', $this->company->id) + ->whereIn('id', $this->ids) + ->chunk(100, function ($recurring_invoices) { + foreach ($recurring_invoices as $recurring_invoice) { + if ($this->user->can('edit', $recurring_invoice)) { + if ($this->action == 'update_prices') { + $recurring_invoice->service()->updatePrice(); + } elseif ($this->action == 'increase_prices') { + $recurring_invoice->service()->increasePrice($this->percentage); + } + } + } + }); + } + + public function failed($exception = null) + { + } +} diff --git a/app/Listeners/Subscription/PlayStoreRenewSubscription.php b/app/Listeners/Subscription/PlayStoreRenewSubscription.php index 49ec9805d6fc..d03b7111ec9c 100644 --- a/app/Listeners/Subscription/PlayStoreRenewSubscription.php +++ b/app/Listeners/Subscription/PlayStoreRenewSubscription.php @@ -32,7 +32,7 @@ class PlayStoreRenewSubscription implements ShouldQueue $expirationTime = $event->getSubscription()->getExpiryTime(); - $account = Account::where('inapp_transaction_id', $in_app_identifier)->first(); + $account = Account::where('inapp_transaction_id', 'like', $in_app_identifier."%")->first(); if ($account) { $account->update(['plan_expires' => Carbon::parse($expirationTime)]); diff --git a/app/Models/Client.php b/app/Models/Client.php index 2259a449adfb..a7d00e361954 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -474,7 +474,7 @@ class Client extends BaseModel implements HasLocalePreference * of settings which have been merged from * Client > Group > Company levels. * - * @return stdClass stdClass object of settings + * @return \stdClass stdClass object of settings */ public function getMergedSettings() :object { diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index f95a941723f0..4a5de7fc39aa 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -735,7 +735,9 @@ class RecurringInvoice extends BaseModel } /** - * Service entry points. + * service + * + * @return RecurringService */ public function service() :RecurringService { diff --git a/app/PaymentDrivers/Braintree/CreditCard.php b/app/PaymentDrivers/Braintree/CreditCard.php index 0dc9f8760002..c8dbeba2c8a7 100644 --- a/app/PaymentDrivers/Braintree/CreditCard.php +++ b/app/PaymentDrivers/Braintree/CreditCard.php @@ -100,8 +100,8 @@ class CreditCard */ public function paymentResponse(PaymentResponseRequest $request) { - // nlog($request->all()); - + $this->braintree->client->fresh(); + $state = [ 'server_response' => json_decode($request->gateway_response), 'payment_hash' => $request->payment_hash, @@ -124,14 +124,15 @@ class CreditCard 'options' => [ 'submitForSettlement' => true, ], + 'billing' => [ + 'streetAddress' => $this->braintree->client->address1 ?: '', + 'extendedAddress' =>$this->braintree->client->address2 ?: '', + 'locality' => $this->braintree->client->city ?: '', + 'postalCode' => $this->braintree->client->postal_code ?: '', + 'countryCodeAlpha2' => $this->braintree->client->country ? $this->braintree->client->country->iso_3166_2 : 'US', + ] ]; - // uses the same auth id twice when this is enabled. - - // if($state['server_response']?->threeDSecureInfo){ - // $data['threeDSecureAuthenticationId'] = $state['server_response']?->threeDSecureInfo?->threeDSecureAuthenticationId; - // } - if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) { /** https://developer.paypal.com/braintree/docs/reference/request/transaction/sale/php#full-example */ $data['merchantAccountId'] = $this->braintree->company_gateway->getConfigField('merchantAccountId'); @@ -139,6 +140,7 @@ class CreditCard try { $result = $this->braintree->gateway->transaction()->sale($data); + } catch (\Exception $e) { if ($e instanceof \Braintree\Exception\Authorization) { $this->braintree->sendFailureMail(ctrans('texts.generic_gateway_error')); @@ -182,6 +184,13 @@ class CreditCard 'options' => [ 'verifyCard' => true, ], + 'billingAddress' => [ + 'streetAddress' => $this->braintree->client->address1 ?: '', + 'extendedAddress' =>$this->braintree->client->address2 ?: '', + 'locality' => $this->braintree->client->city ?: '', + 'postalCode' => $this->braintree->client->postal_code ?: '', + 'countryCodeAlpha2' => $this->braintree->client->country ? $this->braintree->client->country->iso_3166_2 : 'US', + ] ]; if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) { diff --git a/app/PaymentDrivers/BraintreePaymentDriver.php b/app/PaymentDrivers/BraintreePaymentDriver.php index 374f4cfc2b2c..b9c2db92b3ca 100644 --- a/app/PaymentDrivers/BraintreePaymentDriver.php +++ b/app/PaymentDrivers/BraintreePaymentDriver.php @@ -48,7 +48,7 @@ class BraintreePaymentDriver extends BaseDriver const SYSTEM_LOG_TYPE = SystemLog::TYPE_BRAINTREE; - public function init(): void + public function init(): self { $this->gateway = new Gateway([ 'environment' => $this->company_gateway->getConfigField('testMode') ? 'sandbox' : 'production', @@ -56,6 +56,8 @@ class BraintreePaymentDriver extends BaseDriver 'publicKey' => $this->company_gateway->getConfigField('publicKey'), 'privateKey' => $this->company_gateway->getConfigField('privateKey'), ]); + + return $this; } public function setPaymentMethod($payment_method_id) @@ -109,6 +111,12 @@ class BraintreePaymentDriver extends BaseDriver return $this->gateway->customer()->find($existing->gateway_customer_reference); } + $customer = $this->searchByEmail(); + + if ($customer) { + return $customer; + } + $result = $this->gateway->customer()->create([ 'firstName' => $this->client->present()->name(), 'email' => $this->client->present()->email(), @@ -138,6 +146,45 @@ class BraintreePaymentDriver extends BaseDriver SystemLogger::dispatch(['server_response' => $result, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); } + private function searchByEmail() + { + $result = $this->gateway->customer()->search([ + \Braintree\CustomerSearch::email()->is($this->client->present()->email()), + ]); + + if ($result->maximumCount() > 0) { + return $result->firstItem(); + } + } + + // public function updateCustomer() + // { + // $customer = $this->findOrCreateCustomer(); + + // $result = $this->gateway->customer()->update( + // $customer->id, + // [ + // 'firstName' => $this->client->present()->name(), + // 'email' => $this->client->present()->email(), + // 'phone' => $this->client->present()->phone(), + // 'creditCard' => [ + // 'billingAddress' => [ + // 'options' => [ + // 'updateExisting' => true + // ], + // 'firstName' => $this->client->present()->first_name() ?: $this->client->present()->name(), + // 'lastName' => $this->client->present()->last_name() ?: '', + // 'streetAddress' => $this->client->address1 ?: '', + // 'extendedAddress' =>$this->client->address2 ?: '', + // 'locality' => $this->client->city ?: '', + // 'postalCode' => $this->client->postal_code ?: '', + // 'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : 'US', + // ], + // ], + // ] + // ); + // } + public function refund(Payment $payment, $amount, $return_client_response = false) { $this->init(); diff --git a/app/Repositories/Migration/PaymentMigrationRepository.php b/app/Repositories/Migration/PaymentMigrationRepository.php index 11d40e8f976c..d5a00202522b 100644 --- a/app/Repositories/Migration/PaymentMigrationRepository.php +++ b/app/Repositories/Migration/PaymentMigrationRepository.php @@ -100,7 +100,7 @@ class PaymentMigrationRepository extends BaseRepository $payment->deleted_at = $data['deleted_at'] ?: null; $payment->save(); - if (array_key_exists('currency_id', $data) && $data['currency_id'] == 0) { + if ($payment->currency_id == 0) { $payment->currency_id = $payment->company->settings->currency_id; $payment->save(); } diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index edb6e4ada293..4daf13338e20 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -261,11 +261,33 @@ class Email implements ShouldQueue LightLogs::create(new EmailSuccess($this->company->company_key)) ->send(); + } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { + nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + return; + } catch(\Symfony\Component\Mime\Exception\LogicException $e) { + nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + return; } catch (\Exception | \RuntimeException | \Google\Service\Exception $e) { - nlog("Mailer failed with {$e->getMessage()}"); - + + nlog("Mailer failed with {$e->getMessage()}"); $message = $e->getMessage(); + if (stripos($e->getMessage(), 'code 406') || stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413')) { + $message = "Either Attachment too large, or recipient has been suppressed."; + + $this->fail(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + $this->cleanUpMailers(); + + return; + } + /** * Post mark buries the proper message in a a guzzle response * this merges a text string with a json object diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 476ab592cdd8..2aa356a4d85c 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -69,6 +69,7 @@ class EmailDefaults $this->setLocale() ->setFrom() ->setTo() + ->setCc() ->setTemplate() ->setBody() ->setSubject() @@ -127,7 +128,15 @@ class EmailDefaults private function setFrom(): self { if (Ninja::isHosted() && $this->email->email_object->settings->email_sending_method == 'default') { - $this->email->email_object->from = new Address(config('mail.from.address'), $this->email->company->owner()->name()); + + if ($this->email->company->account->isPaid() && property_exists($this->email->email_object->settings, 'email_from_name') && strlen($this->email->email_object->settings->email_from_name) > 1) { + $email_from_name = $this->email->email_object->settings->email_from_name; + } else { + $email_from_name = $this->email->company->present()->name(); + } + + $this->email->email_object->from = new Address(config('mail.from.address'), $email_from_name); + return $this; } @@ -251,13 +260,14 @@ class EmailDefaults /** * Sets the CC of the email - * @todo at some point.... */ - private function buildCc() + private function setCc(): self { - return [ + return $this; + // return $this->email->email_object->cc; + // return [ - ]; + // ]; } /** @@ -273,7 +283,7 @@ class EmailDefaults $documents = []; /* Return early if the user cannot attach documents */ - if (!$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { + if (!$this->email->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { return $this; } @@ -304,7 +314,7 @@ class EmailDefaults } } - if(!$this->email->email_object->settings->document_email_attachment) + if(!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) return $this; /* Company Documents */ diff --git a/app/Services/Email/EmailMailable.php b/app/Services/Email/EmailMailable.php index 665a0c0ac797..08c8a54dc2ae 100644 --- a/app/Services/Email/EmailMailable.php +++ b/app/Services/Email/EmailMailable.php @@ -39,14 +39,15 @@ class EmailMailable extends Mailable * @return \Illuminate\Mail\Mailables\Envelope */ public function envelope() - { + {nlog($this->email_object->cc); return new Envelope( subject: $this->email_object->subject, tags: [$this->email_object->company_key], replyTo: $this->email_object->reply_to, from: $this->email_object->from, to: $this->email_object->to, - bcc: $this->email_object->bcc + bcc: $this->email_object->bcc, + cc: $this->email_object->cc, ); } diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index eb2feebc3bf7..ffe2ea2b0c7b 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -445,6 +445,14 @@ class PdfBuilder return $elements; } + $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + $table_type = "{$_type}_columns"; + + if ($_type == 'product' && $this->service->config->entity instanceof Quote && !$this->service->config->settings?->sync_invoice_quote_columns) { + $table_type = "product_quote_columns"; + } + + foreach ($items as $row) { $element = ['element' => 'tr', 'elements' => []]; @@ -645,7 +653,13 @@ class PdfBuilder '$task.rate' => '$task.cost', ]; - foreach ($this->service->config->pdf_variables["{$type}_columns"] as $column) { + $table_type = "{$type}_columns"; + + if ($type == 'product' && $this->service->config->entity instanceof Quote && !$this->service->config->settings_object?->sync_invoice_quote_columns) { + $table_type = "product_quote_columns"; + } + + foreach ($this->service->config->pdf_variables[$table_type] as $column) { if (array_key_exists($column, $aliases)) { $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; } elseif ($column == '$product.discount' && !$this->service->company->enable_product_discount) { diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index f1b18faa1273..510508b4bdb9 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -11,26 +11,23 @@ namespace App\Services\Pdf; -use App\Models\Quote; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; use App\Models\Client; -use App\Models\Credit; -use App\Models\Design; -use App\Models\Vendor; use App\Models\Company; use App\Models\Country; -use App\Models\Invoice; -use App\Models\Currency; -use App\Models\PurchaseOrder; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; +use App\Models\Credit; use App\Models\CreditInvitation; -use App\Services\Pdf\PdfBuilder; -use App\Services\Pdf\PdfService; +use App\Models\Currency; +use App\Models\Design; +use App\Models\Invoice; use App\Models\InvoiceInvitation; -use App\Services\Pdf\PdfDesigner; -use App\DataMapper\ClientSettings; -use App\Services\Pdf\PdfConfiguration; +use App\Models\PurchaseOrder; use App\Models\PurchaseOrderInvitation; +use App\Models\Quote; +use App\Models\QuoteInvitation; +use App\Models\Vendor; +use App\Utils\Traits\MakesHash; class PdfMock { @@ -40,17 +37,23 @@ class PdfMock public object $settings; + private string $entity_string = 'invoice'; + public function __construct(public array $request, public Company $company) { } public function getPdf(): mixed { - $pdf_service = new PdfService($this->mock->invitation); + //need to resolve the pdf type here, ie product / purchase order + $document_type = $this->request['entity_type'] == 'purchase_order' ? 'purchase_order' : 'product'; + + $pdf_service = new PdfService($this->mock->invitation, $document_type); $pdf_config = (new PdfConfiguration($pdf_service)); $pdf_config->entity = $this->mock; $pdf_config->entity_string = $this->request['entity_type']; + $this->entity_string = $this->request['entity_type']; $pdf_config->setTaxMap($this->mock->tax_map); $pdf_config->setTotalTaxMap($this->mock->total_tax_map); $pdf_config->client = $this->mock->client; @@ -69,7 +72,7 @@ class PdfMock $pdf_designer = (new PdfDesigner($pdf_service))->build(); $pdf_service->designer = $pdf_designer; - $pdf_service->html_variables = $this->getStubVariables(); + $pdf_service->html_variables = $document_type == 'purchase_order' ? $this->getVendorStubVariables() : $this->getStubVariables(); $pdf_builder = (new PdfBuilder($pdf_service))->build(); $pdf_service->builder = $pdf_builder; @@ -147,6 +150,8 @@ class PdfMock default => $settings = $this->company->settings, }; + $settings = CompanySettings::setProperties($settings); + return $settings; } @@ -271,7 +276,7 @@ class PdfMock '$task.line_total' => '', '$line_tax_labels' => '', '$line_tax_values' => '', - '$secondary_color' => $this->settings->secondary_color, + '$secondary_color' => isset($this->settings->secondary_color) ? $this->settings->secondary_color : '#3d3d3d;', '$invoice.balance' => '$0.00', '$invoice.custom1' => 'custom value', '$invoice.custom2' => 'custom value', @@ -325,7 +330,7 @@ class PdfMock '$entity_images' => '', '$task.discount' => '', '$contact.email' => 'bob@gmail.com', - '$primary_color' => $this->settings->primary_color, + '$primary_color' => isset($this->settings->primary_color) ? $this->settings->primary_color : '#4e4e4e', '$credit_amount' => '$0.00', '$invoice.total' => '$0.00', '$invoice.taxes' => '$0.00', @@ -384,38 +389,7 @@ class PdfMock '$invoiceDate' => '25/Feb/2023', '$view_button' => 'View Invoice', '$client.city' => 'Aufderharchester', - '$spc_qr_code' => 'SPC -0200 -1 - -K -434343 - - - - -CH - - - - - - - -0.000000 -USD - - - - - - - -NON - -0029 -EPD -', + '$spc_qr_code' => '', '$client_name' => 'A Client Called Bob', '$client.name' => 'A Client Called Bob', '$paymentLink' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', @@ -508,301 +482,843 @@ EPD '$show_shipping_address_block' => $this->settings->show_shipping_address ? 'block' : 'none', '$show_shipping_address_visibility' => $this->settings->show_shipping_address ? 'visible' : 'hidden', ], - 'labels' => - [ - '$client.shipping_postal_code_label' => 'Shipping Postal Code', - '$client.billing_postal_code_label' => 'Postal Code', - '$company.city_state_postal_label' => 'City/State/Postal', - '$company.postal_city_state_label' => 'Postal/City/State', - '$company.postal_city_label' => 'Postal/City', - '$product.gross_line_total_label' => 'Gross line total', - '$client.postal_city_state_label' => 'Postal/City/State', - '$client.postal_city_label' => 'Postal/City', - '$client.shipping_address1_label' => 'Shipping Street', - '$client.shipping_address2_label' => 'Shipping Apt/Suite', - '$client.city_state_postal_label' => 'City/State/Postal', - '$client.shipping_address_label' => 'Shipping Address', - '$client.billing_address2_label' => 'Apt/Suite', - '$client.billing_address1_label' => 'Street', - '$client.shipping_country_label' => 'Shipping Country', - '$invoiceninja.whitelabel_label' => '', - '$client.billing_address_label' => 'Address', - '$client.billing_country_label' => 'Country', - '$task.gross_line_total_label' => 'Gross line total', - '$contact.portal_button_label' => 'view_client_portal', - '$client.shipping_state_label' => 'Shipping State/Province', - '$invoice.public_notes_label' => 'Public Notes', - '$client.shipping_city_label' => 'Shipping City', - '$client.billing_state_label' => 'State/Province', - '$product.description_label' => 'Description', - '$product.product_key_label' => 'Product', - '$entity.public_notes_label' => 'Public Notes', - '$invoice.balance_due_label' => 'Balance Due', - '$client.public_notes_label' => 'Notes', - '$company.postal_code_label' => 'Postal Code', - '$client.billing_city_label' => 'City', - '$secondary_font_name_label' => '', - '$product.line_total_label' => 'Line Total', - '$product.tax_amount_label' => 'Tax', - '$company.vat_number_label' => 'VAT Number', - '$invoice.invoice_no_label' => 'Invoice Number', - '$quote.quote_number_label' => 'Quote Number', - '$client.postal_code_label' => 'Postal Code', - '$contact.first_name_label' => 'First Name', - '$secondary_font_url_label' => '', - '$contact.signature_label' => '', - '$company_logo_size_label' => '', - '$product.tax_name1_label' => 'Tax', - '$product.tax_name2_label' => 'Tax', - '$product.tax_name3_label' => 'Tax', - '$product.unit_cost_label' => 'Unit Cost', - '$quote.valid_until_label' => 'Valid Until', - '$custom_surcharge1_label' => '', - '$custom_surcharge2_label' => '', - '$custom_surcharge3_label' => '', - '$custom_surcharge4_label' => '', - '$quote.balance_due_label' => 'Balance Due', - '$company.id_number_label' => 'ID Number', - '$invoice.po_number_label' => 'PO Number', - '$invoice_total_raw_label' => 'Invoice Total', - '$postal_city_state_label' => 'Postal/City/State', - '$client.vat_number_label' => 'VAT Number', - '$city_state_postal_label' => 'City/State/Postal', - '$contact.full_name_label' => 'Name', - '$contact.last_name_label' => 'Last Name', - '$company.country_2_label' => 'Country', - '$product.product1_label' => '', - '$product.product2_label' => '', - '$product.product3_label' => '', - '$product.product4_label' => '', - '$statement_amount_label' => 'Amount', - '$task.description_label' => 'Description', - '$product.discount_label' => 'Discount', - '$entity_issued_to_label' => 'Invoice issued to', - '$assigned_to_user_label' => 'Name', - '$product.quantity_label' => 'Quantity', - '$total_tax_labels_label' => 'Taxes', - '$total_tax_values_label' => 'Taxes', - '$invoice.discount_label' => 'Discount', - '$invoice.subtotal_label' => 'Subtotal', - '$company.address2_label' => 'Apt/Suite', - '$partial_due_date_label' => 'Due Date', - '$invoice.due_date_label' => 'Due Date', - '$client.id_number_label' => 'ID Number', - '$credit.po_number_label' => 'PO Number', - '$company.address1_label' => 'Street', - '$credit.credit_no_label' => 'Invoice Number', - '$invoice.datetime_label' => 'Date', - '$contact.custom1_label' => '', - '$contact.custom2_label' => '', - '$contact.custom3_label' => '', - '$contact.custom4_label' => '', - '$task.line_total_label' => 'Line Total', - '$line_tax_labels_label' => 'Taxes', - '$line_tax_values_label' => 'Taxes', - '$secondary_color_label' => '', - '$invoice.balance_label' => 'Balance', - '$invoice.custom1_label' => '', - '$invoice.custom2_label' => '', - '$invoice.custom3_label' => '', - '$invoice.custom4_label' => '', - '$company.custom1_label' => '', - '$company.custom2_label' => '', - '$company.custom3_label' => '', - '$company.custom4_label' => '', - '$quote.po_number_label' => 'PO Number', - '$company.website_label' => 'Website', - '$balance_due_raw_label' => 'Balance Due', - '$entity.datetime_label' => 'Date', - '$credit.datetime_label' => 'Date', - '$client.address2_label' => 'Apt/Suite', - '$client.address1_label' => 'Street', - '$user.first_name_label' => 'First Name', - '$created_by_user_label' => 'Name', - '$client.currency_label' => '', - '$company.country_label' => 'Country', - '$company.address_label' => 'Address', - '$tech_hero_image_label' => '', - '$task.tax_name1_label' => 'Tax', - '$task.tax_name2_label' => 'Tax', - '$task.tax_name3_label' => 'Tax', - '$client.balance_label' => 'Account balance', - '$client_balance_label' => 'Account balance', - '$credit.balance_label' => 'Balance', - '$credit_balance_label' => 'Credit Balance', - '$gross_subtotal_label' => 'Subtotal', - '$invoice.amount_label' => 'Total', - '$client.custom1_label' => '', - '$client.custom2_label' => '', - '$client.custom3_label' => '', - '$client.custom4_label' => '', - '$emailSignature_label' => '', - '$invoice.number_label' => 'Invoice Number', - '$quote.quote_no_label' => 'Quote Number', - '$quote.datetime_label' => 'Date', - '$client_address_label' => 'Address', - '$client.address_label' => 'Address', - '$payment_button_label' => 'Pay Now', - '$payment_qrcode_label' => 'Pay Now', - '$client.country_label' => 'Country', - '$user.last_name_label' => 'Last Name', - '$client.website_label' => 'Website', - '$dir_text_align_label' => '', - '$entity_images_label' => '', - '$task.discount_label' => 'Discount', - '$contact.email_label' => 'Email', - '$primary_color_label' => '', - '$credit_amount_label' => 'Credit Amount', - '$invoice.total_label' => 'Invoice Total', - '$invoice.taxes_label' => 'Taxes', - '$quote.custom1_label' => '', - '$quote.custom2_label' => '', - '$quote.custom3_label' => '', - '$quote.custom4_label' => '', - '$company.email_label' => 'Email', - '$client.number_label' => 'Number', - '$company.phone_label' => 'Phone', - '$company.state_label' => 'State/Province', - '$credit.number_label' => 'Credit Number', - '$entity_number_label' => 'Invoice Number', - '$credit_number_label' => 'Invoice Number', - '$global_margin_label' => '', - '$contact.phone_label' => 'Phone', - '$portal_button_label' => 'view_client_portal', - '$paymentButton_label' => 'Pay Now', - '$entity_footer_label' => '', - '$client.lang_2_label' => '', - '$product.date_label' => 'Date', - '$client.email_label' => 'Email', - '$product.item_label' => 'Item', - '$public_notes_label' => 'Public Notes', - '$task.service_label' => 'Service', - '$credit.total_label' => 'Credit Total', - '$net_subtotal_label' => 'Net', - '$paid_to_date_label' => 'Paid to Date', - '$quote.amount_label' => 'Quote Total', - '$company.city_label' => 'City', - '$payment.date_label' => 'Payment Date', - '$client.phone_label' => 'Phone', - '$number_short_label' => 'Invoice #', - '$quote.number_label' => 'Quote Number', - '$invoice.date_label' => 'Invoice Date', - '$company.name_label' => 'Company Name', - '$portalButton_label' => 'view_client_portal', - '$contact.name_label' => 'Contact Name', - '$entity.terms_label' => 'Invoice Terms', - '$client.state_label' => 'State/Province', - '$company.logo_label' => 'Logo', - '$company_logo_label' => 'Logo', - '$payment_link_label' => 'Pay Now', - '$status_logo_label' => '', - '$description_label' => 'Description', - '$product.tax_label' => 'Tax', - '$valid_until_label' => 'Valid Until', - '$your_entity_label' => 'Your Invoice', - '$shipping_label' => 'Shipping', - '$balance_due_label' => 'Balance Due', - '$outstanding_label' => 'Balance Due', - '$partial_due_label' => 'Partial Due', - '$quote.total_label' => 'Total', - '$payment_due_label' => 'Payment due', - '$credit.date_label' => 'Credit Date', - '$invoiceDate_label' => 'Invoice Date', - '$view_button_label' => 'View Invoice', - '$client.city_label' => 'City', - '$spc_qr_code_label' => '', - '$client_name_label' => 'Client Name', - '$client.name_label' => 'Client Name', - '$paymentLink_label' => 'Pay Now', - '$payment_url_label' => 'Pay Now', - '$page_layout_label' => '', - '$task.task1_label' => '', - '$task.task2_label' => '', - '$task.task3_label' => '', - '$task.task4_label' => '', - '$task.hours_label' => 'Hours', - '$amount_due_label' => 'Amount due', - '$amount_raw_label' => 'Amount', - '$invoice_no_label' => 'Invoice Number', - '$quote.date_label' => 'Quote Date', - '$vat_number_label' => 'VAT Number', - '$viewButton_label' => 'View Invoice', - '$portal_url_label' => '', - '$task.date_label' => 'Date', - '$task.rate_label' => 'Rate', - '$task.cost_label' => 'Rate', - '$statement_label' => 'Statement', - '$user_iban_label' => '', - '$signature_label' => '', - '$id_number_label' => 'ID Number', - '$credit_no_label' => 'Invoice Number', - '$font_size_label' => '', - '$view_link_label' => 'View Invoice', - '$page_size_label' => '', - '$country_2_label' => 'Country', - '$firstName_label' => 'First Name', - '$user.name_label' => 'Name', - '$font_name_label' => '', - '$auto_bill_label' => '', - '$payments_label' => 'Payments', - '$task.tax_label' => 'Tax', - '$discount_label' => 'Discount', - '$subtotal_label' => 'Subtotal', - '$company1_label' => '', - '$company2_label' => '', - '$company3_label' => '', - '$company4_label' => '', - '$due_date_label' => 'Due Date', - '$poNumber_label' => 'PO Number', - '$quote_no_label' => 'Quote Number', - '$address2_label' => 'Apt/Suite', - '$address1_label' => 'Street', - '$viewLink_label' => 'View Invoice', - '$autoBill_label' => '', - '$view_url_label' => 'View Invoice', - '$font_url_label' => '', - '$details_label' => 'Details', - '$balance_label' => 'Balance', - '$partial_label' => 'Partial Due', - '$client1_label' => '', - '$client2_label' => '', - '$client3_label' => '', - '$client4_label' => '', - '$dueDate_label' => 'Due Date', - '$invoice_label' => 'Invoice Number', - '$account_label' => 'Company Name', - '$country_label' => 'Country', - '$contact_label' => 'Name', - '$app_url_label' => '', - '$website_label' => 'Website', - '$entity_label' => 'Invoice', - '$thanks_label' => 'Thanks', - '$amount_label' => 'Total', - '$method_label' => 'Method', - '$number_label' => 'Invoice Number', - '$footer_label' => '', - '$client_label' => 'Client Name', - '$email_label' => 'Email', - '$notes_label' => 'Public Notes', - '_rate1_label' => 'Tax', - '_rate2_label' => 'Tax', - '_rate3_label' => 'Tax', - '$taxes_label' => 'Taxes', - '$total_label' => 'Total', - '$phone_label' => 'Phone', - '$terms_label' => 'Invoice Terms', - '$from_label' => 'From', - '$item_label' => 'Item', - '$date_label' => 'Invoice Date', - '$tax_label' => 'Tax', - '$dir_label' => '', - '$to_label' => 'To', - '$show_paid_stamp_label' => '', - '$status_logo_label' => '', - '$show_shipping_address_label' => '', - '$show_shipping_address_block_label' => '', - '$show_shipping_address_visibility_label' => '', - ], + 'labels' => $this->mockTranslatedLabels(), ]; } + + + private function mockTranslatedLabels() + { + return [ + '$show_shipping_address_visibility_label' => ctrans('texts.shipping_address'), + '$client.shipping_postal_code_label' => ctrans('texts.shipping_postal_code'), + '$show_shipping_address_block_label' => ctrans('texts.shipping_address'), + '$client.billing_postal_code_label' => ctrans('texts.billing_postal_code'), + '$company.postal_city_state_label' => ctrans('texts.postal_city_state'), + '$company.city_state_postal_label' => ctrans('texts.city_state_postal'), + '$product.gross_line_total_label' => ctrans('texts.gross_line_total'), + '$client.shipping_address1_label' => ctrans('texts.shipping_address1'), + '$client.postal_city_state_label' => ctrans('texts.postal_city_state'), + '$client.shipping_address2_label' => ctrans('texts.shipping_address2'), + '$client.city_state_postal_label' => ctrans('texts.city_state_postal'), + '$client.billing_address2_label' => ctrans('texts.billing_address2'), + '$client.shipping_address_label' => ctrans('texts.shipping_address'), + '$client.billing_address1_label' => ctrans('texts.billing_address1'), + '$client.shipping_country_label' => ctrans('texts.shipping_country'), + '$invoiceninja.whitelabel_label' => ctrans('texts.white_label_link'), + '$client.billing_country_label' => ctrans('texts.billing_country'), + '$client.billing_address_label' => ctrans('texts.billing_address'), + '$task.gross_line_total_label' => ctrans('texts.gross_line_total'), + '$contact.portal_button_label' => ctrans('texts.button'), + '$client.shipping_state_label' => ctrans('texts.shipping_state'), + '$show_shipping_address_label' => ctrans('texts.show_shipping_address'), + '$invoice.public_notes_label' => ctrans('texts.public_notes'), + '$client.billing_state_label' => ctrans('texts.billing_state'), + '$client.shipping_city_label' => ctrans('texts.shipping_city'), + '$product.description_label' => ctrans('texts.description'), + '$product.product_key_label' => ctrans('texts.product_key'), + '$entity.public_notes_label' => ctrans('texts.public_notes'), + '$client.public_notes_label' => ctrans('texts.public_notes'), + '$company.postal_code_label' => ctrans('texts.postal_code'), + '$company.postal_city_label' => ctrans('texts.postal_city'), + '$secondary_font_name_label' => ctrans('texts.secondary_font'), + '$client.billing_city_label' => ctrans('texts.billing_city'), + '$invoice.balance_due_label' => ctrans('texts.balance_due'), + '$product.line_total_label' => ctrans('texts.line_total'), + '$product.tax_amount_label' => ctrans('texts.tax_amount'), + '$client.postal_code_label' => ctrans('texts.postal_code'), + '$company.vat_number_label' => ctrans('texts.vat_number'), + '$client.postal_city_label' => ctrans('texts.postal_city'), + '$quote.quote_number_label' => ctrans('texts.quote_number'), + '$invoice.invoice_no_label' => ctrans('texts.invoice_no'), + '$contact.first_name_label' => ctrans('texts.first_name'), + '$secondary_font_url_label' => ctrans('texts.secondary_font'), + '$contact.signature_label' => ctrans('texts.signature'), + '$product.tax_name1_label' => ctrans('texts.tax_name1'), + '$product.tax_name2_label' => ctrans('texts.tax_name2'), + '$product.tax_name3_label' => ctrans('texts.tax_name3'), + '$product.unit_cost_label' => ctrans('texts.unit_cost'), + '$company.id_number_label' => ctrans('texts.id_number'), + '$quote.valid_until_label' => ctrans('texts.valid_until'), + '$invoice_total_raw_label' => ctrans('texts.invoice_total'), + '$client.vat_number_label' => ctrans('texts.vat_number'), + '$company_logo_size_label' => ctrans('texts.logo'), + '$postal_city_state_label' => ctrans('texts.postal_city_state'), + '$invoice.po_number_label' => ctrans('texts.po_number'), + '$contact.last_name_label' => ctrans('texts.last_name'), + '$contact.full_name_label' => ctrans('texts.full_name'), + '$city_state_postal_label' => ctrans('texts.city_state_postal'), + '$company.country_2_label' => ctrans('texts.country'), + '$custom_surcharge1_label' => ctrans('texts.custom_surcharge1'), + '$custom_surcharge2_label' => ctrans('texts.custom_surcharge2'), + '$custom_surcharge3_label' => ctrans('texts.custom_surcharge3'), + '$custom_surcharge4_label' => ctrans('texts.custom_surcharge4'), + '$quote.balance_due_label' => ctrans('texts.balance_due'), + '$product.product1_label' => ctrans('texts.product1'), + '$product.product2_label' => ctrans('texts.product2'), + '$product.product3_label' => ctrans('texts.product3'), + '$product.product4_label' => ctrans('texts.product4'), + '$statement_amount_label' => ctrans('texts.amount'), + '$task.description_label' => ctrans('texts.description'), + '$product.discount_label' => ctrans('texts.discount'), + '$product.quantity_label' => ctrans('texts.quantity'), + '$entity_issued_to_label' => ctrans('texts.quote_issued_to'), + '$partial_due_date_label' => ctrans('texts.partial_due_date'), + '$invoice.datetime_label' => ctrans('texts.datetime_format_id'), + '$invoice.due_date_label' => ctrans('texts.due_date'), + '$company.address1_label' => ctrans('texts.address1'), + '$company.address2_label' => ctrans('texts.address2'), + '$total_tax_labels_label' => ctrans('texts.total_taxes'), + '$total_tax_values_label' => ctrans('texts.total_taxes'), + '$credit.po_number_label' => ctrans('texts.po_number'), + '$client.id_number_label' => ctrans('texts.id_number'), + '$credit.credit_note_label' => ctrans('texts.credit_note'), + '$assigned_to_user_label' => ctrans('texts.assigned_to'), + '$invoice.discount_label' => ctrans('texts.discount'), + '$invoice.subtotal_label' => ctrans('texts.subtotal'), + '$contact.custom1_label' => ctrans('texts.custom1'), + '$contact.custom2_label' => ctrans('texts.custom2'), + '$contact.custom3_label' => ctrans('texts.custom3'), + '$contact.custom4_label' => ctrans('texts.custom4'), + '$task.line_total_label' => ctrans('texts.line_total'), + '$task.tax_amount_label' => ctrans('texts.tax_amount'), + '$line_tax_labels_label' => ctrans('texts.line_taxes'), + '$line_tax_values_label' => ctrans('texts.line_taxes'), + '$invoice.custom1_label' => ctrans('texts.custom1'), + '$invoice.custom2_label' => ctrans('texts.custom2'), + '$invoice.custom3_label' => ctrans('texts.custom3'), + '$invoice.custom4_label' => ctrans('texts.custom4'), + '$company.custom1_label' => ctrans('texts.custom1'), + '$company.custom2_label' => ctrans('texts.custom2'), + '$company.custom3_label' => ctrans('texts.custom3'), + '$company.custom4_label' => ctrans('texts.custom4'), + '$secondary_color_label' => ctrans('texts.secondary_color'), + '$balance_due_raw_label' => ctrans('texts.balance_due'), + '$entity.datetime_label' => ctrans('texts.datetime_format_id'), + '$credit.datetime_label' => ctrans('texts.datetime_format_id'), + '$client.address2_label' => ctrans('texts.address2'), + '$company.address_label' => ctrans('texts.address'), + '$client.address1_label' => ctrans('texts.address1'), + '$quote.po_number_label' => ctrans('texts.po_number'), + '$client.currency_label' => ctrans('texts.currency'), + '$user.first_name_label' => ctrans('texts.first_name'), + '$created_by_user_label' => ctrans('texts.created_by'), + '$company.country_label' => ctrans('texts.country'), + '$tech_hero_image_label' => '', + '$company.website_label' => ctrans('texts.website'), + '$invoice.balance_label' => ctrans('texts.balance'), + '$client.country_label' => ctrans('texts.country'), + '$task.tax_name1_label' => ctrans('texts.tax_name1'), + '$task.tax_name2_label' => ctrans('texts.tax_name2'), + '$task.tax_name3_label' => ctrans('texts.tax_name3'), + '$payment_button_label' => '', + '$credit.custom1_label' => ctrans('texts.custom1'), + '$credit.custom2_label' => ctrans('texts.custom2'), + '$credit.custom3_label' => ctrans('texts.custom3'), + '$credit.custom4_label' => ctrans('texts.custom4'), + '$emailSignature_label' => ctrans('texts.email_signature'), + '$quote.datetime_label' => ctrans('texts.datetime_format_id'), + '$client.custom1_label' => ctrans('texts.custom1'), + '$client_address_label' => ctrans('texts.address'), + '$client.address_label' => ctrans('texts.address'), + '$payment_qrcode_label' => '', + '$client.custom2_label' => ctrans('texts.custom2'), + '$invoice.number_label' => ctrans('texts.number'), + '$quote.quote_no_label' => ctrans('texts.quote_no'), + '$user.last_name_label' => ctrans('texts.last_name'), + '$client.custom3_label' => ctrans('texts.custom3'), + '$client.website_label' => ctrans('texts.website'), + '$dir_text_align_label' => '', + '$client.custom4_label' => ctrans('texts.custom4'), + '$client.balance_label' => ctrans('texts.balance'), + '$client_balance_label' => ctrans('texts.client_balance'), + '$credit.balance_label' => ctrans('texts.balance'), + '$credit_balance_label' => ctrans('texts.credit_balance'), + '$gross_subtotal_label' => ctrans('texts.subtotal'), + '$invoice.amount_label' => ctrans('texts.amount'), + '$entity_footer_label' => ctrans('texts.footer'), + '$entity_images_label' => '', + '$task.discount_label' => ctrans('texts.discount'), + '$portal_button_label' => '', + '$approveButton_label' => ctrans('texts.approve'), + '$quote.custom1_label' => ctrans('texts.custom1'), + '$quote.custom2_label' => ctrans('texts.custom2'), + '$quote.custom3_label' => ctrans('texts.custom3'), + '$quote.custom4_label' => ctrans('texts.custom4'), + '$company.email_label' => ctrans('texts.email'), + '$primary_color_label' => ctrans('texts.primary_color'), + '$company.phone_label' => ctrans('texts.phone'), + '$exchange_rate_label' => ctrans('texts.exchange_rate'), + '$client.number_label' => ctrans('texts.number'), + '$global_margin_label' => '', + '$contact.phone_label' => ctrans('texts.phone'), + '$company.state_label' => ctrans('texts.state'), + '$credit.number_label' => ctrans('texts.number'), + '$entity_number_label' => ctrans('texts.quote_number'), + '$credit_number_label' => ctrans('texts.credit_number'), + '$client.lang_2_label' => ctrans('texts.language'), + '$contact.email_label' => ctrans('texts.email'), + '$invoice.taxes_label' => ctrans('texts.taxes'), + '$credit_amount_label' => ctrans('texts.credit_amount'), + '$invoice.total_label' => ctrans('texts.total'), + '$product.date_label' => ctrans('texts.date'), + '$product.item_label' => ctrans('texts.item'), + '$public_notes_label' => ctrans('texts.public_notes'), + '$entity.terms_label' => ctrans('texts.terms'), + '$task.service_label' => ctrans('texts.service'), + '$portalButton_label' => '', + '$payment.date_label' => ctrans('texts.date'), + '$client.phone_label' => ctrans('texts.phone'), + '$invoice.date_label' => ctrans('texts.date'), + '$client.state_label' => ctrans('texts.state'), + '$number_short_label' => '', + '$quote.number_label' => ctrans('texts.number'), + '$contact.name_label' => ctrans('texts.name'), + '$company.city_label' => ctrans('texts.city'), + '$company.name_label' => ctrans('texts.name'), + '$company.logo_label' => ctrans('texts.logo'), + '$company_logo_label' => ctrans('texts.logo'), + '$payment_link_label' => ctrans('texts.link'), + '$client.email_label' => ctrans('texts.email'), + '$paid_to_date_label' => ctrans('texts.paid_to_date'), + '$net_subtotal_label' => ctrans('texts.net_subtotal'), + '$credit.total_label' => ctrans('texts.total'), + '$quote.amount_label' => ctrans('texts.amount'), + '$description_label' => ctrans('texts.description'), + '$product.tax_label' => ctrans('texts.tax'), + '$your_entity_label' => ctrans("texts.your_{$this->entity_string}"), + '$view_button_label' => ctrans('texts.view'), + '$status_logo_label' => ctrans('texts.logo'), + '$credit.date_label' => ctrans('texts.date'), + '$payment_due_label' => ctrans('texts.payment_due'), + '$invoiceDate_label' => ctrans('texts.date'), + '$valid_until_label' => ctrans('texts.valid_until'), + '$postal_city_label' => ctrans('texts.postal_city'), + '$client_name_label' => ctrans('texts.client_name'), + '$client.name_label' => ctrans('texts.name'), + '$spc_qr_code_label' => '', + '$client.city_label' => ctrans('texts.city'), + '$paymentLink_label' => ctrans('texts.link'), + '$payment_url_label' => ctrans('texts.url'), + '$page_layout_label' => ctrans('texts.page_layout'), + '$balance_due_label' => ctrans('texts.balance_due'), + '$outstanding_label' => ctrans('texts.outstanding'), + '$partial_due_label' => ctrans('texts.partial_due'), + '$quote.total_label' => ctrans('texts.total'), + '$task.task1_label' => ctrans('texts.task1'), + '$task.task2_label' => ctrans('texts.task2'), + '$task.task3_label' => ctrans('texts.task3'), + '$task.task4_label' => ctrans('texts.task4'), + '$task.hours_label' => ctrans('texts.hours'), + '$viewButton_label' => ctrans('texts.view'), + '$quote.date_label' => ctrans('texts.date'), + '$amount_raw_label' => ctrans('texts.amount'), + '$vat_number_label' => ctrans('texts.vat_number'), + '$invoice_no_label' => ctrans('texts.invoice_no'), + '$portal_url_label' => ctrans('texts.url'), + '$amount_due_label' => ctrans('texts.amount_due'), + '$country_2_label' => ctrans('texts.country'), + '$task.date_label' => ctrans('texts.date'), + '$task.rate_label' => ctrans('texts.rate'), + '$task.cost_label' => ctrans('texts.cost'), + '$statement_label' => ctrans('texts.statement'), + '$view_link_label' => ctrans('texts.link'), + '$user_iban_label' => ctrans('texts.iban'), + '$signature_label' => ctrans('texts.signature'), + '$font_size_label' => ctrans('texts.font_size'), + '$po_number_label' => ctrans('texts.po_number'), + '$page_size_label' => ctrans('texts.page_size'), + '$user.name_label' => ctrans('texts.name'), + '$id_number_label' => ctrans('texts.id_number'), + '$credit_no_label' => ctrans('texts.credit_note'), + '$firstName_label' => ctrans('texts.first_name'), + '$font_name_label' => '', + '$auto_bill_label' => ctrans('texts.auto_bill'), + '$payments_label' => ctrans('texts.payments'), + '$shipping_label' => '', + '$task.tax_label' => ctrans('texts.tax'), + '$viewLink_label' => ctrans('texts.link'), + '$company1_label' => ctrans('texts.company1'), + '$company2_label' => ctrans('texts.company2'), + '$company3_label' => ctrans('texts.company3'), + '$company4_label' => ctrans('texts.company4'), + '$due_date_label' => ctrans('texts.due_date'), + '$address2_label' => ctrans('texts.address2'), + '$address1_label' => ctrans('texts.address1'), + '$poNumber_label' => ctrans('texts.po_number'), + '$quote_no_label' => ctrans('texts.quote_no'), + '$autoBill_label' => ctrans('texts.auto_bill'), + '$view_url_label' => ctrans('texts.url'), + '$font_url_label' => '', + '$discount_label' => ctrans('texts.discount'), + '$subtotal_label' => ctrans('texts.subtotal'), + '$country_label' => ctrans('texts.country'), + '$details_label' => ctrans('texts.details'), + '$custom1_label' => ctrans('texts.custom1'), + '$custom2_label' => ctrans('texts.custom2'), + '$custom3_label' => ctrans('texts.custom3'), + '$custom4_label' => ctrans('texts.custom4'), + '$dueDate_label' => ctrans('texts.due_date'), + '$client1_label' => ctrans('texts.client1'), + '$client2_label' => ctrans('texts.client2'), + '$contact_label' => ctrans('texts.contact'), + '$account_label' => '', + '$client3_label' => ctrans('texts.client3'), + '$app_url_label' => ctrans('texts.url'), + '$website_label' => ctrans('texts.website'), + '$client4_label' => ctrans('texts.client4'), + '$balance_label' => ctrans('texts.balance'), + '$partial_label' => ctrans('texts.partial'), + '$footer_label' => ctrans('texts.footer'), + '$entity_label' => ctrans("texts.{$this->entity_string}"), + '$thanks_label' => ctrans('texts.thanks'), + '$method_label' => ctrans('texts.method'), + '$client_label' => ctrans('texts.client'), + '$number_label' => ctrans('texts.number'), + '$amount_label' => ctrans('texts.amount'), + '$notes_label' => ctrans('texts.notes'), + '$terms_label' => ctrans('texts.terms'), + 'tax_rate1_label' => ctrans('texts.tax_rate1'), + 'tax_rate2_label' => ctrans('texts.tax_rate2'), + 'tax_rate3_label' => ctrans('texts.tax_rate3'), + '$phone_label' => ctrans('texts.phone'), + '$email_label' => ctrans('texts.email'), + '$taxes_label' => ctrans('texts.taxes'), + '$total_label' => ctrans('texts.total'), + '$from_label' => ctrans('texts.from'), + '$item_label' => ctrans('texts.item'), + '$date_label' => ctrans('texts.date'), + '$tax_label' => ctrans('texts.tax'), + '$dir_label' => '', + '$to_label' => ctrans('texts.to'), + ]; + } + + private function getVendorStubVariables() + { + return ['values' => [ + '$vendor.billing_postal_code' => '06270-5526', + '$company.postal_city_state' => '29359 New Loy, Delaware', + '$company.city_state_postal' => 'New Loy, Delaware 29359', + '$product.gross_line_total' => '', + '$purchase_order.po_number' => 'PO12345', + '$vendor.postal_city_state' => '06270-5526 Jameyhaven, West Virginia', + '$vendor.city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', + '$purchase_order.due_date' => '02-12-2021', + '$vendor.billing_address1' => '589', + '$vendor.billing_address2' => '761 Odessa Centers Suite 673', + '$invoiceninja.whitelabel' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', + '$purchase_order.custom1' => 'Custom 1', + '$purchase_order.custom2' => 'Custom 2', + '$purchase_order.custom3' => 'Custom 3', + '$purchase_order.custom4' => 'Custom 4', + '$vendor.billing_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', + '$vendor.billing_country' => 'Afghanistan', + '$purchase_order.number' => 'Live Preview #790', + '$purchase_order.total' => '$10,256.40', + '$vendor.billing_state' => 'West Virginia', + '$product.description' => '', + '$product.product_key' => '', + '$entity.public_notes' => 'These are public notes for the Vendor', + '$purchase_order.date' => '14/Mar/2023', + '$company.postal_code' => '29359', + '$company.postal_city' => '29359 New Loy', + '$vendor.billing_city' => 'Jameyhaven', + '$vendor.public_notes' => 'Public notes', + '$product.line_total' => '', + '$product.tax_amount' => '', + '$vendor.postal_code' => '06270-5526', + '$vendor.postal_city' => '06270-5526 Jameyhaven', + '$contact.first_name' => 'Geo', + '$company.vat_number' => 'vat number', + '$contact.signature' => '', + '$product.tax_name1' => '', + '$product.tax_name2' => '', + '$product.tax_name3' => '', + '$product.unit_cost' => '', + '$custom_surcharge1' => '$0.00', + '$custom_surcharge2' => '$0.00', + '$custom_surcharge3' => '$0.00', + '$custom_surcharge4' => '$0.00', + '$postal_city_state' => '06270-5526 Jameyhaven, West Virginia', + '$company_logo_size' => '65%', + '$vendor.vat_number' => 'At qui autem iusto et.', + '$contact.full_name' => 'Geo Maggio', + '$city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', + '$contact.last_name' => 'Maggio', + '$company.country_2' => 'US', + '$company.id_number' => 'id number', + '$product.product1' => '', + '$product.product2' => '', + '$product.product3' => '', + '$product.product4' => '', + '$statement_amount' => '', + '$product.discount' => '', + '$assigned_to_user' => '', + '$entity_issued_to' => '', + '$product.quantity' => '', + '$total_tax_labels' => '', + '$total_tax_values' => '', + '$partial_due_date' => ' ', + '$company.address2' => '70218 Lori Station Suite 529', + '$company.address1' => 'Christiansen Garden', + '$vendor.id_number' => 'Libero debitis.', + '$contact.custom1' => null, + '$contact.custom2' => null, + '$contact.custom3' => null, + '$contact.custom4' => null, + '$secondary_color' => '#7081e0', + '$company.custom1' => ' ', + '$company.custom2' => ' ', + '$company.custom3' => ' ', + '$company.custom4' => ' ', + '$balance_due_raw' => '10256.40', + '$entity.datetime' => '14/Mar/2023 10:43 pm', + '$vendor.address1' => '589', + '$vendor.address2' => '761 Odessa Centers Suite 673', + '$line_tax_values' => '$488.40', + '$line_tax_labels' => 'Sales Tax 5%', + '$company.address' => 'Christiansen Garden
70218 Lori Station Suite 529
New Loy, Delaware 29359
United States
Phone: 1-240-886-2233
Email: immanuel53@example.net
', + '$user.first_name' => 'Mr. Louvenia Armstrong', + '$created_by_user' => 'Mr. Louvenia Armstrong Prof. Reyes Anderson', + '$vendor.currency' => 'USD', + '$company.country' => 'United States', + '$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg', + '$company.website' => 'http://www.dare.com/vero-consequatur-eveniet-dolorum-exercitationem-alias-repellat.html', + '$gross_subtotal' => '$10,256.40', + '$emailSignature' => ' ', + '$vendor_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', + '$vendor.address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', + '$vendor.country' => 'Afghanistan', + '$vendor.custom3' => 'Ea quia tempore.', + '$vendor.custom1' => 'Necessitatibus aut.', + '$vendor.custom4' => 'Nobis aut harum.', + '$user.last_name' => 'Prof. Reyes Anderson', + '$vendor.custom2' => 'Sit fuga quas sint.', + '$vendor.website' => 'http://abernathy.com/consequatur-at-beatae-nesciunt', + '$dir_text_align' => 'left', + '$entity_footer' => '', + '$entity_images' => '', + '$contact.email' => '', + '$primary_color' => '#298AAB', + '$contact.phone' => '+1 (920) 735-1990', + '$vendor.number' => '0001', + '$company.phone' => '1-240-886-2233', + '$global_margin' => '6.35mm', + '$company.state' => 'Delaware', + '$entity_number' => 'Live Preview #790', + '$company.email' => 'immanuel53@example.net', + '$product.date' => '', + '$vendor.email' => '', + '$entity.terms' => '', + '$product.item' => '', + '$public_notes' => null, + '$paid_to_date' => '$0.00', + '$net_subtotal' => '$9,768.00', + '$payment.date' => ' ', + '$vendor.phone' => ' ', + '$contact.name' => 'Geo Maggio', + '$number_short' => 'Live Preview #790', + '$company.name' => 'Mrs. Kristina Powlowski', + '$company.city' => 'New Loy', + '$vendor.state' => 'West Virginia', + '$company.logo' => 'data:image/png;base64, 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', + '$company_logo' => 'data:image/png;base64, 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', + '$description' => '', + '$product.tax' => '', + '$view_button' => ' +
+ + + + + + +
+ + View Purchase Order + +
+ +
+ ', + '$status_logo' => ' ', + '$partial_due' => '$0.00', + '$balance_due' => '$10,256.40', + '$outstanding' => '$10,256.40', + '$payment_due' => ' ', + '$postal_city' => '06270-5526 Jameyhaven', + '$vendor_name' => 'Claudie Nikolaus MD', + '$vendor.name' => 'Claudie Nikolaus MD', + '$vendor.city' => 'Jameyhaven', + '$page_layout' => 'portrait', + '$viewButton' => ' +
+ + + + + + +
+ + View Purchase Order + +
+ +
+ ', + '$amount_due' => '$10,256.40', + '$amount_raw' => '10256.40', + '$vat_number' => 'At qui autem iusto et.', + '$portal_url' => 'http://ninja.test:8000/vendor/', + '$po_number' => null, + '$statement' => '', + '$view_link' => ' +
+ + + + + + +
+ + View Purchase Order + +
+ +
+ ', + '$signature' => ' ', + '$font_size' => '16px', + '$page_size' => 'A4', + '$country_2' => 'AF', + '$firstName' => 'Geo', + '$id_number' => 'Libero debitis.', + '$user.name' => 'Mr. Louvenia Armstrong Prof. Reyes Anderson', + '$font_name' => 'Roboto', + '$auto_bill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$poNumber' => null, + '$payments' => '', + '$viewLink' => ' +
+ + + + + + +
+ + View Purchase Order + +
+ +
+ ', + '$subtotal' => '$9,768.00', + '$company1' => ' ', + '$company2' => ' ', + '$company3' => ' ', + '$company4' => ' ', + '$due_date' => ' ', + '$discount' => 0.0, + '$address1' => '589', + '$address2' => '761 Odessa Centers Suite 673', + '$autoBill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$view_url' => 'http://ninja.test:8000/vendor/purchase_order/OwH1Bkl0AP3EBQxJpGvEsU7YbTk5durD', + '$font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', + '$details' => '', + '$balance' => '$0.00', + '$partial' => '$0.00', + '$custom1' => ' ', + '$custom2' => ' ', + '$custom3' => ' ', + '$custom4' => ' ', + '$dueDate' => ' ', + '$country' => 'Afghanistan', + '$vendor3' => 'Ea quia tempore.', + '$contact' => 'Geo Maggio', + '$account' => 'Mrs. Kristina Powlowski', + '$vendor1' => 'Necessitatibus aut.', + '$vendor4' => 'Nobis aut harum.', + '$vendor2' => 'Sit fuga quas sint.', + '$website' => 'http://abernathy.com/consequatur-at-beatae-nesciunt', + '$app_url' => 'http://ninja.test:8000', + '$footer' => '', + '$entity' => '', + '$thanks' => '', + '$amount' => '$10,256.40', + '$method' => ' ', + '$vendor' => 'Claudie Nikolaus MD', + '$number' => 'Live Preview #790', + '$email' => '', + '$terms' => '', + '$notes' => null, + '$tax_rate1' => '', + '$tax_rate2' => '', + '$tax_rate3' => '', + '$total' => '$10,256.40', + '$taxes' => '$488.40', + '$phone' => ' ', + '$from' => '', + '$item' => '', + '$date' => '14/Mar/2023', + '$tax' => '', + '$dir' => 'ltr', + '$to' => '', + ], + 'labels' => $this->vendorLabels(), +]; + } + + private function vendorLabels() + { + return [ + '$vendor.billing_postal_code_label' => ctrans('texts.billing_postal_code'), + '$company.postal_city_state_label' => ctrans('texts.postal_city_state'), + '$company.city_state_postal_label' => ctrans('texts.city_state_postal'), + '$product.gross_line_total_label' => ctrans('texts.gross_line_total'), + '$purchase_order.po_number_label' => ctrans('texts.po_number'), + '$vendor.postal_city_state_label' => ctrans('texts.postal_city_state'), + '$vendor.city_state_postal_label' => ctrans('texts.city_state_postal'), + '$purchase_order.due_date_label' => ctrans('texts.due_date'), + '$vendor.billing_address1_label' => ctrans('texts.billing_address1'), + '$vendor.billing_address2_label' => ctrans('texts.billing_address2'), + '$invoiceninja.whitelabel_label' => ctrans('texts.white_label'), + '$purchase_order.custom1_label' => ctrans('texts.custom1'), + '$purchase_order.custom2_label' => ctrans('texts.custom2'), + '$purchase_order.custom3_label' => ctrans('texts.custom3'), + '$purchase_order.custom4_label' => ctrans('texts.custom4'), + '$vendor.billing_address_label' => ctrans('texts.billing_address'), + '$vendor.billing_country_label' => ctrans('texts.billing_country'), + '$purchase_order.number_label' => ctrans('texts.number'), + '$purchase_order.total_label' => ctrans('texts.total'), + '$vendor.billing_state_label' => ctrans('texts.billing_state'), + '$product.description_label' => ctrans('texts.description'), + '$product.product_key_label' => ctrans('texts.product_key'), + '$entity.public_notes_label' => ctrans('texts.public_notes'), + '$purchase_order.date_label' => ctrans('texts.date'), + '$company.postal_code_label' => ctrans('texts.postal_code'), + '$company.postal_city_label' => ctrans('texts.postal_city'), + '$vendor.billing_city_label' => ctrans('texts.billing_city'), + '$vendor.public_notes_label' => ctrans('texts.public_notes'), + '$product.line_total_label' => ctrans('texts.line_total'), + '$product.tax_amount_label' => ctrans('texts.tax_amount'), + '$vendor.postal_code_label' => ctrans('texts.postal_code'), + '$vendor.postal_city_label' => ctrans('texts.postal_city'), + '$contact.first_name_label' => ctrans('texts.first_name'), + '$company.vat_number_label' => ctrans('texts.vat_number'), + '$contact.signature_label' => ctrans('texts.signature'), + '$product.tax_name1_label' => ctrans('texts.tax_name1'), + '$product.tax_name2_label' => ctrans('texts.tax_name2'), + '$product.tax_name3_label' => ctrans('texts.tax_name3'), + '$product.unit_cost_label' => ctrans('texts.unit_cost'), + '$custom_surcharge1_label' => ctrans('texts.custom_surcharge1'), + '$custom_surcharge2_label' => ctrans('texts.custom_surcharge2'), + '$custom_surcharge3_label' => ctrans('texts.custom_surcharge3'), + '$custom_surcharge4_label' => ctrans('texts.custom_surcharge4'), + '$postal_city_state_label' => ctrans('texts.postal_city_state'), + '$company_logo_size_label' => ctrans('texts.logo'), + '$vendor.vat_number_label' => ctrans('texts.vat_number'), + '$contact.full_name_label' => ctrans('texts.full_name'), + '$city_state_postal_label' => ctrans('texts.city_state_postal'), + '$contact.last_name_label' => ctrans('texts.last_name'), + '$company.country_2_label' => ctrans('texts.country'), + '$company.id_number_label' => ctrans('texts.id_number'), + '$product.product1_label' => ctrans('texts.product1'), + '$product.product2_label' => ctrans('texts.product2'), + '$product.product3_label' => ctrans('texts.product3'), + '$product.product4_label' => ctrans('texts.product4'), + '$statement_amount_label' => ctrans('texts.amount'), + '$product.discount_label' => ctrans('texts.discount'), + '$assigned_to_user_label' => ctrans('texts.assigned_to'), + '$entity_issued_to_label' => ctrans('texts.purchase_order_issued_to'), + '$product.quantity_label' => ctrans('texts.quantity'), + '$total_tax_labels_label' => ctrans('texts.total_taxes'), + '$total_tax_label' => ctrans('texts.total_taxes'), + '$partial_due_date_label' => ctrans('texts.partial_due_date'), + '$company.address2_label' => ctrans('texts.address2'), + '$company.address1_label' => ctrans('texts.address1'), + '$vendor.id_number_label' => ctrans('texts.id_number'), + '$contact.custom1_label' => ctrans('texts.custom1'), + '$contact.custom2_label' => ctrans('texts.custom2'), + '$contact.custom3_label' => ctrans('texts.custom3'), + '$contact.custom4_label' => ctrans('texts.custom4'), + '$secondary_color_label' => ctrans('texts.secondary_color'), + '$company.custom1_label' => ctrans('texts.custom1'), + '$company.custom2_label' => ctrans('texts.custom2'), + '$company.custom3_label' => ctrans('texts.custom3'), + '$company.custom4_label' => ctrans('texts.custom4'), + '$balance_due_raw_label' => ctrans('texts.balance_due'), + '$entity.datetime_label' => ctrans('texts.datetime_format_id'), + '$vendor.address1_label' => ctrans('texts.address1'), + '$vendor.address2_label' => ctrans('texts.address2'), + '$line_tax_label' => ctrans('texts.line_taxes'), + '$line_tax_labels_label' => ctrans('texts.line_taxes'), + '$company.address_label' => ctrans('texts.address'), + '$user.first_name_label' => ctrans('texts.first_name'), + '$created_by_user_label' => ctrans('texts.created_by', ['name' => 'Manuel']), + '$vendor.currency_label' => ctrans('texts.currency'), + '$company.country_label' => ctrans('texts.country'), + '$tech_hero_image_label' => ctrans('texts.logo'), + '$company.website_label' => ctrans('texts.website'), + '$gross_subtotal_label' => ctrans('texts.subtotal'), + '$emailSignature_label' => ctrans('texts.email_signature'), + '$vendor_address_label' => ctrans('texts.address'), + '$vendor.address_label' => ctrans('texts.address'), + '$vendor.country_label' => ctrans('texts.country'), + '$vendor.custom3_label' => ctrans('texts.custom3'), + '$vendor.custom1_label' => ctrans('texts.custom1'), + '$vendor.custom4_label' => ctrans('texts.custom4'), + '$user.last_name_label' => ctrans('texts.last_name'), + '$vendor.custom2_label' => ctrans('texts.custom2'), + '$vendor.website_label' => ctrans('texts.website'), + '$dir_text_align_label' => '', + '$entity_footer_label' => ctrans('texts.footer'), + '$entity_images_label' => ctrans('texts.logo'), + '$contact.email_label' => ctrans('texts.email'), + '$primary_color_label' => ctrans('texts.primary_color'), + '$contact.phone_label' => ctrans('texts.phone'), + '$vendor.number_label' => ctrans('texts.number'), + '$company.phone_label' => ctrans('texts.phone'), + '$global_margin_label' => '', + '$company.state_label' => ctrans('texts.state'), + '$entity_number_label' => ctrans('texts.purchase_order_number'), + '$company.email_label' => ctrans('texts.email'), + '$product.date_label' => ctrans('texts.date'), + '$vendor.email_label' => ctrans('texts.email'), + '$entity.terms_label' => ctrans('texts.terms'), + '$product.item_label' => ctrans('texts.item'), + '$public_notes_label' => ctrans('texts.public_notes'), + '$paid_to_date_label' => ctrans('texts.paid_to_date'), + '$net_subtotal_label' => ctrans('texts.net_subtotal'), + '$payment.date_label' => ctrans('texts.date'), + '$vendor.phone_label' => ctrans('texts.phone'), + '$contact.name_label' => ctrans('texts.name'), + '$number_short_label' => ctrans('texts.purchase_order_number_short'), + '$company.name_label' => ctrans('texts.name'), + '$company.city_label' => ctrans('texts.city'), + '$vendor.state_label' => ctrans('texts.state'), + '$company.logo_label' => ctrans('texts.logo'), + '$company_logo_label' => ctrans('texts.logo'), + '$description_label' => ctrans('texts.description'), + '$product.tax_label' => ctrans('texts.tax'), + '$view_button_label' => ctrans('texts.link'), + '$status_logo_label' => ctrans('texts.logo'), + '$partial_due_label' => ctrans('texts.partial_due'), + '$balance_due_label' => ctrans('texts.balance_due'), + '$outstanding_label' => ctrans('texts.outstanding'), + '$payment_due_label' => ctrans('texts.payment_due'), + '$postal_city_label' => ctrans('texts.postal_city'), + '$vendor_name_label' => ctrans('texts.vendor_name'), + '$vendor.name_label' => ctrans('texts.name'), + '$vendor.city_label' => ctrans('texts.city'), + '$page_layout_label' => ctrans('texts.page_layout'), + '$viewButton_label' => ctrans('texts.view'), + '$amount_due_label' => ctrans('texts.amount_due'), + '$amount_raw_label' => ctrans('texts.amount'), + '$vat_number_label' => ctrans('texts.vat_number'), + '$portal_url_label' => ctrans('texts.link'), + '$po_number_label' => ctrans('texts.po_number'), + '$statement_label' => ctrans('texts.statement'), + '$view_link_label' => ctrans('texts.link'), + '$signature_label' => ctrans('texts.signature'), + '$font_size_label' => ctrans('texts.font_size'), + '$page_size_label' => ctrans('texts.page_size'), + '$country_2_label' => ctrans('texts.country'), + '$firstName_label' => ctrans('texts.name'), + '$id_number_label' => ctrans('texts.id_number'), + '$user.name_label' => ctrans('texts.name'), + '$font_name_label' => ctrans('texts.name'), + '$auto_bill_label' => ctrans('texts.auto_bill'), + '$poNumber_label' => ctrans('texts.po_number'), + '$payments_label' => ctrans('texts.payments'), + '$viewLink_label' => ctrans('texts.link'), + '$subtotal_label' => ctrans('texts.subtotal'), + '$company1_label' => ctrans('texts.company1'), + '$company2_label' => ctrans('texts.company2'), + '$company3_label' => ctrans('texts.company3'), + '$company4_label' => ctrans('texts.company4'), + '$due_date_label' => ctrans('texts.due_date'), + '$discount_label' => ctrans('texts.discount'), + '$address1_label' => ctrans('texts.address1'), + '$address2_label' => ctrans('texts.address2'), + '$autoBill_label' => ctrans('texts.auto_bill'), + '$view_url_label' => ctrans('texts.url'), + '$font_url_label' => ctrans('texts.url'), + '$details_label' => ctrans('texts.details'), + '$balance_label' => ctrans('texts.balance'), + '$partial_label' => ctrans('texts.partial'), + '$custom1_label' => ctrans('texts.custom1'), + '$custom2_label' => ctrans('texts.custom2'), + '$custom3_label' => ctrans('texts.custom3'), + '$custom4_label' => ctrans('texts.custom4'), + '$dueDate_label' => ctrans('texts.due_date'), + '$country_label' => ctrans('texts.country'), + '$vendor3_label' => ctrans('texts.vendor3'), + '$contact_label' => ctrans('texts.contact'), + '$account_label' => ctrans('texts.company'), + '$vendor1_label' => ctrans('texts.vendor1'), + '$vendor4_label' => ctrans('texts.vendor4'), + '$vendor2_label' => ctrans('texts.vendor2'), + '$website_label' => ctrans('texts.website'), + '$app_url_label' => ctrans('texts.url'), + '$footer_label' => ctrans('texts.footer'), + '$entity_label' => ctrans('texts.purchase_order'), + '$thanks_label' => ctrans('texts.thanks'), + '$amount_label' => ctrans('texts.amount'), + '$method_label' => ctrans('texts.method'), + '$vendor_label' => ctrans('texts.vendor'), + '$number_label' => ctrans('texts.number'), + '$email_label' => ctrans('texts.email'), + '$terms_label' => ctrans('texts.terms'), + '$notes_label' => ctrans('texts.notes'), + '$tax_rate1_label' => ctrans('texts.tax_rate1'), + '$tax_rate2_label' => ctrans('texts.tax_rate2'), + '$tax_rate3_label' => ctrans('texts.tax_rate3'), + '$total_label' => ctrans('texts.total'), + '$taxes_label' => ctrans('texts.taxes'), + '$phone_label' => ctrans('texts.phone'), + '$from_label' => ctrans('texts.from'), + '$item_label' => ctrans('texts.item'), + '$date_label' => ctrans('texts.date'), + '$tax_label' => ctrans('texts.tax'), + '$dir_label' => '', + '$to_label' => ctrans('texts.to'), + ]; + } } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index c10c96b15766..eccdb0d4dfc3 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -679,7 +679,12 @@ class Design extends BaseDesign '$task.rate' => '$task.cost', ]; - foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) { + $table_type = "{$type}_columns"; + + if($type == 'product' && $this->entity instanceof Quote && !$this->settings_object->getSetting('sync_invoice_quote_columns')) + $table_type = "product_quote_columns"; + + foreach ($this->context['pdf_variables'][$table_type] as $column) { if (array_key_exists($column, $aliases)) { $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; } elseif ($column == '$product.discount' && !$this->company->enable_product_discount) { @@ -748,6 +753,14 @@ class Design extends BaseDesign return $elements; } + $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + $table_type = "{$_type}_columns"; + + if ($_type == 'product' && $this->entity instanceof Quote && !$this->settings_object->getSetting('sync_invoice_quote_columns')) { + $table_type = "product_quote_columns"; + } + + foreach ($items as $row) { $element = ['element' => 'tr', 'elements' => []]; @@ -775,9 +788,8 @@ class Design extends BaseDesign } } } else { - $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; - foreach ($this->context['pdf_variables']["{$_type}_columns"] as $key => $cell) { + foreach ($this->context['pdf_variables'][$table_type] as $key => $cell) { // We want to keep aliases like these: // $task.cost => $task.rate // $task.quantity => $task.hours diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 1d40bc51835f..3fb32229612e 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -13,8 +13,7 @@ namespace App\Services\Quote; use App\Jobs\Entity\EmailEntity; use App\Models\ClientContact; -use App\Services\Email\MailEntity; -use App\Services\Email\MailObject; + class SendEmail { @@ -46,11 +45,10 @@ class SendEmail $this->reminder_template = $this->quote->calculateTemplate('quote'); } - $mo = new MailObject(); $this->quote->service()->markSent()->save(); - $this->quote->invitations->each(function ($invitation) use ($mo) { + $this->quote->invitations->each(function ($invitation) { if (! $invitation->contact->trashed() && $invitation->contact->email) { EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template); diff --git a/app/Services/Recurring/IncreasePrice.php b/app/Services/Recurring/IncreasePrice.php new file mode 100644 index 000000000000..b8486a206710 --- /dev/null +++ b/app/Services/Recurring/IncreasePrice.php @@ -0,0 +1,43 @@ +recurring_invoice->line_items; + foreach ($line_items as $key => $line_item) { + + $line_items[$key]->cost = $line_item->cost * (1 + round(($this->percentage / 100), 2)); + + } + + $this->recurring_invoice->line_items = $line_items; + $this->recurring_invoice->calc()->getInvoice()->save(); + + + + } + + +} diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index 7a056a5c1d32..5c20a19bc5ed 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -11,18 +11,22 @@ namespace App\Services\Recurring; -use App\Jobs\RecurringInvoice\SendRecurring; use App\Jobs\Util\UnlinkFile; -use App\Models\RecurringInvoice; use Illuminate\Support\Carbon; +use App\Models\RecurringExpense; +use App\Models\RecurringInvoice; +use App\Services\Recurring\ApplyNumber; +use App\Services\Recurring\UpdatePrice; +use App\Services\Recurring\GetInvoicePdf; +use App\Services\Recurring\IncreasePrice; +use App\Jobs\RecurringInvoice\SendRecurring; +use App\Services\Recurring\CreateRecurringInvitations; class RecurringService { - protected $recurring_entity; - public function __construct($recurring_entity) + public function __construct(public RecurringInvoice | RecurringExpense $recurring_entity) { - $this->recurring_entity = $recurring_entity; } //set schedules - update next_send_dates @@ -135,6 +139,21 @@ class RecurringService { return $this; } + + public function increasePrice(float $percentage) + { + (new IncreasePrice($this->recurring_entity, $percentage))->run(); + + return $this; + + } + + public function updatePrice() + { + (new UpdatePrice($this->recurring_entity))->run(); + + return $this; + } public function save() { diff --git a/app/Services/Recurring/UpdatePrice.php b/app/Services/Recurring/UpdatePrice.php new file mode 100644 index 000000000000..1e196c68bc90 --- /dev/null +++ b/app/Services/Recurring/UpdatePrice.php @@ -0,0 +1,50 @@ +recurring_invoice->line_items; + + foreach($line_items as $key => $line_item) + { + + $product = Product::where('company_id', $this->recurring_invoice->company_id) + ->where('product_key', $line_item->product_key) + ->where('is_deleted', 0) + ->first(); + + if($product){ + + $line_items[$key]->cost = $product->cost; + } + + } + + $this->recurring_invoice->line_items = $line_items; + $this->recurring_invoice->calc()->getInvoice()->save(); + + + } +} \ No newline at end of file diff --git a/app/Transformers/CompanyUserTransformer.php b/app/Transformers/CompanyUserTransformer.php index f507ec603a6c..6ae6f81474f4 100644 --- a/app/Transformers/CompanyUserTransformer.php +++ b/app/Transformers/CompanyUserTransformer.php @@ -80,7 +80,11 @@ class CompanyUserTransformer extends EntityTransformer public function includeToken(CompanyUser $company_user) { - $token = $company_user->tokens()->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first(); + $token = $company_user->tokens() + ->where('company_id', $company_user->company_id) + ->where('user_id', $company_user->user_id) + ->where('is_system', 1) + ->first(); $transformer = new CompanyTokenTransformer($this->serializer); diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 8aa488d454a8..3872ef95666e 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -683,6 +683,8 @@ class HtmlEngine $data['labels'][$key.'_label'] = $value['label']; } + // nlog($data); + return $data; } @@ -762,9 +764,6 @@ class HtmlEngine if ($country) { return $country->iso_3166_2; } - // if ($country) { - // return ctrans('texts.country_' . $country->iso_3166_2); - // } return ' '; } diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index aab9b9fb3302..4eac57ac59bc 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -511,7 +511,7 @@ class VendorHtmlEngine $data['values'][$key] = $value['value']; $data['labels'][$key.'_label'] = $value['label']; } - +nlog($data); return $data; } diff --git a/composer.lock b/composer.lock index 6483f46fdb0d..6cf928a7b942 100644 --- a/composer.lock +++ b/composer.lock @@ -2171,16 +2171,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.289.0", + "version": "v0.290.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7" + "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/937f83a927db2d09db7eebb69ce2ac4114559bd7", - "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/df7e6cbab08f60509b3f360d8286c194ad2930e2", + "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2", "shasum": "" }, "require": { @@ -2209,9 +2209,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.289.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.290.0" }, - "time": "2023-02-26T01:10:11+00:00" + "time": "2023-03-01T17:20:18+00:00" }, { "name": "google/auth", @@ -14019,16 +14019,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.14.4", + "version": "v3.15.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b" + "reference": "7306744c63e9cc1337894252b4eec4920c38b053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1b3d9dba63d93b8a202c31e824748218781eae6b", - "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7306744c63e9cc1337894252b4eec4920c38b053", + "reference": "7306744c63e9cc1337894252b4eec4920c38b053", "shasum": "" }, "require": { @@ -14095,9 +14095,15 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.4" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.15.0" }, "funding": [ { @@ -14105,7 +14111,7 @@ "type": "github" } ], - "time": "2023-02-09T21:49:13+00:00" + "time": "2023-03-12T22:44:55+00:00" }, { "name": "hamcrest/hamcrest-php", diff --git a/config/liap.php b/config/liap.php index b7f91958ab8e..f140554cf65f 100644 --- a/config/liap.php +++ b/config/liap.php @@ -1,5 +1,10 @@ env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.89', - 'app_tag' => '5.5.89', + 'app_version' => '5.5.90', + 'app_tag' => '5.5.90', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/config/purchase.php b/config/purchase.php deleted file mode 100644 index 6501dc836aaa..000000000000 --- a/config/purchase.php +++ /dev/null @@ -1,74 +0,0 @@ - [], - - 'google_play_package_name' => env('GOOGLE_PLAY_PACKAGE_NAME', 'com.invoiceninja.app'), - - 'appstore_password' => env('APPSTORE_PASSWORD', ''), - - 'eventListeners' => [ - /** - * -------------------------------------------------------- - * Google Play Events - * -------------------------------------------------------- - */ - SubscriptionPurchased::class => [], - SubscriptionRenewed::class => [PlayStoreRenewSubscription::class], - SubscriptionInGracePeriod::class => [], - SubscriptionExpired::class => [], - SubscriptionCanceled::class => [], - SubscriptionPaused::class => [], - SubscriptionRestarted::class => [], - SubscriptionDeferred::class => [], - SubscriptionRevoked::class => [], - SubscriptionOnHold::class => [], - SubscriptionRecovered::class => [], - SubscriptionPauseScheduleChanged::class => [], - SubscriptionPriceChangeConfirmed::class => [], - - /** - * -------------------------------------------------------- - * Appstore Events - * -------------------------------------------------------- - */ - Cancel::class => [], - DidChangeRenewalPref::class => [], - DidChangeRenewalStatus::class => [], - DidFailToRenew::class => [], - DidRecover::class => [], - DidRenew::class => [AppStoreRenewSubscription::class], - InitialBuy::class => [], - InteractiveRenewal::class => [], - PriceIncreaseConsent::class => [], - Refund::class => [], - Revoke::class => [], - ], -]; \ No newline at end of file diff --git a/resources/views/email/template/client.blade.php b/resources/views/email/template/client.blade.php index 544437512b3d..d99d69d3dd76 100644 --- a/resources/views/email/template/client.blade.php +++ b/resources/views/email/template/client.blade.php @@ -118,6 +118,9 @@ background-color: {{ $primary_color }}; } + .logo { + + }