Merge branch 'v5-develop' into preview

This commit is contained in:
David Bomba 2022-08-28 15:04:10 +10:00
commit f0b518d156
60 changed files with 79534 additions and 78911 deletions

View File

@ -1 +1 @@
5.5.14 5.5.17

View File

@ -114,6 +114,7 @@ class CheckData extends Command
$this->checkEntityInvitations(); $this->checkEntityInvitations();
$this->checkCompanyData(); $this->checkCompanyData();
$this->checkBalanceVsPaidStatus(); $this->checkBalanceVsPaidStatus();
$this->checkDuplicateRecurringInvoices();
if(Ninja::isHosted()) if(Ninja::isHosted())
$this->checkAccountStatuses(); $this->checkAccountStatuses();
@ -145,6 +146,23 @@ class CheckData extends Command
$this->log .= $str."\n"; $this->log .= $str."\n";
} }
private function checkDuplicateRecurringInvoices()
{
if(Ninja::isHosted())
{
$c = Client::on('db-ninja-01')->where('company_id', config('ninja.ninja_default_company_id'))
->with('recurring_invoices')
->cursor()
->each(function ($client){
if($client->recurring_invoices()->where('is_deleted', 0)->where('deleted_at', null)->count() > 1)
$this->logMessage("Duplicate Recurring Invoice => {$client->custom_value1}");
});
}
}
private function checkOAuth() private function checkOAuth()
{ {
// check for duplicate oauth ids // check for duplicate oauth ids

View File

@ -232,7 +232,12 @@ class BaseController extends Controller
$query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents'); $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents');
if (! $user->hasPermission('view_client')) { if (! $user->hasPermission('view_client')) {
// $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
});
} }
}, },
'company.company_gateways' => function ($query) use ($user) { 'company.company_gateways' => function ($query) use ($user) {
@ -246,7 +251,11 @@ class BaseController extends Controller
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_credit')) { if (! $user->hasPermission('view_credit')) {
// $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id);
});
} }
}, },
'company.designs'=> function ($query) use ($updated_at, $user) { 'company.designs'=> function ($query) use ($updated_at, $user) {
@ -263,7 +272,11 @@ class BaseController extends Controller
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_expense')) { if (! $user->hasPermission('view_expense')) {
// $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
});
} }
}, },
'company.groups' => function ($query) use ($updated_at, $user) { 'company.groups' => function ($query) use ($updated_at, $user) {
@ -276,14 +289,25 @@ class BaseController extends Controller
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_invoice')) { if (! $user->hasPermission('view_invoice')) {
// $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id);
});
} }
}, },
'company.payments'=> function ($query) use ($updated_at, $user) { 'company.payments'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents'); $query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents');
if (! $user->hasPermission('view_payment')) { if (! $user->hasPermission('view_payment')) {
// $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id);
});
} }
}, },
'company.payment_terms'=> function ($query) use ($updated_at, $user) { 'company.payment_terms'=> function ($query) use ($updated_at, $user) {
@ -297,49 +321,88 @@ class BaseController extends Controller
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_product')) { if (! $user->hasPermission('view_product')) {
// $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id);
});
} }
}, },
'company.projects'=> function ($query) use ($updated_at, $user) { 'company.projects'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_project')) { if (! $user->hasPermission('view_project')) {
// $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
});
} }
}, },
'company.purchase_orders'=> function ($query) use ($updated_at, $user) { 'company.purchase_orders'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_purchase_order')) { if (! $user->hasPermission('view_purchase_order')) {
// $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
});
} }
}, },
'company.quotes'=> function ($query) use ($updated_at, $user) { 'company.quotes'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_quote')) { if (! $user->hasPermission('view_quote')) {
// $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id);
});
} }
}, },
'company.recurring_invoices'=> function ($query) use ($updated_at, $user) { 'company.recurring_invoices'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company'); $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company');
if (! $user->hasPermission('view_recurring_invoice')) { if (! $user->hasPermission('view_recurring_invoice')) {
// $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
});
} }
}, },
'company.recurring_expenses'=> function ($query) use ($updated_at, $user) { 'company.recurring_expenses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_recurring_expense')) { if (! $user->hasPermission('view_recurring_expense')) {
// $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
});
} }
}, },
'company.tasks'=> function ($query) use ($updated_at, $user) { 'company.tasks'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('documents');
if (! $user->hasPermission('view_task')) { if (! $user->hasPermission('view_task')) {
// $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id);
});
} }
}, },
'company.tax_rates'=> function ($query) use ($updated_at, $user) { 'company.tax_rates'=> function ($query) use ($updated_at, $user) {
@ -349,7 +412,12 @@ class BaseController extends Controller
$query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); $query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents');
if (! $user->hasPermission('view_vendor')) { if (! $user->hasPermission('view_vendor')) {
// $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id);
});
} }
}, },
'company.expense_categories'=> function ($query) use ($updated_at, $user) { 'company.expense_categories'=> function ($query) use ($updated_at, $user) {
@ -480,7 +548,12 @@ class BaseController extends Controller
$query->where('clients.created_at', '>=', $created_at)->with('contacts.company', 'gateway_tokens', 'documents'); $query->where('clients.created_at', '>=', $created_at)->with('contacts.company', 'gateway_tokens', 'documents');
if (! $user->hasPermission('view_client')) { if (! $user->hasPermission('view_client')) {
// $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
});
} }
}, },
'company.company_gateways' => function ($query) use ($user) { 'company.company_gateways' => function ($query) use ($user) {
@ -494,7 +567,11 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); $query->where('created_at', '>=', $created_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_credit')) { if (! $user->hasPermission('view_credit')) {
// $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id);
});
} }
}, },
'company.documents'=> function ($query) use ($created_at, $user) { 'company.documents'=> function ($query) use ($created_at, $user) {
@ -504,7 +581,13 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_expense')) { if (! $user->hasPermission('view_expense')) {
// $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
});
} }
}, },
'company.groups' => function ($query) use ($created_at, $user) { 'company.groups' => function ($query) use ($created_at, $user) {
@ -514,14 +597,24 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); $query->where('created_at', '>=', $created_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_invoice')) { if (! $user->hasPermission('view_invoice')) {
// $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id);
});
} }
}, },
'company.payments'=> function ($query) use ($created_at, $user) { 'company.payments'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('paymentables', 'documents'); $query->where('created_at', '>=', $created_at)->with('paymentables', 'documents');
if (! $user->hasPermission('view_payment')) { if (! $user->hasPermission('view_payment')) {
// $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id);
});
} }
}, },
'company.payment_terms'=> function ($query) use ($created_at, $user) { 'company.payment_terms'=> function ($query) use ($created_at, $user) {
@ -531,42 +624,67 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_product')) { if (! $user->hasPermission('view_product')) {
// $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id);
});
} }
}, },
'company.projects'=> function ($query) use ($created_at, $user) { 'company.projects'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_project')) { if (! $user->hasPermission('view_project')) {
// $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
});
} }
}, },
'company.purchase_orders'=> function ($query) use ($created_at, $user) { 'company.purchase_orders'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_purchase_order')) { if (! $user->hasPermission('view_purchase_order')) {
// $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
});
} }
}, },
'company.quotes'=> function ($query) use ($created_at, $user) { 'company.quotes'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); $query->where('created_at', '>=', $created_at)->with('invitations', 'documents');
if (! $user->hasPermission('view_quote')) { if (! $user->hasPermission('view_quote')) {
// $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id);
});
} }
}, },
'company.recurring_invoices'=> function ($query) use ($created_at, $user) { 'company.recurring_invoices'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company'); $query->where('created_at', '>=', $created_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company');
if (! $user->hasPermission('view_recurring_invoice')) { if (! $user->hasPermission('view_recurring_invoice')) {
// $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
});
} }
}, },
'company.tasks'=> function ($query) use ($created_at, $user) { 'company.tasks'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_task')) { if (! $user->hasPermission('view_task')) {
// $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id);
});
} }
}, },
'company.tax_rates' => function ($query) use ($created_at, $user) { 'company.tax_rates' => function ($query) use ($created_at, $user) {
@ -576,7 +694,12 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('contacts', 'documents'); $query->where('created_at', '>=', $created_at)->with('contacts', 'documents');
if (! $user->hasPermission('view_vendor')) { if (! $user->hasPermission('view_vendor')) {
// $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id);
});
} }
}, },
'company.expense_categories'=> function ($query) use ($created_at, $user) { 'company.expense_categories'=> function ($query) use ($created_at, $user) {
@ -610,7 +733,12 @@ class BaseController extends Controller
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('documents');
if (! $user->hasPermission('view_recurring_expense')) { if (! $user->hasPermission('view_recurring_expense')) {
// $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
$query->whereNested(function($query) use ($user) {
$query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
});
} }
}, },
] ]

