Multiple fixes and features (#3411)

* Performance improvements for seeding

* Differentiating between system notification and user notifications

* Remove hard coded webhook url

* Working on system and user notifications

* notifications

* Set the currency on client if blank

* Refactor for inserting invoice defaults

* Refactor Default Invoice/Quote/Credit objects

* working on credits

* Implement mark_sent for quotes and credits
This commit is contained in:
David Bomba 2020-03-03 20:44:26 +11:00 committed by GitHub
parent cf345b1932
commit 1393179160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1784 additions and 213 deletions

View File

@ -5,9 +5,6 @@ APP_DEBUG=true
APP_URL=http://localhost
USER_AUTH_PROVIDER=users
CONTACT_AUTH_PROVIDER=contacts
DB_CONNECTION=db-ninja-01
MULTI_DB_ENABLED=true

View File

@ -19,6 +19,5 @@ TRAVIS=true
API_SECRET=password
TEST_USERNAME=user@example.com
TEST_PERMISSIONS_USERNAME=permissions@example.com
USER_AUTH_PROVIDER=users
CONTACT_AUTH_PROVIDER=contacts
MULTI_DB_ENABLED=true

View File

@ -2,6 +2,9 @@
namespace App\Console\Commands;
use App\Console\Commands\TestData\CreateTestCreditJob;
use App\Console\Commands\TestData\CreateTestInvoiceJob;
use App\Console\Commands\TestData\CreateTestQuoteJob;
use App\DataMapper\DefaultSettings;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasMarkedSent;
@ -81,15 +84,9 @@ class CreateTestData extends Command
$account = factory(\App\Models\Account::class)->create();
$company = factory(\App\Models\Company::class)->create([
'account_id' => $account->id,
'slack_webhook_url' => config('ninja.notification.slack'),
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -172,17 +169,9 @@ class CreateTestData extends Command
$account = factory(\App\Models\Account::class)->create();
$company = factory(\App\Models\Company::class)->create([
'account_id' => $account->id,
'slack_webhook_url' => config('ninja.notification.slack'),
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -279,16 +268,9 @@ class CreateTestData extends Command
$account = factory(\App\Models\Account::class)->create();
$company = factory(\App\Models\Company::class)->create([
'account_id' => $account->id,
'slack_webhook_url' => config('ninja.notification.slack'),
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -367,24 +349,28 @@ class CreateTestData extends Command
private function createClient($company, $user)
{
$client = factory(\App\Models\Client::class)->create([
'user_id' => $user->id,
'company_id' => $company->id
]);
factory(\App\Models\ClientContact::class, 1)->create([
// dispatch(function () use ($company, $user) {
// });
$client = factory(\App\Models\Client::class)->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class, rand(1, 5))->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id
]);
factory(\App\Models\ClientContact::class, 1)->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class, rand(1, 5))->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id
]);
}
@ -445,149 +431,162 @@ class CreateTestData extends Command
private function createInvoice($client)
{
$faker = \Faker\Factory::create();
$invoice = InvoiceFactory::create($client->company->id, $client->user->id, $client->getMergedSettings(), $client);//stub the company and user_id
$invoice->client_id = $client->id;
// $invoice->date = $faker->date();
$dateable = Carbon::now()->subDays(rand(0,90));
$invoice->date = $dateable;
$invoice->line_items = $this->buildLineItems(rand(1,10));
$invoice->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$invoice->tax_name1 = 'GST';
$invoice->tax_rate1 = 10.00;
for($x=0; $x<$this->count; $x++){
dispatch(new CreateTestInvoiceJob($client));
}
if (rand(0, 1)) {
$invoice->tax_name2 = 'VAT';
$invoice->tax_rate2 = 17.50;
}
// $faker = \Faker\Factory::create();
if (rand(0, 1)) {
$invoice->tax_name3 = 'CA Sales Tax';
$invoice->tax_rate3 = 5;
}
// $invoice = InvoiceFactory::create($client->company->id, $client->user->id);//stub the company and user_id
// $invoice->client_id = $client->id;
// // $invoice->date = $faker->date();
// $dateable = Carbon::now()->subDays(rand(0,90));
// $invoice->date = $dateable;
$invoice->save();
// $invoice->line_items = $this->buildLineItems(rand(1,10));
// $invoice->uses_inclusive_taxes = false;
$invoice_calc = new InvoiceSum($invoice);
$invoice_calc->build();
// if (rand(0, 1)) {
// $invoice->tax_name1 = 'GST';
// $invoice->tax_rate1 = 10.00;
// }
$invoice = $invoice_calc->getInvoice();
// if (rand(0, 1)) {
// $invoice->tax_name2 = 'VAT';
// $invoice->tax_rate2 = 17.50;
// }
$invoice->save();
$invoice->service()->createInvitations();
// if (rand(0, 1)) {
// $invoice->tax_name3 = 'CA Sales Tax';
// $invoice->tax_rate3 = 5;
// }
$invoice->ledger()->updateInvoiceBalance($invoice->balance);
// $invoice->save();
//UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance, $invoice->company);
// $invoice_calc = new InvoiceSum($invoice);
// $invoice_calc->build();
$this->invoice_repo->markSent($invoice);
// $invoice = $invoice_calc->getInvoice();
$invoice->service()->createInvitations();
// $invoice->save();
// $invoice->service()->createInvitations();
if (rand(0, 1)) {
$payment = PaymentFactory::create($client->company->id, $client->user->id);
$payment->date = $dateable;
$payment->client_id = $client->id;
$payment->amount = $invoice->balance;
$payment->transaction_reference = rand(0, 500);
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->number = $client->getNextPaymentNumber($client);
$payment->save();
// $invoice->ledger()->updateInvoiceBalance($invoice->balance);
$payment->invoices()->save($invoice);
// //UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance, $invoice->company);
event(new PaymentWasCreated($payment, $payment->company));
// $this->invoice_repo->markSent($invoice);
$payment->service()->updateInvoicePayment();
//UpdateInvoicePayment::dispatchNow($payment, $payment->company);
}
//@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging.
event(new InvoiceWasCreated($invoice, $invoice->company));
// $invoice->service()->createInvitations();
// if (rand(0, 1)) {
// $payment = PaymentFactory::create($client->company->id, $client->user->id);
// $payment->date = $dateable;
// $payment->client_id = $client->id;
// $payment->amount = $invoice->balance;
// $payment->transaction_reference = rand(0, 500);
// $payment->type_id = PaymentType::CREDIT_CARD_OTHER;
// $payment->status_id = Payment::STATUS_COMPLETED;
// $payment->number = $client->getNextPaymentNumber($client);
// $payment->save();
// $payment->invoices()->save($invoice);
// event(new PaymentWasCreated($payment, $payment->company));
// $payment->service()->updateInvoicePayment();
// //UpdateInvoicePayment::dispatchNow($payment, $payment->company);
// }
// //@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging.
// event(new InvoiceWasCreated($invoice, $invoice->company));
}
private function createCredit($client)
{
$faker = \Faker\Factory::create();
for($x=0; $x<$this->count; $x++){
$credit = factory(\App\Models\Credit::class)->create(['user_id' => $client->user->id, 'company_id' => $client->company->id, 'client_id' => $client->id]);
dispatch(new CreateTestCreditJob($client));
//$invoice = InvoiceFactory::create($client->company->id, $client->user->id);//stub the company and user_id
//$invoice->client_id = $client->id;
// $invoice->date = $faker->date();
$dateable = Carbon::now()->subDays(rand(0,90));
$credit->date = $dateable;
$credit->line_items = $this->buildLineItems(rand(1,10));
$credit->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$credit->tax_name1 = 'GST';
$credit->tax_rate1 = 10.00;
}
// $faker = \Faker\Factory::create();
if (rand(0, 1)) {
$credit->tax_name2 = 'VAT';
$credit->tax_rate2 = 17.50;
}
// $credit = factory(\App\Models\Credit::class)->create(['user_id' => $client->user->id, 'company_id' => $client->company->id, 'client_id' => $client->id]);
if (rand(0, 1)) {
$credit->tax_name3 = 'CA Sales Tax';
$credit->tax_rate3 = 5;
}
// //$invoice = InvoiceFactory::create($client->company->id, $client->user->id);//stub the company and user_id
// //$invoice->client_id = $client->id;
// // $invoice->date = $faker->date();
// $dateable = Carbon::now()->subDays(rand(0,90));
// $credit->date = $dateable;
$credit->save();
// $credit->line_items = $this->buildLineItems(rand(1,10));
// $credit->uses_inclusive_taxes = false;
$invoice_calc = new InvoiceSum($credit);
$invoice_calc->build();
// if (rand(0, 1)) {
// $credit->tax_name1 = 'GST';
// $credit->tax_rate1 = 10.00;
// }
$credit = $invoice_calc->getCredit();
// if (rand(0, 1)) {
// $credit->tax_name2 = 'VAT';
// $credit->tax_rate2 = 17.50;
// }
$credit->save();
// if (rand(0, 1)) {
// $credit->tax_name3 = 'CA Sales Tax';
// $credit->tax_rate3 = 5;
// }
event(new CreateCreditInvitation($credit));
// $credit->save();
// $invoice_calc = new InvoiceSum($credit);
// $invoice_calc->build();
// $credit = $invoice_calc->getCredit();
// $credit->save();
// event(new CreateCreditInvitation($credit));
}
private function createQuote($client)
{
$faker = \Faker\Factory::create();
for($x=0; $x<$this->count; $x++){
$quote =factory(\App\Models\Quote::class)->create(['user_id' => $client->user->id, 'company_id' => $client->company->id, 'client_id' => $client->id]);
$quote->date = $faker->date();
$quote->line_items = $this->buildLineItems(rand(1,10));
$quote->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$quote->tax_name1 = 'GST';
$quote->tax_rate1 = 10.00;
dispatch(new CreateTestQuoteJob($client));
}
// $faker = \Faker\Factory::create();
if (rand(0, 1)) {
$quote->tax_name2 = 'VAT';
$quote->tax_rate2 = 17.50;
}
// $quote =factory(\App\Models\Quote::class)->create(['user_id' => $client->user->id, 'company_id' => $client->company->id, 'client_id' => $client->id]);
// $quote->date = $faker->date();
if (rand(0, 1)) {
$quote->tax_name3 = 'CA Sales Tax';
$quote->tax_rate3 = 5;
}
// $quote->line_items = $this->buildLineItems(rand(1,10));
// $quote->uses_inclusive_taxes = false;
$quote->save();
// if (rand(0, 1)) {
// $quote->tax_name1 = 'GST';
// $quote->tax_rate1 = 10.00;
// }
$quote_calc = new InvoiceSum($quote);
$quote_calc->build();
// if (rand(0, 1)) {
// $quote->tax_name2 = 'VAT';
// $quote->tax_rate2 = 17.50;
// }
$quote = $quote_calc->getQuote();
$quote->service()->markSent()->save();
// if (rand(0, 1)) {
// $quote->tax_name3 = 'CA Sales Tax';
// $quote->tax_rate3 = 5;
// }
CreateQuoteInvitations::dispatch($quote, $quote->company);
// $quote->save();
// $quote_calc = new InvoiceSum($quote);
// $quote_calc->build();
// $quote = $quote_calc->getQuote();
// $quote->service()->markSent()->save();
// CreateQuoteInvitations::dispatch($quote, $quote->company);
}
private function buildLineItems($count = 1)

View File

@ -131,7 +131,7 @@ class SendTestEmails extends Command
]);
}
$invoice = InvoiceFactory::create($company->id, $user->id, $client->getMergedSettings(), $client);
$invoice = InvoiceFactory::create($company->id, $user->id);
$invoice->client_id = $client->id;
$invoice->setRelation('client', $client);
$invoice->save();

