275 lines
11 KiB
PHP
275 lines
11 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 FinanceLedgerFlowSummaryEditor
|
|
{
|
|
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'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $fieldNames
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
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<string,mixed>
|
|
*/
|
|
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<string,mixed>
|
|
*/
|
|
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<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('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(),
|
|
]);
|
|
}
|
|
}
|