mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Set casts on Invoice Line Items (#3049)
* Implement client/group/company level counters clientCounter, groupCounter and counter * Implement functionalityfor customising the timing of invoice_number creation * Add Jobs * adjustments * clean line items at the request layer * Clean line items at the request layer
This commit is contained in:
parent
a6f928b181
commit
a78b6aaacd
@ -72,6 +72,7 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $translations;
|
||||
|
||||
public $counter_number_applied = 'when_saved'; // when_saved , when_sent , when_paid
|
||||
/* Counters */
|
||||
public $invoice_number_pattern = '';
|
||||
public $invoice_number_counter = 1;
|
||||
@ -132,7 +133,6 @@ class CompanySettings extends BaseSettings
|
||||
public $tax_rate2 = 0;
|
||||
public $tax_name3 = '';
|
||||
public $tax_rate3 = 0;
|
||||
public $number_of_tax_rates = 1;
|
||||
public $payment_type_id = '1';
|
||||
public $custom_fields = '';
|
||||
public $invoice_fields = '';
|
||||
@ -226,7 +226,7 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
|
||||
public static $casts = [
|
||||
'number_of_tax_rates' => 'int',
|
||||
'counter_number_applied' => 'string',
|
||||
'email_subject_custom1' => 'string',
|
||||
'email_subject_custom2' => 'string',
|
||||
'email_subject_custom3' => 'string',
|
||||
|
@ -241,6 +241,9 @@ class InvoiceItemSum
|
||||
foreach($this->line_items as $this->item)
|
||||
{
|
||||
|
||||
if($this->item->line_total == 0)
|
||||
continue;
|
||||
|
||||
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount/$this->sub_total));
|
||||
$item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount);
|
||||
|
||||
|
@ -14,11 +14,13 @@ namespace App\Http\Requests\Invoice;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class StoreInvoiceRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
@ -34,6 +36,7 @@ class StoreInvoiceRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$this->sanitize();
|
||||
|
||||
return [
|
||||
'client_id' => 'required',
|
||||
// 'invoice_type_id' => 'integer',
|
||||
@ -46,11 +49,12 @@ class StoreInvoiceRequest extends Request
|
||||
$input = $this->all();
|
||||
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
$input['line_items'] = isset($input['line_items']) ? $input['line_items'] : [];
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
//$input['line_items'] = json_encode($input['line_items']);
|
||||
$this->replace($input);
|
||||
|
||||
return $this->all();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Requests\Invoice;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -19,6 +20,7 @@ use Illuminate\Validation\Rule;
|
||||
class UpdateInvoiceRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
@ -52,6 +54,8 @@ class UpdateInvoiceRequest extends Request
|
||||
if(isset($input['client_id']))
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
$this->replace($input);
|
||||
|
||||
return $this->all();
|
||||
|
81
app/Jobs/Invoice/ApplyInvoiceNumber.php
Normal file
81
app/Jobs/Invoice/ApplyInvoiceNumber.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
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\Carbon;
|
||||
|
||||
class ApplyInvoiceNumber implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, GeneratesCounter;
|
||||
|
||||
public $invoice;
|
||||
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Invoice $invoice, $settings)
|
||||
{
|
||||
|
||||
$this->invoice = $invoice;
|
||||
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//return early
|
||||
if($this->invoice->invoice_number != '')
|
||||
return $this->invoice;
|
||||
|
||||
switch ($this->settings->counter_number_applied) {
|
||||
case 'when_saved':
|
||||
$this->invoice->invoice_number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
break;
|
||||
case 'when_sent':
|
||||
if($this->invoice->status_id == Invoice::STATUS_SENT)
|
||||
$this->invoice->invoice_number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
break;
|
||||
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
$this->invoice->save();
|
||||
|
||||
return $this->invoice;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Jobs\Invoice\ApplyInvoiceNumber;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
@ -108,6 +109,8 @@ class ApplyPaymentToInvoice implements ShouldQueue
|
||||
|
||||
$this->invoice->save();
|
||||
|
||||
$this->invoice = ApplyInvoiceNumber::dispatchNow($this->invoice, $invoice->client->getMergedSettings());
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
|
||||
use App\Jobs\Invoice\ApplyInvoiceNumber;
|
||||
use App\Listeners\Invoice\CreateInvoiceInvitation;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
@ -120,6 +121,8 @@ class InvoiceRepository extends BaseRepository
|
||||
if($finished_amount != $starting_amount)
|
||||
UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, ($finished_amount - $starting_amount));
|
||||
|
||||
$invoice = ApplyInvoiceNumber::dispatchNow($invoice, $invoice->client->getMergedSettings());
|
||||
|
||||
return $invoice->fresh();
|
||||
|
||||
}
|
||||
@ -139,7 +142,7 @@ class InvoiceRepository extends BaseRepository
|
||||
|
||||
$invoice->status_id = Invoice::STATUS_SENT;
|
||||
|
||||
$this->markInvitationsSent();
|
||||
$this->markInvitationsSent($invoice);
|
||||
|
||||
$invoice->save();
|
||||
|
||||
@ -148,7 +151,9 @@ class InvoiceRepository extends BaseRepository
|
||||
* When marked as sent it becomes a ledgerable item.
|
||||
*
|
||||
*/
|
||||
UpdateCompanyLedgerWithInvoice::dispatchNow($this->invoice, $this->balance);
|
||||
$invoice = ApplyInvoiceNumber::dispatchNow($invoice, $invoice->client->getMergedSettings());
|
||||
|
||||
UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $this->balance);
|
||||
|
||||
return $invoice;
|
||||
|
||||
|
@ -92,7 +92,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||
'design_id' => (string) ($invoice->design_id ?: 1),
|
||||
'updated_at' => $invoice->updated_at,
|
||||
'archived_at' => $invoice->deleted_at,
|
||||
'invoice_number' => $invoice->invoice_number,
|
||||
'invoice_number' => $invoice->invoice_number ?: '',
|
||||
'discount' => (float) $invoice->discount,
|
||||
'po_number' => $invoice->po_number ?: '',
|
||||
'invoice_date' => $invoice->invoice_date ?: '',
|
||||
|
67
app/Utils/Traits/CleanLineItems.php
Normal file
67
app/Utils/Traits/CleanLineItems.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Utils\Traits;
|
||||
|
||||
use App\DataMapper\BaseSettings;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
|
||||
|
||||
/**
|
||||
* Class CleanLineItems.
|
||||
*/
|
||||
trait CleanLineItems
|
||||
{
|
||||
|
||||
public function cleanItems($items) :array
|
||||
{
|
||||
|
||||
if(!isset($items))
|
||||
return [];
|
||||
|
||||
$cleaned_items = [];
|
||||
|
||||
foreach($items as $item)
|
||||
$cleaned_items[] = $this->cleanLineItem($item);
|
||||
|
||||
return $cleaned_items;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default values for the line_items
|
||||
* @return $this
|
||||
*/
|
||||
private function cleanLineItem($item)
|
||||
{
|
||||
$invoice_item = (object)get_class_vars(InvoiceItem::class);
|
||||
unset($invoice_item->casts);
|
||||
|
||||
foreach($invoice_item as $key => $value)
|
||||
{
|
||||
|
||||
if(!array_key_exists($key, $item) || !isset($item[$key])){
|
||||
$item[$key] = $value;
|
||||
$item[$key] = BaseSettings::castAttribute(InvoiceItem::$casts[$key], $value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(array_key_exists("id", $item))
|
||||
{
|
||||
$item['id'] = $item['id']*-1;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,33 +41,33 @@ trait GeneratesCounter
|
||||
//Reset counters if enabled
|
||||
$this->resetCounters($client);
|
||||
|
||||
$is_client_counter = false;
|
||||
|
||||
//todo handle if we have specific client patterns in the future
|
||||
$pattern = $client->company->settings->invoice_number_pattern;
|
||||
$pattern = $client->getSetting('invoice_number_pattern');
|
||||
|
||||
//Determine if we are using client_counters
|
||||
if(strpos($pattern, 'client_counter') === false)
|
||||
if(strpos($pattern, 'clientCounter'))
|
||||
{
|
||||
$counter = $client->company->settings->invoice_number_counter;
|
||||
$counter = $client->settings->invoice_number_counter;
|
||||
$counter_entity = $client;
|
||||
}
|
||||
elseif(strpos($pattern, 'groupCounter'))
|
||||
{
|
||||
$counter = $client->group_settings->invoice_number_counter;
|
||||
$counter_entity = $client->group_settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
$counter = $client->settings->invoice_number_counter;
|
||||
$is_client_counter = true;
|
||||
$counter = $client->company->settings->invoice_number_counter;
|
||||
$counter_entity = $client->company;
|
||||
}
|
||||
|
||||
//Return a valid counter
|
||||
$pattern = $client->company->settings->invoice_number_pattern;
|
||||
$prefix = $client->company->settings->invoice_number_prefix;
|
||||
$padding = $client->company->settings->counter_padding;
|
||||
$pattern = $client->getSetting('invoice_number_pattern');
|
||||
$prefix = $client->getSetting('invoice_number_prefix');
|
||||
$padding = $client->getSetting('counter_padding');
|
||||
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $prefix, $pattern);
|
||||
|
||||
//increment the correct invoice_number Counter (company vs client)
|
||||
if($is_client_counter)
|
||||
$this->incrementCounter($client, 'invoice_number_counter');
|
||||
else
|
||||
$this->incrementCounter($client->company, 'invoice_number_counter');
|
||||
$this->incrementCounter($counter_entity, 'invoice_number_counter');
|
||||
|
||||
return $invoice_number;
|
||||
}
|
||||
@ -339,7 +339,10 @@ trait GeneratesCounter
|
||||
$search[] = '{$counter}';
|
||||
$replace[] = $counter;
|
||||
|
||||
$search[] = '{$client_counter}';
|
||||
$search[] = '{$clientCounter}';
|
||||
$replace[] = $counter;
|
||||
|
||||
$search[] = '{$groupCounter}';
|
||||
$replace[] = $counter;
|
||||
|
||||
if (strstr($pattern, '{$user_id}')) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUsersTable extends Migration
|
||||
@ -182,6 +183,8 @@ class CreateUsersTable extends Migration
|
||||
|
||||
});
|
||||
|
||||
DB::statement('ALTER table companies key_block_size=8 row_format=compressed');
|
||||
|
||||
|
||||
Schema::create('company_user', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
|
@ -30,7 +30,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['name' => 'PayPal Pro', 'provider' => 'PayPal_Pro', 'key' => '80af24a6a69f5c0bbec33e930ab40665', 'fields' => '{"username":"","password":"","signature":"","testMode":false}'],
|
||||
['name' => 'Pin', 'provider' => 'Pin', 'key' => '0749cb92a6b36c88bd9ff8aabd2efcab', 'fields' => '{"secretKey":"","testMode":false}'],
|
||||
['name' => 'SagePay Direct', 'provider' => 'SagePay_Direct', 'key' => '4c8f4e5d0f353a122045eb9a60cc0f2d', 'fields' => '{"vendor":"","testMode":false,"referrerId":""}'],
|
||||
['name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost', 'key' => '8036a5aadb2bdaafb23502da8790b6a2', 'fields' => '{"merchantId":"","transactionPassword":"","testMode":false}'],
|
||||
['name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost', 'key' => '8036a5aadb2bdaafb23502da8790b6a2', 'fields' => '{"merchantId":"","transactionPassword":"","testMode":false,"enable_ach":"","enable_sofort":"","enable_apple_pay":"","enable_alipay":""}'],
|
||||
['name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1, 'key' => 'd14dd26a37cecc30fdd65700bfb55b23', 'fields' => '{"apiKey":"", "publishableKey":""}'],
|
||||
['name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking', 'key' => 'd14dd26a37cdcc30fdd65700bfb55b23', 'fields' => '{"subAccountId":""}'],
|
||||
['name' => 'TargetPay Ideal', 'provider' => 'TargetPay_Ideal', 'key' => 'ea3b328bd72d381387281c3bd83bd97c', 'fields' => '{"subAccountId":""}'],
|
||||
|
@ -114,7 +114,7 @@ class RandomDataSeeder extends Seeder
|
||||
factory(\App\Models\Product::class,50)->create(['user_id' => $user->id, 'company_id' => $company->id]);
|
||||
|
||||
/** Invoice Factory */
|
||||
factory(\App\Models\Invoice::class,50)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'settings' => ClientSettings::buildClientSettings($company->settings, $client->settings)]);
|
||||
factory(\App\Models\Invoice::class,500)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'settings' => ClientSettings::buildClientSettings($company->settings, $client->settings)]);
|
||||
|
||||
$invoices = Invoice::all();
|
||||
$invoice_repo = new InvoiceRepository();
|
||||
|
@ -103,7 +103,8 @@ class GeneratesCounterTest extends TestCase
|
||||
public function testInvoiceNumberPattern()
|
||||
{
|
||||
$settings = $this->client->company->settings;
|
||||
$settings->invoice_number_prefix = null;
|
||||
$settings->invoice_number_prefix = '';
|
||||
$settings->invoice_number_counter = 1;
|
||||
$settings->invoice_number_pattern = '{$year}-{$counter}';
|
||||
|
||||
$this->client->company->settings = $settings;
|
||||
@ -122,8 +123,8 @@ class GeneratesCounterTest extends TestCase
|
||||
{
|
||||
$settings = $this->client->company->settings;
|
||||
|
||||
$settings->invoice_number_prefix = null;
|
||||
$settings->invoice_number_pattern = '{$year}-{$client_counter}';
|
||||
$settings->invoice_number_prefix = '';
|
||||
$settings->invoice_number_pattern = '{$year}-{$clientCounter}';
|
||||
$this->client->company->settings = $settings;
|
||||
$this->client->company->save();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user