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
f09ea9c0d3
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -50,7 +50,8 @@ jobs:
|
||||
cp -r dist/react/* ../public/react
|
||||
cd ..
|
||||
rm -rf ui
|
||||
|
||||
php artisan ninja:react
|
||||
|
||||
- name: Prepare JS/CSS assets
|
||||
run: |
|
||||
npm i
|
||||
|
@ -1 +1 @@
|
||||
5.3.92
|
||||
5.3.94
|
@ -866,6 +866,7 @@ class CheckData extends Command
|
||||
|
||||
foreach(Invoice::with(['payments'])->whereHas('payments')->where('status_id', 4)->where('balance', '>', 0)->where('is_deleted',0)->cursor() as $invoice)
|
||||
{
|
||||
$this->$this->wrong_paid_status++;
|
||||
|
||||
$this->logMessage("# {$invoice->id} " . ' - '.$invoice->number." - Marked as paid, but balance = {$invoice->balance}");
|
||||
|
||||
|
88
app/Console/Commands/ReactBuilder.php
Normal file
88
app/Console/Commands/ReactBuilder.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?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\Console\Commands;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Design;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use stdClass;
|
||||
|
||||
class ReactBuilder extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:react';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Builds blade component for react includes';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$includes = '';
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator(public_path('react'), \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
|
||||
foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) {
|
||||
|
||||
if(str_contains($file->getFileName(), '.js')) {
|
||||
|
||||
if(str_contains($file->getFileName(), 'index.')){
|
||||
|
||||
$includes .= '<script type="module" crossorigin src="/react/' . $file->getFileName() . '"></script>'."\n";
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
$includes .= '<link rel="modulepreload" href="/react/' . $file->getFileName() . '">'."\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(str_contains($file->getFileName(), '.css')) {
|
||||
|
||||
$includes .= '<link rel="stylesheet" href="/react/' . $file->getFileName() . '">'."\n";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
file_put_contents(resource_path('views/react/head.blade.php'), $includes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Ninja\QueueSize;
|
||||
use App\Jobs\Ninja\SystemMaintenance;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Util\DiskCleanup;
|
||||
use App\Jobs\Util\ReminderJob;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
@ -69,19 +70,20 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new TaskScheduler())->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
|
||||
|
||||
if(Ninja::isSelfHost())
|
||||
{
|
||||
|
||||
$schedule->call(function () {
|
||||
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
|
||||
})->everyFiveMinutes();
|
||||
|
||||
})->everyFiveMinutes();
|
||||
|
||||
}
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
@ -99,12 +101,12 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
}
|
||||
|
||||
if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
|
||||
if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
|
||||
|
||||
$schedule->command('queue:work database --stop-when-empty')->everyMinute()->withoutOverlapping();
|
||||
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -116,7 +118,7 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
56
app/Factory/PurchaseOrderFactory.php
Normal file
56
app/Factory/PurchaseOrderFactory.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?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\Factory;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PurchaseOrderFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id, object $settings = null, Client $client = null) :PurchaseOrder
|
||||
{
|
||||
$purchase_order = new PurchaseOrder();
|
||||
$purchase_order->status_id = PurchaseOrder::STATUS_DRAFT;
|
||||
$purchase_order->number = null;
|
||||
$purchase_order->discount = 0;
|
||||
$purchase_order->is_amount_discount = true;
|
||||
$purchase_order->po_number = '';
|
||||
$purchase_order->footer = '';
|
||||
$purchase_order->terms = '';
|
||||
$purchase_order->public_notes = '';
|
||||
$purchase_order->private_notes = '';
|
||||
$purchase_order->date = now()->format('Y-m-d');
|
||||
$purchase_order->due_date = null;
|
||||
$purchase_order->partial_due_date = null;
|
||||
$purchase_order->is_deleted = false;
|
||||
$purchase_order->line_items = json_encode([]);
|
||||
$purchase_order->tax_name1 = '';
|
||||
$purchase_order->tax_rate1 = 0;
|
||||
$purchase_order->tax_name2 = '';
|
||||
$purchase_order->tax_rate2 = 0;
|
||||
$purchase_order->tax_name3 = '';
|
||||
$purchase_order->tax_rate3 = 0;
|
||||
$purchase_order->custom_value1 = '';
|
||||
$purchase_order->custom_value2 = '';
|
||||
$purchase_order->custom_value3 = '';
|
||||
$purchase_order->custom_value4 = '';
|
||||
$purchase_order->amount = 0;
|
||||
$purchase_order->balance = 0;
|
||||
$purchase_order->partial = 0;
|
||||
$purchase_order->user_id = $user_id;
|
||||
$purchase_order->company_id = $company_id;
|
||||
$purchase_order->recurring_id = null;
|
||||
|
||||
return $purchase_order;
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ class RecurringInvoiceFactory
|
||||
$invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$invoice->last_sent_date = null;
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->next_send_date_client = null;
|
||||
$invoice->remaining_cycles = -1;
|
||||
$invoice->paid_to_date = 0;
|
||||
$invoice->auto_bill_enabled = false;
|
||||
|
@ -49,7 +49,7 @@ class CreditFilters extends QueryFilters
|
||||
}
|
||||
|
||||
if (in_array('partial', $status_parameters)) {
|
||||
$this->builder->where('status_id', Credit::STAUTS_PARTIAL);
|
||||
$this->builder->where('status_id', Credit::STATUS_PARTIAL);
|
||||
}
|
||||
|
||||
if (in_array('applied', $status_parameters)) {
|
||||
|
185
app/Filters/PurchaseOrderFilters.php
Normal file
185
app/Filters/PurchaseOrderFilters.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?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\Filters;
|
||||
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PurchaseOrderFilters extends QueryFilters
|
||||
{
|
||||
/**
|
||||
* Filter based on client status.
|
||||
*
|
||||
* Statuses we need to handle
|
||||
* - all
|
||||
* - paid
|
||||
* - unpaid
|
||||
* - overdue
|
||||
* - reversed
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function credit_status(string $value = '') :Builder
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$status_parameters = explode(',', $value);
|
||||
|
||||
if (in_array('all', $status_parameters)) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (in_array('draft', $status_parameters)) {
|
||||
$this->builder->where('status_id', PurchaseOrder::STATUS_DRAFT);
|
||||
}
|
||||
|
||||
if (in_array('partial', $status_parameters)) {
|
||||
$this->builder->where('status_id', PurchaseOrder::STATUS_PARTIAL);
|
||||
}
|
||||
|
||||
if (in_array('applied', $status_parameters)) {
|
||||
$this->builder->where('status_id', PurchaseOrder::STATUS_APPLIED);
|
||||
}
|
||||
|
||||
//->where('due_date', '>', Carbon::now())
|
||||
//->orWhere('partial_due_date', '>', Carbon::now());
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
* @param string query filter
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function filter(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('purchase_orders.number', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.number', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.date', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.amount', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.balance', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.custom_value3', 'like', '%'.$filter.'%')
|
||||
->orWhere('purchase_orders.custom_value4', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
* archived, active, deleted - legacy from V1.
|
||||
*
|
||||
* @param string filter
|
||||
* @return Builder
|
||||
*/
|
||||
public function status(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$table = 'purchase_orders';
|
||||
$filters = explode(',', $filter);
|
||||
|
||||
return $this->builder->where(function ($query) use ($filters, $table) {
|
||||
$query->whereNull($table.'.id');
|
||||
|
||||
if (in_array(parent::STATUS_ACTIVE, $filters)) {
|
||||
$query->orWhereNull($table.'.deleted_at');
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
|
||||
$query->orWhere(function ($query) use ($table) {
|
||||
$query->whereNotNull($table.'.deleted_at');
|
||||
|
||||
if (! in_array($table, ['users'])) {
|
||||
$query->where($table.'.is_deleted', '=', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_DELETED, $filters)) {
|
||||
$query->orWhere($table.'.is_deleted', '=', 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
* @param string sort formatted as column|asc
|
||||
* @return Builder
|
||||
*/
|
||||
public function sort(string $sort) : Builder
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base query.
|
||||
*
|
||||
* @param int company_id
|
||||
* @param User $user
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function baseQuery(int $company_id, User $user) : Builder
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query by the users company ID.
|
||||
*
|
||||
* We need to ensure we are using the correct company ID
|
||||
* as we could be hitting this from either the client or company auth guard
|
||||
*
|
||||
*/
|
||||
public function entityFilter()
|
||||
{
|
||||
if (auth()->guard('contact')->user()) {
|
||||
return $this->contactViewFilter();
|
||||
} else {
|
||||
return $this->builder->company();
|
||||
}
|
||||
|
||||
// return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need additional filters when showing purchase orders for the
|
||||
* client portal. Need to automatically exclude drafts and cancelled purchase orders.
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
private function contactViewFilter() : Builder
|
||||
{
|
||||
return $this->builder
|
||||
->whereCompanyId(auth()->guard('contact')->user()->company->id)
|
||||
->whereNotIn('status_id', [PurchaseOrder::STATUS_DRAFT]);
|
||||
}
|
||||
}
|
@ -81,7 +81,7 @@ abstract class QueryFilters
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strlen($value)) {
|
||||
if (is_string($value) && strlen($value)) {
|
||||
$this->$name($value);
|
||||
} else {
|
||||
$this->$name();
|
||||
|
@ -91,6 +91,33 @@ class ActivityController extends BaseController
|
||||
$activities = Activity::orderBy('created_at', 'DESC')->company()
|
||||
->take($default_activities);
|
||||
|
||||
if($request->has('react')){
|
||||
|
||||
$system = ctrans('texts.system');
|
||||
|
||||
$data = $activities->cursor()->map(function ($activity) use($system){
|
||||
|
||||
$arr=
|
||||
[
|
||||
'client' => $activity->client ? $activity->client : '',
|
||||
'contact' => $activity->contact ? $activity->contact : '',
|
||||
'quote' => $activity->quote ? $activity->quote : '',
|
||||
'user' => $activity->user ? $activity->user : '',
|
||||
'expense' => $activity->expense ? $activity->expense : '',
|
||||
'invoice' => $activity->invoice ? $activity->invoice : '',
|
||||
'recurring_invoice' => $activity->recurring_invoice ? $activity->recurring_invoice : '',
|
||||
'payment' => $activity->payment ? $activity->payment : '',
|
||||
'credit' => $activity->credit ? $activity->credit : '',
|
||||
'task' => $activity->task ? $activity->task : '',
|
||||
];
|
||||
|
||||
return array_merge($arr, $activity->toArray());
|
||||
|
||||
});
|
||||
|
||||
return response()->json(['data' => $data->toArray()], 200);
|
||||
}
|
||||
|
||||
return $this->listResponse($activities);
|
||||
}
|
||||
|
||||
|
@ -780,7 +780,10 @@ class BaseController extends Controller
|
||||
|
||||
$this->buildCache();
|
||||
|
||||
return response()->view('index.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
|
||||
if(config('ninja.react_app_enabled'))
|
||||
return response()->view('react.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
|
||||
else
|
||||
return response()->view('index.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
|
||||
|
||||
}
|
||||
|
||||
|
@ -125,13 +125,14 @@ class NinjaPlanController extends Controller
|
||||
$gateway_driver->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]);
|
||||
|
||||
//set free trial
|
||||
// $account = auth()->guard('contact')->user()->company->account;
|
||||
if(auth()->guard('contact')->user()->client->custom_value2){
|
||||
MultiDB::findAndSetDbByAccountKey(auth()->guard('contact')->user()->client->custom_value2);
|
||||
$account = Account::where('key', auth()->guard('contact')->user()->client->custom_value2)->first();
|
||||
$account->trial_started = now();
|
||||
$account->trial_plan = 'pro';
|
||||
// $account->trial_started = now();
|
||||
// $account->trial_plan = 'pro';
|
||||
$account->plan = 'pro';
|
||||
$account->plan_term = 'month';
|
||||
$account->plan_started = now();
|
||||
$account->plan_expires = now()->addDays(14);
|
||||
$account->save();
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest;
|
||||
use App\Jobs\Util\ApplePayDomain;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
|
||||
use App\Repositories\CompanyRepository;
|
||||
use App\Transformers\CompanyGatewayTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -212,6 +213,11 @@ class CompanyGatewayController extends BaseController
|
||||
|
||||
ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db);
|
||||
|
||||
if(in_array($company_gateway->gateway_key, $this->stripe_keys))
|
||||
{
|
||||
StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id);
|
||||
}
|
||||
|
||||
return $this->itemResponse($company_gateway);
|
||||
}
|
||||
|
||||
|
56
app/Http/Controllers/OpenAPI/TaskSchedulerSchema.php
Normal file
56
app/Http/Controllers/OpenAPI/TaskSchedulerSchema.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="TaskSchedulerSchema",
|
||||
* type="object",
|
||||
*
|
||||
*
|
||||
* @OA\Property(property="paused",type="boolean",example="false",description="The scheduler paused state"),
|
||||
* @OA\Property(property="repeat_every",type="string",example="DAY",description="Accepted values (DAY,WEEK,MONTH,3MONTHS,YEAR)"),
|
||||
* @OA\Property(property="start_from",type="integer",example="1652898504",description="Timestamp when we should start the scheduler, default is today"),
|
||||
* @OA\Property(property="job",type="string",example="create_credit_report",description="Job, we can find list of available jobs in Scheduler model"),
|
||||
* @OA\Property(property="date_range", type="string", example="last7", description="The string representation of the date range of data to be returned"),
|
||||
* @OA\Property(property="date_key", type="string", example="created_at", description="The date column to search between."),
|
||||
* @OA\Property(property="start_date", type="string", example="2022-10-31", description="The start date to search between"),
|
||||
* @OA\Property(property="end_date", type="string", example="2022-10-31", description="The end date to search between"),
|
||||
* @OA\Property(
|
||||
* property="report_keys",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="string",
|
||||
* description="Array of Keys to export",
|
||||
* example="['name','date']",
|
||||
* ),
|
||||
* ),
|
||||
*
|
||||
* )
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="UpdateTaskSchedulerSchema",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="paused",type="boolean",example="false",description="The scheduler paused state"),
|
||||
* @OA\Property(property="repeat_every",type="string",example="DAY",description="Accepted values (DAY,WEEK,MONTH,3MONTHS,YEAR)"),
|
||||
* @OA\Property(property="start_from",type="integer",example="1652898504",description="Timestamp when we should start the scheduler, default is today"),
|
||||
* @OA\Property(property="job",type="string",example="create_credit_report",description="Job, we can find list of available jobs in Scheduler model"),
|
||||
* @OA\Property(property="date_range", type="string", example="last7", description="The string representation of the date range of data to be returned"),
|
||||
* @OA\Property(property="date_key", type="string", example="created_at", description="The date column to search between."),
|
||||
* @OA\Property(property="start_date", type="string", example="2022-10-31", description="The start date to search between"),
|
||||
* @OA\Property(property="end_date", type="string", example="2022-10-31", description="The end date to search between"),
|
||||
* )
|
||||
*/
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="UpdateJobForASchedulerSchema",
|
||||
* type="object",
|
||||
* @OA\Property(property="job",type="string",example="create_client_report",description="Set action name, action names can be found in Scheduler Model"),
|
||||
*
|
||||
* )
|
||||
*/
|
||||
|
413
app/Http/Controllers/PurchaseOrderController.php
Normal file
413
app/Http/Controllers/PurchaseOrderController.php
Normal file
@ -0,0 +1,413 @@
|
||||
<?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\Controllers;
|
||||
|
||||
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
use App\Filters\PurchaseOrderFilters;
|
||||
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class PurchaseOrderController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = PurchaseOrder::class;
|
||||
protected $entity_transformer = PurchaseOrderTransformer::class;
|
||||
protected $purchase_order_repository;
|
||||
|
||||
public function __construct(PurchaseOrderRepository $purchase_order_repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->purchase_order_repository = $purchase_order_repository;
|
||||
}
|
||||
/**
|
||||
* Show the list of Purchase Orders.
|
||||
*
|
||||
* @param \App\Filters\PurchaseOrderFilters $filters The filters
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/purchase_orders",
|
||||
* operationId="getPurchaseOrders",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Gets a list of purchase orders",
|
||||
* description="Lists purchase orders, search and filters allow fine grained lists to be generated.
|
||||
*
|
||||
* Query parameters can be added to performed more fine grained filtering of the purchase orders, these are handled by the PurchaseOrderFilters class which defines the methods available",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A list of purchase orders",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function index(PurchaseOrderFilters $filters)
|
||||
{
|
||||
$purchase_orders = PurchaseOrder::filter($filters);
|
||||
|
||||
return $this->listResponse($purchase_orders);
|
||||
}
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param CreatePurchaseOrderRequest $request The request
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/purchase_orders/create",
|
||||
* operationId="getPurchaseOrderCreate",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Gets a new blank purchase order object",
|
||||
* description="Returns a blank object with default values",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A blank purchase order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function create(CreatePurchaseOrderRequest $request)
|
||||
{
|
||||
$purchase_order = PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StorePurchaseOrderRequest $request The request
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/purchase_orders",
|
||||
* operationId="storePurchaseOrder",
|
||||
* tags={"purhcase_orders"},
|
||||
* summary="Adds a purchase order",
|
||||
* description="Adds an purchase order to the system",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved purchase order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function store(StorePurchaseOrderRequest $request)
|
||||
{
|
||||
|
||||
$client = Client::find($request->get('client_id'));
|
||||
|
||||
$purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$purchase_order = $purchase_order->service()
|
||||
->fillDefaults()
|
||||
->save();
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param ShowPurchaseOrderRequest $request The request
|
||||
* @param PurchaseOrder $purchase_order The purchase order
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/purchase_orders/{id}",
|
||||
* operationId="showPurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Shows an purcase orders",
|
||||
* description="Displays an purchase order by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Purchase order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the purchase order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function show(ShowPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param EditPurchaseOrderRequest $request The request
|
||||
* @param PurchaseOrder $purchase_order The purchase order
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/purchase_orders/{id}/edit",
|
||||
* operationId="editPurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Shows an purchase order for editting",
|
||||
* description="Displays an purchase order by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The purchase order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the purchase order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Invoice"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function edit(EditPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdatePurchaseOrderRequest $request The request
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
* @OA\Put(
|
||||
* path="/api/v1/purchase_order/{id}",
|
||||
* operationId="updatePurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Updates an purchase order",
|
||||
* description="Handles the updating of an purchase order by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The purchase order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the purchase order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function update(UpdatePurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
if ($request->entityIsDeleted($purchase_order)) {
|
||||
return $request->disallowUpdate();
|
||||
}
|
||||
|
||||
$purchase_order = $this->purchase_order_repository->save($request->all(), $purchase_order);
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param DestroyPurchaseOrderRequest $request
|
||||
* @param PurchaseOrder $purchase_order
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Exception
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/purchase_orders/{id}",
|
||||
* operationId="deletePurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Deletes a purchase order",
|
||||
* description="Handles the deletion of an purchase orders by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The purhcase order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a HTTP status",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function destroy(DestroyPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order_repository->delete($purchase_order);
|
||||
|
||||
return $this->itemResponse($purchase_order->fresh());
|
||||
}
|
||||
}
|
@ -204,9 +204,9 @@ class RecurringInvoiceController extends BaseController
|
||||
{
|
||||
$recurring_invoice = $this->recurring_invoice_repo->save($request->all(), RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$offset = $recurring_invoice->client->timezone_offset();
|
||||
$recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
|
||||
$recurring_invoice->saveQuietly();
|
||||
// $offset = $recurring_invoice->client->timezone_offset();
|
||||
// $recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
|
||||
// $recurring_invoice->saveQuietly();
|
||||
|
||||
$recurring_invoice->service()
|
||||
->triggeredActions($request)
|
||||
|
@ -31,11 +31,11 @@ class CreditReportController extends BaseController
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/reports/clients",
|
||||
* operationId="getClientReport",
|
||||
* path="/api/v1/reports/credit",
|
||||
* operationId="getCreditReport",
|
||||
* tags={"reports"},
|
||||
* summary="Client reports",
|
||||
* description="Export client reports",
|
||||
* summary="Credit reports",
|
||||
* description="Export credit reports",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
|
@ -31,11 +31,11 @@ class QuoteItemReportController extends BaseController
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/reports/invoice_items",
|
||||
* path="/api/v1/reports/quote_items",
|
||||
* operationId="getQuoteItemReport",
|
||||
* tags={"reports"},
|
||||
* summary="Invoice item reports",
|
||||
* description="Export invoice item reports",
|
||||
* summary="Quote item reports",
|
||||
* description="Export Quote item reports",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
|
@ -16,6 +16,7 @@ use App\Models\Client;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\ClientGroupSettingsSaver;
|
||||
use Beganovich\Snappdf\Snappdf;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -134,6 +135,15 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
nlog("Extracting zip");
|
||||
|
||||
try{
|
||||
$s = new Snappdf;
|
||||
$s->getChromiumPath();
|
||||
chmod($this->generatePlatformExecutable($s->getChromiumPath()), 0755);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("I could not set the file permissions for chrome");
|
||||
}
|
||||
|
||||
// $zipFile = new \PhpZip\ZipFile();
|
||||
|
||||
// $zipFile->openFile($file);
|
||||
|
243
app/Http/Controllers/TaskSchedulerController.php
Normal file
243
app/Http/Controllers/TaskSchedulerController.php
Normal file
@ -0,0 +1,243 @@
|
||||
<?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\Controllers;
|
||||
|
||||
use App\Http\Requests\TaskScheduler\CreateScheduledTaskRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduleRequest;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Report\ProfitAndLoss;
|
||||
use App\Models\Scheduler;
|
||||
use App\Repositories\TaskSchedulerRepository;
|
||||
use App\Transformers\TaskSchedulerTransformer;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class TaskSchedulerController extends BaseController
|
||||
{
|
||||
protected $entity_type = Scheduler::class;
|
||||
protected $entity_transformer = TaskSchedulerTransformer::class;
|
||||
protected TaskSchedulerRepository $scheduler_repository;
|
||||
|
||||
public function __construct(TaskSchedulerRepository $scheduler_repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->scheduler_repository = $scheduler_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\GET(
|
||||
* path="/api/v1/task_scheduler/",
|
||||
* operationId="getTaskSchedulers",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Task Scheduler Index",
|
||||
* description="Get all schedulers with associated jobs",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function index()
|
||||
{
|
||||
|
||||
$schedulers = Scheduler::where('company_id', auth()->user()->company()->id);
|
||||
|
||||
return $this->listResponse($schedulers);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/task_scheduler/",
|
||||
* operationId="createTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Create task scheduler with job ",
|
||||
* description="Create task scheduler with a job (action(job) request should be sent via request also. Example: We want client report to be job which will be run
|
||||
* multiple times, we should send the same parameters in the request as we would send if we wanted to get report, see example",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function store(CreateScheduledTaskRequest $request)
|
||||
{
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->service()->store($scheduler, $request);
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\GET(
|
||||
* path="/api/v1/task_scheduler/{id}",
|
||||
* operationId="showTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Show given scheduler",
|
||||
* description="Get scheduler with associated job",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Scheduler Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function show(Scheduler $scheduler)
|
||||
{
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\PUT(
|
||||
* path="/api/v1/task_scheduler/{id}",
|
||||
* operationId="updateTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Update task scheduler ",
|
||||
* description="Update task scheduler",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Scheduler Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ), * @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateTaskSchedulerSchema")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function update(Scheduler $scheduler, UpdateScheduleRequest $request)
|
||||
{
|
||||
$scheduler->service()->update($scheduler, $request);
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @OA\DELETE(
|
||||
* path="/api/v1/task_scheduler/{id}",
|
||||
* operationId="destroyTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Destroy Task Scheduler",
|
||||
* description="Destroy task scheduler and its associated job",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Scheduler Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function destroy(Scheduler $scheduler)
|
||||
{
|
||||
$this->scheduler_repository->delete($scheduler);
|
||||
return $this->itemResponse($scheduler->fresh());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\PurchaseOrder;
|
||||
|
||||
class CreatePurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', PurchaseOrder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class DestroyPurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
40
app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php
Normal file
40
app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class EditPurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->can('edit', $this->purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
40
app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php
Normal file
40
app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ShowPurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('view', $this->purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StorePurchaseOrderRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->can('create', PurchaseOrder::class);
|
||||
}
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = 'required';
|
||||
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?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\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdatePurchaseOrderRequest extends Request
|
||||
{
|
||||
use ChecksEntityStatus;
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->purchase_order);
|
||||
}
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)->ignore($this->purchase_order->id);
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
|
||||
$input['id'] = $this->purchase_order->id;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -55,6 +55,10 @@ class StoreRecurringExpenseRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
@ -66,6 +66,10 @@ class UpdateRecurringExpenseRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
@ -61,6 +61,10 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ class GenericReportRequest extends Request
|
||||
if(!array_key_exists('report_keys', $input))
|
||||
$input['report_keys'] = [];
|
||||
|
||||
if(!array_key_exists('send_email', $input))
|
||||
$input['send_email'] = true;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,6 @@ trait RuntimeFormRequest
|
||||
$instance = $validator->getValidatorInstance();
|
||||
|
||||
return $instance;
|
||||
// if ($instance->fails()) {
|
||||
// return $instance->errors();
|
||||
// }
|
||||
|
||||
// $validator->passedValidation();
|
||||
|
||||
// return $validator->all();
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Requests\TaskScheduler;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class CreateScheduledTaskRequest 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 [
|
||||
'paused' => 'sometimes|bool',
|
||||
'repeat_every' => 'required|string|in:DAY,WEEK,MONTH,3MONTHS,YEAR',
|
||||
'start_from' => 'sometimes|string',
|
||||
'job' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(!array_key_exists('start_from', $input))
|
||||
$input['start_from'] = now();
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
52
app/Http/Requests/TaskScheduler/UpdateScheduleRequest.php
Normal file
52
app/Http/Requests/TaskScheduler/UpdateScheduleRequest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?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\TaskScheduler;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateScheduleRequest 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(): array
|
||||
{
|
||||
return [
|
||||
'paused' => 'sometimes|bool',
|
||||
'repeat_every' => 'sometimes|string|in:DAY,WEEK,BIWEEKLY,MONTH,3MONTHS,YEAR',
|
||||
'start_from' => 'sometimes',
|
||||
'scheduled_run'=>'sometimes'
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (isset($input['start_from'])) {
|
||||
$input['scheduled_run'] = Carbon::parse((int)$input['start_from']);
|
||||
$input['start_from'] = Carbon::parse((int)$input['start_from']);
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Requests\TaskScheduler;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class UpdateScheduledJobRequest 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(): array
|
||||
{
|
||||
return [
|
||||
'action_name' => 'sometimes|string',
|
||||
];
|
||||
}
|
||||
}
|
@ -562,7 +562,7 @@ class BaseImport
|
||||
}
|
||||
}
|
||||
|
||||
protected function finalizeImport()
|
||||
public function finalizeImport()
|
||||
{
|
||||
$data = [
|
||||
'errors' => $this->error_array,
|
||||
|
@ -60,10 +60,7 @@ class Csv extends BaseImport implements ImportInterface
|
||||
) {
|
||||
$this->{$entity}();
|
||||
}
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -41,7 +41,7 @@ class Freshbooks extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -39,7 +39,7 @@ class Invoice2Go extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ class Invoicely extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -54,7 +54,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -40,7 +40,7 @@ class Zoho extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -94,6 +94,8 @@ class RecurringExpensesCron
|
||||
$expense->save();
|
||||
|
||||
$recurring_expense->next_send_date = $recurring_expense->nextSendDate();
|
||||
$recurring_expense->next_send_date_client = $recurring_expense->next_send_date;
|
||||
|
||||
$recurring_expense->remaining_cycles = $recurring_expense->remainingCycles();
|
||||
$recurring_expense->save();
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ class CSVIngest implements ShouldQueue {
|
||||
|
||||
}
|
||||
|
||||
$engine->finalizeImport();
|
||||
|
||||
$this->checkContacts();
|
||||
}
|
||||
|
||||
|
124
app/Jobs/Ninja/TaskScheduler.php
Normal file
124
app/Jobs/Ninja/TaskScheduler.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?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\Jobs\Report\SendToAdmin;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Scheduler;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TaskScheduler 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()
|
||||
{
|
||||
foreach (MultiDB::$dbs as $db)
|
||||
{
|
||||
|
||||
MultiDB::setDB($db);
|
||||
|
||||
Scheduler::with('company')
|
||||
->where('paused', false)
|
||||
->where('is_deleted', false)
|
||||
->where('scheduled_run', '<', now())
|
||||
->cursor()
|
||||
->each(function ($scheduler){
|
||||
$this->doJob($scheduler);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function doJob(Scheduler $scheduler)
|
||||
{
|
||||
nlog("Doing job {$scheduler->action_name}");
|
||||
|
||||
$company = $scheduler->company;
|
||||
|
||||
$parameters = $scheduler->parameters;
|
||||
|
||||
|
||||
switch ($scheduler->action_name) {
|
||||
case Scheduler::CREATE_CLIENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'contacts.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_CLIENT_CONTACT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'clients.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_CREDIT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'credits.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_DOCUMENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'documents.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_EXPENSE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'expense.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_INVOICE_ITEM_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'invoice_items.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_INVOICE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'invoices.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_PAYMENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'payments.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_PRODUCT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'products.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_PROFIT_AND_LOSS_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'profit_and_loss.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_QUOTE_ITEM_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'quote_items.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_QUOTE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'quotes.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_RECURRING_INVOICE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'recurring_invoices.csv');
|
||||
break;
|
||||
case Scheduler::CREATE_TASK_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $scheduler->action_class, 'tasks.csv');
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
$scheduler->scheduled_run = $scheduler->nextScheduledDate();
|
||||
$scheduler->save();
|
||||
}
|
||||
|
||||
}
|
@ -105,6 +105,7 @@ class SendRecurring implements ShouldQueue
|
||||
nlog("updating recurring invoice dates");
|
||||
/* Set next date here to prevent a recurring loop forming */
|
||||
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
|
||||
$this->recurring_invoice->next_send_date_client = $this->recurring_invoice->nextSendDateClient();
|
||||
$this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles();
|
||||
$this->recurring_invoice->last_sent_date = now();
|
||||
|
||||
|
@ -48,6 +48,7 @@ class SendToAdmin implements ShouldQueue
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
$export = new $this->report_class($this->company, $this->request);
|
||||
$csv = $export->run();
|
||||
|
@ -70,6 +70,7 @@ class Activity extends StaticModel
|
||||
const ARCHIVE_USER = 50;
|
||||
const DELETE_USER = 51;
|
||||
const RESTORE_USER = 52;
|
||||
|
||||
const MARK_SENT_INVOICE = 53; // not needed?
|
||||
const PAID_INVOICE = 54; //
|
||||
const EMAIL_INVOICE_FAILED = 57;
|
||||
@ -138,13 +139,11 @@ class Activity extends StaticModel
|
||||
return $this->hasOne(Backup::class);
|
||||
}
|
||||
|
||||
|
||||
public function history()
|
||||
{
|
||||
return $this->hasOne(Backup::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -177,6 +176,14 @@ class Activity extends StaticModel
|
||||
return $this->belongsTo(Invoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function recurring_invoice()
|
||||
{
|
||||
return $this->belongsTo(RecurringInvoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function credit()
|
||||
{
|
||||
return $this->belongsTo(Credit::class)->withTrashed();
|
||||
@ -198,15 +205,16 @@ class Activity extends StaticModel
|
||||
return $this->belongsTo(Payment::class)->withTrashed();
|
||||
}
|
||||
|
||||
// public function task()
|
||||
// {
|
||||
// return $this->belongsTo(Task::class)->withTrashed();
|
||||
// }
|
||||
public function expense()
|
||||
{
|
||||
return $this->belongsTo(Expense::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function task()
|
||||
{
|
||||
return $this->belongsTo(Task::class)->withTrashed();
|
||||
}
|
||||
|
||||
// public function expense()
|
||||
// {
|
||||
// return $this->belongsTo(Expense::class)->withTrashed();
|
||||
// }
|
||||
|
||||
public function company()
|
||||
{
|
||||
|
@ -667,6 +667,8 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
$offset -= $timezone->utc_offset;
|
||||
$offset += ($entity_send_time * 3600);
|
||||
|
||||
nlog("offset = {$offset}");
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
|
169
app/Models/PurchaseOrder.php
Normal file
169
app/Models/PurchaseOrder.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?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\Models;
|
||||
|
||||
|
||||
use App\Services\PurchaseOrder\PurchaseOrderService;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PurchaseOrder extends BaseModel
|
||||
{
|
||||
use Filterable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'number',
|
||||
'discount',
|
||||
'company_id',
|
||||
'status_id',
|
||||
'user_id',
|
||||
'last_sent_date',
|
||||
'is_deleted',
|
||||
'po_number',
|
||||
'date',
|
||||
'due_date',
|
||||
'terms',
|
||||
'public_notes',
|
||||
'private_notes',
|
||||
'tax_name1',
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'tax_name3',
|
||||
'tax_rate3',
|
||||
'total_taxes',
|
||||
'uses_inclusive_taxes',
|
||||
'is_amount_discount',
|
||||
'partial',
|
||||
'recurring_id',
|
||||
'next_send_date',
|
||||
'reminder1_sent',
|
||||
'reminder2_sent',
|
||||
'reminder3_sent',
|
||||
'reminder_last_sent',
|
||||
'partial_due_date',
|
||||
'project_id',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_value3',
|
||||
'custom_value4',
|
||||
'backup',
|
||||
'footer',
|
||||
'line_items',
|
||||
'client_id',
|
||||
'custom_surcharge1',
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
// 'custom_surcharge_tax1',
|
||||
// 'custom_surcharge_tax2',
|
||||
// 'custom_surcharge_tax3',
|
||||
// 'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'invoice_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
'balance',
|
||||
'partial',
|
||||
'paid_to_date',
|
||||
'subscription_id',
|
||||
'vendor_id',
|
||||
'last_viewed'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'line_items' => 'object',
|
||||
'backup' => 'object',
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'is_amount_discount' => 'bool',
|
||||
|
||||
];
|
||||
|
||||
const STATUS_DRAFT = 1;
|
||||
const STATUS_SENT = 2;
|
||||
const STATUS_PARTIAL = 3;
|
||||
const STATUS_APPLIED = 4;
|
||||
|
||||
public function assigned_user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();
|
||||
}
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function history()
|
||||
{
|
||||
return $this->hasManyThrough(Backup::class, Activity::class);
|
||||
}
|
||||
|
||||
public function activities()
|
||||
{
|
||||
return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class)->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
public function invitations()
|
||||
{
|
||||
return $this->hasMany(CreditInvitation::class);
|
||||
}
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function service()
|
||||
{
|
||||
return new PurchaseOrderService($this);
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->belongsToMany(Invoice::class)->using(Paymentable::class);
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->morphToMany(Payment::class, 'paymentable');
|
||||
}
|
||||
|
||||
public function documents()
|
||||
{
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
}
|
@ -63,6 +63,7 @@ class RecurringExpense extends BaseModel
|
||||
'last_sent_date',
|
||||
'next_send_date',
|
||||
'remaining_cycles',
|
||||
'next_send_date_client',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -153,6 +154,43 @@ class RecurringExpense extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function nextSendDateClient() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addDay();
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek();
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4);
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow();
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4);
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6);
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYear();
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function remainingCycles() : int
|
||||
{
|
||||
if ($this->remaining_cycles == 0) {
|
||||
|
@ -108,6 +108,7 @@ class RecurringInvoice extends BaseModel
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
'vendor_id',
|
||||
'next_send_date_client',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -224,7 +225,7 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
public function nextSendDate() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date) {
|
||||
if (!$this->next_send_date_client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -236,49 +237,93 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
/* 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');
|
||||
$this->next_send_date_client = 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
|
||||
*/
|
||||
if($offset < 0)
|
||||
$offset += 86400;
|
||||
// if($offset < 0)
|
||||
// $offset += 86400;
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay()->addSeconds($offset);
|
||||
case self::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4)->addSeconds($offset);
|
||||
case self::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
case self::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
|
||||
case self::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
|
||||
case self::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3)->addSeconds($offset);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextSendDateClient() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date_client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* 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_client = now()->format('Y-m-d');
|
||||
|
||||
}
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay();
|
||||
case self::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek();
|
||||
case self::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2);
|
||||
case self::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4);
|
||||
case self::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow();
|
||||
case self::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2);
|
||||
case self::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3);
|
||||
case self::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4);
|
||||
case self::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6);
|
||||
case self::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear();
|
||||
case self::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2);
|
||||
case self::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextDateByFrequency($date)
|
||||
{
|
||||
$offset = $this->client->timezone_offset();
|
||||
@ -463,11 +508,11 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
$data = [];
|
||||
|
||||
if (!Carbon::parse($this->next_send_date)) {
|
||||
if (!Carbon::parse($this->next_send_date_client)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$next_send_date = Carbon::parse($this->next_send_date)->copy();
|
||||
$next_send_date = Carbon::parse($this->next_send_date_client)->copy();
|
||||
|
||||
for ($x=0; $x<$iterations; $x++) {
|
||||
// we don't add the days... we calc the day of the month!!
|
||||
|
138
app/Models/Scheduler.php
Normal file
138
app/Models/Scheduler.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?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\Models;
|
||||
|
||||
use App\Services\TaskScheduler\TaskSchedulerService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property boolean paused
|
||||
* @property boolean is_deleted
|
||||
* @property \Carbon\Carbon|mixed start_from
|
||||
* @property string repeat_every
|
||||
* @property \Carbon\Carbon|mixed scheduled_run
|
||||
* @property integer company_id
|
||||
* @property integer updated_at
|
||||
* @property integer created_at
|
||||
* @property integer deleted_at
|
||||
* @property string action_name
|
||||
* @property mixed company
|
||||
* @property array parameters
|
||||
* @property string action_class
|
||||
*/
|
||||
class Scheduler extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'start_from',
|
||||
'paused',
|
||||
'repeat_every',
|
||||
'scheduled_run',
|
||||
'action_class',
|
||||
'action_name',
|
||||
'parameters',
|
||||
'company_id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_from' => 'timestamp',
|
||||
'scheduled_run' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'paused' => 'boolean',
|
||||
'is_deleted' => 'boolean',
|
||||
'parameters' => 'array',
|
||||
];
|
||||
|
||||
|
||||
const DAILY = 'DAY';
|
||||
const WEEKLY = 'WEEK';
|
||||
const BIWEEKLY = 'BIWEEKLY';
|
||||
const MONTHLY = 'MONTH';
|
||||
const QUARTERLY = '3MONTHS';
|
||||
const ANNUALLY = 'YEAR';
|
||||
|
||||
const CREATE_CLIENT_REPORT = 'create_client_report';
|
||||
const CREATE_CLIENT_CONTACT_REPORT = 'create_client_contact_report';
|
||||
const CREATE_CREDIT_REPORT = 'create_credit_report';
|
||||
const CREATE_DOCUMENT_REPORT = 'create_document_report';
|
||||
const CREATE_EXPENSE_REPORT = 'create_expense_report';
|
||||
const CREATE_INVOICE_ITEM_REPORT = 'create_invoice_item_report';
|
||||
const CREATE_INVOICE_REPORT = 'create_invoice_report';
|
||||
const CREATE_PAYMENT_REPORT = 'create_payment_report';
|
||||
const CREATE_PRODUCT_REPORT = 'create_product_report';
|
||||
const CREATE_PROFIT_AND_LOSS_REPORT = 'create_profit_and_loss_report';
|
||||
const CREATE_QUOTE_ITEM_REPORT = 'create_quote_item_report';
|
||||
const CREATE_QUOTE_REPORT = 'create_quote_report';
|
||||
const CREATE_RECURRING_INVOICE_REPORT = 'create_recurring_invoice_report';
|
||||
const CREATE_TASK_REPORT = 'create_task_report';
|
||||
|
||||
|
||||
/**
|
||||
* Service entry points.
|
||||
*/
|
||||
public function service(): TaskSchedulerService
|
||||
{
|
||||
return new TaskSchedulerService($this);
|
||||
}
|
||||
|
||||
|
||||
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
|
||||
public function nextScheduledDate(): ?Carbon
|
||||
{
|
||||
|
||||
$offset = 0;
|
||||
|
||||
$entity_send_time = $this->company->settings->entity_send_time;
|
||||
|
||||
if ($entity_send_time != 0) {
|
||||
$timezone = $this->company->timezone();
|
||||
|
||||
$offset -= $timezone->utc_offset;
|
||||
$offset += ($entity_send_time * 3600);
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
if ($offset < 0)
|
||||
$offset += 86400;
|
||||
|
||||
switch ($this->repeat_every) {
|
||||
case self::DAILY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addDay()->addSeconds($offset);
|
||||
case self::WEEKLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
case self::BIWEEKLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
case self::MONTHLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
case self::QUARTERLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
case self::ANNUALLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addYearNoOverflow()->addSeconds($offset);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
72
app/Observers/PurchaseOrderObserver.php
Normal file
72
app/Observers/PurchaseOrderObserver.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?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\Observers;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
|
||||
class PurchaseOrderObserver
|
||||
{
|
||||
/**
|
||||
* Handle the client "created" event.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return void
|
||||
*/
|
||||
public function created(PurchaseOrder $purchase_order)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the client "updated" event.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return void
|
||||
*/
|
||||
public function updated(PurchaseOrder $purchase_order)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the client "deleted" event.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(PurchaseOrder $purchase_order)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the client "restored" event.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return void
|
||||
*/
|
||||
public function restored(PurchaseOrder $purchase_order)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the client "force deleted" event.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(PurchaseOrder $purchase_order)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -12,11 +12,14 @@
|
||||
|
||||
namespace App\PaymentDrivers\Authorize;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\PaymentDrivers\AuthorizePaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use net\authorize\api\contract\v1\CreateTransactionRequest;
|
||||
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
|
||||
use net\authorize\api\contract\v1\OrderType;
|
||||
use net\authorize\api\contract\v1\PaymentProfileType;
|
||||
use net\authorize\api\contract\v1\ExtendedAmountType;
|
||||
use net\authorize\api\contract\v1\TransactionRequestType;
|
||||
use net\authorize\api\controller\CreateTransactionController;
|
||||
|
||||
@ -25,6 +28,8 @@ use net\authorize\api\controller\CreateTransactionController;
|
||||
*/
|
||||
class ChargePaymentProfile
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __construct(AuthorizePaymentDriver $authorize)
|
||||
{
|
||||
$this->authorize = $authorize;
|
||||
@ -44,19 +49,40 @@ class ChargePaymentProfile
|
||||
$profileToCharge->setPaymentProfile($paymentProfile);
|
||||
|
||||
$invoice_numbers = '';
|
||||
$taxAmount = 0;
|
||||
$invoiceTotal = 0;
|
||||
$invoiceTaxes = 0;
|
||||
|
||||
if($this->authorize->payment_hash->data)
|
||||
$invoice_numbers = collect($this->authorize->payment_hash->data->invoices)->pluck('invoice_number')->implode(',');
|
||||
if($this->authorize->payment_hash->data) {
|
||||
$invoice_numbers = collect($this->authorize->payment_hash->data->invoices)->pluck('invoice_number')->implode(",");
|
||||
$invObj = Invoice::whereIn('id', $this->transformKeys(array_column($this->authorize->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
$invoiceTotal = round($invObj->pluck('amount')->sum(), 2);
|
||||
$invoiceTaxes = round($invObj->pluck('total_taxes')->sum(), 2);
|
||||
|
||||
if ($invoiceTotal != $amount) {
|
||||
$taxRatio = $amount/$invoiceTotal;
|
||||
$taxAmount = round($invoiceTaxes*$taxRatio, 2);
|
||||
} else {
|
||||
$taxAmount = $invoiceTaxes;
|
||||
}
|
||||
}
|
||||
|
||||
$description = "Invoices: {$invoice_numbers} for {$amount} for client {$this->authorize->client->present()->name()}";
|
||||
|
||||
$order = new OrderType();
|
||||
$order->setInvoiceNumber(substr($invoice_numbers,0,19));
|
||||
$order->setDescription(substr($description,0,255));
|
||||
|
||||
$tax = new ExtendedAmountType();
|
||||
$tax->setName('tax');
|
||||
$tax->setAmount($taxAmount);
|
||||
|
||||
$transactionRequestType = new TransactionRequestType();
|
||||
$transactionRequestType->setTransactionType('authCaptureTransaction');
|
||||
$transactionRequestType->setAmount($amount);
|
||||
$transactionRequestType->setTax($tax);
|
||||
$transactionRequestType->setTaxExempt(empty($taxAmount));
|
||||
$transactionRequestType->setOrder($order);
|
||||
$transactionRequestType->setProfile($profileToCharge);
|
||||
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);
|
||||
|
@ -245,7 +245,7 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
sleep(1);
|
||||
|
||||
foreach ($request->events as $event) {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out') {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out' || $event['action'] === 'paid') {
|
||||
|
||||
nlog("Searching for transaction reference");
|
||||
|
||||
|
101
app/PaymentDrivers/Stripe/Jobs/StripeWebhook.php
Normal file
101
app/PaymentDrivers/Stripe/Jobs/StripeWebhook.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?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\PaymentDrivers\Stripe\Jobs;
|
||||
|
||||
use App\Jobs\Mail\PaymentFailedMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class StripeWebhook implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public int $company_gateway_id;
|
||||
|
||||
public string $company_key;
|
||||
|
||||
private bool $url_found = false;
|
||||
|
||||
private array $events = [
|
||||
'source.chargeable',
|
||||
'charge.succeeded',
|
||||
'charge.failed',
|
||||
'payment_intent.succeeded',
|
||||
'payment_intent.payment_failed'
|
||||
];
|
||||
|
||||
public function __construct(string $company_key, int $company_gateway_id)
|
||||
{
|
||||
$this->company_key = $company_key;
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
$company_gateway = CompanyGateway::find($this->company_gateway_id);
|
||||
|
||||
$stripe = $company_gateway->driver()->init();
|
||||
|
||||
$endpoints = \Stripe\WebhookEndpoint::all([], $stripe->stripe_connect_auth);
|
||||
|
||||
$webhook_url = $company_gateway->webhookUrl();
|
||||
|
||||
foreach($endpoints['data'] as $endpoint)
|
||||
{
|
||||
|
||||
if($endpoint->url === $webhook_url)
|
||||
{
|
||||
|
||||
\Stripe\WebhookEndpoint::update($endpoint->id, ['enabled_events' => $this->events], $stripe->stripe_connect_auth);
|
||||
|
||||
$this->url_found = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Add new webhook */
|
||||
if(!$this->url_found)
|
||||
{
|
||||
|
||||
\Stripe\WebhookEndpoint::create([
|
||||
'url' => $webhook_url,
|
||||
'enabled_events' => $this->events,
|
||||
], $stripe->stripe_connect_auth);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
app/Policies/PurchaseOrderPolicy.php
Normal file
27
app/Policies/PurchaseOrderPolicy.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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\Policies;
|
||||
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PurchaseOrderPolicy extends EntityPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin() || $user->hasPermission('create_purchase_order') || $user->hasPermission('create_all');
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
@ -54,6 +55,7 @@ use App\Policies\PaymentPolicy;
|
||||
use App\Policies\PaymentTermPolicy;
|
||||
use App\Policies\ProductPolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\PurchaseOrderPolicy;
|
||||
use App\Policies\QuotePolicy;
|
||||
use App\Policies\RecurringExpensePolicy;
|
||||
use App\Policies\RecurringInvoicePolicy;
|
||||
@ -103,6 +105,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
TaxRate::class => TaxRatePolicy::class,
|
||||
User::class => UserPolicy::class,
|
||||
Vendor::class => VendorPolicy::class,
|
||||
PurchaseOrder::class => PurchaseOrderPolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -215,6 +215,7 @@ use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\Proposal;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Task;
|
||||
@ -231,6 +232,7 @@ use App\Observers\PaymentObserver;
|
||||
use App\Observers\ProductObserver;
|
||||
use App\Observers\ProjectObserver;
|
||||
use App\Observers\ProposalObserver;
|
||||
use App\Observers\PurchaseOrderObserver;
|
||||
use App\Observers\QuoteObserver;
|
||||
use App\Observers\SubscriptionObserver;
|
||||
use App\Observers\TaskObserver;
|
||||
@ -593,5 +595,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
Quote::observe(QuoteObserver::class);
|
||||
Task::observe(TaskObserver::class);
|
||||
User::observe(UserObserver::class);
|
||||
PurchaseOrder::observe(PurchaseOrderObserver::class);
|
||||
}
|
||||
}
|
||||
|
34
app/Repositories/PurchaseOrderRepository.php
Normal file
34
app/Repositories/PurchaseOrderRepository.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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\Repositories;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderRepository extends BaseRepository
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function save(array $data, PurchaseOrder $purchase_order) : ?PurchaseOrder
|
||||
{
|
||||
$purchase_order->fill($data);
|
||||
$purchase_order->save();
|
||||
|
||||
return $purchase_order;
|
||||
}
|
||||
|
||||
}
|
18
app/Repositories/TaskSchedulerRepository.php
Normal file
18
app/Repositories/TaskSchedulerRepository.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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\Repositories;
|
||||
|
||||
|
||||
class TaskSchedulerRepository extends BaseRepository
|
||||
{
|
||||
|
||||
}
|
57
app/Services/PurchaseOrder/PurchaseOrderService.php
Normal file
57
app/Services/PurchaseOrder/PurchaseOrderService.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\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct($purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the purchase order.
|
||||
* @return \App\Models\PurchaseOrder object
|
||||
*/
|
||||
public function save(): ?PurchaseOrder
|
||||
{
|
||||
$this->purchase_order->saveQuietly();
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
$settings = $this->purchase_order->client->getMergedSettings();
|
||||
|
||||
//TODO implement design, footer, terms
|
||||
|
||||
|
||||
/* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
|
||||
if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
|
||||
$this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
|
||||
|
||||
if (!isset($this->purchase_order->public_notes))
|
||||
$this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -106,6 +106,12 @@ class RecurringService
|
||||
$this->stop();
|
||||
}
|
||||
|
||||
if(isset($this->recurring_entity->client))
|
||||
{
|
||||
$offset = $this->recurring_entity->client->timezone_offset();
|
||||
$this->recurring_entity->next_send_date = Carbon::parse($this->recurring_entity->next_send_date_client)->startOfDay()->addSeconds($offset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
175
app/Services/TaskScheduler/TaskSchedulerService.php
Normal file
175
app/Services/TaskScheduler/TaskSchedulerService.php
Normal file
@ -0,0 +1,175 @@
|
||||
<?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\Services\TaskScheduler;
|
||||
|
||||
|
||||
use App\Export\CSV\ClientExport;
|
||||
use App\Export\CSV\ContactExport;
|
||||
use App\Export\CSV\CreditExport;
|
||||
use App\Export\CSV\DocumentExport;
|
||||
use App\Export\CSV\ExpenseExport;
|
||||
use App\Export\CSV\InvoiceExport;
|
||||
use App\Export\CSV\InvoiceItemExport;
|
||||
use App\Export\CSV\PaymentExport;
|
||||
use App\Export\CSV\ProductExport;
|
||||
use App\Export\CSV\QuoteExport;
|
||||
use App\Export\CSV\QuoteItemExport;
|
||||
use App\Export\CSV\RecurringInvoiceExport;
|
||||
use App\Export\CSV\TaskExport;
|
||||
use App\Http\Requests\Report\GenericReportRequest;
|
||||
use App\Http\Requests\Report\ProfitLossRequest;
|
||||
use App\Http\Requests\TaskScheduler\CreateScheduledTaskRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduledJobRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduleRequest;
|
||||
use App\Jobs\Report\ProfitAndLoss;
|
||||
use App\Models\Company;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Ninja;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class TaskSchedulerService
|
||||
{
|
||||
public Scheduler $scheduler;
|
||||
|
||||
public function __construct(Scheduler $scheduler)
|
||||
{
|
||||
$this->scheduler = $scheduler;
|
||||
}
|
||||
|
||||
public function store(Scheduler $scheduler, CreateScheduledTaskRequest $request)
|
||||
{
|
||||
$scheduler->action_name = $request->job;
|
||||
$scheduler->paused = $request->get('paused', false);
|
||||
$scheduler->start_from = $request->get('start_from') ? Carbon::parse((int)$request->get('start_from')) : Carbon::now();
|
||||
$scheduler->repeat_every = $request->get('repeat_every');
|
||||
$scheduler->scheduled_run = $request->get('start_from') ? Carbon::parse((int)$request->get('start_from')) : Carbon::now();;
|
||||
$scheduler->company_id = auth()->user()->company()->id;
|
||||
$scheduler = $this->setJobParameters($scheduler, $request);
|
||||
$scheduler->save();
|
||||
|
||||
}
|
||||
|
||||
public function update(Scheduler $scheduler, UpdateScheduleRequest $request)
|
||||
{
|
||||
if (array_key_exists('job', $request->all())) {
|
||||
$scheduler->action_name = $request->get('job');
|
||||
$scheduler = $this->setJobParameters($scheduler, $request);
|
||||
}
|
||||
$data = $request->validated();
|
||||
$update = $this->scheduler->update($data);
|
||||
}
|
||||
|
||||
private function runValidation($form_request, $data)
|
||||
{
|
||||
$_syn_request_class = new $form_request();
|
||||
$_syn_request_class->setContainer(app());
|
||||
$_syn_request_class->initialize($data);
|
||||
$_syn_request_class->prepareForValidation();
|
||||
$_syn_request_class->setValidator(Validator::make($_syn_request_class->all(), $_syn_request_class->rules()));
|
||||
|
||||
return $_syn_request_class->validated();
|
||||
}
|
||||
|
||||
public function setJobParameters(Scheduler $scheduler, $request)
|
||||
{
|
||||
switch ($scheduler->action_name) {
|
||||
case Scheduler::CREATE_CLIENT_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_CLIENT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(ClientExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_CLIENT_CONTACT_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_CLIENT_CONTACT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(ContactExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_CREDIT_REPORT:
|
||||
|
||||
$scheduler->action_name = Scheduler::CREATE_CREDIT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(CreditExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_DOCUMENT_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_DOCUMENT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(DocumentExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_EXPENSE_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_EXPENSE_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(ExpenseExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_INVOICE_ITEM_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_INVOICE_ITEM_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(InvoiceItemExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_INVOICE_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_INVOICE_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(InvoiceExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_PAYMENT_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_PAYMENT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(PaymentExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_PRODUCT_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_PRODUCT_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(ProductExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_PROFIT_AND_LOSS_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_PROFIT_AND_LOSS_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(ProfitAndLoss::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_QUOTE_ITEM_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_QUOTE_ITEM_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(QuoteItemExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_QUOTE_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_QUOTE_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(QuoteExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_RECURRING_INVOICE_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_RECURRING_INVOICE_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(RecurringInvoiceExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
case Scheduler::CREATE_TASK_REPORT:
|
||||
$scheduler->action_name = Scheduler::CREATE_TASK_REPORT;
|
||||
$scheduler->action_class = $this->getClassPath(TaskExport::class);
|
||||
$scheduler->parameters = $this->runValidation(GenericReportRequest::class, $request->all());
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $scheduler;
|
||||
}
|
||||
|
||||
public function getClassPath($class): string
|
||||
{
|
||||
return $class = is_object($class) ? get_class($class) : $class;
|
||||
}
|
||||
|
||||
public function updateJob(Scheduler $scheduler, UpdateScheduledJobRequest $request)
|
||||
{
|
||||
$scheduler = $this->setJobParameters($scheduler, $request);
|
||||
$scheduler->save();
|
||||
}
|
||||
}
|
@ -13,6 +13,10 @@ namespace App\Transformers;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Backup;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class ActivityTransformer extends EntityTransformer
|
||||
@ -25,7 +29,17 @@ class ActivityTransformer extends EntityTransformer
|
||||
* @var array
|
||||
*/
|
||||
protected $availableIncludes = [
|
||||
'history'
|
||||
'history',
|
||||
'user',
|
||||
'client',
|
||||
'contact',
|
||||
'recurring_invoice',
|
||||
'invoice',
|
||||
'credit',
|
||||
'quote',
|
||||
'payment',
|
||||
'expense',
|
||||
'task',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -66,4 +80,75 @@ class ActivityTransformer extends EntityTransformer
|
||||
|
||||
return $this->includeItem($activity->backup, $transformer, Backup::class);
|
||||
}
|
||||
|
||||
public function includeClient(Activity $activity)
|
||||
{
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->client, $transformer, Client::class);
|
||||
}
|
||||
|
||||
public function includeContact(Activity $activity)
|
||||
{
|
||||
$transformer = new ClientContactTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->contact, $transformer, ClientContact::class);
|
||||
}
|
||||
|
||||
public function includeRecurringInvoice(Activity $activity)
|
||||
{
|
||||
$transformer = new RecurringInvoiceTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->recurring_invoice, $transformer, RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function includeQuote(Activity $activity)
|
||||
{
|
||||
$transformer = new RecurringInvoiceTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->quote, $transformer, Quote::class);
|
||||
}
|
||||
|
||||
public function includeInvoice(Activity $activity)
|
||||
{
|
||||
$transformer = new InvoiceTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->invoice, $transformer, Invoice::class);
|
||||
}
|
||||
|
||||
public function includeCredit(Activity $activity)
|
||||
{
|
||||
$transformer = new CreditTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->credit, $transformer, Credit::class);
|
||||
}
|
||||
|
||||
public function includePayment(Activity $activity)
|
||||
{
|
||||
$transformer = new PaymentTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->payment, $transformer, Payment::class);
|
||||
}
|
||||
|
||||
public function includeUser(Activity $activity)
|
||||
{
|
||||
$transformer = new UserTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->user, $transformer, User::class);
|
||||
}
|
||||
|
||||
public function includeExpense(Activity $activity)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->expense, $transformer, Expense::class);
|
||||
}
|
||||
|
||||
public function includeTask(Activity $activity)
|
||||
{
|
||||
$transformer = new TaskTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($activity->task, $transformer, Task::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
88
app/Transformers/PurchaseOrderTransformer.php
Normal file
88
app/Transformers/PurchaseOrderTransformer.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?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\Transformers;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function transform(PurchaseOrder $purchase_order)
|
||||
{
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($purchase_order->id),
|
||||
'user_id' => $this->encodePrimaryKey($purchase_order->user_id),
|
||||
'project_id' => $this->encodePrimaryKey($purchase_order->project_id),
|
||||
'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id),
|
||||
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'amount' => (float) $purchase_order->amount,
|
||||
'balance' => (float) $purchase_order->balance,
|
||||
'client_id' => (string) $this->encodePrimaryKey($purchase_order->client_id),
|
||||
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'status_id' => (string) ($purchase_order->status_id ?: 1),
|
||||
'design_id' => (string) $this->encodePrimaryKey($purchase_order->design_id),
|
||||
'created_at' => (int) $purchase_order->created_at,
|
||||
'updated_at' => (int) $purchase_order->updated_at,
|
||||
'archived_at' => (int) $purchase_order->deleted_at,
|
||||
'is_deleted' => (bool) $purchase_order->is_deleted,
|
||||
'number' => $purchase_order->number ?: '',
|
||||
'discount' => (float) $purchase_order->discount,
|
||||
'po_number' => $purchase_order->po_number ?: '',
|
||||
'date' => $purchase_order->date ?: '',
|
||||
'last_sent_date' => $purchase_order->last_sent_date ?: '',
|
||||
'next_send_date' => $purchase_order->next_send_date ?: '',
|
||||
'reminder1_sent' => $purchase_order->reminder1_sent ?: '',
|
||||
'reminder2_sent' => $purchase_order->reminder2_sent ?: '',
|
||||
'reminder3_sent' => $purchase_order->reminder3_sent ?: '',
|
||||
'reminder_last_sent' => $purchase_order->reminder_last_sent ?: '',
|
||||
'due_date' => $purchase_order->due_date ?: '',
|
||||
'terms' => $purchase_order->terms ?: '',
|
||||
'public_notes' => $purchase_order->public_notes ?: '',
|
||||
'private_notes' => $purchase_order->private_notes ?: '',
|
||||
'uses_inclusive_taxes' => (bool) $purchase_order->uses_inclusive_taxes,
|
||||
'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '',
|
||||
'tax_rate1' => (float) $purchase_order->tax_rate1,
|
||||
'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '',
|
||||
'tax_rate2' => (float) $purchase_order->tax_rate2,
|
||||
'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '',
|
||||
'tax_rate3' => (float) $purchase_order->tax_rate3,
|
||||
'total_taxes' => (float) $purchase_order->total_taxes,
|
||||
'is_amount_discount' => (bool) ($purchase_order->is_amount_discount ?: false),
|
||||
'footer' => $purchase_order->footer ?: '',
|
||||
'partial' => (float) ($purchase_order->partial ?: 0.0),
|
||||
'partial_due_date' => $purchase_order->partial_due_date ?: '',
|
||||
'custom_value1' => (string) $purchase_order->custom_value1 ?: '',
|
||||
'custom_value2' => (string) $purchase_order->custom_value2 ?: '',
|
||||
'custom_value3' => (string) $purchase_order->custom_value3 ?: '',
|
||||
'custom_value4' => (string) $purchase_order->custom_value4 ?: '',
|
||||
'has_tasks' => (bool) $purchase_order->has_tasks,
|
||||
'has_expenses' => (bool) $purchase_order->has_expenses,
|
||||
'custom_surcharge1' => (float) $purchase_order->custom_surcharge1,
|
||||
'custom_surcharge2' => (float) $purchase_order->custom_surcharge2,
|
||||
'custom_surcharge3' => (float) $purchase_order->custom_surcharge3,
|
||||
'custom_surcharge4' => (float) $purchase_order->custom_surcharge4,
|
||||
'custom_surcharge_tax1' => (bool) $purchase_order->custom_surcharge_tax1,
|
||||
'custom_surcharge_tax2' => (bool) $purchase_order->custom_surcharge_tax2,
|
||||
'custom_surcharge_tax3' => (bool) $purchase_order->custom_surcharge_tax3,
|
||||
'custom_surcharge_tax4' => (bool) $purchase_order->custom_surcharge_tax4,
|
||||
'line_items' => $purchase_order->line_items ?: (array) [],
|
||||
'entity_type' => 'credit',
|
||||
'exchange_rate' => (float) $purchase_order->exchange_rate,
|
||||
'paid_to_date' => (float) $purchase_order->paid_to_date,
|
||||
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -100,7 +100,8 @@ class RecurringExpenseTransformer extends EntityTransformer
|
||||
'frequency_id' => (string) $recurring_expense->frequency_id,
|
||||
'remaining_cycles' => (int) $recurring_expense->remaining_cycles,
|
||||
'last_sent_date' => $recurring_expense->last_sent_date ?: '',
|
||||
'next_send_date' => $recurring_expense->next_send_date ?: '',
|
||||
// 'next_send_date' => $recurring_expense->next_send_date ?: '',
|
||||
'next_send_date' => $recurring_expense->next_send_date_client ?: '',
|
||||
'recurring_dates' => (array) [],
|
||||
];
|
||||
|
||||
|
@ -95,7 +95,8 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
'po_number' => $invoice->po_number ?: '',
|
||||
'date' => $invoice->date ?: '',
|
||||
'last_sent_date' => $invoice->last_sent_date ?: '',
|
||||
'next_send_date' => $invoice->next_send_date ?: '',
|
||||
// 'next_send_date' => $invoice->next_send_date ?: '',
|
||||
'next_send_date' => $invoice->next_send_date_client ?: '',
|
||||
'due_date' => $invoice->due_date ?: '',
|
||||
'terms' => $invoice->terms ?: '',
|
||||
'public_notes' => $invoice->public_notes ?: '',
|
||||
|
40
app/Transformers/TaskSchedulerTransformer.php
Normal file
40
app/Transformers/TaskSchedulerTransformer.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?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\Transformers;
|
||||
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class TaskSchedulerTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
|
||||
public function transform(Scheduler $scheduler)
|
||||
{
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($scheduler->id),
|
||||
'is_deleted' => (bool)$scheduler->is_deleted,
|
||||
'paused' => (bool)$scheduler->paused,
|
||||
'repeat_every' => (string)$scheduler->repeat_every,
|
||||
'start_from' => (int)$scheduler->start_from,
|
||||
'scheduled_run' => (int)$scheduler->scheduled_run,
|
||||
'updated_at' => (int)$scheduler->updated_at,
|
||||
'created_at' => (int)$scheduler->created_at,
|
||||
'archived_at' => (int) $scheduler->deleted_at,
|
||||
'action_name' => (string) $scheduler->action_name,
|
||||
'action_class' => (string) $scheduler->action_class,
|
||||
'parameters'=> (array)$scheduler->parameters,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ trait PageNumbering
|
||||
private function pageNumbering($pdf_data_object, $company)
|
||||
{
|
||||
|
||||
if(!$company->settings->page_numbering)
|
||||
if(!property_exists($company->settings, 'page_numbering') || !$company->settings->page_numbering)
|
||||
return $pdf_data_object;
|
||||
|
||||
try
|
||||
|
@ -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.92',
|
||||
'app_tag' => '5.3.92',
|
||||
'app_version' => '5.3.94',
|
||||
'app_tag' => '5.3.94',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -187,7 +187,7 @@ return [
|
||||
'ninja_apple_api_key' => env('APPLE_API_KEY', false),
|
||||
'ninja_apple_private_key' => env('APPLE_PRIVATE_KEY', false),
|
||||
'ninja_apple_bundle_id' => env('APPLE_BUNDLE_ID', false),
|
||||
'ninja_apple_issuer_id' => env('APPLE_ISSUER_ID', false)
|
||||
|
||||
'ninja_apple_issuer_id' => env('APPLE_ISSUER_ID', false),
|
||||
'react_app_enabled' => env('REACT_APP_ENABLED', false),
|
||||
];
|
||||
|
||||
|
@ -51,6 +51,7 @@ class RecurringInvoiceFactory extends Factory
|
||||
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
|
||||
'last_sent_date' => now()->subMonth(),
|
||||
'next_send_date' => now()->addMonthNoOverflow(),
|
||||
'next_send_date_client' => now()->addMonthNoOverflow(),
|
||||
'remaining_cycles' => $this->faker->numberBetween(1, 10),
|
||||
'amount' => $this->faker->randomFloat(2, $min = 1, $max = 1000), // 48.8932
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateScheduledJobsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('scheduled_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('action_name');
|
||||
$table->string('action_class');
|
||||
$table->json('parameters')->nullable();
|
||||
$table->foreignIdFor(\App\Models\Company::class);
|
||||
$table->foreignIdFor(\App\Models\Scheduler::class);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('scheduled_jobs');
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSchedulersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('schedulers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->boolean('paused')->default(false);
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
$table->string('repeat_every');
|
||||
$table->timestamp('start_from');
|
||||
$table->timestamp('scheduled_run');
|
||||
$table->foreignIdFor(\App\Models\Company::class);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('schedulers');
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
<?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
|
||||
*/
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePurchaseOrdersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('purchase_orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('client_id')->index();
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('assigned_user_id')->nullable();
|
||||
$table->unsignedInteger('company_id')->index();
|
||||
$table->unsignedInteger('status_id');
|
||||
$table->unsignedInteger('project_id')->nullable();
|
||||
$table->unsignedInteger('vendor_id')->nullable();
|
||||
$table->unsignedInteger('recurring_id')->nullable();
|
||||
$table->unsignedInteger('design_id')->nullable();
|
||||
$table->unsignedInteger('invoice_id')->nullable();
|
||||
|
||||
$table->string('number')->nullable();
|
||||
$table->float('discount')->default(0);
|
||||
$table->boolean('is_amount_discount')->default(0);
|
||||
|
||||
$table->string('po_number')->nullable();
|
||||
$table->date('date')->nullable();
|
||||
$table->datetime('last_sent_date')->nullable();
|
||||
|
||||
$table->date('due_date')->nullable();
|
||||
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
$table->mediumText('line_items')->nullable();
|
||||
$table->mediumText('backup')->nullable();
|
||||
$table->text('footer')->nullable();
|
||||
$table->text('public_notes')->nullable();
|
||||
$table->text('private_notes')->nullable();
|
||||
$table->text('terms')->nullable();
|
||||
|
||||
$table->string('tax_name1')->nullable();
|
||||
|
||||
|
||||
$table->decimal('tax_rate1', 20, 6)->default(0);
|
||||
|
||||
$table->string('tax_name2')->nullable();
|
||||
$table->decimal('tax_rate2', 20, 6)->default(0);
|
||||
|
||||
$table->string('tax_name3')->nullable();
|
||||
$table->decimal('tax_rate3', 20, 6)->default(0);
|
||||
|
||||
|
||||
$table->decimal('total_taxes', 20, 6)->default(0);
|
||||
$table->boolean('uses_inclusive_taxes')->default(0);
|
||||
|
||||
$table->date('reminder1_sent')->nullable();
|
||||
$table->date('reminder2_sent')->nullable();
|
||||
$table->date('reminder3_sent')->nullable();
|
||||
$table->date('reminder_last_sent')->nullable();
|
||||
|
||||
$table->text('custom_value1')->nullable();
|
||||
$table->text('custom_value2')->nullable();
|
||||
$table->text('custom_value3')->nullable();
|
||||
$table->text('custom_value4')->nullable();
|
||||
|
||||
|
||||
$table->datetime('next_send_date')->nullable();
|
||||
|
||||
|
||||
|
||||
$table->decimal('custom_surcharge1', 20,6)->nullable();
|
||||
$table->decimal('custom_surcharge2', 20,6)->nullable();
|
||||
$table->decimal('custom_surcharge3', 20,6)->nullable();
|
||||
$table->decimal('custom_surcharge4', 20,6)->nullable();
|
||||
|
||||
|
||||
$table->boolean('custom_surcharge_tax1')->default(false);
|
||||
$table->boolean('custom_surcharge_tax2')->default(false);
|
||||
$table->boolean('custom_surcharge_tax3')->default(false);
|
||||
$table->boolean('custom_surcharge_tax4')->default(false);
|
||||
|
||||
|
||||
$table->decimal('exchange_rate', 20, 6)->default(1);
|
||||
$table->decimal('balance', 20, 6);
|
||||
$table->decimal('partial', 20, 6)->nullable();
|
||||
$table->decimal('amount', 20, 6);
|
||||
$table->decimal('paid_to_date', 20, 6)->default(0);
|
||||
|
||||
$table->datetime('partial_due_date')->nullable();
|
||||
|
||||
$table->datetime('last_viewed')->nullable();
|
||||
|
||||
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->index(['company_id', 'deleted_at']);
|
||||
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class DropScheduledJobsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('scheduled_jobs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddJobRelatedFieldsToSchedulersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('schedulers', function (Blueprint $table) {
|
||||
$table->string('action_name')->index();
|
||||
$table->string('action_class');
|
||||
$table->json('parameters')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SetRecurringClientTimestamp extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||
$table->datetime('next_send_date_client')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('recurring_expenses', function (Blueprint $table) {
|
||||
$table->datetime('next_send_date_client')->nullable();
|
||||
});
|
||||
|
||||
|
||||
RecurringInvoice::whereNotNull('next_send_date')->cursor()->each(function ($recurring_invoice){
|
||||
|
||||
// $offset = $recurring_invoice->client->timezone_offset();
|
||||
// $re = Carbon::parse($recurring_invoice->next_send_date)->subSeconds($offset)->format('Y-m-d');
|
||||
$re = Carbon::parse($recurring_invoice->next_send_date)->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = $re;
|
||||
$recurring_invoice->saveQuietly();
|
||||
|
||||
});
|
||||
|
||||
RecurringExpense::whereNotNull('next_send_date')->cursor()->each(function ($recurring_expense){
|
||||
$recurring_expense->next_send_date_client = $recurring_expense->next_send_date;
|
||||
$recurring_expense->saveQuietly();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -3,13 +3,13 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"main.dart.js": "b552020eead077b50b54a51de32ba513",
|
||||
"main.dart.js": "dc33896edc7d40c104d85ca2d6c83176",
|
||||
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
|
||||
"canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd",
|
||||
"canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900",
|
||||
"canvaskit/canvaskit.js": "c2b4e5f3d7a3d82aed024e7249a78487",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
"/": "15b97e9f966c26d599d1d9c0ecd01354",
|
||||
"/": "e6fea64a985bef161e4db9ad8e7fee99",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"version.json": "3afb81924daf4f751571755436069115",
|
||||
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
|
||||
|
270114
public/main.dart.js
vendored
270114
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
268094
public/main.foss.dart.js
vendored
268094
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
260350
public/main.html.dart.js
vendored
260350
public/main.html.dart.js
vendored
File diff suppressed because one or more lines are too long
264754
public/main.next.dart.js
vendored
264754
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
18251
public/main.profile.dart.js
vendored
18251
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -794,12 +794,12 @@ $LANG = array(
|
||||
'activity_45' => ':user deleted task :task',
|
||||
'activity_46' => ':user restored task :task',
|
||||
'activity_47' => ':user updated expense :expense',
|
||||
'activity_48' => ':user updated ticket :ticket',
|
||||
'activity_49' => ':user closed ticket :ticket',
|
||||
'activity_50' => ':user merged ticket :ticket',
|
||||
'activity_51' => ':user split ticket :ticket',
|
||||
'activity_52' => ':contact opened ticket :ticket',
|
||||
'activity_53' => ':contact reopened ticket :ticket',
|
||||
'activity_48' => ':user created user :user',
|
||||
'activity_49' => ':user updated user :user',
|
||||
'activity_50' => ':user archived user :user',
|
||||
'activity_51' => ':user deleted user :user',
|
||||
'activity_52' => ':user restored user :user',
|
||||
'activity_53' => ':user marked sent :invoice',
|
||||
'activity_54' => ':user reopened ticket :ticket',
|
||||
'activity_55' => ':contact replied ticket :ticket',
|
||||
'activity_56' => ':user viewed ticket :ticket',
|
||||
@ -4583,8 +4583,30 @@ $LANG = array(
|
||||
'alternate_pdf_viewer' => 'Alternate PDF Viewer',
|
||||
'alternate_pdf_viewer_help' => 'Improve scrolling over the PDF preview [BETA]',
|
||||
'currency_cayman_island_dollar' => 'Cayman Island Dollar',
|
||||
'download_report_description' => 'Please see attached file to check your report.'
|
||||
|
||||
'download_report_description' => 'Please see attached file to check your report.',
|
||||
'left' => 'Left',
|
||||
'right' => 'Right',
|
||||
'center' => 'Center',
|
||||
'page_numbering' => 'Page Numbering',
|
||||
'page_numbering_alignment' => 'Page Numbering Alignment',
|
||||
'invoice_sent_notification_label' => 'Invoice Sent',
|
||||
'show_product_description' => 'Show Product Description',
|
||||
'show_product_description_help' => 'Include the description in the product dropdown',
|
||||
'invoice_items' => 'Invoice Items',
|
||||
'quote_items' => 'Quote Items',
|
||||
'profitloss' => 'Profit and Loss',
|
||||
'import_format' => 'Import Format',
|
||||
'export_format' => 'Export Format',
|
||||
'export_type' => 'Export Type',
|
||||
'stop_on_unpaid' => 'Stop On Unpaid',
|
||||
'stop_on_unpaid_help' => 'Stop creating recurring invoices if the last invoice is unpaid.',
|
||||
'use_quote_terms' => 'Use Quote Terms',
|
||||
'use_quote_terms_help' => 'When converting a quote to an invoice',
|
||||
'add_country' => 'Add Country',
|
||||
'enable_tooltips' => 'Enable Tooltips',
|
||||
'enable_tooltips_help' => 'Show tooltips when hovering the mouse',
|
||||
'multiple_client_error' => 'Error: records belong to more than one client',
|
||||
'login_label' => 'Login to an existing account',
|
||||
|
||||
);
|
||||
|
||||
|
207
resources/views/react/head.blade.php
Normal file
207
resources/views/react/head.blade.php
Normal file
@ -0,0 +1,207 @@
|
||||
<link rel="modulepreload" href="/react/nth-check.58e8385d.js">
|
||||
<link rel="stylesheet" href="/react/index.bff791ad.css">
|
||||
<link rel="modulepreload" href="/react/uuid.ef4f150d.js">
|
||||
<link rel="modulepreload" href="/react/@emotion.9aa0086d.js">
|
||||
<link rel="modulepreload" href="/react/react-select.0c11d83f.js">
|
||||
<link rel="modulepreload" href="/react/space-separated-tokens.59bb4ca8.js">
|
||||
<link rel="modulepreload" href="/react/raf-schd.43184c40.js">
|
||||
<link rel="modulepreload" href="/react/recharts-scale.dc8389ce.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-position.04d68b2b.js">
|
||||
<link rel="modulepreload" href="/react/reduce-css-calc.281532ee.js">
|
||||
<link rel="modulepreload" href="/react/is-plain-obj.260ab441.js">
|
||||
<link rel="modulepreload" href="/react/remark-rehype.66f21be3.js">
|
||||
<link rel="modulepreload" href="/react/extend.ca41ef84.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-footnote.5e766f8c.js">
|
||||
<link rel="modulepreload" href="/react/boolbase.bd705bc9.js">
|
||||
<link rel="modulepreload" href="/react/react.967f52d5.js">
|
||||
<link rel="modulepreload" href="/react/micromark-factory-whitespace.4e8e9fce.js">
|
||||
<link rel="modulepreload" href="/react/react-router-dom.09ff5a14.js">
|
||||
<script type="module" crossorigin src="/react/index.a4e53c8d.js"></script>
|
||||
<link rel="modulepreload" href="/react/mdast-util-to-string.717718d5.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-html-tag-name.8b7c83fe.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm-autolink-literal.4b04cd2e.js">
|
||||
<link rel="stylesheet" href="/react/@uiw.d17b7851.css">
|
||||
<link rel="modulepreload" href="/react/unist-util-is.bd39e4f2.js">
|
||||
<link rel="modulepreload" href="/react/react-qr-code.4878cb6b.js">
|
||||
<link rel="modulepreload" href="/react/web-namespaces.a24d7a09.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-sanitize-uri.f9c714eb.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-task-list-item.33d9f10c.js">
|
||||
<link rel="modulepreload" href="/react/html-parse-stringify.32417b46.js">
|
||||
<link rel="modulepreload" href="/react/i18next.cf61342d.js">
|
||||
<link rel="modulepreload" href="/react/html-escaper.7cd36ce3.js">
|
||||
<link rel="modulepreload" href="/react/lodash-es.8df7cfb3.js">
|
||||
<link rel="modulepreload" href="/react/d3-shape.c407f78b.js">
|
||||
<link rel="modulepreload" href="/react/tslib.1cf50020.js">
|
||||
<link rel="modulepreload" href="/react/use-memo-one.4a0d5bdc.js">
|
||||
<link rel="modulepreload" href="/react/qr.js.da7c6dcd.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm.0152e39c.js">
|
||||
<link rel="modulepreload" href="/react/is-decimal.247a88b7.js">
|
||||
<link rel="modulepreload" href="/react/react-hot-toast.80758c68.js">
|
||||
<link rel="modulepreload" href="/react/react-popper.d301c4dd.js">
|
||||
<link rel="modulepreload" href="/react/react-debounce-input.1347744c.js">
|
||||
<link rel="modulepreload" href="/react/d3-format.5a3f6ba7.js">
|
||||
<link rel="modulepreload" href="/react/react-markdown.3a061863.js">
|
||||
<link rel="modulepreload" href="/react/rehype-rewrite.cb5dac09.js">
|
||||
<link rel="modulepreload" href="/react/attr-accept.6d6aa28d.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm.3af3365b.js">
|
||||
<link rel="modulepreload" href="/react/@popperjs.50883750.js">
|
||||
<link rel="modulepreload" href="/react/warning.3e618ed9.js">
|
||||
<link rel="modulepreload" href="/react/immer.a08a4913.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-encode.b78670b5.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-character.811e4fc8.js">
|
||||
<link rel="modulepreload" href="/react/property-information.17621e99.js">
|
||||
<link rel="modulepreload" href="/react/formik.0c2f9d9e.js">
|
||||
<link rel="modulepreload" href="/react/character-reference-invalid.9429e209.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-to-parse5.d0422f4e.js">
|
||||
<link rel="modulepreload" href="/react/prop-types.c5f1cd8f.js">
|
||||
<link rel="modulepreload" href="/react/currency.js.cd0659e5.js">
|
||||
<link rel="modulepreload" href="/react/redux.acdf7418.js">
|
||||
<link rel="modulepreload" href="/react/react-feather.ba7315c7.js">
|
||||
<link rel="modulepreload" href="/react/decode-named-character-reference.58fdd984.js">
|
||||
<link rel="modulepreload" href="/react/raf.ef427214.js">
|
||||
<link rel="modulepreload" href="/react/react-redux.7269b315.js">
|
||||
<link rel="modulepreload" href="/react/ccount.24f4fb2a.js">
|
||||
<link rel="modulepreload" href="/react/react-resize-detector.73849032.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-heading-rank.0127330c.js">
|
||||
<link rel="modulepreload" href="/react/character-entities-html4.bfdecac8.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-visit.551af7f6.js">
|
||||
<link rel="modulepreload" href="/react/is-alphabetical.a64573e0.js">
|
||||
<link rel="modulepreload" href="/react/prismjs.1fc2e6a8.js">
|
||||
<link rel="modulepreload" href="/react/comma-separated-tokens.826a3e44.js">
|
||||
<link rel="modulepreload" href="/react/react-dropzone.87de0271.js">
|
||||
<link rel="modulepreload" href="/react/parse-entities.0d3eeb8f.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-stringify-position.6cdff1ba.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-from-parse5.1bc31bb6.js">
|
||||
<link rel="modulepreload" href="/react/lodash.debounce.bf978eb6.js">
|
||||
<link rel="modulepreload" href="/react/rehype-prism-plus.2cab531a.js">
|
||||
<link rel="modulepreload" href="/react/react-onclickoutside.05557887.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-select.9295ace7.js">
|
||||
<link rel="modulepreload" href="/react/zwitch.84c833f3.js">
|
||||
<link rel="modulepreload" href="/react/css-unit-converter.d617fa75.js">
|
||||
<link rel="modulepreload" href="/react/rehype-autolink-headings.4312e6cb.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-autolink-literal.75d97f6e.js">
|
||||
<link rel="modulepreload" href="/react/vfile.8a59bc59.js">
|
||||
<link rel="modulepreload" href="/react/hoist-non-react-statics.cc69b77f.js">
|
||||
<link rel="modulepreload" href="/react/collect.js.64c89be2.js">
|
||||
<link rel="modulepreload" href="/react/react-beautiful-dnd.1242ed17.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-tagfilter.97dd1d78.js">
|
||||
<link rel="modulepreload" href="/react/react-dom.bbdc7aed.js">
|
||||
<link rel="modulepreload" href="/react/micromark-core-commonmark.894384bd.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm-footnote.9832ece2.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-normalize-identifier.f045ed13.js">
|
||||
<link rel="modulepreload" href="/react/array-move.20870289.js">
|
||||
<link rel="modulepreload" href="/react/lodash.6e1b81d9.js">
|
||||
<link rel="modulepreload" href="/react/date-fns.c9928d0b.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-from-markdown.b6500a0a.js">
|
||||
<link rel="modulepreload" href="/react/eventemitter3.e831f4a3.js">
|
||||
<link rel="modulepreload" href="/react/is-alphanumerical.39e15300.js">
|
||||
<link rel="modulepreload" href="/react/hastscript.aa96013f.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-generated.22d80e74.js">
|
||||
<link rel="modulepreload" href="/react/remark-gfm.f031f689.js">
|
||||
<link rel="modulepreload" href="/react/remark-parse.a34fb5cc.js">
|
||||
<link rel="modulepreload" href="/react/axios.308741bb.js">
|
||||
<link rel="modulepreload" href="/react/character-entities-legacy.cb27298c.js">
|
||||
<link rel="modulepreload" href="/react/parse5.761ebfb8.js">
|
||||
<link rel="modulepreload" href="/react/void-elements.fa8a9e47.js">
|
||||
<link rel="modulepreload" href="/react/scheduler.84e669cb.js">
|
||||
<link rel="modulepreload" href="/react/markdown-table.f01db7c9.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm-strikethrough.4389f7f3.js">
|
||||
<link rel="stylesheet" href="/react/react-datepicker.e2be669b.css">
|
||||
<link rel="modulepreload" href="/react/micromark-factory-label.2e0fc916.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-strikethrough.39c8bc63.js">
|
||||
<link rel="modulepreload" href="/react/vfile-message.9560db1f.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-to-markdown.7e1348ca.js">
|
||||
<link rel="modulepreload" href="/react/redux-thunk.281dc3fb.js">
|
||||
<link rel="modulepreload" href="/react/is-buffer.f91fab60.js">
|
||||
<link rel="modulepreload" href="/react/rehype-raw.4c3d8b62.js">
|
||||
<link rel="modulepreload" href="/react/d3-array.1895b022.js">
|
||||
<link rel="modulepreload" href="/react/parse-numeric-range.fd682744.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-parse-selector.1be1f27a.js">
|
||||
<link rel="modulepreload" href="/react/refractor.9012dc30.js">
|
||||
<link rel="modulepreload" href="/react/css-box-model.54a3012b.js">
|
||||
<link rel="modulepreload" href="/react/tiny-invariant.817bfd36.js">
|
||||
<link rel="modulepreload" href="/react/object-assign.f7ed182d.js">
|
||||
<link rel="modulepreload" href="/react/reselect.68dc670d.js">
|
||||
<link rel="modulepreload" href="/react/rehype-slug.8f0b0db8.js">
|
||||
<link rel="modulepreload" href="/react/resize-observer-polyfill.2e10476d.js">
|
||||
<link rel="modulepreload" href="/react/micromark-factory-title.f4e107a2.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm-table.f1729384.js">
|
||||
<link rel="modulepreload" href="/react/react-query.15f431cf.js">
|
||||
<link rel="modulepreload" href="/react/react-i18next.b628690b.js">
|
||||
<link rel="modulepreload" href="/react/micromark-factory-destination.08e32d90.js">
|
||||
<link rel="modulepreload" href="/react/d3-scale.40adf854.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-to-string.03a4649a.js">
|
||||
<link rel="modulepreload" href="/react/d3-path.a0f08300.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-whitespace.b9acc724.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-classify-character.bb02743f.js">
|
||||
<link rel="modulepreload" href="/react/@babel.d4f90752.js">
|
||||
<link rel="modulepreload" href="/react/performance-now.3ab08c2f.js">
|
||||
<link rel="modulepreload" href="/react/d3-time.03d82efa.js">
|
||||
<link rel="modulepreload" href="/react/react-smooth.ec2093ff.js">
|
||||
<link rel="modulepreload" href="/react/stringify-entities.a3ae839a.js">
|
||||
<link rel="modulepreload" href="/react/@sentry.c26c3197.js">
|
||||
<link rel="modulepreload" href="/react/react-lifecycles-compat.8d6bbd76.js">
|
||||
<link rel="modulepreload" href="/react/html-void-elements.d3b5aea3.js">
|
||||
<link rel="modulepreload" href="/react/rehype-parse.74f2042d.js">
|
||||
<link rel="modulepreload" href="/react/d3-color.69ee14e3.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-subtokenize.0273ad72.js">
|
||||
<link rel="modulepreload" href="/react/react-router.47f931a4.js">
|
||||
<link rel="modulepreload" href="/react/deepmerge.a6dda5ef.js">
|
||||
<link rel="modulepreload" href="/react/file-selector.6172b388.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-gfm-task-list-item.36dc3006.js">
|
||||
<link rel="modulepreload" href="/react/hast-to-hyperscript.55881f10.js">
|
||||
<link rel="modulepreload" href="/react/react-fast-compare.d5a5a050.js">
|
||||
<link rel="modulepreload" href="/react/fast-equals.9df4a03b.js">
|
||||
<link rel="modulepreload" href="/react/tippy.js.1053d821.js">
|
||||
<link rel="modulepreload" href="/react/@headlessui.f2f54a45.js">
|
||||
<link rel="modulepreload" href="/react/bail.58fa4042.js">
|
||||
<link rel="modulepreload" href="/react/vfile-location.85c89654.js">
|
||||
<link rel="modulepreload" href="/react/recharts.38803f97.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-filter.14ea3396.js">
|
||||
<link rel="modulepreload" href="/react/direction.5373b893.js">
|
||||
<link rel="modulepreload" href="/react/micromark.31ec2ae2.js">
|
||||
<link rel="modulepreload" href="/react/goober.56221fd8.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-decode-string.d48b94e2.js">
|
||||
<link rel="modulepreload" href="/react/react-datepicker.3e071b73.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-is-element.4ae8c712.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-chunked.62b98efd.js">
|
||||
<link rel="modulepreload" href="/react/mdurl.530e10e1.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-find-and-replace.c8360cfe.js">
|
||||
<link rel="modulepreload" href="/react/internmap.bf78dfdb.js">
|
||||
<link rel="modulepreload" href="/react/tiny-warning.14328f40.js">
|
||||
<link rel="modulepreload" href="/react/css-selector-parser.838f527e.js">
|
||||
<link rel="modulepreload" href="/react/decimal.js-light.b1218db1.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-has-property.bf6e0cae.js">
|
||||
<link rel="modulepreload" href="/react/unified.ac4b28d0.js">
|
||||
<link rel="modulepreload" href="/react/@uiw.e53102d7.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-combine-extensions.fc6008e7.js">
|
||||
<link rel="modulepreload" href="/react/d3-time-format.0353a6ed.js">
|
||||
<link rel="modulepreload" href="/react/style-to-object.74a4cfe6.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-raw.6bdb4dc9.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-resolve-all.b4e652e2.js">
|
||||
<link rel="modulepreload" href="/react/bcp-47-match.b7ba7942.js">
|
||||
<link rel="modulepreload" href="/react/stylis.ba53937c.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-definitions.fd07577f.js">
|
||||
<link rel="modulepreload" href="/react/micromark-util-decode-numeric-character-reference.029edbd3.js">
|
||||
<link rel="modulepreload" href="/react/unist-builder.67dd3ae6.js">
|
||||
<link rel="modulepreload" href="/react/memoize-one.413f527d.js">
|
||||
<link rel="modulepreload" href="/react/hast-util-to-html.54b58e35.js">
|
||||
<link rel="modulepreload" href="/react/micromark-extension-gfm-table.f7983341.js">
|
||||
<link rel="modulepreload" href="/react/unist-util-visit-parents.78083329.js">
|
||||
<link rel="modulepreload" href="/react/github-slugger.144f2824.js">
|
||||
<link rel="modulepreload" href="/react/dayjs.6e560245.js">
|
||||
<link rel="modulepreload" href="/react/rehype.bee4df94.js">
|
||||
<link rel="modulepreload" href="/react/history.8119d98b.js">
|
||||
<link rel="modulepreload" href="/react/inline-style-parser.ec55a77b.js">
|
||||
<link rel="modulepreload" href="/react/is-hexadecimal.d91cb013.js">
|
||||
<link rel="modulepreload" href="/react/react-is.c6bf3c04.js">
|
||||
<link rel="modulepreload" href="/react/rehype-attr.8040248d.js">
|
||||
<link rel="modulepreload" href="/react/pretty-bytes.1ef697de.js">
|
||||
<link rel="modulepreload" href="/react/micromark-factory-space.84551a41.js">
|
||||
<link rel="modulepreload" href="/react/rehype-stringify.540b84fb.js">
|
||||
<link rel="modulepreload" href="/react/d3-interpolate.f5146873.js">
|
||||
<link rel="modulepreload" href="/react/mdast-util-to-hast.d1d8e0b2.js">
|
||||
<link rel="modulepreload" href="/react/trough.714478ec.js">
|
||||
<link rel="modulepreload" href="/react/@tippyjs.36ad76fa.js">
|
||||
<link rel="modulepreload" href="/react/@reduxjs.9b745b7e.js">
|
||||
<link rel="modulepreload" href="/react/classnames.02e8c2fa.js">
|
20
resources/views/react/index.blade.php
Normal file
20
resources/views/react/index.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}" data-login="{{ $login }}">
|
||||
<head>
|
||||
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
|
||||
<!-- Version: {{ config('ninja.app_version') }} -->
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ config('ninja.app_name') }}</title>
|
||||
<meta name="google-signin-client_id" content="{{ config('services.google.client_id') }}">
|
||||
|
||||
@include('react.head')
|
||||
|
||||
</head>
|
||||
|
||||
<body class="h-full">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -45,8 +45,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
|
||||
Route::post('filters/{entity}', 'FilterController@index')->name('filters');
|
||||
|
||||
Route::resource('client_gateway_tokens', 'ClientGatewayTokenController');
|
||||
|
||||
Route::resource('client_gateway_tokens', 'ClientGatewayTokenController');
|
||||
|
||||
Route::post('connected_account', 'ConnectedAccountController@index');
|
||||
Route::post('connected_account/gmail', 'ConnectedAccountController@handleGmailOauth');
|
||||
|
||||
@ -54,9 +54,9 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
|
||||
Route::post('companies/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
|
||||
Route::post('companies/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected');
|
||||
|
||||
|
||||
Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
|
||||
|
||||
|
||||
Route::put('companies/{company}/upload', 'CompanyController@upload');
|
||||
Route::post('companies/{company}/default', 'CompanyController@default');
|
||||
|
||||
@ -170,6 +170,10 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
Route::post('reports/tasks', 'Reports\TaskReportController');
|
||||
Route::post('reports/profitloss', 'Reports\ProfitAndLossController');
|
||||
|
||||
|
||||
Route::resource('task_scheduler', 'TaskSchedulerController')->except('edit')->parameters(['task_scheduler' => 'scheduler']);
|
||||
|
||||
|
||||
Route::get('scheduler', 'SchedulerController@index');
|
||||
Route::post('support/messages/send', 'Support\Messages\SendingController');
|
||||
|
||||
@ -202,6 +206,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
|
||||
Route::put('vendors/{vendor}/upload', 'VendorController@upload');
|
||||
|
||||
Route::resource('purchase_orders', 'PurchaseOrderController');
|
||||
|
||||
Route::get('users', 'UserController@index');
|
||||
Route::get('users/{user}', 'UserController@show')->middleware('password_protected');
|
||||
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
|
||||
|
142
tests/Feature/PurchaseOrderTest.php
Normal file
142
tests/Feature/PurchaseOrderTest.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?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 Tests;
|
||||
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class PurchaseOrderTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testPurchaseOrderRest()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id).'/edit');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$credit_update = [
|
||||
'tax_name1' => 'dippy',
|
||||
];
|
||||
|
||||
$this->assertNotNull($this->purchase_order);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $credit_update)
|
||||
->assertStatus(200);
|
||||
}
|
||||
public function testPostNewPurchaseOrder()
|
||||
{
|
||||
$purchase_order = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '34343xx43',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/purchase_orders/', $purchase_order)
|
||||
->assertStatus(200);
|
||||
}
|
||||
public function testPurchaseOrderDelete()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->delete('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
public function testPurchaseOrderUpdate()
|
||||
{
|
||||
$data = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '3434343',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/purchase_orders/', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
}
|
157
tests/Feature/Scheduler/SchedulerTest.php
Normal file
157
tests/Feature/Scheduler/SchedulerTest.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Tests\Feature\Scheduler;
|
||||
|
||||
use App\Export\CSV\ClientExport;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithoutEvents;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockUnitData;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class SchedulerTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use MockUnitData;
|
||||
use WithoutEvents;
|
||||
// use RefreshDatabase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
// $this->withoutExceptionHandling();
|
||||
|
||||
}
|
||||
|
||||
public function testSchedulerCantBeCreatedWithWrongData()
|
||||
{
|
||||
$data = [
|
||||
'repeat_every' => Scheduler::DAILY,
|
||||
'job' => Scheduler::CREATE_CLIENT_REPORT,
|
||||
'date_key' => '123',
|
||||
'report_keys' => ['test'],
|
||||
'date_range' => 'all',
|
||||
// 'start_from' => '2022-01-01'
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/task_scheduler/', $data);
|
||||
|
||||
|
||||
$response->assertSessionHasErrors();
|
||||
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeUpdated()
|
||||
{
|
||||
$response = $this->createScheduler();
|
||||
|
||||
$arr = $response->json();
|
||||
$id = $arr['data']['id'];
|
||||
|
||||
$scheduler = Scheduler::find($this->decodePrimaryKey($id));
|
||||
|
||||
$updateData = [
|
||||
'start_from' => 1655934741
|
||||
];
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id), $updateData);
|
||||
|
||||
$responseData = $response->json();
|
||||
$this->assertEquals($updateData['start_from'], $responseData['data']['start_from']);
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeSeen()
|
||||
{
|
||||
$response = $this->createScheduler();
|
||||
|
||||
$arr = $response->json();
|
||||
$id = $arr['data']['id'];
|
||||
|
||||
$scheduler = Scheduler::find($this->decodePrimaryKey($id));
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id));
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertEquals('create_client_report', $arr['data']['action_name']);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function testSchedulerJobCanBeUpdated()
|
||||
{
|
||||
$response = $this->createScheduler();
|
||||
|
||||
$arr = $response->json();
|
||||
$id = $arr['data']['id'];
|
||||
|
||||
$scheduler = Scheduler::find($this->decodePrimaryKey($id));
|
||||
|
||||
$this->assertSame('create_client_report', $scheduler->action_name);
|
||||
|
||||
$updateData = [
|
||||
'job' => Scheduler::CREATE_CREDIT_REPORT,
|
||||
'date_range' => 'all',
|
||||
'report_keys' => ['test1']
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id), $updateData);
|
||||
|
||||
$updatedSchedulerJob = Scheduler::first()->action_name;
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertSame('create_credit_report', $arr['data']['action_name']);
|
||||
}
|
||||
|
||||
public function createScheduler()
|
||||
{
|
||||
$data = [
|
||||
'repeat_every' => Scheduler::DAILY,
|
||||
'job' => Scheduler::CREATE_CLIENT_REPORT,
|
||||
'date_key' => '123',
|
||||
'report_keys' => ['test'],
|
||||
'date_range' => 'all',
|
||||
'start_from' => '2022-01-01'
|
||||
];
|
||||
|
||||
return $response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/task_scheduler/', $data);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\InvoiceToRecurringInvoiceFactory;
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
use App\Models\Account;
|
||||
@ -181,10 +182,10 @@ trait MockAccountData
|
||||
'hosted_client_count' => 1000,
|
||||
'hosted_company_count' => 1000
|
||||
]);
|
||||
|
||||
|
||||
$this->account->num_users = 3;
|
||||
$this->account->save();
|
||||
|
||||
|
||||
$this->company = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
@ -212,7 +213,7 @@ trait MockAccountData
|
||||
$settings->use_credits_payment = 'always';
|
||||
$settings->timezone_id = '1';
|
||||
$settings->entity_send_time = 0;
|
||||
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
|
||||
@ -387,7 +388,7 @@ trait MockAccountData
|
||||
$this->invoice->setRelation('company', $this->company);
|
||||
|
||||
$this->invoice->save();
|
||||
|
||||
|
||||
$this->invoice->load("client");
|
||||
|
||||
InvoiceInvitation::factory()->create([
|
||||
@ -447,13 +448,43 @@ trait MockAccountData
|
||||
|
||||
$this->quote->save();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$this->purchase_order = PurchaseOrderFactory::create($this->company->id, $user_id);
|
||||
$this->purchase_order->client_id = $this->client->id;
|
||||
|
||||
|
||||
$this->purchase_order->amount = 10;
|
||||
$this->purchase_order->balance = 10;
|
||||
|
||||
// $this->credit->due_date = now()->addDays(200);
|
||||
|
||||
$this->purchase_order->tax_name1 = '';
|
||||
$this->purchase_order->tax_name2 = '';
|
||||
$this->purchase_order->tax_name3 = '';
|
||||
|
||||
$this->purchase_order->tax_rate1 = 0;
|
||||
$this->purchase_order->tax_rate2 = 0;
|
||||
$this->purchase_order->tax_rate3 = 0;
|
||||
|
||||
$this->purchase_order->uses_inclusive_taxes = false;
|
||||
$this->purchase_order->save();
|
||||
|
||||
|
||||
|
||||
|
||||
$this->credit = CreditFactory::create($this->company->id, $user_id);
|
||||
$this->credit->client_id = $this->client->id;
|
||||
|
||||
$this->credit->line_items = $this->buildLineItems();
|
||||
$this->credit->amount = 10;
|
||||
$this->credit->balance = 10;
|
||||
|
||||
|
||||
// $this->credit->due_date = now()->addDays(200);
|
||||
|
||||
$this->credit->tax_name1 = '';
|
||||
|
Loading…
x
Reference in New Issue
Block a user