fiskaltrust / fiskaltrust/middleware

QueueAT: a rejected or unsigned monthly/yearly closing is still recorded as a successful receipt

Open
#767 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
20
Forks
11
Avg merge
1d 18h
Merged PRs (30d)
13

Description

## Summary

An AT monthly (0x05) or yearly (0x06) closing request that the queue **does not** process as a closing is still recorded as a successful receipt: it gets a receipt number and a receipt-journal row, and downstream systems treat it as a closing. The legacy queue did not do this. A closing that fails must not be considered succeeded.

## Where

`queue/src/fiskaltrust.Middleware.Localization.QueueAT/RequestCommands/MonthlyClosingReceiptCommand.cs` (the yearly command is identical):

- **Rejected closing** (line 33): when the request carries charge or pay items, the command writes the "Tried to create a monthly receipt … but the incoming receipt is not a zero receipt" action journal and returns the *unchanged* response (line 38). No error state, no flag.
- **Unsigned closing** (line 104): when `SignReceiptAsync` yields no `journalAT`, the command writes "Monthly receipt failed: Could not sign receipt", does not advance `LastSettlementMonth` / `LastSettlementMoment`, and returns the response as a normal receipt.

`queue/src/fiskaltrust.Middleware.Queue/SignProcessor.cs`: the only thing that suppresses the receipt journal is `receiptResponse.IsError()` at line 226, which is `(ftState & 0xFFFF_FFFF) == 0xEEEE_EEEE`. Neither closing branch sets that state, so both fall into the `else` at line 247: `CreateReceiptJournalAsync` increments `ftReceiptNumerator` (line 344) and inserts the row, with `ftReceiptTotal` = `cbReceiptAmount` or the sum of the charge items.

## What legacy did

`middleware-legacy/src/fiskaltrust.service.shared/worker.cs:843` gates the numerator and the receipt-journal insert on `success`, and the rejected monthly branch in `workerAT.cs:1491` returns `false` before `MonthReceiptAT`. A rejected closing left only the action journal: no receipt number, no receipt-journal row. That has been the behaviour since the closing was introduced in 2016.

## Effect downstream

- The rejected attempt consumes a receipt number and appears in the receipt journal with a non-zero total while its request says 0x05/0x06.
- helipad triggers the AT DEP export on the uploaded receipt case alone, so the rejected attempt fires an export and, until fiskaltrust/service-helipad#31, was also used as the lower bound of the *next* export, i.e. exports were anchored on a request that never closed anything. helipad#31 now skips non-zero rows for that reason, but the queue should not produce them in the first place.
- The receipt hash chain includes a receipt that the queue itself says did not happen.

## Expected

**Definition of a zero receipt.** Today only the item lists are checked (`HasChargeAndPayItems`). A closing must also require `cbReceiptAmount` to be absent or 0: a POS that sends an amount on an otherwise empty closing currently gets a signed closing whose receipt-journal total is that amount (`CreateReceiptJournalAsync` takes `cbReceiptAmount` when present, otherwise the item sum). Every consumer that identifies closings by "zero total" then has to reason about that case; refusing it at signing time removes it for good. Legacy had the same gap.

A rejected or unsigned closing is not a successful receipt: no receipt number, no receipt-journal row, the response carries an error state (or a dedicated state flag) that the POS can act on, and the action journal stays as the record of the attempt. In terms of the current code: the two branches above should return a response `SignProcessor.IsError()` recognises, or `SignProcessor` needs a second condition besides `0xEEEE_EEEE` for "processed but not a receipt".

Whether the unsigned case should instead be treated like any other SSCD-failed-mode receipt (journaled, but with the failed-mode state and without settling the month) is a product decision; the rejected case is not.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

Start with QueueAT RequestCommands/MonthlyClosingReceiptCommand.cs and the identical yearly command, then trace receipt handling in Queue/SignProcessor.cs, especially IsError() and CreateReceiptJournalAsync. Compare the relevant behavior in middleware-legacy/service.shared/worker.cs and workerAT.cs. Done means rejected or unsigned closings do not create receipt numbers or receipt-journal rows, while preserving the action journal and an actionable error or agreed failed-mode state; the unsigned behavior remains a product decision.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.