diff --git a/fusion_plating/fusion_plating_reports/__manifest__.py b/fusion_plating/fusion_plating_reports/__manifest__.py index 6404bdc7..a93caaa3 100644 --- a/fusion_plating/fusion_plating_reports/__manifest__.py +++ b/fusion_plating/fusion_plating_reports/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Reports', - 'version': '19.0.4.5.0', + 'version': '19.0.4.7.0', 'category': 'Manufacturing/Plating', 'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.', 'depends': [ diff --git a/fusion_plating/fusion_plating_reports/report/report_base_styles.xml b/fusion_plating/fusion_plating_reports/report/report_base_styles.xml index ca784006..369dfc59 100644 --- a/fusion_plating/fusion_plating_reports/report/report_base_styles.xml +++ b/fusion_plating/fusion_plating_reports/report/report_base_styles.xml @@ -46,10 +46,9 @@ .fp-report .status-ok { color: #2e7d32; font-weight: bold; } .fp-report .status-warning { color: #f57f17; 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-table { width: 100%; border-collapse: separate; border-spacing: 8px 0; margin-top: 16px; 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 { width: 100%; border-collapse: collapse; margin-top: 16px; border: 1px solid #000; 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 .fp-cell-mid { vertical-align: middle !important; } .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-warning { color: #f57f17; 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-table { width: 100%; border-collapse: separate; border-spacing: 6px 0; margin-top: 6px; 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 { width: 100%; border-collapse: collapse; margin-top: 6px; border: 1px solid #000; 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 .fp-cell-mid { vertical-align: middle !important; } .fp-landscape .fp-keep-together { page-break-inside: avoid; break-inside: avoid; } diff --git a/fusion_plating/fusion_plating_reports/report/report_fp_bol.xml b/fusion_plating/fusion_plating_reports/report/report_fp_bol.xml index 43e1ff69..ab1665cf 100644 --- a/fusion_plating/fusion_plating_reports/report/report_fp_bol.xml +++ b/fusion_plating/fusion_plating_reports/report/report_fp_bol.xml @@ -183,25 +183,19 @@ according to the applicable regulations of the Department of Transportation. - +
- - -
-
-
-
Shipper (Signature / Date)
-
+
+
+
Shipper (Signature / Date)
-
-
-
Carrier / Driver (Signature / Date)
-
+
+
+
Carrier / Driver (Signature / Date)
-
-
-
Consignee (Signature / Date)
-
+
+
+
Consignee (Signature / Date)
@@ -374,26 +368,22 @@ -
-
-
+ + + + + + +
Shipper (Signature / Date)
-
- -
-
+
Carrier / Driver (Signature / Date)
-
- -
-
+
Consignee (Signature / Date)
-
- - +
diff --git a/fusion_plating/scripts/fp_bol_portrait_inspect.py b/fusion_plating/scripts/fp_bol_portrait_inspect.py new file mode 100644 index 00000000..488fd5b1 --- /dev/null +++ b/fusion_plating/scripts/fp_bol_portrait_inspect.py @@ -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'(
)\s*
\s*
\s*', + 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'()', 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) diff --git a/fusion_plating/scripts/fp_bol_portrait_save.py b/fusion_plating/scripts/fp_bol_portrait_save.py new file mode 100644 index 00000000..41d89f7b --- /dev/null +++ b/fusion_plating/scripts/fp_bol_portrait_save.py @@ -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') diff --git a/fusion_plating/scripts/fp_bol_stress.py b/fusion_plating/scripts/fp_bol_stress.py new file mode 100644 index 00000000..60e8f80e --- /dev/null +++ b/fusion_plating/scripts/fp_bol_stress.py @@ -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', '

Handle with care.

', 1), + ('three lines', '

Line 1

Line 2

Line 3

', 1), + ('ten lines', ''.join(f'

Special instruction line {i}: handle with care, fragile, do not stack.

' for i in range(10)), 1), + ('paragraph block', '

' + ('Long instructions filling the cargo description box. ' * 30) + '

', 1), + ('huge block', '

' + ('Very long instructions. ' * 80) + '

', 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') diff --git a/fusion_plating/scripts/fp_grep.py b/fusion_plating/scripts/fp_grep.py new file mode 100644 index 00000000..d6bb956a --- /dev/null +++ b/fusion_plating/scripts/fp_grep.py @@ -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})(
)(.{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']*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}')