220 lines
8.5 KiB
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(),
|
|
]);
|
|
}
|
|
}
|