format('Y'); // lockForUpdate, not an atomic increment: the year check below has to // see a value nobody else can change between the read and the write. $locked = InvoiceSeries::query() ->whereKey($series->getKey()) ->lockForUpdate() ->firstOrFail(); $sequence = (int) $locked->next_number; $counterYear = $locked->counter_year; // A yearly series starts at 1 in a new year. Checked against the stored // year rather than against "is this January": an installation that // issued nothing for fourteen months must still restart, and one that // issues on New Year's Eve at 23:59 must not restart for the invoice // that follows a minute later under the old year. if ($locked->yearly_reset && $counterYear !== null && (int) $counterYear !== $year) { $sequence = 1; } $locked->forceFill([ 'next_number' => $sequence + 1, 'counter_year' => $locked->yearly_reset ? $year : null, ])->save(); return [$this->format($locked, $sequence, $year), $sequence, $locked->yearly_reset ? $year : null]; } /** "RE-2026-0001", or "RE-0001" for a series that does not reset. */ private function format(InvoiceSeries $series, int $sequence, int $year): string { $padded = str_pad((string) $sequence, (int) $series->digits, '0', STR_PAD_LEFT); return $series->yearly_reset ? sprintf('%s-%d-%s', $series->prefix, $year, $padded) : sprintf('%s-%s', $series->prefix, $padded); } }