View File

@ -0,0 +1,142 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands\TestData;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\PaymentFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Listeners\Credit\CreateCreditInvitation;
use App\Models\Client;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\Product;
use App\Utils\Traits\MakesHash;
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\Carbon;
class CreateTestCreditJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
protected $client;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$faker = \Faker\Factory::create();
$credit = factory(\App\Models\Credit::class)->create(['user_id' => $this->client->user->id, 'company_id' => $this->client->company->id, 'client_id' => $this->client->id]);
//$invoice = InvoiceFactory::create($this->client->company->id, $this->client->user->id);//stub the company and user_id
//$invoice->client_id = $this->client->id;
// $invoice->date = $faker->date();
$dateable = Carbon::now()->subDays(rand(0,90));
$credit->date = $dateable;
$credit->line_items = $this->buildLineItems(rand(1,10));
$credit->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$credit->tax_name1 = 'GST';
$credit->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$credit->tax_name2 = 'VAT';
$credit->tax_rate2 = 17.50;
}
if (rand(0, 1)) {
$credit->tax_name3 = 'CA Sales Tax';
$credit->tax_rate3 = 5;
}
$credit->save();
$invoice_calc = new InvoiceSum($credit);
$invoice_calc->build();
$credit = $invoice_calc->getCredit();
$credit->save();
event(new CreateCreditInvitation($credit));
}
private function buildLineItems($count = 1)
{
$line_items = [];
for($x=0; $x<$count; $x++)
{
$item = InvoiceItemFactory::create();
$item->quantity = 1;
//$item->cost = 10;
if (rand(0, 1)) {
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$item->tax_name1 = 'VAT';
$item->tax_rate1 = 17.50;
}
if (rand(0, 1)) {
$item->tax_name1 = 'Sales Tax';
$item->tax_rate1 = 5;
}
$product = Product::all()->random();
$item->cost = (float)$product->cost;
$item->product_key = $product->product_key;
$item->notes = $product->notes;
$item->custom_value1 = $product->custom_value1;
$item->custom_value2 = $product->custom_value2;
$item->custom_value3 = $product->custom_value3;
$item->custom_value4 = $product->custom_value4;
$line_items[] = $item;
}
return $line_items;
}
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands\TestData;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\PaymentFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Models\Client;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\Product;
use App\Utils\Traits\MakesHash;
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\Carbon;
class CreateTestInvoiceJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
protected $client;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$faker = \Faker\Factory::create();
$invoice = InvoiceFactory::create($this->client->company->id, $this->client->user->id);//stub the company and user_id
$invoice->client_id = $this->client->id;
// $invoice->date = $faker->date();
$dateable = Carbon::now()->subDays(rand(0,90));
$invoice->date = $dateable;
$invoice->line_items = $this->buildLineItems(rand(1,10));
$invoice->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$invoice->tax_name1 = 'GST';
$invoice->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$invoice->tax_name2 = 'VAT';
$invoice->tax_rate2 = 17.50;
}
if (rand(0, 1)) {
$invoice->tax_name3 = 'CA Sales Tax';
$invoice->tax_rate3 = 5;
}
$invoice->save();
$invoice_calc = new InvoiceSum($invoice);
$invoice_calc->build();
$invoice = $invoice_calc->getInvoice();
$invoice->save();
$invoice->service()->createInvitations()->markSent()->save();
$invoice->ledger()->updateInvoiceBalance($invoice->balance);
//UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance, $invoice->company);
//$this->invoice_repo->markSent($invoice);
if (rand(0, 1)) {
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
$payment->date = $dateable;
$payment->client_id = $this->client->id;
$payment->amount = $invoice->balance;
$payment->transaction_reference = rand(0, 500);
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->number = $this->client->getNextPaymentNumber($this->client);
$payment->save();
$payment->invoices()->save($invoice);
event(new PaymentWasCreated($payment, $payment->company));
$payment->service()->updateInvoicePayment();
//UpdateInvoicePayment::dispatchNow($payment, $payment->company);
}
//@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging.
event(new InvoiceWasCreated($invoice, $invoice->company));
}
private function buildLineItems($count = 1)
{
$line_items = [];
for($x=0; $x<$count; $x++)
{
$item = InvoiceItemFactory::create();
$item->quantity = 1;
//$item->cost = 10;
if (rand(0, 1)) {
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$item->tax_name1 = 'VAT';
$item->tax_rate1 = 17.50;
}
if (rand(0, 1)) {
$item->tax_name1 = 'Sales Tax';
$item->tax_rate1 = 5;
}
$product = Product::all()->random();
$item->cost = (float)$product->cost;
$item->product_key = $product->product_key;
$item->notes = $product->notes;
$item->custom_value1 = $product->custom_value1;
$item->custom_value2 = $product->custom_value2;
$item->custom_value3 = $product->custom_value3;
$item->custom_value4 = $product->custom_value4;
$line_items[] = $item;
}
return $line_items;
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands\TestData;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\PaymentFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Quote\CreateQuoteInvitations;
use App\Models\Client;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\Product;
use App\Utils\Traits\MakesHash;
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\Carbon;
class CreateTestQuoteJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
protected $client;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$faker = \Faker\Factory::create();
$quote =factory(\App\Models\Quote::class)->create(['user_id' => $this->client->user->id, 'company_id' => $this->client->company->id, 'client_id' => $this->client->id]);
$quote->date = $faker->date();
$quote->line_items = $this->buildLineItems(rand(1,10));
$quote->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$quote->tax_name1 = 'GST';
$quote->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$quote->tax_name2 = 'VAT';
$quote->tax_rate2 = 17.50;
}
if (rand(0, 1)) {
$quote->tax_name3 = 'CA Sales Tax';
$quote->tax_rate3 = 5;
}
$quote->save();
$quote_calc = new InvoiceSum($quote);
$quote_calc->build();
$quote = $quote_calc->getQuote();
$quote->service()->markSent()->save();
CreateQuoteInvitations::dispatch($quote, $quote->company);
}
private function buildLineItems($count = 1)
{
$line_items = [];
for($x=0; $x<$count; $x++)
{
$item = InvoiceItemFactory::create();
$item->quantity = 1;
//$item->cost = 10;
if (rand(0, 1)) {
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$item->tax_name1 = 'VAT';
$item->tax_rate1 = 17.50;
}
if (rand(0, 1)) {
$item->tax_name1 = 'Sales Tax';
$item->tax_rate1 = 5;
}
$product = Product::all()->random();
$item->cost = (float)$product->cost;
$item->product_key = $product->product_key;
$item->notes = $product->notes;
$item->custom_value1 = $product->custom_value1;
$item->custom_value2 = $product->custom_value2;
$item->custom_value3 = $product->custom_value3;
$item->custom_value4 = $product->custom_value4;
$line_items[] = $item;
}
return $line_items;
}
}

