CaiWuXiTong/modules/EcommerceFinance/Services/FinanceLedgerIndependentTransactionColumnEditor.php

220 lines
8.5 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Services;
use App\Utilities\FinanceLedgerIndependentAccounts;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class FinanceLedgerIndependentTransactionColumnEditor
{
private const MAX_COLUMNS = 30;
private readonly ConnectionInterface $connection;
public function __construct(DatabaseManager $databases)
{
$this->connection = $databases->connection(
(string) config('upstream_mirrors.connection', 'business_mirror'),
);
}
/**
* @return array<string,mixed>
*/
public function create(string $accountShort, string $fieldName): array
{
$fields = $this->createBatch($accountShort, [$fieldName], null, 'end');
return $fields[0];
}
/**
* @param list<string> $fieldNames
* @return list<array<string,mixed>>
*/
public function createBatch(
string $accountShort,
array $fieldNames,
?int $anchorFieldId = null,
string $position = 'end',
): array {
$accountShort = trim($accountShort);
if (! FinanceLedgerIndependentAccounts::contains($accountShort)) {
throw ValidationException::withMessages([
'account_short' => '只有独立账单页面可以新增自定义列。',
]);
}
$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 (
$accountShort,
$fieldNames,
$normalizedNames,
$anchorFieldId,
$position,
): array {
$existingFields = $this->connection->table('fin_transaction_custom_fields')
->where('account_short', $accountShort)
->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 = [
'account_short' => $accountShort,
'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_transaction_custom_fields')
->insertGetId($values);
$created[] = ['id' => $id] + $values;
continue;
}
if ((int) $field->sort_order !== $sortOrder) {
$this->connection->table('fin_transaction_custom_fields')
->where('id', (int) $field->id)
->update([
'sort_order' => $sortOrder,
'updated_by' => (string) (user()?->name ?? ''),
'updated_at' => $now,
]);
}
}
$this->audit(
'statement_col_add',
$accountShort . '|' . (string) Str::uuid(),
[
'position' => $position,
'anchor_field_id' => $anchorFieldId,
'after' => $created,
],
);
return $created;
});
}
/**
* @return array<string,mixed>
*/
public function delete(int $id): array
{
return $this->connection->transaction(function () use ($id): array {
$field = $this->connection->table('fin_transaction_custom_fields')
->where('id', $id)
->lockForUpdate()
->first();
if ($field === null || ! FinanceLedgerIndependentAccounts::contains($field->account_short)) {
throw new NotFoundHttpException('未找到可删除的独立账单自定义列。');
}
$this->connection->table('fin_transaction_custom_values')
->where('custom_field_id', $id)
->delete();
$this->connection->table('fin_transaction_custom_fields')->where('id', $id)->delete();
$this->audit(
'statement_col_delete',
(string) $field->account_short . '|' . (string) $field->field_key,
['before' => (array) $field, 'after' => null],
);
return (array) $field;
});
}
/**
* @param array<string,mixed> $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('statement_column|' . $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(),
]);
}
}