View File

@ -35,6 +35,7 @@ class CreditController extends Controller
$data = [ $data = [
'credit' => $credit, 'credit' => $credit,
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'invitation' => $invitation
]; ];
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) { if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {

View File

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

View File

@ -98,6 +98,27 @@ class NinjaPlanController extends Controller
$stripe_response = json_decode($request->input('gateway_response')); $stripe_response = json_decode($request->input('gateway_response'));
$customer = $gateway_driver->findOrCreateCustomer(); $customer = $gateway_driver->findOrCreateCustomer();
//27-08-2022 Ensure customer is updated appropriately
$update_client_object['name'] = $client->present()->name();
$update_client_object['phone'] = substr($client->present()->phone(), 0, 20);
$update_client_object['address']['line1'] = $client->address1 ?: '';
$update_client_object['address']['line2'] = $client->address2 ?: '';
$update_client_object['address']['city'] = $client->city ?: '';
$update_client_object['address']['postal_code'] = $client->postal_code ?: '';
$update_client_object['address']['state'] = $client->state ?: '';
$update_client_object['address']['country'] = $client->country ? $client->country->iso_3166_2 : '';
$update_client_object['shipping']['name'] = $client->present()->name();
$update_client_object['shipping']['address']['line1'] = $client->shipping_address1 ?: '';
$update_client_object['shipping']['address']['line2'] = $client->shipping_address2 ?: '';
$update_client_object['shipping']['address']['city'] = $client->shipping_city ?: '';
$update_client_object['shipping']['address']['postal_code'] = $client->shipping_postal_code ?: '';
$update_client_object['shipping']['address']['state'] = $client->shipping_state ?: '';
$update_client_object['shipping']['address']['country'] = $client->shipping_country ? $client->shipping_country->iso_3166_2 : '';
\Stripe\Customer::update($customer->id, $update_client_object, $gateway_driver->stripe_connect_auth);
$gateway_driver->attach($stripe_response->payment_method, $customer); $gateway_driver->attach($stripe_response->payment_method, $customer);
$method = $gateway_driver->getStripePaymentMethod($stripe_response->payment_method); $method = $gateway_driver->getStripePaymentMethod($stripe_response->payment_method);

View File

@ -61,6 +61,7 @@ class QuoteController extends Controller
$data = [ $data = [
'quote' => $quote, 'quote' => $quote,
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'invitation' => $invitation
]; ];
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) { if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
@ -180,7 +181,6 @@ class QuoteController extends Controller
if ($process) { if ($process) {
foreach ($quotes as $quote) { foreach ($quotes as $quote) {
$quote->service()->approve(auth()->user())->save(); $quote->service()->approve(auth()->user())->save();
// event(new QuoteWasApproved(auth()->guard('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
if (request()->has('signature') && ! is_null(request()->signature) && ! empty(request()->signature)) { if (request()->has('signature') && ! is_null(request()->signature) && ! empty(request()->signature)) {
InjectSignature::dispatch($quote, request()->signature); InjectSignature::dispatch($quote, request()->signature);

View File

@ -99,6 +99,7 @@ class PurchaseOrderController extends Controller
'settings' => $purchase_order->company->settings, 'settings' => $purchase_order->company->settings,
'sidebar' => $this->sidebarMenu(), 'sidebar' => $this->sidebarMenu(),
'company' => $purchase_order->company, 'company' => $purchase_order->company,
'invitation' => $invitation
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {

View File

@ -28,17 +28,17 @@ class QuotesTable extends Component
public $company; public $company;
public $sort_field = 'status_id'; // Default sortBy. Feel free to change or pull from client/company settings. public $sort = 'status_id'; // Default sortBy. Feel free to change or pull from client/company settings.
public $sort_asc = true; public $sort_asc = true;
public function sortBy($field) public function sortBy($field)
{ {
$this->sort_field === $field $this->sort === $field
? $this->sort_asc = ! $this->sort_asc ? $this->sort_asc = ! $this->sort_asc
: $this->sort_asc = true; : $this->sort_asc = true;
$this->sort_field = $field; $this->sort = $field;
} }
public function mount() public function mount()
@ -51,7 +51,7 @@ class QuotesTable extends Component
$query = Quote::query() $query = Quote::query()
->with('client.gateway_tokens', 'company', 'client.contacts') ->with('client.gateway_tokens', 'company', 'client.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc'); ->orderBy($this->sort, $this->sort_asc ? 'asc' : 'desc');
if (count($this->status) > 0) { if (count($this->status) > 0) {

View File

@ -69,7 +69,7 @@ class StoreExpenseRequest extends Request
/* Ensure the project is related */ /* Ensure the project is related */
if (array_key_exists('project_id', $input) && isset($input['project_id'])) { if (array_key_exists('project_id', $input) && isset($input['project_id'])) {
$project = Project::withTrashed()->find($input['project_id'])->company()->first(); $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first();
if($project){ if($project){
$input['client_id'] = $project->client_id; $input['client_id'] = $project->client_id;

View File

@ -64,7 +64,7 @@ class UpdateExpenseRequest extends Request
/* Ensure the project is related */ /* Ensure the project is related */
if (array_key_exists('project_id', $input) && isset($input['project_id'])) { if (array_key_exists('project_id', $input) && isset($input['project_id'])) {
$project = Project::withTrashed()->find($input['project_id'])->company()->first(); $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first();
if($project){ if($project){
$input['client_id'] = $project->client_id; $input['client_id'] = $project->client_id;

View File

@ -68,15 +68,14 @@ class StorePaymentRequest extends Request
if (isset($input['credits']) && is_array($input['credits']) !== false) { if (isset($input['credits']) && is_array($input['credits']) !== false) {
foreach ($input['credits'] as $key => $value) { foreach ($input['credits'] as $key => $value) {
if (array_key_exists('credit_id', $input['credits'][$key])) { if (array_key_exists('credit_id', $input['credits'][$key])) {
$input['credits'][$key]['credit_id'] = $value['credit_id']; // $input['credits'][$key]['credit_id'] = $value['credit_id'];
$input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
$credits_total += $value['amount']; $credits_total += $value['amount'];
} }
} }
} }
// if (array_key_exists('amount', $input))
// $input['amount'] = 0;
if (isset($input['credits']) && is_array($input['credits']) === false) { if (isset($input['credits']) && is_array($input['credits']) === false) {
$input['credits'] = null; $input['credits'] = null;
} }
@ -97,14 +96,14 @@ class StorePaymentRequest extends Request
public function rules() public function rules()
{ {
$rules = [ $rules = [
'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()], 'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule($this->all())],
'client_id' => 'bail|required|exists:clients,id', 'client_id' => 'bail|required|exists:clients,id',
'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id', 'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id',
'invoices.*.amount' => 'bail|required', 'invoices.*.amount' => 'bail|required',
'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()), 'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()),
'credits.*.credit_id' => 'bail|required|exists:credits,id', 'credits.*.credit_id' => 'bail|required|exists:credits,id',
'credits.*.credit_id' => new ValidCreditsRules($this->all()), 'credits.*.credit_id' => new ValidCreditsRules($this->all()),
'credits.*.amount' => ['required', new CreditsSumRule($this->all())], 'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())],
'invoices' => new ValidPayableInvoicesRule(), 'invoices' => new ValidPayableInvoicesRule(),
'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)], 'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)],

View File

@ -36,7 +36,7 @@ class UpdatePaymentRequest extends Request
public function rules() public function rules()
{ {
$rules = [ $rules = [
'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule], 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule($this->all())],
'invoices.*.invoice_id' => 'distinct', 'invoices.*.invoice_id' => 'distinct',
'documents' => 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', 'documents' => 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
]; ];
@ -79,6 +79,14 @@ class UpdatePaymentRequest extends Request
} }
} }
} }
if (isset($input['credits']) && is_array($input['credits']) !== false) {
foreach ($input['credits'] as $key => $value) {
if (array_key_exists('credits', $input['credits'][$key])) {
$input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
}
}
}
$this->replace($input); $this->replace($input);
} }

View File

@ -70,8 +70,8 @@ class StoreTaskRequest extends Request
/* Ensure the project is related */ /* Ensure the project is related */
if (array_key_exists('project_id', $input) && isset($input['project_id'])) { if (array_key_exists('project_id', $input) && isset($input['project_id'])) {
$project = Project::withTrashed()->find($input['project_id'])->company()->first(); $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first();
;
if($project){ if($project){
$input['client_id'] = $project->client_id; $input['client_id'] = $project->client_id;
} }

View File

@ -69,7 +69,7 @@ class UpdateTaskRequest extends Request
/* Ensure the project is related */ /* Ensure the project is related */
if (array_key_exists('project_id', $input) && isset($input['project_id'])) { if (array_key_exists('project_id', $input) && isset($input['project_id'])) {
$project = Project::withTrashed()->find($input['project_id'])->company()->first(); $project = Project::withTrashed()->where('id', $input['project_id'])->company()->first();
if($project){ if($project){
$input['client_id'] = $project->client_id; $input['client_id'] = $project->client_id;

View File

@ -51,10 +51,22 @@ class ValidCreditsRules implements Rule
$unique_array = []; $unique_array = [];
$total_credit_amount = array_sum(array_column($this->input['credits'], 'amount'));
if($total_credit_amount <= 0){
$this->error_msg = "Total of credits must be more than zero.";
return false;
}
$cred_collection = Credit::withTrashed()->whereIn('id', array_column($this->input['credits'], 'credit_id'))->get();
foreach ($this->input['credits'] as $credit) { foreach ($this->input['credits'] as $credit) {
$unique_array[] = $credit['credit_id']; $unique_array[] = $credit['credit_id'];
$cred = Credit::find($this->decodePrimaryKey($credit['credit_id'])); // $cred = Credit::find($this->decodePrimaryKey($credit['credit_id']));
$cred = $cred_collection->firstWhere('id', $credit['credit_id']);
if (! $cred) { if (! $cred) {
$this->error_msg = ctrans('texts.credit_not_found'); $this->error_msg = ctrans('texts.credit_not_found');

View File

@ -51,6 +51,9 @@ class ValidInvoicesRules implements Rule
$unique_array = []; $unique_array = [];
/////
$inv_collection = Invoice::withTrashed()->whereIn('id', array_column($this->input['invoices'], 'invoice_id'))->get();
//todo optimize this into a single query //todo optimize this into a single query
foreach ($this->input['invoices'] as $invoice) { foreach ($this->input['invoices'] as $invoice) {
$unique_array[] = $invoice['invoice_id']; $unique_array[] = $invoice['invoice_id'];
@ -61,7 +64,10 @@ class ValidInvoicesRules implements Rule
return false; return false;
} }
$inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first(); /////
$inv = $inv_collection->firstWhere('id', $invoice['invoice_id']);
// $inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first();
if (! $inv) { if (! $inv) {
$this->error_msg = ctrans('texts.invoice_not_found'); $this->error_msg = ctrans('texts.invoice_not_found');

View File

@ -22,6 +22,13 @@ class ValidCreditsPresentRule implements Rule
{ {
use MakesHash; use MakesHash;
private $input;
public function __construct($input)
{
$this->input = $input;
}
/** /**
* @param string $attribute * @param string $attribute
* @param mixed $value * @param mixed $value
@ -44,11 +51,10 @@ class ValidCreditsPresentRule implements Rule
{ {
//todo need to ensure the clients credits are here not random ones! //todo need to ensure the clients credits are here not random ones!
if (request()->input('credits') && is_array(request()->input('credits')) && count(request()->input('credits')) > 0) { if (array_key_exists('credits', $this->input) && is_array($this->input['credits']) && count($this->input['credits']) > 0) {
$credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id'))) $credit_collection = Credit::whereIn('id', array_column($this->input['credits'], 'credit_id'))->count();
->count();
return $credit_collection == count(request()->input('credits')); return $credit_collection == count($this->input['credits']);
} }
return true; return true;

View File

@ -85,6 +85,11 @@ class CreateAccount
$sp794f3f->hosted_client_count = config('ninja.quotas.free.clients'); $sp794f3f->hosted_client_count = config('ninja.quotas.free.clients');
$sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies'); $sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies');
$sp794f3f->account_sms_verified = true; $sp794f3f->account_sms_verified = true;
if(in_array($this->getDomain($this->request['email']), ['gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com'])){
$sp794f3f->account_sms_verified = false;
}
// $sp794f3f->trial_started = now(); // $sp794f3f->trial_started = now();
// $sp794f3f->trial_plan = 'pro'; // $sp794f3f->trial_plan = 'pro';
} }
@ -155,4 +160,19 @@ class CreateAccount
return $sp794f3f; return $sp794f3f;
} }
private function getDomain($email)
{
if( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
// split on @ and return last value of array (the domain)
$domain = explode('@', $email);
$domain_name = end($domain);
return $domain_name;
}
return 'gmail.com';
}
} }

View File

@ -85,6 +85,13 @@ class ApplyCreditPayment implements ShouldQueue
->save(); ->save();
} }
//22-08-2022
$this->credit
->client
->service()
->adjustCreditBalance($this->amount * -1)
->save();
/* Update Payment Applied Amount*/ /* Update Payment Applied Amount*/
$this->payment->save(); $this->payment->save();
} }

View File

@ -12,6 +12,7 @@
namespace App\Jobs\Ninja; namespace App\Jobs\Ninja;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -54,6 +55,8 @@ class CompanySizeCheck implements ShouldQueue
private function check() private function check()
{ {
nlog("Checking all company sizes");
Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) { Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) {
if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) { if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) {
nlog("Marking company {$company->id} as large"); nlog("Marking company {$company->id} as large");
@ -61,5 +64,17 @@ class CompanySizeCheck implements ShouldQueue
$company->account->companies()->update(['is_large' => true]); $company->account->companies()->update(['is_large' => true]);
} }
}); });
nlog("updating all client credit balances");
Client::where('updated_at', '>', now()->subDay())
->cursor()
->each(function ($client){
$client->credit_balance = $client->service()->getCreditBalance();
$client->save();
});
} }
} }

View File

@ -42,13 +42,16 @@ class MailSentListener implements ShouldQueue
*/ */
public function handle(MessageSent $event) public function handle(MessageSent $event)
{ {
if(!Ninja::isHosted()); if(!Ninja::isHosted())
return; return;
$message_id = $event->sent->getMessageId(); $message_id = $event->sent->getMessageId();
$message = MessageConverter::toEmail($event->sent->getOriginalMessage()); $message = MessageConverter::toEmail($event->sent->getOriginalMessage());
if(!$message->getHeaders()->get('x-invitation'))
return;
$invitation_key = $message->getHeaders()->get('x-invitation')->getValue(); $invitation_key = $message->getHeaders()->get('x-invitation')->getValue();
if($message_id && $invitation_key) if($message_id && $invitation_key)

View File

@ -120,6 +120,7 @@ class Company extends BaseModel
'inventory_notification_threshold', 'inventory_notification_threshold',
'stock_notification', 'stock_notification',
'enabled_expense_tax_rates', 'enabled_expense_tax_rates',
'invoice_task_project',
]; ];
protected $hidden = [ protected $hidden = [

View File

@ -24,9 +24,6 @@ class UserObserver
public function created(User $user) public function created(User $user)
{ {
if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class))
\Modules\Admin\Jobs\Account\UserQuality::dispatch($user, $user->account->key);
} }
/** /**
@ -38,9 +35,6 @@ class UserObserver
public function updated(User $user) public function updated(User $user)
{ {
if(class_exists(\Modules\Admin\Jobs\Account\UserQuality::class))
\Modules\Admin\Jobs\Account\UserQuality::dispatch($user, $user->account->key);
} }
/** /**

View File

@ -339,6 +339,11 @@ class BaseRepository
else else
event('eloquent.updated: App\Models\Credit', $model); event('eloquent.updated: App\Models\Credit', $model);
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Credit::STATUS_DRAFT)) {
$model->client->service()->adjustCreditBalance(($state['finished_amount'] - $state['starting_amount']))->save();
}
} }
if ($model instanceof Quote) { if ($model instanceof Quote) {

View File

@ -43,4 +43,31 @@ class CreditRepository extends BaseRepository
{ {
return CreditInvitation::where('key', $key)->first(); return CreditInvitation::where('key', $key)->first();
} }
public function delete($credit)
{
if ($credit->is_deleted) {
return;
}
$credit = $credit->service()->deleteCredit()->save();
return parent::delete($credit);
}
public function restore($credit)
{
//we cannot restore a deleted payment.
if (! $credit->trashed()) {
return;
}
parent::restore($credit);
$credit = $credit->service()->restoreCredit()->save();
return $credit;
}
} }

View File

@ -73,7 +73,10 @@ class PaymentRepository extends BaseRepository {
unset($data['exchange_rate']); unset($data['exchange_rate']);
$is_existing_payment = false; $is_existing_payment = false;
$client = Client::where('id', $data['client_id'])->withTrashed()->first();
\DB::connection(config('database.default'))->transaction(function () use ($data) {
$client = Client::where('id', $data['client_id'])->withTrashed()->lockForUpdate()->first();
/*We only update the paid to date ONCE per payment*/ /*We only update the paid to date ONCE per payment*/
if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) { if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) {
@ -81,21 +84,28 @@ class PaymentRepository extends BaseRepository {
$data['amount'] = array_sum(array_column($data['invoices'], 'amount')); $data['amount'] = array_sum(array_column($data['invoices'], 'amount'));
} }
$client->service()->updatePaidToDate($data['amount'])->save(); // $client->service()->updatePaidToDate($data['amount'])->save();
$client->paid_to_date += $data['amount'];
$client->save();
} }
else{ else{
//this fixes an edge case with unapplied payments //this fixes an edge case with unapplied payments
$client->service()->updatePaidToDate($data['amount'])->save(); // $client->service()->updatePaidToDate($data['amount'])->save();
$client->paid_to_date += $data['amount'];
$client->save();
} }
if (array_key_exists('credits', $data) && is_array($data['credits']) && count($data['credits']) > 0) { if (array_key_exists('credits', $data) && is_array($data['credits']) && count($data['credits']) > 0) {
$_credit_totals = array_sum(array_column($data['credits'], 'amount')); $_credit_totals = array_sum(array_column($data['credits'], 'amount'));
$client->service()->updatePaidToDate($_credit_totals)->save(); // $client->service()->updatePaidToDate($_credit_totals)->save();
$client->paid_to_date += $_credit_totals;
$client->save();
} }
}, 1);
} }
/*Fill the payment*/ /*Fill the payment*/
@ -139,7 +149,8 @@ class PaymentRepository extends BaseRepository {
//todo optimize this into a single query //todo optimize this into a single query
foreach ($data['invoices'] as $paid_invoice) { foreach ($data['invoices'] as $paid_invoice) {
$invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first(); // $invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first();
$invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']);
if ($invoice) { if ($invoice) {
$invoice = $invoice->service() $invoice = $invoice->service()
@ -157,16 +168,20 @@ class PaymentRepository extends BaseRepository {
if (array_key_exists('credits', $data) && is_array($data['credits'])) { if (array_key_exists('credits', $data) && is_array($data['credits'])) {
$credit_totals = array_sum(array_column($data['credits'], 'amount')); $credit_totals = array_sum(array_column($data['credits'], 'amount'));
$credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get(); // $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get();
$credits = Credit::whereIn('id', array_column($data['credits'], 'credit_id'))->get();
$payment->credits()->saveMany($credits); $payment->credits()->saveMany($credits);
//todo optimize into a single query //todo optimize into a single query
foreach ($data['credits'] as $paid_credit) { foreach ($data['credits'] as $paid_credit) {
$credit = Credit::withTrashed()->find($this->decodePrimaryKey($paid_credit['credit_id'])); // $credit = Credit::withTrashed()->find($paid_credit['credit_id']);
$credit = $credits->firstWhere('id', $paid_credit['credit_id']);
if ($credit) { if ($credit) {
$credit = $credit->service()->markSent()->save(); $credit = $credit->service()->markSent()->save();
ApplyCreditPayment::dispatchNow($credit, $payment, $paid_credit['amount'], $credit->company); (new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle();
} }
} }
} }
@ -245,7 +260,7 @@ class PaymentRepository extends BaseRepository {
event(new PaymentWasDeleted($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new PaymentWasDeleted($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $payment; return $payment;
//return parent::delete($payment);
} }
public function restore($payment) public function restore($payment)

View File

@ -48,6 +48,7 @@ class InstantPayment
public function run() public function run()
{ {
$is_credit_payment = false; $is_credit_payment = false;
$tokens = []; $tokens = [];
@ -69,7 +70,8 @@ class InstantPayment
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
$invoice->service() $invoice->service()
->markSent() ->markSent()
->removeUnpaidGatewayFees(); ->removeUnpaidGatewayFees()
->save();
}); });
/* pop non payable invoice from the $payable_invoices array */ /* pop non payable invoice from the $payable_invoices array */
@ -107,6 +109,7 @@ class InstantPayment
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision)); $payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision));
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision); $invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
/*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/ /*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/
if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) { if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) {

View File

@ -142,6 +142,7 @@ class CreditService
$client = $this->credit->client->fresh(); $client = $this->credit->client->fresh();
$client->service() $client->service()
->updatePaidToDate($adjustment) ->updatePaidToDate($adjustment)
->adjustCreditBalance($adjustment * -1)
->save(); ->save();
event('eloquent.created: App\Models\Payment', $payment); event('eloquent.created: App\Models\Payment', $payment);
@ -256,6 +257,29 @@ class CreditService
return $this; return $this;
} }
public function deleteCredit()
{
$this->credit
->client
->service()
->adjustCreditBalance($this->credit->balance * -1)
->save();
return $this;
}
public function restoreCredit()
{
$this->credit
->client
->service()
->adjustCreditBalance($this->credit->balance)
->save();
return $this;
}
/** /**
* Saves the credit. * Saves the credit.
* @return Credit object * @return Credit object

View File

@ -45,6 +45,11 @@ class MarkSent
->touchPdf() ->touchPdf()
->save(); ->save();
$this->client
->service()
->adjustCreditBalance($this->credit->amount)
->save();
event(new CreditWasMarkedSent($this->credit, $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new CreditWasMarkedSent($this->credit, $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->credit; return $this->credit;

View File

@ -49,6 +49,6 @@ class SendEmail
} }
}); });
$this->credit->service()->markSent(); $this->credit->service()->markSent()->save();
} }
} }

View File

@ -42,7 +42,7 @@ class ApplyPaymentAmount extends AbstractService
public function run() public function run()
{ {
if ($this->invoice->status_id == Invoice::STATUS_DRAFT) { if ($this->invoice->status_id == Invoice::STATUS_DRAFT) {
$this->invoice->service()->markSent(); $this->invoice->service()->markSent()->save();
} }
/*Don't double pay*/ /*Don't double pay*/

View File

@ -162,6 +162,7 @@ class DeletePayment
$client $client
->service() ->service()
->updatePaidToDate(($paymentable_credit->pivot->amount) * -1) ->updatePaidToDate(($paymentable_credit->pivot->amount) * -1)
->adjustCreditBalance($paymentable_credit->pivot->amount)
->save(); ->save();
}); });
} }

View File

@ -178,6 +178,7 @@ class CompanyTransformer extends EntityTransformer
'track_inventory' => (bool) $company->track_inventory, 'track_inventory' => (bool) $company->track_inventory,
'enable_applying_payments' => (bool) $company->enable_applying_payments, 'enable_applying_payments' => (bool) $company->enable_applying_payments,
'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates, 'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates,
'invoice_task_project' => (bool) $company->invoice_task_project,
]; ];
} }

View File

@ -14,6 +14,7 @@ namespace App\Transformers;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation; use App\Models\PurchaseOrderInvitation;
use App\Models\Vendor;
use App\Transformers\DocumentTransformer; use App\Transformers\DocumentTransformer;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -27,7 +28,8 @@ class PurchaseOrderTransformer extends EntityTransformer
]; ];
protected $availableIncludes = [ protected $availableIncludes = [
'expense' 'expense',
'vendor',
]; ];
public function includeInvitations(PurchaseOrder $purchase_order) public function includeInvitations(PurchaseOrder $purchase_order)
@ -49,7 +51,22 @@ class PurchaseOrderTransformer extends EntityTransformer
{ {
$transformer = new ExpenseTransformer($this->serializer); $transformer = new ExpenseTransformer($this->serializer);
return $this->includeItem($purchase_order->expense, $transformer, Document::class); if (!$purchase_order->expense) {
return null;
}
return $this->includeItem($purchase_order->expense, $transformer, Expense::class);
}
public function includeVendor(PurchaseOrder $purchase_order)
{
$transformer = new VendorTransformer($this->serializer);
if (!$purchase_order->vendor) {
return null;
}
return $this->includeItem($purchase_order->vendor, $transformer, Vendor::class);
} }
public function transform(PurchaseOrder $purchase_order) public function transform(PurchaseOrder $purchase_order)

