0, 422, '缺少当前公司。'); return DB::table(self::TABLE)->where('company_id', $companyId); } public function activeRates(int $companyId): array { $result = []; foreach ($this->query($companyId)->whereNull('deleted_at')->get() as $row) { $result[trim($row->courier_name)] = (array) $row; } return $result; } public function filtered(int $companyId, array $filters) { $query = $this->query($companyId); $status = $filters['deleted_status'] ?? 'active'; if ($status === 'deleted') { $query->whereNotNull('deleted_at'); } elseif ($status !== 'all') { $query->whereNull('deleted_at'); } foreach (['courier_name', 'creator'] as $field) { if (($filters[$field] ?? '') !== '') { $query->where($field, 'like', '%' . addcslashes($filters[$field], '\\%_') . '%'); } } if (($filters['created_from'] ?? '') !== '') { $query->whereRaw('COALESCE(source_created_at, created_at) >= ?', [$filters['created_from'] . ' 00:00:00']); } if (($filters['created_to'] ?? '') !== '') { $query->whereRaw('COALESCE(source_created_at, created_at) < ?', [Carbon::parse($filters['created_to'])->addDay()->format('Y-m-d') . ' 00:00:00']); } return $query->orderByRaw('COALESCE(source_created_at, created_at) DESC')->orderByDesc('id'); } public function save(int $companyId, array $data, string $actor, ?int $id = null): int { return DB::transaction(function () use ($companyId, $data, $actor, $id): int { $before = $id === null ? null : $this->query($companyId)->where('id', $id)->whereNull('deleted_at')->lockForUpdate()->first(); abort_if($id !== null && $before === null, 404); $this->assertIdentity($companyId, $data, $id); $time = $this->time(); $values = ['courier_code' => trim($data['courier_code']), 'courier_name' => trim($data['courier_name']), 'rate_basis_points' => (int) $data['rate_basis_points'], 'updated_at' => $time]; if ($id === null) { $id = DB::table(self::TABLE)->insertGetId($values + ['company_id' => $companyId, 'creator' => $actor, 'source_created_at' => null, 'created_at' => $time]); } else { $this->query($companyId)->where('id', $id)->update($values); } $this->event($companyId, $id, $actor, $before ? 'update' : 'create', $before ? '修改快递扣款配置' : '新增快递扣款配置', ['before' => $before, 'after' => $values]); return $id; }); } public function changeDeleted(int $companyId, int $id, bool $deleted, string $actor): void { DB::transaction(function () use ($companyId, $id, $deleted, $actor): void { $row = $this->query($companyId)->where('id', $id)->lockForUpdate()->first(); abort_if($row === null, 404); if (($row->deleted_at !== null) === $deleted) { return; } $this->query($companyId)->where('id', $id)->update(['deleted_at' => $deleted ? $this->time() : null, 'updated_at' => $this->time()]); $this->event($companyId, $id, $actor, $deleted ? 'delete' : 'restore', ($deleted ? '删除' : '恢复') . '快递扣款配置', ['courier_code' => $row->courier_code, 'courier_name' => $row->courier_name]); }); } /** 整批事务:重复导入相同配置跳过,冲突不覆盖、删除状态不自动恢复。 */ public function import(int $companyId, array $rows, string $actor, string $sourceHash): array { return DB::transaction(function () use ($companyId, $rows, $actor, $sourceHash): array { $added = 0; $skipped = 0; foreach ($rows as $row) { $existing = $this->query($companyId)->where('courier_code', $row['courier_code'])->lockForUpdate()->first(); if ($existing !== null) { if ($existing->courier_name !== $row['courier_name'] || (int) $existing->rate_basis_points !== $row['rate_basis_points'] || $existing->deleted_at !== null) { throw ValidationException::withMessages(['file' => '快递代码 ' . $row['courier_code'] . ' 已存在不同配置或已删除,整批未导入,请核对后重试。']); } $skipped++; continue; } $this->assertIdentity($companyId, $row); DB::table(self::TABLE)->insert($row + ['company_id' => $companyId, 'created_at' => $this->time(), 'updated_at' => $this->time()]); $added++; } $this->event($companyId, null, $actor, 'import', "导入快递配置:新增{$added}条,重复跳过{$skipped}条", ['added' => $added, 'skipped' => $skipped, 'source_sha256' => $sourceHash]); return compact('added', 'skipped'); }); } private function assertIdentity(int $companyId, array $data, ?int $exceptId = null): void { $query = $this->query($companyId)->where(function ($query) use ($data): void { $query->where('courier_code', trim($data['courier_code']))->orWhere('courier_name', trim($data['courier_name'])); }); if ($exceptId !== null) { $query->where('id', '<>', $exceptId); } if ($query->exists()) { throw ValidationException::withMessages(['courier_name' => '快递代码或名称已存在(含已删除配置),请编辑或恢复原配置。']); } } private function event(int $companyId, ?int $id, string $actor, string $action, string $description, array $details): void { DB::table(self::EVENTS)->insert(['company_id' => $companyId, 'config_id' => $id, 'actor' => $actor, 'action' => $action, 'description' => $description, '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'); } }