View File

@ -114,6 +114,7 @@ class CompanySettings extends BaseSettings {
public $enabled_item_tax_rates = 0;
public $invoice_design_id = '1';
public $quote_design_id = '1';
public $credit_design_id = '1';
public $invoice_footer = '';
public $credit_footer = '';
public $credit_terms = '';
@ -345,6 +346,7 @@ class CompanySettings extends BaseSettings {
'phone' => 'string',
'postal_code' => 'string',
'quote_design_id' => 'string',
'credit_design_id' => 'string',
'quote_number_pattern' => 'string',
'quote_number_counter' => 'integer',
'quote_terms' => 'string',

View File

@ -34,6 +34,7 @@ class CompanyFactory
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
$company->custom_fields = (object) [];
$company->subdomain = '';
$company->enabled_modules = 2047;
return $company;
}

View File

@ -24,9 +24,9 @@ class CreditFactory
$credit->discount = 0;
$credit->is_amount_discount = true;
$credit->po_number = '';
$credit->footer = isset($settings) && strlen($settings->credit_footer) > 0 ? $settings->credit_footer : '';
$credit->terms = isset($settings) && strlen($settings->credit_terms) > 0 ? $settings->credit_terms : '';
$credit->public_notes = isset($client) && strlen($client->public_notes) > 0 ? $client->public_notes : '';
$credit->footer = '';
$credit->terms = '';
$credit->public_notes = '';
$credit->private_notes = '';
$credit->date = null;
$credit->due_date = null;

View File

@ -19,7 +19,7 @@ use Illuminate\Support\Facades\Log;
class InvoiceFactory
{
public static function create(int $company_id, int $user_id, object $settings = null, Client $client = null) :Invoice
public static function create(int $company_id, int $user_id) :Invoice
{
$invoice = new Invoice();
$invoice->status_id = Invoice::STATUS_DRAFT;
@ -27,9 +27,9 @@ class InvoiceFactory
$invoice->discount = 0;
$invoice->is_amount_discount = true;
$invoice->po_number = '';
$invoice->footer = isset($settings) && strlen($settings->invoice_footer) > 0 ? $settings->invoice_footer : '';
$invoice->terms = isset($settings) && strlen($settings->invoice_terms) > 0 ? $settings->invoice_terms : '';
$invoice->public_notes = isset($settings) && strlen($client->public_notes) > 0 ? $client->public_notes : '';
$invoice->footer = '';
$invoice->terms = '';
$invoice->public_notes = '';
$invoice->private_notes = '';
$invoice->date = null;
$invoice->due_date = null;

View File

@ -19,7 +19,7 @@ use Illuminate\Support\Facades\Log;
class QuoteFactory
{
public static function create(int $company_id, int $user_id, object $settings = null, Client $client = null) :Quote
public static function create(int $company_id, int $user_id) :Quote
{
$quote = new Quote();
$quote->status_id = Quote::STATUS_DRAFT;
@ -27,9 +27,9 @@ class QuoteFactory
$quote->discount = 0;
$quote->is_amount_discount = true;
$quote->po_number = '';
$quote->footer = isset($settings) && strlen($settings->quote_footer) > 0 ? $settings->quote_footer : '';
$quote->terms = isset($settings) && strlen($settings->quote_terms) > 0 ? $settings->quote_terms : '';
$quote->public_notes = isset($client) && strlen($client->public_notes) > 0 ? $client->public_notes : '';
$quote->footer = '';
$quote->terms = '';
$quote->public_notes = '';
$quote->private_notes = '';
$quote->date = null;
$quote->due_date = null;

View File

@ -288,6 +288,7 @@ class BaseController extends Controller
'company.invoices',
'company.payments.paymentables',
'company.quotes',
'company.credits',
'company.vendors.contacts',
'company.expenses',
'company.tasks',

View File

@ -18,11 +18,17 @@ use App\Http\Requests\Credit\UpdateCreditRequest;
use App\Jobs\Credit\StoreCredit;
use App\Jobs\Invoice\EmailCredit;
use App\Jobs\Invoice\MarkInvoicePaid;
use App\Models\Client;
use App\Models\Credit;
use App\Repositories\CreditRepository;
use App\Transformers\CreditTransformer;
use App\Utils\Traits\MakesHash;
/**
* Class CreditController
* @package App\Http\Controllers\CreditController
*/
class CreditController extends BaseController
{
use MakesHash;
@ -40,6 +46,48 @@ class CreditController extends BaseController
$this->credit_repository = $credit_repository;
}
/**
* Show the list of Credits
*
* @param \App\Filters\CreditFilters $filters The filters
*
* @return \Illuminate\Http\Response
*
* @OA\Get(
* path="/api/v1/credits",
* operationId="getCredits",
* tags={"invoices"},
* summary="Gets a list of credits",
* description="Lists credits, search and filters allow fine grained lists to be generated.
*
* Query parameters can be added to performed more fine grained filtering of the credits, these are handled by the CreditFilters 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 credits",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(CreditFilters $filters)
{
$credits = Credit::filter($filters);
@ -47,6 +95,46 @@ class CreditController extends BaseController
return $this->listResponse($credits);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Http\Requests\Credit\CreateCreditRequest $request The request
*
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/credits/create",
* operationId="getCreditsCreate",
* tags={"credits"},
* summary="Gets a new blank credit 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 credit object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(CreateCreditRequest $request)
{
$credit = CreditFactory::create(auth()->user()->company()->id, auth()->user()->id);
@ -54,9 +142,51 @@ class CreditController extends BaseController
return $this->itemResponse($credit);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Credit\StoreCreditRequest $request The request
*
* @return \Illuminate\Http\Response
*
*
* @OA\Post(
* path="/api/v1/credits",
* operationId="storeCredit",
* tags={"credits"},
* summary="Adds a credit",
* description="Adds an credit 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 credit object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(StoreCreditRequest $request)
{
$credit = $this->credit_repository->save($request->all(), CreditFactory::create(auth()->user()->company()->id, auth()->user()->id));
$client = Client::find($request->input('client_id'));
$credit = $this->credit_repository->save($request->all(), $client->setCreditDefaults());
$credit = StoreCredit::dispatchNow($credit, $request->all(), $credit->company);
@ -65,16 +195,171 @@ class CreditController extends BaseController
return $this->itemResponse($credit);
}
/**
* Display the specified resource.
*
* @param \App\Http\Requests\Credit\ShowCreditRequest $request The request
* @param \App\Models\Credit $credit The credit
*
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/credits/{id}",
* operationId="showCredit",
* tags={"credits"},
* summary="Shows an credit",
* description="Displays an credit 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 Credit Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the credit object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(ShowCreditRequest $request, Credit $credit)
{
return $this->itemResponse($credit);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Http\Requests\Invoice\EditInvoiceRequest $request The request
* @param \App\Models\Invoice $credit The credit
*
* @return \Illuminate\Http\Response
*
* @OA\Get(
* path="/api/v1/credits/{id}/edit",
* operationId="editInvoice",
* tags={"credits"},
* summary="Shows an credit for editting",
* description="Displays an credit 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 Invoice Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the credit object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(EditCreditRequest $request, Credit $credit)
{
return $this->itemResponse($credit);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Credit\UpdateCreditRequest $request The request
* @param \App\Models\Credit $Credit The Credit
*
* @return \Illuminate\Http\Response
*
*
* @OA\Put(
* path="/api/v1/Credits/{id}",
* operationId="updateCredit",
* tags={"Credits"},
* summary="Updates an Credit",
* description="Handles the updating of an Credit 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 Credit Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Credit object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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(UpdateCreditRequest $request, Credit $credit)
{
if($request->entityIsDeleted($credit))
@ -87,6 +372,56 @@ class CreditController extends BaseController
return $this->itemResponse($credit);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\Credit\DestroyCreditRequest $request
* @param \App\Models\Credit $credit
*
* @return \Illuminate\Http\Response
*
* @OA\Delete(
* path="/api/v1/credits/{id}",
* operationId="deleteCredit",
* tags={"credits"},
* summary="Deletes a credit",
* description="Handles the deletion of an credit 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 Credit 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-API-Version", ref="#/components/headers/X-API-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(DestroyCreditRequest $request, Credit $credit)
{
$credit->delete();
@ -94,6 +429,57 @@ class CreditController extends BaseController
return response()->json([], 200);
}
/**
* Perform bulk actions on the list view
*
* @return Collection
*
* @OA\Post(
* path="/api/v1/credits/bulk",
* operationId="bulkCredits",
* tags={"credits"},
* summary="Performs bulk actions on an array of credits",
* 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="User credentials",
* 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-API-Version", ref="#/components/headers/X-API-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');
@ -150,7 +536,7 @@ class CreditController extends BaseController
}
break;
case 'mark_sent':
$credit->markSent();
$credit->service()->markSent()->save();
if (!$bulk) {
return $this->itemResponse($credit);

View File

@ -159,7 +159,7 @@ class InvoiceController extends BaseController {
*/
public function create(CreateInvoiceRequest $request) {
$invoice = InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id, $client->getMergedSettings(), $client);
$invoice = InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($invoice);
}
@ -208,7 +208,7 @@ class InvoiceController extends BaseController {
$client = Client::find($request->input('client_id'));
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id), $client->getMergedSettings(), $client);
$invoice = $this->invoice_repo->save($request->all(), $client->setInvoiceDefaults());
$invoice = StoreInvoice::dispatchNow($invoice, $request->all(), $invoice->company);//todo potentially this may return mixed ie PDF/$invoice... need to revisit when we implement UI
@ -514,6 +514,10 @@ class InvoiceController extends BaseController {
return response()->json(['message' => 'No Invoices Found']);
}
/*
* Download Invoice/s
*/
if($action == 'download' && $invoices->count() > 1)
{
@ -530,7 +534,9 @@ class InvoiceController extends BaseController {
return response()->json(['message' => 'Email Sent!'],200);
}
/*
* Send the other actions to the switch
*/
$invoices->each(function ($invoice, $key) use ($action) {
if (auth()->user()->can('edit', $invoice)) {

View File

@ -0,0 +1,53 @@
<?php
/**
* @OA\Schema(
* schema="Credit",
* type="object",
* @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"),
* @OA\Property(property="user_id", type="string", example="", description="__________"),
* @OA\Property(property="assigned_user_id", type="string", example="", description="__________"),
* @OA\Property(property="company_id", type="string", example="", description="________"),
* @OA\Property(property="client_id", type="string", example="", description="________"),
* @OA\Property(property="status_id", type="string", example="", description="________"),
* @OA\Property(property="invoice_id", type="string", example="", description="The linked invoice this credit is applied to"),
* @OA\Property(property="number", type="string", example="QUOTE_101", description="The credit number - is a unique alpha numeric number per credit per company"),
* @OA\Property(property="po_number", type="string", example="", description="________"),
* @OA\Property(property="terms", type="string", example="", description="________"),
* @OA\Property(property="public_notes", type="string", example="", description="________"),
* @OA\Property(property="private_notes", type="string", example="", description="________"),
* @OA\Property(property="footer", type="string", example="", description="________"),
* @OA\Property(property="custom_value1", type="string", example="", description="________"),
* @OA\Property(property="custom_value2", type="string", example="", description="________"),
* @OA\Property(property="custom_value3", type="string", example="", description="________"),
* @OA\Property(property="custom_value4", type="string", example="", description="________"),
* @OA\Property(property="tax_name1", type="string", example="", description="________"),
* @OA\Property(property="tax_name2", type="string", example="", description="________"),
* @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="tax_name3", type="string", example="", description="________"),
* @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the credit"),
* @OA\Property(property="line_items", type="object", example="", description="_________"),
* @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"),
* @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"),
* @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"),
* @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The Credit Date"),
* @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The last date the credit was sent out"),
* @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"),
* @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="_________"),
* @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="_________"),
* @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"),
* @OA\Property(property="last_viewed", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="custom_surcharge1", type="number", format="float", example="10.00", description="First Custom Surcharge"),
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* )
*/

View File

@ -24,6 +24,8 @@ use App\Http\Requests\Quote\EditQuoteRequest;
use App\Http\Requests\Quote\ShowQuoteRequest;
use App\Http\Requests\Quote\StoreQuoteRequest;
use App\Http\Requests\Quote\UpdateQuoteRequest;
use App\Jobs\Invoice\ZipInvoices;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Quote;
use App\Repositories\QuoteRepository;
@ -201,7 +203,9 @@ class QuoteController extends BaseController
*/
public function store(StoreQuoteRequest $request)
{
$quote = $this->quote_repo->save($request->all(), QuoteFactory::create(auth()->user()->company()->id, auth()->user()->id));
$client = Client::find($request->input('client_id'));
$quote = $this->quote_repo->save($request->all(), $client->setQuoteDefaults());
return $this->itemResponse($quote);
}
@ -495,20 +499,55 @@ class QuoteController extends BaseController
*/
public function bulk()
{
/*
* WIP!
*/
$action = request()->input('action');
$ids = request()->input('ids');
$quotes = Quote::withTrashed()->find($this->transformKeys($ids));
$quotes = Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (!$quotes) {
return response()->json(['message' => 'No Quotes Found']);
}
/*
* Download Invoice/s
*/
if($action == 'download' && $quotes->count() > 1)
{
$quotes->each(function ($quote) {
if(auth()->user()->cannot('view', $quote)){
return response()->json(['message'=>'Insufficient privileges to access quote '. $quote->number]);
}
});
ZipInvoices::dispatch($quotes, $quotes->first()->company, auth()->user()->email);
return response()->json(['message' => 'Email Sent!'],200);
}
/*
* Send the other actions to the switch
*/
$quotes->each(function ($quote, $key) use ($action) {
if (auth()->user()->can('edit', $quote)) {
$this->quote_repo->{$action}($quote);
$this->performAction($quote, $action, true);
}
});
return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids)));
/* Need to understand which permission are required for the given bulk action ie. view / edit */
return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
/**
* Quote Actions
@ -580,13 +619,19 @@ class QuoteController extends BaseController
* )
*
*/
public function action(ActionQuoteRequest $request, Quote $quote, $action)
public function action(ActionQuoteRequest $request, Quote $quote, $action) {
return $this->performAction($quote, $action);
}
private function performAction(Quote $quote, $action, $bulk = false)
{
switch ($action) {
case 'clone_to_invoice':
$this->entity_type = Invoice::class;
$this->entity_transformer = InvoiceTransformer::class;
$this->entity_type = Quote::class;
$this->entity_transformer = QuoteTransformer::class;
$invoice = CloneQuoteToInvoiceFactory::create($quote, auth()->user()->id);
return $this->itemResponse($invoice);
@ -618,9 +663,16 @@ class QuoteController extends BaseController
case 'email':
return response()->json(['message'=>'email sent'], 200);
break;
case 'mark_sent':
$quote->service()->markSent()->save();
if (!$bulk) {
return $this->itemResponse($quote);
}
default:
return response()->json(['message' => "The requested action `{$action}` is not available."], 400);
break;
}
}
}

View File

@ -15,6 +15,7 @@ use App\DataMapper\ClientSettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
use App\Models\Client;
use App\Models\GroupSetting;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
@ -80,6 +81,23 @@ class StoreClientRequest extends Request
$input['group_settings_id'] = $this->decodePrimaryKey($input['group_settings_id']);
}
if(empty($input['settings']->currency_id))
{
if(empty($input['group_settings_id']))
{
$input['settings']->currency_id = auth()->user()->company()->settings->currency_id;
}
else
{
$group_settings = GroupSetting::find($input['group_settings_id']);
if($group_settings && property_exists($group_settings, 'currency_id') && is_int($group_settings->currency_id))
$input['settings']->currency_id = $group_settings->currency_id;
else
$input['settings']->currency_id = auth()->user()->company()->settings->currency_id;
}
}
if(isset($input['contacts']))
{
foreach($input['contacts'] as $key => $contact)

View File

@ -56,6 +56,8 @@ class StoreCompanyRequest extends Request
$input['settings']['pdf_variables'] = CompanySettings::getEntityVariableDefaults();
}
$this->replace($input);
}
}

View File

@ -17,7 +17,7 @@ use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\User\CreateUser;
use App\Models\Account;
use App\Models\User;
use App\Notifications\NewAccountCreated;
use App\Notifications\Ninja\NewAccountCreated;
use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request;
@ -92,14 +92,7 @@ class CreateAccount
$user->fresh();
$company->notification(new NewAccountCreated($user, $company))->run();
// $user->route('slack', $company->settings->system_notifications_slack)
// ->route('mail', $company->settings->system_notifications_email)
// ->notify(new NewAccountCreated($user, $company));
// Notification::route('slack', config('ninja.notification.slack'))
// ->notify(new NewAccountCreated($user, $company));
$company->notification(new NewAccountCreated($user, $company))->ninja();
return $account;
}

