Merge pull request #7792 from turbo124/v5-develop

v5.5.18
This commit is contained in:
David Bomba 2022-09-01 20:28:26 +10:00 committed by GitHub
commit 68ff176725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1508 additions and 147 deletions

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
matrix: matrix:
operating-system: ['ubuntu-18.04', 'ubuntu-20.04', 'ubuntu-22.04'] operating-system: ['ubuntu-20.04', 'ubuntu-22.04']
php-versions: ['8.1'] php-versions: ['8.1']
phpunit-versions: ['latest'] phpunit-versions: ['latest']

View File

@ -1 +1 @@
5.5.17 5.5.18

View File

@ -29,6 +29,7 @@ use App\Models\Payment;
use App\Models\Paymentable; use App\Models\Paymentable;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use App\Models\User;
use App\Models\Vendor; use App\Models\Vendor;
use App\Utils\Ninja; use App\Utils\Ninja;
use Exception; use Exception;
@ -115,7 +116,8 @@ class CheckData extends Command
$this->checkCompanyData(); $this->checkCompanyData();
$this->checkBalanceVsPaidStatus(); $this->checkBalanceVsPaidStatus();
$this->checkDuplicateRecurringInvoices(); $this->checkDuplicateRecurringInvoices();
$this->checkOauthSanity();
if(Ninja::isHosted()) if(Ninja::isHosted())
$this->checkAccountStatuses(); $this->checkAccountStatuses();
@ -146,6 +148,15 @@ class CheckData extends Command
$this->log .= $str."\n"; $this->log .= $str."\n";
} }
private function checkOauthSanity()
{
User::where('oauth_provider_id', '1')->cursor()->each(function ($user){
$this->logMessage("Invalid provider ID for user id# {$user->id}");
});
}
private function checkDuplicateRecurringInvoices() private function checkDuplicateRecurringInvoices()
{ {
@ -955,7 +966,7 @@ class CheckData extends Command
{ {
$this->wrong_paid_status = 0; $this->wrong_paid_status = 0;
foreach(Invoice::with(['payments'])->whereHas('payments')->where('status_id', 4)->where('balance', '>', 0)->where('is_deleted',0)->cursor() as $invoice) foreach(Invoice::with(['payments'])->where('is_deleted',0)->where('balance', '>', 0)->whereHas('payments')->where('status_id', 4)->cursor() as $invoice)
{ {
$this->wrong_paid_status++; $this->wrong_paid_status++;

View File

@ -168,6 +168,9 @@ class ClientFilters extends QueryFilters
{ {
$sort_col = explode('|', $sort); $sort_col = explode('|', $sort);
if($sort_col[0] == 'display_name')
$sort_col[0] = 'name';
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -107,6 +107,7 @@ class ActivityController extends BaseController
'payment' => $activity->payment ? $activity->payment : '', 'payment' => $activity->payment ? $activity->payment : '',
'credit' => $activity->credit ? $activity->credit : '', 'credit' => $activity->credit ? $activity->credit : '',
'task' => $activity->task ? $activity->task : '', 'task' => $activity->task ? $activity->task : '',
'vendor' => $activity->vendor ? $activity->vendor : '',
]; ];
return array_merge($arr, $activity->toArray()); return array_merge($arr, $activity->toArray());

View File

@ -351,7 +351,7 @@ class LoginController extends BaseController
private function handleSocialiteLogin($provider, $token) private function handleSocialiteLogin($provider, $token)
{ {
$user = $this->getSocialiteUser($provider, $token); $user = $this->getSocialiteUser($provider, $token);
nlog($user);
if ($user) { if ($user) {
return $this->loginOrCreateFromSocialite($user, $provider); return $this->loginOrCreateFromSocialite($user, $provider);
} }
@ -368,6 +368,7 @@ class LoginController extends BaseController
'oauth_user_id' => $user->id, 'oauth_user_id' => $user->id,
'oauth_provider_id' => $provider, 'oauth_provider_id' => $provider,
]; ];
if ($existing_user = MultiDB::hasUser($query)) { if ($existing_user = MultiDB::hasUser($query)) {
if (!$existing_user->account) { if (!$existing_user->account) {
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
@ -749,10 +750,6 @@ class LoginController extends BaseController
public function handleMicrosoftProviderCallback($provider = 'microsoft') public function handleMicrosoftProviderCallback($provider = 'microsoft')
{ {
$socialite_user = Socialite::driver($provider)->user(); $socialite_user = Socialite::driver($provider)->user();
nlog($socialite_user);
nlog('refresh token ' . $socialite_user->accessTokenResponseBody['refresh_token']);
nlog('access token ' . $socialite_user->accessTokenResponseBody['access_token']);
$oauth_user_token = $socialite_user->accessTokenResponseBody['access_token']; $oauth_user_token = $socialite_user->accessTokenResponseBody['access_token'];

View File

@ -69,7 +69,7 @@ class InvoiceController extends Controller
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice,
'invitation' => $invitation, 'invitation' => $invitation ?: $invoice->invitations->first(),
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
]; ];

View File

@ -195,6 +195,7 @@ class NinjaPlanController extends Controller
public function plan() public function plan()
{ {
// return $this->trial(); // return $this->trial();
//harvest the current plan //harvest the current plan
$data = []; $data = [];

View File

@ -101,7 +101,8 @@ class PaymentController extends Controller
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice,
'key' => false 'key' => false,
'invitation' => $invoice->invitations->first()
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {

View File

@ -139,9 +139,14 @@ class Request extends FormRequest
$input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']); $input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']);
} }
if (is_string($input['invitations'][$key]['client_contact_id'])) { if (array_key_exists('client_contact_id', $input['invitations'][$key]) && is_string($input['invitations'][$key]['client_contact_id'])) {
$input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']); $input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']);
} }
if (array_key_exists('vendor_contact_id', $input['invitations'][$key]) && is_string($input['invitations'][$key]['vendor_contact_id'])) {
$input['invitations'][$key]['vendor_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['vendor_contact_id']);
}
} }
} }

View File

@ -33,6 +33,15 @@ class VendorMap
14 => 'vendor.state', 14 => 'vendor.state',
15 => 'vendor.postal_code', 15 => 'vendor.postal_code',
16 => 'vendor.country_id', 16 => 'vendor.country_id',
17 => 'contact.first_name',
18 => 'contact.last_name',
19 => 'contact.email',
20 => 'contact.phone',
21 => 'contact.custom_value1',
22 => 'contact.custom_value2',
23 => 'contact.custom_value3',
24 => 'contact.custom_value4',
]; ];
} }
@ -56,6 +65,14 @@ class VendorMap
14 => 'texts.state', 14 => 'texts.state',
15 => 'texts.postal_code', 15 => 'texts.postal_code',
16 => 'texts.country', 16 => 'texts.country',
17 => 'texts.first_name',
18 => 'texts.last_name',
19 => 'texts.email',
20 => 'texts.phone',
21 => 'texts.custom_value',
22 => 'texts.custom_value',
23 => 'texts.custom_value',
24 => 'texts.custom_value',
]; ];
} }
} }

View File

