mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-08-11 15:34:21 -04:00
Merge branch 'v5-develop' into v5-stable
This commit is contained in:
commit
11ab8462a6
@ -46,7 +46,7 @@ TRUSTED_PROXIES=
|
||||
NINJA_ENVIRONMENT=selfhost
|
||||
|
||||
#options - snappdf / phantom / hosted_ninja
|
||||
PDF_GENERATOR=phantom
|
||||
PDF_GENERATOR=hosted_ninja
|
||||
|
||||
PHANTOMJS_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||
PHANTOMJS_SECRET=secret
|
||||
|
@ -1 +1 @@
|
||||
5.3.88
|
||||
5.3.89
|
@ -607,7 +607,7 @@ class CheckData extends Command
|
||||
invoices ON
|
||||
clients.id=invoices.client_id
|
||||
WHERE invoices.is_deleted = false
|
||||
AND invoices.status_id IN (2,3,4)
|
||||
AND invoices.status_id IN (2,3)
|
||||
GROUP BY clients.id
|
||||
HAVING invoice_balance != clients.balance
|
||||
ORDER BY clients.id;
|
||||
@ -663,7 +663,7 @@ class CheckData extends Command
|
||||
ON invoices.client_id = clients.id
|
||||
WHERE invoices.is_deleted = 0
|
||||
AND clients.is_deleted = 0
|
||||
AND invoices.status_id IN (2,3,4)
|
||||
AND invoices.status_id IN (2,3)
|
||||
GROUP BY clients.id
|
||||
HAVING(invoices_balance != clients.balance)
|
||||
ORDER BY clients.id;
|
||||
@ -683,7 +683,7 @@ class CheckData extends Command
|
||||
{
|
||||
$client = Client::withTrashed()->find($_client->id);
|
||||
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3,4])->sum('balance');
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3])->sum('balance');
|
||||
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
@ -724,7 +724,7 @@ class CheckData extends Command
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3,4])->sum('balance');
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3])->sum('balance');
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
if ($ledger && number_format($ledger->balance, 4) != number_format($client->balance, 4)) {
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Ledger\LedgerBalanceUpdate;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Ninja\QueueSize;
|
||||
use App\Jobs\Util\DiskCleanup;
|
||||
use App\Jobs\Util\ReminderJob;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
@ -55,7 +56,8 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new LedgerBalanceUpdate)->everyFiveMinutes()->withoutOverlapping();
|
||||
// $schedule->job(new LedgerBalanceUpdate)->everyFiveMinutes()->withoutOverlapping();
|
||||
$schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping();
|
||||
|
||||
@ -87,9 +89,9 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->daily('01:00')->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->daily('02:00')->withoutOverlapping();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('01:05')->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:05')->withoutOverlapping();
|
||||
|
||||
$schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping();
|
||||
|
||||
|
79
app/DataMapper/Analytics/QueueSize.php
Normal file
79
app/DataMapper/Analytics/QueueSize.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataMapper\Analytics;
|
||||
|
||||
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
|
||||
|
||||
class QueueSize extends GenericMixedMetric
|
||||
{
|
||||
|
||||
/**
|
||||
* The type of Sample.
|
||||
*
|
||||
* Monotonically incrementing counter
|
||||
*
|
||||
* - counter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'mixed_metric';
|
||||
|
||||
/**
|
||||
* The name of the counter.
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'ninja.queue_size';
|
||||
|
||||
/**
|
||||
* The datetime of the counter measurement.
|
||||
*
|
||||
* date("Y-m-d H:i:s")
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
public $datetime;
|
||||
|
||||
/**
|
||||
* The Class failure name
|
||||
* set to 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric5 = 'stub';
|
||||
|
||||
/**
|
||||
* The exception string
|
||||
* set to 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric6 = 'stub';
|
||||
|
||||
/**
|
||||
* The counter
|
||||
* set to 1.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $int_metric1 = 1;
|
||||
|
||||
/**
|
||||
* Company Key
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric7 = '';
|
||||
|
||||
public function __construct($int_metric1) {
|
||||
$this->int_metric1 = $int_metric1;
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,8 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['invitations']);
|
||||
|
||||
//preserve terms if they exist on Quotes
|
||||
if(array_key_exists('terms', $quote_array) && strlen($quote_array['terms']) < 2)
|
||||
//if(array_key_exists('terms', $quote_array) && strlen($quote_array['terms']) < 2)
|
||||
if(!$quote->company->use_quote_terms_on_conversion)
|
||||
unset($quote_array['terms']);
|
||||
|
||||
// unset($quote_array['public_notes']);
|
||||
@ -38,7 +39,6 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['design_id']);
|
||||
unset($quote_array['user']);
|
||||
|
||||
|
||||
foreach ($quote_array as $key => $value) {
|
||||
$invoice->{$key} = $value;
|
||||
}
|
||||
|
@ -25,10 +25,12 @@ use App\Http\Requests\Invoice\EditInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\ShowInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\UpdateInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\UpdateReminderRequest;
|
||||
use App\Http\Requests\Invoice\UploadInvoiceRequest;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\Invoice\BulkInvoiceJob;
|
||||
use App\Jobs\Invoice\StoreInvoice;
|
||||
use App\Jobs\Invoice\UpdateReminders;
|
||||
use App\Jobs\Invoice\ZipInvoices;
|
||||
use App\Jobs\Ninja\TransactionLog;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
@ -957,4 +959,12 @@ class InvoiceController extends BaseController
|
||||
return $this->itemResponse($invoice->fresh());
|
||||
|
||||
}
|
||||
|
||||
public function update_reminders(UpdateReminderRequest $request)
|
||||
{
|
||||
|
||||
UpdateReminders::dispatch(auth()->user()->company());
|
||||
|
||||
return response()->json(["message" => "Updating reminders"], 200);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class SubdomainController extends BaseController
|
||||
'stage',
|
||||
'html',
|
||||
'lb',
|
||||
'shopify',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -29,6 +29,7 @@ class WePayController extends BaseController
|
||||
*/
|
||||
public function signup(string $token)
|
||||
{
|
||||
return render('gateways.wepay.signup.finished');
|
||||
|
||||
$hash = Cache::get($token);
|
||||
|
||||
|
@ -83,7 +83,7 @@ class InvoicesTable extends Component
|
||||
|
||||
return render('components.livewire.invoices-table', [
|
||||
'invoices' => $query,
|
||||
'gateway_available' => !empty(auth()->user()->client->service()->getPaymentMethods(0)),
|
||||
'gateway_available' => !empty(auth()->user()->client->service()->getPaymentMethods(-1)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class CreatePaymentMethodRequest extends FormRequest
|
||||
|
||||
$available_methods = [];
|
||||
|
||||
collect($client->service()->getPaymentMethods(1))
|
||||
collect($client->service()->getPaymentMethods(-1))
|
||||
->filter(function ($method) use (&$available_methods) {
|
||||
$available_methods[] = $method['gateway_type_id'];
|
||||
});
|
||||
|
32
app/Http/Requests/Invoice/UpdateReminderRequest.php
Normal file
32
app/Http/Requests/Invoice/UpdateReminderRequest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Invoice;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class UpdateReminderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
@ -104,7 +104,7 @@ class StorePaymentRequest extends Request
|
||||
'credits.*.credit_id' => new ValidCreditsRules($this->all()),
|
||||
'credits.*.amount' => ['required', new CreditsSumRule($this->all())],
|
||||
'invoices' => new ValidPayableInvoicesRule(),
|
||||
'number' => ['nullable', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)],
|
||||
'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)],
|
||||
|
||||
];
|
||||
|
||||
|
@ -14,6 +14,9 @@ namespace App\Http\Requests\Preview;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -30,15 +33,13 @@ class PreviewInvoiceRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', Invoice::class);
|
||||
return auth()->user()->can('create', Invoice::class) || auth()->user()->can('create', Quote::class) || auth()->user()->can('create', RecurringInvoice::class) || auth()->user()->can('create', Credit::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
// $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
||||
|
||||
$rules['number'] = ['nullable'];
|
||||
|
||||
return $rules;
|
||||
|
@ -32,7 +32,8 @@ class GenericReportRequest extends Request
|
||||
'end_date' => 'string|date',
|
||||
'date_key' => 'string',
|
||||
'date_range' => 'string',
|
||||
'report_keys' => 'present|array'
|
||||
'report_keys' => 'present|array',
|
||||
'send_email' => 'bool',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ class ProfitLossRequest extends Request
|
||||
'is_income_billed' => 'required|bail|bool',
|
||||
'is_expense_billed' => 'required|bail|bool',
|
||||
'include_tax' => 'required|bail|bool',
|
||||
'date_range' => 'required|bail|string'
|
||||
'date_range' => 'required|bail|string',
|
||||
'send_email' => 'bool',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ class AutoBillCron
|
||||
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('auto_bill_tries', '<', 3)
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', false)
|
||||
->whereHas('company', function ($query) {
|
||||
@ -70,6 +71,7 @@ class AutoBillCron
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('auto_bill_tries', '<', 3)
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', false)
|
||||
->whereHas('company', function ($query) {
|
||||
@ -94,6 +96,7 @@ class AutoBillCron
|
||||
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('auto_bill_tries', '<', 3)
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', false)
|
||||
->whereHas('company', function ($query) {
|
||||
@ -111,6 +114,7 @@ class AutoBillCron
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('auto_bill_tries', '<', 3)
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', false)
|
||||
->whereHas('company', function ($query) {
|
||||
|
@ -67,11 +67,23 @@ class RecurringInvoicesCron
|
||||
|
||||
nlog("Trying to send {$recurring_invoice->number}");
|
||||
|
||||
/* Special check if we should generate another invoice is the previous one is yet to be paid */
|
||||
if($recurring_invoice->company->stop_on_unpaid_recurring && $recurring_invoice->invoices()->whereIn('status_id', [2,3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) {
|
||||
|
||||
nlog("Existing invoice exists, skipping");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
nlog("Unable to sending recurring invoice {$recurring_invoice->id} ". $e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@ -103,6 +115,13 @@ class RecurringInvoicesCron
|
||||
|
||||
nlog("Trying to send {$recurring_invoice->number}");
|
||||
|
||||
if($recurring_invoice->company->stop_on_unpaid_recurring) {
|
||||
|
||||
if($recurring_invoice->invoices()->whereIn('status_id', [2,3])->where('is_deleted', 0)->where('balance', '>', 0)->exists())
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
try{
|
||||
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class SubscriptionCron
|
||||
public function handle() : void
|
||||
{
|
||||
|
||||
nlog("Subscription Cron");
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
|
||||
$this->loopSubscriptions();
|
||||
|
57
app/Jobs/Invoice/UpdateReminders.php
Normal file
57
app/Jobs/Invoice/UpdateReminders.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdateReminders implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Company $company;
|
||||
|
||||
|
||||
public function __construct(Company $company)
|
||||
{
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->company
|
||||
->invoices()
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->cursor()
|
||||
->each(function ($invoice){
|
||||
$invoice->service()->setReminder()->save();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
85
app/Jobs/Ledger/ClientLedgerBalanceUpdate.php
Normal file
85
app/Jobs/Ledger/ClientLedgerBalanceUpdate.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Ledger;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyLedger;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientLedgerBalanceUpdate implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $company;
|
||||
|
||||
public $client;
|
||||
|
||||
|
||||
public function __construct(Company $company, Client $client)
|
||||
{
|
||||
|
||||
$this->company = $company;
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() :void
|
||||
{
|
||||
nlog("Updating company ledgers");
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
CompanyLedger::where('balance', 0)->where('client_id', $this->client->id)->cursor()->each(function ($company_ledger){
|
||||
|
||||
if($company_ledger->balance > 0)
|
||||
return;
|
||||
|
||||
$last_record = CompanyLedger::where('client_id', $company_ledger->client_id)
|
||||
->where('company_id', $company_ledger->company_id)
|
||||
->where('balance', '!=', 0)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
|
||||
if(!$last_record)
|
||||
return;
|
||||
|
||||
nlog("Updating Balance NOW");
|
||||
|
||||
$company_ledger->balance = $last_record->balance + $company_ledger->adjustment;
|
||||
$company_ledger->save();
|
||||
|
||||
});
|
||||
|
||||
nlog("Finished checking company ledgers");
|
||||
|
||||
}
|
||||
|
||||
public function checkLedger()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,8 @@ class LedgerBalanceUpdate implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -34,26 +36,31 @@ class LedgerBalanceUpdate implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :void
|
||||
{
|
||||
nlog("Updating company ledgers");
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->check();
|
||||
$this->checkLedger();
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->check();
|
||||
$this->checkLedger();
|
||||
}
|
||||
}
|
||||
|
||||
nlog("Finished checking company ledgers");
|
||||
|
||||
}
|
||||
|
||||
public function check()
|
||||
public function checkLedger()
|
||||
{
|
||||
|
||||
CompanyLedger::where('balance', 0)->cursor()->each(function ($company_ledger){
|
||||
nlog("Checking ledgers....");
|
||||
|
||||
CompanyLedger::where('balance', 0)->where('adjustment', '!=', 0)->cursor()->each(function ($company_ledger){
|
||||
|
||||
if($company_ledger->balance > 0)
|
||||
return;
|
||||
@ -67,6 +74,8 @@ class LedgerBalanceUpdate implements ShouldQueue
|
||||
if(!$last_record)
|
||||
return;
|
||||
|
||||
nlog("Updating Balance NOW");
|
||||
|
||||
$company_ledger->balance = $last_record->balance + $company_ledger->adjustment;
|
||||
$company_ledger->save();
|
||||
|
||||
|
@ -111,9 +111,10 @@ class PaymentFailedMailer implements ShouldQueue
|
||||
|
||||
//add client payment failures here.
|
||||
//
|
||||
if($contact = $this->client->contacts()->first() && $this->payment_hash)
|
||||
if($this->client->contacts()->whereNotNull('email')->exists() && $this->payment_hash)
|
||||
{
|
||||
|
||||
$contact = $this->client->contacts()->whereNotNull('email')->first();
|
||||
|
||||
$mail_obj = (new ClientPaymentFailureObject($this->client, $this->error, $this->company, $this->payment_hash))->build();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
|
48
app/Jobs/Ninja/QueueSize.php
Normal file
48
app/Jobs/Ninja/QueueSize.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Ninja;
|
||||
|
||||
use App\DataMapper\Analytics\QueueSize as QueueSizeAnalytic;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Turbo124\Beacon\Jobs\Database\MySQL\DbStatus;
|
||||
|
||||
class QueueSize implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
LightLogs::create(new QueueSizeAnalytic(Queue::size()))
|
||||
->queue();
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ class DiskCleanup implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
nlog("Cleaning Storage");
|
||||
|
||||
// Get all files in a directory
|
||||
$files = Storage::allFiles(config('filesystems.default'), 'backups/');
|
||||
|
@ -153,10 +153,12 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
|
||||
$line_items = $this->invoice->line_items;
|
||||
|
||||
$expense_ids = [];
|
||||
|
||||
foreach($line_items as $item)
|
||||
{
|
||||
|
||||
$expense_ids = [];
|
||||
|
||||
if(property_exists($item, 'expense_id'))
|
||||
{
|
||||
$expense_ids[] = $item->expense_id;
|
||||
@ -177,6 +179,8 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
});
|
||||
}
|
||||
|
||||
$task_ids = [];
|
||||
|
||||
if(property_exists($item, 'task_id'))
|
||||
{
|
||||
$task_ids[] = $item->task_id;
|
||||
|
@ -432,7 +432,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
public function getCreditCardGateway() :?CompanyGateway
|
||||
{
|
||||
|
||||
$pms = $this->service()->getPaymentMethods(0);
|
||||
$pms = $this->service()->getPaymentMethods(-1);
|
||||
|
||||
foreach($pms as $pm)
|
||||
{
|
||||
@ -462,7 +462,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
//todo refactor this - it is only searching for existing tokens
|
||||
public function getBankTransferGateway() :?CompanyGateway
|
||||
{
|
||||
$pms = $this->service()->getPaymentMethods(0);
|
||||
$pms = $this->service()->getPaymentMethods(-1);
|
||||
|
||||
if($this->currency()->code == 'USD' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))){
|
||||
|
||||
|
@ -100,6 +100,9 @@ class Company extends BaseModel
|
||||
'client_registration_fields',
|
||||
'convert_rate_to_client',
|
||||
'markdown_email_enabled',
|
||||
'stop_on_unpaid_recurring',
|
||||
'use_quote_terms_on_conversion',
|
||||
'show_production_description_dropdown',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
@ -48,4 +48,9 @@ class CompanyLedger extends Model
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class Invoice extends BaseModel
|
||||
'subscription_id',
|
||||
'auto_bill_enabled',
|
||||
'uses_inclusive_taxes',
|
||||
'vendor_id',
|
||||
'vendor_id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -445,14 +445,14 @@ class Invoice extends BaseModel
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation, config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
|
||||
try{
|
||||
$file_exists = Storage::disk('public')->exists($file_path);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
nlog($e->getMessage());
|
||||
|
||||
|
||||
}
|
||||
|
||||
if($file_exists)
|
||||
@ -553,10 +553,10 @@ class Invoice extends BaseModel
|
||||
$invoice = $this->fresh();
|
||||
|
||||
return [
|
||||
'invoice_id' => $invoice->id,
|
||||
'invoice_amount' => $invoice->amount ?: 0,
|
||||
'invoice_partial' => $invoice->partial ?: 0,
|
||||
'invoice_balance' => $invoice->balance ?: 0,
|
||||
'invoice_id' => $invoice->id,
|
||||
'invoice_amount' => $invoice->amount ?: 0,
|
||||
'invoice_partial' => $invoice->partial ?: 0,
|
||||
'invoice_balance' => $invoice->balance ?: 0,
|
||||
'invoice_paid_to_date' => $invoice->paid_to_date ?: 0,
|
||||
'invoice_status' => $invoice->status_id ?: 1,
|
||||
];
|
||||
|
@ -227,12 +227,21 @@ class RecurringInvoice extends BaseModel
|
||||
if (!$this->next_send_date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nlog("frequency = $this->frequency_id");
|
||||
nlog("frequency = $this->next_send_date");
|
||||
|
||||
$offset = $this->client->timezone_offset();
|
||||
|
||||
/* If this setting is enabled, the recurring invoice may be set in the past */
|
||||
|
||||
if($this->company->stop_on_unpaid_recurring) {
|
||||
|
||||
/* Lets set the next send date to now so we increment from today, rather than in the past*/
|
||||
if(Carbon::parse($this->next_send_date)->lt(now()->subDays(3)))
|
||||
$this->next_send_date = now()->format('Y-m-d');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
|
||||
to add ON a day - a day = 86400 seconds
|
||||
|
@ -30,7 +30,7 @@ class ClientObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_CLIENT, $client, $client->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_CLIENT, $client, $client->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ class ClientObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_CLIENT, $client, $client->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_CLIENT, $client, $client->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ class ClientObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_CLIENT, $client, $client->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_CLIENT, $client, $client->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class ExpenseObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_EXPENSE, $expense, $expense->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_EXPENSE, $expense, $expense->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ class ExpenseObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_EXPENSE, $expense, $expense->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_EXPENSE, $expense, $expense->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ class ExpenseObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_EXPENSE, $expense, $expense->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_EXPENSE, $expense, $expense->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class InvoiceObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ class InvoiceObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company, 'client')->delay(now()->addSeconds(2));
|
||||
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ class InvoiceObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_INVOICE, $invoice, $invoice->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_INVOICE, $invoice, $invoice->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class PaymentObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(5);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(now()->addSeconds(20));
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class PaymentObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(5);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(now()->addSeconds(20));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ class ProjectObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_CREATE, $project, $project->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_CREATE, $project, $project->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ class ProjectObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_UPDATE, $project, $project->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_UPDATE, $project, $project->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class QuoteObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_QUOTE, $quote, $quote->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_QUOTE, $quote, $quote->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ class QuoteObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_QUOTE, $quote, $quote->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_QUOTE, $quote, $quote->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
|
||||
}
|
||||
@ -72,7 +72,7 @@ class QuoteObserver
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_QUOTE, $quote, $quote->company, 'client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_QUOTE, $quote, $quote->company, 'client')->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class TaskObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_TASK, $task, $task->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_TASK, $task, $task->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ class TaskObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_TASK, $task, $task->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_TASK, $task, $task->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ class TaskObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_TASK, $task, $task->company);
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_TASK, $task, $task->company)->delay(now()->addSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,7 +245,7 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
sleep(1);
|
||||
|
||||
foreach ($request->events as $event) {
|
||||
if ($event['action'] === 'confirmed') {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out') {
|
||||
|
||||
nlog("Searching for transaction reference");
|
||||
|
||||
|
@ -568,7 +568,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
//payment_intent.succeeded - this will confirm or cancel the payment
|
||||
if($request->type === 'payment_intent.succeeded'){
|
||||
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(10);
|
||||
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(10));
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
@ -579,7 +579,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
if(array_key_exists('payment_intent', $transaction))
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
// ->where('company_id', $request->getCompany()->id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
$query->where('transaction_reference', $transaction['payment_intent'])
|
||||
->orWhere('transaction_reference', $transaction['id']);
|
||||
@ -589,7 +589,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
else
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
// ->where('company_id', $request->getCompany()->id)
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->first();
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use App\Models\Company;
|
||||
use App\Utils\Traits\ClientGroupSettingsSaver;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Illuminate\Database\QueryException;
|
||||
|
||||
/**
|
||||
* ClientRepository.
|
||||
@ -26,6 +27,8 @@ class ClientRepository extends BaseRepository
|
||||
use GeneratesCounter;
|
||||
use SavesDocuments;
|
||||
|
||||
private bool $completed = true;
|
||||
|
||||
/**
|
||||
* @var ClientContactRepository
|
||||
*/
|
||||
@ -76,8 +79,34 @@ class ClientRepository extends BaseRepository
|
||||
|
||||
|
||||
if (!isset($client->number) || empty($client->number) || strlen($client->number) == 0) {
|
||||
$client->number = $this->getNextClientNumber($client);
|
||||
$client->save();
|
||||
// $client->number = $this->getNextClientNumber($client);
|
||||
// $client->save();
|
||||
|
||||
$x=1;
|
||||
|
||||
do{
|
||||
|
||||
try{
|
||||
|
||||
$client->number = $this->getNextClientNumber($client);
|
||||
$client->saveQuietly();
|
||||
|
||||
$this->completed = false;
|
||||
|
||||
|
||||
}
|
||||
catch(QueryException $e){
|
||||
|
||||
$x++;
|
||||
|
||||
if($x>10)
|
||||
$this->completed = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
while($this->completed);
|
||||
|
||||
}
|
||||
|
||||
if (empty($data['name'])) {
|
||||
|
@ -132,8 +132,7 @@ class PaymentMethod
|
||||
|
||||
private function getMethods()
|
||||
{
|
||||
// we should prefilter $gateway->driver($this)->gatewayTypes()
|
||||
// and only include the enabled payment methods on the gateway
|
||||
|
||||
$this->payment_methods = [];
|
||||
|
||||
foreach ($this->gateways as $gateway) {
|
||||
@ -148,13 +147,12 @@ class PaymentMethod
|
||||
|
||||
if ($this->validGatewayForAmount($gateway->fees_and_limits->{$type}, $this->amount) && $gateway->fees_and_limits->{$type}->is_enabled) {
|
||||
|
||||
// if($type == GatewayType::BANK_TRANSFER);
|
||||
|
||||
$this->payment_methods[] = [$gateway->id => $type];
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//$this->payment_methods[] = [$gateway->id => $type];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,11 +241,11 @@ class PaymentMethod
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $fees_and_limits->min_limit != -1 && $this->amount < $fees_and_limits->min_limit) {
|
||||
if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $fees_and_limits->min_limit != -1 && ($this->amount < $fees_and_limits->min_limit && $this->amount != -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $fees_and_limits->max_limit != -1 && $this->amount > $fees_and_limits->max_limit) {
|
||||
if ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $fees_and_limits->max_limit != -1 && ($this->amount > $fees_and_limits->max_limit && $this->amount != -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -45,8 +45,8 @@ class SendEmail
|
||||
|
||||
$this->credit->invitations->each(function ($invitation) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
$email_builder = (new CreditEmail())->build($invitation, $this->reminder_template);
|
||||
|
||||
|
||||
// $email_builder = (new CreditEmail())->build($invitation, $this->reminder_template);
|
||||
// EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company);
|
||||
EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template);
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class AutoBillInvoice extends AbstractService
|
||||
public function __construct(Invoice $invoice, $db)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ class AutoBillInvoice extends AbstractService
|
||||
$is_partial = false;
|
||||
|
||||
/* Is the invoice payable? */
|
||||
if (! $this->invoice->isPayable())
|
||||
if (! $this->invoice->isPayable())
|
||||
return $this->invoice;
|
||||
|
||||
/* Mark the invoice as sent */
|
||||
@ -114,25 +114,29 @@ class AutoBillInvoice extends AbstractService
|
||||
]);
|
||||
|
||||
nlog("Payment hash created => {$payment_hash->id}");
|
||||
|
||||
|
||||
$payment = false;
|
||||
|
||||
try{
|
||||
$payment = $gateway_token->gateway
|
||||
->driver($this->client)
|
||||
->setPaymentHash($payment_hash)
|
||||
->tokenBilling($gateway_token, $payment_hash);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage());
|
||||
// $this->invoice->service()->removeUnpaidGatewayFees();
|
||||
}
|
||||
try {
|
||||
$payment = $gateway_token->gateway
|
||||
->driver($this->client)
|
||||
->setPaymentHash($payment_hash)
|
||||
->tokenBilling($gateway_token, $payment_hash);
|
||||
} catch (\Exception $e) {
|
||||
$this->invoice->auto_bill_tries = $number_of_retries + 1;
|
||||
|
||||
if($payment){
|
||||
info("Auto Bill payment captured for ".$this->invoice->number);
|
||||
if ($this->invoice->auto_bill_tries == 3) {
|
||||
$this->invoice->auto_bill_enabled = false;
|
||||
$this->invoice->auto_bill_tries = 0; //reset the counter here in case auto billing is turned on again in the future.
|
||||
$this->invoice->save();
|
||||
}
|
||||
nlog("payment NOT captured for " . $this->invoice->number . " with error " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
info("Auto Bill payment captured for " . $this->invoice->number);
|
||||
}
|
||||
|
||||
// return $this->invoice->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ class UpdateReminder extends AbstractService
|
||||
$this->settings = $this->invoice->client->getMergedSettings();
|
||||
}
|
||||
|
||||
if (! $this->invoice->isPayable()) {
|
||||
if (! $this->invoice->isPayable() || $this->invoice->status_id == Invoice::STATUS_DRAFT) {
|
||||
$this->invoice->next_send_date = null;
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
@ -55,7 +55,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 1 after due date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder1_sent) &&
|
||||
@ -64,7 +64,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 1 before_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder1_sent) &&
|
||||
@ -73,7 +73,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 1 after_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder2_sent) &&
|
||||
@ -82,7 +82,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 2 after_invoice_date = ".$reminder_date->format('Y-m-d'));
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder2_sent) &&
|
||||
@ -91,7 +91,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 2 before_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder2_sent) &&
|
||||
@ -100,7 +100,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 2 after_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder3_sent) &&
|
||||
@ -109,7 +109,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 3 after_invoice_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder3_sent) &&
|
||||
@ -118,7 +118,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 3 before_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if (is_null($this->invoice->reminder3_sent) &&
|
||||
@ -127,7 +127,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings reminder 3 after_due_date");
|
||||
$date_collection->push($reminder_date);
|
||||
}
|
||||
|
||||
if ($this->invoice->last_sent_date &&
|
||||
@ -140,7 +140,7 @@ class UpdateReminder extends AbstractService
|
||||
$reminder_date->addSeconds($offset);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
|
||||
$date_collection->push($reminder_date); nlog("settings endless reminder");
|
||||
$date_collection->push($reminder_date);
|
||||
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Services\Ledger;
|
||||
|
||||
use App\Factory\CompanyLedgerFactory;
|
||||
use App\Jobs\Ledger\ClientLedgerBalanceUpdate;
|
||||
use App\Models\Activity;
|
||||
use App\Models\CompanyLedger;
|
||||
|
||||
@ -36,6 +37,7 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -52,6 +54,8 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -67,6 +71,8 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ class Design extends BaseDesign
|
||||
{
|
||||
if ($this->type === 'statement') {
|
||||
|
||||
$s_date = $this->translateDate(now()->format($this->client->company->date_format()), $this->client->date_format(), $this->client->locale());
|
||||
$s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
|
||||
|
||||
return [
|
||||
['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [
|
||||
@ -391,10 +391,10 @@ class Design extends BaseDesign
|
||||
$element = ['element' => 'tr', 'elements' => []];
|
||||
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->client->locale()) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->client->locale()) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->client) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->client) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->client->locale()) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->client->locale()) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->client) ?: ' '];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->client) ?: ' '];
|
||||
|
||||
$tbody[] = $element;
|
||||
}
|
||||
@ -453,6 +453,9 @@ class Design extends BaseDesign
|
||||
foreach ($this->invoices as $invoice) {
|
||||
foreach ($invoice->payments as $payment) {
|
||||
|
||||
if($payment->is_deleted)
|
||||
continue;
|
||||
|
||||
$element = ['element' => 'tr', 'elements' => []];
|
||||
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
|
||||
|
@ -168,6 +168,9 @@ class CompanyTransformer extends EntityTransformer
|
||||
'client_registration_fields' => (array) $company->client_registration_fields,
|
||||
'convert_rate_to_client' => (bool) $company->convert_rate_to_client,
|
||||
'markdown_email_enabled' => (bool) $company->markdown_email_enabled,
|
||||
'stop_on_unpaid_recurring' => (bool) $company->stop_on_unpaid_recurring,
|
||||
'show_production_description_dropdown' => (bool)$company->show_production_description_dropdown,
|
||||
'use_quote_terms_on_conversion' => (bool) $company->use_quote_terms_on_conversion,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,11 @@ trait UserNotifies
|
||||
$notifiable_methods = [];
|
||||
$notifications = $company_user->notifications;
|
||||
|
||||
if ($invitation->company->is_disabled && is_array($notifications->email) || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
if ($invitation->company->is_disabled &&
|
||||
is_array($notifications->email) ||
|
||||
$company_user->trashed() ||
|
||||
!$company_user->user ||
|
||||
$company_user->user->trashed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -56,7 +60,11 @@ trait UserNotifies
|
||||
$notifiable_methods = [];
|
||||
$notifications = $company_user->notifications;
|
||||
|
||||
if ($entity->company->is_disabled || ! $notifications || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
if ($entity->company->is_disabled ||
|
||||
! $notifications ||
|
||||
$company_user->trashed() ||
|
||||
! $company_user->user ||
|
||||
$company_user->user->trashed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -125,7 +133,10 @@ trait UserNotifies
|
||||
public function findCompanyUserNotificationType($company_user, $required_permissions) :array
|
||||
{
|
||||
|
||||
if ($company_user->company->is_disabled || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
if ($company_user->company->is_disabled ||
|
||||
$company_user->trashed() ||
|
||||
!$company_user->user ||
|
||||
$company_user->user->trashed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.88',
|
||||
'app_tag' => '5.3.88',
|
||||
'app_version' => '5.3.89',
|
||||
'app_tag' => '5.3.89',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddAutoBillTriesToInvoicesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->smallInteger('auto_bill_tries')->default(0);
|
||||
});
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('stop_on_unpaid_recurring')->default(0);
|
||||
$table->boolean('use_quote_terms_on_conversion')->default(0);
|
||||
$table->boolean('show_production_description_dropdown')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
144
public/flutter.js
vendored
Normal file
144
public/flutter.js
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* This script installs service_worker.js to provide PWA functionality to
|
||||
* application. For more information, see:
|
||||
* https://developers.google.com/web/fundamentals/primers/service-workers
|
||||
*/
|
||||
|
||||
if (!_flutter) {
|
||||
var _flutter = {};
|
||||
}
|
||||
_flutter.loader = null;
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
class FlutterLoader {
|
||||
// TODO: Move the below methods to "#private" once supported by all the browsers
|
||||
// we support. In the meantime, we use the "revealing module" pattern.
|
||||
|
||||
// Watchdog to prevent injecting the main entrypoint multiple times.
|
||||
_scriptLoaded = null;
|
||||
|
||||
// Resolver for the pending promise returned by loadEntrypoint.
|
||||
_didCreateEngineInitializerResolve = null;
|
||||
|
||||
/**
|
||||
* Initializes the main.dart.js with/without serviceWorker.
|
||||
* @param {*} options
|
||||
* @returns a Promise that will eventually resolve with an EngineInitializer,
|
||||
* or will be rejected with the error caused by the loader.
|
||||
*/
|
||||
loadEntrypoint(options) {
|
||||
const {
|
||||
entrypointUrl = "main.dart.js",
|
||||
serviceWorker,
|
||||
} = (options || {});
|
||||
return this._loadWithServiceWorker(entrypointUrl, serviceWorker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the promise created by loadEntrypoint. Called by Flutter.
|
||||
* Needs to be weirdly bound like it is, so "this" is preserved across
|
||||
* the JS <-> Flutter jumps.
|
||||
* @param {*} engineInitializer
|
||||
*/
|
||||
didCreateEngineInitializer = (function(engineInitializer) {
|
||||
if (typeof this._didCreateEngineInitializerResolve != "function") {
|
||||
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
|
||||
}
|
||||
this._didCreateEngineInitializerResolve(engineInitializer);
|
||||
// Remove this method after it's done, so Flutter Web can hot restart.
|
||||
delete this.didCreateEngineInitializer;
|
||||
}).bind(this);
|
||||
|
||||
_loadEntrypoint(entrypointUrl) {
|
||||
if (!this._scriptLoaded) {
|
||||
this._scriptLoaded = new Promise((resolve, reject) => {
|
||||
let scriptTag = document.createElement("script");
|
||||
scriptTag.src = entrypointUrl;
|
||||
scriptTag.type = "application/javascript";
|
||||
this._didCreateEngineInitializerResolve = resolve; // Cache the resolve, so it can be called from Flutter.
|
||||
scriptTag.addEventListener("error", reject);
|
||||
document.body.append(scriptTag);
|
||||
});
|
||||
}
|
||||
|
||||
return this._scriptLoaded;
|
||||
}
|
||||
|
||||
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
||||
if (!serviceWorker || serviceWorker.state == "activated") {
|
||||
if (!serviceWorker) {
|
||||
console.warn("Cannot activate a null service worker. Falling back to plain <script> tag.");
|
||||
} else {
|
||||
console.debug("Service worker already active.");
|
||||
}
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
}
|
||||
return new Promise((resolve, _) => {
|
||||
serviceWorker.addEventListener("statechange", () => {
|
||||
if (serviceWorker.state == "activated") {
|
||||
console.debug("Installed new service worker.");
|
||||
resolve(this._loadEntrypoint(entrypointUrl));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
||||
if (!("serviceWorker" in navigator) || serviceWorkerOptions == null) {
|
||||
console.warn("Service worker not supported (or configured). Falling back to plain <script> tag.", serviceWorkerOptions);
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
}
|
||||
|
||||
const {
|
||||
serviceWorkerVersion,
|
||||
timeoutMillis = 4000,
|
||||
} = serviceWorkerOptions;
|
||||
|
||||
let serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
|
||||
let loader = navigator.serviceWorker.register(serviceWorkerUrl)
|
||||
.then((reg) => {
|
||||
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||
// No active web worker and we have installed or are installing
|
||||
// one for the first time. Simply wait for it to activate.
|
||||
let sw = reg.installing || reg.waiting;
|
||||
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||
// When the app updates the serviceWorkerVersion changes, so we
|
||||
// need to ask the service worker to update.
|
||||
console.debug("New service worker available.");
|
||||
return reg.update().then((reg) => {
|
||||
console.debug("Service worker updated.");
|
||||
let sw = reg.installing || reg.waiting || reg.active;
|
||||
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
||||
});
|
||||
} else {
|
||||
// Existing service worker is still good.
|
||||
console.debug("Loading app from service worker.");
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// Timeout race promise
|
||||
let timeout;
|
||||
if (timeoutMillis > 0) {
|
||||
timeout = new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
if (!this._scriptLoaded) {
|
||||
console.warn("Failed to load app from service worker. Falling back to plain <script> tag.");
|
||||
resolve(this._loadEntrypoint(entrypointUrl));
|
||||
}
|
||||
}, timeoutMillis);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.race([loader, timeout]);
|
||||
}
|
||||
}
|
||||
|
||||
_flutter.loader = new FlutterLoader();
|
||||
}());
|
12
public/flutter_service_worker.js
vendored
12
public/flutter_service_worker.js
vendored
@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"main.dart.js": "405baf11805a6cc292fe5d6ad21109e2",
|
||||
"main.dart.js": "0b89ccdb131cf8a2d7bdb28e5373c68f",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
@ -14,9 +14,9 @@ const RESOURCES = {
|
||||
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
|
||||
"version.json": "3afb81924daf4f751571755436069115",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "b62641afc9ab487008e996a5c5865e56",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "7e7a6cccddf6d7b20012a548461d5d81",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "95db9098c58fd6db106f1116bae85a0b",
|
||||
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
|
||||
"assets/NOTICES": "e39f4d8d7a45f123298d0dd94ac1ba80",
|
||||
"assets/NOTICES": "52d7174bb068ef86545951d5bc8c5744",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
@ -37,15 +37,15 @@ const RESOURCES = {
|
||||
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
|
||||
"/": "11012b191ca75cbf64b79eb3bb3931ef",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
"/": "d58545047c6b4aa7c8c528dc89852fb6",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
// start.
|
||||
const CORE = [
|
||||
"/",
|
||||
"main.dart.js",
|
||||
"main.dart.js",
|
||||
"assets/NOTICES",
|
||||
"assets/AssetManifest.json",
|
||||
"assets/FontManifest.json"];
|
||||
|
379570
public/main.dart.js
vendored
379570
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
392972
public/main.foss.dart.js
vendored
392972
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
376763
public/main.html.dart.js
vendored
376763
public/main.html.dart.js
vendored
File diff suppressed because one or more lines are too long
394458
public/main.next.dart.js
vendored
394458
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
62686
public/main.profile.dart.js
vendored
62686
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -4582,6 +4582,7 @@ $LANG = array(
|
||||
'auto_archive_cancelled_invoices_help' => 'Automatically archive invoices when they are cancelled.',
|
||||
'alternate_pdf_viewer' => 'Alternate PDF Viewer',
|
||||
'alternate_pdf_viewer_help' => 'Improve scrolling over the PDF preview [BETA]',
|
||||
'currency_cayman_island_dollar' => 'Cayman Island Dollar',
|
||||
|
||||
);
|
||||
|
||||
|
@ -105,7 +105,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
Route::put('invoices/{invoice}/upload', 'InvoiceController@upload')->name('invoices.upload');
|
||||
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');
|
||||
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
|
||||
|
||||
Route::post('invoices/update_reminders', 'InvoiceController@update_reminders')->name('invoices.update_reminders');
|
||||
|
||||
Route::post('logout', 'LogoutController@index')->name('logout');
|
||||
|
||||
Route::post('migrate', 'MigrationController@index')->name('migrate.start');
|
||||
|
@ -169,4 +169,30 @@ class ReminderTest extends TestCase
|
||||
|
||||
}
|
||||
|
||||
public function testReminderIsSet()
|
||||
{
|
||||
$this->invoice->next_send_date = null;
|
||||
$this->invoice->date = now()->format('Y-m-d');
|
||||
$this->invoice->due_date = Carbon::now()->addDays(30)->format('Y-m-d');
|
||||
$this->invoice->save();
|
||||
|
||||
$settings = $this->company->settings;
|
||||
$settings->enable_reminder1 = true;
|
||||
$settings->schedule_reminder1 = 'after_invoice_date';
|
||||
$settings->num_days_reminder1 = 7;
|
||||
$settings->enable_reminder2 = true;
|
||||
$settings->schedule_reminder2 = 'before_due_date';
|
||||
$settings->num_days_reminder2 = 1;
|
||||
$settings->enable_reminder3 = true;
|
||||
$settings->schedule_reminder3 = 'after_due_date';
|
||||
$settings->num_days_reminder3 = 1;
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->invoice = $this->invoice->service()->markSent()->save();
|
||||
$this->invoice->service()->setReminder($settings)->save();
|
||||
|
||||
$this->assertNotNull($this->invoice->next_send_date);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user