Skip to content

Commit a049017

Browse files
committed
feat: inventory PDF export via headless and report refinements
- Add admin-only /api/orders/inventory.pdf route that 303-redirects the user agent to the Headless service (headless.bemisc.com) with a URL pointing back at the HTML inventory report, authenticated via the admin account's own secret key (skey query parameter). - Expose the new export as "Inventory PDF" via a context link on the Order model alongside the existing "Inventory Report" entry. - Configure via BUDY_HEADLESS_URL and BUDY_HEADLESS_KEY; use appier's url_for absolute mode so BASE_URL drives the public report URL handed to Headless. - Polish the A4 print stylesheet: switch the line-items table to border-collapse: collapse so headers, footers and side rules repeat cleanly across page breaks; keep thead/tfoot at 1pt and body rows at 0.5pt; use a 2pt top rule (with no extra padding) to mark order boundaries so operators can spot the transition. - Carry thumbnails into the print output with a compact 8mm cell. - Suffix every quantity cell (inventory and order reports, body rows and footer totals, plus the inventory Summary total units) with an "x" unit for readability.
1 parent 47c037f commit a049017

6 files changed

Lines changed: 58 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* Inventory HTML report for selected orders at `/api/orders/inventory`, available from the admin via the "Inventory Report" context link
1313
* Per-order HTML report at `/api/orders/<id>/report`, available from the admin via the "Report" link and cross-linked from the inventory report rows
14+
* Inventory PDF export at `/api/orders/inventory.pdf` that renders the inventory report via the Headless service and streams the resulting PDF inline; admin-only and reachable via the new "Inventory PDF" context link. Requires `BASE_URL`, `BUDY_HEADLESS_URL` and `BUDY_HEADLESS_KEY` configuration
1415
* Reusable `report/base.html.tpl` template with a typographic, mailog-inspired look and a dedicated `report.css` stylesheet
1516
* A4 portrait print stylesheet for the inventory report, with a per-row tick box column, repeating table header and `Page X of Y` numbering
1617
* `scripts/load_inventory_demo.py` utility for populating a local Budy instance with sample products and orders to exercise the inventory report

src/budy/controllers/api/order.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,44 @@ def inventory(self):
335335
generated_by=generated_by,
336336
)
337337

338+
@appier.route("/api/orders/inventory.pdf", "GET")
339+
@appier.ensure(token="admin")
340+
def inventory_pdf(self):
341+
start = self.field("start", cast=int)
342+
end = self.field("end", cast=int)
343+
paid = self.field("paid", True, cast=bool)
344+
thumbnail = self.field("thumbnail", True, cast=bool)
345+
view = self.field("view")
346+
context = self.field("context")
347+
headless_url = appier.conf("BUDY_HEADLESS_URL", "https://headless.bemisc.com")
348+
headless_key = appier.conf("BUDY_HEADLESS_KEY", None)
349+
appier.verify(not headless_key == None)
350+
account = self.admin_account.from_session()
351+
account = account.reload(rules=False)
352+
params = dict(paid=paid, thumbnail=thumbnail, skey=account.key)
353+
if start:
354+
params["start"] = start
355+
if end:
356+
params["end"] = end
357+
if view:
358+
params["view"] = view
359+
if context:
360+
params["context"] = context
361+
report_url = appier.get_app().url_for(
362+
"order_api.inventory", absolute=True, **params
363+
)
364+
return self.redirect(
365+
headless_url,
366+
params=dict(
367+
url=report_url,
368+
format="pdf",
369+
page_format="A4",
370+
wait_until="networkidle0",
371+
timeout=60000,
372+
key=headless_key,
373+
),
374+
)
375+
338376
def _format_quantity(self, quantity):
339377
quantity_int = int(quantity)
340378
if quantity_int == quantity:

src/budy/models/order.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ def inventory_url(cls, view=None, context=None, absolute=False):
236236
"order_api.inventory", view=view, context=context, absolute=absolute
237237
)
238238

