为特定公司的发票生成唯一序列号Php-Laravel

为特定公司的发票生成唯一序列号Php-Laravel,php,mysql,laravel,laravel-6,Php,Mysql,Laravel,Laravel 6,我正在构建一个Laravel应用程序来生成销售发票。 来自多个公司的多个人一次生成发票。 我有一个发票表格作为发票表格,其中id是主键,编号是唯一的 id | company_id | series | number | amount 现在要生成发票号,我要做的是;对于一家特定的公司,计算并加上1 //assume $inv->prefix is a string which gives me series for particular company. $series = Invoi

我正在构建一个Laravel应用程序来生成销售发票。 来自多个公司的多个人一次生成发票。 我有一个发票表格作为发票表格,其中id是主键,编号是唯一的

id | company_id | series |  number | amount
现在要生成发票号,我要做的是;对于一家特定的公司,计算并加上1

//assume $inv->prefix is a string which gives me series for particular company.
$series = Invoice::where('series', $inv->prefix)->max('number');
if(isset($series) && strlen($series) > 0){
    $series += 1; 
} else {
    $series = 1;
}
$inv->number = $series;
现在的问题是,当两个用户试图同时为同一系列生成发票时,由于我的表中的数字列是唯一的,所以会出现重复错误

我能做点像这样的事吗

do{
    $series = Invoice::where('series', $inv->prefix)->max('number');
    if(isset($series) && strlen($series) > 0){
        $series += 1; 
    } else {
        $series = 1;
    }
    $inv->number = $series;
} while($inv->save())
有人能帮我吗。
如果出现重复条目异常,代码应再次显示计数,并尝试再次保存记录。

我建议不要只是增加新InvoiceId的编号,而是为该项设置一个模式-

创建一个实用程序类-

class NumberUtility
{
    /**
     * This method generates the random number
     *
     * @param int $length
     *
     * @return int
     */
    public static function getRandomNumber($length = 8): int
    {
        $intMin = (10 ** $length) / 10;
        $intMax = (10 ** $length) - 1;

        try {
            $randomNumber = random_int($intMin, $intMax);
        } catch (\Exception $exception) {
            \Log::error('Failed to generate random number Retrying...');
            \Log::debug(' Error: '.$exception->getMessage());
            $randomNumber = self::getRandomNumber($length);
        }
        return $randomNumber;
    }
}
现在创建一个获取唯一发票号的方法,如下所示-

public function getUniqueInvoiceNumber($companyId)
{
    $randomNumber = NumberUtility::getRandomNumber(4);
    $invoiceNumber =  (string)$companyId.$randomNumber;
    $exist = Invoice::where('number', $invoiceNumber)->first();
    if ($exist) {
        $invoiceNumber = $this->getUniqueInvoiceNumber($companyId);
    }
    return $invoiceNumber;
}
你可以用它来处理你的发票。有重复记录的机会非常低;尽管出于设计考虑,我还是选择了表锁定(参见完整答案)


来自多个公司的多个人一次生成发票

在这种情况下,随机化数据或增加数据并不是可行的方法。如果您是基于自定义模式生成发票,该模式将数据库中的数据用作变量,则生成重复发票的可能性很高。您应该为当前事务锁定正在使用的表。这将防止插入任何重复的数据。我不知道你的整个数据库结构,尽管我提供的是一个演示:

LOCK TABLES working_table WRITE, working_table2 WRITE;

INSERT INTO working_table ( ... ) 
VALUES ( ...,
    (SELECT number
    FROM working_table2
    WHERE series = 'prefix'
    ORDERBY number DESC
    LIMIT 1)+1
);

UNLOCK TABLES;

然后可以像这样调用sql语句:

DB::select("
    LOCK TABLES working_table WRITE, working_table2 WRITE;

    INSERT INTO working_table ( column ) 
    VALUES (
        (SELECT number
        FROM working_table2
        WHERE series = ?
        ORDERBY number DESC
        LIMIT 1)+1
    );

    UNLOCK TABLES;
", [ 'param1' ]) // these are the parameters to prepare
注意:如果使用持久数据库连接,则必须处理Laravel应用程序引发的所有错误,因为未经处理的错误将停止脚本,而不会停止保持与数据库连接的子进程。取消对子进程的处理将保持表锁定!因为您的数据库正在为连接会话锁定它们。有关此行为的详细信息,请阅读。

您可以使用PHP的uniqid()函数:

$series = uniqid(); //current time in microseconds 

你最好使用全局锁定机制。例如,在伪代码
中,当资源(我猜是公司)存在锁时,睡眠时间为100ms;然后再次检查锁==>当锁不存在时,创建它==>执行原子操作(增量)==>释放锁
这样,一次只有一个客户端将为给定前缀递增序列,同时尝试将保持一段时间,然后才能递增。然后使用表锁定。然后用序列更改随机数。它会解决你的问题。
$series = uniqid(); //current time in microseconds