fix(bol): bigger title, shipper info, uniform headers, cargo qty, taller signatures
Five fixes applied to the Bill of Lading and (where relevant) all
report templates:
1. **Bigger title + BoL #** — portrait now uses h2 24pt (was h4 16pt),
landscape h2 26pt; BoL # ticker is 13/14pt instead of body size.
2. **Shipper info missing** — root cause: `_fp_build_delivery_vals`
was creating deliveries without `company_id`, so the BoL's
`<span t-field="doc.company_id.name"/>` rendered empty. Two fixes:
- Hook now sets `company_id = mo.company_id.id or env.company.id`.
- Template falls back defensively to `env.company` when
`doc.company_id` is empty (covers any legacy delivery that
somehow slips through without it).
- Backfilled 14 existing deliveries via SQL on entech.
3. **Uniform header backgrounds** — replaced mixed `info-header`
(gray) + default-th (brand black) headers with a single
`fp-header-primary` (brand black) across all sub-tables for a
consistent look.
4. **Cargo description alignment + missing column** — added a QTY
column (matches landscape variant), pulled from the linked MO
via job_ref → mrp.production.product_qty. Added `.fp-cell-mid`
utility class with `vertical-align: middle !important;` and
applied it to every cargo + info cell so values sit centred
instead of jammed against the top border.
5. **Signature box too short** — bumped `.sig-box` from 70 → 110 px
(portrait) / 130 px (landscape), `.sig-line` from 28 → 60/70 px,
added flex layout so the label sits at the bottom and signers
have a real space to write in. Lives in the shared
`report_base_styles.xml` so EVERY FP template benefits, not just
the BoL.
Verified: BoL portrait renders cleanly at 140 KB with full shipper
block + uniform headers + middle-aligned cargo cells.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
"name": "Fusion Plating — MRP Bridge",
|
||||
'version': '19.0.6.0.0',
|
||||
'version': '19.0.6.1.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Bridge Fusion Plating facilities, baths and tanks to Odoo MRP work orders.',
|
||||
'description': """
|
||||
|
||||
@@ -674,6 +674,7 @@ class MrpProduction(models.Model):
|
||||
], order='id', limit=1)
|
||||
|
||||
return {
|
||||
'company_id': mo.company_id.id or self.env.company.id,
|
||||
'partner_id': job.partner_id.id,
|
||||
'job_ref': job.name,
|
||||
'source_facility_id': (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
{
|
||||
'name': 'Fusion Plating — Reports',
|
||||
'version': '19.0.4.1.0',
|
||||
'version': '19.0.4.2.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
||||
'depends': [
|
||||
|
||||
@@ -46,9 +46,10 @@
|
||||
.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; min-height: 70px; }
|
||||
.fp-report .sig-line { border-bottom: 1px solid #000; min-height: 28px; }
|
||||
.fp-report .sig-box { border: 1px solid #000; padding: 14px 12px 8px 12px; min-height: 110px; display: flex; flex-direction: column; justify-content: flex-end; }
|
||||
.fp-report .sig-line { border-bottom: 1px solid #000; min-height: 60px; }
|
||||
.fp-report .small-muted { font-size: 8pt; color: #666; }
|
||||
.fp-report .fp-cell-mid { vertical-align: middle !important; }
|
||||
</style>
|
||||
</template>
|
||||
|
||||
@@ -82,9 +83,10 @@
|
||||
.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: 12px; min-height: 70px; }
|
||||
.fp-landscape .sig-line { border-bottom: 1px solid #000; min-height: 28px; }
|
||||
.fp-landscape .sig-box { border: 1px solid #000; padding: 14px 12px 8px 12px; min-height: 130px; display: flex; flex-direction: column; justify-content: flex-end; }
|
||||
.fp-landscape .sig-line { border-bottom: 1px solid #000; min-height: 70px; }
|
||||
.fp-landscape .small-muted { font-size: 9pt; color: #666; }
|
||||
.fp-landscape .fp-cell-mid { vertical-align: middle !important; }
|
||||
</style>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -19,10 +19,14 @@
|
||||
<div class="fp-report">
|
||||
<div class="page">
|
||||
|
||||
<h4 class="text-center" style="text-align: center;">
|
||||
<!-- Resolve shipper company defensively — fall back to env.company
|
||||
when delivery.company_id is missing on legacy records. -->
|
||||
<t t-set="ship_co" t-value="doc.company_id or env.company"/>
|
||||
|
||||
<h2 class="text-center" style="text-align: center; font-size: 24pt; margin: 0 0 6px 0;">
|
||||
BILL OF LADING
|
||||
</h4>
|
||||
<div class="text-center" style="text-align: center; margin-bottom: 10px;">
|
||||
</h2>
|
||||
<div class="text-center" style="text-align: center; margin-bottom: 14px; font-size: 13pt;">
|
||||
<strong>BoL #: <span t-field="doc.name"/></strong>
|
||||
</div>
|
||||
|
||||
@@ -30,21 +34,21 @@
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%;">SHIPPER</th>
|
||||
<th style="width: 50%;">CONSIGNEE</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">SHIPPER</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">CONSIGNEE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="height: 90px;">
|
||||
<strong><span t-field="doc.company_id.name"/></strong><br/>
|
||||
<td style="height: 110px;">
|
||||
<strong><span t-esc="ship_co.name"/></strong><br/>
|
||||
<t t-if="doc.source_facility_id">
|
||||
<em t-field="doc.source_facility_id.name"/><br/>
|
||||
</t>
|
||||
<div t-field="doc.company_id.partner_id"
|
||||
<div t-field="ship_co.partner_id"
|
||||
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
|
||||
</td>
|
||||
<td style="height: 90px;">
|
||||
<td style="height: 110px;">
|
||||
<strong><span t-field="doc.partner_id.name"/></strong><br/>
|
||||
<t t-if="doc.delivery_address_id">
|
||||
<div t-field="doc.delivery_address_id"
|
||||
@@ -69,21 +73,21 @@
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="info-header" style="width: 33%;">SHIP DATE</th>
|
||||
<th class="info-header" style="width: 33%;">DRIVER</th>
|
||||
<th class="info-header" style="width: 34%;">VEHICLE</th>
|
||||
<th class="fp-header-primary" style="width: 33%;">SHIP DATE</th>
|
||||
<th class="fp-header-primary" style="width: 33%;">DRIVER</th>
|
||||
<th class="fp-header-primary" style="width: 34%;">VEHICLE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-center"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
|
||||
<td class="text-center fp-cell-mid">
|
||||
<t t-if="doc.assigned_driver_id">
|
||||
<span t-field="doc.assigned_driver_id.name"/>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid">
|
||||
<t t-if="doc.vehicle_id">
|
||||
<span t-field="doc.vehicle_id"/>
|
||||
</t>
|
||||
@@ -97,14 +101,14 @@
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="info-header" style="width: 50%;">JOB REFERENCE</th>
|
||||
<th class="info-header" style="width: 50%;">DANGEROUS GOODS (TDG)</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">JOB REFERENCE</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">DANGEROUS GOODS (TDG)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-center"><span t-esc="doc.job_ref or '-'"/></td>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid"><span t-esc="doc.job_ref or '-'"/></td>
|
||||
<td class="text-center fp-cell-mid">
|
||||
<span t-if="doc.tdg_required" class="status-warning">TDG REQUIRED</span>
|
||||
<span t-else="" class="status-ok">No TDG</span>
|
||||
</td>
|
||||
@@ -112,30 +116,35 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Cargo description -->
|
||||
<!-- Cargo description — added QTY column to match landscape -->
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" class="fp-header-primary">CARGO DESCRIPTION</th>
|
||||
<th colspan="5" class="fp-header-primary">CARGO DESCRIPTION</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 12%;">PACKAGES</th>
|
||||
<th class="text-start" style="width: 58%;">DESCRIPTION OF GOODS</th>
|
||||
<th style="width: 15%;">WEIGHT</th>
|
||||
<th style="width: 15%;">CLASS</th>
|
||||
<th class="fp-header-primary" style="width: 12%;">PACKAGES</th>
|
||||
<th class="fp-header-primary text-start" style="width: 48%;">DESCRIPTION OF GOODS</th>
|
||||
<th class="fp-header-primary" style="width: 12%;">QTY</th>
|
||||
<th class="fp-header-primary" style="width: 14%;">WEIGHT</th>
|
||||
<th class="fp-header-primary" style="width: 14%;">CLASS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-center">1</td>
|
||||
<td>
|
||||
<td class="text-center fp-cell-mid">1</td>
|
||||
<td class="fp-cell-mid">
|
||||
Plated parts — Job <span t-esc="doc.job_ref or doc.name"/>
|
||||
<t t-if="doc.notes">
|
||||
<br/><span t-field="doc.notes"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-center">—</td>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid">
|
||||
<t t-set="_mo" t-value="env['mrp.production'].sudo().search([('name', '=', doc.job_ref)], limit=1) if doc.job_ref else False"/>
|
||||
<span t-esc="int(_mo.product_qty) if _mo else '—'"/>
|
||||
</td>
|
||||
<td class="text-center fp-cell-mid">—</td>
|
||||
<td class="text-center fp-cell-mid">
|
||||
<span t-if="doc.tdg_required">TDG</span>
|
||||
<span t-else="">NON-HAZ</span>
|
||||
</td>
|
||||
@@ -147,17 +156,17 @@
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="info-header" style="width: 50%;">CoC ATTACHED</th>
|
||||
<th class="info-header" style="width: 50%;">PACKING LIST</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">CoC ATTACHED</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">PACKING LIST</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid">
|
||||
<span t-if="doc.coc_attachment_id" class="status-ok">✓ Attached</span>
|
||||
<span t-else="">—</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<td class="text-center fp-cell-mid">
|
||||
<span t-if="doc.packing_list_attachment_id" class="status-ok">✓ Attached</span>
|
||||
<span t-else="">—</span>
|
||||
</td>
|
||||
@@ -212,8 +221,10 @@
|
||||
<div class="fp-landscape">
|
||||
<div class="page">
|
||||
|
||||
<h2 style="text-align: center;">BILL OF LADING</h2>
|
||||
<div class="text-center" style="text-align: center; margin-bottom: 10px;">
|
||||
<t t-set="ship_co" t-value="doc.company_id or env.company"/>
|
||||
|
||||
<h2 style="text-align: center; font-size: 26pt; margin: 0 0 6px 0;">BILL OF LADING</h2>
|
||||
<div class="text-center" style="text-align: center; margin-bottom: 14px; font-size: 14pt;">
|
||||
<strong>BoL #: <span t-field="doc.name"/></strong>
|
||||
</div>
|
||||
|
||||
@@ -221,21 +232,21 @@
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%;">SHIPPER</th>
|
||||
<th style="width: 50%;">CONSIGNEE</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">SHIPPER</th>
|
||||
<th class="fp-header-primary" style="width: 50%;">CONSIGNEE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="height: 100px; font-size: 12pt;">
|
||||
<strong><span t-field="doc.company_id.name"/></strong><br/>
|
||||
<td style="height: 120px; font-size: 12pt;">
|
||||
<strong><span t-esc="ship_co.name"/></strong><br/>
|
||||
<t t-if="doc.source_facility_id">
|
||||
<em t-field="doc.source_facility_id.name"/><br/>
|
||||
</t>
|
||||
<div t-field="doc.company_id.partner_id"
|
||||
<div t-field="ship_co.partner_id"
|
||||
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
|
||||
</td>
|
||||
<td style="height: 100px; font-size: 12pt;">
|
||||
<td style="height: 120px; font-size: 12pt;">
|
||||
<strong><span t-field="doc.partner_id.name"/></strong><br/>
|
||||
<t t-if="doc.delivery_address_id">
|
||||
<div t-field="doc.delivery_address_id"
|
||||
|
||||
15
fusion_plating/scripts/fp_bol_inspect.py
Normal file
15
fusion_plating/scripts/fp_bol_inspect.py
Normal file
@@ -0,0 +1,15 @@
|
||||
env = env # noqa
|
||||
import re
|
||||
report = env.ref('fusion_plating_reports.action_report_fp_bol_portrait')
|
||||
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||
html, _ = report.with_context(force_report_rendering=True
|
||||
)._render_qweb_html(report.report_name, [dlv.id])
|
||||
out = html.decode() if isinstance(html, bytes) else str(html)
|
||||
# Pull just the SHIPPER td
|
||||
m = re.search(r'>SHIPPER<.*?</thead>(.*?)</table>', out, re.S)
|
||||
if m:
|
||||
print('--- SHIPPER/CONSIGNEE table body ---')
|
||||
print(m.group(1)[:2500])
|
||||
else:
|
||||
print('SHIPPER section not found, dumping first 2000 chars:')
|
||||
print(out[:2000])
|
||||
19
fusion_plating/scripts/fp_company_check.py
Normal file
19
fusion_plating/scripts/fp_company_check.py
Normal file
@@ -0,0 +1,19 @@
|
||||
env = env # noqa
|
||||
co = env['res.company'].search([], limit=1)
|
||||
p = co.partner_id
|
||||
print('company:', co.name)
|
||||
print(' partner_id:', p.id, p.name)
|
||||
print(' street:', repr(p.street))
|
||||
print(' street2:', repr(p.street2))
|
||||
print(' city:', repr(p.city), 'zip:', repr(p.zip))
|
||||
print(' state:', p.state_id.name if p.state_id else None)
|
||||
print(' country:', p.country_id.name if p.country_id else None)
|
||||
print(' phone:', repr(p.phone), 'email:', repr(p.email))
|
||||
print()
|
||||
# Also check if delivery has a source_facility_id with address
|
||||
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||
if dlv.source_facility_id:
|
||||
f = dlv.source_facility_id
|
||||
print('facility:', f.name)
|
||||
print(' address:', getattr(f, 'address', None) or getattr(f, 'street', None) or '(no address field)')
|
||||
print(' fields:', [k for k in f._fields if 'addr' in k or 'street' in k or 'city' in k])
|
||||
7
fusion_plating/scripts/fp_dlv_check.py
Normal file
7
fusion_plating/scripts/fp_dlv_check.py
Normal file
@@ -0,0 +1,7 @@
|
||||
env = env # noqa
|
||||
dlv = env['fusion.plating.delivery'].search([], order='id desc', limit=1)
|
||||
print('delivery:', dlv.name)
|
||||
print(' company_id:', dlv.company_id, '/', dlv.company_id.name if dlv.company_id else None)
|
||||
print(' source_facility_id:', dlv.source_facility_id, '/', dlv.source_facility_id.name if dlv.source_facility_id else None)
|
||||
print(' has company_id field?', 'company_id' in dlv._fields)
|
||||
print(' field def:', dlv._fields.get('company_id'))
|
||||
Reference in New Issue
Block a user