Refactor inventory health calculations and enhance account mismatch detection
- Renamed and refactored `ledgerBalanceInCurrency` to `ledgerRowsInAccountCurrency` for clarity. - Introduced `accountHasApiLedgerMismatch` function to streamline balance mismatch checks between API and ledger. - Updated `computeInventoryHealth` and `getBalanceMismatchAccountIds` to utilize the new mismatch detection logic. - Enhanced user feedback in `AccountsPage` regarding balance discrepancies and ledger activity.
This commit is contained in:
+27
-24
@@ -2,12 +2,15 @@ import { getPaidUntilDate } from './paid-until'
|
||||
|
||||
const STALE_SYNC_HOURS = 48
|
||||
|
||||
function ledgerBalanceInCurrency(account, balanceLedger) {
|
||||
function ledgerRowsInAccountCurrency(account, balanceLedger) {
|
||||
const cur = (account.balance_currency || account.currency || '').trim()
|
||||
const rows = balanceLedger.filter((row) => row.providerAccountId === account.id)
|
||||
const filtered = cur
|
||||
? rows.filter((row) => !row.currency || row.currency === cur)
|
||||
: rows
|
||||
if (!cur) return rows
|
||||
return rows.filter((row) => !row.currency || row.currency === cur)
|
||||
}
|
||||
|
||||
function ledgerBalanceInCurrency(account, balanceLedger) {
|
||||
const filtered = ledgerRowsInAccountCurrency(account, balanceLedger)
|
||||
const credits = filtered
|
||||
.filter((row) => row.direction === 'credit')
|
||||
.reduce((acc, row) => acc + Number(row.amount || 0), 0)
|
||||
@@ -17,6 +20,23 @@ function ledgerBalanceInCurrency(account, balanceLedger) {
|
||||
return credits - debits
|
||||
}
|
||||
|
||||
/**
|
||||
* Сравниваем баланс из API с суммой по balance_ledger.
|
||||
* Если в ledger нет ни одной строки по аккаунту (в валюте баланса) — не считаем расхождением:
|
||||
* пользователь видит только API, а «0 из ledger» — не противоречие, а отсутствие учёта.
|
||||
*/
|
||||
export function accountHasApiLedgerMismatch(account, balanceLedger) {
|
||||
if (account.balance_api == null || !Number.isFinite(Number(account.balance_api))) return false
|
||||
const rows = ledgerRowsInAccountCurrency(account, balanceLedger)
|
||||
if (rows.length === 0) return false
|
||||
const ledger = ledgerBalanceInCurrency(account, balanceLedger)
|
||||
if (!Number.isFinite(ledger)) return false
|
||||
const api = Number(account.balance_api)
|
||||
const diff = Math.abs(api - ledger)
|
||||
const tol = Math.max(10, Math.abs(api) * 0.05)
|
||||
return diff > tol
|
||||
}
|
||||
|
||||
export function lastOkSyncFinishedAt(accountId, syncLog) {
|
||||
const rows = (syncLog || []).filter(
|
||||
(r) => r.accountId === accountId && r.status === 'ok' && r.finishedAt,
|
||||
@@ -108,21 +128,14 @@ export function computeInventoryHealth(input) {
|
||||
})
|
||||
}
|
||||
|
||||
const mismatchAccounts = providerAccounts.filter((a) => {
|
||||
if (a.balance_api == null || !Number.isFinite(Number(a.balance_api))) return false
|
||||
const ledger = ledgerBalanceInCurrency(a, balanceLedger)
|
||||
if (!Number.isFinite(ledger)) return false
|
||||
const api = Number(a.balance_api)
|
||||
const diff = Math.abs(api - ledger)
|
||||
const tol = Math.max(10, Math.abs(api) * 0.05)
|
||||
return diff > tol
|
||||
})
|
||||
const mismatchAccounts = providerAccounts.filter((a) => accountHasApiLedgerMismatch(a, balanceLedger))
|
||||
if (mismatchAccounts.length) {
|
||||
issues.push({
|
||||
key: 'balance-mismatch',
|
||||
title: 'Баланс API и ledger расходятся',
|
||||
count: mismatchAccounts.length,
|
||||
to: '/accounts?health=balance-mismatch',
|
||||
hint: 'Считается только если в журнале «Баланс и списания» есть движения по аккаунту',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,17 +165,7 @@ export function getStaleSyncAccountIds(providerAccounts, syncLog, now = new Date
|
||||
* @param {object[]} balanceLedger
|
||||
*/
|
||||
export function getBalanceMismatchAccountIds(providerAccounts, balanceLedger) {
|
||||
return providerAccounts
|
||||
.filter((a) => {
|
||||
if (a.balance_api == null || !Number.isFinite(Number(a.balance_api))) return false
|
||||
const ledger = ledgerBalanceInCurrency(a, balanceLedger)
|
||||
if (!Number.isFinite(ledger)) return false
|
||||
const api = Number(a.balance_api)
|
||||
const diff = Math.abs(api - ledger)
|
||||
const tol = Math.max(10, Math.abs(api) * 0.05)
|
||||
return diff > tol
|
||||
})
|
||||
.map((a) => a.id)
|
||||
return providerAccounts.filter((a) => accountHasApiLedgerMismatch(a, balanceLedger)).map((a) => a.id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -262,7 +262,9 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
highlightAccountIds?.size ? (
|
||||
<div className="alert alert-warning d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<span>
|
||||
Подсвечены аккаунты, где баланс API заметно расходится с суммой по ledger (та же валюта).
|
||||
В колонке «Баланс» для BILLmanager показывается сумма из API (в двух строках — с конвертацией и в валюте аккаунта).
|
||||
Здесь же сверяется с <strong>журналом на странице «Баланс и списания»</strong> (те же движения в валюте баланса).
|
||||
Подсветка только если в журнале уже есть записи по аккаунту, но итог заметно не совпадает с API.
|
||||
</span>
|
||||
<Link to="/accounts" className="btn btn-sm btn-outline-secondary">
|
||||
Сбросить фильтр
|
||||
|
||||
Reference in New Issue
Block a user