Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2022-06-11 15:18:19 +10:00
commit 3ecc85903c
562 changed files with 615961 additions and 599995 deletions

View File

@ -58,3 +58,6 @@ DELETE_BACKUP_DAYS=60
COMPOSER_AUTH='{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}'
SENTRY_LARAVEL_DSN=https://39389664f3f14969b4c43dadda00a40b@sentry2.invoicing.co/5
GOOGLE_PLAY_PACKAGE_NAME=
APPSTORE_PASSWORD=

View File

@ -39,6 +39,19 @@ jobs:
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
sudo find ./ -type d -exec chmod 755 {} \;
- name: Prepare React FrontEnd
run: |
mkdir public/react
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
cd ui
git checkout main
npm i
npm run build
cp -r dist/react/* ../public/react
cd ..
rm -rf ui
php artisan ninja:react
- name: Prepare JS/CSS assets
run: |
npm i

View File

@ -1 +1 @@
5.3.90
5.3.99

View File

@ -74,7 +74,7 @@ class CheckData extends Command
/**
* @var string
*/
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=} {--vendor_id=} {--paid_to_date=} {--client_balance=} {--ledger_balance=}';
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=} {--vendor_id=} {--paid_to_date=} {--client_balance=} {--ledger_balance=} {--balance_status=}';
/**
* @var string
@ -89,6 +89,9 @@ class CheckData extends Command
protected $wrong_balances = 0;
protected $wrong_paid_status = 0;
public function handle()
{
$time_start = microtime(true);
@ -109,6 +112,7 @@ class CheckData extends Command
$this->checkVendorContacts();
$this->checkEntityInvitations();
$this->checkCompanyData();
$this->checkBalanceVsPaidStatus();
if(Ninja::isHosted())
$this->checkAccountStatuses();
@ -856,4 +860,45 @@ class CheckData extends Command
});
}
public function checkBalanceVsPaidStatus()
{
$this->wrong_paid_status = 0;
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}");
if($this->option('balance_status')){
$val = $invoice->balance;
$invoice->balance = 0;
$invoice->paid_to_date=$val;
$invoice->save();
$p = $invoice->payments->first();
if($p && (int)$p->amount == 0)
{
$p->amount = $val;
$p->applied = $val;
$p->save();
$pivot = $p->paymentables->first();
$pivot->amount = $val;
$pivot->save();
}
$this->logMessage("Fixing {$invoice->id} settings payment to {$val}");
}
}
$this->logMessage($this->wrong_paid_status." wrong invoices with bad balance state");
}
}

View File

@ -108,6 +108,7 @@ class CreateSingleAccount extends Command
'default_password_timeout' => 30*60000,
'portal_mode' => 'domain',
'portal_domain' => 'http://ninja.test:8000',
'track_inventory' => true
]);
$settings = $company->settings;

View 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);
}
}

View File

@ -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;
@ -73,11 +74,12 @@ class Kernel extends ConsoleKernel
$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();
@ -99,9 +101,9 @@ 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:work database --stop-when-empty --memory=256')->everyMinute()->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');
}

View File

@ -116,6 +116,9 @@ class CompanySettings extends BaseSettings
public $project_number_pattern = ''; //@implemented
public $project_number_counter = 1; //@implemented
public $purchase_order_number_pattern = ''; //@implemented
public $purchase_order_number_counter = 1; //@implemented
public $shared_invoice_quote_counter = false; //@implemented
public $shared_invoice_credit_counter = false; //@implemented
public $recurring_number_prefix = ''; //@implemented
@ -133,6 +136,13 @@ class CompanySettings extends BaseSettings
public $invoice_design_id = 'Wpmbk5ezJn'; //@implemented
public $quote_design_id = 'Wpmbk5ezJn'; //@implemented
public $credit_design_id = 'Wpmbk5ezJn'; //@implemented
public $purchase_order_design_id = 'Wpmbk5ezJn';
public $purchase_order_footer = ''; //@implemented
public $purchase_order_terms = ''; //@implemented
public $purchase_order_public_notes = ''; //@implemented
public $require_purchase_order_signature = false; //@TODO ben to confirm
public $invoice_footer = ''; //@implemented
public $credit_footer = ''; //@implemented
public $credit_terms = ''; //@implemented
@ -170,6 +180,8 @@ class CompanySettings extends BaseSettings
public $email_subject_payment = ''; //@implemented
public $email_subject_payment_partial = ''; //@implemented
public $email_subject_statement = ''; //@implemented
public $email_subject_purchase_order = ''; //@implemented
public $email_template_purchase_order = ''; //@implemented
public $email_template_invoice = ''; //@implemented
public $email_template_credit = ''; //@implemented
public $email_template_quote = ''; //@implemented
@ -249,6 +261,9 @@ class CompanySettings extends BaseSettings
public $primary_color = '#298AAB';
public $secondary_color = '#7081e0';
public $page_numbering = false;
public $page_numbering_alignment = 'C'; //C,R,L
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@deprecated 31-05-2021
@ -274,6 +289,17 @@ class CompanySettings extends BaseSettings
public $auto_archive_invoice_cancelled = false;
public static $casts = [
'email_subject_purchase_order' => 'string',
'email_template_purchase_order' => 'string',
'require_purchase_order_signature' => 'bool',
'purchase_order_public_notes' => 'string',
'purchase_order_terms' => 'string',
'purchase_order_design_id' => 'string',
'purchase_order_footer' => 'string',
'purchase_order_number_pattern' => 'string',
'purchase_order_number_counter' => 'int',
'page_numbering_alignment' => 'string',
'page_numbering' => 'bool',
'auto_archive_invoice_cancelled' => 'bool',
'email_from_name' => 'string',
'show_all_tasks_client_portal' => 'string',
@ -469,6 +495,7 @@ class CompanySettings extends BaseSettings
'portal_custom_footer' => 'string',
'portal_custom_js' => 'string',
'client_portal_enable_uploads' => 'bool',
'purchase_order_number_counter' => 'integer',
];
public static $free_plan_casts = [
@ -522,6 +549,7 @@ class CompanySettings extends BaseSettings
'invoice_design_id',
'quote_design_id',
'credit_design_id',
'purchase_order_design_id',
];
/**
@ -620,6 +648,25 @@ class CompanySettings extends BaseSettings
'$client.phone',
'$contact.email',
],
'vendor_details' => [
'$vendor.name',
'$vendor.number',
'$vendor.vat_number',
'$vendor.address1',
'$vendor.address2',
'$vendor.city_state_postal',
'$vendor.country',
'$vendor.phone',
'$contact.email',
],
'purchase_order_details' => [
'$purchase_order.number',
'$purchase_order.po_number',
'$purchase_order.date',
'$purchase_order.due_date',
'$purchase_order.total',
'$purchase_order.balance_due',
],
'company_details' => [
'$company.name',
'$company.id_number',

View File

@ -61,10 +61,13 @@ class EmailTemplateDefaults
break;
case 'email_template_custom3':
return self::emailInvoiceTemplate();
case 'email_template_purchase_order':
return self::emailPurchaseOrderTemplate();
break;
/* Subject */
case 'email_subject_purchase_order':
return self::emailPurchaseOrderSubject();
case 'email_subject_invoice':
return self::emailInvoiceSubject();
break;
@ -152,6 +155,20 @@ class EmailTemplateDefaults
return ctrans('texts.payment_subject');
}
public static function emailPurchaseOrderSubject()
{
return ctrans('texts.purchase_order_subject', ['number' => '$number', 'account' => '$account']);
}
public static function emailPurchaseOrderTemplate()
{
$purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div class="center">$view_button</div>';
return $purchase_order_message;
}
public static function emailPaymentTemplate()
{
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>';

View File

@ -12,7 +12,6 @@
namespace App\Events\Invoice;
use App\Models\Company;
use App\Models\InvoiceInvitation;
use Illuminate\Queue\SerializesModels;
/**
@ -35,12 +34,12 @@ class InvoiceWasEmailedAndFailed
/**
* Create a new event instance.
*
* @param InvoiceInvitation $invitation
* @param $invitation
* @param Company $company
* @param string $errors
* @param array $event_vars
*/
public function __construct(InvoiceInvitation $invitation, Company $company, string $message, string $template, array $event_vars)
public function __construct($invitation, Company $company, string $message, string $template, array $event_vars)
{
$this->invitation = $invitation;

View File

@ -0,0 +1,47 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasArchived.
*/
class PurchaseOrderWasArchived
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasCreated.
*/
class PurchaseOrderWasCreated
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasDeleted.
*/
class PurchaseOrderWasDeleted
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasEmailed.
*/
class PurchaseOrderWasEmailed
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $invitation;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars)
{
$this->invitation = $invitation;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,49 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasRestored.
*/
class PurchaseOrderWasRestored
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
public $fromDeleted;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, $fromDeleted, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->fromDeleted = $fromDeleted;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasUpdated.
*/
class PurchaseOrderWasUpdated
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasViewed.
*/
class PurchaseOrderWasViewed
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -78,7 +78,7 @@ class QuoteItemExport extends BaseExport
'tax_name2' => 'item.tax_name2',
'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total',
'gross_line_total' => 'item.gross_line_total',
// 'gross_line_total' => 'item.gross_line_total',
'custom_value1' => 'item.custom_value1',
'custom_value2' => 'item.custom_value2',
'custom_value3' => 'item.custom_value3',

View 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;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Factory;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class PurchaseOrderInvitationFactory
{
public static function create(int $company_id, int $user_id) :PurchaseOrderInvitation
{
$ci = new PurchaseOrderInvitation();
$ci->company_id = $company_id;
$ci->user_id = $user_id;
$ci->vendor_contact_id = null;
$ci->purchase_order_id = null;
$ci->key = Str::random(config('ninja.key_length'));
$ci->transaction_reference = null;
$ci->message_id = null;
$ci->email_error = '';
$ci->signature_base64 = '';
$ci->signature_date = null;
$ci->sent_date = null;
$ci->viewed_date = null;
$ci->opened_date = null;
return $ci;
}
}

View File

@ -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;

View File

@ -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)) {

View 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]);
}
}

