Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/print_label.cgi
Diff

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/print_label.cgi

added on local at 2026-07-01 21:47:35

Added
+276
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 95c04da6d3db
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# RePricer -- printable shipping label + packing slip.
4#
5# /print_label.cgi?label_id=N
6#
7# Renders a print-ready HTML page (no app chrome) the seller can hit
8# Ctrl+P on. Designed to fit on US Letter or A4. Includes:
9# - From / To addresses
10# - Carrier + service + tracking number with a CODE128 barcode
11# - Weight + dimensions
12# - A packing slip listing the order items
13#
14# Until a real carrier-API label PDF is available, this is the
15# label. It's not for carrier acceptance (no postage paid) -- it's
16# what the seller staples on the box for their own records / to
17# stick on top of a carrier-issued label they bought elsewhere.
18#======================================================================
19use strict;
20use warnings;
21
22use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
23use CGI;
24use MODS::DBConnect;
25use MODS::Login;
26use MODS::RePricer::Config;
27use MODS::RePricer::Permissions;
28use MODS::RePricer::Shipping;
29
30$| = 1;
31
32my $q = CGI->new;
33my $form = $q->Vars;
34my $auth = MODS::Login->new;
35my $db = MODS::DBConnect->new;
36my $cfg = MODS::RePricer::Config->new;
37my $perm = MODS::RePricer::Permissions->new;
38my $ship = MODS::RePricer::Shipping->new;
39my $DB = $cfg->settings('database_name');
40
41my $userinfo = $auth->login_verify();
42unless ($userinfo) {
43 print "Status: 302 Found\nLocation: /login.cgi?return=/fulfill.cgi\n\n"; exit;
44}
45$perm->require_feature($userinfo, 'view_orders');
46
47my $uid = $userinfo->{user_id}; $uid =~ s/[^0-9]//g;
48my $owner_uid = $userinfo->{owner_user_id} || $uid;
49$owner_uid =~ s/[^0-9]//g;
50
51my $lid = $form->{label_id} || 0; $lid =~ s/[^0-9]//g;
52unless ($lid) {
53 print "Status: 302 Found\nLocation: /fulfill.cgi\n\n"; exit;
54}
55
56my $dbh = $db->db_connect();
57my $label = $ship->get_label($db, $dbh, $DB, $lid, $owner_uid);
58unless ($label) {
59 $db->db_disconnect($dbh);
60 print "Status: 302 Found\nLocation: /fulfill.cgi\n\n"; exit;
61}
62my $order = $ship->get_order($db, $dbh, $DB, $label->{order_id}, $owner_uid);
63my $items = $ship->get_order_items($db, $dbh, $DB, $label->{order_id}, $owner_uid);
64my $settings = $ship->get_settings($db, $dbh, $DB, $owner_uid) || {};
65$db->db_disconnect($dbh);
66
67my $brand = $cfg->settings('brand_name') || 'RePricer';
68my $tracking = $label->{tracking_number} || '';
69
70print "Content-Type: text/html; charset=utf-8\n\n";
71print qq~<!DOCTYPE html>
72<html lang="en">
73<head>
74<meta charset="utf-8">
75<title>Label #$lid &mdash; Order #$label->{order_id}</title>
76<style>
77 \@page { size: letter; margin: 0.4in; }
78 * { box-sizing: border-box; }
79 body { margin:0; padding:24px; font-family: -apple-system, "Segoe UI", system-ui, Arial, sans-serif; color:#111; background:#fff; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
80 .pl-noprint { background:#f4f4f5; border:1px dashed #cbd5e1; padding:10px 14px; border-radius:6px; margin-bottom:18px; font-size:13px; color:#475569; display:flex; gap:10px; align-items:center; justify-content:space-between; }
81 .pl-noprint button { padding:6px 14px; font-size:13px; background:#111; color:#fff; border:none; border-radius:5px; cursor:pointer; }
82 \@media print { .pl-noprint { display:none !important; } }
83
84 .pl-card { border:2px solid #111; border-radius:6px; overflow:hidden; max-width:7.5in; }
85 .pl-row { display:flex; }
86 .pl-cell { flex:1; padding:14px 16px; }
87 .pl-cell + .pl-cell { border-left:1.5px solid #111; }
88 .pl-row + .pl-row { border-top:1.5px solid #111; }
89
90 .pl-lbl { font-size:9px; text-transform:uppercase; letter-spacing:2px; color:#000; font-weight:700; margin-bottom:6px; }
91 .pl-addr { font-size:14px; line-height:1.45; }
92 .pl-big { font-size:22px; font-weight:700; }
93 .pl-carrier { font-size:32px; font-weight:900; letter-spacing:2px; text-transform:uppercase; }
94 .pl-service { font-size:13px; color:#444; }
95
96 .pl-track { padding:14px 16px; border-top:1.5px solid #111; }
97 .pl-track-num { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size:18px; font-weight:700; letter-spacing:1px; }
98 .pl-barcode { display:flex; gap:1px; height:60px; margin-top:6px; }
99 .pl-barcode span { display:block; background:#111; }
100
101 .pl-meta { display:flex; gap:24px; padding:10px 16px; font-size:11px; color:#444; }
102 .pl-meta strong { color:#111; font-size:12px; }
103
104 /* Packing slip below the label */
105 .pl-slip { max-width:7.5in; margin-top:24px; }
106 .pl-slip h2 { font-size:18px; margin:0 0 4px; }
107 .pl-slip .pl-sub { font-size:12px; color:#666; margin-bottom:14px; }
108 table.pl-items { width:100%; border-collapse:collapse; font-size:13px; }
109 .pl-items th { text-align:left; padding:8px 6px; border-bottom:2px solid #111; font-size:11px; text-transform:uppercase; letter-spacing:1px; }
110 .pl-items td { padding:6px; border-bottom:1px solid #d4d4d8; }
111 .pl-items td.r { text-align:right; font-variant-numeric: tabular-nums; }
112 .pl-thanks { margin-top:16px; padding:12px 14px; background:#f4f4f5; border-radius:6px; font-size:13px; color:#444; }
113</style>
114</head>
115<body>
116
117<div class="pl-noprint">
118 <span>This is a print preview &mdash; tap Print or hit Ctrl/Cmd + P.</span>
119 <button onclick="window.print()">Print</button>
120</div>
121
122<div class="pl-card">
123 <div class="pl-row">
124 <div class="pl-cell" style="flex:1.2">
125 <div class="pl-lbl">From</div>
126 <div class="pl-addr">
127~;
128
129my $from_html = _addr_block(
130 $settings->{origin_name}, $settings->{origin_company},
131 $settings->{origin_line1}, $settings->{origin_line2},
132 $settings->{origin_city}, $settings->{origin_region}, $settings->{origin_postal},
133 $settings->{origin_country}, $settings->{origin_phone},
134);
135print $from_html;
136print qq~
137 </div>
138 </div>
139 <div class="pl-cell" style="flex:0.8;display:flex;flex-direction:column;align-items:flex-end;justify-content:space-between">
140 <div class="pl-carrier">~ . uc(_h($label->{carrier} || 'manual')) . qq~</div>
141 <div class="pl-service">~ . _h($label->{service} || '') . qq~</div>
142 </div>
143 </div>
144
145 <div class="pl-row">
146 <div class="pl-cell">
147 <div class="pl-lbl">Ship to</div>
148 <div class="pl-addr pl-big">
149~;
150
151my $to_html = _addr_block(
152 $order->{ship_name}, undef,
153 $order->{ship_addr1}, $order->{ship_addr2},
154 $order->{ship_city}, $order->{ship_region}, $order->{ship_postal},
155 $order->{ship_country}, $order->{ship_phone},
156);
157print $to_html;
158print qq~
159 </div>
160 </div>
161 </div>
162~;
163
164if (length $tracking) {
165 my $barcode = _barcode_strips($tracking);
166 print qq~
167 <div class="pl-track">
168 <div class="pl-lbl">Tracking number</div>
169 <div class="pl-track-num">$tracking</div>
170 <div class="pl-barcode">$barcode</div>
171 </div>
172~;
173}
174
175print qq~
176 <div class="pl-meta">
177 <div><strong>Order</strong> #$label->{order_id}</div>
178 <div><strong>Label</strong> #$lid</div>
179~;
180if ($label->{weight_grams}) {
181 print qq~<div><strong>Weight</strong> $label->{weight_grams} g</div>~;
182}
183if (defined $label->{cost_cents}) {
184 my $c = sprintf('%.2f', $label->{cost_cents} / 100);
185 print qq~<div><strong>Postage</strong> \$$c</div>~;
186}
187print qq~ </div>
188</div>
189
190<div class="pl-slip">
191 <h2>Packing slip &mdash; Order #$label->{order_id}</h2>
192 <div class="pl-sub">From $brand &middot; ~ . _h($order->{paid_at} || $order->{created_at} || '') . qq~</div>
193 <table class="pl-items">
194 <thead>
195 <tr>
196 <th style="width:50%">Item</th>
197 <th>Variant</th>
198 <th class="r">Qty</th>
199 <th class="r">Price</th>
200 </tr>
201 </thead>
202 <tbody>
203~;
204
205my $st_subtotal = 0;
206foreach my $it (@$items) {
207 next unless ($it->{purchase_kind} || '') eq 'physical';
208 my $variant = '';
209 if (length($it->{selected_color} || '')) { $variant .= _h($it->{selected_color}); }
210 if (length($it->{selected_material} || '')) {
211 $variant .= ' &middot; ' if length $variant;
212 $variant .= _h($it->{selected_material});
213 }
214 my $line = ($it->{price_paid_cents} || 0) * ($it->{quantity} || 1);
215 $st_subtotal += $line;
216 print qq~
217 <tr>
218 <td>~ . _h($it->{model_title} || '') . qq~</td>
219 <td>$variant</td>
220 <td class="r">$it->{quantity}</td>
221 <td class="r">\$~ . sprintf('%.2f', $line / 100) . qq~</td>
222 </tr>~;
223}
224
225my $total_disp = sprintf('%.2f', ($order->{total_amount_cents} || 0) / 100);
226my $sub_disp = sprintf('%.2f', $st_subtotal / 100);
227my $shp_disp = sprintf('%.2f', ($order->{shipping_cents} || 0) / 100);
228
229print qq~
230 </tbody>
231 <tfoot>
232 <tr><td colspan="3" class="r" style="border-bottom:none;color:#666">Items subtotal</td><td class="r" style="border-bottom:none">\$$sub_disp</td></tr>
233 <tr><td colspan="3" class="r" style="border-bottom:none;color:#666">Shipping</td><td class="r" style="border-bottom:none">\$$shp_disp</td></tr>
234 <tr><td colspan="3" class="r" style="border-bottom:none;font-weight:700">Total</td><td class="r" style="border-bottom:none;font-weight:700">\$$total_disp ~ . _h($order->{currency} || 'USD') . qq~</td></tr>
235 </tfoot>
236 </table>
237 <div class="pl-thanks">Thank you for your order! Questions? Reply to the email on your receipt.</div>
238</div>
239
240</body>
241</html>
242~;
243exit;
244
245sub _h {
246 my $s = shift // '';
247 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
248 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
249 return $s;
250}
251sub _addr_block {
252 my ($name, $company, $l1, $l2, $city, $region, $postal, $country, $phone) = @_;
253 my @lines;
254 push @lines, _h($name) if length($name || '');
255 push @lines, _h($company) if length($company || '');
256 push @lines, _h($l1) if length($l1 || '');
257 push @lines, _h($l2) if length($l2 || '');
258 my $csz = join(' ', grep { length } (_h($city || ''), _h($region || ''), _h($postal || '')));
259 push @lines, $csz if length $csz;
260 push @lines, _h($country) if length($country || '');
261 return @lines ? join('<br>', @lines) : '(no address)';
262}
263# Pseudo-barcode: not a real CODE128 -- a visual representation that
264# matches the digits/characters in the tracking number. Carriers will
265# only scan their own printed barcodes, but this gives the label a
266# legitimate look and lets a seller eyeball-check a tracking number.
267sub _barcode_strips {
268 my ($s) = @_;
269 my $bars = '';
270 foreach my $ch (split //, $s) {
271 my $w = (ord($ch) % 4) + 1; # 1..4 px
272 my $g = (ord($ch) % 3) + 1; # 1..3 px gap
273 $bars .= qq~<span style="width:${w}px"></span><span style="width:${g}px;background:#fff"></span>~;
274 }
275 return $bars;
276}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help