diff --git a/app/DataMapper/ClientSettings.php b/app/DataMapper/ClientSettings.php index 290666afe53b..6987e5dffc28 100644 --- a/app/DataMapper/ClientSettings.php +++ b/app/DataMapper/ClientSettings.php @@ -68,6 +68,8 @@ class ClientSettings extends BaseSettings public $credit_number_counter; public $shared_invoice_quote_counter; + public $recurring_invoice_number_prefix; + public $counter_padding; /** diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index c7141902a76c..43eebd45bdcf 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -96,7 +96,7 @@ class CompanySettings extends BaseSettings public $shared_invoice_quote_counter; public $entity_number_padding; - public $recurring_number_prefix; + public $recurring_invoice_number_prefix; public $reset_counter_frequency_id; public $reset_counter_date; public $counter_padding; diff --git a/app/Models/Client.php b/app/Models/Client.php index 6f4c6715fa8d..ae225ac0c976 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -10,6 +10,7 @@ use App\Models\Country; use App\Models\Filterable; use App\Models\Timezone; use App\Utils\Traits\GeneratesNumberCounter; +use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; use Hashids\Hashids; use Illuminate\Database\Eloquent\SoftDeletes; @@ -19,6 +20,7 @@ class Client extends BaseModel { use PresentableTrait; use MakesHash; + use MakesDates; use SoftDeletes; use Filterable; use GeneratesNumberCounter; diff --git a/app/Models/Company.php b/app/Models/Company.php index 3c99442c7bf9..55e915eb7405 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -37,7 +37,6 @@ class Company extends BaseModel ]; protected $appends = [ - 'settings_object' ]; protected $casts = [ @@ -48,16 +47,6 @@ class Company extends BaseModel // 'tokens' ]; - public function getSettingsObjectAttribute() - { - return new CompanySettings($this->settings); - } - - public function getRouteKeyName() - { - return 'company_id'; - } - public function getCompanyIdAttribute() { return $this->encodePrimaryKey($this->id); @@ -130,11 +119,11 @@ class Company extends BaseModel } /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * */ public function timezone() { - return $this->belongsTo(Timezone::class); + return Timezone::find($this->settings->timezone_id); } /** @@ -142,7 +131,7 @@ class Company extends BaseModel */ public function language() { - return $this->belongsTo(Language::class); + return Language::find($this->settings->language_id); } /** diff --git a/app/Utils/Traits/GeneratesNumberCounter.php b/app/Utils/Traits/GeneratesNumberCounter.php index 4289d1cf4b96..799945edd00e 100644 --- a/app/Utils/Traits/GeneratesNumberCounter.php +++ b/app/Utils/Traits/GeneratesNumberCounter.php @@ -6,6 +6,8 @@ use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; use App\Models\Quote; +use App\Models\RecurringInvoice; +use Illuminate\Support\Carbon; /** * Class GeneratesNumberCounter @@ -18,22 +20,79 @@ trait GeneratesNumberCounter { $counter = $this->getCounter($entity); + $check = false; + + do { + + if ($this->hasNumberPattern($entity)) { + $number = $this->applyNumberPattern($entity, $counter); + } else { + $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + } + + if ($entity->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; + } + + if ($entity->isentity(ENTITY_CLIENT)) { + $check = Client::scope(false, $this->id)->whereIdNumber($number)->withTrashed()->first(); + } else { + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + } + $counter++; + $counterOffset++; + + // prevent getting stuck in a loop + if ($number == $lastNumber) { + return ''; + } + $lastNumber = $number; + + } while ($check); + } public function hasSharedCounter() : bool { - return $this->getSettingsByKey($shared_invoice_quote_counter)->shared_invoice_quote_counter; + return $this->getSettingsByKey('shared_invoice_quote_counter')->shared_invoice_quote_counter; } + /** + * @param $entity + * + * @return bool + */ + public function hasNumberPattern($entity) : bool + { + return $this->getNumberPattern($entity) ? true : false; + } + + /** + * @param $entity + * + * @return NULL|string + */ + public function getNumberPattern($entity) + { + /** Recurring invoice share the same number pattern as invoices */ + if($entity == RecurringInvoice::class ) + $entity = Invoice::class; + + $field = $this->entityName($entity) . "_number_pattern"; + + return $this->getSettingsByKey( $field )->{$field}; + + } + private function incrementCounter($entity) { } - private function entity_name($entity) + private function entityName($entity) { return strtolower(class_basename($entity)); @@ -42,10 +101,57 @@ trait GeneratesNumberCounter public function getCounter($entity) : int { - $counter = $this->entity_name($entity) . '_number_counter'; + /** Recurring invoice share the same counter as invoices also harvest the invoice_counter if quote and invoices are sharing a counter */ + if($entity == RecurringInvoice::class || $this->hasSharedCounter()) + $entity = Invoice::class; + + $counter = $this->entityName($entity) . '_number_counter'; return $this->getSettingsByKey( $counter )->{$counter}; } + /** + * @param $entity + * @param mixed $counter + * todo localize PHP date + * @return bool|mixed + */ + public function applyNumberPattern($entity, $counter = 1) + { + $counter = $counter ?: $this->getCounter($entity); + $pattern = $this->getNumberPattern($entity); + + if (! $pattern) { + return false; + } + + $search = ['{$year}']; + $replace = [date('Y')]; + + $search[] = '{$counter}'; + $replace[] = str_pad($counter, $this->getSettingsByKey( 'counter_padding' )->counter_padding, '0', STR_PAD_LEFT); + + if (strstr($pattern, '{$user_id}')) { + $user_id = $entity->user ? $entity->user->id : (auth()->check() ? auth()->user()->id : 0); + $search[] = '{$user_id}'; + $replace[] = str_pad(($user_id + 1), 2, '0', STR_PAD_LEFT); + } + + $matches = false; + preg_match('/{\$date:(.*?)}/', $pattern, $matches); + if (count($matches) > 1) { + $format = $matches[1]; + $search[] = $matches[0]; + + $date = Carbon::now($this->company->timezone()->name)->format($format); + $replace[] = str_replace($format, $date, $matches[1]); + } + + $pattern = str_replace($search, $replace, $pattern); + $pattern = $this->getClientInvoiceNumber($pattern, $entity); + + return $pattern; + } + } \ No newline at end of file diff --git a/tests/Unit/GenerateNumberTest.php b/tests/Unit/GenerateNumberTest.php index 9ff728ed8e4d..f14c917bac00 100644 --- a/tests/Unit/GenerateNumberTest.php +++ b/tests/Unit/GenerateNumberTest.php @@ -37,7 +37,7 @@ class GenerateNumberTest extends TestCase public function testEntityName() { - $this->assertEquals($this->entity_name(Client::class), 'client'); + $this->assertEquals($this->entityName(Client::class), 'client'); }