View File

@ -0,0 +1,107 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Credit;
use App\Designs\Custom;
use App\Designs\Designer;
use App\Designs\Modern;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Design;
use App\Models\Invoice;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\Traits\MakesInvoiceHtml;
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\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
class CreateCreditPdf implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker;
public $credit;
public $company;
public $contact;
private $disk;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($credit, Company $company, ClientContact $contact = null)
{
$this->credit = $credit;
$this->company = $company;
$this->contact = $contact;
$this->disk = $disk ?? config('filesystems.default');
}
public function handle() {
MultiDB::setDB($this->company->db);
$this->credit->load('client');
if(!$this->contact)
$this->contact = $this->credit->client->primary_contact()->first();
App::setLocale($this->contact->preferredLocale());
$path = $this->credit->client->credit_filepath();
$file_path = $path . $this->credit->number . '.pdf';
$design = Design::find($this->credit->client->getSetting('credit_design_id'));
if($design->is_custom){
$credit_design = new Custom($design->design);
}
else{
$class = 'App\Designs\\'.$design->name;
$credit_design = new $class();
}
$designer = new Designer($credit_design, $this->credit->client->getSetting('pdf_variables'), 'credit');
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->credit)->getHtml(), $this->credit, $this->contact);
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);
//\Log::error($html);
$pdf = $this->makePdf(null, null, $html);
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
//$instance= Storage::disk($this->disk)->path($file_path);
//
return $file_path;
}
}