View File

@ -81,7 +81,7 @@ abstract class QueryFilters
continue;
}
if (strlen($value)) {
if (is_string($value) && strlen($value)) {
$this->$name($value);
} else {
$this->$name();

View File

@ -52,7 +52,10 @@ class InvoiceItemSum
$this->invoice = $invoice;
$this->currency = $this->invoice->client->currency();
if($this->invoice->client)
$this->currency = $this->invoice->client->currency();
else
$this->currency = $this->invoice->vendor->currency();
$this->line_items = [];
}

View File

@ -46,7 +46,10 @@ class InvoiceItemSumInclusive
$this->invoice = $invoice;
$this->currency = $this->invoice->client->currency();
if($this->invoice->client)
$this->currency = $this->invoice->client->currency();
else
$this->currency = $this->invoice->vendor->currency();
$this->line_items = [];
}

View File

@ -44,6 +44,8 @@ class InvoiceSum
private $gross_sub_total;
private $precision;
/**
* Constructs the object with Invoice and Settings object.
*
@ -53,8 +55,10 @@ class InvoiceSum
{
$this->invoice = $invoice;
// if(!$this->invoice->relationLoaded('client'))
// $this->invoice->load('client');
if($this->invoice->client)
$this->precision = $this->invoice->client->currency()->precision;
else
$this->precision = $this->invoice->vendor->currency()->precision;
$this->tax_map = new Collection;
}
@ -224,11 +228,19 @@ class InvoiceSum
return $this->invoice;
}
public function getPurchaseOrder()
{
$this->setCalculatedAttributes();
$this->invoice->saveQuietly();
return $this->invoice;
}
public function getRecurringInvoice()
{
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes();
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->saveQuietly();
@ -247,13 +259,13 @@ class InvoiceSum
if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
} else {
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
}
}
/* Set new calculated total */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes();

