getTable(); return Schema::hasTable($table) && Schema::hasColumn($table, 'supplier') && $this->types->ready(); } catch (QueryException) { return false; } } /** * @param array $filters * @return array{ready:bool, rows:Collection, summary:array, options:array} */ public function pageData(array $filters): array { if (! $this->ready()) { return [ 'ready' => false, 'rows' => collect(), 'summary' => ['total' => 0, 'active' => 0, 'inactive' => 0, 'couriers' => 0], 'options' => $this->emptyOptions(), ]; } try { $all = CourierSurchargeQuote::query() ->orderBy('id') ->get(); } catch (QueryException) { return [ 'ready' => false, 'rows' => collect(), 'summary' => ['total' => 0, 'active' => 0, 'inactive' => 0, 'couriers' => 0], 'options' => $this->emptyOptions(), ]; } $filtered = $all->filter(function (CourierSurchargeQuote $quote) use ($filters): bool { foreach (['courier', 'scope', 'reconciler', 'supplier', 'mode'] as $key) { if (($filters[$key] ?? '') !== '' && (string) $quote->{$key} !== $filters[$key]) { return false; } } if (($filters['type'] ?? '') !== '' && ! in_array($filters['type'], (array) $quote->types, true)) { return false; } if (($filters['status'] ?? '') !== '' && $quote->statusLabel() !== $filters['status']) { return false; } $keyword = trim((string) ($filters['keyword'] ?? '')); if ($keyword !== '') { $searchable = implode('|', [ $quote->courier, $quote->scope, $quote->reconciler, (string) $quote->supplier, $quote->typeLabel(), $quote->mode, $quote->standard, $quote->condition, (string) $quote->remark, ]); if (! str_contains($searchable, $keyword)) { return false; } } return true; })->values(); $typeRecords = $this->types->all(); $activeTypeNames = $typeRecords ->where('status', CourierSurchargeType::STATUS_ACTIVE) ->pluck('name') ->values(); return [ 'ready' => true, 'rows' => $filtered->map(fn (CourierSurchargeQuote $quote): array => $this->present($quote)), 'summary' => [ 'total' => $filtered->count(), 'active' => $filtered->where('status', CourierSurchargeQuote::STATUS_ACTIVE)->count(), 'inactive' => $filtered->where('status', CourierSurchargeQuote::STATUS_INACTIVE)->count(), 'couriers' => $filtered->pluck('courier')->unique()->count(), ], 'options' => [ 'couriers' => $all->pluck('courier')->filter()->unique()->sort()->values(), 'types' => $activeTypeNames, 'filter_types' => $typeRecords->pluck('name')->values(), 'type_records' => $typeRecords->map(fn ($type): array => [ 'id' => $type->id, 'name' => $type->name, 'status' => $type->status, 'status_label' => $type->statusLabel(), ])->values(), 'modes' => collect(self::MODES), 'scopes' => self::SCOPES, 'reconcilers' => $all->pluck('reconciler')->filter()->unique()->sort()->values(), 'suppliers' => $all->pluck('supplier')->filter()->unique()->sort()->values(), 'statuses' => ['已启用', '已停用'], ], ]; } /** @return list */ public function validTypeNames(): array { return $this->types->validNames(); } /** @return list */ public function activeTypeNames(): array { return $this->types->activeNames(); } /** @param array $data */ public function findImportDuplicate(array $data, bool $lockForUpdate = false): ?CourierSurchargeQuote { $query = CourierSurchargeQuote::query() ->where('company_id', company_id()) ->where('courier', trim((string) $data['courier'])) ->where('scope', trim((string) $data['scope'])) ->where('reconciler', trim((string) $data['reconciler'])) ->where('mode', trim((string) $data['mode'])) ->where('condition', trim((string) $data['condition'])) ->whereDate('effective_at', (string) $data['effective_at']); $supplier = trim((string) ($data['supplier'] ?? '')); $supplier === '' ? $query->whereNull('supplier') : $query->where('supplier', $supplier); if ($lockForUpdate) { $query->lockForUpdate(); } $types = $this->normalizedTypes((array) $data['types']); return $query->get()->first( fn (CourierSurchargeQuote $quote): bool => $this->normalizedTypes((array) $quote->types) === $types, ); } /** @param array $data */ public function importIdentity(array $data): string { return hash('sha256', json_encode([ trim((string) ($data['courier'] ?? '')), trim((string) ($data['scope'] ?? '')), trim((string) ($data['reconciler'] ?? '')), trim((string) ($data['supplier'] ?? '')), $this->normalizedTypes((array) ($data['types'] ?? [])), trim((string) ($data['mode'] ?? '')), trim((string) ($data['effective_at'] ?? '')), trim((string) ($data['condition'] ?? '')), ], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); } /** @param array $data */ public function create(array $data, int $userId): CourierSurchargeQuote { return DB::transaction(function () use ($data, $userId): CourierSurchargeQuote { $quote = CourierSurchargeQuote::query()->create($this->attributes($data, $userId) + [ 'status' => CourierSurchargeQuote::STATUS_ACTIVE, 'created_by' => $userId, ]); $this->audits->record( CourierSurchargeAuditLog::ACTION_CREATED, CourierSurchargeAuditLog::TARGET_QUOTE, (int) $quote->id, $this->targetLabel($quote), ['after' => $this->snapshot($quote)], $userId, ); return $quote; }); } /** @param array $data */ public function update(CourierSurchargeQuote $quote, array $data, int $userId): CourierSurchargeQuote { return DB::transaction(function () use ($quote, $data, $userId): CourierSurchargeQuote { $before = $this->snapshot($quote); $quote->update($this->attributes($data, $userId)); $fresh = $quote->fresh(); $this->audits->record( CourierSurchargeAuditLog::ACTION_UPDATED, CourierSurchargeAuditLog::TARGET_QUOTE, (int) $fresh->id, $this->targetLabel($fresh), ['before' => $before, 'after' => $this->snapshot($fresh)], $userId, ); return $fresh; }); } public function setStatus(CourierSurchargeQuote $quote, string $status, int $userId): CourierSurchargeQuote { abort_unless(in_array($status, [CourierSurchargeQuote::STATUS_ACTIVE, CourierSurchargeQuote::STATUS_INACTIVE], true), 422, '报价规则状态不合法。'); return DB::transaction(function () use ($quote, $status, $userId): CourierSurchargeQuote { $oldStatus = $quote->status; $quote->forceFill(['status' => $status, 'updated_by' => $userId])->save(); $fresh = $quote->fresh(); if ($oldStatus !== $status) { $this->audits->record( $status === CourierSurchargeQuote::STATUS_ACTIVE ? CourierSurchargeAuditLog::ACTION_ACTIVATED : CourierSurchargeAuditLog::ACTION_DEACTIVATED, CourierSurchargeAuditLog::TARGET_QUOTE, (int) $fresh->id, $this->targetLabel($fresh), [ 'before' => ['status' => $this->statusLabel($oldStatus)], 'after' => ['status' => $this->statusLabel($status)], ], $userId, ); } return $fresh; }); } private function targetLabel(CourierSurchargeQuote $quote): string { $type = $quote->typeLabel(); return trim(sprintf('%s%s', $quote->courier, $type === '' ? '' : ' · ' . $type)); } /** @return array */ private function snapshot(CourierSurchargeQuote $quote): array { return [ 'courier' => $quote->courier, 'scope' => $quote->scope, 'reconciler' => $quote->reconciler, 'supplier' => $quote->supplier, 'types' => array_values((array) $quote->types), 'mode' => $quote->mode, 'standard' => $quote->standard, 'condition' => $quote->condition, 'effective_at' => optional($quote->effective_at)->format('Y-m-d'), 'status' => $this->statusLabel($quote->status), 'remark' => $quote->remark, ]; } private function statusLabel(?string $status): string { return $status === CourierSurchargeQuote::STATUS_ACTIVE ? '已启用' : '已停用'; } /** @param array $data */ private function attributes(array $data, int $userId): array { return [ 'company_id' => company_id(), 'courier' => trim((string) $data['courier']), 'scope' => trim((string) $data['scope']), 'reconciler' => trim((string) $data['reconciler']), 'supplier' => trim((string) ($data['supplier'] ?? '')) ?: null, 'types' => array_values(array_unique(array_map('strval', $data['types']))), 'mode' => trim((string) $data['mode']), 'standard' => trim((string) $data['standard']), 'condition' => trim((string) $data['condition']), 'effective_at' => $data['effective_at'], 'remark' => trim((string) ($data['remark'] ?? '')) ?: null, 'updated_by' => $userId, ]; } /** @param array $types @return list */ private function normalizedTypes(array $types): array { $types = array_values(array_unique(array_filter(array_map( static fn (mixed $type): string => trim((string) $type), $types, )))); sort($types, SORT_STRING); return $types; } /** @return array */ private function present(CourierSurchargeQuote $quote): array { return [ 'id' => 'SUR-' . str_pad((string) $quote->id, 3, '0', STR_PAD_LEFT), 'db_id' => $quote->id, 'courier' => $quote->courier, 'scope' => $quote->scope, 'reconciler' => $quote->reconciler, 'supplier' => $quote->supplier, 'types' => array_values((array) $quote->types), 'type' => $quote->typeLabel(), 'mode' => $quote->mode, 'standard' => $quote->standard, 'condition' => $quote->condition, 'effective_at' => optional($quote->effective_at)->format('Y-m-d'), 'status_code' => $quote->status, 'status' => $quote->statusLabel(), 'remark' => $quote->remark, ]; } /** @return array */ private function emptyOptions(): array { return [ 'couriers' => collect(), 'types' => collect(self::TYPES), 'filter_types' => collect(self::TYPES), 'type_records' => collect(), 'modes' => collect(self::MODES), 'scopes' => self::SCOPES, 'reconcilers' => collect(), 'suppliers' => collect(), 'statuses' => ['已启用', '已停用'], ]; } }