View File

@ -18,6 +18,7 @@ use App\Notifications\Payment\NewPaymentNotification;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class PaymentNotification implements ShouldQueue
{
@ -46,5 +47,12 @@ class PaymentNotification implements ShouldQueue
{
$company_user->user->notify(new NewPaymentNotification($payment, $payment->company));
}
if(isset($payment->company->slack_webhook_url)){
Notification::route('slack', $payment->company->slack_webhook_url)
->notify(new NewPaymentNotification($payment, $payment->company, true));
}
}
}

View File

@ -13,17 +13,23 @@ namespace App\Models;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\QuoteFactory;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Country;
use App\Models\Credit;
use App\Models\Currency;
use App\Models\DateFormat;
use App\Models\DatetimeFormat;
use App\Models\Filterable;
use App\Models\GatewayType;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\Language;
use App\Models\Quote;
use App\Models\Timezone;
use App\Models\User;
use App\Services\Client\ClientService;
@ -454,4 +460,31 @@ class Client extends BaseModel implements HasLocalePreference
{
return $this->client_hash . '/credits/';
}
public function setInvoiceDefaults() :Invoice
{
$invoice_factory = InvoiceFactory::create($this->company_id, auth()->user()->id);
$invoice_factory->terms = $this->getSetting('invoice_terms');
$invoice_factory->footer = $this->getSetting('invoice_footer');
$invoice_factory->public_notes = isset($this->public_notes) ? $this->public_notes : '';
return $invoice_factory;
}
public function setQuoteDefaults() :Quote
{
$quote_factory = QuoteFactory::create($this->company_id, auth()->user()->id);
$quote_factory->terms = $this->getSetting('quote_terms');
$quote_factory->footer = $this->getSetting('quote_footer');
$quote_factory->public_notes = isset($this->public_notes) ? $this->public_notes : '';
return $quote_factory;
}
public function setCreditDefaults() :Credit
{
$credit_factory = CreditFactory::create($this->company_id, auth()->user()->id);
$credit_factory->terms = $this->getSetting('credit_terms');
$credit_factory->footer = $this->getSetting('credit_footer');
$credit_factory->public_notes = isset($this->public_notes) ? $this->public_notes : '';
return $credit_factory;
}
}