View File

@ -41,6 +41,7 @@ class InvoiceSumInclusive
private $sub_total;
private $precision;
/**
* Constructs the object with Invoice and Settings object.
*
@ -50,6 +51,11 @@ class InvoiceSumInclusive
{
$this->invoice = $invoice;
if($this->invoice->client)
$this->precision = $this->invoice->client->currency()->precision;
else
$this->precision = $this->invoice->vendor->currency()->precision;
$this->tax_map = new Collection;
}
@ -164,32 +170,15 @@ class InvoiceSumInclusive
private function calculateTotals()
{
//$this->total += $this->total_taxes;
// if (is_numeric($this->invoice->custom_value1)) {
// $this->total += $this->invoice->custom_value1;
// }
// if (is_numeric($this->invoice->custom_value2)) {
// $this->total += $this->invoice->custom_value2;
// }
// if (is_numeric($this->invoice->custom_value3)) {
// $this->total += $this->invoice->custom_value3;
// }
// if (is_numeric($this->invoice->custom_value4)) {
// $this->total += $this->invoice->custom_value4;
// }
return $this;
}
public function getRecurringInvoice()
{
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes();
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->saveQuietly();
@ -229,6 +218,15 @@ class InvoiceSumInclusive
return $this->invoice;
}
public function getPurchaseOrder()
{
//Build invoice values here and return Invoice
$this->setCalculatedAttributes();
$this->invoice->saveQuietly();
return $this->invoice;
}
/**
* Build $this->invoice variables after
* calculations have been performed.
@ -240,14 +238,14 @@ class InvoiceSumInclusive
if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
} else {
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
}
}
/* Set new calculated total */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes();

View File

@ -17,6 +17,7 @@ use App\Transformers\ActivityTransformer;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -27,7 +28,7 @@ use stdClass;
class ActivityController extends BaseController
{
use PdfMaker;
use PdfMaker, PageNumbering;
protected $entity_type = Activity::class;
@ -90,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);
}
@ -164,12 +192,35 @@ class ActivityController extends BaseController
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
$pdf = (new Phantom)->convertHtmlToPdf($html_backup);
$numbered_pdf = $this->pageNumbering($pdf, $activity->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
elseif(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($html_backup);
$numbered_pdf = $this->pageNumbering($pdf, $activity->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
else {
$pdf = $this->makePdf(null, null, $html_backup);
$numbered_pdf = $this->pageNumbering($pdf, $activity->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
if (isset($activity->invoice_id)) {

View File

@ -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);
}

View File

@ -154,9 +154,11 @@ class EntityViewController extends Controller
if (! $invitation->viewed_date) {
$invitation->markViewed();
event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars()));
if(!session()->get('is_silent'))
event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars()));
$this->fireEntityViewedEvent($invitation, $request->entity_type);
if(!session()->get('is_silent'))
$this->fireEntityViewedEvent($invitation, $request->entity_type);
}
return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]);

View File

@ -22,6 +22,7 @@ use App\Models\ClientContact;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation;
use App\Services\ClientPortal\InstantPayment;
use App\Utils\CurlUtils;
@ -128,9 +129,11 @@ class InvitationController extends Controller
if (auth()->guard('contact')->user() && ! request()->has('silent') && ! $invitation->viewed_date) {
$invitation->markViewed();
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
if(!session()->get('is_silent'))
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
$this->fireEntityViewedEvent($invitation, $entity);
if(!session()->get('is_silent'))
$this->fireEntityViewedEvent($invitation, $entity);
}
else{
$is_silent = 'true';
@ -191,7 +194,6 @@ class InvitationController extends Controller
return response()->json(["message" => "no record found"], 400);
$file_name = $invitation->{$entity}->numberFormatter().'.pdf';
nlog($file_name);
$file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
@ -280,6 +282,10 @@ class InvitationController extends Controller
$invite = CreditInvitation::withTrashed()->where('key', $invitation_key)->first();
$invite->contact->send_email = false;
$invite->contact->save();
}elseif($entity == 'purchase_order'){
$invite = PurchaseOrderInvitation::withTrashed()->where('key', $invitation_key)->first();
$invite->contact->send_email = false;
$invite->contact->save();
}
else
return abort(404);

View File

@ -61,7 +61,7 @@ class InvoiceController extends Controller
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
if ($invitation && auth()->guard('contact') && !session()->get('is_silent') && ! $invitation->viewed_date) {
$invitation->markViewed();

View File

@ -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();
}

View File

@ -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);
}

View File

@ -220,13 +220,12 @@ class InvoiceController extends BaseController
public function store(StoreInvoiceRequest $request)
{
// $client = Client::find($request->input('client_id'));
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
$invoice = $invoice->service()
->fillDefaults()
->triggeredActions($request)
->adjustInventory()
->save();
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -416,9 +415,14 @@ class InvoiceController extends BaseController
return response()->json(['message' => ctrans('texts.locked_invoice')], 403);
}
$old_invoice = $invoice->line_items;
$invoice = $this->invoice_repo->save($request->all(), $invoice);
$invoice->service()->triggeredActions($request)->touchPdf();
$invoice->service()
->triggeredActions($request)
->touchPdf()
->adjustInventory($old_invoice);
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -711,7 +715,6 @@ class InvoiceController extends BaseController
echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']);
break;
case 'restore':
$this->invoice_repo->restore($invoice);

