fix(ui): block illegal VAT rates, honest opening-balance state, surface account-save errors (#902)
* fix(ui): block illegal VAT rates, honest opening-balance state, surface account-save errors - Supplier invoice form (#863 item 1): onSubmit now blocks any non-reverse-charge line whose VAT rate is outside the legal Swedish set {25, 12, 6, 0} with a destructive toast naming the line and the legal rates. Server-side schema tightening stays out of scope. - Opening balance import (#837): the summary derives isBalanced from the running totals (0.01 epsilon) and renders the AlertCircle destructive pattern with the differens amount when unbalanced; canExecute includes isBalanced so the commit button is disabled instead of funneling users into a server-side rejection. - EditAccountDialog (#838): a failed account save now shows a destructive toast with the server-provided message instead of being silently swallowed; the dialog stays open for retry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): compare opening-balance totals in whole ore, map account-save errors to Swedish Review findings on the first pass: the < 0.01 epsilon misclassified exact 1-ore imbalances as balanced (0.03 - 0.02 evaluates just under 0.01), and the save-failure toast surfaced raw English server text instead of routing through getErrorMessage like the sibling handlers in the same file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8e7e7201d3
commit
ac1529e413
@@ -44,6 +44,11 @@ export default function OpeningBalancePeriodStep({
|
||||
totalDebit = Math.round((totalDebit + row.debit_amount) * 100) / 100
|
||||
totalCredit = Math.round((totalCredit + row.credit_amount) * 100) / 100
|
||||
}
|
||||
// Compare in whole öre, mirroring the engine's validateBalance: a float
|
||||
// epsilon like (< 0.01) would misclassify exact 1-öre imbalances as
|
||||
// balanced, since e.g. 0.03 - 0.02 evaluates to just under 0.01.
|
||||
const balanceDiff = Math.round((totalDebit - totalCredit) * 100) / 100
|
||||
const isBalanced = Math.round((totalDebit - totalCredit) * 100) === 0
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPeriods() {
|
||||
@@ -83,6 +88,7 @@ export default function OpeningBalancePeriodStep({
|
||||
!!selectedPeriodId &&
|
||||
!periodIsClosed &&
|
||||
!periodIsLocked &&
|
||||
isBalanced &&
|
||||
!isLoading
|
||||
|
||||
const handleExecute = useCallback(() => {
|
||||
@@ -160,10 +166,23 @@ export default function OpeningBalancePeriodStep({
|
||||
{totalCredit.toLocaleString('sv-SE', { minimumFractionDigits: 2 })} SEK
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-1 border-t text-sm">
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
<span className="text-success font-medium">Balanserar</span>
|
||||
</div>
|
||||
{isBalanced ? (
|
||||
<div className="flex items-center gap-2 pt-1 border-t text-sm">
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
<span className="text-success font-medium">Balanserar</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 pt-1 border-t text-sm">
|
||||
<AlertCircle className="h-4 w-4 text-destructive shrink-0" />
|
||||
<span className="text-destructive font-medium">
|
||||
Balanserar inte (differens{' '}
|
||||
<span className="tabular-nums">
|
||||
{balanceDiff.toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
|
||||
</span>{' '}
|
||||
SEK)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
|
||||
Reference in New Issue
Block a user