View File

@ -51,6 +51,18 @@ class Company extends BaseModel
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
use \Staudenmeir\EloquentHasManyDeep\HasTableAlias;
const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
const ENTITY_CREDIT = 'credit';
const ENTITY_QUOTE = 'quote';
const ENTITY_TASK = 'task';
const ENTITY_EXPENSE = 'expense';
const ENTITY_PROJECT = 'project';
const ENTITY_VENDOR = 'vendor';
const ENTITY_TICKET = 'ticket';
const ENTITY_PROPOSAL = 'proposal';
const ENTITY_RECURRING_EXPENSE = 'expense';
const ENTITY_RECURRING_TASK = 'task';
protected $presenter = 'App\Models\Presenters\CompanyPresenter';
protected $fillable = [
@ -100,6 +112,20 @@ class Company extends BaseModel
// 'tokens'
];
public static $modules = [
self::ENTITY_RECURRING_INVOICE => 1,
self::ENTITY_CREDIT => 2,
self::ENTITY_QUOTE => 4,
self::ENTITY_TASK => 8,
self::ENTITY_EXPENSE => 16,
self::ENTITY_PROJECT => 32,
self::ENTITY_VENDOR => 64,
self::ENTITY_TICKET => 128,
self::ENTITY_PROPOSAL => 256,
self::ENTITY_RECURRING_EXPENSE => 512,
self::ENTITY_RECURRING_TASK => 1024,
];
public function getCompanyIdAttribute()
{
return $this->encodePrimaryKey($this->id);
@ -337,9 +363,8 @@ class Company extends BaseModel
{
//todo need to return the company channel here for hosted users
//else the env variable for selfhosted
if(config('ninja.environment') == 'selfhosted')
return config('ninja.notification.slack');
else
return $this->settings->system_notifications_slack;
//
return $this->slack_webhook_url;
}
}