View 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"),
*
* )
*/

View File

@ -41,6 +41,7 @@ use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
@ -51,6 +52,8 @@ class PreviewController extends BaseController
{
use MakesHash;
use MakesInvoiceHtml;
use PageNumbering;
public function __construct()
{
@ -157,7 +160,15 @@ class PreviewController extends BaseController
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
//else
@ -285,7 +296,14 @@ class PreviewController extends BaseController
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), $company);
@ -354,7 +372,14 @@ class PreviewController extends BaseController
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
@ -443,7 +468,14 @@ class PreviewController extends BaseController
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());

View File

@ -0,0 +1,646 @@
<?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\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Factory\PurchaseOrderFactory;
use App\Filters\PurchaseOrderFilters;
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
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\Jobs\Invoice\ZipInvoices;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
use App\Models\Client;
use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository;
use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
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)
{
$purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id));
$purchase_order = $purchase_order->service()
->fillDefaults()
->triggeredActions($request)
->save();
event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
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);
event(new PurchaseOrderWasUpdated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
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());
}
/**
* Perform bulk actions on the list view.
*
* @return Collection
*
* @OA\Post(
* path="/api/v1/purchase_orders/bulk",
* operationId="bulkPurchaseOrderss",
* tags={"purchase_orders"},
* summary="Performs bulk actions on an array of purchase_orders",
* description="",
* @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/index"),
* @OA\RequestBody(
* description="Purchase Order IDS",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The Bulk Action response",
* @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 bulk()
{
$action = request()->input('action');
$ids = request()->input('ids');
$purchase_orders = PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (! $purchase_orders) {
return response()->json(['message' => 'No Purchase Orders Found']);
}
/*
* Download Purchase Order/s
*/
if ($action == 'bulk_download' && $purchase_orders->count() >= 1) {
$purchase_orders->each(function ($purchase_order) {
if (auth()->user()->cannot('view', $purchase_order)) {
nlog("access denied");
return response()->json(['message' => ctrans('text.access_denied')]);
}
});
ZipPurchaseOrders::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
/*
* Send the other actions to the switch
*/
$purchase_orders->each(function ($purchase_order, $key) use ($action) {
if (auth()->user()->can('edit', $purchase_order)) {
$this->performAction($purchase_order, $action, true);
}
});
/* Need to understand which permission are required for the given bulk action ie. view / edit */
return $this->listResponse(PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
/**
* @OA\Get(
* path="/api/v1/purchase_orders/{id}/{action}",
* operationId="actionPurchaseOrder",
* tags={"purchase_orders"},
* summary="Performs a custom action on an purchase order",
* description="Performs a custom action on an purchase order.
*
* The current range of actions are as follows
* - mark_paid
* - download
* - archive
* - delete
* - email",
* @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\Parameter(
* name="action",
* in="path",
* description="The action string to be performed",
* example="clone_to_quote",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the invoice 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"),
* ),
* )
* @param ActionPurchaseOrderRequest $request
* @param PurchaseOrder $purchase_order
* @param $action
* @return \App\Http\Controllers\Response|\Illuminate\Http\JsonResponse|Response|mixed|\Symfony\Component\HttpFoundation\StreamedResponse
*/
public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action)
{
return $this->performAction($purchase_order, $action);
}
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
{
/*If we are using bulk actions, we don't want to return anything */
switch ($action) {
case 'mark_sent':
$purchase_order->service()->markSent()->save();
if (! $bulk) {
return $this->itemResponse($purchase_order);
}
break;
case 'download':
$file = $purchase_order->service()->getPurchaseOrderPdf();
return response()->streamDownload(function () use($file) {
echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']);
break;
case 'restore':
$this->purchase_order_repository->restore($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'archive':
$this->purchase_order_repository->archive($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'delete':
$this->purchase_order_repository->delete($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'email':
//check query parameter for email_type and set the template else use calculateTemplate
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
if (! $bulk) {
return response()->json(['message' => 'email sent'], 200);
}
default:
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break;
}
}
}

View File

@ -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)

View File

@ -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(

View File

@ -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(

View File

@ -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);
@ -142,6 +152,14 @@ class SelfUpdateController extends BaseController
$zipFile->close();
// $zip = new \ZipArchive;
// $res = $zip->open($file);
// if ($res === TRUE) {
// $zip->extractTo(base_path());
// $zip->close();
// }
nlog("Finished extracting files");
unlink($file);

View File

@ -49,7 +49,7 @@ class SetupController extends Controller
{
$check = SystemHealth::check(false);
if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::all()->first()) {
if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::first()) {
return redirect('/');
}
@ -211,29 +211,31 @@ class SetupController extends Controller
public function checkPdf(Request $request)
{
try {
if (config('ninja.pdf_generator') == 'phantom') {
return $this->testPhantom();
}
$pdf = new Snappdf();
// if (config('ninja.pdf_generator') == 'phantom') {
// return $this->testPhantom();
// }
if (config('ninja.snappdf_chromium_path')) {
$pdf->setChromiumPath(config('ninja.snappdf_chromium_path'));
}
// $pdf = new Snappdf();
if (config('ninja.snappdf_chromium_arguments')) {
$pdf->clearChromiumArguments();
$pdf->addChromiumArguments(config('ninja.snappdf_chromium_arguments'));
}
// if (config('ninja.snappdf_chromium_path')) {
// $pdf->setChromiumPath(config('ninja.snappdf_chromium_path'));
// }
$pdf = $pdf
->setHtml('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!')
->generate();
// if (config('ninja.snappdf_chromium_arguments')) {
// $pdf->clearChromiumArguments();
// $pdf->addChromiumArguments(config('ninja.snappdf_chromium_arguments'));
// }
Storage::disk(config('filesystems.default'))->put('test.pdf', $pdf);
Storage::disk('local')->put('test.pdf', $pdf);
// $pdf = $pdf
// ->setHtml('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!')
// ->generate();
return response(['url' => Storage::disk('local')->url('test.pdf')], 200);
// Storage::disk(config('filesystems.default'))->put('test.pdf', $pdf);
// Storage::disk('local')->put('test.pdf', $pdf);
return response(['url' => ''], 200);
// return response(['url' => Storage::disk('local')->url('test.pdf')], 200);
} catch (Exception $e) {
nlog($e->getMessage());

View 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());
}
}

View File

@ -362,6 +362,7 @@ class BillingPortalPurchase extends Component
->service()
->markSent()
->fillDefaults()
->adjustInventory()
->save();
Cache::put($this->hash, [

View File

@ -60,6 +60,8 @@ class CheckClientExistence
session()->put('multiple_contacts', $multiple_contacts);
session()->put('is_silent', request()->has('silent'));
return $next($request);
}
}

View File

@ -23,7 +23,7 @@ class ShowInvoiceRequest extends Request
*/
public function authorize() : bool
{
return auth()->guard('contact')->user()->client_id === $this->invoice->client_id
return auth()->guard('contact')->user()->client_id === (int)$this->invoice->client_id
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES;
}
}

View File

@ -19,7 +19,7 @@ class ShowQuoteRequest extends FormRequest
{
public function authorize()
{
return auth()->guard('contact')->user()->client->id === $this->quote->client_id
return auth()->guard('contact')->user()->client->id === (int)$this->quote->client_id
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES;
}

View File

@ -74,6 +74,9 @@ class StorePaymentRequest extends Request
}
}
// if (array_key_exists('amount', $input))
// $input['amount'] = 0;
if (isset($input['credits']) && is_array($input['credits']) === false) {
$input['credits'] = null;
}
@ -94,8 +97,7 @@ class StorePaymentRequest extends Request
public function rules()
{
$rules = [
'amount' => 'sometimes|numeric',
'amount' => [new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()],
'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()],
'client_id' => 'bail|required|exists:clients,id',
'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id',
'invoices.*.amount' => 'bail|required',

View File

@ -38,9 +38,12 @@ class StoreProductRequest extends Request
$rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
$rules['cost'] = 'numeric';
$rules['price'] = 'numeric';
$rules['quantity'] = 'numeric';
$rules['cost'] = 'sometimes|numeric';
$rules['price'] = 'sometimes|numeric';
$rules['quantity'] = 'sometimes|numeric';
$rules['in_stock_quantity'] = 'sometimes|numeric';
$rules['stock_notification_threshold'] = 'sometimes|numeric';
$rules['stock_notification'] = 'sometimes|bool';
return $rules;
}

View File

@ -44,6 +44,9 @@ class UpdateProductRequest extends Request
$rules['cost'] = 'numeric';
$rules['price'] = 'numeric';
$rules['quantity'] = 'numeric';
$rules['in_stock_quantity'] = 'sometimes|numeric';
$rules['stock_notification_threshold'] = 'sometimes|numeric';
$rules['stock_notification'] = 'sometimes|bool';
return $rules;
}
@ -60,6 +63,13 @@ class UpdateProductRequest extends Request
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if(array_key_exists('in_stock_quantity', $input) && request()->has('update_in_stock_quantity') && request()->input('update_in_stock_quantity') == 'true'){
}
elseif(array_key_exists('in_stock_quantity', $input)){
unset($input['in_stock_quantity']);
}
$this->replace($input);
}
}

