Give each user independent read, write and delete flags per area (invoices, bingo, users), so someone can e.g. review and remove bingo incidents without being able to add any. The request method and path decide the flag needed: GET is read except the new/duplicate forms, POST to .../delete is delete, any other POST is write. Unmapped admin paths are superuser-only, and a test enumerates every registered admin route so a new one cannot ship without a rule. Pages follow the same flags: nav and dashboard tiles show readable areas only, write forms and delete buttons are hidden, and the datacenter form is shown read-only. Assisted-by: Claude:opus-5
55 lines
2.5 KiB
Text
55 lines
2.5 KiB
Text
<div class="header">
|
|
<h1><%= @invoice.number %></h1>
|
|
<p class="tagline"><a href="/admin/invoices">← all invoices</a></p>
|
|
</div>
|
|
|
|
<section class="section">
|
|
<dl class="kv">
|
|
<dt>Client</dt><dd><%= @invoice.client_name %> <<%= @invoice.client_email %>></dd>
|
|
<dt>Issued</dt><dd class="mono"><%= @invoice.issued_on.strftime('%Y-%m-%d') %></dd>
|
|
<dt>Due</dt><dd class="mono"><%= @invoice.due_on.strftime('%Y-%m-%d') %></dd>
|
|
<dt>Total</dt><dd class="mono"><%= @invoice.currency %> <%= '%.2f' % @invoice.total %><% unless @invoice.currency == 'GEL' %> · GEL <%= '%.2f' % @invoice.total_gel %><% end %></dd>
|
|
<dt>Status</dt><dd><span class="badge badge-<%= @invoice.status %>"><%= @invoice.status %></span></dd>
|
|
<% if @invoice.paid_at %>
|
|
<dt>Paid at</dt><dd class="mono"><%= @invoice.paid_at.utc.strftime('%Y-%m-%d %H:%M UTC') %></dd>
|
|
<% end %>
|
|
<% if @invoice.crypto? %>
|
|
<dt><%= @invoice.crypto_coin %></dt>
|
|
<dd class="mono">
|
|
<% if (amt = @invoice.crypto_amount_due) %><%= fmt_crypto(amt) %> <%= @invoice.crypto_coin %><% if @invoice.crypto_rate %> @ <%= fmt_rate(@invoice.crypto_rate) %> <%= @invoice.currency %><% end %><br /><% end %>
|
|
<span style="word-break:break-all;"><%= @invoice.crypto_address %></span>
|
|
</dd>
|
|
<% end %>
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="section">
|
|
<h2>Actions</h2>
|
|
<p>
|
|
<a href="/i/<%= @invoice.uuid %>" target="_blank">Public landing page</a>
|
|
·
|
|
<a href="/i/<%= @invoice.uuid %>/pdf" target="_blank">Download PDF</a>
|
|
</p>
|
|
<p>
|
|
Share this URL with the client:<br />
|
|
<code class="mono">https://asxp.io/i/<%= @invoice.uuid %></code>
|
|
</p>
|
|
<% if can?('invoices', 'write') %>
|
|
<form method="post" action="/admin/invoices/<%= @invoice.uuid %>/paid" style="margin-top: 14px;">
|
|
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>" />
|
|
<button type="submit">
|
|
<% if @invoice.paid? %>Mark unpaid<% else %>Mark paid<% end %>
|
|
</button>
|
|
</form>
|
|
<% end %>
|
|
<p style="margin-top: 14px;">
|
|
<% if can?('invoices', 'write') %><a href="/admin/invoices/<%= @invoice.uuid %>/duplicate">Use as template for a new invoice →</a><% end %>
|
|
</p>
|
|
</section>
|
|
|
|
<% unless @invoice.notes.to_s.strip.empty? %>
|
|
<section class="section">
|
|
<h2>Notes</h2>
|
|
<p style="white-space: pre-wrap;"><%= @invoice.notes %></p>
|
|
</section>
|
|
<% end %>
|