View File

@ -13,12 +13,14 @@ namespace App\Models;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Credit\CreateCreditPdf;
use App\Models\Filterable;
use App\Services\Credit\CreditService;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
class Credit extends BaseModel
{
@ -64,8 +66,9 @@ class Credit extends BaseModel
];
const STATUS_DRAFT = 1;
const STAUS_PARTIAL = 2;
const STATUS_APPLIED = 3;
const STATUS_SENT = 2;
const STATUS_PARTIAL = 3;
const STATUS_APPLIED = 4;
public function assigned_user()
{
@ -172,5 +175,31 @@ class Credit extends BaseModel
$this->status_id = $status;
$this->save();
}
public function pdf_file_path($invitation = null)
{
$storage_path = 'storage/' . $this->client->credit_filepath() . $this->number . '.pdf';
if (Storage::exists($storage_path))
return $storage_path;
if(!$invitation)
CreateCreditPdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
else
CreateCreditPdf::dispatchNow($invitation->credit, $invitation->company, $invitation->contact);
return $storage_path;
}
public function markInvitationsSent()
{
$this->invitations->each(function ($invitation) {
if (!isset($invitation->sent_date)) {
$invitation->sent_date = Carbon::now();
$invitation->save();
}
});
}
}

View File

@ -22,6 +22,7 @@ use App\Utils\Traits\MakesInvoiceValues;
use App\Utils\Traits\MakesReminders;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Laracasts\Presenter\PresentableTrait;

View File

@ -303,7 +303,6 @@ class User extends Authenticatable implements MustVerifyEmail
if($this->company())
return $this->company()->slack_webhook_url;
}

View File

@ -23,10 +23,13 @@ class NewAccountCreated extends Notification implements ShouldQueue
protected $company;
public function __construct($user, $company)
public $is_system;
public function __construct($user, $company, $is_system = false)
{
$this->user = $user;
$this->company = $company;
$this->is_system = $is_system;
}
/**
@ -37,7 +40,6 @@ class NewAccountCreated extends Notification implements ShouldQueue
*/
public function via($notifiable)
{
//return ['mail'];
return ['slack','mail'];
}
@ -95,8 +97,7 @@ class NewAccountCreated extends Notification implements ShouldQueue
return (new SlackMessage)
->success()
->to("#devv2")
->from("System")
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content("A new account has been created by {$user_name} - {$email} - from IP: {$ip}");
}

View File

