diff --git a/README.md b/README.md index b0c6d91..c240aeb 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,17 @@ async def main(): for month in usage.by_month.months: print(f"{month.month} {month.year}: {month.usage_total} kWh — RM {month.amount_total}") - # Bill history & due amount + # Bill history, payment history & due amount history = await client.get_bill_history("220123456789") + payments = await client.get_payment_history("220123456789") due = await client.get_account_due_amount("220123456789") + # Most account-scoped methods also accept a CustomerAccount directly + # (is_owner and account_type are derived automatically) + for acc in accounts: + if acc.is_smart_meter: + usage = await client.get_account_usage_smart(acc) + asyncio.run(main()) ``` @@ -81,7 +88,8 @@ mytnb usage --daily # Daily usage breakdown mytnb usage --json # Full usage data as JSON mytnb current-usage # Simplified current usage summary mytnb due-amount # Outstanding balance -mytnb bill-history # Payment history +mytnb bill-history # Bill history (bills issued) +mytnb payment-history # Bill & payment history ``` Global options: `--debug` for full tracebacks, `--version`. @@ -106,17 +114,20 @@ Request encryption for the ASMX API is automatic — just pass plaintext paramet ## Data Models -| Model | Description | -| ----------------- | ---------------------------------------------------- | -| `CustomerAccount` | Linked account: number, owner, address, SMR status | -| `AccountUsage` | Full usage response: metrics, monthly and daily data | -| `UsageMetric` | Current/average usage (kWh) | -| `CostMetric` | Current/projected cost (RM) | -| `BillingMonth` | Monthly billing record with tariff blocks | -| `DailyUsage` | Daily consumption and cost | -| `TariffBlock` | Tariff pricing block details | -| `SMRAccount` | Smart Meter Reading eligibility status | -| `BREligibility` | Bill rendering opt-in status | +| Model | Description | +| --------------------- | ---------------------------------------------------- | +| `CustomerAccount` | Linked account: number, owner, address, SMR status | +| `AccountUsage` | Full usage response: metrics, monthly and daily data | +| `UsageMetric` | Current/average usage (kWh) | +| `CostMetric` | Current/projected cost (RM) | +| `BillingMonth` | Monthly billing record with tariff blocks | +| `DailyUsage` | Daily consumption and cost | +| `TariffBlock` | Tariff pricing block details | +| `BillHistoryEntry` | Single bill issued (date, amount, billing number) | +| `PaymentHistoryEntry` | Bill or payment entry with type, method, reference | +| `AccountDueAmount` | Outstanding balance and due date | +| `SMRAccount` | Smart Meter Reading eligibility status | +| `BREligibility` | Bill rendering opt-in status | ## Geographic Restrictions diff --git a/src/mytnb/cli.py b/src/mytnb/cli.py index 1e9d93d..1dc58ed 100644 --- a/src/mytnb/cli.py +++ b/src/mytnb/cli.py @@ -313,7 +313,7 @@ async def _due(): @click.option("--json", "as_json", is_flag=True, help="Output full JSON.") @click.pass_context def bill_history(ctx, account, as_json): - """Get bill payment history.""" + """Get bill history (bills issued).""" async def _history(): client = await _get_client(ctx) @@ -340,6 +340,42 @@ async def _history(): _run_async(_history()) +@cli.command("payment-history") +@click.argument("account") +@click.option("--json", "as_json", is_flag=True, help="Output full JSON.") +@click.pass_context +def payment_history(ctx, account, as_json): + """Get bill & payment history.""" + + async def _history(): + client = await _get_client(ctx) + async with client: + with console.status("[bold green]Fetching..."): + result = await client.get_payment_history(account) + + if as_json: + _print_json(result) + return + + table = Table(title=f"Bill & Payment History — {account}") + table.add_column("Date", style="cyan") + table.add_column("Type") + table.add_column("Amount (RM)", justify="right", style="green") + table.add_column("Method / Ref") + for entry in result: + style = "green" if entry.is_payment else "red" + table.add_row( + entry.date.isoformat() if entry.date else "--", + entry.history_type_text or entry.history_type or "--", + f"[{style}]--[/{style}]" if entry.amount is None + else f"[{style}]{entry.amount:,.2f}[/{style}]", + entry.paid_via or entry.reference_number or "--", + ) + console.print(table) + + _run_async(_history()) + + @cli.command("init-config") @click.option("-o", "--output", default="mytnb.json", help="Output path.") def init_config(output):