View File

@ -0,0 +1,61 @@
<?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;
class ActionPurchaseOrderRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
private $error_msg;
// private $invoice;
public function authorize() : bool
{
return auth()->user()->can('edit', $this->purchase_order);
}
public function rules()
{
return [
'action' => 'required'
];
}
protected function prepareForValidation()
{
$input = $this->all();
if($this->action){
$input['action'] = $this->action;
} elseif (!array_key_exists('action', $input) ) {
$this->error_msg = 'Action is a required field';
}
$this->replace($input);
}
public function messages()
{
return [
'action' => $this->error_msg,
];
}
}

View File

@ -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 [
//
];
}
}

View 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 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View File

@ -0,0 +1,62 @@
<?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['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
$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);
}
}

View File

@ -0,0 +1,62 @@
<?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);
}
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -31,9 +31,25 @@ class GenericReportRequest extends Request
'start_date' => 'string|date',
'end_date' => 'string|date',
'date_key' => 'string',
'date_range' => 'string',
'date_range' => 'sometimes|string',
'report_keys' => 'present|array',
'send_email' => 'bool',
'send_email' => 'required|bool',
];
}
public function prepareForValidation()
{
$input = $this->all();
if(!array_key_exists('date_range', $input))
$input['date_range'] = 'all';
if(!array_key_exists('report_keys', $input))
$input['report_keys'] = [];
if(!array_key_exists('send_email', $input))
$input['send_email'] = true;
$this->replace($input);
}
}

View File

