mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-03 07:24:34 -04:00
commit
0be452e144
@ -364,10 +364,14 @@ class DemoMode extends Command
|
|||||||
|
|
||||||
private function createProject($client, $assigned_user_id = null)
|
private function createProject($client, $assigned_user_id = null)
|
||||||
{
|
{
|
||||||
$vendor = Project::factory()->create([
|
$project = Project::factory()->create([
|
||||||
'user_id' => $client->user->id,
|
'user_id' => $client->user->id,
|
||||||
'company_id' => $client->company_id,
|
'company_id' => $client->company_id,
|
||||||
|
'client_id' => $client->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$project->number = $this->getNextProjectNumber($project);
|
||||||
|
$project->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createInvoice($client, $assigned_user_id = null)
|
private function createInvoice($client, $assigned_user_id = null)
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\Ninja\SendReminders;
|
use App\Jobs\Ninja\SendReminders;
|
||||||
|
use App\Jobs\Util\WebHookHandler;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Quote;
|
||||||
|
use App\Models\Webhook;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class SendRemindersCron extends Command
|
class SendRemindersCron extends Command
|
||||||
@ -48,5 +52,34 @@ class SendRemindersCron extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
SendReminders::dispatchNow();
|
SendReminders::dispatchNow();
|
||||||
|
|
||||||
|
$this->webHookOverdueInvoices();
|
||||||
|
$this->webHookExpiredQuotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function webHookOverdueInvoices()
|
||||||
|
{
|
||||||
|
$invoices = Invoice::where('is_deleted', 0)
|
||||||
|
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||||
|
->where('balance', '>', 0)
|
||||||
|
->whereDate('due_date', now()->subDays(1)->startOfDay())
|
||||||
|
->cursor();
|
||||||
|
|
||||||
|
$invoices->each(function ($invoice){
|
||||||
|
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function webHookExpiredQuotes()
|
||||||
|
{
|
||||||
|
$quotes = Quote::where('is_deleted', 0)
|
||||||
|
->where('status_id', Quote::STATUS_SENT)
|
||||||
|
->whereDate('due_date', now()->subDays(1)->startOfDay())
|
||||||
|
->cursor();
|
||||||
|
|
||||||
|
$quotes->each(function ($quote){
|
||||||
|
WebHookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class InvitationController extends Controller
|
|||||||
|
|
||||||
$key = $entity.'_id';
|
$key = $entity.'_id';
|
||||||
|
|
||||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; //todo sensitive to the route parameters here
|
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||||
|
|
||||||
$invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key])
|
$invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key])
|
||||||
->with('contact.client')
|
->with('contact.client')
|
||||||
|
@ -572,10 +572,10 @@ class CreditController extends BaseController
|
|||||||
public function downloadPdf($invitation_key)
|
public function downloadPdf($invitation_key)
|
||||||
{
|
{
|
||||||
$invitation = $this->credit_repository->getInvitationByKey($invitation_key);
|
$invitation = $this->credit_repository->getInvitationByKey($invitation_key);
|
||||||
$contact = $invitation->contact;
|
// $contact = $invitation->contact;
|
||||||
$credit = $invitation->credit;
|
$credit = $invitation->credit;
|
||||||
|
|
||||||
$file_path = $credit->service()->getCreditPdf($contact);
|
$file_path = $credit->service()->getCreditPdf($invitation);
|
||||||
|
|
||||||
return response()->download($file_path);
|
return response()->download($file_path);
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,10 @@ use App\Jobs\Util\UnlinkFile;
|
|||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceInvitation;
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\Quote;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
use App\Transformers\InvoiceTransformer;
|
use App\Transformers\InvoiceTransformer;
|
||||||
|
use App\Transformers\QuoteTransformer;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Utils\TempFile;
|
use App\Utils\TempFile;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
@ -639,7 +641,12 @@ class InvoiceController extends BaseController
|
|||||||
break;
|
break;
|
||||||
case 'clone_to_quote':
|
case 'clone_to_quote':
|
||||||
$quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id);
|
$quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id);
|
||||||
// todo build the quote transformer and return response here
|
|
||||||
|
$this->entity_transformer = QuoteTransformer::class;
|
||||||
|
$this->entity_type = Quote::class;
|
||||||
|
|
||||||
|
return $this->itemResponse($quote);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'history':
|
case 'history':
|
||||||
// code...
|
// code...
|
||||||
|
@ -23,9 +23,11 @@ use App\Http\Requests\RecurringQuote\ShowRecurringQuoteRequest;
|
|||||||
use App\Http\Requests\RecurringQuote\StoreRecurringQuoteRequest;
|
use App\Http\Requests\RecurringQuote\StoreRecurringQuoteRequest;
|
||||||
use App\Http\Requests\RecurringQuote\UpdateRecurringQuoteRequest;
|
use App\Http\Requests\RecurringQuote\UpdateRecurringQuoteRequest;
|
||||||
use App\Jobs\Entity\ActionEntity;
|
use App\Jobs\Entity\ActionEntity;
|
||||||
|
use App\Models\Quote;
|
||||||
use App\Models\RecurringQuote;
|
use App\Models\RecurringQuote;
|
||||||
use App\Repositories\BaseRepository;
|
use App\Repositories\BaseRepository;
|
||||||
use App\Repositories\RecurringQuoteRepository;
|
use App\Repositories\RecurringQuoteRepository;
|
||||||
|
use App\Transformers\QuoteTransformer;
|
||||||
use App\Transformers\RecurringQuoteTransformer;
|
use App\Transformers\RecurringQuoteTransformer;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -583,8 +585,11 @@ class RecurringQuoteController extends BaseController
|
|||||||
// return $this->itemResponse($recurring_invoice);
|
// return $this->itemResponse($recurring_invoice);
|
||||||
break;
|
break;
|
||||||
case 'clone_to_quote':
|
case 'clone_to_quote':
|
||||||
// $quote = CloneRecurringQuoteToQuoteFactory::create($recurring_invoice, auth()->user()->id);
|
$quote = CloneRecurringQuoteToQuoteFactory::create($recurring_invoice, auth()->user()->id);
|
||||||
// todo build the quote transformer and return response here
|
$this->entity_transformer = QuoteTransformer::class;
|
||||||
|
$this->entity_type = Quote::class;
|
||||||
|
|
||||||
|
return $this->itemResponse($quote);
|
||||||
break;
|
break;
|
||||||
case 'history':
|
case 'history':
|
||||||
// code...
|
// code...
|
||||||
|
@ -84,4 +84,9 @@ class SelfUpdateController extends BaseController
|
|||||||
|
|
||||||
return response()->json(['message' => ''], 200);
|
return response()->json(['message' => ''], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkVersion()
|
||||||
|
{
|
||||||
|
return trim(file_get_contents(config('ninja.version_url')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class ContactTokenAuth
|
|||||||
//stateless, don't remember the contact.
|
//stateless, don't remember the contact.
|
||||||
auth()->guard('contact')->login($client_contact, false);
|
auth()->guard('contact')->login($client_contact, false);
|
||||||
|
|
||||||
event(new ContactLoggedIn($client_contact, $client_contact->company, Ninja::eventVars())); //todo
|
event(new ContactLoggedIn($client_contact, $client_contact->company, Ninja::eventVars()));
|
||||||
} else {
|
} else {
|
||||||
$error = [
|
$error = [
|
||||||
'message' => 'Invalid token',
|
'message' => 'Invalid token',
|
||||||
|
@ -75,7 +75,7 @@ class StoreClientRequest extends Request
|
|||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
//@todo implement feature permissions for > 100 clients
|
//@todo implement feature permissions for > 100 clients
|
||||||
//
|
|
||||||
$settings = ClientSettings::defaults();
|
$settings = ClientSettings::defaults();
|
||||||
|
|
||||||
if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
|
if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
|
||||||
|
@ -77,7 +77,7 @@ class StorePaymentRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! isset($input['amount']) || $input['amount'] == 0) {
|
if (! isset($input['amount']) || $input['amount'] == 0) {
|
||||||
$input['amount'] = $invoices_total - $credits_total; //todo the payment amount is always less the credit amount applied
|
$input['amount'] = $invoices_total - $credits_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
$input['is_manual'] = true;
|
$input['is_manual'] = true;
|
||||||
|
@ -109,22 +109,20 @@ class StoreRecurringInvoiceRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
|
||||||
|
|
||||||
private function setAutoBillFlag($auto_bill)
|
|
||||||
{
|
|
||||||
if($auto_bill == 'always')
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if($auto_bill == 'off')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
//todo do we need to handle optin / optout here?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setAutoBillFlag($auto_bill)
|
||||||
|
{
|
||||||
|
if($auto_bill == 'always')
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if($auto_bill == 'off')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,28 @@ 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'))) {
|
// if (request()->input('credits') && is_array(request()->input('credits'))) {
|
||||||
foreach (request()->input('credits') as $credit) {
|
// foreach (request()->input('credits') as $credit) {
|
||||||
$cred = Credit::find($this->decodePrimaryKey($credit['credit_id']));
|
// $cred = Credit::find($this->decodePrimaryKey($credit['credit_id']));
|
||||||
|
|
||||||
if (! $cred || $cred->balance == 0) {
|
// if (! $cred || $cred->balance == 0) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
|
||||||
|
|
||||||
|
if (request()->input('credits') && is_array(request()->input('credits'))) {
|
||||||
|
|
||||||
|
$credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id')))
|
||||||
|
->where('balance', '>', 0)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return $credit_collection->count() == count(request()->input('credits'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,10 +285,13 @@ class SendReminders implements ShouldQueue
|
|||||||
* @param Invoice $invoice
|
* @param Invoice $invoice
|
||||||
* @param float $amount The fee amount
|
* @param float $amount The fee amount
|
||||||
* @param float $percent The fee percentage amount
|
* @param float $percent The fee percentage amount
|
||||||
|
*
|
||||||
* @return Invoice
|
* @return Invoice
|
||||||
*/
|
*/
|
||||||
private function setLateFee($invoice, $amount, $percent) :Invoice
|
private function setLateFee($invoice, $amount, $percent) :Invoice
|
||||||
{
|
{
|
||||||
|
$temp_invoice_balance = $invoice->balance;
|
||||||
|
|
||||||
if ($amount <= 0 && $percent <= 0)
|
if ($amount <= 0 && $percent <= 0)
|
||||||
return $invoice;
|
return $invoice;
|
||||||
|
|
||||||
@ -314,7 +317,9 @@ class SendReminders implements ShouldQueue
|
|||||||
/**Refresh Invoice values*/
|
/**Refresh Invoice values*/
|
||||||
$invoice = $invoice->calc()->getInvoice();
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
|
||||||
//@todo update the ledger!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
$this->invoice->client->service()->updateBalance($this->invoice->balance - $temp_invoice_balance)->save();
|
||||||
|
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance - $temp_invoice_balance);
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
namespace App\Jobs\Util;
|
namespace App\Jobs\Util;
|
||||||
|
|
||||||
use App\Models\Webhook;
|
use App\Models\Webhook;
|
||||||
@ -40,7 +48,7 @@ class WebhookHandler implements ShouldQueue
|
|||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function handle() :bool
|
public function handle() :bool
|
||||||
{
|
{//todo set multidb here
|
||||||
if (! $this->entity->company || $this->entity->company->is_disabled) {
|
if (! $this->entity->company || $this->entity->company->is_disabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -384,7 +384,7 @@ class Client extends BaseModel implements HasLocalePreference
|
|||||||
return array_search($model->id, $transformed_ids);
|
return array_search($model->id, $transformed_ids);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$gateways = $this->company->company_gateways; //todo perhaps we can remove this or keep as a catch all.
|
$gateways = $this->company->company_gateways;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($gateways as $gateway) {
|
foreach ($gateways as $gateway) {
|
||||||
|
@ -224,8 +224,6 @@ class Credit extends BaseModel
|
|||||||
if ($this->balance == 0) {
|
if ($this->balance == 0) {
|
||||||
$this->status_id = self::STATUS_APPLIED;
|
$this->status_id = self::STATUS_APPLIED;
|
||||||
$this->save();
|
$this->save();
|
||||||
//event(new InvoiceWasPaid($this, $this->company));//todo
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,10 +246,10 @@ class Credit extends BaseModel
|
|||||||
|
|
||||||
if (! $invitation) {
|
if (! $invitation) {
|
||||||
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
|
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||||
CreateEntityPdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
|
CreateEntityPdf::dispatchNow($this->invitations->first());
|
||||||
} else {
|
} else {
|
||||||
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
|
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||||
CreateEntityPdf::dispatchNow($invitation->credit, $invitation->company, $invitation->contact);
|
CreateEntityPdf::dispatchNow($invitation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $storage_path;
|
return $storage_path;
|
||||||
|
@ -40,7 +40,10 @@ class Webhook extends BaseModel
|
|||||||
const EVENT_UPDATE_TASK = 19;
|
const EVENT_UPDATE_TASK = 19;
|
||||||
const EVENT_DELETE_TASK = 20;
|
const EVENT_DELETE_TASK = 20;
|
||||||
const EVENT_APPROVE_QUOTE = 21;
|
const EVENT_APPROVE_QUOTE = 21;
|
||||||
|
const EVENT_LATE_INVOICE = 22;
|
||||||
|
const EVENT_EXPIRED_QUOTE = 23;
|
||||||
|
const EVENT_REMIND_INVOICE = 24;
|
||||||
|
|
||||||
public static $valid_events = [
|
public static $valid_events = [
|
||||||
self::EVENT_CREATE_CLIENT,
|
self::EVENT_CREATE_CLIENT,
|
||||||
self::EVENT_CREATE_PAYMENT,
|
self::EVENT_CREATE_PAYMENT,
|
||||||
|
@ -29,9 +29,9 @@ class CreditService
|
|||||||
$this->credit = $credit;
|
$this->credit = $credit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreditPdf($contact)
|
public function getCreditPdf($invitation)
|
||||||
{
|
{
|
||||||
return (new GetCreditPdf($this->credit, $contact))->run();
|
return (new GetCreditPdf($invitation))->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,14 +19,17 @@ use Illuminate\Support\Facades\Storage;
|
|||||||
|
|
||||||
class GetCreditPdf extends AbstractService
|
class GetCreditPdf extends AbstractService
|
||||||
{
|
{
|
||||||
private $credit;
|
public $credit;
|
||||||
|
|
||||||
private $contact;
|
public $contact;
|
||||||
|
|
||||||
public function __construct(Credit $credit, ClientContact $contact = null)
|
public $invitation;
|
||||||
|
|
||||||
|
public function __construct($invitation)
|
||||||
{
|
{
|
||||||
$this->credit = $credit;
|
$this->invitation = $invitation;
|
||||||
$this->contact = $contact;
|
$this->credit = $invitation->credit;
|
||||||
|
$this->contact = $invitation->contact;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
@ -44,7 +47,7 @@ class GetCreditPdf extends AbstractService
|
|||||||
$file = Storage::disk($disk)->exists($file_path);
|
$file = Storage::disk($disk)->exists($file_path);
|
||||||
|
|
||||||
if (! $file) {
|
if (! $file) {
|
||||||
$file_path = CreateEntityPdf::dispatchNow($this->credit, $this->credit->company, $this->contact);
|
$file_path = CreateEntityPdf::dispatchNow($this->invitation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storage::disk($disk)->path($file_path);
|
return Storage::disk($disk)->path($file_path);
|
||||||
|
@ -74,7 +74,7 @@ class AccountTransformer extends EntityTransformer
|
|||||||
'utm_content' => (string) $account->utm_content,
|
'utm_content' => (string) $account->utm_content,
|
||||||
'utm_term' => (string) $account->utm_term,
|
'utm_term' => (string) $account->utm_term,
|
||||||
'referral_code' => (string) $account->referral_code,
|
'referral_code' => (string) $account->referral_code,
|
||||||
'latest_version' => (string) $account->latest_version,
|
'latest_version' => (string) trim($account->latest_version),
|
||||||
'current_version' => (string) config('ninja.app_version'),
|
'current_version' => (string) config('ninja.app_version'),
|
||||||
'updated_at' => (int) $account->updated_at,
|
'updated_at' => (int) $account->updated_at,
|
||||||
'archived_at' => (int) $account->deleted_at,
|
'archived_at' => (int) $account->deleted_at,
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
"expenses",
|
"expenses",
|
||||||
"CRM",
|
"CRM",
|
||||||
"Credit card billing",
|
"Credit card billing",
|
||||||
"projects"
|
"projects",
|
||||||
|
"tasks"
|
||||||
],
|
],
|
||||||
"license": "Attribution Assurance License",
|
"license": "Attribution Assurance License",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user