Added invoices page

This commit is contained in:
Pinga 2023-11-17 01:10:10 +02:00
parent ac61a4c3ad
commit 94053d17ca
8 changed files with 181 additions and 130 deletions

View file

@ -18,9 +18,9 @@ class FinancialsController extends Controller
return view($response,'admin/financials/overview.twig');
}
public function pricing(Request $request, Response $response)
public function invoices(Request $request, Response $response)
{
return view($response,'admin/financials/pricing.twig');
return view($response,'admin/financials/invoices.twig');
}
public function deposit(Request $request, Response $response)

View file

@ -0,0 +1,65 @@
{% extends "layouts/app.twig" %}
{% block title %}{{ __('Invoices') }}{% 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">
<!-- Page pre-title -->
<div class="page-pretitle">
{{ __('Overview') }}
</div>
<h2 class="page-title">
{{ __('Invoices') }}
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="col-12">
<div class="card">
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-secondary">
<button class="btn btn-info btn-icon" onclick="downloadCSV()"><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><path d="M10 15a1 1 0 0 0 1 1h2a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-2a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1"></path><path d="M17 8l2 8l2 -8"></path><path d="M7 10a2 2 0 1 0 -4 0v4a2 2 0 1 0 4 0"></path></svg></button>
<button class="btn btn-info btn-icon" onclick="downloadJSON()"><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><path d="M20 16v-8l3 8v-8"></path><path d="M15 8a2 2 0 0 1 2 2v4a2 2 0 1 1 -4 0v-4a2 2 0 0 1 2 -2z"></path><path d="M1 8h3v6.5a1.5 1.5 0 0 1 -3 0v-.5"></path><path d="M7 15a1 1 0 0 0 1 1h1a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h1a1 1 0 0 1 1 1"></path></svg></button>
<button class="btn btn-green btn-icon" onclick="downloadXLSX()"><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><path d="M14 3v4a1 1 0 0 0 1 1h4"></path><path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"></path><path d="M4 15l4 6"></path><path d="M4 21l4 -6"></path><path d="M17 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"></path><path d="M11 15v6h3"></path></svg></button>
<button class="btn btn-red btn-icon" onclick="downloadPDF()"><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><path d="M10 8v8h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-2z"></path><path d="M3 12h2a2 2 0 1 0 0 -4h-2v8"></path><path d="M17 12h3"></path><path d="M21 8h-4v8"></path></svg></button>
</div>
<div class="ms-auto text-secondary">
Search:
<div class="ms-2 d-inline-block">
<input id="search-input" type="text" class="form-control" aria-label="Search invoices">
</div>
</div>
</div>
</div>
<div class="table-responsive">
<div id="invoicesTable"></div>
</div>
</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

@ -1,124 +0,0 @@
{% extends "layouts/app.twig" %}
{% block title %}{{ __('Price Management') }}{% 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">
<!-- Page pre-title -->
<div class="page-pretitle">
Overview
</div>
<h2 class="page-title">
{{ __('Price Management') }}
</h2>
</div>
<!-- Page title actions -->
<div class="col-auto ms-auto d-print-none">
<div class="btn-list">
<span class="d-none d-sm-inline">
<a href="#" class="btn">
New view
</a>
</span>
<a href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal" data-bs-target="#modal-report">
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
<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"/><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
Create new report
</a>
<a href="#" class="btn btn-primary d-sm-none btn-icon" data-bs-toggle="modal" data-bs-target="#modal-report" aria-label="Create new report">
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
<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"/><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="col-12">
<div class="card">
<div class="card-body border-bottom py-3">
<!-- Price list (This will be dynamically populated using your backend) -->
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>TLD ID</th>
<th>Command</th>
<th>Initial</th>
<th>1 Year</th>
<th>2 Years</th>
<th>3 Years</th>
<th>4 Years</th>
<th>5 Years</th>
<th>6 Years</th>
<th>7 Years</th>
<th>8 Years</th>
<th>9 Years</th>
<th>10 Years</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Example row. This should be generated based on the data from your backend -->
<tr>
<td>1</td>
<td><strong>Create</strong></td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$100</td>
<td><button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#priceModal">Edit</button></td>
</tr>
<tr>
<td>1</td>
<td>Renew</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$10</td>
<td>$100</td>
<td><button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#priceModal">Edit</button></td>
</tr>
<!-- ... other rows ... -->
</tbody>
</table>
</div>
</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,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>{% block title %}{% endblock %} | Namingo</title>
<!-- CSS files -->
{% if route_is('domains') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or route_is('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or route_is('support') or route_is('poll') or route_is('log') %}
{% if route_is('domains') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or route_is('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or route_is('support') or route_is('poll') or route_is('log') or route_is('invoices') %}
{% include 'partials/css-tables.twig' %}
{% else %}
{% include 'partials/css.twig' %}
@ -172,7 +172,7 @@
</a>
</div>
</li>
<li {{ is_current_url('deposit') or is_current_url('transactions') or is_current_url('overview') ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
<li {{ is_current_url('deposit') or is_current_url('transactions') or is_current_url('overview') or is_current_url('invoices') ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false">
<span class="nav-link-icon d-md-none d-lg-inline-block"><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><path d="M7 9m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path><path d="M14 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M17 9v-2a2 2 0 0 0 -2 -2h-10a2 2 0 0 0 -2 2v6a2 2 0 0 0 2 2h2"></path></svg>
</span>
@ -187,7 +187,7 @@
<a class="dropdown-item" href="{{route('transactions')}}">
{{ __('Transactions') }}
</a>
<a class="dropdown-item" href="#">
<a class="dropdown-item" href="{{route('invoices')}}">
{{ __('Invoices') }}
</a>
<a class="dropdown-item" href="{{route('deposit')}}">
@ -385,6 +385,8 @@
{% include 'partials/js-poll.twig' %}
{% elseif route_is('log') %}
{% include 'partials/js-log.twig' %}
{% elseif route_is('invoices') %}
{% include 'partials/js-invoices.twig' %}
{% else %}
{% include 'partials/js.twig' %}
{% endif %}

View file

@ -0,0 +1,75 @@
<script src="/assets/js/tabulator.min.js" defer></script>
<script src="/assets/js/tabler.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.6.0/jspdf.plugin.autotable.min.js"></script>
<script>
var table;
document.addEventListener("DOMContentLoaded", function(){
table = new Tabulator("#invoicesTable", {
ajaxURL:"/api/records/invoices?join=registrar", // Set the URL for your JSON data
ajaxConfig:"GET",
pagination:"local",
paginationSize:10,
ajaxResponse:function(url, params, response){
return response.records;
},
layout:"fitDataFill",
responsiveLayout: "collapse",
responsiveLayoutCollapseStartOpen:false,
resizableColumns:false,
initialSort:[
{column:"invoice_number", dir:"desc"},
],
columns:[
{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:"Registrar", field:"registrar_id.name", 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},
],
placeholder:function(){
return this.getHeaderFilters().length ? "No Matching Data" : "No Data"; //set placeholder based on if there are currently any header filters
}
});
var searchInput = document.getElementById("search-input");
searchInput.addEventListener("input", function () {
var term = searchInput.value.toLowerCase();
if (term) { // Only apply the filter when there's a term to search for
table.setFilter(function (data) {
// Check if any of the fields contain the search term
return (
String(data.invoice_number).toLowerCase().includes(term) ||
String(data.registrar_id.name).toLowerCase().includes(term) ||
String(data.issue_date).toLowerCase().includes(term) ||
String(data.total_amount).toLowerCase().includes(term)
);
});
} else {
table.clearFilter(); // Clear the filter when the search box is emptied
}
});
});
function downloadCSV() {
table.download("csv", "data.csv");
}
function downloadJSON() {
table.download("json", "data.json");
}
function downloadXLSX() {
table.download("xlsx", "data.xlsx", {sheetName:"My Invoices"});
}
function downloadPDF() {
table.download("pdf", "data.pdf", {
orientation:"portrait",
title:"My Invoices",
jsPDF:{unit:"mm", format:"a4", orientation:"p"}
});
}
</script>

View file

@ -67,7 +67,7 @@ $app->group('', function ($route) {
$route->get('/log', LogsController::class .':log')->setName('log');
$route->get('/reports', ReportsController::class .':view')->setName('reports');
$route->get('/pricing', FinancialsController::class .':pricing')->setName('pricing');
$route->get('/invoices', FinancialsController::class .':invoices')->setName('invoices');
$route->map(['GET', 'POST'], '/deposit', FinancialsController::class .':deposit')->setName('deposit');
$route->get('/transactions', FinancialsController::class .':transactions')->setName('transactions');
$route->get('/overview', FinancialsController::class .':overview')->setName('overview');

View file

@ -173,6 +173,22 @@ CREATE TABLE IF NOT EXISTS `registry`.`statement` (
CONSTRAINT `statement_ibfk_1` FOREIGN KEY (`registrar_id`) REFERENCES `registrar` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='financial statement';
CREATE TABLE IF NOT EXISTS `registry`.`invoices` (
id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
invoice_number VARCHAR(20),
registrar_id INT(10) UNSIGNED,
billing_contact_id INT(10) UNSIGNED,
issue_date DATETIME(3),
due_date DATETIME(3) default NULL,
total_amount DECIMAL(10,2),
payment_status ENUM('unpaid', 'paid', 'overdue', 'cancelled') DEFAULT 'unpaid',
notes TEXT default NULL,
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
FOREIGN KEY (registrar_id) REFERENCES registrar(id),
FOREIGN KEY (billing_contact_id) REFERENCES registrar_contact(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='invoices';
CREATE TABLE IF NOT EXISTS `registry`.`contact` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`identifier` varchar(255) NOT NULL,

View file

@ -178,6 +178,23 @@ CREATE TABLE registry.statement (
primary key ("id")
);
CREATE TABLE registry.invoices (
id SERIAL PRIMARY KEY,
invoice_number VARCHAR(20),
registrar_id INT,
billing_contact_id INT,
issue_date TIMESTAMP(3),
due_date TIMESTAMP(3) DEFAULT NULL,
total_amount NUMERIC(10,2),
payment_status VARCHAR(10) DEFAULT 'unpaid' CHECK (payment_status IN ('unpaid', 'paid', 'overdue', 'cancelled')),
notes TEXT DEFAULT NULL,
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (registrar_id) REFERENCES registrar(id),
FOREIGN KEY (billing_contact_id) REFERENCES registrar_contact(id)
);
CREATE TABLE registry.contact (
"id" serial8,
"identifier" varchar(255) NOT NULL,