mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 16:17:32 -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