connection = $databases->connection( (string) config('upstream_mirrors.connection', 'business_mirror'), ); } /** * @param list $fieldNames * @return list> */ public function createColumns( array $fieldNames, ?int $anchorFieldId = null, string $position = 'end', ): array { $position = in_array($position, ['left', 'right'], true) ? $position : 'end'; $fieldNames = array_values(array_map(function (mixed $value): string { $text = trim((string) $value); return preg_replace('/\s+/u', ' ', $text) ?? $text; }, $fieldNames)); if ($fieldNames === [] || count($fieldNames) > 20) { throw ValidationException::withMessages([ 'field_names' => '一次可新增 1 至 20 个自定义列。', ]); } foreach ($fieldNames as $index => $fieldName) { if ($fieldName === '') { throw ValidationException::withMessages([ 'field_names.' . $index => '请填写列名。', ]); } if (mb_strlen($fieldName) > 50) { throw ValidationException::withMessages([ 'field_names.' . $index => '列名不能超过 50 个字符。', ]); } } $normalizedNames = array_map(static fn (string $name): string => mb_strtolower($name), $fieldNames); if (count(array_unique($normalizedNames)) !== count($normalizedNames)) { throw ValidationException::withMessages([ 'field_names' => '一次新增的列名不能重复。', ]); } return $this->connection->transaction(function () use ( $fieldNames, $normalizedNames, $anchorFieldId, $position, ): array { $existingFields = $this->connection->table('fin_flow_summary_custom_fields') ->orderBy('sort_order') ->orderBy('id') ->lockForUpdate() ->get(); if ($existingFields->count() + count($fieldNames) > self::MAX_COLUMNS) { throw ValidationException::withMessages([ 'field_names' => '账户资金汇总最多可设置 ' . self::MAX_COLUMNS . ' 个自定义列。', ]); } $existingNames = $existingFields ->pluck('field_name') ->map(static fn (mixed $name): string => mb_strtolower(trim((string) $name))) ->all(); if (array_intersect($normalizedNames, $existingNames) !== []) { throw ValidationException::withMessages([ 'field_names' => '账户资金汇总已存在同名列。', ]); } $anchor = null; if ($anchorFieldId !== null) { $anchor = $existingFields->firstWhere('id', $anchorFieldId); if ($anchor === null) { throw ValidationException::withMessages([ 'anchor_field_id' => '插入位置已变更,请刷新页面后重试。', ]); } } $insertIndex = $existingFields->count(); if ($anchor !== null) { $anchorIndex = $existingFields->search( static fn (object $field): bool => (int) $field->id === $anchorFieldId, ); $insertIndex = $position === 'left' ? (int) $anchorIndex : (int) $anchorIndex + 1; } $now = now(); $ordered = $existingFields->values()->all(); $placeholders = array_map( static fn (string $name): array => ['new_name' => $name], $fieldNames, ); array_splice($ordered, $insertIndex, 0, $placeholders); $created = []; foreach ($ordered as $index => $field) { $sortOrder = ($index + 1) * 10; if (is_array($field) && isset($field['new_name'])) { $values = [ 'field_key' => (string) Str::uuid(), 'field_name' => (string) $field['new_name'], 'sort_order' => $sortOrder, 'created_by' => (string) (user()?->name ?? ''), 'updated_by' => (string) (user()?->name ?? ''), 'created_at' => $now, 'updated_at' => $now, ]; $id = (int) $this->connection->table('fin_flow_summary_custom_fields') ->insertGetId($values); $created[] = ['id' => $id] + $values; continue; } if ((int) $field->sort_order !== $sortOrder) { $this->connection->table('fin_flow_summary_custom_fields') ->where('id', (int) $field->id) ->update([ 'sort_order' => $sortOrder, 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now, ]); } } $this->audit( 'flow_summary_col_add', (string) Str::uuid(), [ 'position' => $position, 'anchor_field_id' => $anchorFieldId, 'after' => $created, ], ); return $created; }); } /** * @return array */ public function deleteColumn(int $id): array { return $this->connection->transaction(function () use ($id): array { $field = $this->connection->table('fin_flow_summary_custom_fields') ->where('id', $id) ->lockForUpdate() ->first(); if ($field === null) { throw new NotFoundHttpException('未找到可删除的账户资金汇总自定义列。'); } $this->connection->table('fin_flow_summary_custom_values') ->where('custom_field_id', $id) ->delete(); $this->connection->table('fin_flow_summary_custom_fields')->where('id', $id)->delete(); $this->audit( 'flow_summary_col_del', (string) $field->field_key, ['before' => (array) $field, 'after' => null], ); return (array) $field; }); } /** * @return array */ public function updateValue(int $fieldId, string $accountShort, mixed $value): array { $accountShort = preg_replace('/\s+/u', ' ', trim($accountShort)) ?? trim($accountShort); $text = trim((string) ($value ?? '')); $text = preg_replace('/\s+/u', ' ', $text) ?? $text; return $this->connection->transaction(function () use ($fieldId, $accountShort, $text): array { $field = $this->connection->table('fin_flow_summary_custom_fields') ->where('id', $fieldId) ->lockForUpdate() ->first(); if ($field === null) { throw new NotFoundHttpException('自定义列已删除,请刷新页面后重试。'); } if ($accountShort === '' || FinanceLedgerIndependentAccounts::contains($accountShort)) { throw ValidationException::withMessages([ 'account_short' => '请选择账户资金汇总中的账户。', ]); } if (! $this->connection->table('fin_accounts')->where('account_short', $accountShort)->exists()) { throw ValidationException::withMessages([ 'account_short' => '账户简称不存在,请刷新页面后重试。', ]); } $query = $this->connection->table('fin_flow_summary_custom_values') ->where('custom_field_id', $fieldId) ->where('account_short', $accountShort); $existing = $query->first(); $before = $existing?->value; $nextValue = $text !== '' ? mb_substr($text, 0, 4000) : null; if ($nextValue === null) { if ($existing !== null) { $query->delete(); } } elseif ($existing === null) { $this->connection->table('fin_flow_summary_custom_values')->insert([ 'custom_field_id' => $fieldId, 'account_short' => $accountShort, 'value' => $nextValue, 'created_at' => now(), 'updated_at' => now(), ]); } else { $this->connection->table('fin_flow_summary_custom_values') ->where('id', $existing->id) ->update([ 'value' => $nextValue, 'updated_at' => now(), ]); } $this->audit( 'flow_summary_val_set', (string) $field->field_key . '|' . $accountShort, [ 'before' => $before, 'after' => $nextValue, ], ); return [ 'field_id' => $fieldId, 'field_key' => (string) $field->field_key, 'account_short' => $accountShort, 'value' => $nextValue, ]; }); } /** * @param array $changes */ private function audit(string $entityType, string $entityKey, array $changes): void { $this->connection->table('fin_ledger_edit_events')->insert([ 'entity_type' => $entityType, 'entity_key' => mb_substr('flow_summary|' . $entityKey, 0, 200), 'changes' => json_encode($changes, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), 'user_id' => user()?->id, 'user_name' => (string) (user()?->name ?? ''), 'created_at' => now(), ]); } }