@ -33,8 +33,18 @@ class ProfitLossRequest extends Request
'is_income_billed' => 'required|bail|bool',
'is_expense_billed' => 'required|bail|bool',
'include_tax' => 'required|bail|bool',
'date_range' => 'required|bail|string',
'date_range' => 'sometimes|string',
'send_email' => 'bool',
];
}
public function prepareForValidation()
{
$input = $this->all();
if(!array_key_exists('date_range', $input))
$input['date_range'] = 'all';
$this->replace($input);
}
}

View File

@ -22,13 +22,6 @@ trait RuntimeFormRequest
$instance = $validator->getValidatorInstance();
return $instance;
// if ($instance->fails()) {
// return $instance->errors();
// }
// $validator->passedValidation();
// return $validator->all();
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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',
];
}
}

View File

@ -62,7 +62,7 @@ class ValidInvoicesRules implements Rule
return false;
}
$inv = Invoice::whereId($invoice['invoice_id'])->first();
$inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first();
if (! $inv) {

View File

@ -562,7 +562,7 @@ class BaseImport
}
}
protected function finalizeImport()
public function finalizeImport()
{
$data = [
'errors' => $this->error_array,

View File

@ -61,9 +61,6 @@ class Csv extends BaseImport implements ImportInterface
$this->{$entity}();
}
//collate any errors
$this->finalizeImport();
}
public function client()

View File

@ -41,7 +41,7 @@ class Freshbooks extends BaseImport
//collate any errors
$this->finalizeImport();
// $this->finalizeImport();
}
public function client()

View File

@ -39,7 +39,7 @@ class Invoice2Go extends BaseImport
//collate any errors
$this->finalizeImport();
// $this->finalizeImport();
}

View File

@ -38,7 +38,7 @@ class Invoicely extends BaseImport
//collate any errors
$this->finalizeImport();
// $this->finalizeImport();
}
public function client()

View File

@ -54,7 +54,7 @@ class Wave extends BaseImport implements ImportInterface
//collate any errors
$this->finalizeImport();
// $this->finalizeImport();
}
public function client()

View File

@ -40,7 +40,7 @@ class Zoho extends BaseImport
//collate any errors
$this->finalizeImport();
// $this->finalizeImport();
}
public function client()

View File

@ -71,6 +71,7 @@ class BaseTransformer
$client_id_search = $this->company
->clients()
->where('is_deleted', false)
->where('id_number', $client_name);
if ($client_id_search->count() >= 1) {
@ -79,6 +80,7 @@ class BaseTransformer
$client_name_search = $this->company
->clients()
->where('is_deleted', false)
->where('name', $client_name);
if ($client_name_search->count() >= 1) {
@ -86,10 +88,11 @@ class BaseTransformer
}
}
if (!empty($client_email)) {
$contacts = ClientContact::where(
'company_id',
$this->company->id
)->where('email', $client_email);
$contacts = ClientContact::whereHas('client', function($query){
$query->where('is_deleted', false);
})
->where('company_id', $this->company->id)
->where('email', $client_email);
if ($contacts->count() >= 1) {
return $contacts->first()->client_id;
@ -109,6 +112,7 @@ class BaseTransformer
{
return $this->company
->clients()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -124,6 +128,7 @@ class BaseTransformer
{
return $this->company
->vendors()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -139,6 +144,7 @@ class BaseTransformer
{
return $this->company
->projects()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -154,6 +160,7 @@ class BaseTransformer
{
return $this->company
->products()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)),
])
@ -186,6 +193,7 @@ class BaseTransformer
{
$client = $this->company
->clients()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -203,6 +211,7 @@ class BaseTransformer
{
$product = $this->company
->products()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)),
])
@ -273,6 +282,7 @@ class BaseTransformer
$tax_rate = $this->company
->tax_rates()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -292,6 +302,7 @@ class BaseTransformer
$tax_rate = $this->company
->tax_rates()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -341,6 +352,7 @@ class BaseTransformer
{
$invoice = $this->company
->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)),
])
@ -358,6 +370,7 @@ class BaseTransformer
{
return $this->company
->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)),
])
@ -371,6 +384,7 @@ class BaseTransformer
{
return $this->company
->expenses()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $expense_number)),
])
@ -386,6 +400,7 @@ class BaseTransformer
{
return $this->company
->quotes()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $quote_number)),
])
@ -401,6 +416,7 @@ class BaseTransformer
{
$invoice = $this->company
->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)),
])
@ -418,6 +434,7 @@ class BaseTransformer
{
$vendor = $this->company
->vendors()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -452,6 +469,7 @@ class BaseTransformer
{
$ec = $this->company
->expense_categories()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])
@ -486,6 +504,7 @@ class BaseTransformer
{
$project = $this->company
->projects()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)),
])

View File

@ -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();
}

View File

@ -1,11 +1,11 @@
<?php
/**
* Entity Ninja (https://entityninja.com).
* Invoice Ninja (https://entityninja.com).
*
* @link https://github.com/entityninja/entityninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Entity Ninja LLC (https://entityninja.com)
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
@ -34,6 +34,8 @@ use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PDF;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -43,10 +45,11 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Storage;
use setasign\Fpdi\PdfParser\StreamReader;
class CreateEntityPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
public $entity;
@ -102,6 +105,7 @@ class CreateEntityPdf implements ShouldQueue
public function handle()
{
MultiDB::setDb($this->company->db);
/* Forget the singleton*/
@ -186,9 +190,23 @@ class CreateEntityPdf implements ShouldQueue
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
} catch (\Exception $e) {
@ -204,9 +222,9 @@ class CreateEntityPdf implements ShouldQueue
try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
}
catch(\Exception $e)
@ -225,4 +243,5 @@ class CreateEntityPdf implements ShouldQueue
}
}

View File