View File

@ -90,7 +90,7 @@ class Number
return (float) $s; return (float) $s;
} }
// remove all seperators from first part and keep the end // remove all separators from first part and keep the end
$s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3); $s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3);
// return float // return float

View File

@ -95,12 +95,6 @@ return [
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=DYNAMIC', 'engine' => 'InnoDB ROW_FORMAT=DYNAMIC',
'options' => [], 'options' => [],
// 'options' => [
// PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
// PDO::MYSQL_ATTR_SSL_KEY => env("DB_CLIENT_KEY", ''),
// PDO::MYSQL_ATTR_SSL_CERT => env("DB_CLIENT_CERT", ''),
// PDO::MYSQL_ATTR_SSL_CA => env("DB_CA_CERT", ''),
// ],
], ],
'db-ninja-01a' => [ 'db-ninja-01a' => [

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.14', 'app_version' => '5.5.17',
'app_tag' => '5.5.14', 'app_tag' => '5.5.17',
'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,31 @@
<?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('companies', function (Blueprint $table) {
$table->boolean('invoice_task_project')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

View File

@ -0,0 +1,32 @@
<?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('purchase_order_invitations', function (Blueprint $table) {
$table->enum('email_status', ['delivered', 'bounced', 'spam'])->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

@ -800,7 +800,7 @@ $LANG = array(
'activity_51' => ':user deleted user :user', 'activity_51' => ':user deleted user :user',
'activity_52' => ':user restored user :user', 'activity_52' => ':user restored user :user',
'activity_53' => ':user marked sent :invoice', 'activity_53' => ':user marked sent :invoice',
'activity_54' => ':user reopened ticket :ticket', 'activity_54' => ':user paid invoice :invoice',
'activity_55' => ':contact replied ticket :ticket', 'activity_55' => ':contact replied ticket :ticket',
'activity_56' => ':user viewed ticket :ticket', 'activity_56' => ':user viewed ticket :ticket',
@ -4767,6 +4767,9 @@ $LANG = array(
'bulk_email_invoices' => 'Email Invoices', 'bulk_email_invoices' => 'Email Invoices',
'bulk_email_quotes' => 'Email Quotes', 'bulk_email_quotes' => 'Email Quotes',
'bulk_email_credits' => 'Email Credits', 'bulk_email_credits' => 'Email Credits',
'archive_purchase_order' => 'Archive Purchase Order',
'restore_purchase_order' => 'Restore Purchase Order',
'delete_purchase_order' => 'Delete Purchase Order',
); );
return $LANG; return $LANG;

View File

@ -7,7 +7,7 @@ const RESOURCES = {
"canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900", "canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900",
"canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd", "canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd",
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba", "canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
"main.dart.js": "809c193905ea4c80a7c0b2b0e484e1db", "main.dart.js": "3234746bf3c8a3ccec3cf901969c352c",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa", "assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
"assets/assets/google_fonts/Roboto-Regular.ttf": "8a36205bd9b83e03af0591a004bc97f4", "assets/assets/google_fonts/Roboto-Regular.ttf": "8a36205bd9b83e03af0591a004bc97f4",
@ -295,9 +295,9 @@ const RESOURCES = {
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1", "assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
"assets/NOTICES": "254a5bf1eeb00601955e148b31cb925c", "assets/NOTICES": "254a5bf1eeb00601955e148b31cb925c",
"flutter.js": "eb2682e33f25cd8f1fc59011497c35f8", "flutter.js": "eb2682e33f25cd8f1fc59011497c35f8",
"/": "f1ab1648b6acf56aebbd6ae07968a461", "/": "48872e415511ff066e9403545e4ba572",
"favicon.png": "dca91c54388f52eded692718d5a98b8b", "favicon.png": "dca91c54388f52eded692718d5a98b8b",
"version.json": "a10748384e57f928f4d2871ac7563faf", "version.json": "9eca00898047311eda7456072e79d77d",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40", "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35" "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35"

78643
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

78401
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

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 @@
{"app_name":"invoiceninja_flutter","version":"5.0.91","build_number":"91","package_name":"invoiceninja_flutter"} {"app_name":"invoiceninja_flutter","version":"5.0.92","build_number":"92","package_name":"invoiceninja_flutter"}

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="" 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: 570px; display: block; margin-left: auto; margin-right: auto;"/>
<!--[if gte mso 9]> <!--[if gte mso 9]>
</div> </div>
<![endif]--> <![endif]-->

View File

@ -81,6 +81,15 @@
text-align: left !important; text-align: left !important;
} }
</style> </style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head> </head>
<body <body
@ -102,7 +111,7 @@
<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="" 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: 155px; display: block; margin-left: auto; margin-right: auto;"/>
<!--[if gte mso 9]> <!--[if gte mso 9]>
</div> </div>
<![endif]--> <![endif]-->

View File

@ -21,7 +21,7 @@
</script> </script>
<script> <script>
window.flutterConfiguration = { window.flutterConfiguration = {
canvasKitBaseUrl: "{{config('ninja.app_url')}}/canvaskit/" canvasKitBaseUrl: "/canvaskit/"
}; };
</script> </script>
</head> </head>

View File

@ -3,7 +3,7 @@
@endphp @endphp
@push('head') @push('head')
<meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}?cache_buster={{time()}}"> <meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path($invitation, 'url', true) }}?cache_buster={{time()}}">
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script> <script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
@endpush @endpush
@ -86,7 +86,7 @@
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white"></canvas> <canvas id="pdf-placeholder" class="shadow rounded-lg bg-white"></canvas>
</div> </div>
@else @else
<iframe id="pdf-iframe" src="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}?cache_buster={{time()}}" class="h-screen w-full border-0 mt-4"></iframe> <iframe id="pdf-iframe" src="{{ $url ?? $entity->pdf_file_path($invitation, 'url', true) }}?cache_buster={{time()}}" class="h-screen w-full border-0 mt-4"></iframe>
@endif @endif

View File

@ -33,7 +33,7 @@
@include('portal.ninja2020.components.entity-documents', ['entity' => $credit]) @include('portal.ninja2020.components.entity-documents', ['entity' => $credit])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $credit]) @include('portal.ninja2020.components.pdf-viewer', ['entity' => $credit, 'invitation' => $invitation])
@endsection @endsection

View File

@ -1,2 +1,2 @@
<iframe src="{{ $invoice->pdf_file_path(null, 'url', true) }}" <iframe src="{{ $invoice->pdf_file_path($invitation, 'url', true) }}"
style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe> style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>

View File

@ -96,7 +96,7 @@
@endif @endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $invoice]) @include('portal.ninja2020.components.entity-documents', ['entity' => $invoice])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $invoice]) @include('portal.ninja2020.components.pdf-viewer', ['entity' => $invoice, 'invitation' => $invitation])
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -46,7 +46,7 @@
@endif @endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $purchase_order]) @include('portal.ninja2020.components.entity-documents', ['entity' => $purchase_order])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $purchase_order]) @include('portal.ninja2020.components.pdf-viewer', ['entity' => $purchase_order, 'invitation' => $invitation])
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -105,7 +105,7 @@
@endif @endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $quote]) @include('portal.ninja2020.components.entity-documents', ['entity' => $quote])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $quote]) @include('portal.ninja2020.components.pdf-viewer', ['entity' => $quote, 'invitation' => $invitation])
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -13,11 +13,13 @@ namespace Tests\Feature;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\DataMapper\DefaultSettings; use App\DataMapper\DefaultSettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account; use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyToken; use App\Models\CompanyToken;
use App\Models\Credit;
use App\Models\User; use App\Models\User;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -61,6 +63,97 @@ class ClientTest extends TestCase
$this->makeTestData(); $this->makeTestData();
} }
private function buildLineItems($number = 2)
{
$line_items = [];
for($x=0; $x<$number; $x++)
{
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
}
return $line_items;
}
public function testCreditBalance()
{
$this->client->credit_balance = 0;
$this->client->save();
$this->assertEquals(0, $this->client->credit_balance);
$credit = [
'status_id' => 1,
'number' => 'dfdfd',
'discount' => 0,
'is_amount_discount' => 1,
'number' => '34343xx43',
'public_notes' => 'notes',
'is_deleted' => 0,
'custom_value1' => 0,
'custom_value2' => 0,
'custom_value3' => 0,
'custom_value4' => 0,
'status' => 1,
'client_id' => $this->encodePrimaryKey($this->client->id),
'line_items' => $this->buildLineItems()
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/', $credit)
->assertStatus(200);
$arr = $response->json();
$credit_id = $arr['data']['id'];
$credit = Credit::find($this->decodePrimaryKey($credit_id));
$this->assertNotNull($credit);
$this->assertEquals(0, $credit->balance);
$credit->service()->markSent()->save();
$this->assertEquals(20, $credit->balance);
$this->assertEquals(20, $credit->client->fresh()->credit_balance);
//lets now update the credit and increase its balance, this should also increase the credit balance
$data = [
'number' => 'dfdfd',
'discount' => 0,
'is_amount_discount' => 1,
'number' => '34343xx43',
'public_notes' => 'notes',
'is_deleted' => 0,
'custom_value1' => 0,
'custom_value2' => 0,
'custom_value3' => 0,
'custom_value4' => 0,
'status' => 1,
'client_id' => $this->encodePrimaryKey($this->client->id),
'line_items' => $this->buildLineItems(3)
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/credits/'.$credit->hashed_id, $data)
->assertStatus(200);
$credit = $credit->fresh();
$this->assertEquals(30, $credit->balance);
$this->assertEquals(30, $credit->client->fresh()->credit_balance);
}
public function testStoreClientUsingCountryCode() public function testStoreClientUsingCountryCode()
{ {
$data = [ $data = [

View File

@ -78,7 +78,7 @@ class ImportCompanyTest extends TestCase
{ {
parent::setUp(); parent::setUp();
$this->artisan('db:seed'); // $this->artisan('db:seed');
$this->withoutMiddleware( $this->withoutMiddleware(
ThrottleRequests::class ThrottleRequests::class

View File

@ -70,4 +70,54 @@ class CreditBalanceTest extends TestCase
$this->assertEquals($this->client->service()->getCreditBalance(), 0); $this->assertEquals($this->client->service()->getCreditBalance(), 0);
} }
public function testCreditDeleteCheckClientBalance()
{
$credit = Credit::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'client_id' => $this->client->id,
'balance' => 10,
'number' => 'testing-number-01',
'status_id' => Credit::STATUS_SENT,
]);
$credit->client->credit_balance = 10;
$credit->push();
//delete invoice
$data = [
'ids' => [$credit->hashed_id],
];
//restore invoice
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=delete', $data)->assertStatus(200);
$client = $credit->client->fresh();
$this->assertEquals(0, $client->credit_balance);
//restore invoice
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=restore', $data)->assertStatus(200);
$client = $credit->client->fresh();
$this->assertEquals(10, $client->credit_balance);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=archive', $data)->assertStatus(200);
$client = $credit->client->fresh();
$this->assertEquals(10, $client->credit_balance);
}
} }