Added invoice view

This commit is contained in:
Pinga 2023-12-11 12:10:22 +02:00
parent 5bdb5c2a65
commit 93f1a06a40
5 changed files with 184 additions and 1 deletions

View file

@ -23,6 +23,39 @@ class FinancialsController extends Controller
return view($response,'admin/financials/invoices.twig'); return view($response,'admin/financials/invoices.twig');
} }
public function viewInvoice(Request $request, Response $response, $args)
{
$invoiceNumberPattern = '/^[A-Za-z]+\d+-?\d+$/';
if (preg_match($invoiceNumberPattern, $args)) {
$invoiceNumber = $args; // valid format
} else {
$this->container->get('flash')->addMessage('error', 'Invalid invoice number');
return $response->withHeader('Location', '/invoices')->withStatus(302);
}
$db = $this->container->get('db');
$invoice_details = $db->selectRow('SELECT * FROM invoices WHERE invoice_number = ?',
[ $invoiceNumber ]
);
$billing = $db->selectRow('SELECT * FROM registrar_contact WHERE id = ?',
[ $invoice_details['billing_contact_id'] ]
);
$issueDate = new \DateTime($invoice_details['issue_date']);
$firstDayPrevMonth = (clone $issueDate)->modify('first day of last month')->format('Y-m-d');
$lastDayPrevMonth = (clone $issueDate)->modify('last day of last month')->format('Y-m-d');
$statement = $db->select('SELECT * FROM statement WHERE date BETWEEN ? AND ? AND registrar_id = ?',
[ $firstDayPrevMonth, $lastDayPrevMonth, $invoice_details['registrar_id'] ]
);
return view($response,'admin/financials/viewInvoice.twig', [
'invoice_details' => $invoice_details,
'billing' => $billing,
'statement' => $statement
]);
}
public function deposit(Request $request, Response $response) public function deposit(Request $request, Response $response)
{ {
if ($_SESSION["auth_roles"] != 0) { if ($_SESSION["auth_roles"] != 0) {

View file

@ -24,6 +24,7 @@
<div class="page-body"> <div class="page-body">
<div class="container-xl"> <div class="container-xl">
<div class="col-12"> <div class="col-12">
{% include 'partials/flash.twig' %}
<div class="card"> <div class="card">
<div class="card-body border-bottom py-3"> <div class="card-body border-bottom py-3">
<div class="d-flex"> <div class="d-flex">

View file

@ -0,0 +1,136 @@
{% extends "layouts/app.twig" %}
{% block title %}{{ __('View Invoice') }}{% endblock %}
{% block content %}
<div class="page-wrapper">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Invoice {{ invoice_details.invoice_number }}
</h2>
</div>
<!-- Page title actions -->
<div class="col-auto ms-auto d-print-none">
<button type="button" class="btn btn-primary" onclick="javascript:window.print();">
<!-- Download SVG icon from http://tabler-icons.io/i/printer -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 17h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-14a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h2" /><path d="M17 9v-4a2 2 0 0 0 -2 -2h-6a2 2 0 0 0 -2 2v4" /><path d="M7 13m0 2a2 2 0 0 1 2 -2h6a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-6a2 2 0 0 1 -2 -2z" /></svg>
Print Invoice
</button>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="card card-lg">
<div class="card-body">
<div class="row">
<div class="col-6">
<p class="h3">Provider / Registry</p>
<address>
Street Address<br>
State, City<br>
Region, Postal Code<br>
ltd@example.com
</address>
</div>
<div class="col-6 text-end">
<p class="h3">Client / Registrar</p>
<address>
{{ billing.first_name }} {{ billing.last_name }}<br>
{{ billing.org }}<br>
{{ billing.street1 }}<br>
{{ billing.city }}, {{ billing.sp }}<br>
{{ billing.pc }}, {{ billing.cc }}<br>
{{ billing.email }}
</address>
</div>
<div class="col-6">
<p class="h3">Invoice Issued On: {{ invoice_details.issue_date|date("Y-m-d") }}</p>
</div>
<div class="col-6 text-end">
<p class="h3">Due Date: {{ invoice_details.due_date|date("Y-m-d") }}</p>
</div>
<div class="col-12 my-5">
<h1>Invoice {{ invoice_details.invoice_number }} {% set status = invoice_details.payment_status %}
<span class="badge
{% if status == 'unpaid' %}bg-red text-red-fg
{% elseif status == 'paid' %}bg-green text-green-fg
{% elseif status == 'overdue' %}bg-orange text-orange-fg
{% elseif status == 'cancelled' %}bg-grey text-grey-fg
{% else %}bg-secondary
{% endif %}">
{{ status|capitalize }}
</span>
</h1>
</div>
</div>
<table class="table table-transparent table-responsive">
<thead>
<tr>
<th class="text-center" style="width: 1%"></th>
<th>Product</th>
<th class="text-end" style="width: 1%">Amount</th>
</tr>
</thead>
{% if statement is not empty %}
{% set totalAmount = 0 %}
{% for item in statement %}
<tr>
<td class="text-center">{{ loop.index }}</td>
<td>
<p class="strong mb-1">{{ item.command }} {{ item.domain_name }}</p>
</td>
<td class="text-end">{{ item.amount }}</td>
</tr>
{% set totalAmount = totalAmount + item.amount %}
{% endfor %}
{% else %}
<tr>
<td colspan="2" class="text-center">No items found.</td>
</tr>
{% endif %}
<tr>
<td colspan="2" class="strong text-end">Subtotal</td>
<td class="text-end">{{ totalAmount|number_format(2, '.', ',') }}</td>
</tr>
<tr>
<td colspan="2" class="strong text-end">Vat Rate</td>
<td class="text-end">TODO</td>
</tr>
<tr>
<td colspan="2" class="strong text-end">Vat Due</td>
<td class="text-end">TODO</td>
</tr>
<tr>
<td colspan="2" class="font-weight-bold text-uppercase text-end">Total Due</td>
<td class="font-weight-bold text-end">{{ invoice_details.total_amount }}</td>
</tr>
</table>
<p class="text-secondary text-center mt-5">Notes: {{ invoice_details.notes }}</p>
<p class="text-secondary text-center mt-5">Thank you very much for doing business with us. We look forward to working with
you again!</p>
</div>
</div>
</div>
</div>
<footer class="footer footer-transparent d-print-none">
<div class="container-xl">
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
Copyright &copy; 2023
<a href="https://namingo.org" target="_blank" class="link-secondary">Namingo</a>.
</li>
</ul>
</div>
</div>
</div>
</footer>
</div>
{% endblock %}

View file

@ -6,6 +6,17 @@
<script> <script>
var table; var table;
document.addEventListener("DOMContentLoaded", function(){ document.addEventListener("DOMContentLoaded", function(){
function invoiceLinkFormatter(cell){
var value = cell.getValue();
return `<a href="/invoice/${cell.getRow().getData().invoice_number}" style="font-weight:bold;">${value}</a>`;
}
function actionsFormatter(cell, formatterParams, onRendered) {
return `
<a class="btn btn-outline-info btn-icon" href="/invoice/${cell.getRow().getData().invoice_number}" title="View Invoice"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /></svg></a>
`;
}
table = new Tabulator("#invoicesTable", { table = new Tabulator("#invoicesTable", {
ajaxURL:"/api/records/invoices?join=registrar", // Set the URL for your JSON data ajaxURL:"/api/records/invoices?join=registrar", // Set the URL for your JSON data
@ -24,10 +35,11 @@
], ],
columns:[ columns:[
{formatter:"responsiveCollapse", width:30, minWidth:30, hozAlign:"center", resizable:false, headerSort:false, responsive:0}, {formatter:"responsiveCollapse", width:30, minWidth:30, hozAlign:"center", resizable:false, headerSort:false, responsive:0},
{title:"Number", field:"invoice_number", width:200, headerSort:true, responsive:0}, {title:"Number", field:"invoice_number", width:200, headerSort:true, formatter: invoiceLinkFormatter, responsive:0},
{title:"Registrar", field:"registrar_id.name", width:300, headerSort:true, responsive:0}, {title:"Registrar", field:"registrar_id.name", width:300, headerSort:true, responsive:0},
{title:"Date", field:"issue_date", width:300, headerSort:true, responsive:0}, {title:"Date", field:"issue_date", width:300, headerSort:true, responsive:0},
{title:"Amount", field:"total_amount", width:200, headerSort:true, responsive:0}, {title:"Amount", field:"total_amount", width:200, headerSort:true, responsive:0},
{title: "Actions", formatter: actionsFormatter, headerSort: false, download:false, hozAlign: "center", responsive:0, cellClick:function(e, cell){ e.stopPropagation(); }},
], ],
placeholder:function(){ placeholder:function(){
return this.getHeaderFilters().length ? "No Matching Data" : "No Data"; //set placeholder based on if there are currently any header filters return this.getHeaderFilters().length ? "No Matching Data" : "No Data"; //set placeholder based on if there are currently any header filters

View file

@ -81,6 +81,7 @@ $app->group('', function ($route) {
$route->get('/reports', ReportsController::class .':view')->setName('reports'); $route->get('/reports', ReportsController::class .':view')->setName('reports');
$route->get('/invoices', FinancialsController::class .':invoices')->setName('invoices'); $route->get('/invoices', FinancialsController::class .':invoices')->setName('invoices');
$route->get('/invoice/{invoice}', FinancialsController::class . ':viewInvoice')->setName('viewInvoice');
$route->map(['GET', 'POST'], '/deposit', FinancialsController::class .':deposit')->setName('deposit'); $route->map(['GET', 'POST'], '/deposit', FinancialsController::class .':deposit')->setName('deposit');
$route->map(['GET', 'POST'], '/create-payment', FinancialsController::class .':createPayment')->setName('createPayment'); $route->map(['GET', 'POST'], '/create-payment', FinancialsController::class .':createPayment')->setName('createPayment');
$route->map(['GET', 'POST'], '/payment-success', FinancialsController::class .':success')->setName('success'); $route->map(['GET', 'POST'], '/payment-success', FinancialsController::class .':success')->setName('success');