Last active
April 25, 2022 10:53
-
-
Save ratiw/e15d643417039dd2d04b to your computer and use it in GitHub Desktop.
Make new running number (e.g. doc no., invoice no.) based on the given pattern and the last number.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
class InvalidPatternException extends \Exception | |
{ | |
} | |
/** | |
* Pattern is enclosed in the open and close delimiter | |
* The default open delimter is { | |
* The default close delimiter is } | |
* E.g. {YYYY} | |
* | |
* Pattern can be easily added by creating public static function | |
* that begins with "replace_" and followed by the pattern name | |
* E.g. public static function replace_YYYY($option) {} | |
* | |
* Pattern can define optional option by adding colon (:) at the end | |
* of the pattern, followed by the option string. This option will be | |
* passed to the corresponding "replace_" function. The meaning of the | |
* option is totally depending on the replace function. | |
* E.g. {YYYY:thai} | |
* | |
* Available Patterns | |
* 9 = number | |
* YYYY = 4 digits year | |
* YY = 2 digits year | |
* MM = 2 digits month | |
* DD = 2 digits day | |
* HH = 2 digits hour | |
* NN = 2 digits minute | |
* SS = 2 digits second | |
* | |
* E.g. 'JB{YY}{MM}-{9999}' | |
* JB = not a pattern, will left in-place | |
* {YY} = will be replaced with 2 digits of the current year | |
* {MM} = will be replaced with 2 digits of the current month | |
* - = not a pattern, will left in-place | |
* {9999} = running number, will be replaced with the 0001 | |
* or the next number if the $lastNo is given | |
*/ | |
class RunningNumberGenerator | |
{ | |
/** | |
* Generate next running number based on the given pattern and last no | |
* If the last no. is not given, it will start from 1 | |
* | |
* @param $pattern | |
* @param null $lastNo | |
* @return string | |
* @throws InvalidPatternException | |
*/ | |
public static function make($pattern, $lastNo = null) | |
{ | |
$patterns = static::parsePattern($pattern); | |
$offset = 0; | |
$reset = false; | |
$newNo = []; | |
foreach ($patterns as $prop) { | |
if (!$prop['is_pattern']) { | |
$newNo[] = $prop['value']; | |
$offset += strlen($prop['value']); | |
continue; | |
} | |
$key = (string) $prop['key']; | |
$length = strlen($key); | |
$segment = $lastNo ? substr($lastNo, $offset, $length) : '0'; | |
if ($key[0] == '9') { | |
$prop['value'] = $reset ? 0 : (int)$segment; | |
$newNo[] = str_pad((string) ($prop['value'] + 1), $length, '0', STR_PAD_LEFT); | |
} else { | |
if ($segment != $prop['value']) $reset = true; | |
$newNo[] = $prop['value']; | |
} | |
$offset += $length; | |
} | |
return implode('', $newNo); | |
} | |
/** | |
* Parse the given pattern into array. | |
* Pattern is denoted by the given openning and closing symbol | |
* the defaults are { and } | |
* | |
* @param $pattern | |
* @param string $open openning symbol for the pattern | |
* @param string $close closing symbol for the pattern | |
* @return array | |
* @throws InvalidPatternException | |
*/ | |
public static function parsePattern($pattern, $open = '{', $close = '}') | |
{ | |
$patterns = []; | |
$other = ''; | |
for ($i = 0; $i < strlen($pattern); $i++) { | |
if ($pattern[$i] == $open) { | |
if (!empty($other)) { | |
$patterns[] = ['key' => $other, 'is_pattern' => false, 'value' => $other]; | |
$other = ''; | |
} | |
if ($j = strpos($pattern, $close, $i + 1)) { | |
$key = substr($pattern, $i + 1, $j - $i - 1); | |
$option = explode(':', $key); | |
$key = array_shift($option); | |
$patterns[] = ['key' => $key, 'is_pattern' => true, 'value' => static::patternReplace($key, $option)]; | |
$i = $j; | |
} else { | |
throw new InvalidPatternException("No close symbol: $close"); | |
} | |
} else { | |
$other .= $pattern[$i]; | |
} | |
} | |
return $patterns; | |
} | |
/** | |
* Replace the pattern with the corresponding value. | |
* More replacers can be added by extending the class and define | |
* replacer function with the function name starts with 'replace_' | |
* followed by the pattern itself. | |
* | |
* @param $pattern | |
* @param $option | |
* @return string | |
* @throws InvalidPatternException | |
*/ | |
public static function patternReplace($pattern, $option) | |
{ | |
if ($pattern[0] == '9') { | |
return str_repeat('0', strlen($pattern)); | |
} | |
$replacer = 'replace_' . $pattern; | |
if (method_exists(__CLASS__, $replacer)) { | |
return static::$replacer($option); | |
} | |
throw new InvalidPatternException("No replacer for pattern {$pattern}"); | |
} | |
protected static function replace_YYYY($option) | |
{ | |
if (!empty($option) && $option[0] == 'thai') { | |
return (string) ((int) date('Y') + 543); | |
} | |
return date('Y'); | |
} | |
protected static function replace_YY($option) | |
{ | |
if (!empty($option) && $option[0] == 'thai') { | |
return substr((string) ((int) date('y') + 543), -2); | |
} | |
return date('y'); | |
} | |
protected static function replace_MM($option) | |
{ | |
return date('m'); | |
} | |
protected static function replace_DD($option) | |
{ | |
return date('d'); | |
} | |
protected static function replace_HH($option) | |
{ | |
return date('H'); | |
} | |
protected static function replace_NN($option) | |
{ | |
return date('i'); | |
} | |
protected static function replace_SS($option) | |
{ | |
return date('s'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment