feat(财务台账): 支持逐行修改动账账户关联 #456

Merged
shanshi merged 1 commits from feature/shanshi-transaction-account-edit into main 2026-10-22 11:49:53 +08:00
5 changed files with 387 additions and 17 deletions

View File

@ -673,6 +673,145 @@ class FinanceLedger extends Controller
], $inserted ? 201 : 200, ['Cache-Control' => 'private, no-store']);
}
public function updateTransactionAccount(Request $request, int $id, FinanceLedgerCalculator $calculator): JsonResponse
{
abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以修改动账账户关联。');
$data = $request->validate([
'account_short' => ['required', 'string', 'max:100'],
]);
$accountShort = trim((string) $data['account_short']);
$connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'));
$account = $connection->table('fin_accounts')
->where('account_short', $accountShort)
->orderBy('id')
->first(['account_short', 'company_name']);
abort_if($account === null, 422, '账户简称不存在,请从账户总表中选择。');
$result = $connection->transaction(function () use (
$connection,
$calculator,
$id,
$accountShort,
): array {
$transaction = $connection->table('fin_transactions')
->where('id', $id)
->lockForUpdate()
->first();
abort_if($transaction === null, 404);
$source = trim((string) $transaction->source);
$sourceValue = trim((string) ($transaction->source_value ?? ''));
$directTransaction = $source === FinanceLedgerTransactionWriter::MANUAL_SOURCE || $sourceValue === '';
$groupKey = $directTransaction
? 'transaction:' . $id
: hash('sha256', $source . '|' . $sourceValue);
$mapping = null;
$old = trim((string) ($transaction->account_short ?? ''));
if (! $directTransaction) {
$mapping = $connection->table('fin_account_map')
->where('source', $source)
->where('source_value', $sourceValue)
->lockForUpdate()
->first();
$mappedAccount = trim((string) ($mapping->account_short ?? ''));
if ($mappedAccount !== '') {
$old = $mappedAccount;
}
}
if ($old === $accountShort) {
return [
'changed' => false,
'scope' => $directTransaction ? 'transaction' : 'mapping',
'group_key' => $groupKey,
'txn_date' => (string) $transaction->txn_date,
];
}
$now = now();
if ($directTransaction) {
$connection->table('fin_transactions')->where('id', $id)->update([
'account_short' => $accountShort,
'updated_by' => (string) user()->name,
'updated_at' => $now,
]);
} else {
if ($mapping === null) {
$stats = $connection->table('fin_transactions')
->where('source', $source)
->where('source_value', $sourceValue)
->selectRaw('COUNT(*) AS txn_count, SUM(income) AS income_sum, SUM(expense) AS expense_sum')
->first();
$connection->table('fin_account_map')->insert([
'source' => $source,
'source_value' => $sourceValue,
'account_short' => $accountShort,
'txn_count' => (int) ($stats->txn_count ?? 0),
'income_sum' => round((float) ($stats->income_sum ?? 0), 2),
'expense_sum' => round((float) ($stats->expense_sum ?? 0), 2),
'updated_by' => (string) user()->name,
'created_at' => $now,
'updated_at' => $now,
]);
} else {
$connection->table('fin_account_map')->where('id', $mapping->id)->update([
'account_short' => $accountShort,
'updated_by' => (string) user()->name,
'updated_at' => $now,
]);
}
$connection->table('fin_account_map_events')->insert([
'source' => $source,
'source_value' => $sourceValue,
'old_account_short' => $old !== '' ? $old : null,
'new_account_short' => $accountShort,
'user_id' => (int) user()->id,
'user_name' => (string) user()->name,
'created_at' => $now,
]);
}
$calculator->recalculate();
return [
'changed' => true,
'scope' => $directTransaction ? 'transaction' : 'mapping',
'group_key' => $groupKey,
'txn_date' => (string) $transaction->txn_date,
];
});
$flow = $connection->table('fin_daily_flows')
->where('account_short', $accountShort)
->whereDate('flow_date', (string) $result['txn_date'])
->first(['flow_date', 'opening_balance', 'inflow', 'outflow', 'closing_balance']);
$currentBalance = $connection->table('fin_accounts')
->where('account_short', $accountShort)
->orderBy('id')
->value('current_balance');
return response()->json([
'ok' => true,
'changed' => (bool) $result['changed'],
'scope' => (string) $result['scope'],
'group_key' => (string) $result['group_key'],
'account_short' => $accountShort,
'company_name' => (string) ($account->company_name ?? ''),
'current_balance' => round((float) ($currentBalance ?? 0), 2),
'daily_flow' => $flow === null ? null : [
'flow_date' => (string) $flow->flow_date,
'opening_balance' => round((float) $flow->opening_balance, 2),
'inflow' => round((float) $flow->inflow, 2),
'outflow' => round((float) $flow->outflow, 2),
'closing_balance' => round((float) $flow->closing_balance, 2),
],
], 200, ['Cache-Control' => 'private, no-store']);
}
public function updateAccountStatus(Request $request, FinanceLedgerCalculator $calculator): JsonResponse
{
abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改账户状态。');

View File

@ -278,6 +278,12 @@
.ledger-manual-date__button:hover { background:#efe4c8; }
.ledger-manual-date__button:focus-visible { position:relative; z-index:2; outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.18); }
.ledger-manual-date__button .material-icons-outlined { font-size:1rem; }
.ledger-transaction-account-input { width:100%; min-width:0; height:1.65rem; padding:0 .38rem; border:1px solid transparent; border-radius:.3rem; background:transparent; color:var(--vip-content-ink, #171717); font:inherit; font-size:.7rem; }
.ledger-transaction-account-input:hover { border-color:var(--vip-border, #D8D2C7); background:#fff; }
.ledger-transaction-account-input:focus { border-color:var(--vip-content-accent-dark, #856632); outline:none; background:#fff; box-shadow:0 0 0 3px rgba(133,102,50,.14); }
.ledger-transaction-account-input.is-saving { border-color:var(--vip-content-accent, #B9954F); background:#fffdf6; }
.ledger-transaction-account-input.is-error { border-color:var(--vip-danger, #B80000); background:#fff5f5; }
.ledger-transaction-account-input:disabled { cursor:wait; opacity:.65; }
.ledger-manual-actions { display:flex; align-items:center; gap:.25rem; }
.ledger-manual-action { display:inline-grid; place-items:center; width:1.75rem; height:1.75rem; padding:0; border:1px solid var(--vip-border, #D8D2C7); border-radius:.35rem; background:#fff; color:var(--vip-content-muted, #595959); cursor:pointer; }
.ledger-manual-action.is-save { border-color:var(--vip-content-accent-dark, #856632); background:var(--vip-content-accent-dark, #856632); color:#fff; }
@ -547,7 +553,7 @@
<input type="hidden" name="tab" value="flows">
<div class="ledger-field"><label for="flows-date">日期</label><input id="flows-date" type="date" name="flows_date" value="{{ request('flows_date') }}"></div>
<div class="ledger-field"><label for="flows-company">公司</label><input id="flows-company" name="flows_company" value="{{ request('flows_company') }}" placeholder="公司名称"></div>
<div class="ledger-field"><label for="flows-account">账户简称</label><input id="flows-account" name="flows_account" value="{{ request('flows_account') }}" placeholder="账户简称"></div>
<div class="ledger-field"><label for="flows-account">账户简称</label><input id="flows-account" name="flows_account" list="ledger-account-list" value="{{ request('flows_account') }}" placeholder="输入或选择"></div>
<div class="ledger-actions"><button class="ledger-btn primary" type="submit"><span class="material-icons-outlined">search</span>查询</button><a class="ledger-btn" href="{{ $tabUrl('flows') }}"><span class="material-icons-outlined">refresh</span>重置</a></div>
</form>
<div class="ledger-summary"><div class="ledger-summary__item"><span>流水条数</span><b>{{ number_format((int) ($ledgerFlowSummary['count'] ?? 0)) }}</b></div><div class="ledger-summary__item"><span>期初余额({{ $ledgerOpeningDate }})</span><b>{{ $money($meta['opening_sum'] ?? 0) }}</b></div><div class="ledger-summary__item"><span>当期入账</span><b>{{ $money($ledgerFlowSummary['inflow'] ?? 0) }}</b></div><div class="ledger-summary__item"><span>当期出账</span><b>{{ $money($ledgerFlowSummary['outflow'] ?? 0) }}</b></div><div class="ledger-summary__item"><span>期末余额合计</span><b>{{ $money($ledgerFlowSummary['closing'] ?? 0) }}</b></div></div>
@ -577,11 +583,11 @@
</section>
{{-- 动账明细 --}}
<section class="ledger-panel" id="ledger-panel-transactions" role="tabpanel" data-ledger-panel="transactions" @if ($activeTab !== 'transactions') hidden @endif>
<section class="ledger-panel" id="ledger-panel-transactions" role="tabpanel" data-ledger-panel="transactions" data-transaction-account-url-template="{{ route('business.financial-ledger.transactions.account.update', ['id' => '__TRANSACTION__']) }}" @if ($activeTab !== 'transactions') hidden @endif>
<form class="ledger-toolbar" method="GET" action="{{ $tabUrl('transactions') }}">
<input type="hidden" name="tab" value="transactions">
<div class="ledger-field" style="min-width:15rem"><label for="txns-q">关键词</label><input id="txns-q" name="txns_q" value="{{ request('txns_q') }}" placeholder="账户、明细、审批编号"></div>
<div class="ledger-field"><label for="txns-account">账户简称</label><input id="txns-account" name="txns_account" value="{{ request('txns_account') }}" placeholder="含未匹配"></div>
<div class="ledger-field"><label for="txns-account">账户简称</label><input id="txns-account" name="txns_account" list="ledger-account-list" value="{{ request('txns_account') }}" placeholder="输入或选择(含未匹配)"></div>
<div class="ledger-field"><label for="txns-source">来源</label><select id="txns-source" name="txns_source"><option value="">全部</option>@foreach ($sourceLabels as $key => $label)<option value="{{ $key }}" @selected(request('txns_source') === $key)>{{ $label }}</option>@endforeach</select></div>
<div class="ledger-field"><label for="txns-from">日期起</label><input id="txns-from" type="date" name="txns_from" value="{{ request('txns_from') }}"></div>
<div class="ledger-field"><label for="txns-to">日期止</label><input id="txns-to" type="date" name="txns_to" value="{{ request('txns_to') }}"></div>
@ -629,11 +635,6 @@
</td>
<td>
<input id="ledger-manual-account" class="ledger-manual-input" form="ledger-manual-transaction-form" type="text" name="account_short" list="ledger-manual-account-options" maxlength="100" placeholder="输入账户简称" autocomplete="off" required data-ledger-manual-account>
<datalist id="ledger-manual-account-options">
@foreach ($manualTransactionAccountOptions as $option)
<option value="{{ $option['value'] }}" label="{{ $option['label'] }}"></option>
@endforeach
</datalist>
</td>
<td><input class="ledger-manual-input" form="ledger-manual-transaction-form" type="text" name="detail" maxlength="4000" placeholder="填写明细" required></td>
<td><span class="ledger-source-tag">手工录入</span></td>
@ -651,7 +652,13 @@
@forelse ($ledgerTransactions as $row)
<tr class="ledger-transaction-row" data-ledger-transaction-row data-transaction-id="{{ $row->id }}" data-source="{{ $row->source }}">
<td>{{ $date($row->txn_date) }}</td>
<td class="{{ $row->effective_account_short === '未匹配' ? 'danger' : '' }}">{{ $row->effective_account_short }}</td>
<td>
@if ($canEdit)
<input class="ledger-transaction-account-input" type="text" list="ledger-manual-account-options" value="{{ $row->effective_account_short === '未匹配' ? '' : $row->effective_account_short }}" placeholder="选择账户简称" autocomplete="off" aria-label="修改动账明细账户简称" title="输入并选择账户总表简称" data-ledger-transaction-account data-url="{{ route('business.financial-ledger.transactions.account.update', ['id' => $row->id]) }}" data-previous-account="{{ $row->effective_account_short === '未匹配' ? '' : $row->effective_account_short }}" data-account-group="{{ $row->source === 'manual' || blank($row->source_value) ? 'transaction:' . $row->id : hash('sha256', (string) $row->source . '|' . (string) $row->source_value) }}">
@else
<span class="{{ $row->effective_account_short === '未匹配' ? 'danger' : '' }}">{{ $row->effective_account_short }}</span>
@endif
</td>
<td class="wrap">{{ $row->detail }}</td>
<td><span class="ledger-source-tag">{{ $sourceLabels[$row->source] ?? $row->source }}</span></td>
<td class="num">{{ $row->income > 0 ? $money($row->income) : '' }}</td>
@ -669,6 +676,11 @@
'paginationLabel' => '动账明细分页',
])
</div>
<datalist id="ledger-manual-account-options">
@foreach ($manualTransactionAccountOptions as $option)
<option value="{{ $option['value'] }}" label="{{ $option['label'] }}"></option>
@endforeach
</datalist>
</section>
{{-- 账户映射 --}}
@ -784,7 +796,7 @@
};
tabs.forEach((tab) => tab.addEventListener('click', () => {
const name = tab.dataset.ledgerTab;
if (ledgerDependentDataStale && ['accounts', 'shops', 'flows', 'sources'].includes(name)) {
if (ledgerDependentDataStale && ['accounts', 'shops', 'flows', 'mapping', 'sources'].includes(name)) {
const url = new URL(window.location.href);
url.searchParams.set('tab', name);
window.location.assign(url.toString());
@ -829,6 +841,119 @@
const manualTransactionTrigger = page.querySelector('[data-ledger-manual-trigger]');
const manualTransactionDraft = page.querySelector('[data-ledger-manual-draft]');
const manualTransactionForm = page.querySelector('[data-ledger-manual-form]');
const transactionAccountUrlTemplate = page.querySelector('#ledger-panel-transactions')?.dataset.transactionAccountUrlTemplate || '';
const ledgerAccountOptions = page.querySelector('#ledger-manual-account-options');
const ledgerAccountShortValues = new Set(
Array.from(ledgerAccountOptions?.options || []).map((option) => option.value)
);
const ledgerCsrfToken = manualTransactionForm?.querySelector('input[name="_token"]')?.value || '';
const createTransactionAccountInput = (transactionId, accountShort, accountGroup) => {
const input = document.createElement('input');
input.className = 'ledger-transaction-account-input';
input.type = 'text';
input.setAttribute('list', 'ledger-manual-account-options');
input.value = accountShort || '';
input.placeholder = '选择账户简称';
input.autocomplete = 'off';
input.setAttribute('aria-label', '修改动账明细账户简称');
input.title = '输入并选择账户总表简称';
input.dataset.ledgerTransactionAccount = '';
input.dataset.url = transactionAccountUrlTemplate.replace('__TRANSACTION__', encodeURIComponent(String(transactionId)));
input.dataset.previousAccount = accountShort || '';
input.dataset.accountGroup = accountGroup || '';
return input;
};
const bindTransactionAccountInput = (input) => {
if (!input || input.dataset.accountBound === '1') return;
input.dataset.accountBound = '1';
const previous = () => {
input.value = input.dataset.previousAccount || '';
input.setCustomValidity('');
input.classList.remove('is-error');
};
input.addEventListener('input', () => {
input.setCustomValidity('');
input.classList.remove('is-error');
});
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
input.blur();
} else if (event.key === 'Escape') {
event.preventDefault();
previous();
input.blur();
}
});
input.addEventListener('change', async () => {
const accountShort = input.value.trim();
if (!ledgerAccountShortValues.has(accountShort)) {
input.setCustomValidity('请选择账户总表中的账户简称');
input.classList.add('is-error');
input.reportValidity();
notify('请选择账户总表中的账户简称', true);
return;
}
if (accountShort === (input.dataset.previousAccount || '')) {
input.value = accountShort;
input.setCustomValidity('');
return;
}
input.disabled = true;
input.classList.remove('is-error');
input.classList.add('is-saving');
try {
const response = await fetch(input.dataset.url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-CSRF-TOKEN': ledgerCsrfToken,
'X-Requested-With': 'XMLHttpRequest',
},
body: new URLSearchParams({ account_short: accountShort }),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const firstError = Object.values(payload.errors || {})[0]?.[0];
throw new Error(firstError || payload.message || '账户简称保存失败');
}
const targets = payload.scope === 'mapping'
? Array.from(page.querySelectorAll('[data-ledger-transaction-account]')).filter((item) => (
(item.dataset.accountGroup || '') === payload.group_key
))
: [input];
targets.forEach((item) => {
item.value = payload.account_short;
item.dataset.previousAccount = payload.account_short;
item.setCustomValidity('');
item.classList.remove('is-error');
});
ledgerDependentDataStale = true;
const balance = payload.daily_flow
? ',当日期末余额 ' + new Intl.NumberFormat('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(payload.daily_flow.closing_balance)
: '';
notify('账户简称已更新为 ' + payload.account_short + balance);
} catch (error) {
previous();
notify(error.message || '账户简称保存失败', true);
} finally {
input.disabled = false;
input.classList.remove('is-saving');
}
});
};
if (manualTransactionTrigger && manualTransactionDraft && manualTransactionForm) {
const triggerRow = page.querySelector('[data-ledger-manual-trigger-row]');
const tokenInput = manualTransactionForm.querySelector('[data-ledger-manual-token]');
@ -837,14 +962,10 @@
const dateInput = page.querySelector('[data-ledger-manual-date]');
const datePickerButton = page.querySelector('[data-ledger-manual-date-picker]');
const accountInput = page.querySelector('[data-ledger-manual-account]');
const accountOptions = page.querySelector('#ledger-manual-account-options');
const accountShortValues = new Set(
Array.from(accountOptions?.options || []).map((option) => option.value)
);
const validateManualAccount = () => {
if (!accountInput) return false;
const accountShort = accountInput.value.trim();
const invalid = accountShort !== '' && !accountShortValues.has(accountShort);
const invalid = accountShort !== '' && !ledgerAccountShortValues.has(accountShort);
accountInput.setCustomValidity(invalid ? '请选择账户总表中的账户简称' : '');
return !invalid;
};
@ -906,7 +1027,10 @@
};
addCell(row.txn_date);
addCell(row.account_short);
const accountCell = addCell();
const accountEditor = createTransactionAccountInput(row.id, row.account_short, 'transaction:' + row.id);
accountCell.appendChild(accountEditor);
bindTransactionAccountInput(accountEditor);
addCell(row.detail).classList.add('wrap');
const sourceCell = addCell();
const sourceTag = document.createElement('span');
@ -946,7 +1070,7 @@
manualTransactionForm.addEventListener('submit', async (event) => {
event.preventDefault();
const accountShort = accountInput?.value.trim() || '';
if (!accountShortValues.has(accountShort)) {
if (!ledgerAccountShortValues.has(accountShort)) {
accountInput?.setCustomValidity('请选择账户总表中的账户简称');
accountInput?.reportValidity();
return;
@ -1005,6 +1129,8 @@
});
}
page.querySelectorAll('[data-ledger-transaction-account]').forEach(bindTransactionAccountInput);
const accountTemplateDownload = page.querySelector('[data-account-template-download]');
accountTemplateDownload?.addEventListener('click', () => {
const status = page.querySelector('[data-account-template-download-status]');

View File

@ -669,6 +669,11 @@ Route::post('workspace/financial-ledger/transactions/manual', [\App\Http\Control
->middleware('business.page:financial-operations,ledger')
->name('business.financial-ledger.transactions.manual');
Route::post('workspace/financial-ledger/transactions/{id}/account', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateTransactionAccount'])
->whereNumber('id')
->middleware('business.page:financial-operations,ledger')
->name('business.financial-ledger.transactions.account.update');
Route::post('workspace/financial-ledger/accounts/status', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateAccountStatus'])
->middleware('business.page:financial-operations,ledger')
->name('business.financial-ledger.accounts.status');

View File

@ -55,6 +55,14 @@ class FinanceLedgerManualTransactionTest extends FeatureTestCase
]);
}
private function transactionAccountUrl(int $transactionId): string
{
return route('business.financial-ledger.transactions.account.update', [
'company_id' => $this->company->id,
'id' => $transactionId,
]);
}
private function payload(array $overrides = []): array
{
return array_merge([
@ -204,6 +212,97 @@ class FinanceLedgerManualTransactionTest extends FeatureTestCase
->assertJsonPath('total_transactions', 1);
}
public function test_transaction_row_can_reassign_the_source_account_mapping(): void
{
$connection = DB::connection('business_mirror');
$transactionId = $connection->table('fin_transactions')->insertGetId([
'txn_date' => '2026-10-18',
'account_short' => null,
'detail' => '待修正来源账户',
'income' => 12,
'expense' => 0,
'approval_no' => 'MAP-ROW-1',
'source' => 'dingtalk',
'source_value' => 'SOURCE-MAP-ROW-1',
'process_type' => '报销',
'row_key' => 'manual-account-map-row-1',
]);
$connection->table('fin_account_map')->insert([
'source' => 'dingtalk',
'source_value' => 'SOURCE-MAP-ROW-1',
'account_short' => null,
'txn_count' => 1,
'income_sum' => 12,
'expense_sum' => 0,
]);
$this->loginAs()->get($this->pageUrl())
->assertOk()
->assertSee('data-ledger-transaction-account', false)
->assertSee($this->transactionAccountUrl($transactionId), false);
$this->loginAs()->postJson($this->transactionAccountUrl($transactionId), [
'account_short' => '手工账户乙',
])
->assertOk()
->assertJsonPath('changed', true)
->assertJsonPath('scope', 'mapping')
->assertJsonPath('account_short', '手工账户乙')
->assertJsonPath('daily_flow.closing_balance', 12)
->assertJsonPath('current_balance', 12);
$this->assertDatabaseHas('fin_account_map', [
'source' => 'dingtalk',
'source_value' => 'SOURCE-MAP-ROW-1',
'account_short' => '手工账户乙',
'updated_by' => 'Test User',
], 'business_mirror');
$this->assertDatabaseHas('fin_account_map_events', [
'source' => 'dingtalk',
'source_value' => 'SOURCE-MAP-ROW-1',
'old_account_short' => null,
'new_account_short' => '手工账户乙',
'user_name' => 'Test User',
], 'business_mirror');
$this->assertDatabaseHas('fin_daily_flows', [
'flow_date' => '2026-10-18',
'account_short' => '手工账户乙',
'inflow' => 12,
'closing_balance' => 12,
], 'business_mirror');
}
public function test_manual_transaction_row_can_change_its_own_account(): void
{
$this->loginAs()->postJson($this->storeUrl(), $this->payload())->assertCreated();
$transactionId = (int) DB::connection('business_mirror')->table('fin_transactions')
->where('source', FinanceLedgerTransactionWriter::MANUAL_SOURCE)
->value('id');
$this->loginAs()->postJson($this->transactionAccountUrl($transactionId), [
'account_short' => '手工账户乙',
])
->assertOk()
->assertJsonPath('changed', true)
->assertJsonPath('scope', 'transaction')
->assertJsonPath('account_short', '手工账户乙')
->assertJsonPath('daily_flow.closing_balance', 25.5)
->assertJsonPath('current_balance', 25.5);
$this->assertDatabaseHas('fin_transactions', [
'id' => $transactionId,
'source' => FinanceLedgerTransactionWriter::MANUAL_SOURCE,
'account_short' => '手工账户乙',
'updated_by' => 'Test User',
], 'business_mirror');
$this->assertDatabaseHas('fin_daily_flows', [
'flow_date' => '2026-10-18',
'account_short' => '手工账户乙',
'inflow' => 25.5,
'closing_balance' => 25.5,
], 'business_mirror');
}
public function test_manual_submit_validates_amount_account_date_and_permission(): void
{
$this->withExceptionHandling()->loginAs()->postJson($this->storeUrl(), $this->payload([

View File

@ -74,6 +74,7 @@ class FinanceLedgerNavigationTest extends FeatureTestCase
'business.financial-ledger.accounts.status',
'business.financial-ledger.accounts.type',
'business.financial-ledger.accounts.template',
'business.financial-ledger.transactions.account.update',
'business.financial-ledger.mapping.shop.preview',
'business.financial-ledger.mapping.shop.apply',
'business.financial-ledger.mapping.payable.preview',