# Receipt No — WebSocket Integration Guide

> Format: `NNN` (3-digit padded, no prefix/year)
> Example: `001`, `066`, `099`

---

## 1. Flow Overview

```
Flutter App                         WS Server                        PHP receipt.php
    |                                    |                                  |
    |-- receipt_no:next (peek) --------->|                                  |
    |<-- ack { receipt_no: "066" } ------|                                  |
    |                                    |                                  |
    |  (display "066" to user)           |                                  |
    |                                    |                                  |
    |  (user taps Save)                  |                                  |
    |                                    |                                  |
    |------- POST /receipt.php --------->|  (PHP saves to DB)               |
    |                                    |                                  |
    |                          PHP calls POST /internal/receipt-created     |
    |                                    |<---------------------------------|
    |                                    |                                  |
    |                                    |-- receipt_no:next broadcast ---->|  (all terminals)
    |<-- receipt_no:next "067" ----------|                                  |
    |                                    |                                  |
```

**Key Rule**: Never call `receipt_no:request` before PHP save. This causes number gaps.

---

## 2. Dart WebSocket Service

```dart
import 'dart:async';
import 'dart:convert';
import 'package:socket_io_client/socket_io_client.dart' as io;

class ReceiptWebSocketService {
  late io.Socket _socket;
  final _receiptNextController = StreamController<String>.broadcast();

  Stream<String> get receiptNextStream => _receiptNextController.stream;

  void connect({required String token}) {
    _socket = io.io(
      'https://dayloan.agniplay.com/admin',
      io.OptionBuilder()
          .setTransports(['websocket'])
          .setAuth({'token': token})
          .setPath('/ws')
          .build(),
    );

    _socket.onConnect((_) => print('WS connected'));

    _socket.on('receipt_no:next', (data) {
      final receiptNo = data['data']?['receipt_no'] as String?;
      if (receiptNo != null) _receiptNextController.add(receiptNo);
    });

    _socket.onDisconnect((_) => print('WS disconnected'));
  }

  void peekReceiptNo({
    required String requestId,
    required Function(String receiptNo) onResult,
  }) {
    _socket.emitWithAck('receipt_no:next', {'requestId': requestId},
        ack: (response) {
      final receiptNo = response['data']?['receipt_no'] as String?;
      if (receiptNo != null) onResult(receiptNo);
    });
  }

  void dispose() {
    _receiptNextController.close();
    _socket.disconnect();
  }
}
```

---

## 3. Flutter Screen

```dart
import 'dart:async';
import 'package:flutter/material.dart';

class ReceiptScreen extends StatefulWidget {
  final String token;
  const ReceiptScreen({required this.token, Key? key}) : super(key: key);

  @override
  _ReceiptScreenState createState() => _ReceiptScreenState();
}

class _ReceiptScreenState extends State<ReceiptScreen> {
  final _ws = ReceiptWebSocketService();
  String? _nextReceiptNo;
  String? _assignedReceiptNo;
  StreamSubscription? _receiptSub;

  @override
  void initState() {
    super.initState();
    _ws.connect(token: widget.token);

    _receiptSub = _ws.receiptNextStream.listen((receiptNo) {
      setState(() => _nextReceiptNo = receiptNo);
    });

    _fetchNext();
  }

  void _fetchNext() {
    _ws.peekReceiptNo(
      requestId: 'receipt_${DateTime.now().millisecondsSinceEpoch}',
      onResult: (receiptNo) {
        setState(() => _nextReceiptNo = receiptNo);
      },
    );
  }

  void _onSave() {
    final receiptNo = _nextReceiptNo;
    if (receiptNo == null) return;

    setState(() => _assignedReceiptNo = receiptNo);
    _saveToApi(receiptNo);
  }

  Future<void> _saveToApi(String receiptNo) async {
    // POST to https://dayloanphp.agniplay.com/receipt.php
    // On success, PHP auto-broadcasts next receipt_no to all terminals
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Receipt')),
      body: Column(
        children: [
          if (_assignedReceiptNo != null)
            Container(
              padding: const EdgeInsets.all(16),
              color: Colors.green.shade100,
              width: double.infinity,
              child: Text(
                'Receipt #$_assignedReceiptNo',
                style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
            ),
          if (_assignedReceiptNo == null)
            Container(
              padding: const EdgeInsets.all(16),
              color: Colors.blue.shade50,
              width: double.infinity,
              child: Text(
                'Next Receipt: $_nextReceiptNo',
                style: const TextStyle(fontSize: 18),
              ),
            ),
          const Expanded(child: SizedBox()),
          ElevatedButton(
            onPressed: _assignedReceiptNo == null ? _onSave : null,
            child: const Text('Save Receipt'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _receiptSub?.cancel();
    _ws.dispose();
    super.dispose();
  }
}
```

---

## 4. Server Events

| Event | Direction | Purpose |
|-------|-----------|---------|
| `receipt_no:next` | Client → Server | Peek at next receipt number (read-only) |
| `receipt_no:next` | Server → Client | Broadcast next number after PHP insert |

---

## 5. Ack Response Format

```json
// receipt_no:next (peek)
{
  "type": "receipt_no:next",
  "requestId": "...",
  "data": { "receipt_no": "066" }
}
```

---

## 6. Broadcast Format

```json
// receipt_no:next broadcast
{
  "version": 1,
  "type": "receipt_no:next",
  "timestamp": 1234567890,
  "data": { "receipt_no": "067" }
}
```

---

## 7. PHP Internal Endpoint

After successful insert, `receipt.php` calls:

```
POST http://127.0.0.1:3000/internal/receipt-created
Header: Authorization: Bearer <internal_token>
Body: { "com_id": "119" }
```

This triggers:
1. Refreshes in-memory counter from DB
2. Broadcasts `receipt_no:next` to all admin/staff sockets in the company

---

## 8. Rules

| Rule | Detail |
|------|--------|
| **No request before save** | Never call `receipt_no:request` before PHP save — causes gaps |
| **Peek only** | Use `receipt_no:next` to display current number |
| **PHP broadcasts** | After insert, PHP auto-broadcasts next number to all terminals |
| **DB gaps OK** | Historical gaps from pre-WebSocket are handled by skip logic |
| **Singleton socket** | Create once at app start, share across screens |
| **Listen for updates** | Subscribe to `receiptNextStream` to stay in sync across devices |
