initial version
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<option value="">— new clock —</option>
|
||||
{% for clock in clocks %}
|
||||
<option value="{{ clock.id }}">
|
||||
{{- clock.manufacturer or '' }} {{ clock.model or '' }}{% if clock.description %} ({{ clock.description }}){% endif -%}
|
||||
</option>
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends "layout.njk" %}
|
||||
{% block title %}Customers — Harding's Clocks{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Customers</h1>
|
||||
</div>
|
||||
|
||||
<form class="search-form" method="get" action="/customers">
|
||||
<input type="search" name="q" placeholder="Search name, phone, or email…" value="{{ q }}">
|
||||
<button type="submit">Search</button>
|
||||
{% if q %}<a class="button-link" href="/customers">Clear</a>{% endif %}
|
||||
</form>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in customers %}
|
||||
<tr>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{{ c.phone or '' }}</td>
|
||||
<td>{{ c.email or '' }}</td>
|
||||
<td>
|
||||
{%- if c.address_line1 -%}
|
||||
{{ c.address_line1 }}{% if c.city %}, {{ c.city }}{% endif %}{% if c.state %}, {{ c.state }}{% endif %} {{ c.zip or '' }}
|
||||
{%- endif -%}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="empty">No customers found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,93 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Invoice #{{ order.order_number }} — Harding's Clocks</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body class="invoice-body">
|
||||
<div class="invoice-toolbar no-print">
|
||||
<button type="button" onclick="window.print()">Print</button>
|
||||
</div>
|
||||
|
||||
<div class="invoice">
|
||||
<header class="invoice-header">
|
||||
<div>
|
||||
<h1>Harding's Clocks</h1>
|
||||
<p>Clock Sales & Repair</p>
|
||||
</div>
|
||||
<div class="invoice-meta">
|
||||
<p><strong>Invoice #{{ order.order_number }}</strong></p>
|
||||
<p>Date In: {{ order.date_in or '' }}</p>
|
||||
{% if order.date_out %}<p>Date Out: {{ order.date_out }}</p>{% endif %}
|
||||
{% if order.due_date %}<p>Due: {{ order.due_date }}</p>{% endif %}
|
||||
<p>Status: {{ order.status }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="invoice-parties">
|
||||
<div>
|
||||
<h2>Ship To</h2>
|
||||
<p>{{ order.customer_name }}</p>
|
||||
{% if order.address_line1 %}<p>{{ order.address_line1 }}</p>{% endif %}
|
||||
{% if order.address_line2 %}<p>{{ order.address_line2 }}</p>{% endif %}
|
||||
{% if order.city or order.state or order.zip %}<p>{{ order.city or '' }}{% if order.state %}, {{ order.state }}{% endif %} {{ order.zip or '' }}</p>{% endif %}
|
||||
{% if order.phone %}<p>{{ order.phone }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Clock</h2>
|
||||
{% if order.manufacturer or order.model %}<p>{{ order.manufacturer or '' }} {{ order.model or '' }}</p>{% endif %}
|
||||
{% if order.serial_number %}<p>Serial: {{ order.serial_number }}</p>{% endif %}
|
||||
{% if order.reference %}<p>Reference: {{ order.reference }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Order Info</h2>
|
||||
<p>Job Type: {{ order.job_type or '' }}</p>
|
||||
{% if order.service_rep %}<p>Rep: {{ order.service_rep }}</p>{% endif %}
|
||||
{% if order.technician %}<p>Technician: {{ order.technician }}</p>{% endif %}
|
||||
{% if order.cust_po %}<p>PO: {{ order.cust_po }}</p>{% endif %}
|
||||
{% if order.terms %}<p>Terms: {{ order.terms }}</p>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<table class="invoice-table">
|
||||
<thead>
|
||||
<tr><th>Type</th><th>Stock #</th><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in order.lineItems %}
|
||||
<tr>
|
||||
<td>{{ line.kind }}</td>
|
||||
<td>{{ line.stock_code or '' }}</td>
|
||||
<td>{{ line.description or '' }}</td>
|
||||
<td>{{ line.quantity }}</td>
|
||||
<td>{{ line.unit_price | money }}</td>
|
||||
<td>{{ line.unit_price | lineTotal(line.quantity) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<section class="invoice-totals">
|
||||
<dl>
|
||||
<div><dt>Parts</dt><dd>{{ order.totals.total_parts | money }}</dd></div>
|
||||
<div><dt>Labor</dt><dd>{{ order.totals.total_labor | money }}</dd></div>
|
||||
<div><dt>Sublet</dt><dd>{{ order.totals.total_sublet | money }}</dd></div>
|
||||
<div><dt>Tax</dt><dd>{{ order.tax | money }}</dd></div>
|
||||
<div><dt>Shipping</dt><dd>{{ order.shipping | money }}</dd></div>
|
||||
<div><dt>Pickup/Delivery</dt><dd>{{ order.pickup_delivery | money }}</dd></div>
|
||||
<div><dt>Service Call</dt><dd>{{ order.service_call | money }}</dd></div>
|
||||
<div><dt>Disposal</dt><dd>{{ order.disposal | money }}</dd></div>
|
||||
<div class="grand"><dt>Grand Total</dt><dd>{{ order.totals.grand_total | money }}</dd></div>
|
||||
<div><dt>Amount Paid</dt><dd>{{ order.amount_paid | money }}</dd></div>
|
||||
<div class="balance-due"><dt>Balance Due</dt><dd>{{ order.totals.balance_due | money }}</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<footer class="invoice-footer">
|
||||
<p>Thank you for your business.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Harding's Clocks{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<script src="/vendor/htmx/htmx.min.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="no-print">
|
||||
<a class="brand" href="/">Harding's Clocks</a>
|
||||
<a href="/">Orders</a>
|
||||
<a href="/orders/new">New Order</a>
|
||||
<a href="/customers">Customers</a>
|
||||
</nav>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
{% include "orders/_line_item_row.njk" %}
|
||||
{% include "orders/_totals.njk" %}
|
||||
@@ -0,0 +1,15 @@
|
||||
<tr id="line-{{ line.id }}">
|
||||
<td>{{ line.kind }}</td>
|
||||
<td>{{ line.stock_code or '' }}</td>
|
||||
<td>{{ line.description or '' }}</td>
|
||||
<td>{{ line.quantity }}</td>
|
||||
<td>{{ line.unit_price | money }}</td>
|
||||
<td>{{ line.unit_price | lineTotal(line.quantity) }}</td>
|
||||
<td>
|
||||
<button type="button" class="icon-button"
|
||||
hx-delete="/orders/{{ order.id }}/line-items/{{ line.id }}"
|
||||
hx-target="closest tr"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Remove this line?">✕</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,44 @@
|
||||
{% set totals = order.totals %}
|
||||
<div id="order-summary" class="card">
|
||||
<form hx-post="/orders/{{ order.id }}" hx-target="#order-summary" hx-swap="outerHTML">
|
||||
<div class="grid-2">
|
||||
<label>
|
||||
Status
|
||||
<select name="status">
|
||||
{% for s in statuses %}
|
||||
<option value="{{ s }}" {% if order.status == s %}selected{% endif %}>{{ s }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Job type
|
||||
<select name="job_type">
|
||||
<option value="">— select —</option>
|
||||
{% for jt in jobTypes %}
|
||||
<option value="{{ jt }}" {% if order.job_type == jt %}selected{% endif %}>{{ jt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Service rep <input type="text" name="service_rep" value="{{ order.service_rep or '' }}"></label>
|
||||
<label>Technician <input type="text" name="technician" value="{{ order.technician or '' }}"></label>
|
||||
<label>Date in <input type="text" value="{{ order.date_in or '' }}" disabled></label>
|
||||
<label>Date out <input type="date" name="date_out" value="{{ order.date_out or '' }}"></label>
|
||||
<label>Due date <input type="date" name="due_date" value="{{ order.due_date or '' }}"></label>
|
||||
<label>Customer PO <input type="text" name="cust_po" value="{{ order.cust_po or '' }}"></label>
|
||||
<label>Terms <input type="text" name="terms" value="{{ order.terms or '' }}"></label>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<label>Tax <input type="text" name="tax" value="{{ (order.tax / 100) | round(2) }}"></label>
|
||||
<label>Shipping <input type="text" name="shipping" value="{{ (order.shipping / 100) | round(2) }}"></label>
|
||||
<label>Pickup/Delivery <input type="text" name="pickup_delivery" value="{{ (order.pickup_delivery / 100) | round(2) }}"></label>
|
||||
<label>Service call <input type="text" name="service_call" value="{{ (order.service_call / 100) | round(2) }}"></label>
|
||||
<label>Disposal <input type="text" name="disposal" value="{{ (order.disposal / 100) | round(2) }}"></label>
|
||||
<label>Amount paid <input type="text" name="amount_paid" value="{{ (order.amount_paid / 100) | round(2) }}"></label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
{% include "orders/_totals.njk" %}
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<dl id="totals-summary" class="totals-summary"{% if oob %} hx-swap-oob="true"{% endif %}>
|
||||
<div><dt>Parts</dt><dd>{{ totals.total_parts | money }}</dd></div>
|
||||
<div><dt>Labor</dt><dd>{{ totals.total_labor | money }}</dd></div>
|
||||
<div><dt>Sublet</dt><dd>{{ totals.total_sublet | money }}</dd></div>
|
||||
<div><dt>Grand Total</dt><dd>{{ totals.grand_total | money }}</dd></div>
|
||||
<div class="balance-due"><dt>Balance Due</dt><dd>{{ totals.balance_due | money }}</dd></div>
|
||||
</dl>
|
||||
@@ -0,0 +1,4 @@
|
||||
<li id="log-{{ entry.id }}" class="work-log-item">
|
||||
<time>{{ entry.logged_at }}</time>
|
||||
<p>{{ entry.note }}</p>
|
||||
</li>
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends "layout.njk" %}
|
||||
{% block title %}Order #{{ order.order_number }} — Harding's Clocks{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Order #{{ order.order_number }}</h1>
|
||||
<div class="header-actions">
|
||||
<a class="button-link" href="/orders/{{ order.id }}/invoice" target="_blank">Print Invoice</a>
|
||||
<button type="button" class="danger"
|
||||
hx-delete="/orders/{{ order.id }}"
|
||||
hx-confirm="Delete this order? It can be recovered later if needed.">
|
||||
Delete Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card two-col">
|
||||
<div>
|
||||
<h2>Customer</h2>
|
||||
<p>{{ order.customer_name }}</p>
|
||||
{% if order.address_line1 %}<p>{{ order.address_line1 }}{% if order.address_line2 %}, {{ order.address_line2 }}{% endif %}</p>{% endif %}
|
||||
{% if order.city or order.state or order.zip %}<p>{{ order.city or '' }}{% if order.state %}, {{ order.state }}{% endif %} {{ order.zip or '' }}</p>{% endif %}
|
||||
{% if order.phone %}<p>{{ order.phone }}{% if order.alt_phone %} / {{ order.alt_phone }}{% endif %}</p>{% endif %}
|
||||
{% if order.email %}<p>{{ order.email }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Clock</h2>
|
||||
{% if order.manufacturer or order.model %}
|
||||
<p>{{ order.manufacturer or '' }} {{ order.model or '' }}</p>
|
||||
{% endif %}
|
||||
{% if order.serial_number %}<p>Serial: {{ order.serial_number }}</p>{% endif %}
|
||||
{% if order.alternate_id %}<p>Alt ID: {{ order.alternate_id }}</p>{% endif %}
|
||||
{% if order.reference %}<p>Reference: {{ order.reference }}</p>{% endif %}
|
||||
{% if order.clock_description %}<p>{{ order.clock_description }}</p>{% endif %}
|
||||
{% if not order.manufacturer and not order.model and not order.clock_description %}
|
||||
<p class="empty">No clock on file.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% include "orders/_order_summary.njk" %}
|
||||
|
||||
<section class="card">
|
||||
<h2>Line Items</h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr><th>Type</th><th>Stock #</th><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody id="lines-body">
|
||||
{% for line in order.lineItems %}
|
||||
{% include "orders/_line_item_row.njk" %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form class="inline-form"
|
||||
hx-post="/orders/{{ order.id }}/line-items"
|
||||
hx-target="#lines-body"
|
||||
hx-swap="beforeend"
|
||||
hx-on::after-request="this.reset()">
|
||||
<select name="kind">
|
||||
<option value="labor">Labor</option>
|
||||
<option value="part">Part</option>
|
||||
<option value="sublet">Sublet</option>
|
||||
</select>
|
||||
<input type="text" name="stock_code" placeholder="Stock #">
|
||||
<input type="text" name="description" placeholder="Description" required>
|
||||
<input type="number" name="quantity" placeholder="Qty" value="1" step="0.25" min="0">
|
||||
<input type="text" name="unit_price" placeholder="Unit price">
|
||||
<button type="submit">Add Line</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Work Log</h2>
|
||||
<ul id="work-log-list" class="work-log-list">
|
||||
{% for entry in order.workLog %}
|
||||
{% include "orders/_work_log_item.njk" %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<form class="inline-form"
|
||||
hx-post="/orders/{{ order.id }}/work-log"
|
||||
hx-target="#work-log-list"
|
||||
hx-swap="afterbegin"
|
||||
hx-on::after-request="this.reset()">
|
||||
<input type="text" name="note" placeholder="Add a note…" required>
|
||||
<button type="submit">Add Note</button>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "layout.njk" %}
|
||||
{% block title %}Orders — Harding's Clocks{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Service Orders</h1>
|
||||
<a class="button-link" href="/orders/new">+ New Order</a>
|
||||
</div>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Customer</th>
|
||||
<th>Job Type</th>
|
||||
<th>Status</th>
|
||||
<th>Date In</th>
|
||||
<th>Due</th>
|
||||
<th>Balance Due</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for o in orders %}
|
||||
<tr onclick="location.href='/orders/{{ o.id }}'" class="row-link">
|
||||
<td>{{ o.order_number }}</td>
|
||||
<td>{{ o.customer_name }}</td>
|
||||
<td>{{ o.job_type or '' }}</td>
|
||||
<td><span class="status-badge status-{{ o.status | lower | replace(' ', '-') }}">{{ o.status }}</span></td>
|
||||
<td>{{ o.date_in or '' }}</td>
|
||||
<td>{{ o.due_date or '' }}</td>
|
||||
<td>{{ o.balance_due | money }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="empty">No orders yet. <a href="/orders/new">Create one</a>.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,81 @@
|
||||
{% extends "layout.njk" %}
|
||||
{% block title %}New Order — Harding's Clocks{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>New Service Order</h1>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/orders" class="stacked-form">
|
||||
<fieldset>
|
||||
<legend>Customer</legend>
|
||||
<label>
|
||||
Existing customer
|
||||
<select name="existing_customer_id" id="existing_customer_id"
|
||||
hx-get="/clocks/by-customer"
|
||||
hx-trigger="change"
|
||||
hx-target="#clock-select"
|
||||
hx-include="this">
|
||||
<option value="">— new customer —</option>
|
||||
{% for c in customers %}
|
||||
<option value="{{ c.id }}">{{ c.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="grid-2">
|
||||
<label>Name <input type="text" name="new_customer_name"></label>
|
||||
<label>Phone <input type="text" name="new_customer_phone"></label>
|
||||
<label>Alt phone <input type="text" name="new_customer_alt_phone"></label>
|
||||
<label>Email <input type="email" name="new_customer_email"></label>
|
||||
<label>Address line 1 <input type="text" name="new_customer_address_line1"></label>
|
||||
<label>Address line 2 <input type="text" name="new_customer_address_line2"></label>
|
||||
<label>City <input type="text" name="new_customer_city"></label>
|
||||
<label>State <input type="text" name="new_customer_state"></label>
|
||||
<label>Zip <input type="text" name="new_customer_zip"></label>
|
||||
</div>
|
||||
<p class="hint">Leave the name blank above and pick an existing customer instead, if applicable.</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Clock</legend>
|
||||
<label>
|
||||
Existing clock
|
||||
<select name="existing_clock_id" id="clock-select">
|
||||
<option value="">— new clock —</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="grid-2">
|
||||
<label>Manufacturer <input type="text" name="new_clock_manufacturer"></label>
|
||||
<label>Model <input type="text" name="new_clock_model"></label>
|
||||
<label>Serial number <input type="text" name="new_clock_serial_number"></label>
|
||||
<label>Alternate ID <input type="text" name="new_clock_alternate_id"></label>
|
||||
<label>Reference <input type="text" name="new_clock_reference"></label>
|
||||
<label>Description <input type="text" name="new_clock_description"></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Order</legend>
|
||||
<div class="grid-2">
|
||||
<label>
|
||||
Job type
|
||||
<select name="job_type">
|
||||
<option value="">— select —</option>
|
||||
{% for jt in jobTypes %}
|
||||
<option value="{{ jt }}">{{ jt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Service rep <input type="text" name="service_rep"></label>
|
||||
<label>Technician <input type="text" name="technician"></label>
|
||||
<label>Date in <input type="date" name="date_in" value="{{ today }}"></label>
|
||||
<label>Due date <input type="date" name="due_date"></label>
|
||||
<label>Customer PO <input type="text" name="cust_po"></label>
|
||||
<label>Terms <input type="text" name="terms"></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Create Order</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user