> 供应商名称 => 生效中的配置(未删除) */ public function activeConfigs(): array { $result = []; foreach ($this->query()->whereNull('deleted_at')->get() as $row) { $result[trim($row->supplier_name)] = (array) $row; } return $result; } public function filtered(array $filters) { $query = $this->query(); $status = $filters['deleted_status'] ?? 'active'; if ($status === 'deleted') { $query->whereNotNull('deleted_at'); } elseif ($status !== 'all') { $query->whereNull('deleted_at'); } if (($filters['supplier_name'] ?? '') !== '') { $query->where('supplier_name', 'like', '%' . addcslashes($filters['supplier_name'], '\\%_') . '%'); } if (($filters['mode'] ?? '') !== '') { $query->where('mode', $filters['mode']); } return $query->orderBy('supplier_name')->orderBy('id'); } /** @param array{supplier_name:string,mode:string,effective_from:?string,effective_to:?string,source_text?:string} $data */ public function save(array $data, string $actor, ?int $id = null): int { return DB::transaction(function () use ($data, $actor, $id): int { $before = $id === null ? null : $this->query()->where('id', $id)->whereNull('deleted_at')->lockForUpdate()->first(); abort_if($id !== null && $before === null, 404); $name = trim($data['supplier_name']); $duplicate = $this->query()->where('supplier_name', $name)->when($id !== null, fn ($q) => $q->where('id', '<>', $id))->exists(); if ($duplicate) { throw ValidationException::withMessages(['supplier_name' => '该供应商已有配置(含已删除),请编辑或恢复原配置。']); } $time = $this->time(); $values = ['supplier_name' => $name, 'updated_at' => $time] + $this->values($data); if ($id === null) { $id = $this->query()->insertGetId($values + ['creator' => $actor, 'created_at' => $time]); } else { $this->query()->where('id', $id)->update($values); } $this->event($id, $actor, $before ? 'update' : 'create', ($before ? '修改' : '新增') . '厂家记账配置:' . $name . ' → ' . Rule::describe($values), ['before' => $before, 'after' => $values]); return $id; }); } public function changeDeleted(int $id, bool $deleted, string $actor): void { DB::transaction(function () use ($id, $deleted, $actor): void { $row = $this->query()->where('id', $id)->lockForUpdate()->first(); abort_if($row === null, 404); if (($row->deleted_at !== null) === $deleted) { return; } $this->query()->where('id', $id)->update(['deleted_at' => $deleted ? $this->time() : null, 'updated_at' => $this->time()]); $this->event($id, $actor, $deleted ? 'delete' : 'restore', ($deleted ? '删除' : '恢复') . '厂家记账配置:' . $row->supplier_name, ['supplier_name' => $row->supplier_name, 'mode' => $row->mode]); }); } /** * 整批导入:新供应商新增;已有供应商记账方式或生效期不同则按文件更新(已删除的一并恢复);相同则跳过。 * * @param list $rows * @return array{added:int,updated:int,skipped:int} */ public function import(array $rows, string $actor, string $sourceHash): array { return DB::transaction(function () use ($rows, $actor, $sourceHash): array { $added = $updated = $skipped = 0; $time = $this->time(); foreach ($rows as $row) { $name = trim($row['supplier_name']); $values = $this->values($row); $existing = $this->query()->where('supplier_name', $name)->lockForUpdate()->first(); if ($existing === null) { $this->query()->insert(['supplier_name' => $name, 'creator' => $actor, 'created_at' => $time, 'updated_at' => $time] + $values); $added++; continue; } $same = $existing->deleted_at === null; foreach (['mode', 'effective_from', 'effective_to'] as $field) { $same = $same && (string) $existing->{$field} === (string) $values[$field]; } if ($same) { $skipped++; continue; } $this->query()->where('id', $existing->id)->update($values + ['deleted_at' => null, 'updated_at' => $time]); $updated++; } $this->event(null, $actor, 'import', "导入厂家记账配置:新增{$added}条,更新{$updated}条,未变化{$skipped}条", compact('added', 'updated', 'skipped') + ['source_sha256' => $sourceHash]); return compact('added', 'updated', 'skipped'); }); } public function events() { return DB::table(self::EVENTS)->orderByDesc('id'); } private function values(array $data): array { $values = []; foreach (self::FIELDS as $field) { $value = $data[$field] ?? null; $values[$field] = $field === 'source_text' ? mb_substr(trim((string) $value), 0, 255) : (($value === '' || $value === null) ? null : (string) $value); } abort_unless(isset(Rule::MODES[$values['mode']]), 422, '记账方式无效。'); if ($values['effective_from'] !== null && $values['effective_to'] !== null && $values['effective_from'] > $values['effective_to']) { throw ValidationException::withMessages(['effective_to' => '生效结束日期不能早于开始日期。']); } return $values; } private function event(?int $id, string $actor, string $action, string $description, array $details): void { DB::table(self::EVENTS)->insert(['config_id' => $id, 'actor' => $actor, 'action' => $action, 'description' => mb_substr($description, 0, 1000), 'details' => json_encode($details, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), 'created_at' => $this->time()]); } private function time(): string { return Carbon::now(config('app.platform_timezone', 'Asia/Shanghai'))->format('Y-m-d H:i:s'); } }