Sub-solution workbook pipeline on layout 5, fix element order in both importers

- New, deliberately separate pipeline for sub-solutions (markets and
  solutions must never break each other): generator
  vitec:generate-subsolution-workbooks (list from the page tree below
  /solutions, 39 workbooks, every sub-solution matched to its record),
  SubsolutionWorkbookImporter + CLI vitec:import-subsolution-workbooks,
  and a "Sub-Solutions" tab in the VITEC Import module with its own
  inbox migrations/subsolutions_xlsx_in/
- Targets the existing template 5 "Solution Detail" without changing
  it: hero element in colPos 1, the remaining sections in fixed order in
  colPos 0, found again by CType on re-import; existing empty sub-solution
  pages are switched to layout 5, missing ones created hidden;
  detail_page set on the solution record
- Workbook per the Figma "07 Solution Detail page": hero without video
  teaser, intro with headline and without learn-more/portfolio, extended
  icon list, own page-layout reference image
- Shared content elements: market hero/intro/insights relabelled
  "Market/Solution", market intro gains the optional intro_headline
  field (new column, requires extension:setup)
- Fix: DataHandler puts every NEW record with a positive pid at the top
  of its column, so pages built in one run came out reversed. Both
  importers now track the page order per colPos and write
  tt_content.sorting explicitly after the DataHandler run; re-applying
  repairs pages imported earlier (verified on the control-room
  sub-solution and sub-market page 443)
- migrations/fill_test_subsolution.php: realistic test content from
  vitec.com copy and images for the control-room sub-solution
- Docs section 5z
This commit is contained in:
orasche committed 2026-10-07 15:39:18 +02:00
1 parent 3d5459b8d2
commit 8e67f5db23
84 files changed
+1772 -35

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

@@ -29,6 +29,9 @@
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.subsolutions')}">Sub-Solutions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
</li>
@@ -26,6 +26,9 @@
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.subsolutions')}">Sub-Solutions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
</li>
@@ -21,6 +21,9 @@
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.subsolutions')}">Sub-Solutions</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
</li>
@@ -21,6 +21,9 @@
<li class="nav-item">
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.subsolutions')}">Sub-Solutions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
</li>
@@ -0,0 +1,144 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:layout name="Backend/Default" />
<f:section name="main">
<div class="module-docheader-bar module-docheader-bar-navigation">
<div class="module-docheader-bar-column-left">
<h2 class="t3js-title-inlineedit">VITEC Import &mdash; Sub-Solutions</h2>
</div>
</div>
<ul class="nav nav-tabs" style="margin-bottom:1rem;">
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport')}">CSV Import</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.products')}">Products</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.subsolutions')}">Sub-Solutions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
</li>
</ul>
<!-- Upload: drops Joe's filled workbooks into the shared inbox directory -->
<div class="card" style="margin-bottom:1rem;">
<div class="card-header"><strong>Upload filled workbooks</strong></div>
<div class="card-body">
<form action="{f:be.uri(route: 'web_vitecimport.subsolution_upload')}" method="post" enctype="multipart/form-data" class="row row-cols-auto align-items-center g-2">
<div class="col">
<input type="file" name="workbooks[]" accept=".xlsx" multiple="multiple" class="form-control" required="required" />
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Upload</button>
</div>
<div class="col form-text">
Multiple files allowed. A file with the same name replaces the stored one ({inbox}/).
</div>
</form>
</div>
</div>
<!-- Inbox + dry-run / apply -->
<div class="card" style="margin-bottom:1rem;">
<div class="card-header"><strong>Workbooks in the inbox</strong></div>
<div class="card-body">
<f:if condition="{workbooks}">
<f:then>
<form action="{f:be.uri(route: 'web_vitecimport.subsolution_process')}" method="post">
<table class="table table-striped table-sm" style="max-width:900px;">
<thead>
<tr>
<th style="width:30px;"></th>
<th>File</th>
<th>Size</th>
<th>Uploaded / changed</th>
</tr>
</thead>
<tbody>
<f:for each="{workbooks}" as="w">
<tr>
<td><input type="checkbox" name="files[]" value="{w.name}" checked="checked" /></td>
<td>{w.name}</td>
<td>{w.size}</td>
<td>{w.modified}</td>
</tr>
</f:for>
</tbody>
</table>
<button type="submit" name="op" value="dry" class="btn btn-default">Dry run (no changes)</button>
<button type="submit" name="op" value="apply" class="btn btn-warning">Apply &mdash; write pages</button>
<span class="form-text" style="display:inline-block;margin-left:.75rem;">
Always dry-run first. Apply updates existing elements in place, creates missing ones, never deletes.
New pages are created HIDDEN for editorial review.
</span>
</form>
</f:then>
<f:else>
<p>No workbooks yet &mdash; upload Joe's filled .xlsx files above.</p>
</f:else>
</f:if>
</div>
</div>
<!-- Results -->
<f:if condition="{hasResults}">
<f:for each="{results}" as="r">
<div class="card" style="margin-bottom:1rem;">
<div class="card-header">
<strong>{r.file}</strong>
<f:if condition="{r.error}">
<f:then>
<span class="vitec-badge vitec-badge-danger">ERROR</span>
</f:then>
<f:else>
&mdash; {r.group} / {r.subsolution}
<f:if condition="{r.applied}">
<f:then><span class="vitec-badge vitec-badge-ok">written</span></f:then>
<f:else><span class="vitec-badge vitec-badge-info">dry run</span></f:else>
</f:if>
</f:else>
</f:if>
</div>
<div class="card-body">
<f:if condition="{r.error}">
<f:then>
<p class="text-danger">{r.error}</p>
</f:then>
<f:else>
<p>
Page:
<f:if condition="{r.pageUid}">
<f:then>
<a href="{f:be.uri(route: 'web_layout', parameters: {id: r.pageUid})}">#{r.pageUid} &mdash; open in page module</a>
</f:then>
<f:else>
NEW (would be created hidden)
</f:else>
</f:if>
&middot; {r.elements} element(s) touched
</p>
<f:if condition="{r.report}">
<ul style="margin-bottom:0;">
<f:for each="{r.report}" as="line">
<li>{line}</li>
</f:for>
</ul>
</f:if>
</f:else>
</f:if>
</div>
</div>
</f:for>
</f:if>
</f:section>
</html>