class TransaksiTableInputService:
    def collect_ringkasan_items(self, table_barang, data_barang_cache, parse_rupiah_callback, parse_int_callback):
        total_harga = 0.0
        items = []
        row_count = table_barang.rowCount() if table_barang is not None else 0
        for row in range(row_count):
            subtotal_item = table_barang.item(row, 6)
            harga_item = table_barang.item(row, 3)
            qty_item = table_barang.item(row, 4)
            id_item = table_barang.item(row, 0)
            jenis_item = table_barang.item(row, 9)
            if not subtotal_item or not harga_item or not qty_item:
                continue
            subtotal = parse_rupiah_callback(subtotal_item.text())
            harga = parse_rupiah_callback(harga_item.text())
            qty = parse_int_callback(qty_item.text())
            total_harga += subtotal
            barang_detail = None
            if id_item:
                barang_detail = (data_barang_cache or {}).get(str(id_item.text()))
            produk_jenis = str(jenis_item.text() if jenis_item else "invoice").strip().lower()
            free_qty = 0
            free_nama = ""
            if produk_jenis == "free_produk":
                free_qty = (barang_detail or {}).get("jumlah_free", 0)
                free_nama = (barang_detail or {}).get("free_produk_nama")
            items.append(
                {
                    "harga": harga,
                    "qty": qty,
                    "subtotal": subtotal,
                    "free_produk_nama": free_nama,
                    "free_qty": free_qty,
                }
            )
        return total_harga, items

    # edited by glg
    def collect_payload_rows(
        self,
        table_barang,
        data_barang_cache=None,
        on_row_error=None,
        strict_mode=False,
        error_log_limit=20,
    ):
        rows = []
        errors_logged = 0
        max_error_logs = max(1, int(error_log_limit or 1))
        row_count = table_barang.rowCount() if table_barang is not None else 0
        for row in range(row_count):
            try:
                produk_id_item = table_barang.item(row, 0)
                produk_nama_item = table_barang.item(row, 2)
                harga_item = table_barang.item(row, 3)
                subtotal_item = table_barang.item(row, 6)
                jumlah_item = table_barang.item(row, 4)
                satuan_item = table_barang.item(row, 5)
                diskon_item = table_barang.item(row, 7)
                diskon_rp_item = table_barang.item(row, 10)
                jenis_item = table_barang.item(row, 9)
                if not produk_id_item or not produk_nama_item or not harga_item or not jumlah_item:
                    continue
                produk_id_text = str(produk_id_item.text() or "").strip()
                barang_detail = {}
                if isinstance(data_barang_cache, dict) and produk_id_text:
                    barang_detail = (
                        data_barang_cache.get(produk_id_text)
                        or data_barang_cache.get(produk_id_item.text())
                        or {}
                    )
                rows.append(
                    {
                        "produk_id": produk_id_text,
                        "produk_nama": produk_nama_item.text(),
                        "harga_text": harga_item.text(),
                        # edited by glg
                        # Simpan subtotal baris dari UI sebagai source-of-truth
                        # agar nilai simpan transaksi konsisten dengan dialog pembayaran.
                        "subtotal_text": subtotal_item.text() if subtotal_item else "",
                        "jumlah_text": jumlah_item.text(),
                        "satuan": satuan_item.text() if satuan_item else "",
                        "diskon_text": diskon_item.text() if diskon_item else "0",
                        # edited by glg
                        # Nominal diskon per-unit dari UI (kolom diskon rupiah) untuk menjaga akurasi print.
                        "diskon_rp_text": diskon_rp_item.text() if diskon_rp_item else "0",
                        "produk_jenis": jenis_item.text() if jenis_item else "invoice",
                        # edited by glg
                        # Payload relasi promo free (source -> free) untuk split detail saat simpan.
                        "free_qty": (barang_detail or {}).get("jumlah_free", 0),
                        "free_produk_id": (barang_detail or {}).get("free_produk_id"),
                        "free_produk_nama": (barang_detail or {}).get("free_produk_nama"),
                        "source_produk_id": produk_id_text,
                        "source_produk_nama": produk_nama_item.text(),
                    }
                )
            except (TypeError, ValueError, KeyError, AttributeError, IndexError) as exc:
                errors_logged += 1
                if callable(on_row_error) and errors_logged <= max_error_logs:
                    try:
                        on_row_error(
                            {
                                "stage": "collect_payload_rows",
                                "row_index": int(row),
                                "error": str(exc),
                            }
                        )
                    except (TypeError, ValueError, KeyError, AttributeError, RuntimeError):
                        pass
                if bool(strict_mode):
                    raise ValueError(
                        f"transaksi_payload_row_invalid stage=collect_payload_rows row={row}: {exc}"
                    ) from exc
                continue
        return rows
