Add Invoice All Tasks scheduler?
This commit is contained in:
parent
688a7b5e60
commit
5abe5f1fbb
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\Schedule;
|
||||||
|
|
||||||
|
class InvoiceOutstandingTasks
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the template name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $template = 'invoice_outstanding_tasks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date range the report should include
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $date_range = 'this_month';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of clients hashed_ids
|
||||||
|
*
|
||||||
|
* Leave blank if this action should apply to all clients
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public array $clients = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the invoice will be auto-sent
|
||||||
|
* else it will be generated and kept in a draft state
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $auto_send = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the project tasks will be included in the report
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $include_project_tasks = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,8 @@ class StoreSchedulerRequest extends Request
|
||||||
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:vendor,purchase_order_item,purchase_order,ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,activities,client,clients,client_contact,client_contacts,credit,credits,document,documents,expense,expenses,invoice,invoices,invoice_item,invoice_items,quote,quotes,quote_item,quote_items,recurring_invoice,recurring_invoices,payment,payments,product,products,task,tasks'],
|
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:vendor,purchase_order_item,purchase_order,ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,activities,client,clients,client_contact,client_contacts,credit,credits,document,documents,expense,expenses,invoice,invoices,invoice_item,invoice_items,quote,quotes,quote_item,quote_items,recurring_invoice,recurring_invoices,payment,payments,product,products,task,tasks'],
|
||||||
'parameters.date_key' => ['bail','sometimes', 'string'],
|
'parameters.date_key' => ['bail','sometimes', 'string'],
|
||||||
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
||||||
|
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
|
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ class UpdateSchedulerRequest extends Request
|
||||||
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:vendor,purchase_order_item,purchase_order,ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,activities,client,clients,client_contact,client_contacts,credit,credits,document,documents,expense,expenses,invoice,invoices,invoice_item,invoice_items,quote,quotes,quote_item,quote_items,recurring_invoice,recurring_invoices,payment,payments,product,products,task,tasks'],
|
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:vendor,purchase_order_item,purchase_order,ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,activities,client,clients,client_contact,client_contacts,credit,credits,document,documents,expense,expenses,invoice,invoices,invoice_item,invoice_items,quote,quotes,quote_item,quote_items,recurring_invoice,recurring_invoices,payment,payments,product,products,task,tasks'],
|
||||||
'parameters.date_key' => ['bail','sometimes', 'string'],
|
'parameters.date_key' => ['bail','sometimes', 'string'],
|
||||||
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
||||||
|
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
|
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,10 @@ class AdjustProductInventory implements ShouldQueue
|
||||||
collect($this->invoice->line_items)->filter(function ($item) {
|
collect($this->invoice->line_items)->filter(function ($item) {
|
||||||
return $item->type_id == '1';
|
return $item->type_id == '1';
|
||||||
})->each(function ($i) {
|
})->each(function ($i) {
|
||||||
$p = Product::query()->where('product_key', $i->product_key)->where('company_id', $this->company->id)->first();
|
$p = Product::query()
|
||||||
|
->where('company_id', $this->company->id)
|
||||||
|
->where('product_key', $i->product_key)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($p) {
|
if ($p) {
|
||||||
$p->in_stock_quantity += $i->quantity;
|
$p->in_stock_quantity += $i->quantity;
|
||||||
|
|
@ -82,7 +85,10 @@ class AdjustProductInventory implements ShouldQueue
|
||||||
collect($this->invoice->line_items)->filter(function ($item) {
|
collect($this->invoice->line_items)->filter(function ($item) {
|
||||||
return $item->type_id == '1';
|
return $item->type_id == '1';
|
||||||
})->each(function ($i) {
|
})->each(function ($i) {
|
||||||
$p = Product::query()->where('product_key', $i->product_key)->where('company_id', $this->company->id)->first();
|
$p = Product::query()
|
||||||
|
->where('company_id', $this->company->id)
|
||||||
|
->where('product_key', $i->product_key)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($p) {
|
if ($p) {
|
||||||
$p->in_stock_quantity -= $i->quantity;
|
$p->in_stock_quantity -= $i->quantity;
|
||||||
|
|
@ -103,7 +109,10 @@ class AdjustProductInventory implements ShouldQueue
|
||||||
collect($this->invoice->line_items)->filter(function ($item) {
|
collect($this->invoice->line_items)->filter(function ($item) {
|
||||||
return $item->type_id == '1';
|
return $item->type_id == '1';
|
||||||
})->each(function ($i) {
|
})->each(function ($i) {
|
||||||
$p = Product::query()->where('product_key', $i->product_key)->where('company_id', $this->company->id)->first();
|
$p = Product::query()
|
||||||
|
->where('company_id', $this->company->id)
|
||||||
|
->where('product_key', $i->product_key)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($p) {
|
if ($p) {
|
||||||
$p->in_stock_quantity -= $i->quantity;
|
$p->in_stock_quantity -= $i->quantity;
|
||||||
|
|
@ -125,7 +134,10 @@ class AdjustProductInventory implements ShouldQueue
|
||||||
collect($this->old_invoice)->filter(function ($item) {
|
collect($this->old_invoice)->filter(function ($item) {
|
||||||
return $item->type_id == '1';
|
return $item->type_id == '1';
|
||||||
})->each(function ($i) {
|
})->each(function ($i) {
|
||||||
$p = Product::query()->where('product_key', $i->product_key)->where('company_id', $this->company->id)->first();
|
$p = Product::query()
|
||||||
|
->where('company_id', $this->company->id)
|
||||||
|
->where('product_key', $i->product_key)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($p) {
|
if ($p) {
|
||||||
$p->in_stock_quantity += $i->quantity;
|
$p->in_stock_quantity += $i->quantity;
|
||||||
|
|
|
||||||
|
|
@ -248,30 +248,28 @@ class Task extends BaseModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function calcDuration($start_time_cutoff = 0, $end_time_cutoff = 0)
|
public function calcDuration(bool $billable = false)
|
||||||
{
|
{
|
||||||
$duration = 0;
|
$duration = 0;
|
||||||
$parts = json_decode($this->time_log ?? '{}') ?: [];
|
$parts = json_decode($this->time_log ?? '{}') ?: [];
|
||||||
|
|
||||||
foreach ($parts as $part) {
|
foreach ($parts as $part) {
|
||||||
|
|
||||||
|
if($billable && isset($part[3]) && !$part[3]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$start_time = $part[0];
|
$start_time = $part[0];
|
||||||
|
|
||||||
if (count($part) == 1 || ! $part[1]) {
|
if (count($part) == 1 || ! $part[1]) {
|
||||||
$end_time = time();
|
$end_time = time();
|
||||||
} else {
|
} else {
|
||||||
$end_time = $part[1];
|
$end_time = $part[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($start_time_cutoff) {
|
|
||||||
$start_time = max($start_time, $start_time_cutoff);
|
|
||||||
}
|
|
||||||
if ($end_time_cutoff) {
|
|
||||||
$end_time = min($end_time, $end_time_cutoff);
|
|
||||||
}
|
|
||||||
|
|
||||||
$duration += max($end_time - $start_time, 0);
|
$duration += max($end_time - $start_time, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return CarbonInterval::seconds(round($duration))->locale($this->company->locale())->cascade()->forHumans();
|
|
||||||
return round($duration);
|
return round($duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +311,7 @@ class Task extends BaseModel
|
||||||
|
|
||||||
public function getQuantity(): float
|
public function getQuantity(): float
|
||||||
{
|
{
|
||||||
return round(($this->calcDuration() / 3600), 2);
|
return round(($this->calcDuration(true) / 3600), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logDuration(int $start_time, int $end_time)
|
public function logDuration(int $start_time, int $end_time)
|
||||||
|
|
@ -323,7 +321,7 @@ class Task extends BaseModel
|
||||||
|
|
||||||
public function taskValue(): float
|
public function taskValue(): float
|
||||||
{
|
{
|
||||||
return round(($this->calcDuration() / 3600) * $this->getRate(), 2);
|
return round(($this->calcDuration(true) / 3600) * $this->getRate(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isRunning(): bool
|
public function isRunning(): bool
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class ProjectRepository extends BaseRepository
|
||||||
->cursor()
|
->cursor()
|
||||||
->each(function ($task, $key) use (&$lines) {
|
->each(function ($task, $key) use (&$lines) {
|
||||||
|
|
||||||
if (!$task->isRunning()) {
|
if (!$task->isRunning() && $task->calcDuration(true) > 0) {
|
||||||
if ($key == 0 && $task->company->invoice_task_project) {
|
if ($key == 0 && $task->company->invoice_task_project) {
|
||||||
$body = '<div class="project-header">'.$task->project->name.'</div>' .$task->project?->public_notes ?? ''; //@phpstan-ignore-line
|
$body = '<div class="project-header">'.$task->project->name.'</div>' .$task->project?->public_notes ?? ''; //@phpstan-ignore-line
|
||||||
$body .= '<div class="task-time-details">'.$task->description().'</div>';
|
$body .= '<div class="task-time-details">'.$task->description().'</div>';
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class ProjectService
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'date' => $task->calculated_start_date ?? \Carbon\Carbon::parse($task->created_at)->format('Y-m-d'),
|
'date' => $task->calculated_start_date ?? \Carbon\Carbon::parse($task->created_at)->format('Y-m-d'),
|
||||||
'hours_used' => $task->calcDuration() / 60 / 60,
|
'hours_used' => $task->calcDuration(true) / 60 / 60,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Scheduler;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Models\Task;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Models\Scheduler;
|
||||||
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use App\Utils\Traits\MakesDates;
|
||||||
|
use App\DataMapper\Schedule\EmailStatement;
|
||||||
|
use App\Repositories\InvoiceRepository;
|
||||||
|
|
||||||
|
class InvoiceOutstandingTasksService
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
use MakesDates;
|
||||||
|
|
||||||
|
public function __construct(public Scheduler $scheduler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
|
||||||
|
// $query = Task::query()
|
||||||
|
// ->where('company_id', $this->scheduler->company_id)
|
||||||
|
// ->where('is_deleted', 0);
|
||||||
|
|
||||||
|
// if (count($this->scheduler->parameters['clients']) >= 1) {
|
||||||
|
// $query->whereIn('client_id', $this->transformKeys($this->scheduler->parameters['clients']));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!$this->scheduler->parameters['include_project_tasks']) {
|
||||||
|
// $query->whereNull('project_id');
|
||||||
|
// }
|
||||||
|
$query = Client::query()
|
||||||
|
->where('company_id', $this->scheduler->company_id)
|
||||||
|
->where('is_deleted', 0);
|
||||||
|
|
||||||
|
if (count($this->scheduler->parameters['clients']) >= 1) {
|
||||||
|
$query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->whereHas('tasks', function ($sub_query){
|
||||||
|
$sub_query->whereNull('invoice_id')
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->whereBetween('calculated_start_date', $this->calculateStartAndEndDates())
|
||||||
|
->when(!$this->scheduler->parameters['include_project_tasks'], function ($sub_query_two){
|
||||||
|
$sub_query_two->whereNull('project_id');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$invoice_repo = new InvoiceRepository();
|
||||||
|
|
||||||
|
$query->cursor()
|
||||||
|
->each(function (Client $client) use ($invoice_repo) {
|
||||||
|
|
||||||
|
$line_items = $client->tasks()->whereNull('invoice_id')
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->whereBetween('calculated_start_date', $this->calculateStartAndEndDates())
|
||||||
|
->when(!$this->scheduler->parameters['include_project_tasks'], function ($sub_query_two){
|
||||||
|
return $sub_query_two->whereNull('project_id');
|
||||||
|
})
|
||||||
|
->get()
|
||||||
|
->filter(function (Task $task){
|
||||||
|
return $task->calcDuration(true) > 0 && !$task->isRunning();
|
||||||
|
})
|
||||||
|
->map(function (Task $task, $key){
|
||||||
|
|
||||||
|
if ($key == 0 && $task->company->invoice_task_project) {
|
||||||
|
$body = '<div class="project-header">'.$task->project->name.'</div>' .$task->project?->public_notes ?? ''; //@phpstan-ignore-line
|
||||||
|
$body .= '<div class="task-time-details">'.$task->description().'</div>';
|
||||||
|
} elseif (!$task->company->invoice_task_hours && !$task->company->invoice_task_timelog && !$task->company->invoice_task_datelog && !$task->company->invoice_task_item_description) {
|
||||||
|
$body = $task->description ?? '';
|
||||||
|
} else {
|
||||||
|
$body = '<div class="task-time-details">'.$task->description().'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->quantity = $task->getQuantity();
|
||||||
|
$item->cost = $task->getRate();
|
||||||
|
$item->product_key = '';
|
||||||
|
$item->notes = $body;
|
||||||
|
$item->task_id = $task->hashed_id;
|
||||||
|
$item->tax_id = (string) Product::PRODUCT_TYPE_SERVICE;
|
||||||
|
$item->type_id = '2';
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'date' => now()->addSeconds($client->company->utc_offset())->format('Y-m-d'),
|
||||||
|
'line_items' => array_values($line_items),
|
||||||
|
'uses_inclusive_taxes' => $client->company->settings->inclusive_taxes ?? false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$invoice = $invoice_repo->save($data, InvoiceFactory::create($client->company_id, $client->user_id));
|
||||||
|
|
||||||
|
if($this->scheduler->parameters['auto_send']){
|
||||||
|
nlog('sending email');
|
||||||
|
$invoice->service()->sendEmail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->scheduler->calculateNextRun();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start and end date of the statement
|
||||||
|
*
|
||||||
|
* @return array [$start_date, $end_date];
|
||||||
|
*/
|
||||||
|
private function calculateStartAndEndDates(): array
|
||||||
|
{
|
||||||
|
return match ($this->scheduler->parameters['date_range']) {
|
||||||
|
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
|
||||||
|
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
|
||||||
|
EmailStatement::LAST365 => [now()->startOfDay()->subDays(365)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
|
||||||
|
EmailStatement::THIS_MONTH => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
|
||||||
|
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
|
||||||
|
EmailStatement::THIS_QUARTER => [now()->startOfDay()->startOfQuarter()->format('Y-m-d'), now()->startOfDay()->endOfQuarter()->format('Y-m-d')],
|
||||||
|
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->startOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->endOfQuarter()->format('Y-m-d')],
|
||||||
|
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
|
||||||
|
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
|
||||||
|
EmailStatement::ALL_TIME => [
|
||||||
|
$client->tasks()->selectRaw('MIN(tasks.calculated_start_date) as start_date')->pluck('start_date')->first()
|
||||||
|
?: Carbon::now()->format('Y-m-d'),
|
||||||
|
Carbon::now()->format('Y-m-d')
|
||||||
|
],
|
||||||
|
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
|
||||||
|
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,6 @@ namespace App\Services\Scheduler;
|
||||||
use App\Models\Scheduler;
|
use App\Models\Scheduler;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
|
|
||||||
class SchedulerService
|
class SchedulerService
|
||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
|
|
@ -52,6 +51,11 @@ class SchedulerService
|
||||||
(new EmailReport($this->scheduler))->run();
|
(new EmailReport($this->scheduler))->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function invoice_outstanding_tasks()
|
||||||
|
{
|
||||||
|
(new InvoiceOutstandingTasksService($this->scheduler))->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the next run date of the scheduled task
|
* Sets the next run date of the scheduled task
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,24 @@
|
||||||
|
|
||||||
namespace Tests\Feature\Scheduler;
|
namespace Tests\Feature\Scheduler;
|
||||||
|
|
||||||
use App\DataMapper\Schedule\EmailStatement;
|
|
||||||
use App\Factory\SchedulerFactory;
|
|
||||||
use App\Models\Client;
|
|
||||||
use App\Models\RecurringInvoice;
|
|
||||||
use App\Models\Scheduler;
|
|
||||||
use App\Services\Scheduler\EmailReport;
|
|
||||||
use App\Services\Scheduler\EmailStatementService;
|
|
||||||
use App\Utils\Traits\MakesHash;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
|
||||||
use Illuminate\Support\Facades\Session;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Tests\MockAccountData;
|
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
use App\Models\Task;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\Scheduler;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
|
use App\Factory\SchedulerFactory;
|
||||||
|
use App\Services\Scheduler\EmailReport;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use App\DataMapper\Schedule\EmailStatement;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use App\Services\Scheduler\EmailStatementService;
|
||||||
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use App\Services\Scheduler\InvoiceOutstandingTasksService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -59,6 +61,58 @@ class SchedulerTest extends TestCase
|
||||||
// $this->withoutExceptionHandling();
|
// $this->withoutExceptionHandling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInvoiceOutstandingTasks()
|
||||||
|
{
|
||||||
|
|
||||||
|
$start = now()->subMonth()->addDays(1)->timestamp;
|
||||||
|
$end = now()->subMonth()->addDays(5)->timestamp;
|
||||||
|
|
||||||
|
Task::factory()->count(10)->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'client_id' => $this->client->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'description' => 'Test task',
|
||||||
|
'time_log' => '[['.$start.','.$end.',null,false]]',
|
||||||
|
'rate' => 100,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => 'A test invoice outstanding tasks scheduler',
|
||||||
|
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
|
||||||
|
'next_run' => now()->format('Y-m-d'),
|
||||||
|
'template' => 'invoice_outstanding_tasks',
|
||||||
|
'parameters' => [
|
||||||
|
'clients' => [],
|
||||||
|
'include_project_tasks' => true,
|
||||||
|
'auto_send' => true,
|
||||||
|
'date_range' => 'last_month',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = false;
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $this->token,
|
||||||
|
])->postJson('/api/v1/task_schedulers', $data);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
|
||||||
|
$id = $this->decodePrimaryKey($arr['data']['id']);
|
||||||
|
$scheduler = Scheduler::find($id);
|
||||||
|
$user = $scheduler->user;
|
||||||
|
$user->email = "{rand(5,555555}@gmail.com";
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$this->assertNotNull($scheduler);
|
||||||
|
|
||||||
|
$export = (new InvoiceOutstandingTasksService($scheduler))->run();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testReportValidationRulesForStartAndEndDate()
|
public function testReportValidationRulesForStartAndEndDate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue