*/ public static function get_invoice_series(): array { $prefix = self::normalize_series_prefix( (string) get_option( 'eightdigits_factura_invoice_prefix', 'F' ) ); return array( array( 'prefix' => '' !== $prefix ? $prefix : 'F', 'name' => __( 'General', '8digits-factura-for-woocommerce' ), 'start' => max( 1, (int) get_option( 'eightdigits_factura_series_start', 1 ) ), ), ); } /** * Resolve a human-readable label for a series prefix. */ public static function get_series_label( string $prefix ): string { $prefix = self::normalize_series_prefix( $prefix ); if ( '' === $prefix ) { return ''; } foreach ( self::get_invoice_series() as $series ) { if ( $series['prefix'] === $prefix ) { return $series['name']; } } return $prefix; } /** * Generate and reserve the next invoice number. */ public static function next( string $type = 'invoice', string $series_prefix = '' ): string { global $wpdb; $year = (int) current_time( 'Y' ); $prefix = self::get_prefix( $series_prefix ); $wpdb->query( 'START TRANSACTION' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $option_key = 'eightdigits_factura_invoice_counter'; $counters = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s FOR UPDATE", $option_key ) ); $counters = $counters ? maybe_unserialize( $counters ) : array(); $counter_key = $type . '_' . $year . '_' . $prefix; $current = isset( $counters[ $counter_key ] ) ? (int) $counters[ $counter_key ] : ( self::get_series_start( $prefix ) - 1 ); $next_number = $current + 1; $counters[ $counter_key ] = $next_number; update_option( $option_key, $counters ); $wpdb->query( 'COMMIT' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return sprintf( '%s-%04d-%04d', $prefix, $year, $next_number ); } /** * Parse an invoice number back into its components. * * @return array{prefix:string, year:int, sequence:int}|false */ public static function parse( string $invoice_number ) { if ( preg_match( '/^([A-Z0-9\-]+)-(\d{4})-(\d+)$/', $invoice_number, $matches ) ) { return array( 'prefix' => $matches[1], 'year' => (int) $matches[2], 'sequence' => (int) $matches[3], ); } return false; } /** * Resolve the prefix for an invoice. */ private static function get_prefix( string $series_prefix = '' ): string { $prefix = self::normalize_series_prefix( $series_prefix ); if ( '' === $prefix ) { $series = self::get_invoice_series(); $prefix = $series[0]['prefix'] ?? 'F'; } return (string) apply_filters( 'eightdigits_factura_invoice_prefix', $prefix ); } /** * Resolve the starting number for the series. */ private static function get_series_start( string $prefix ): int { foreach ( self::get_invoice_series() as $series ) { if ( $series['prefix'] === $prefix ) { return max( 1, (int) $series['start'] ); } } return max( 1, (int) get_option( 'eightdigits_factura_series_start', 1 ) ); } }