mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 11:14:31 -04:00
Added per client invoice counter #1344
This commit is contained in:
parent
d292fc2786
commit
cc63385e14
@ -49,6 +49,8 @@ class Client extends EntityModel
|
|||||||
'language_id',
|
'language_id',
|
||||||
'payment_terms',
|
'payment_terms',
|
||||||
'website',
|
'website',
|
||||||
|
'invoice_number_counter',
|
||||||
|
'quote_number_counter',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,9 +64,11 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
*/
|
*/
|
||||||
public static $patternFields = [
|
public static $patternFields = [
|
||||||
'counter',
|
'counter',
|
||||||
'custom1',
|
'clientInvoiceCounter',
|
||||||
'custom2',
|
'clientQuoteCounter',
|
||||||
'idNumber',
|
'clientCustom1',
|
||||||
|
'clientCustom2',
|
||||||
|
'clientIdNumber',
|
||||||
'userId',
|
'userId',
|
||||||
'year',
|
'year',
|
||||||
'date:',
|
'date:',
|
||||||
|
@ -125,7 +125,7 @@ trait GeneratesNumbers
|
|||||||
{
|
{
|
||||||
$pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern;
|
$pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern;
|
||||||
|
|
||||||
return strstr($pattern, '$custom') || strstr($pattern, '$idNumber');
|
return strstr($pattern, '$client') !== false || strstr($pattern, '$idNumber') !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,16 +161,12 @@ trait GeneratesNumbers
|
|||||||
if (count($matches) > 1) {
|
if (count($matches) > 1) {
|
||||||
$format = $matches[1];
|
$format = $matches[1];
|
||||||
$search[] = $matches[0];
|
$search[] = $matches[0];
|
||||||
//$date = date_create()->format($format);
|
|
||||||
$date = Carbon::now(session(SESSION_TIMEZONE, DEFAULT_TIMEZONE))->format($format);
|
$date = Carbon::now(session(SESSION_TIMEZONE, DEFAULT_TIMEZONE))->format($format);
|
||||||
$replace[] = str_replace($format, $date, $matches[1]);
|
$replace[] = str_replace($format, $date, $matches[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pattern = str_replace($search, $replace, $pattern);
|
$pattern = str_replace($search, $replace, $pattern);
|
||||||
|
$pattern = $this->getClientInvoiceNumber($pattern, $entity);
|
||||||
if ($entity->client_id) {
|
|
||||||
$pattern = $this->getClientInvoiceNumber($pattern, $entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pattern;
|
return $pattern;
|
||||||
}
|
}
|
||||||
@ -183,7 +179,7 @@ trait GeneratesNumbers
|
|||||||
*/
|
*/
|
||||||
private function getClientInvoiceNumber($pattern, $invoice)
|
private function getClientInvoiceNumber($pattern, $invoice)
|
||||||
{
|
{
|
||||||
if (! $invoice->client) {
|
if (! $invoice->client_id) {
|
||||||
return $pattern;
|
return $pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +187,22 @@ trait GeneratesNumbers
|
|||||||
'{$custom1}',
|
'{$custom1}',
|
||||||
'{$custom2}',
|
'{$custom2}',
|
||||||
'{$idNumber}',
|
'{$idNumber}',
|
||||||
|
'{$clientCustom1}',
|
||||||
|
'{$clientCustom2}',
|
||||||
|
'{$clientIdNumber}',
|
||||||
|
'{$clientInvoiceCounter}',
|
||||||
|
'{$clientQuoteCounter}',
|
||||||
];
|
];
|
||||||
|
|
||||||
$replace = [
|
$replace = [
|
||||||
$invoice->client->custom_value1,
|
$invoice->client->custom_value1,
|
||||||
$invoice->client->custom_value2,
|
$invoice->client->custom_value2,
|
||||||
$invoice->client->id_number,
|
$invoice->client->id_number,
|
||||||
|
$invoice->client->custom_value1, // backwards compatibility
|
||||||
|
$invoice->client->custom_value2,
|
||||||
|
$invoice->client->id_number,
|
||||||
|
str_pad($invoice->client->invoice_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT),
|
||||||
|
str_pad($invoice->client->quote_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT),
|
||||||
];
|
];
|
||||||
|
|
||||||
return str_replace($search, $replace, $pattern);
|
return str_replace($search, $replace, $pattern);
|
||||||
@ -225,7 +231,9 @@ trait GeneratesNumbers
|
|||||||
*/
|
*/
|
||||||
public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE)
|
public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE)
|
||||||
{
|
{
|
||||||
$invoice = $this->createInvoice($entityType);
|
$client = \App\Models\Client::scope()->first();
|
||||||
|
|
||||||
|
$invoice = $this->createInvoice($entityType, $client ? $client->id : 0);
|
||||||
|
|
||||||
return $this->getNextNumber($invoice);
|
return $this->getNextNumber($invoice);
|
||||||
}
|
}
|
||||||
@ -239,13 +247,41 @@ trait GeneratesNumbers
|
|||||||
if ($this->client_number_counter) {
|
if ($this->client_number_counter) {
|
||||||
$this->client_number_counter += 1;
|
$this->client_number_counter += 1;
|
||||||
}
|
}
|
||||||
} elseif ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) {
|
$this->save();
|
||||||
$this->quote_number_counter += 1;
|
return;
|
||||||
} else {
|
|
||||||
$this->invoice_number_counter += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->save();
|
if ($this->usesClientInvoiceCounter()) {
|
||||||
|
$entity->client->invoice_number_counter += 1;
|
||||||
|
$entity->client->save();
|
||||||
|
} elseif ($this->usesClientQuoteCounter()) {
|
||||||
|
$entity->client->quote_number_counter += 1;
|
||||||
|
$entity->client->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->usesInvoiceCounter()) {
|
||||||
|
if ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) {
|
||||||
|
$this->quote_number_counter += 1;
|
||||||
|
} else {
|
||||||
|
$this->invoice_number_counter += 1;
|
||||||
|
}
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function usesInvoiceCounter()
|
||||||
|
{
|
||||||
|
return strpos($this->invoice_number_pattern, '{$counter}') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function usesClientInvoiceCounter()
|
||||||
|
{
|
||||||
|
return strpos($this->invoice_number_pattern, '{$clientInvoiceCounter}') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function usesClientQuoteCounter()
|
||||||
|
{
|
||||||
|
return strpos($this->invoice_number_pattern, '{$clientQuoteCounter}') !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clientNumbersEnabled()
|
public function clientNumbersEnabled()
|
||||||
|
@ -131,6 +131,8 @@ class ClientTransformer extends EntityTransformer
|
|||||||
'currency_id' => (int) $client->currency_id,
|
'currency_id' => (int) $client->currency_id,
|
||||||
'custom_value1' => $client->custom_value1,
|
'custom_value1' => $client->custom_value1,
|
||||||
'custom_value2' => $client->custom_value2,
|
'custom_value2' => $client->custom_value2,
|
||||||
|
'invoice_number_counter' => (int) $client->invoice_number_counter,
|
||||||
|
'quote_number_counter' => (int) $client->quote_number_counter,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,15 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
Validator::extend('has_counter', function ($attribute, $value, $parameters) {
|
Validator::extend('has_counter', function ($attribute, $value, $parameters) {
|
||||||
return ! $value || strstr($value, '{$counter}');
|
if (! $value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr($value, '{$counter}') !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((strstr($value, '{$idNumber}') !== false || strstr($value, '{$clientIdNumber}') != false) && (strstr($value, '{$clientInvoiceCounter}') || strstr($value, '{$clientQuoteCounter}')));
|
||||||
});
|
});
|
||||||
|
|
||||||
Validator::extend('valid_invoice_items', function ($attribute, $value, $parameters) {
|
Validator::extend('valid_invoice_items', function ($attribute, $value, $parameters) {
|
||||||
|
@ -19,6 +19,11 @@ class AddGatewayFeeLocation extends Migration
|
|||||||
$table->date('reset_counter_date')->nullable();
|
$table->date('reset_counter_date')->nullable();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Schema::table('clients', function ($table) {
|
||||||
|
$table->integer('invoice_number_counter')->default(1)->nullable();
|
||||||
|
$table->integer('quote_number_counter')->default(1)->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
// update invoice_item_type_id for task invoice items
|
// update invoice_item_type_id for task invoice items
|
||||||
DB::statement('update invoice_items
|
DB::statement('update invoice_items
|
||||||
left join invoices on invoices.id = invoice_items.invoice_id
|
left join invoices on invoices.id = invoice_items.invoice_id
|
||||||
@ -37,5 +42,10 @@ class AddGatewayFeeLocation extends Migration
|
|||||||
$table->dropColumn('gateway_fee_enabled');
|
$table->dropColumn('gateway_fee_enabled');
|
||||||
$table->dropColumn('reset_counter_date');
|
$table->dropColumn('reset_counter_date');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Schema::table('clients', function ($table) {
|
||||||
|
$table->dropColumn('invoice_number_counter');
|
||||||
|
$table->dropColumn('quote_number_counter');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1151,3 +1151,10 @@ function firstJSONError(json) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
|
||||||
|
function pad(n, width, z) {
|
||||||
|
z = z || '0';
|
||||||
|
n = n + '';
|
||||||
|
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
|
||||||
|
}
|
||||||
|
@ -732,7 +732,7 @@ $LANG = array(
|
|||||||
'recurring_hour' => 'Recurring Hour',
|
'recurring_hour' => 'Recurring Hour',
|
||||||
'pattern' => 'Pattern',
|
'pattern' => 'Pattern',
|
||||||
'pattern_help_title' => 'Pattern Help',
|
'pattern_help_title' => 'Pattern Help',
|
||||||
'pattern_help_1' => 'Create custom invoice and quote numbers by specifying a pattern',
|
'pattern_help_1' => 'Create custom numbers by specifying a pattern',
|
||||||
'pattern_help_2' => 'Available variables:',
|
'pattern_help_2' => 'Available variables:',
|
||||||
'pattern_help_3' => 'For example, :example would be converted to :value',
|
'pattern_help_3' => 'For example, :example would be converted to :value',
|
||||||
'see_options' => 'See options',
|
'see_options' => 'See options',
|
||||||
|
@ -73,7 +73,7 @@ return array(
|
|||||||
"has_credit" => "The client does not have enough credit.",
|
"has_credit" => "The client does not have enough credit.",
|
||||||
"notmasked" => "The values are masked",
|
"notmasked" => "The values are masked",
|
||||||
"less_than" => "The :attribute must be less than :value",
|
"less_than" => "The :attribute must be less than :value",
|
||||||
"has_counter" => "The value must contain {\$counter}",
|
"has_counter" => "To enusre all invoice numbers are unique the pattern needs to contain either {\$counter} or {\$clientIdNumber} and {\$clientInvoiceCounter}",
|
||||||
"valid_contacts" => "The contact must have either an email or name",
|
"valid_contacts" => "The contact must have either an email or name",
|
||||||
"valid_invoice_items" => "The invoice exceeds the maximum amount",
|
"valid_invoice_items" => "The invoice exceeds the maximum amount",
|
||||||
"valid_subdomain" => "The subdomain is restricted",
|
"valid_subdomain" => "The subdomain is restricted",
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
{!! Former::text('client_number_pattern')
|
{!! Former::text('client_number_pattern')
|
||||||
->appendIcon('question-sign')
|
->appendIcon('question-sign')
|
||||||
->addGroupClass('client-pattern')
|
->addGroupClass('client-pattern')
|
||||||
->addGroupClass('number-pattern')
|
->addGroupClass('client-number-pattern')
|
||||||
->label(trans('texts.pattern')) !!}
|
->label(trans('texts.pattern')) !!}
|
||||||
{!! Former::text('client_number_counter')
|
{!! Former::text('client_number_counter')
|
||||||
->label(trans('texts.counter'))
|
->label(trans('texts.counter'))
|
||||||
@ -352,12 +352,14 @@
|
|||||||
@foreach (\App\Models\Invoice::$patternFields as $field)
|
@foreach (\App\Models\Invoice::$patternFields as $field)
|
||||||
@if ($field == 'date:')
|
@if ($field == 'date:')
|
||||||
<li>{$date:format} - {!! link_to(PHP_DATE_FORMATS, trans('texts.see_options'), ['target' => '_blank']) !!}</li>
|
<li>{$date:format} - {!! link_to(PHP_DATE_FORMATS, trans('texts.see_options'), ['target' => '_blank']) !!}</li>
|
||||||
|
@elseif (strpos($field, 'client') !== false)
|
||||||
|
<li class="hide-client">{${{ $field }}}</li>
|
||||||
@else
|
@else
|
||||||
<li>{${{ $field }}}</li>
|
<li>{${{ $field }}}</li>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
<p>{{ trans('texts.pattern_help_3', [
|
<p class="hide-client">{{ trans('texts.pattern_help_3', [
|
||||||
'example' => '{$year}-{$counter}',
|
'example' => '{$year}-{$counter}',
|
||||||
'value' => date('Y') . '-0001'
|
'value' => date('Y') . '-0001'
|
||||||
]) }}</p>
|
]) }}</p>
|
||||||
@ -439,6 +441,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('.number-pattern .input-group-addon').click(function() {
|
$('.number-pattern .input-group-addon').click(function() {
|
||||||
|
$('.hide-client').show();
|
||||||
|
$('#patternHelpModal').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.client-number-pattern .input-group-addon').click(function() {
|
||||||
|
$('.hide-client').hide();
|
||||||
$('#patternHelpModal').modal('show');
|
$('#patternHelpModal').modal('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -24,8 +24,12 @@
|
|||||||
@if ($client)
|
@if ($client)
|
||||||
{!! Former::populate($client) !!}
|
{!! Former::populate($client) !!}
|
||||||
{!! Former::hidden('public_id') !!}
|
{!! Former::hidden('public_id') !!}
|
||||||
@elseif ($account->client_number_counter)
|
@else
|
||||||
{!! Former::populateField('id_number', $account->getNextNumber()) !!}
|
{!! Former::populateField('invoice_number_counter', 1) !!}
|
||||||
|
{!! Former::populateField('quote_number_counter', 1) !!}
|
||||||
|
@if ($account->client_number_counter)
|
||||||
|
{!! Former::populateField('id_number', $account->getNextNumber()) !!}
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -52,8 +56,16 @@
|
|||||||
{!! Former::text('custom_value2')->label($customLabel2) !!}
|
{!! Former::text('custom_value2')->label($customLabel2) !!}
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if ($account->usesClientInvoiceCounter())
|
||||||
|
{!! Former::text('invoice_number_counter')->label('invoice_counter') !!}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($account->usesClientQuoteCounter())
|
||||||
|
{!! Former::text('quote_number_counter')->label('quote_counter') !!}
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default" style="min-height: 500px">
|
<div class="panel panel-default" style="min-height: 500px">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
@ -1622,11 +1622,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setInvoiceNumber(client) {
|
function setInvoiceNumber(client) {
|
||||||
@if ($invoice->id || !$account->hasClientNumberPattern($invoice))
|
@if ($invoice->id || !$account->hasClientNumberPattern($invoice))
|
||||||
return;
|
return;
|
||||||
@endif
|
@endif
|
||||||
var number = '{{ $account->applyNumberPattern($invoice) }}';
|
var number = '{{ $account->applyNumberPattern($invoice) }}';
|
||||||
number = number.replace('{$custom1}', client.custom_value1 ? client.custom_value1 : '');
|
number = number.replace('{$clientCustom1}', client.custom_value1 ? client.custom_value1 : '');
|
||||||
|
number = number.replace('{$clientCustom2}', client.custom_value2 ? client.custom_value1 : '');
|
||||||
|
number = number.replace('{$clientIdNumber}', client.id_number ? client.id_number : '');
|
||||||
|
number = number.replace('{$clientInvoiceCounter}', pad(client.invoice_number_counter, {{ $account->invoice_number_padding }}));
|
||||||
|
number = number.replace('{$clientQuoteCounter}', pad(client.quote_number_counter, {{ $account->invoice_number_padding }}));
|
||||||
|
// backwards compatibility
|
||||||
|
number = number.replace('{$custom1}', client.custom_value1 ? client.custom_value1 : '');
|
||||||
number = number.replace('{$custom2}', client.custom_value2 ? client.custom_value1 : '');
|
number = number.replace('{$custom2}', client.custom_value2 ? client.custom_value1 : '');
|
||||||
number = number.replace('{$idNumber}', client.id_number ? client.id_number : '');
|
number = number.replace('{$idNumber}', client.id_number ? client.id_number : '');
|
||||||
model.invoice().invoice_number(number);
|
model.invoice().invoice_number(number);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user