@ -1,5 +1,4 @@
<?php
/**
* Entity Ninja (https://entityninja.com).
*
@ -34,6 +33,7 @@ use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -46,7 +46,7 @@ use Illuminate\Support\Facades\Storage;
class CreateRawPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
public $entity;
@ -177,11 +177,22 @@ class CreateRawPdf implements ShouldQueue
if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
{
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
}
else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
} catch (\Exception $e) {

View File

@ -79,6 +79,8 @@ class CSVIngest implements ShouldQueue {
}
$engine->finalizeImport();
$this->checkContacts();
}

View File

@ -0,0 +1,125 @@
<?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\Inventory;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\InventoryNotificationObject;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Product;
use App\Utils\Traits\NumberFormatter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class AdjustProductInventory implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public Company $company;
public Invoice $invoice;
public array $old_invoice;
public function __construct(Company $company, Invoice $invoice, ?array $old_invoice = [])
{
$this->company = $company;
$this->invoice = $invoice;
$this->old_invoice = $old_invoice;
}
/**
* Execute the job.
*
*
* @return false
*/
public function handle()
{
MultiDB::setDb($this->company->db);
if(count($this->old_invoice) > 0)
$this->existingInventoryAdjustment();
return $this->newInventoryAdjustment();
}
public function middleware()
{
return [new WithoutOverlapping($this->company->company_key)];
}
private function newInventoryAdjustment()
{
$line_items = $this->invoice->line_items;
foreach($line_items as $item)
{
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first();
if(!$p)
continue;
$p->in_stock_quantity -= $item->quantity;
$p->saveQuietly();
if($p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold)
$this->notifyStockLevels($p, 'product');
elseif($this->company->stock_notification_threshold && $p->in_stock_quantity <= $this->company->stock_notification_threshold)
$this->notifyStocklevels($p, 'company');
}
}
private function existingInventoryAdjustment()
{
foreach($this->old_invoice as $item)
{
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first();
if(!$p)
continue;
$p->in_stock_quantity += $item->quantity;
$p->saveQuietly();
}
}
private function notifyStocklevels(Product $product, string $notification_level)
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer( (new InventoryNotificationObject($product, $notification_level))->build() );
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();
NinjaMailerJob::dispatch($nmo);
}
}

View File

@ -47,7 +47,7 @@ class ClientLedgerBalanceUpdate implements ShouldQueue
*/
public function handle() :void
{
nlog("Updating company ledger for client ". $this->client->id);
// nlog("Updating company ledger for client ". $this->client->id);
MultiDB::setDb($this->company->db);
@ -71,14 +71,14 @@ class ClientLedgerBalanceUpdate implements ShouldQueue
}
nlog("Updating Balance NOW");
// nlog("Updating Balance NOW");
$company_ledger->balance = $last_record->balance + $company_ledger->adjustment;
$company_ledger->save();
});
nlog("Updating company ledger for client ". $this->client->id);
// nlog("Updating company ledger for client ". $this->client->id);
}

View File

@ -49,8 +49,8 @@ class SystemMaintenance implements ShouldQueue
nlog("Starting System Maintenance");
// if(Ninja::isHosted())
// return;
if(Ninja::isHosted())
return;
$delete_pdf_days = config('ninja.maintenance.delete_pdfs');

View 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();
}
}

View File

@ -0,0 +1,102 @@
<?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\PurchaseOrder;
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Engine\PurchaseOrderEmailEngine;
use App\Mail\VendorTemplateEmail;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Utils\Ninja;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
class PurchaseOrderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public PurchaseOrder $purchase_order;
public Company $company;
public $template_data;
public $tries = 1;
public function __construct(PurchaseOrder $purchase_order, Company $company, $template_data = null)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->template_data = $template_data;
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$this->purchase_order->last_sent_date = now();
$this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) {
/* Don't fire emails if the company is disabled */
if ($this->company->is_disabled)
return true;
/* Set DB */
MultiDB::setDB($this->company->db);
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($invitation->contact->preferredLocale());
$t->replace(Ninja::transformTranslations($this->company->settings));
/* Mark entity sent */
$invitation->purchase_order->service()->markSent()->save();
$email_builder = (new PurchaseOrderEmailEngine($invitation, 'purchase_order', $this->template_data))->build();
$nmo = new NinjaMailerObject;
$nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $invitation->contact;
$nmo->entity_string = 'purchase_order';
$nmo->invitation = $invitation;
$nmo->reminder_template = 'purchase_order';
$nmo->entity = $invitation->purchase_order;
NinjaMailerJob::dispatchNow($nmo);
});
if ($this->purchase_order->invitations->count() >= 1) {
event(new PurchaseOrderWasEmailed($this->purchase_order->invitations->first(), $this->purchase_order->invitations->first()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
}
}

View File

@ -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
*/
namespace App\Jobs\PurchaseOrder;
use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\UnlinkFile;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Libraries\MultiDB;
use App\Mail\DownloadInvoices;
use App\Mail\DownloadPurchaseOrders;
use App\Models\Company;
use App\Models\User;
use App\Utils\TempFile;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use ZipArchive;
class ZipPurchaseOrders implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $purchase_orders;
private $company;
private $user;
public $settings;
public $tries = 1;
/**
* @param $purchase_orders
* @param Company $company
* @param $email
* @deprecated confirm to be deleted
* Create a new job instance.
*
*/
public function __construct($purchase_orders, Company $company, User $user)
{
$this->purchase_orders = $purchase_orders;
$this->company = $company;
$this->user = $user;
$this->settings = $company->settings;
}
/**
* Execute the job.
*
* @return void
* @throws \ZipStream\Exception\FileNotFoundException
* @throws \ZipStream\Exception\FileNotReadableException
* @throws \ZipStream\Exception\OverflowException
*/
public function handle()
{
MultiDB::setDb($this->company->db);
# create new zip object
$zipFile = new \PhpZip\ZipFile();
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
$invitation = $this->purchase_orders->first()->invitations->first();
$path = $this->purchase_orders->first()->vendor->purchase_order_filepath($invitation);
$this->purchase_orders->each(function ($purchase_order){
CreatePurchaseOrderPdf::dispatchNow($purchase_order->invitations()->first());
});
try{
foreach ($this->purchase_orders as $purchase_order) {
$file = $purchase_order->service()->getPurchaseOrderPdf();
$zip_file_name = basename($file);
$zipFile->addFromString($zip_file_name, Storage::get($file));
}
Storage::put($path.$file_name, $zipFile->outputAsString());
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadPurchaseOrders(Storage::url($path.$file_name), $this->company);
$nmo->to_user = $this->user;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
}
catch(\PhpZip\Exception\ZipException $e){
nlog("could not make zip => ". $e->getMessage());
}
finally{
$zipFile->close();
}
}
}

