mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-09-02 13:21:02 +00:00
* refactor: stabilize model identities for domain migration * refactor: extract module platform context * refactor: assign models to domain contexts * refactor: extract ai platform context * refactor: extract storage platform context * refactor: extract mail platform context * refactor: extract pdf platform context * refactor: extract operations platform context * refactor: move installation into operations platform * refactor: extract money domain context * refactor: extract taxation domain context * refactor: extract catalog domain context * refactor: extract metadata domain context * refactor: extract reporting domain context * refactor: extract purchases domain context * refactor: extract receivables domain context * refactor: extract accounts domain context * refactor: complete reporting statement boundary * refactor: extract contacts domain context * refactor: extract sales domain context * refactor: remove legacy application layers * fix: migrate legacy bouncer role identities
270 lines
6.8 KiB
PHP
270 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Sales\Application;
|
|
|
|
use App\Domains\Accounts\Models\CompanySetting;
|
|
use App\Domains\Contacts\Models\Customer;
|
|
|
|
class SerialNumberService
|
|
{
|
|
public const VALID_PLACEHOLDERS = ['CUSTOMER_SERIES', 'SEQUENCE', 'DATE_FORMAT', 'SERIES', 'RANDOM_SEQUENCE', 'DELIMITER', 'CUSTOMER_SEQUENCE'];
|
|
|
|
private $model;
|
|
|
|
private $ob;
|
|
|
|
private $customer;
|
|
|
|
private $company;
|
|
|
|
private $settingKey;
|
|
|
|
private $sequenceScope = [];
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
public $nextSequenceNumber;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
public $nextCustomerSequenceNumber;
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function setModel($model)
|
|
{
|
|
$this->model = $model;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setModelObject($id = null)
|
|
{
|
|
$this->ob = $this->model::find($id);
|
|
|
|
if ($this->ob && $this->ob->sequence_number) {
|
|
$this->nextSequenceNumber = $this->ob->sequence_number;
|
|
}
|
|
|
|
if (isset($this->ob) && isset($this->ob->customer_sequence_number) && isset($this->customer) && $this->ob->customer_id == $this->customer->id) {
|
|
$this->nextCustomerSequenceNumber = $this->ob->customer_sequence_number;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function setCompany($company)
|
|
{
|
|
$this->company = $company;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function setCustomer($customer = null)
|
|
{
|
|
$this->customer = Customer::find($customer);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Override the company setting the number format is read from.
|
|
*
|
|
* Without this the key is derived from the model class name, which is not
|
|
* enough for documents that share a table (credit notes are Invoice rows
|
|
* but carry their own format).
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setSettingKey(string $key)
|
|
{
|
|
$this->settingKey = $key;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Restrict the sequence lookups to a subset of the model's rows.
|
|
*
|
|
* Takes column => value constraints that are applied on top of the company
|
|
* (and customer) filters, so documents sharing a table can each keep an
|
|
* independent, gapless sequence.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setSequenceScope(array $constraints)
|
|
{
|
|
$this->sequenceScope = $constraints;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getNextNumber(?string $format = null)
|
|
{
|
|
$modelName = strtolower(class_basename($this->model));
|
|
$settingKey = $this->settingKey ?: $modelName.'_number_format';
|
|
$companyId = $this->company;
|
|
|
|
if ($format === null) {
|
|
$format = CompanySetting::getSetting(
|
|
$settingKey,
|
|
$companyId
|
|
);
|
|
}
|
|
$this->setNextNumbers();
|
|
|
|
$serialNumber = $this->generateSerialNumber(
|
|
$format
|
|
);
|
|
|
|
return $serialNumber;
|
|
}
|
|
|
|
public function setNextNumbers()
|
|
{
|
|
$this->nextSequenceNumber ?
|
|
$this->nextSequenceNumber : $this->setNextSequenceNumber();
|
|
|
|
$this->nextCustomerSequenceNumber ?
|
|
$this->nextCustomerSequenceNumber : $this->setNextCustomerSequenceNumber();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function setNextSequenceNumber()
|
|
{
|
|
$companyId = $this->company;
|
|
|
|
$query = $this->model::orderBy('sequence_number', 'desc')
|
|
->where('company_id', $companyId)
|
|
->where('sequence_number', '<>', null);
|
|
|
|
foreach ($this->sequenceScope as $column => $value) {
|
|
$query->where($column, $value);
|
|
}
|
|
|
|
$last = $query->take(1)->first();
|
|
|
|
$this->nextSequenceNumber = ($last) ? $last->sequence_number + 1 : 1;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return self
|
|
*/
|
|
public function setNextCustomerSequenceNumber()
|
|
{
|
|
$customer_id = ($this->customer) ? $this->customer->id : 1;
|
|
|
|
$query = $this->model::orderBy('customer_sequence_number', 'desc')
|
|
->where('company_id', $this->company)
|
|
->where('customer_id', $customer_id)
|
|
->where('customer_sequence_number', '<>', null);
|
|
|
|
foreach ($this->sequenceScope as $column => $value) {
|
|
$query->where($column, $value);
|
|
}
|
|
|
|
$last = $query->take(1)->first();
|
|
|
|
$this->nextCustomerSequenceNumber = ($last) ? $last->customer_sequence_number + 1 : 1;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public static function getPlaceholders(string $format)
|
|
{
|
|
$regex = '/{{([A-Z_]{1,})(?::)?([a-zA-Z0-9_]{1,6}|.{1})?}}/';
|
|
|
|
preg_match_all($regex, $format, $placeholders);
|
|
array_shift($placeholders);
|
|
$validPlaceholders = collect();
|
|
|
|
/** @var array */
|
|
$mappedPlaceholders = array_map(
|
|
null,
|
|
current($placeholders),
|
|
end($placeholders)
|
|
);
|
|
|
|
foreach ($mappedPlaceholders as $placeholder) {
|
|
$name = current($placeholder);
|
|
$value = end($placeholder);
|
|
|
|
if (in_array($name, self::VALID_PLACEHOLDERS)) {
|
|
$validPlaceholders->push([
|
|
'name' => $name,
|
|
'value' => $value,
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $validPlaceholders;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
private function generateSerialNumber(string $format)
|
|
{
|
|
$serialNumber = '';
|
|
|
|
$placeholders = self::getPlaceholders($format);
|
|
|
|
foreach ($placeholders as $placeholder) {
|
|
$name = $placeholder['name'];
|
|
$value = $placeholder['value'];
|
|
|
|
switch ($name) {
|
|
case 'SEQUENCE':
|
|
$value = $value ? $value : 6;
|
|
$serialNumber .= str_pad($this->nextSequenceNumber, $value, 0, STR_PAD_LEFT);
|
|
|
|
break;
|
|
case 'DATE_FORMAT':
|
|
$value = $value ? $value : 'Y';
|
|
$serialNumber .= date($value);
|
|
|
|
break;
|
|
case 'RANDOM_SEQUENCE':
|
|
$value = $value ? $value : 6;
|
|
$serialNumber .= substr(bin2hex(random_bytes($value)), 0, $value);
|
|
|
|
break;
|
|
case 'CUSTOMER_SERIES':
|
|
if (isset($this->customer)) {
|
|
$serialNumber .= $this->customer->prefix ?? 'CST';
|
|
} else {
|
|
$serialNumber .= 'CST';
|
|
}
|
|
|
|
break;
|
|
case 'CUSTOMER_SEQUENCE':
|
|
$serialNumber .= str_pad($this->nextCustomerSequenceNumber, $value, 0, STR_PAD_LEFT);
|
|
|
|
break;
|
|
default:
|
|
$serialNumber .= $value;
|
|
}
|
|
}
|
|
|
|
return $serialNumber;
|
|
}
|
|
}
|