fix(reports): collapse sig-row to one bordered table — kill duplicate borders
User reported "multiple unwanted vertical lines in the boxes" on the portrait BoL. Pixel analysis confirmed it: previous design had 3 separate `<div class="sig-box">` each with its own 1px border, with a 4-8px gap between adjacent boxes — visually those adjacent borders read as a doubled / "duplicate" line between cells. Fix: replace 3-box layout with a single `<table class="bordered sig-table">` containing 3 td cells. With border-collapse: collapse, adjacent cells share their border — so the row now shows 4 vertical lines (1 outer left + 2 internal dividers + 1 outer right) instead of 6 close-together border lines. - Dropped `.sig-box` class entirely (no per-box border anymore) - Added `.sig-table` + `.sig-cell` with explicit 1px borders so the layout works without depending on `.bordered` class inheritance - Applied to both portrait + landscape variants - Landscape sig-row was still using the OLD Bootstrap row+col-4 layout (never got replaced earlier) — also migrated to the new table layout Verified: page count unchanged (portrait 1, landscape 1), all labels and content present, structure clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — Reports',
|
'name': 'Fusion Plating — Reports',
|
||||||
'version': '19.0.4.5.0',
|
'version': '19.0.4.7.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
||||||
'depends': [
|
'depends': [
|
||||||
|
|||||||
@@ -46,10 +46,9 @@
|
|||||||
.fp-report .status-ok { color: #2e7d32; font-weight: bold; }
|
.fp-report .status-ok { color: #2e7d32; font-weight: bold; }
|
||||||
.fp-report .status-warning { color: #f57f17; font-weight: bold; }
|
.fp-report .status-warning { color: #f57f17; font-weight: bold; }
|
||||||
.fp-report .status-fail { color: #c62828; font-weight: bold; }
|
.fp-report .status-fail { color: #c62828; font-weight: bold; }
|
||||||
.fp-report .sig-box { border: 1px solid #000; padding: 12px; page-break-inside: avoid; break-inside: avoid; }
|
|
||||||
.fp-report .sig-line { border-bottom: 1px solid #000; height: 60px; margin-bottom: 4px; }
|
.fp-report .sig-line { border-bottom: 1px solid #000; height: 60px; margin-bottom: 4px; }
|
||||||
.fp-report .sig-table { width: 100%; border-collapse: separate; border-spacing: 8px 0; margin-top: 16px; page-break-inside: avoid; break-inside: avoid; }
|
.fp-report .sig-table { width: 100%; border-collapse: collapse; margin-top: 16px; border: 1px solid #000; page-break-inside: avoid; break-inside: avoid; }
|
||||||
.fp-report .sig-table td { padding: 0; vertical-align: top; page-break-inside: avoid; break-inside: avoid; }
|
.fp-report .sig-table .sig-cell { padding: 14px 12px 8px 12px; vertical-align: top; border: 1px solid #000; page-break-inside: avoid; break-inside: avoid; }
|
||||||
.fp-report .small-muted { font-size: 8pt; color: #666; }
|
.fp-report .small-muted { font-size: 8pt; color: #666; }
|
||||||
.fp-report .fp-cell-mid { vertical-align: middle !important; }
|
.fp-report .fp-cell-mid { vertical-align: middle !important; }
|
||||||
.fp-report .fp-keep-together { page-break-inside: avoid; break-inside: avoid; }
|
.fp-report .fp-keep-together { page-break-inside: avoid; break-inside: avoid; }
|
||||||
@@ -88,10 +87,9 @@
|
|||||||
.fp-landscape .status-ok { color: #2e7d32; font-weight: bold; }
|
.fp-landscape .status-ok { color: #2e7d32; font-weight: bold; }
|
||||||
.fp-landscape .status-warning { color: #f57f17; font-weight: bold; }
|
.fp-landscape .status-warning { color: #f57f17; font-weight: bold; }
|
||||||
.fp-landscape .status-fail { color: #c62828; font-weight: bold; }
|
.fp-landscape .status-fail { color: #c62828; font-weight: bold; }
|
||||||
.fp-landscape .sig-box { border: 1px solid #000; padding: 8px 10px; page-break-inside: avoid; break-inside: avoid; }
|
|
||||||
.fp-landscape .sig-line { border-bottom: 1px solid #000; height: 45px; margin-bottom: 3px; }
|
.fp-landscape .sig-line { border-bottom: 1px solid #000; height: 45px; margin-bottom: 3px; }
|
||||||
.fp-landscape .sig-table { width: 100%; border-collapse: separate; border-spacing: 6px 0; margin-top: 6px; page-break-inside: avoid; break-inside: avoid; }
|
.fp-landscape .sig-table { width: 100%; border-collapse: collapse; margin-top: 6px; border: 1px solid #000; page-break-inside: avoid; break-inside: avoid; }
|
||||||
.fp-landscape .sig-table td { padding: 0; vertical-align: top; page-break-inside: avoid; break-inside: avoid; }
|
.fp-landscape .sig-table .sig-cell { padding: 10px 10px 6px 10px; vertical-align: top; border: 1px solid #000; page-break-inside: avoid; break-inside: avoid; }
|
||||||
.fp-landscape .small-muted { font-size: 9pt; color: #666; }
|
.fp-landscape .small-muted { font-size: 9pt; color: #666; }
|
||||||
.fp-landscape .fp-cell-mid { vertical-align: middle !important; }
|
.fp-landscape .fp-cell-mid { vertical-align: middle !important; }
|
||||||
.fp-landscape .fp-keep-together { page-break-inside: avoid; break-inside: avoid; }
|
.fp-landscape .fp-keep-together { page-break-inside: avoid; break-inside: avoid; }
|
||||||
|
|||||||
@@ -183,25 +183,19 @@
|
|||||||
according to the applicable regulations of the Department of Transportation.
|
according to the applicable regulations of the Department of Transportation.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="sig-table">
|
<table class="bordered sig-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 33.33%;">
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="sig-box">
|
<div class="sig-line"/>
|
||||||
<div class="sig-line"/>
|
<div class="small-muted">Shipper (Signature / Date)</div>
|
||||||
<div class="small-muted">Shipper (Signature / Date)</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 33.33%;">
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="sig-box">
|
<div class="sig-line"/>
|
||||||
<div class="sig-line"/>
|
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
|
||||||
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 33.33%;">
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="sig-box">
|
<div class="sig-line"/>
|
||||||
<div class="sig-line"/>
|
<div class="small-muted">Consignee (Signature / Date)</div>
|
||||||
<div class="small-muted">Consignee (Signature / Date)</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -374,26 +368,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sign off -->
|
<!-- Sign off -->
|
||||||
<div class="row" style="margin-top: 20px;">
|
<table class="bordered sig-table">
|
||||||
<div class="col-4">
|
<tr>
|
||||||
<div class="sig-box">
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="sig-line"/>
|
<div class="sig-line"/>
|
||||||
<div class="small-muted">Shipper (Signature / Date)</div>
|
<div class="small-muted">Shipper (Signature / Date)</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="col-4">
|
|
||||||
<div class="sig-box">
|
|
||||||
<div class="sig-line"/>
|
<div class="sig-line"/>
|
||||||
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
|
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
<td class="sig-cell" style="width: 33.33%;">
|
||||||
<div class="col-4">
|
|
||||||
<div class="sig-box">
|
|
||||||
<div class="sig-line"/>
|
<div class="sig-line"/>
|
||||||
<div class="small-muted">Consignee (Signature / Date)</div>
|
<div class="small-muted">Consignee (Signature / Date)</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
25
fusion_plating/scripts/fp_bol_portrait_inspect.py
Normal file
25
fusion_plating/scripts/fp_bol_portrait_inspect.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
env = env # noqa
|
||||||
|
import re
|
||||||
|
rep = env.ref('fusion_plating_reports.action_report_fp_bol_portrait')
|
||||||
|
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||||
|
html, _ = rep.with_context(force_report_rendering=True
|
||||||
|
)._render_qweb_html(rep.report_name, [dlv.id])
|
||||||
|
out = html.decode() if isinstance(html, bytes) else str(html)
|
||||||
|
|
||||||
|
# Pull the sig-table block + a bit before
|
||||||
|
m = re.search(r'(<div class="fp-keep-together".*?</div>)\s*</div>\s*</div>\s*</t>',
|
||||||
|
out, re.S)
|
||||||
|
if m:
|
||||||
|
print('=== fp-keep-together block ===')
|
||||||
|
print(m.group(1)[:3000])
|
||||||
|
else:
|
||||||
|
# Fallback — just find the sig-table
|
||||||
|
m2 = re.search(r'(<table class="sig-table".*?</table>)', out, re.S)
|
||||||
|
if m2:
|
||||||
|
print('=== sig-table block ===')
|
||||||
|
print(m2.group(1))
|
||||||
|
|
||||||
|
# Also dump the relevant CSS rules
|
||||||
|
print('\n=== relevant css ===')
|
||||||
|
for rule in re.findall(r'\.fp-report\s+\.(?:sig-|fp-keep)[^{]*\{[^}]*\}', out):
|
||||||
|
print(rule)
|
||||||
8
fusion_plating/scripts/fp_bol_portrait_save.py
Normal file
8
fusion_plating/scripts/fp_bol_portrait_save.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
env = env # noqa
|
||||||
|
rep = env.ref('fusion_plating_reports.action_report_fp_bol_portrait')
|
||||||
|
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||||
|
pdf, _ = rep.with_context(force_report_rendering=True
|
||||||
|
)._render_qweb_pdf(rep.report_name, [dlv.id])
|
||||||
|
with open('/tmp/bol_portrait.pdf', 'wb') as f:
|
||||||
|
f.write(pdf)
|
||||||
|
print(f'wrote {len(pdf)/1024:.1f} KB')
|
||||||
33
fusion_plating/scripts/fp_bol_stress.py
Normal file
33
fusion_plating/scripts/fp_bol_stress.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Stress-test the BoL with progressively longer notes
|
||||||
|
env = env # noqa
|
||||||
|
import re
|
||||||
|
rep = env.ref('fusion_plating_reports.action_report_fp_bol_landscape')
|
||||||
|
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||||
|
orig_notes = dlv.notes
|
||||||
|
print(f'baseline (current notes={len(orig_notes or "") } chars)')
|
||||||
|
|
||||||
|
scenarios = [
|
||||||
|
('empty notes', '', 1),
|
||||||
|
('one short line', '<p>Handle with care.</p>', 1),
|
||||||
|
('three lines', '<p>Line 1</p><p>Line 2</p><p>Line 3</p>', 1),
|
||||||
|
('ten lines', ''.join(f'<p>Special instruction line {i}: handle with care, fragile, do not stack.</p>' for i in range(10)), 1),
|
||||||
|
('paragraph block', '<p>' + ('Long instructions filling the cargo description box. ' * 30) + '</p>', 1),
|
||||||
|
('huge block', '<p>' + ('Very long instructions. ' * 80) + '</p>', 1),
|
||||||
|
]
|
||||||
|
print(f'\n{"scenario":<22} {"chars":<8} {"pages":<6} signature row intact?')
|
||||||
|
print('-' * 70)
|
||||||
|
for label, notes, _ in scenarios:
|
||||||
|
dlv.write({'notes': notes})
|
||||||
|
pdf, _e = rep.with_context(force_report_rendering=True
|
||||||
|
)._render_qweb_pdf(rep.report_name, [dlv.id])
|
||||||
|
n_pages = len(re.findall(rb'/Type\s*/Page[^s]', pdf))
|
||||||
|
# Save last for inspection
|
||||||
|
with open(f'/tmp/bol_stress_{label.replace(" ","_")}.pdf', 'wb') as f:
|
||||||
|
f.write(pdf)
|
||||||
|
# Quick "intact" heuristic: if it's >1 page and the size is small,
|
||||||
|
# likely overflowed. Real check is in pypdf locally.
|
||||||
|
print(f'{label:<22} {len(notes):<8} {n_pages:<6} (PDF saved)')
|
||||||
|
|
||||||
|
# Restore
|
||||||
|
dlv.write({'notes': orig_notes or False})
|
||||||
|
print('\noriginal notes restored')
|
||||||
22
fusion_plating/scripts/fp_grep.py
Normal file
22
fusion_plating/scripts/fp_grep.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
env = env # noqa
|
||||||
|
import re
|
||||||
|
rep = env.ref('fusion_plating_reports.action_report_fp_bol_portrait')
|
||||||
|
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||||
|
html, _ = rep.with_context(force_report_rendering=True
|
||||||
|
)._render_qweb_html(rep.report_name, [dlv.id])
|
||||||
|
out = html.decode() if isinstance(html, bytes) else str(html)
|
||||||
|
# Extract the sig-table block + 200 chars before and after
|
||||||
|
m = re.search(r'(.{0,400})(<table class="bordered sig-table".*?</table>)(.{0,200})', out, re.S)
|
||||||
|
if m:
|
||||||
|
print('=== before ===')
|
||||||
|
print(m.group(1)[-300:])
|
||||||
|
print('=== sig-table ===')
|
||||||
|
print(m.group(2))
|
||||||
|
else:
|
||||||
|
print('NOT FOUND. Looking for any sig-table:')
|
||||||
|
for m in re.finditer(r'<table[^>]*sig[^>]*>', out):
|
||||||
|
print(' ', m.group(0))
|
||||||
|
# Also search for the labels
|
||||||
|
for label in ['Shipper (Signature', 'sig-cell', 'sig-table', 'sig-box']:
|
||||||
|
i = out.find(label)
|
||||||
|
print(f' {label!r}: pos={i}')
|
||||||
Reference in New Issue
Block a user