@ -0,0 +1,123 @@
<?php
namespace App\Notifications\Payment;
use App\Utils\Number;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class InvoiceViewedNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
protected $invitation;
protected $invoice;
protected $company;
protected $settings;
public $is_system;
protected $contact;
public function __construct($invitation, $company, $is_system = false, $settings = null)
{
$this->invoice = $invitation->invoice;
$this->contact = $invitation->contact;
$this->company = $company;
$this->settings = $invoice->client->getMergedSettings();
$this->is_system = $is_system;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return $this->is_system ? ['slack'] : ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
$subject = ctrans('texts.notification_invoice_viewed_subject',
[
'client' => $this->contact->present()->name(),
'invoice' => $this->invoice->number,
]);
$data = [
'title' => $subject,
'message' => ctrans('texts.notification_invoice_viewed',
[
'amount' => $amount,
'client' => $this->contact->present()->name(),
'invoice' => $this->invoice->number,
]),
'url' => config('ninja.site_url') . '/invoices/' . $this->invoice->hashed_id,
'button' => ctrans('texts.view_invoice'),
'signature' => $this->settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return (new MailMessage)
->subject($subject)
->markdown('email.admin.generic', $data);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$logo = $this->company->present()->logo();
$amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image($logo)
->content(ctrans('texts.notification_invoice_viewed',
[
'amount' => $amount,
'client' => $this->contact->present()->name(),
'invoice' => $this->invoice->number,
]);
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Notifications\Ninja;
use App\Mail\Signup\NewSignup;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class NewAccountCreated extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
protected $user;
protected $company;
public $is_system;
public function __construct($user, $company, $is_system = false)
{
$this->user = $user;
$this->company = $company;
$this->is_system = $is_system;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['slack','mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$user_name = $this->user->first_name . " " . $this->user->last_name;
$email = $this->user->email;
$ip = $this->user->ip;
$data = [
'title' => ctrans('texts.new_signup'),
'message' => ctrans('texts.new_signup_text', ['user' => $user_name, 'email' => $email, 'ip' => $ip]),
'url' => config('ninja.web_url'),
'button' => ctrans('texts.account_login'),
'signature' => $this->company->settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return (new MailMessage)
->subject(ctrans('texts.new_signup'))
->markdown('email.admin.generic', $data);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$this->user->setCompany($this->company);
$user_name = $this->user->first_name . " " . $this->user->last_name;
$email = $this->user->email;
$ip = $this->user->ip;
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content("A new account has been created by {$user_name} - {$email} - from IP: {$ip}");
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace App\Notifications\Payment;
use App\Mail\Signup\NewSignup;
use App\Utils\Number;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class NewPartialPaymentNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
protected $payment;
protected $company;
protected $settings;
protected $is_system;
public function __construct($payment, $company, $is_system = false, $settings = null)
{
$this->payment = $payment;
$this->company = $company;
$this->settings = $payment->client->getMergedSettings();
$this->is_system = $is_system;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return $this->is_system ? ['slack'] : ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');
foreach($this->payment->invoices as $invoice)
$invoice_texts .= $invoice->number . ',';
$invoice_texts = substr($invoice_texts, 0, -1);
$data = [
'title' => ctrans('texts.notification_partial_payment_paid_subject',
['client' => $this->payment->client->present()->name()]),
'message' => ctrans('texts.notification_partial_payment_paid',
['amount' => $amount,
'client' => $this->payment->client->present()->name(),
'invoice' => $invoice_texts,
]),
'url' => config('ninja.site_url') . '/payments/' . $this->payment->hashed_id,
'button' => ctrans('texts.view_payment'),
'signature' => $this->settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return (new MailMessage)
->subject(ctrans('texts.notification_partial_payment_paid_subject',
['client' => $this->payment->client->present()->name()])
)->markdown('email.admin.generic', $data);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$logo = $this->company->present()->logo();
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');
foreach($this->payment->invoices as $invoice)
$invoice_texts .= $invoice->number . ',';
$invoice_texts = substr($invoice_texts, 0, -1);
return (new SlackMessage)
->success()
//->to("#devv2")
->from("System")
->image($logo)
->content(ctrans('texts.notification_payment_paid',
['amount' => $amount,
'client' => $this->payment->client->present()->name(),
'invoice' => $invoice_texts]));
}
}

View File

@ -26,11 +26,14 @@ class NewPaymentNotification extends Notification implements ShouldQueue
protected $settings;
public function __construct($payment, $company, $settings = null)
protected $is_system;
public function __construct($payment, $company, $is_system = false, $settings = null)
{
$this->payment = $payment;
$this->company = $company;
$this->settings = $payment->client->getMergedSettings();
$this->is_system = $is_system;
}
/**
@ -41,8 +44,8 @@ class NewPaymentNotification extends Notification implements ShouldQueue
*/
public function via($notifiable)
{
//return ['mail'];
return ['mail'];
return $this->is_system ? ['slack'] : ['mail'];
}
/**
@ -58,25 +61,29 @@ class NewPaymentNotification extends Notification implements ShouldQueue
$invoice_texts = ctrans('texts.invoice_number_short');
foreach($this->payment->invoices as $invoice)
{
$invoice_texts .= $invoice->number . ',';
}
$invoice_texts = rtrim($invoice_texts);
$invoice_texts = substr($invoice_texts, 0, -1);
$data = [
'title' => ctrans('texts.notification_payment_paid_subject', ['client' => $this->payment->client->present()->name()]),
'message' => ctrans('texts.notification_paid_paid', ['amount' => $amount, 'client' => $this->payment->client->present()->name(), 'invoice' => $invoice_texts]),
'title' => ctrans('texts.notification_payment_paid_subject',
['client' => $this->payment->client->present()->name()]),
'message' => ctrans('texts.notification_payment_paid',
['amount' => $amount,
'client' => $this->payment->client->present()->name(),
'invoice' => $invoice_texts,
]),
'url' => config('ninja.site_url') . '/payments/' . $this->payment->hashed_id,
'button' => ctrans('texts.view_payment'),
'signature' => $this->company->settings->email_signature,
'signature' => $this->settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return (new MailMessage)
->subject(ctrans('texts.notification_payment_paid_subject', ['client' => $this->payment->client->present()->name()]))
->markdown('email.admin.generic', $data);
->subject(ctrans('texts.notification_payment_paid_subject',
['client' => $this->payment->client->present()->name(),])
)->markdown('email.admin.generic', $data);
}
@ -97,13 +104,23 @@ class NewPaymentNotification extends Notification implements ShouldQueue
public function toSlack($notifiable)
{
$logo = $this->company->present()->logo();
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');
foreach($this->payment->invoices as $invoice)
$invoice_texts .= $invoice->number . ',';
$invoice_texts = substr($invoice_texts, 0, -1);
return (new SlackMessage)
->success()
->to("#devv2")
//->to("#devv2")
->from("System")
->image($logo)
->content("A new account has been created by {$user_name} - {$email} - from IP: {$ip}");
->content(ctrans('texts.notification_payment_paid',
['amount' => $amount,
'client' => $this->payment->client->present()->name(),
'invoice' => $invoice_texts]));
}
}

View File

@ -13,6 +13,7 @@ namespace App\Providers;
use App\Events\Client\ClientWasCreated;
use App\Events\Contact\ContactLoggedIn;
use App\Events\Credit\CreditWasMarkedSent;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasMarkedSent;
@ -100,8 +101,10 @@ class EventServiceProvider extends ServiceProvider
'App\Listeners\ActivityListener@restoredClient',
],
//Invoices
CreditWasMarkedSent::class => [
],
//Invoices
InvoiceWasMarkedSent::class => [
CreateInvoiceHtmlBackup::class,
],

View File

@ -23,7 +23,6 @@ class MultiDBProvider extends ServiceProvider
*/
public function boot()
{
}
/**
@ -42,7 +41,7 @@ class MultiDBProvider extends ServiceProvider
if (isset($event->job->payload()['db'])) {
//\Log::error("Provider Setting DB = ".$event->job->payload()['db']);
\Log::error("Provider Setting DB = ".$event->job->payload()['db']);
MultiDB::setDb($event->job->payload()['db']);
}

View File

@ -251,6 +251,10 @@ class BaseRepository
$state['finished_amount'] = $model->amount;
$model = $model->service()->applyNumber()->save();
if ($model->company->update_products !== false) {
UpdateOrCreateProduct::dispatch($model->line_items, $model, $model->company);
}
if ($class->name == Invoice::class) {
@ -258,10 +262,6 @@ class BaseRepository
$model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount']));
}
if ($model->company->update_products !== false) {
UpdateOrCreateProduct::dispatch($model->line_items, $model, $model->company);
}
$model = $model->calc()->getInvoice();
}

View File

@ -2,6 +2,8 @@
namespace App\Services\Credit;
use App\Models\Credit;
use App\Services\Credit\CreateInvitations;
use App\Services\Credit\MarkSent;
class CreditService
{
@ -36,6 +38,20 @@ class CreditService
return $this;
}
public function setStatus($status)
{
$this->credit->status_id = $status;
return $this;
}
public function markSent()
{
$this->credit = (new MarkSent($this->credit->client))->run($this->credit);
return $this;
}
/**
* Saves the credit
* @return Credit object

View File

@ -32,11 +32,32 @@ class NotificationService extends AbstractService
}
public function run()
public function run($is_system = false)
{
$this->company->owner()->notify($this->notification);
if($is_system)
{
$this->notification->is_system = true;
Notification::route('slack', $this->company->slack_webhook_url)
->notify($this->notification);
}
}
/**
* Hosted notifications
* @return void
*/
public function ninja()
{
Notification::route('slack', config('ninja.notification.slack'))
->route('mail', config('ninja.notification.mail'))
->notify($this->notification);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Services\Quote;
use App\Models\Quote;
use App\Services\Quote\CreateInvitations;
class QuoteService
{

View File

@ -74,13 +74,11 @@ return [
'providers' => [
'users' => [
'driver' => 'eloquent',
//'driver' => env('USER_AUTH_PROVIDER', 'eloquent'),
'model' => App\Models\User::class,
],
'contacts' => [
'driver' => 'eloquent',
// 'driver' => env('CONTACT_AUTH_PROVIDER', 'eloquent'),
'model' => App\Models\ClientContact::class,
],

View File

@ -89,6 +89,7 @@ return [
],
'notification' => [
'slack' => env('SLACK_WEBHOOK_URL',''),
'mail' => env('HOSTED_EMAIL',''),
],
'payment_terms' => [
[

View File

@ -3120,8 +3120,10 @@ $LANG = array(
'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip',
'notification_payment_paid_subject' => 'Payment was made by :client',
'notification_paid_paid' => 'A payment of :amount was made by client :client towards :invoice.',
'notification_partial_payment_paid_subject' => 'Partial payment was made by :client',
'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice',
'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice',
'notification_bot' => 'Notification Bot',
'email_link_not_working' => 'If button above isn\'t working for you, please click on the link',
);