116 lines
4.1 KiB
PHP
116 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Qr;
|
|
|
|
use App\Models\Household;
|
|
use App\Models\PartnerStore;
|
|
use App\Models\QrCode;
|
|
use App\Models\QrCodeBatch;
|
|
use App\Models\ServiceArea;
|
|
use App\Models\User;
|
|
use App\States\QrCode\Active;
|
|
use App\States\QrCode\Unassigned;
|
|
use App\Tenancy\Tenancy;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class BatchGenerator
|
|
{
|
|
public function __construct(private readonly QrSerialGenerator $serials) {}
|
|
|
|
/**
|
|
* Generate a new batch with $quantity codes in the unassigned state.
|
|
*/
|
|
public function generate(
|
|
int $quantity,
|
|
string $purpose,
|
|
?ServiceArea $targetArea = null,
|
|
?int $targetStoreId = null,
|
|
?User $createdBy = null,
|
|
?\DateTimeInterface $expiresAt = null,
|
|
?string $notes = null,
|
|
?int $householdId = null,
|
|
): QrCodeBatch {
|
|
$now = Carbon::now();
|
|
$areaCode = $targetArea?->code ?? 'PH';
|
|
$batchSeq = $this->nextBatchSeq($now);
|
|
$batchNumber = sprintf('B%s%04d', $now->format('ymd'), $batchSeq);
|
|
|
|
return DB::transaction(function () use (
|
|
$batchNumber, $quantity, $purpose, $targetArea, $targetStoreId,
|
|
$createdBy, $expiresAt, $notes, $now, $areaCode, $batchSeq, $householdId,
|
|
) {
|
|
$tenantId = null;
|
|
if ($householdId) {
|
|
$tenantId = Household::find($householdId)?->tenant_id;
|
|
} elseif ($targetArea) {
|
|
$tenantId = $targetArea->tenant_id;
|
|
} elseif ($targetStoreId) {
|
|
$tenantId = PartnerStore::find($targetStoreId)?->tenant_id;
|
|
}
|
|
|
|
if (empty($tenantId)) {
|
|
$tenantId = Tenancy::current()?->id;
|
|
}
|
|
|
|
$batch = QrCodeBatch::create([
|
|
'tenant_id' => $tenantId,
|
|
'batch_number' => $batchNumber,
|
|
'quantity' => $quantity,
|
|
'purpose' => $purpose,
|
|
'target_area_id' => $targetArea?->id,
|
|
'target_store_id' => $targetStoreId,
|
|
'household_id' => $householdId,
|
|
'created_by_admin_id' => $createdBy?->id,
|
|
'expires_at' => $expiresAt,
|
|
'notes' => $notes,
|
|
]);
|
|
|
|
$rows = [];
|
|
$tsNow = $now->toDateTimeString();
|
|
// Bulk insert bypasses model events, so we have to set tenant_id
|
|
// explicitly. Inherit from the just-created batch (which goes
|
|
// through the HasTenant trait) so any cross-tenant subtleties
|
|
// bubble up early.
|
|
$tenantId = $batch->tenant_id ?? Tenancy::current()?->id;
|
|
for ($i = 1; $i <= $quantity; $i++) {
|
|
$serial = $this->serials->build($areaCode, $now, $batchSeq, $i);
|
|
$rows[] = [
|
|
'tenant_id' => $tenantId,
|
|
'serial' => $serial,
|
|
'barcode_value' => $serial,
|
|
'batch_id' => $batch->id,
|
|
'status' => $householdId ? Active::$name : Unassigned::$name,
|
|
'assigned_to_household_id' => $householdId,
|
|
'allocated_at' => $householdId ? $tsNow : null,
|
|
'activated_at' => $householdId ? $tsNow : null,
|
|
'expires_at' => $expiresAt,
|
|
'created_at' => $tsNow,
|
|
'updated_at' => $tsNow,
|
|
];
|
|
|
|
// Bulk insert in chunks to keep memory bounded for big batches.
|
|
if (count($rows) >= 1000) {
|
|
QrCode::insert($rows);
|
|
$rows = [];
|
|
}
|
|
}
|
|
|
|
if (! empty($rows)) {
|
|
QrCode::insert($rows);
|
|
}
|
|
|
|
return $batch->fresh();
|
|
});
|
|
}
|
|
|
|
private function nextBatchSeq(Carbon $when): int
|
|
{
|
|
// Per-month batch sequence to match the 'ym' format in QR serials
|
|
$startOfMonth = $when->copy()->startOfMonth();
|
|
$endOfMonth = $when->copy()->endOfMonth();
|
|
|
|
return QrCodeBatch::withTrashed()->whereBetween('created_at', [$startOfMonth, $endOfMonth])->count() + 1;
|
|
}
|
|
}
|