Skip to main content

Overview

AndanDo provides automated invoice generation for all tour reservations. Invoices can be viewed, printed, and shared with customers directly from the dashboard.
Invoices are accessible at /invoice and stored temporarily in browser local storage.

Invoice Page Component

The invoice viewer is a simple component that displays pre-generated HTML:
Components/Pages/Invoice.razor:1
@page "/invoice"
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage Storage
@inject IJSRuntime JS
@layout EmptyLayout

<div>
    @if (string.IsNullOrWhiteSpace(_html))
    {
        <div class="alert alert-warning">
            No hay factura para mostrar. Completa una reserva e intenta nuevamente.
        </div>
    }
    else
    {
        <div @onclick:stopPropagation="true">
            @((MarkupString)_html)
        </div>
    }
</div>

@code {
    private string? _html;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;

        var stored = await Storage.GetAsync<string>("last_invoice_html");
        if (stored.Success && !string.IsNullOrWhiteSpace(stored.Value))
        {
            _html = stored.Value;
            await InvokeAsync(StateHasChanged);
        }
    }
}

Accessing Invoices

Invoices can be accessed from the dashboard reservation table:
1

Navigate to Dashboard

Go to /dashboard/main and select a tour from the dropdown
2

Locate Reservation

Find the reservation in the table
3

Open Actions Menu

Click the “Acciones” button for the reservation
4

View Invoice

Select “Ver factura” to generate and display the invoice

Invoice Generation

Invoices are generated from reservation data in the dashboard:
Components/Pages/Dashboard/Index.razor:206
private async Task OpenInvoiceAsync(TourReservationReportDto r)
{
    try
    {
        var invoiceHtml = await GenerateInvoiceHtmlAsync(r);
        await Storage.SetAsync("last_invoice_html", invoiceHtml);
        Navigation.NavigateTo("/invoice", true);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error generating invoice: {ex.Message}");
        ShowModal("Error", "No se pudo generar la factura.");
    }
}

Invoice Data Structure

Invoices are generated using the TourReservationReportDto:
Dtos/TourDtos.cs:97
public sealed record TourReservationReportDto(
    int ReservationId,
    string ReservationCode,
    DateTime TravelDate,
    DateTime? CreatedAt,
    int TotalTickets,
    decimal TotalAmount,
    string CurrencyCode,
    byte ReservationStatus,
    byte PaymentStatus,
    decimal? AmountPaid,
    decimal? AmountPending,
    DateTime? NextChargeDate,
    byte? PaymentPlanType,
    string? ContactName,
    string? ContactEmail,
    string? ContactPhone,
    IReadOnlyList<TourReservationTicketLineDto> Tickets);

Ticket Line Items

Each invoice includes detailed ticket information:
Dtos/TourDtos.cs:130
public sealed record TourReservationTicketLineDto(
    int ReservationId,
    int TicketTypeId,
    string TicketName,
    int Quantity,
    decimal UnitPrice,
    string? TicketReservationCode = null,
    string? PassengerDocument = null,
    string? PassengerName = null);

Invoice Contents

A typical invoice includes:

Header Information

  • Company logo and details
  • Invoice number (Reservation Code)
  • Issue date
  • Tour title and location

Customer Details

  • Contact name
  • Email address
  • Phone number
  • Travel date

Line Items

  • Ticket type names
  • Quantities
  • Unit prices
  • Line totals

Payment Summary

  • Subtotal
  • Service charges
  • Total amount
  • Payment status

Service Charge Display

The invoice breaks down service charges separately:
var detailBaseTotal = r.Tickets.Sum(x => x.Quantity * x.UnitPrice);
var detailServiceCharge = r.TotalAmount - detailBaseTotal;
var hasServiceCharge = detailServiceCharge > 0.01m;

@if (hasServiceCharge)
{
    <tr style="background:#fff8f5;">
        <td colspan="5" style="text-align:right;">
            <span>Cargo por servicio</span>
        </td>
        <td style="text-align:right;">
            + @FormatCurrency(detailServiceCharge, r.CurrencyCode)
        </td>
    </tr>
}
Service charges cover platform operational costs including payment processing and system maintenance.

Partial Payment Display

For reservations with partial payments, the invoice shows:
@if (r.AmountPaid > 0 && r.AmountPaid < r.TotalAmount)
{
    <tr>
        <td colspan="5" style="text-align:right;">Pagado</td>
        <td style="text-align:right;">
            @FormatCurrency(r.AmountPaid ?? 0, r.CurrencyCode)
        </td>
    </tr>
    <tr>
        <td colspan="5" style="text-align:right;">Pendiente</td>
        <td style="text-align:right;">
            @FormatCurrency(r.AmountPending ?? 0, r.CurrencyCode)
        </td>
    </tr>
    @if (r.NextChargeDate.HasValue)
    {
        <tr>
            <td colspan="5" style="text-align:right;">Próximo cobro</td>
            <td style="text-align:right;">
                @FormatDate(r.NextChargeDate.Value)
            </td>
        </tr>
    }
}

Currency Formatting

Invoices support multiple currencies with proper formatting:
private static string FormatCurrency(decimal amount, string currencyCode)
{
    var symbol = currencyCode switch
    {
        "USD" => "$",
        "EUR" => "EUR",
        _ => "RD$"
    };

    return $"{symbol}{amount:N0}";
}

Supported Currencies

CodeSymbolFormat
USD$$1,000
EUREUREUR1,000
DOPRD$RD$1,000
Invoices can be printed using the browser’s print dialog:
Components/Pages/Invoice.razor:38
private void Print()
{
    JS.InvokeVoidAsync("window.print");
}
Use the browser’s “Save as PDF” option in the print dialog to create a PDF invoice.

Payment Status Indicators

Invoices display payment status with colored badges:
private string GetPaymentLabel(byte paymentStatus) => paymentStatus switch
{
    0 => "Pendiente",
    1 => "Pagado",
    2 => "Pago Parcial",
    3 => "Reembolsado",
    4 => "Cancelado",
    _ => "Desconocido"
};

private string GetPaymentBadge(TourReservationReportDto r)
{
    return r.PaymentStatus switch
    {
        0 => "payment-badge--pending",
        1 => "payment-badge--paid",
        2 => "payment-badge--partial",
        3 => "payment-badge--refunded",
        4 => "payment-badge--cancelled",
        _ => "payment-badge--unknown"
    };
}

Empty Layout

Invoices use an empty layout for clean printing:
@layout EmptyLayout
This removes navigation, headers, and footers, displaying only the invoice content.

Storage and Security

Invoices are stored in browser local storage temporarily. They are not persisted to the database.

Protected Browser Storage

AndanDo uses ProtectedLocalStorage for secure client-side storage:
@inject ProtectedLocalStorage Storage

// Store invoice
await Storage.SetAsync("last_invoice_html", invoiceHtml);

// Retrieve invoice
var stored = await Storage.GetAsync<string>("last_invoice_html");

Customization

To customize invoice appearance, modify the inline styles in the invoice generation method:
private async Task<string> GenerateInvoiceHtmlAsync(TourReservationReportDto r)
{
    var sb = new StringBuilder();
    
    sb.Append("<div style='max-width:800px;margin:0 auto;padding:40px;'>");
    sb.Append($"<h1 style='color:#eb662b;'>FACTURA</h1>");
    sb.Append($"<p>Código: {r.ReservationCode}</p>");
    // ... build HTML
    sb.Append("</div>");
    
    return sb.ToString();
}

Dashboard Overview

Access the main dashboard to view reservations

Managing Bookings

Learn about reservation management

Payment Methods

Configure payment processing

Build docs developers (and LLMs) love