mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
c36e2b8e26
@ -1 +1 @@
|
||||
5.5.89
|
||||
5.5.90
|
@ -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) {
|
||||
|
@ -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',
|
||||
|
@ -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.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -15,7 +16,6 @@ use App\DataMapper\Analytics\LoginFailure;
|
||||
use App\DataMapper\Analytics\LoginSuccess;
|
||||
use App\Events\User\UserLoggedIn;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Login\LoginRequest;
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Jobs\Company\CreateCompanyToken;
|
||||
@ -23,8 +23,6 @@ use App\Libraries\MultiDB;
|
||||
use App\Libraries\OAuth\OAuth;
|
||||
use App\Libraries\OAuth\Providers\Google;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
@ -38,7 +36,6 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Microsoft\Graph\Model;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
@ -46,18 +43,7 @@ use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="login",
|
||||
* description="Authentication",
|
||||
* @OA\ExternalDocumentation(
|
||||
* description="Find out more",
|
||||
* url="https://invoiceninja.github.io"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
use AuthenticatesUsers;
|
||||
|
||||
use UserSessionAttributes;
|
||||
use LoginCache;
|
||||
|
||||
@ -89,7 +75,7 @@ class LoginController extends BaseController
|
||||
* @param Request $request
|
||||
* @param User $user
|
||||
* @return void
|
||||
* deprecated .1 API ONLY we don't need to set any session variables
|
||||
* @deprecated .1 API ONLY we don't need to set any session variables
|
||||
*/
|
||||
public function authenticated(Request $request, User $user): void
|
||||
{
|
||||
@ -99,63 +85,8 @@ class LoginController extends BaseController
|
||||
/**
|
||||
* Login via API.
|
||||
*
|
||||
* @param Request $request The request
|
||||
*
|
||||
* @return Response|User Process user login.
|
||||
*
|
||||
* @param LoginRequest $request The request
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @OA\Post(
|
||||
* path="/api/v1/login",
|
||||
* operationId="postLogin",
|
||||
* tags={"login"},
|
||||
* summary="Attempts authentication",
|
||||
* description="Returns a CompanyUser object on success",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-SECRET"),
|
||||
* @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\RequestBody(
|
||||
* description="User credentials",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="object",
|
||||
* @OA\Property(
|
||||
* property="email",
|
||||
* description="The user email address",
|
||||
* type="string",
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="password",
|
||||
* example="1234567",
|
||||
* description="The user password must meet minimum criteria ~ >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)
|
||||
{
|
||||
@ -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')) {
|
||||
|
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users as well as their
|
||||
| validation and creation. By default this controller uses a trait to
|
||||
| provide this functionality without requiring any additional code.
|
||||
|
|
||||
*/
|
||||
|
||||
use RegistersUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/dashboard';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->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']),
|
||||
]);
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Foundation\Auth\VerifiesEmails;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class VerificationController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Email Verification Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller is responsible for handling email verification for any
|
||||
| user that recently registered with the application. Emails may also
|
||||
| be resent if the user did not receive the original email message.
|
||||
|
|
||||
*/
|
||||
|
||||
use VerifiesEmails;
|
||||
|
||||
/**
|
||||
* Where to redirect users after verification.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/dashboard';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
$this->middleware('signed')->only('verify');
|
||||
$this->middleware('throttle:6,1')->only('verify', 'resend');
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -43,6 +43,7 @@ class SendEmailRequest extends Request
|
||||
'template' => 'bail|required',
|
||||
'entity' => 'bail|required',
|
||||
'entity_id' => 'bail|required',
|
||||
'cc_email' => 'bail|sometimes|email',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\RecurringInvoice;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class BulkRecurringInvoiceRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'ids' => ['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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
61
app/Jobs/RecurringInvoice/UpdateRecurring.php
Normal file
61
app/Jobs/RecurringInvoice/UpdateRecurring.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\RecurringInvoice;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdateRecurring implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public array $ids, public Company $company, public User $user, protected string $action, protected float $percentage = 0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() : void
|
||||
{
|
||||
MultiDB::setDb($this->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)
|
||||
{
|
||||
}
|
||||
}
|
@ -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)]);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -735,7 +735,9 @@ class RecurringInvoice extends BaseModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Service entry points.
|
||||
* service
|
||||
*
|
||||
* @return RecurringService
|
||||
*/
|
||||
public function service() :RecurringService
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ class CreditCard
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
// nlog($request->all());
|
||||
$this->braintree->client->fresh();
|
||||
|
||||
$state = [
|
||||
'server_response' => json_decode($request->gateway_response),
|
||||
@ -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')) {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
43
app/Services/Recurring/IncreasePrice.php
Normal file
43
app/Services/Recurring/IncreasePrice.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Recurring;
|
||||
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Services\AbstractService;
|
||||
|
||||
class IncreasePrice extends AbstractService
|
||||
{
|
||||
|
||||
public function __construct(public RecurringInvoice $recurring_invoice, public float $percentage)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$line_items = $this->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();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
@ -136,6 +140,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()
|
||||
{
|
||||
$this->recurring_entity->saveQuietly();
|
||||
|
50
app/Services/Recurring/UpdatePrice.php
Normal file
50
app/Services/Recurring/UpdatePrice.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Recurring;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Services\AbstractService;
|
||||
|
||||
class UpdatePrice extends AbstractService
|
||||
{
|
||||
public function __construct(public RecurringInvoice $recurring_invoice)
|
||||
{
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
|
||||
$line_items = $this->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();
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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 ' ';
|
||||
}
|
||||
|
@ -511,7 +511,7 @@ class VendorHtmlEngine
|
||||
$data['values'][$key] = $value['value'];
|
||||
$data['labels'][$key.'_label'] = $value['label'];
|
||||
}
|
||||
|
||||
nlog($data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
30
composer.lock
generated
30
composer.lock
generated
@ -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",
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Imdhemy\Purchases\Events\AppStore\DidRenew;
|
||||
use App\Listeners\Subscription\AppStoreRenewSubscription;
|
||||
use App\Listeners\Subscription\PlayStoreRenewSubscription;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => 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', ''),
|
||||
|
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Imdhemy\Purchases\Events\AppStore\Cancel;
|
||||
use Imdhemy\Purchases\Events\AppStore\Refund;
|
||||
use Imdhemy\Purchases\Events\AppStore\Revoke;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidRenew;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidRecover;
|
||||
use Imdhemy\Purchases\Events\AppStore\InitialBuy;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidFailToRenew;
|
||||
use App\Listeners\Subscription\AppStoreRenewSubscription;
|
||||
use Imdhemy\Purchases\Events\AppStore\InteractiveRenewal;
|
||||
use App\Listeners\Subscription\PlayStoreRenewSubscription;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidChangeRenewalPref;
|
||||
use Imdhemy\Purchases\Events\AppStore\PriceIncreaseConsent;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionOnHold;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPaused;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionExpired;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRevoked;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidChangeRenewalStatus;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionCanceled;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionDeferred;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPurchased;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRecovered;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRestarted;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionInGracePeriod;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPauseScheduleChanged;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPriceChangeConfirmed;
|
||||
|
||||
return [
|
||||
'routing' => [],
|
||||
|
||||
'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 => [],
|
||||
],
|
||||
];
|
@ -118,6 +118,9 @@
|
||||
background-color: {{ $primary_color }};
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--[if gte mso 9]>
|
||||
|
@ -18,6 +18,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -40,6 +41,34 @@ class InvoiceEmailTest extends TestCase
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
}
|
||||
|
||||
public function test_cc_email_implementation()
|
||||
{
|
||||
$data = [
|
||||
'template' => 'email_template_invoice',
|
||||
'entity' => 'invoice',
|
||||
'entity_id' => $this->invoice->hashed_id,
|
||||
'cc_email' => 'jj@gmail.com'
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/emails', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
public function test_initial_email_send_emails()
|
||||
|
@ -13,12 +13,15 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\InvoiceToRecurringInvoiceFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
||||
use App\Jobs\RecurringInvoice\UpdateRecurring;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Helpers;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
@ -53,6 +56,194 @@ class RecurringInvoiceTest extends TestCase
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testBulkIncreasePriceWithJob()
|
||||
{
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
$line_items[] = [
|
||||
'product_key' => 'pink',
|
||||
'notes' => 'test',
|
||||
'cost' => 10,
|
||||
'quantity' => 1,
|
||||
'tax_name1' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name3' => '',
|
||||
'tax_rate3' => 0,
|
||||
];
|
||||
$recurring_invoice->line_items = $line_items;
|
||||
|
||||
$recurring_invoice->calc()->getInvoice()->service()->start()->save()->fresh();
|
||||
|
||||
(new UpdateRecurring([$recurring_invoice->id], $this->company, $this->user, 'increase_prices', 10))->handle();
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(11, $recurring_invoice->amount);
|
||||
|
||||
}
|
||||
|
||||
public function testBulkUpdateWithJob()
|
||||
{
|
||||
$p = Product::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'cost' => 20,
|
||||
'product_key' => 'pink',
|
||||
]);
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
$line_items[] = [
|
||||
'product_key' => 'pink',
|
||||
'notes' => 'test',
|
||||
'cost' => 10,
|
||||
'quantity' => 1,
|
||||
'tax_name1' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name3' => '',
|
||||
'tax_rate3' => 0,
|
||||
];
|
||||
$recurring_invoice->line_items = $line_items;
|
||||
|
||||
$recurring_invoice->calc()->getInvoice()->service()->start()->save()->fresh();
|
||||
|
||||
(new UpdateRecurring([$recurring_invoice->id], $this->company, $this->user, 'update_prices'))->handle();
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(20, $recurring_invoice->amount);
|
||||
|
||||
}
|
||||
|
||||
public function testBulkUpdatePrices()
|
||||
{
|
||||
$p = Product::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'cost' => 10,
|
||||
'product_key' => 'pink',
|
||||
]);
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
$line_items[] = [
|
||||
'product_key' => 'pink',
|
||||
'notes' => 'test',
|
||||
'cost' => 10,
|
||||
'quantity' => 1,
|
||||
'tax_name1' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name3' => '',
|
||||
'tax_rate3' => 0,
|
||||
];
|
||||
$recurring_invoice->line_items = $line_items;
|
||||
|
||||
$recurring_invoice->calc()->getInvoice()->service()->start()->save()->fresh();
|
||||
|
||||
$this->assertEquals(10, $recurring_invoice->amount);
|
||||
|
||||
$p->cost = 20;
|
||||
$p->save();
|
||||
|
||||
$recurring_invoice->service()->updatePrice();
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(20, $recurring_invoice->amount);
|
||||
|
||||
$recurring_invoice->service()->increasePrice(10);
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(22, $recurring_invoice->amount);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testBulkUpdateMultiPrices()
|
||||
{
|
||||
$p1 = Product::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'cost' => 10,
|
||||
'product_key' => 'pink',
|
||||
]);
|
||||
|
||||
$p2 = Product::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'cost' => 20,
|
||||
'product_key' => 'floyd',
|
||||
]);
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
$line_items[] = [
|
||||
'product_key' => 'floyd',
|
||||
'notes' => 'test',
|
||||
'cost' => 20,
|
||||
'quantity' => 1,
|
||||
'tax_name1' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name3' => '',
|
||||
'tax_rate3' => 0,
|
||||
];
|
||||
|
||||
$line_items[] = [
|
||||
'product_key' => 'pink',
|
||||
'notes' => 'test',
|
||||
'cost' => 10,
|
||||
'quantity' => 1,
|
||||
'tax_name1' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name3' => '',
|
||||
'tax_rate3' => 0,
|
||||
];
|
||||
|
||||
$recurring_invoice->line_items = $line_items;
|
||||
|
||||
$recurring_invoice->calc()->getInvoice()->service()->start()->save()->fresh();
|
||||
|
||||
$this->assertEquals(30, $recurring_invoice->amount);
|
||||
|
||||
$p1->cost = 20;
|
||||
$p1->save();
|
||||
|
||||
$p2->cost = 40;
|
||||
$p2->save();
|
||||
|
||||
$recurring_invoice->service()->updatePrice();
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(60, $recurring_invoice->amount);
|
||||
|
||||
$recurring_invoice->service()->increasePrice(10);
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(66, $recurring_invoice->amount);
|
||||
|
||||
$recurring_invoice->service()->increasePrice(1);
|
||||
|
||||
$recurring_invoice->refresh();
|
||||
|
||||
$this->assertEquals(66.66, $recurring_invoice->amount);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testRecurringGetStatus()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
|
@ -219,6 +219,7 @@ trait MockAccountData
|
||||
$this->account = Account::factory()->create([
|
||||
'hosted_client_count' => 1000000,
|
||||
'hosted_company_count' => 1000000,
|
||||
'account_sms_verified' => true,
|
||||
]);
|
||||
|
||||
$this->account->num_users = 3;
|
||||
|
Loading…
x
Reference in New Issue
Block a user