Publish under nearle/pos, add a health heartbeat, send the GST slab split

Three changes, all driven by what the back office turned out to need.

The broker is shared with the rider fleet on nearle/riders/…, so topics
move under nearle/pos/{locationid}/{terminal}/… — one ACL rule per
system, and it is obvious from a topic which one owns it. Store ID now
carries the back office's numeric location id; the tenant is resolved
from it server-side and never taken from the wire.

A till publishes a heartbeat every 30 seconds on its own topic. The Last
Will already answers "is it dead", which is not enough to run a hundred
shops on: the failure that costs money is a terminal that is connected,
selling, and quietly holding two hundred bills it has never uploaded. So
the beat carries queue depth, the age of the oldest thing waiting,
today's trading, and printer reachability. Not retained — the back
office holds it under a TTL, and a retained beat would leave an
unplugged till looking alive until something overwrote it.

Bills now carry tax_breakdown, the GST slab split the cart already
computes. A tax return is filed per slab, and recomputing the split
server-side would mean redoing the discount apportionment and getting
exactly the same answer — or else the filed figure stops matching the
paper the shopper was handed.

Docs rewritten against the real deployment: Eclipse Mosquitto 2.1.2, no
NATS anywhere reachable, no TLS, and a broker whose queue and autosave
defaults mean it must not be treated as durable storage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-03 17:47:24 +05:30
parent 09edce5dc6
commit 33b4337933
9 changed files with 435 additions and 185 deletions

View File

@@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../app/providers.dart';
import '../../../core/constants/app_constants.dart';
import '../../../data/remote/mqtt_order_transport.dart';
import '../../../data/sync/health_reporter.dart';
import '../../../data/sync/presence_reporter.dart';
import '../../modules/providers/printer_settings.dart';
import '../../../domain/entities/shift_report.dart';
import '../../../domain/entities/sync_event.dart';
import '../../../domain/repositories/sync_repository.dart';
@@ -251,5 +253,29 @@ final syncBootstrapProvider = FutureProvider<void>((ref) async {
);
ref.onDispose(reporter.dispose);
await reporter.start();
// The 30-second heartbeat the head-office board reads. Separate from the
// retained presence record above: that one is paired with the Last Will and
// answers "is this till alive", while this carries queue depth, today's
// trading and hardware state — what tells a till that is merely quiet from
// one that has stopped uploading.
final health = HealthReporter(
transport: transport,
terminal: ref.read(terminalIdentityProvider),
config: ref.read(syncConfigProvider),
engine: engine,
repository: ref.read(syncRepositoryProvider),
appVersion: AppConstants.appVersion,
// Read on each beat, so re-pointing the printer in Settings takes effect
// without a restart.
printerEndpoint: () {
final printer = ref.read(printerSettingsProvider);
final host = printer.drawerHost;
if (host == null || host.isEmpty) return null;
return (host: host, port: printer.drawerPort);
},
);
ref.onDispose(health.dispose);
await health.start();
}
});