239+
@classmethod
240+
@appier.link(name="Inventory PDF", context=True)
241+
def inventory_pdf_url(cls, view=None, context=None, absolute=False):
242+
return appier.get_app().url_for(
243+
"order_api.inventory_pdf", view=view, context=context, absolute=absolute
244+
)
245+
239246
@classmethod
240247
@appier.operation(
241248
name="Import CTT",

src/budy/static/css/report.css

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ body {
272272

273273
.report-table tbody tr.report-table-order-first td {
274274
border-top: 1px solid #d0d0d0;
275-
padding-top: 22px;
276275
}
277276

278277
.report-table .report-table-mono {
@@ -360,6 +359,8 @@ body {
360359

361360
/* -- Print (A4 portrait, high contrast B&W) -- */
362361
@page {
362+
size: A4 portrait;
363+
margin: 10mm 10mm 10mm 10mm;
363364
@bottom-right {
364365
color: #000000;
365366
content: "Page " counter(page) " of " counter(pages);
@@ -460,7 +461,8 @@ body {
460461
}
461462
.report-table {
462463
background: transparent;
463-
border: 1pt solid #000000;
464+
border: none;
465+
border-collapse: collapse;
464466
border-radius: 0px 0px 0px 0px;
465467
-o-border-radius: 0px 0px 0px 0px;
466468
-ms-border-radius: 0px 0px 0px 0px;
@@ -475,6 +477,7 @@ body {
475477
}
476478
.report-table thead th {
477479
background: transparent;
480+
border-top: 1pt solid #000000;
478481
border-bottom: 1pt solid #000000;
479482
color: #000000;
480483
font-size: 9pt;
@@ -494,8 +497,7 @@ body {
494497
background: transparent;
495498
}
496499
.report-table tbody tr.report-table-order-first td {
497-
border-top: 0.5pt solid #000000;
498-
padding-top: 10pt;
500+
border-top: 2pt solid #000000;
499501
}
500502
.report-table .report-table-mono {
501503
font-size: 10pt;
@@ -507,6 +509,7 @@ body {
507509
.report-table tfoot td {
508510
background: transparent;
509511
border-top: 1pt solid #000000;
512+
border-bottom: 1pt solid #000000;
510513
color: #000000;
511514
font-size: 10pt;
512515
padding: 4pt 6pt 4pt 6pt;

src/budy/templates/report/inventory.html.tpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
{% for entry in order_references %}<a href="{{ url_for('order_api.report', id = entry.id) }}" target="_blank">{{ entry.reference }}</a>{% if not loop.last %}, {% endif %}{% else %}-{% endfor %}
2121
</dd>
2222
<dt class="report-dt">Total Units</dt>
23-
<dd class="report-dd">{{ total_units }}</dd>
23+
<dd class="report-dd">{{ total_units }} x</dd>
2424
<dt class="report-dt">Generated By</dt>
2525
<dd class="report-dd">{{ generated_by or "-" }}</dd>
2626
{% endblock %}
@@ -56,7 +56,7 @@
5656
<td>{{ row.short_description }}</td>
5757
<td>{{ row.gender }}</td>
5858
<td class="report-table-mono">{{ row.size }}</td>
59-
<td class="report-table-quantity">{{ row.quantity }}</td>
59+
<td class="report-table-quantity">{{ row.quantity }} x</td>
6060
</tr>
6161
{% endfor %}
6262
</tbody>
@@ -65,7 +65,7 @@
6565
<td class="report-table-check"></td>
6666
{% if thumbnail %}<td class="report-table-thumbnail"></td>{% endif %}
6767
<td colspan="5">Total</td>
68-
<td class="report-table-quantity">{{ total_units }}</td>
68+
<td class="report-table-quantity">{{ total_units }} x</td>
6969
</tr>
7070
</tfoot>
7171
</table>

src/budy/templates/report/order.html.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,15 @@
128128
<td>{{ row.gender }}</td>
129129
<td class="report-table-mono">{{ row.size }}</td>
130130
<td class="report-table-quantity">{{ "%.2f" % row.price }} {{ row.currency or "" }}</td>
131-
<td class="report-table-quantity">{{ row.quantity }}</td>
131+
<td class="report-table-quantity">{{ row.quantity }} x</td>
132132
<td class="report-table-quantity">{{ "%.2f" % row.total }} {{ row.currency or "" }}</td>
133133
</tr>
134134
{% endfor %}
135135
</tbody>
136136
<tfoot>
137137
<tr>
138138
<td colspan="5">Total</td>
139-
<td class="report-table-quantity">{{ total_units }}</td>
139+
<td class="report-table-quantity">{{ total_units }} x</td>
140140
<td class="report-table-quantity report-total-emph">{{ "%.2f" % order.total }} {{ order.currency or "" }}</td>
141141
</tr>
142142
</tfoot>

0 commit comments

Comments
 (0)