View File

@ -87,6 +87,7 @@ class SendRecurring implements ShouldQueue
->applyNumber()
//->createInvitations() //need to only link invitations to those in the recurring invoice
->fillDefaults()
->adjustInventory()
->save();
}
@ -105,6 +106,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();

View File

@ -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();

View File

@ -89,6 +89,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\UploadedFile;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
@ -187,6 +188,11 @@ class Import implements ShouldQueue
$this->resources = $resources;
}
public function middleware()
{
return [new WithoutOverlapping($this->company->account->key)];
}
/**
* Execute the job.
*

View File

@ -12,6 +12,7 @@
namespace App\Jobs\Util;
use App\Models\Company;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -21,7 +22,7 @@ use Illuminate\Queue\SerializesModels;
class PreviewPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, PdfMaker;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, PdfMaker, PageNumbering;
public $company;
@ -46,6 +47,13 @@ class PreviewPdf implements ShouldQueue
public function handle()
{
return $this->makePdf(null, null, $this->design_string);
$pdf = $this->makePdf(null, null, $this->design_string);
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
}

View File

@ -0,0 +1,221 @@
<?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\Vendor;
use App\Exceptions\FilePermissionsFailure;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PDF;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\VendorHtmlEngine;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Storage;
use setasign\Fpdi\PdfParser\StreamReader;
class CreatePurchaseOrderPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
public $entity;
public $company;
public $contact;
private $disk;
public $invitation;
public $entity_string = '';
public $vendor;
/**
* Create a new job instance.
*
* @param $invitation
*/
public function __construct($invitation, $disk = 'public')
{
$this->invitation = $invitation;
$this->company = $invitation->company;
$this->entity = $invitation->purchase_order;
$this->entity_string = 'purchase_order';
$this->contact = $invitation->contact;
$this->vendor = $invitation->contact->vendor;
$this->vendor->load('company');
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
}
public function handle()
{
MultiDB::setDb($this->company->db);
/* Forget the singleton*/
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->locale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation);
}
$entity_design_id = '';
$path = $this->vendor->purchase_order_filepath($this->invitation);
$entity_design_id = 'purchase_order_design_id';
$file_path = $path.$this->entity->numberFormatter().'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
$design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if(!$design)
$design = Design::find(2);
$html = new VendorHtmlEngine($this->invitation);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true)
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => null,
'vendor' => $this->vendor,
'entity' => $this->entity,
'pdf_variables' => (array) $this->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $this->entity->company->getSetting('all_pages_header'),
'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'),
],
'process_markdown' => $this->entity->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
}
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
if ($pdf) {
try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
}
catch(\Exception $e)
{
throw new FilePermissionsFailure($e->getMessage());
}
}
return $file_path;
}
public function failed($e)
{
}
}

View File

@ -92,6 +92,9 @@ class PaymentNotification implements ShouldQueue
$analytics_id = $company->google_analytics_key;
if(!strlen($analytics_id) > 2)
return;
$client = $payment->client;
$amount = $payment->amount;
@ -124,6 +127,7 @@ class PaymentNotification implements ShouldQueue
*/
private function sendAnalytics($data)
{
$data = utf8_encode($data);
$curl = curl_init();

View File

@ -0,0 +1,58 @@
<?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\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class CreatePurchaseOrderActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::CREATE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,59 @@
<?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\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderArchivedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::ARCHIVE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,58 @@
<?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\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderDeletedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::DELETE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://purchase_orderninja.com).
*
* @link https://github.com/purchase_orderninja/purchase_orderninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://purchase_orderninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderEmailActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->invitation->purchase_order->id;
$fields->company_id = $event->invitation->purchase_order->company_id;
$fields->vendor_contact_id = $event->invitation->purchase_order->vendor_contact_id;
$fields->vendor_id = $event->invitation->purchase_order->vendor_id;
$fields->activity_type_id = Activity::EMAIL_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->invitation->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,58 @@
<?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\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderRestoredActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::RESTORE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,62 @@
<?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\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderViewedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->purchase_order->user_id;
$event->invitation->purchase_order->service()->markSent()->save();
$fields->user_id = $user_id;
$fields->company_id = $event->invitation->company_id;
$fields->activity_type_id = Activity::VIEW_PURCHASE_ORDER;
$fields->vendor_id = $event->invitation->purchase_order->vendor_id;
$fields->vendor_contact_id = $event->invitation->vendor_contact_id;
$fields->invitation_id = $event->invitation->id;
$fields->purchase_order_id = $event->invitation->purchase_order_id;
$this->activity_repo->save($fields, $event->invitation->purchase_order, $event->event_vars);
}
}

Some files were not shown because too many files have changed in this diff Show More