@ -16,12 +16,14 @@ use App\Factory\ExpenseFactory;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Factory\PaymentFactory; use App\Factory\PaymentFactory;
use App\Factory\ProductFactory; use App\Factory\ProductFactory;
use App\Factory\QuoteFactory;
use App\Factory\VendorFactory; use App\Factory\VendorFactory;
use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Client\StoreClientRequest;
use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Expense\StoreExpenseRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Payment\StorePaymentRequest; use App\Http\Requests\Payment\StorePaymentRequest;
use App\Http\Requests\Product\StoreProductRequest; use App\Http\Requests\Product\StoreProductRequest;
use App\Http\Requests\Quote\StoreQuoteRequest;
use App\Http\Requests\Vendor\StoreVendorRequest; use App\Http\Requests\Vendor\StoreVendorRequest;
use App\Import\ImportException; use App\Import\ImportException;
use App\Import\Providers\BaseImport; use App\Import\Providers\BaseImport;
@ -31,12 +33,14 @@ use App\Import\Transformer\Csv\ExpenseTransformer;
use App\Import\Transformer\Csv\InvoiceTransformer; use App\Import\Transformer\Csv\InvoiceTransformer;
use App\Import\Transformer\Csv\PaymentTransformer; use App\Import\Transformer\Csv\PaymentTransformer;
use App\Import\Transformer\Csv\ProductTransformer; use App\Import\Transformer\Csv\ProductTransformer;
use App\Import\Transformer\Csv\QuoteTransformer;
use App\Import\Transformer\Csv\VendorTransformer; use App\Import\Transformer\Csv\VendorTransformer;
use App\Repositories\ClientRepository; use App\Repositories\ClientRepository;
use App\Repositories\ExpenseRepository; use App\Repositories\ExpenseRepository;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Repositories\PaymentRepository; use App\Repositories\PaymentRepository;
use App\Repositories\ProductRepository; use App\Repositories\ProductRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\VendorRepository; use App\Repositories\VendorRepository;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
@ -55,6 +59,7 @@ class Csv extends BaseImport implements ImportInterface
'payment', 'payment',
'vendor', 'vendor',
'expense', 'expense',
'quote',
]) ])
) { ) {
$this->{$entity}(); $this->{$entity}();
@ -151,6 +156,35 @@ class Csv extends BaseImport implements ImportInterface
$this->entity_count['invoices'] = $invoice_count; $this->entity_count['invoices'] = $invoice_count;
} }
public function quote()
{
$entity_type = 'quote';
$data = $this->getCsvData($entity_type);
if (is_array($data)) {
$data = $this->preTransformCsv($data, $entity_type);
}
if (empty($data)) {
$this->entity_count['quotes'] = 0;
return;
}
$this->request_name = StoreQuoteRequest::class;
$this->repository_name = QuoteRepository::class;
$this->factory_name = QuoteFactory::class;
$this->repository = app()->make($this->repository_name);
$this->repository->import_mode = true;
$this->transformer = new QuoteTransformer($this->company);
$quote_count = $this->ingestQuotes($data, 'quote.number');
$this->entity_count['quotes'] = $quote_count;
}
public function payment() public function payment()
{ {
$entity_type = 'payment'; $entity_type = 'payment';
@ -241,10 +275,6 @@ class Csv extends BaseImport implements ImportInterface
$this->entity_count['expenses'] = $expense_count; $this->entity_count['expenses'] = $expense_count;
} }
public function quote()
{
}
public function task() public function task()
{ {
} }

View File

@ -19,6 +19,14 @@ use App\Models\Country;
use App\Models\ExpenseCategory; use App\Models\ExpenseCategory;
use App\Models\PaymentType; use App\Models\PaymentType;
use App\Models\User; use App\Models\User;
use App\Models\Expense;
use App\Models\Project;
use App\Models\Invoice;
use App\Models\Quote;
use App\Models\Client;
use App\Models\TaxRate;
use App\Models\Product;
use App\Models\Vendor;
use App\Utils\Number; use App\Utils\Number;
use Exception; use Exception;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -67,8 +75,7 @@ class BaseTransformer
{ {
if (! empty($client_name)) { if (! empty($client_name)) {
$client_id_search = $this->company $client_id_search = Client::where('company_id', $this->company->id)
->clients()
->where('is_deleted', false) ->where('is_deleted', false)
->where('id_number', $client_name); ->where('id_number', $client_name);
@ -76,8 +83,7 @@ class BaseTransformer
return $client_id_search->first()->id; return $client_id_search->first()->id;
} }
$client_name_search = $this->company $client_name_search = Client::where('company_id', $this->company->id)
->clients()
->where('is_deleted', false) ->where('is_deleted', false)
->where('name', $client_name); ->where('name', $client_name);
@ -108,8 +114,7 @@ class BaseTransformer
*/ */
public function hasClient($name) public function hasClient($name)
{ {
return $this->company return Client::where('company_id', $this->company->id)
->clients()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -124,8 +129,7 @@ class BaseTransformer
*/ */
public function hasVendor($name) public function hasVendor($name)
{ {
return $this->company return Vendor::where('company_id', $this->company->id)
->vendors()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -140,8 +144,7 @@ class BaseTransformer
*/ */
public function hasProject($name) public function hasProject($name)
{ {
return $this->company return Project::where('company_id', $this->company->id)
->projects()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -156,8 +159,7 @@ class BaseTransformer
*/ */
public function hasProduct($key) public function hasProduct($key)
{ {
return $this->company return Product::where('company_id', $this->company->id)
->products()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)), strtolower(str_replace(' ', '', $key)),
@ -189,8 +191,7 @@ class BaseTransformer
*/ */
public function getClientId($name) public function getClientId($name)
{ {
$client = $this->company $client = Client::where('company_id', $this->company->id)
->clients()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -207,8 +208,7 @@ class BaseTransformer
*/ */
public function getProduct($key) public function getProduct($key)
{ {
$product = $this->company $product = Product::where('company_id', $this->company->id)
->products()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)), strtolower(str_replace(' ', '', $key)),
@ -225,8 +225,7 @@ class BaseTransformer
*/ */
public function getContact($email) public function getContact($email)
{ {
$contact = $this->company $contact = ClientContact::where('company_id', $this->company->id)
->client_contacts()
->whereRaw("LOWER(REPLACE(`email`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`email`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $email)), strtolower(str_replace(' ', '', $email)),
]) ])
@ -278,8 +277,7 @@ class BaseTransformer
{ {
$name = strtolower(trim($name)); $name = strtolower(trim($name));
$tax_rate = $this->company $tax_rate = TaxRate::where('company_id', $this->company->id)
->tax_rates()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -298,8 +296,7 @@ class BaseTransformer
{ {
$name = strtolower(trim($name)); $name = strtolower(trim($name));
$tax_rate = $this->company $tax_rate = TaxRate::where('company_id', $this->company->id)
->tax_rates()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -348,8 +345,7 @@ class BaseTransformer
*/ */
public function getInvoiceId($invoice_number) public function getInvoiceId($invoice_number)
{ {
$invoice = $this->company $invoice = Invoice::where('company_id', $this->company->id)
->invoices()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
@ -366,8 +362,7 @@ class BaseTransformer
*/ */
public function hasInvoice($invoice_number) public function hasInvoice($invoice_number)
{ {
return $this->company return Invoice::where('company_id', $this->company->id)
->invoices()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
@ -380,8 +375,7 @@ class BaseTransformer
*/ */
public function hasExpense($expense_number) public function hasExpense($expense_number)
{ {
return $this->company return Expense::where('company_id', $this->company->id)
->expenses()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $expense_number)), strtolower(str_replace(' ', '', $expense_number)),
@ -396,8 +390,7 @@ class BaseTransformer
*/ */
public function hasQuote($quote_number) public function hasQuote($quote_number)
{ {
return $this->company return Quote::where('company_id', $this->company->id)
->quotes()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $quote_number)), strtolower(str_replace(' ', '', $quote_number)),
@ -412,8 +405,7 @@ class BaseTransformer
*/ */
public function getInvoiceClientId($invoice_number) public function getInvoiceClientId($invoice_number)
{ {
$invoice = $this->company $invoice = Invoice::where('company_id', $this->company->id)
->invoices()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
@ -430,8 +422,7 @@ class BaseTransformer
*/ */
public function getVendorId($name) public function getVendorId($name)
{ {
$vendor = $this->company $vendor = Vendor::where('company_id', $this->company->id)
->vendors()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -467,8 +458,7 @@ class BaseTransformer
*/ */
public function getExpenseCategoryId($name) public function getExpenseCategoryId($name)
{ {
$ec = $this->company $ec = ExpenseCategory::where('company_id', $this->company->id)
->expense_categories()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
@ -504,8 +494,7 @@ class BaseTransformer
*/ */
public function getProjectId($name, $clientId = null) public function getProjectId($name, $clientId = null)
{ {
$project = $this->company $project = Project::where('company_id', $this->company->id)
->projects()
->where('is_deleted', false) ->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),

View File

@ -52,15 +52,42 @@ class VendorTransformer extends BaseTransformer
'custom_value2' => $this->getString($data, 'vendor.custom_value2'), 'custom_value2' => $this->getString($data, 'vendor.custom_value2'),
'custom_value3' => $this->getString($data, 'vendor.custom_value3'), 'custom_value3' => $this->getString($data, 'vendor.custom_value3'),
'custom_value4' => $this->getString($data, 'vendor.custom_value4'), 'custom_value4' => $this->getString($data, 'vendor.custom_value4'),
'vendor_contacts' => [ // 'vendor_contacts' => [
// [
// 'first_name' => $this->getString(
// $data,
// 'vendor.first_name'
// ),
// 'last_name' => $this->getString($data, 'vendor.last_name'),
// 'email' => $this->getString($data, 'vendor.email'),
// 'phone' => $this->getString($data, 'vendor.phone'),
// ],
// ],
'contacts' => [
[ [
'first_name' => $this->getString( 'first_name' => $this->getString(
$data, $data,
'vendor.first_name' 'contact.first_name'
),
'last_name' => $this->getString($data, 'contact.last_name'),
'email' => $this->getString($data, 'contact.email'),
'phone' => $this->getString($data, 'contact.phone'),
'custom_value1' => $this->getString(
$data,
'contact.custom_value1'
),
'custom_value2' => $this->getString(
$data,
'contact.custom_value2'
),
'custom_value3' => $this->getString(
$data,
'contact.custom_value3'
),
'custom_value4' => $this->getString(
$data,
'contact.custom_value4'
), ),
'last_name' => $this->getString($data, 'vendor.last_name'),
'email' => $this->getString($data, 'vendor.email'),
'phone' => $this->getString($data, 'vendor.phone'),
], ],
], ],
'country_id' => isset($data['vendor.country_id']) 'country_id' => isset($data['vendor.country_id'])

View File

@ -44,12 +44,12 @@ class RecurringInvoicesCron
nlog('Sending recurring invoices '.$start); nlog('Sending recurring invoices '.$start);
if (! config('ninja.db.multi_db_enabled')) { if (! config('ninja.db.multi_db_enabled')) {
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('is_deleted', false)
->where('remaining_cycles', '!=', '0')
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('is_deleted', false) ->where('next_send_date', '<=', now()->toDateTimeString())
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
$query->where('is_deleted', 0) $query->where('is_deleted', 0)
->where('deleted_at', null); ->where('deleted_at', null);
@ -84,12 +84,12 @@ class RecurringInvoicesCron
foreach (MultiDB::$dbs as $db) { foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db); MultiDB::setDB($db);
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('status_id', RecurringInvoice::STATUS_ACTIVE)
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('is_deleted', false) ->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereNull('deleted_at')
->whereNotNull('next_send_date')
->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
$query->where('is_deleted', 0) $query->where('is_deleted', 0)
->where('deleted_at', null); ->where('deleted_at', null);

View File

@ -74,7 +74,7 @@ class CSVIngest implements ShouldQueue
$engine = $this->bootEngine(); $engine = $this->bootEngine();
foreach (['client', 'product', 'invoice', 'payment', 'vendor', 'expense'] as $entity) { foreach (['client', 'product', 'invoice', 'payment', 'vendor', 'expense', 'quote'] as $entity) {
$engine->import($entity); $engine->import($entity);
} }

View File

@ -354,14 +354,19 @@ class NinjaMailerJob implements ShouldQueue
if(!str_contains($this->nmo->to_user->email, "@")) if(!str_contains($this->nmo->to_user->email, "@"))
return true; return true;
/* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */
if(Ninja::isHosted() && $this->company->account && !$this->company->account->account_sms_verified){
if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class))
return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
return true;
}
/* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */ /* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */
if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class)) if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class))
return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run(); return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
/* On the hosted platform if the user has not verified their account we fail here */
if(Ninja::isHosted() && $this->company->account && !$this->company->account->account_sms_verified)
return true;
return false; return false;
} }

View File

@ -60,11 +60,11 @@ class ReminderJob implements ShouldQueue
{ {
nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s')); nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s'));
Invoice::where('next_send_date', '<=', now()->toDateTimeString()) Invoice::where('is_deleted', 0)
->whereNull('deleted_at')
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->whereNull('deleted_at')
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
$query->where('is_deleted', 0) $query->where('is_deleted', 0)
->where('deleted_at', null); ->where('deleted_at', null);

View File

@ -222,6 +222,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->payment->getLink().'">'.ctrans('texts.view_payment').'</a>', 'label' => ctrans('texts.view_payment')]; $data['$view_link'] = ['value' => '<a class="button" href="'.$this->payment->getLink().'">'.ctrans('texts.view_payment').'</a>', 'label' => ctrans('texts.view_payment')];
$data['$view_button'] = &$data['$view_link']; $data['$view_button'] = &$data['$view_link'];
$data['$viewButton'] = &$data['$view_link'];
$data['$viewLink'] = &$data['$view_link']; $data['$viewLink'] = &$data['$view_link'];
$data['$paymentLink'] = &$data['$view_link']; $data['$paymentLink'] = &$data['$view_link'];
$data['$portalButton'] = ['value' => "<a href='{$this->payment->getPortalLink()}'>".ctrans('texts.login').'</a>', 'label' =>'']; $data['$portalButton'] = ['value' => "<a href='{$this->payment->getPortalLink()}'>".ctrans('texts.login').'</a>', 'label' =>''];
@ -237,6 +238,10 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$invoice.po_number'] = ['value' => $this->formatPoNumber(), 'label' => ctrans('texts.po_number')]; $data['$invoice.po_number'] = ['value' => $this->formatPoNumber(), 'label' => ctrans('texts.po_number')];
$data['$poNumber'] = &$data['$invoice.po_number']; $data['$poNumber'] = &$data['$invoice.po_number'];
$data['$payment.status'] = ['value' => $this->payment->stringStatus($this->payment->status_id), 'label' => ctrans('texts.payment_status')]; $data['$payment.status'] = ['value' => $this->payment->stringStatus($this->payment->status_id), 'label' => ctrans('texts.payment_status')];
$data['$invoices.amount'] = ['value' => $this->formatInvoiceField('amount'), 'label' => ctrans('texts.invoices')];
$data['$invoices.balance'] = ['value' => $this->formatInvoiceField('balance'), 'label' => ctrans('texts.invoices')];
$data['$invoices.due_date'] = ['value' => $this->formatInvoiceField('due_date'), 'label' => ctrans('texts.invoices')];
$data['$invoices.po_number'] = ['value' => $this->formatInvoiceField('po_number'), 'label' => ctrans('texts.invoices')];
$arrKeysLength = array_map('strlen', array_keys($data)); $arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data); array_multisort($arrKeysLength, SORT_DESC, $data);
@ -244,6 +249,22 @@ class PaymentEmailEngine extends BaseEmailEngine
return $data; return $data;
} }
private function formatInvoiceField($field)
{
$invoice = '';
foreach ($this->payment->invoices as $invoice) {
$invoice_field = $invoice->{$field};
$invoice .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}";
}
return $invoice;
}
private function formatInvoice() private function formatInvoice()
{ {
$invoice = ''; $invoice = '';
@ -282,11 +303,15 @@ class PaymentEmailEngine extends BaseEmailEngine
$invoice_list = '<br><br>'; $invoice_list = '<br><br>';
foreach ($this->payment->invoices as $invoice) { foreach ($this->payment->invoices as $invoice) {
$invoice_list .= ctrans('texts.po_number')." {$invoice->po_number} <br>";
if(strlen($invoice->po_number) > 1)
$invoice_list .= ctrans('texts.po_number')." {$invoice->po_number} <br>";
$invoice_list .= ctrans('texts.invoice_number_short')." {$invoice->number} <br>"; $invoice_list .= ctrans('texts.invoice_number_short')." {$invoice->number} <br>";
$invoice_list .= ctrans('texts.invoice_amount').' '.Number::formatMoney($invoice->pivot->amount, $this->client).'<br>'; $invoice_list .= ctrans('texts.invoice_amount').' '.Number::formatMoney($invoice->pivot->amount, $this->client).'<br>';
$invoice_list .= ctrans('texts.invoice_balance').' '.Number::formatMoney($invoice->fresh()->balance, $this->client).'<br>'; $invoice_list .= ctrans('texts.invoice_balance').' '.Number::formatMoney($invoice->fresh()->balance, $this->client).'<br>';
$invoice_list .= '-----<br>'; $invoice_list .= '-----<br>';
} }
return $invoice_list; return $invoice_list;

View File

@ -266,6 +266,11 @@ class Activity extends StaticModel
return $this->belongsTo(Invoice::class)->withTrashed(); return $this->belongsTo(Invoice::class)->withTrashed();
} }
public function vendor()
{
return $this->belongsTo(Vendor::class)->withTrashed();
}
/** /**
* @return mixed * @return mixed
*/ */

View File

@ -131,7 +131,8 @@ class PaymentRepository extends BaseRepository {
/*Ensure payment number generated*/ /*Ensure payment number generated*/
if (! $payment->number || strlen($payment->number) == 0) { if (! $payment->number || strlen($payment->number) == 0) {
$payment->number = $payment->client->getNextPaymentNumber($payment->client, $payment); // $payment->number = $payment->client->getNextPaymentNumber($payment->client, $payment);
$payment->service()->applyNumber();
} }
/*Set local total variables*/ /*Set local total variables*/

View File

@ -62,7 +62,9 @@ class UserRepository extends BaseRepository
// $account->num_users++; // $account->num_users++;
// $account->save(); // $account->save();
// } // }
if(array_key_exists('oauth_provider_id', $details))
unset($details['oauth_provider_id']);
$user->fill($details); $user->fill($details);
//allow users to change only their passwords - not others! //allow users to change only their passwords - not others!

View File

@ -41,20 +41,37 @@ class MarkPaid extends AbstractService
public function run() public function run()
{ {
if ($this->invoice->status_id == Invoice::STATUS_DRAFT) {
$this->invoice->service()->markSent()->save();
}
/*Don't double pay*/ /*Don't double pay*/
if ($this->invoice->status_id == Invoice::STATUS_PAID) { if ($this->invoice->status_id == Invoice::STATUS_PAID) {
return $this->invoice; return $this->invoice;
} }
if ($this->invoice->status_id == Invoice::STATUS_DRAFT) {
$this->invoice->service()->markSent()->save();
}
$payable_balance = $this->invoice->balance;
\DB::connection(config('database.default'))->transaction(function () use($payable_balance) {
$this->invoice = Invoice::where('id', $this->invoice->id)->lockForUpdate()->first();
$this->invoice
->service()
->setExchangeRate()
->updateBalance($payable_balance * -1)
->updatePaidToDate($payable_balance)
->setStatus(Invoice::STATUS_PAID)
->save();
}, 1);
/* Create Payment */ /* Create Payment */
$payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id);
$payment->amount = $this->invoice->balance; $payment->amount = $payable_balance;
$payment->applied = $this->invoice->balance; $payment->applied = $payable_balance;
$payment->status_id = Payment::STATUS_COMPLETED; $payment->status_id = Payment::STATUS_COMPLETED;
$payment->client_id = $this->invoice->client_id; $payment->client_id = $this->invoice->client_id;
$payment->transaction_reference = ctrans('texts.manual_entry'); $payment->transaction_reference = ctrans('texts.manual_entry');
@ -79,20 +96,20 @@ class MarkPaid extends AbstractService
/* Create a payment relationship to the invoice entity */ /* Create a payment relationship to the invoice entity */
$payment->invoices()->attach($this->invoice->id, [ $payment->invoices()->attach($this->invoice->id, [
'amount' => $payment->amount, 'amount' => $payable_balance,
]); ]);
event('eloquent.created: App\Models\Payment', $payment); event('eloquent.created: App\Models\Payment', $payment);
$this->invoice->next_send_date = null; $this->invoice->next_send_date = null;
$this->invoice // $this->invoice
->service() // ->service()
->setExchangeRate() // ->setExchangeRate()
->updateBalance($payment->amount * -1) // ->updateBalance($payment->amount * -1)
->updatePaidToDate($payment->amount) // ->updatePaidToDate($payment->amount)
->setStatus(Invoice::STATUS_PAID) // ->setStatus(Invoice::STATUS_PAID)
->save(); // ->save();
$this->invoice $this->invoice
->service() ->service()
@ -101,7 +118,7 @@ class MarkPaid extends AbstractService
->save(); ->save();
$payment->ledger() $payment->ledger()
->updatePaymentBalance($payment->amount * -1); ->updatePaymentBalance($payable_balance * -1);
\DB::connection(config('database.default'))->transaction(function () use ($payment) { \DB::connection(config('database.default'))->transaction(function () use ($payment) {

View File

@ -42,9 +42,9 @@ class ZeroCostProduct extends AbstractService
$invoice = $this->subscription->service()->createInvoice($this->data); $invoice = $this->subscription->service()->createInvoice($this->data);
$invoice->service() $invoice = $invoice->service()
->markPaid() ->markPaid()
->save(); ->save();
$redirect_url = "/client/invoices/{$invoice->hashed_id}"; $redirect_url = "/client/invoices/{$invoice->hashed_id}";

View File

@ -184,6 +184,9 @@ class SystemHealth
private static function checkPhpCli() private static function checkPhpCli()
{ {
if(!function_exists('exec'))
return "Unable to check CLI version";
try { try {
exec('php -v', $foo, $exitCode); exec('php -v', $foo, $exitCode);

View File

@ -408,6 +408,68 @@ class VendorHtmlEngine
$data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')]; $data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')];
if($this->entity->client()->exists())
{
$data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->entity->client->custom_value1, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')];
$data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->entity->client->custom_value2, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')];
$data['$client3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client3', $this->entity->client->custom_value3, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client3')];
$data['$client4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client4', $this->entity->client->custom_value4, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client4')];
$data['$client.custom1'] = &$data['$client1'];
$data['$client.custom2'] = &$data['$client2'];
$data['$client.custom3'] = &$data['$client3'];
$data['$client.custom4'] = &$data['$client4'];
$data['$client.number'] = ['value' => $this->entity->client->number ?: '&nbsp;', 'label' => ctrans('texts.number')];
$data['$client_name'] = ['value' => $this->entity->client->present()->name() ?: '&nbsp;', 'label' => ctrans('texts.client_name')];
$data['$client.name'] = &$data['$client_name'];
$data['$client'] = &$data['$client_name'];
$data['$client.address1'] = &$data['$address1'];
$data['$client.address2'] = &$data['$address2'];
$data['$client_address'] = ['value' => $this->entity->client->present()->address() ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$client.address'] = &$data['$client_address'];
$data['$client.postal_code'] = ['value' => $this->entity->client->postal_code ?: '&nbsp;', 'label' => ctrans('texts.postal_code')];
$data['$client.public_notes'] = ['value' => $this->entity->client->public_notes ?: '&nbsp;', 'label' => ctrans('texts.notes')];
$data['$client.city'] = ['value' => $this->entity->client->city ?: '&nbsp;', 'label' => ctrans('texts.city')];
$data['$client.state'] = ['value' => $this->entity->client->state ?: '&nbsp;', 'label' => ctrans('texts.state')];
$data['$client.id_number'] = &$data['$id_number'];
$data['$client.vat_number'] = &$data['$vat_number'];
$data['$client.website'] = &$data['$website'];
$data['$client.phone'] = &$data['$phone'];
$data['$city_state_postal'] = ['value' => $this->entity->client->present()->cityStateZip($this->entity->client->city, $this->entity->client->state, $this->entity->client->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => $this->entity->client->present()->cityStateZip($this->entity->client->city, $this->entity->client->state, $this->entity->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$client.country'] = &$data['$country'];
$data['$client.email'] = &$data['$email'];
$data['$client.billing_address'] = &$data['$client_address'];
$data['$client.billing_address1'] = &$data['$client.address1'];
$data['$client.billing_address2'] = &$data['$client.address2'];
$data['$client.billing_city'] = &$data['$client.city'];
$data['$client.billing_state'] = &$data['$client.state'];
$data['$client.billing_postal_code'] = &$data['$client.postal_code'];
$data['$client.billing_country'] = &$data['$client.country'];
$data['$client.shipping_address'] = ['value' => $this->entity->client->present()->shipping_address() ?: '&nbsp;', 'label' => ctrans('texts.shipping_address')];
$data['$client.shipping_address1'] = ['value' => $this->entity->client->shipping_address1 ?: '&nbsp;', 'label' => ctrans('texts.shipping_address1')];
$data['$client.shipping_address2'] = ['value' => $this->entity->client->shipping_address2 ?: '&nbsp;', 'label' => ctrans('texts.shipping_address2')];
$data['$client.shipping_city'] = ['value' => $this->entity->client->shipping_city ?: '&nbsp;', 'label' => ctrans('texts.shipping_city')];
$data['$client.shipping_state'] = ['value' => $this->entity->client->shipping_state ?: '&nbsp;', 'label' => ctrans('texts.shipping_state')];
$data['$client.shipping_postal_code'] = ['value' => $this->entity->client->shipping_postal_code ?: '&nbsp;', 'label' => ctrans('texts.shipping_postal_code')];
$data['$client.shipping_country'] = ['value' => isset($this->entity->client->shipping_country->name) ? ctrans('texts.country_' . $this->entity->client->shipping_country->name) : '', 'label' => ctrans('texts.shipping_country')];
$data['$client.currency'] = ['value' => $this->entity->client->currency()->code, 'label' => ''];
$data['$client.lang_2'] = ['value' => optional($this->entity->client->language())->locale, 'label' => ''];
$data['$client.balance'] = ['value' => Number::formatMoney($this->entity->client->balance, $this->entity->client), 'label' => ctrans('texts.account_balance')];
$data['$client_balance'] = ['value' => Number::formatMoney($this->entity->client->balance, $this->entity->client), 'label' => ctrans('texts.account_balance')];
}
$arrKeysLength = array_map('strlen', array_keys($data)); $arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data); array_multisort($arrKeysLength, SORT_DESC, $data);

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.17', 'app_version' => '5.5.18',
'app_tag' => '5.5.17', 'app_tag' => '5.5.18',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payments', function (Blueprint $table) {
$table->index(['transaction_reference']);
});
Schema::table('paymentables', function (Blueprint $table) {
$table->index(['paymentable_id']);
});
Schema::table('invoices', function (Blueprint $table) {
$table->index(['recurring_id']);
$table->index(['status_id','balance']);
});
Schema::table('quotes', function (Blueprint $table) {
$table->index(['company_id','updated_at']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

@ -0,0 +1,3 @@
<svg width="598" height="450" viewBox="0 0 598 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.05" d="M474.826 149.58C537.96 127.127 603.054 7.0443 592.708 -44.907L-72.3135 -6.27372L-93.116 541.526C-51.621 532.17 19.6183 527.658 70.9927 465.58C155.706 363.219 239.064 427.938 284.962 383.031C330.859 338.124 315.01 295.858 330.529 241.045C346.048 186.232 395.909 177.647 474.826 149.58Z" fill="white" stroke="white" stroke-width="7"/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

View File

@ -0,0 +1,3 @@
<svg width="595" height="450" viewBox="0 0 595 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.2" d="M474.942 153.487C538.076 131.034 603.17 10.9513 592.823 -41L-72.1976 -2.36674L-93 545.433C-51.505 536.077 19.7343 531.565 71.1087 469.487C155.822 367.126 239.18 431.845 285.078 386.938C330.975 342.031 315.126 299.765 330.645 244.952C346.164 190.139 396.025 181.554 474.942 153.487Z" stroke="white"/>
</svg>

After

Width:  |  Height:  |  Size: 430 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 127 KiB

View File

@ -0,0 +1,3 @@
<svg width="598" height="450" viewBox="0 0 598 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.05" d="M474.826 149.58C537.96 127.127 603.054 7.0443 592.708 -44.907L-72.3135 -6.27372L-93.116 541.526C-51.621 532.17 19.6183 527.658 70.9927 465.58C155.706 363.219 239.064 427.938 284.962 383.031C330.859 338.124 315.01 295.858 330.529 241.045C346.048 186.232 395.909 177.647 474.826 149.58Z" fill="white" stroke="white" stroke-width="7"/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"/livewire.js":"/livewire.js?id=c69d0f2801c01fcf8166"} {"/livewire.js":"/livewire.js?id=de3fca26689cb5a39af4"}

View File

@ -125,7 +125,7 @@
<img src="{{ $logo ?? '' }}" alt="" width="155" border="0" align="middle" style="display:block;" /> <img src="{{ $logo ?? '' }}" alt="" width="155" border="0" align="middle" style="display:block;" />
<div style="mso-hide:all;"> <div style="mso-hide:all;">
<![endif]--> <![endif]-->
<img class="logo-light" src="{{ $logo ?? '' }}" alt="" width="400" style="margin-top: 10px; max-width: 570px; display: block; margin-left: auto; margin-right: auto;"/> <img class="logo-light" src="{{ $logo ?? '' }}" alt="" width="400" style="margin-top: 10px; max-width: 200px; display: block; margin-left: auto; margin-right: auto;"/>
<!--[if gte mso 9]> <!--[if gte mso 9]>
</div> </div>
<![endif]--> <![endif]-->

View File

@ -104,14 +104,13 @@
</tr> </tr>
<tr> <tr>
<td align="center" cellpadding="20"> <td align="center" cellpadding="20">
<div <div style="border: 1px solid #c2c2c2; border-bottom: none; padding-bottom: 10px; border-top-left-radius: 3px; border-top-right-radius: 3px;">
style="border: 1px solid #c2c2c2; border-bottom: none; padding-bottom: 10px; border-top-left-radius: 3px; border-top-right-radius: 3px;">
<!--[if gte mso 9]> <!--[if gte mso 9]>
<img src="{{ $logo ?? '' }}" alt="" width="400" border="0" align="middle" style="display:block;" /> <img src="{{ $logo ?? '' }}" alt="" width="400" border="0" align="middle" style="display:block;" />
<div style="mso-hide:all;"> <div style="mso-hide:all;">
<![endif]--> <![endif]-->
<img src="{{ $logo ?? '' }}" alt="" width="400" style="margin-top: 40px; max-width: 155px; display: block; margin-left: auto; margin-right: auto;"/> <img src="{{ $logo ?? '' }}" alt="" width="400" style="margin-top: 40px; max-width: 200px; display: block; margin-left: auto; margin-right: auto;"/>
<!--[if gte mso 9]> <!--[if gte mso 9]>
</div> </div>
<![endif]--> <![endif]-->

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,7 @@ class EntityPaidToDateTest extends TestCase
$this->assertEquals($invoice->balance, 20); $this->assertEquals($invoice->balance, 20);
$invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
$this->assertEquals($invoice->paid_to_date, 20); $this->assertEquals($invoice->paid_to_date, 20);
} }
@ -81,7 +81,7 @@ class EntityPaidToDateTest extends TestCase
{ {
$invoice = $this->bootNewInvoice(); $invoice = $this->bootNewInvoice();
$invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
$this->assertEquals(20, $invoice->paid_to_date); $this->assertEquals(20, $invoice->paid_to_date);

View File

@ -1,24 +1,24 @@
"Invoice Ninja v4.5.17 - December 19, 2020 11:28 pm","","","","","","","","","","","","","","","","","","","","","","","","","" "Invoice Ninja v4.5.17 - December 19, 2020 11:28 pm",,,,,,,,,,,,,,,,,,,,,,,,,
"","","","","","","","","","","","","","","","","","","","","","","","","","" ,,,,,,,,,,,,,,,,,,,,,,,,,
"Vendors","","","","","","","","","","","","","","","","","","","","","","","","","" Vendors,,,,,,,,,,,,,,,,,,,,,,,,,
"Name","Balance","Paid to Date","Billing Street","Billing Apt/Suite","Billing City","Billing State/Province","Billing Postal Code","Billing Country","Shipping Street","Shipping Apt/Suite","Shipping City","Shipping State/Province","Shipping Postal Code","Shipping Country","ID Number","VAT Number","Website","Phone","Currency","Public Notes","Private Notes","First Name","Last Name","Email","Phone" Name,Balance,Paid to Date,Billing Street,Billing Apt/Suite,Billing City,Billing State/Province,Billing Postal Code,Billing Country,Shipping Street,Shipping Apt/Suite,Shipping City,Shipping State/Province,Shipping Postal Code,Shipping Country,ID Number,VAT Number,Website,Phone,Currency,Public Notes,Private Notes,First Name,Last Name,Email,Phone
"Ludwig Krajcik DVM","$-142.85","$205.15","371 O'Connell Summit","Suite 612","Lornamouth","New York","83425-0771","","","","","","","","","","","","","","","Terrill","Ondricka","brook59@example.org","1-537-759-0369" Ludwig Krajcik DVM,$-142.85,$205.15,371 O'Connell Summit,Suite 612,Lornamouth,New York,83425-0771,Australia,,,,,,,,,,,,,,Terrill,Ondricka,brook59@example.org,1-537-759-0369
"Bradly Jaskolski Sr.","$310.81","$313.71","21854 Prosacco Isle","Suite 619","Vicentastad","Colorado","05144","","","","","","","","","","","","","","","Pink","Balistreri","gheidenreich@example.org","1-995-790-2394 x58884" Bradly Jaskolski Sr.,$310.81,$313.71,21854 Prosacco Isle,Suite 619,Vicentastad,Colorado,5144,Germany,,,,,,,,,,,,,,Pink,Balistreri,gheidenreich@example.org,1-995-790-2394 x58884
"Mr. Dustin Stehr I","$285.70","$250.97","2941 Terence Station","Apt. 761","Bernierbury","Massachusetts","47675","","","","","","","","","","","","","","","Shemar","Stehr","labadie.dominique@example.com","624-610-5940" Mr. Dustin Stehr I,$285.70,$250.97,2941 Terence Station,Apt. 761,Bernierbury,Massachusetts,47675,USA,,,,,,,,,,,,,,Shemar,Stehr,labadie.dominique@example.com,624-610-5940
"Dr. Baron Armstrong Sr.","$241.53","$280.42","9469 Ofelia Gateway","Suite 748","Evelynside","New Hampshire","66872","","","","","","","","","","","","","","","Fabiola","Mitchell","nico78@example.net","1-986-772-8058 x00345" Dr. Baron Armstrong Sr.,$241.53,$280.42,9469 Ofelia Gateway,Suite 748,Evelynside,New Hampshire,66872,1,,,,,,,,,,,,,,Fabiola,Mitchell,nico78@example.net,1-986-772-8058 x00345
"Dr. Clemens Douglas MD","$317.94","$342.92","2919 Thompson Common Suite 410","Suite 381","Port Margie","Nevada","49890","","","","","","","","","","","","","","","Lolita","Tremblay","daphney.marquardt@example.com","1-461-699-9192 x9875" Dr. Clemens Douglas MD,$317.94,$342.92,2919 Thompson Common Suite 410,Suite 381,Port Margie,Nevada,49890,,,,,,,,,,,,,,,Lolita,Tremblay,daphney.marquardt@example.com,1-461-699-9192 x9875
"Dr. Claire Huel Sr.","$333.45","$359.51","363 Arlene Causeway Suite 763","Suite 409","Millerstad","Florida","25750","","","","","","","","","","","","","","","Brown","Lakin","vbeer@example.net","(776) 821-0650 x839" Dr. Claire Huel Sr.,$333.45,$359.51,363 Arlene Causeway Suite 763,Suite 409,Millerstad,Florida,25750,,,,,,,,,,,,,,,Brown,Lakin,vbeer@example.net,(776) 821-0650 x839
"Francisca Padberg","$366.58","$203.16","5558 Ratke Flats","Suite 511","Krystelport","Alabama","15359-3783","","","","","","","","","","","","","","","Hallie","Dooley","kgottlieb@example.net","1-573-770-4753 x72129" Francisca Padberg,$366.58,$203.16,5558 Ratke Flats,Suite 511,Krystelport,Alabama,15359-3783,,,,,,,,,,,,,,,Hallie,Dooley,kgottlieb@example.net,1-573-770-4753 x72129
"Dr. Roy Kihn","$272.12","$251.41","20236 O'Hara Shores","Suite 368","Aliciaport","North Carolina","35415","","","","","","","","","","","","","","","Elwyn","Daugherty","wunsch.rozella@example.org","(745) 859-5855 x04216" Dr. Roy Kihn,$272.12,$251.41,20236 O'Hara Shores,Suite 368,Aliciaport,North Carolina,35415,,,,,,,,,,,,,,,Elwyn,Daugherty,wunsch.rozella@example.org,(745) 859-5855 x04216
"Nasir Vandervort","$401.23","$173.17","24599 Hills Centers Suite 467","Apt. 038","North German","Ohio","85363-4720","","","","","","","","","","","","","","","Tre","Moore","wilfrid.kuhic@example.com","1-519-675-7395" Nasir Vandervort,$401.23,$173.17,24599 Hills Centers Suite 467,Apt. 038,North German,Ohio,85363-4720,,,,,,,,,,,,,,,Tre,Moore,wilfrid.kuhic@example.com,1-519-675-7395
"Garry Rosenbaum","$271.82","$306.63","7127 Heidenreich Union Apt. 168","Suite 441","North Murray","North Carolina","29242","","","","","","","","","","","","","","","Ricardo","Johnston","ddubuque@example.com","(682) 216-1962" Garry Rosenbaum,$271.82,$306.63,7127 Heidenreich Union Apt. 168,Suite 441,North Murray,North Carolina,29242,,,,,,,,,,,,,,,Ricardo,Johnston,ddubuque@example.com,(682) 216-1962
"Hildegard Crona PhD","$398.96","$162.49","60142 Janice Islands","Apt. 627","South Stantown","Colorado","10298-5737","","","","","","","","","","","","","","","Miles","Tremblay","sabrina86@example.org","775-210-8656 x93138" Hildegard Crona PhD,$398.96,$162.49,60142 Janice Islands,Apt. 627,South Stantown,Colorado,10298-5737,,,,,,,,,,,,,,,Miles,Tremblay,sabrina86@example.org,775-210-8656 x93138
"Kristopher White I","$318.14","$423.20","7498 Brook Crest Apt. 175","Suite 682","Marianoland","Connecticut","86235-9979","","","","","","","","","","","","","","","Mateo","Welch","jedidiah64@example.com","847.353.7644" Kristopher White I,$318.14,$423.20,7498 Brook Crest Apt. 175,Suite 682,Marianoland,Connecticut,86235-9979,,,,,,,,,,,,,,,Mateo,Welch,jedidiah64@example.com,847.353.7644
"Ethan Grant","$380.71","$367.38","26755 June Extension Suite 589","Suite 706","North Krystelmouth","Delaware","12414","","","","","","","","","","","","","","","Colton","Muller","dorian.mayert@example.net","(267) 647-0537" Ethan Grant,$380.71,$367.38,26755 June Extension Suite 589,Suite 706,North Krystelmouth,Delaware,12414,,,,,,,,,,,,,,,Colton,Muller,dorian.mayert@example.net,(267) 647-0537
"Terry Shields","$230.25","$290.83","60946 Kayden Camp Apt. 046","Apt. 178","Douglashaven","Wyoming","68992","","","","","","","","","","","","","","","Dashawn","Homenick","hills.gina@example.net","(478) 814-9961" Terry Shields,$230.25,$290.83,60946 Kayden Camp Apt. 046,Apt. 178,Douglashaven,Wyoming,68992,,,,,,,,,,,,,,,Dashawn,Homenick,hills.gina@example.net,(478) 814-9961
"Agustina Lockman","$351.60","$264.81","152 Pattie Coves","Suite 971","North Mohamed","Hawaii","52966","","","","","","","","","","","","","","","Bert","Fritsch","greilly@example.org","15342929833" Agustina Lockman,$351.60,$264.81,152 Pattie Coves,Suite 971,North Mohamed,Hawaii,52966,,,,,,,,,,,,,,,Bert,Fritsch,greilly@example.org,15342929833
"Alfonso Schimmel","$343.71","$200.99","358 Hills Coves","Apt. 032","Lake Aisha","District of Columbia","68945-0439","","","","","","","","","","","","","","","Antonio","Hayes","dkshlerin@example.com","1-350-691-4459 x775" Alfonso Schimmel,$343.71,$200.99,358 Hills Coves,Apt. 032,Lake Aisha,District of Columbia,68945-0439,,,,,,,,,,,,,,,Antonio,Hayes,dkshlerin@example.com,1-350-691-4459 x775
"Vergie Monahan","$210.27","$201.10","64004 Anderson Mall Suite 207","Suite 469","Oleshire","California","78369","","","","","","","","","","","","","","","Hilario","Morissette","zjacobs@example.net","552.914.6800 x81120" Vergie Monahan,$210.27,$201.10,64004 Anderson Mall Suite 207,Suite 469,Oleshire,California,78369,,,,,,,,,,,,,,,Hilario,Morissette,zjacobs@example.net,552.914.6800 x81120
"Carol Cremin","$301.53","$473.39","68731 Bartoletti Crescent","Suite 855","Aaronland","Wyoming","07924","","","","","","","","","","","","","","","Nathen","Wehner","jacobi.rosendo@example.com","1-223-910-2060 x09970" Carol Cremin,$301.53,$473.39,68731 Bartoletti Crescent,Suite 855,Aaronland,Wyoming,7924,,,,,,,,,,,,,,,Nathen,Wehner,jacobi.rosendo@example.com,1-223-910-2060 x09970
"Randal Bosco MD","$318.71","$226.47","51884 Peter Falls","Suite 314","Angelicaville","Nebraska","78016-3254","","","","","","","","","","","","","","","Angelo","Ward","kozey.aurelio@example.org","1-329-488-8800" Randal Bosco MD,$318.71,$226.47,51884 Peter Falls,Suite 314,Angelicaville,Nebraska,78016-3254,,,,,,,,,,,,,,,Angelo,Ward,kozey.aurelio@example.org,1-329-488-8800
"Ms. Alena Cassin","$262.45","$388.50","706 Delfina Burgs","Apt. 996","Bertaton","Ohio","22957","","","","","","","","","","","","","","","Eusebio","Reinger","golden.green@example.org","429-551-1362" Ms. Alena Cassin,$262.45,$388.50,706 Delfina Burgs,Apt. 996,Bertaton,Ohio,22957,,,,,,,,,,,,,,,Eusebio,Reinger,golden.green@example.org,429-551-1362

1 Invoice Ninja v4.5.17 - December 19, 2020 11:28 pm
2
3 Vendors
4 Name Balance Paid to Date Billing Street Billing Apt/Suite Billing City Billing State/Province Billing Postal Code Billing Country Shipping Street Shipping Apt/Suite Shipping City Shipping State/Province Shipping Postal Code Shipping Country ID Number VAT Number Website Phone Currency Public Notes Private Notes First Name Last Name Email Phone
5 Ludwig Krajcik DVM $-142.85 $205.15 371 O'Connell Summit Suite 612 Lornamouth New York 83425-0771 Australia Terrill Ondricka brook59@example.org 1-537-759-0369
6 Bradly Jaskolski Sr. $310.81 $313.71 21854 Prosacco Isle Suite 619 Vicentastad Colorado 05144 5144 Germany Pink Balistreri gheidenreich@example.org 1-995-790-2394 x58884
7 Mr. Dustin Stehr I $285.70 $250.97 2941 Terence Station Apt. 761 Bernierbury Massachusetts 47675 USA Shemar Stehr labadie.dominique@example.com 624-610-5940
8 Dr. Baron Armstrong Sr. $241.53 $280.42 9469 Ofelia Gateway Suite 748 Evelynside New Hampshire 66872 1 Fabiola Mitchell nico78@example.net 1-986-772-8058 x00345
9 Dr. Clemens Douglas MD $317.94 $342.92 2919 Thompson Common Suite 410 Suite 381 Port Margie Nevada 49890 Lolita Tremblay daphney.marquardt@example.com 1-461-699-9192 x9875
10 Dr. Claire Huel Sr. $333.45 $359.51 363 Arlene Causeway Suite 763 Suite 409 Millerstad Florida 25750 Brown Lakin vbeer@example.net (776) 821-0650 x839
11 Francisca Padberg $366.58 $203.16 5558 Ratke Flats Suite 511 Krystelport Alabama 15359-3783 Hallie Dooley kgottlieb@example.net 1-573-770-4753 x72129
12 Dr. Roy Kihn $272.12 $251.41 20236 O'Hara Shores Suite 368 Aliciaport North Carolina 35415 Elwyn Daugherty wunsch.rozella@example.org (745) 859-5855 x04216
13 Nasir Vandervort $401.23 $173.17 24599 Hills Centers Suite 467 Apt. 038 North German Ohio 85363-4720 Tre Moore wilfrid.kuhic@example.com 1-519-675-7395
14 Garry Rosenbaum $271.82 $306.63 7127 Heidenreich Union Apt. 168 Suite 441 North Murray North Carolina 29242 Ricardo Johnston ddubuque@example.com (682) 216-1962
15 Hildegard Crona PhD $398.96 $162.49 60142 Janice Islands Apt. 627 South Stantown Colorado 10298-5737 Miles Tremblay sabrina86@example.org 775-210-8656 x93138
16 Kristopher White I $318.14 $423.20 7498 Brook Crest Apt. 175 Suite 682 Marianoland Connecticut 86235-9979 Mateo Welch jedidiah64@example.com 847.353.7644
17 Ethan Grant $380.71 $367.38 26755 June Extension Suite 589 Suite 706 North Krystelmouth Delaware 12414 Colton Muller dorian.mayert@example.net (267) 647-0537
18 Terry Shields $230.25 $290.83 60946 Kayden Camp Apt. 046 Apt. 178 Douglashaven Wyoming 68992 Dashawn Homenick hills.gina@example.net (478) 814-9961
19 Agustina Lockman $351.60 $264.81 152 Pattie Coves Suite 971 North Mohamed Hawaii 52966 Bert Fritsch greilly@example.org 15342929833
20 Alfonso Schimmel $343.71 $200.99 358 Hills Coves Apt. 032 Lake Aisha District of Columbia 68945-0439 Antonio Hayes dkshlerin@example.com 1-350-691-4459 x775
21 Vergie Monahan $210.27 $201.10 64004 Anderson Mall Suite 207 Suite 469 Oleshire California 78369 Hilario Morissette zjacobs@example.net 552.914.6800 x81120
22 Carol Cremin $301.53 $473.39 68731 Bartoletti Crescent Suite 855 Aaronland Wyoming 07924 7924 Nathen Wehner jacobi.rosendo@example.com 1-223-910-2060 x09970
23 Randal Bosco MD $318.71 $226.47 51884 Peter Falls Suite 314 Angelicaville Nebraska 78016-3254 Angelo Ward kozey.aurelio@example.org 1-329-488-8800
24 Ms. Alena Cassin $262.45 $388.50 706 Delfina Burgs Apt. 996 Bertaton Ohio 22957 Eusebio Reinger golden.green@example.org 429-551-1362

View File

@ -185,7 +185,7 @@ class InvoiceAmountPaymentTest extends TestCase
$this->assertEquals(25, $invoice->balance); $this->assertEquals(25, $invoice->balance);
$this->assertEquals(25, $invoice->amount); $this->assertEquals(25, $invoice->amount);
$invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
$invoice->fresh(); $invoice->fresh();

View File

@ -1422,7 +1422,7 @@ class PaymentTest extends TestCase
$this->assertEquals(10, $this->invoice->balance); $this->assertEquals(10, $this->invoice->balance);
$this->assertEquals(10, $this->invoice->client->fresh()->balance); $this->assertEquals(10, $this->invoice->client->fresh()->balance);
$this->invoice->service()->markPaid()->save(); $this->invoice = $this->invoice->service()->markPaid()->save();
$this->assertEquals(0, $this->invoice->balance); $this->assertEquals(0, $this->invoice->balance);
$this->assertEquals(0, $this->invoice->client->balance); $this->assertEquals(0, $this->invoice->client->balance);

View File

@ -40,7 +40,7 @@ class GoogleAnalyticsTest extends TestCase
$invoice = $this->invoice; $invoice = $this->invoice;
$client = $this->client; $client = $this->client;
$invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
$payment = $invoice->payments->first(); $payment = $invoice->payments->first();

View File

@ -45,7 +45,7 @@ class InvoiceActionsTest extends TestCase
{ {
$this->withoutEvents(); $this->withoutEvents();
$this->invoice->service()->markPaid()->save(); $this->invoice = $this->invoice->service()->markPaid()->save();
$this->assertFalse($this->invoiceDeletable($this->invoice)); $this->assertFalse($this->invoiceDeletable($this->invoice));
$this->assertTrue($this->invoiceReversable($this->invoice)); $this->assertTrue($this->invoiceReversable($this->invoice));

View File

@ -82,7 +82,7 @@ class SubscriptionsCalcTest extends TestCase
$this->assertFalse($sub_calculator->isPaidUp()); $this->assertFalse($sub_calculator->isPaidUp());
$invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
$this->assertTrue($sub_calculator->isPaidUp()); $this->assertTrue($sub_calculator->isPaidUp());