Build these in the order shown. Step 1 is foundational — nothing downstream works until the component IDs exist.
Next free ID is 158 (Miscellaneous = 157 is the current highest).
| ID | Config key | Display name | Unit | Progress model |
|---|---|---|---|---|
| 158 | PylonLower | Pylon (Lower) | Mtr | Height-based lifts |
| 159 | PylonUpper | Pylon (Upper / Mast) | Mtr | Height-based lifts |
| 160 | PylonCrossBeam | Pylon Cross Beam | Nos | Binary (H / diamond / delta only) |
| 161 | StayCable | Stay Cable | Nos | 4-stage, per cable |
| 162 | DeckSegment | Deck Segment CONDITIONAL | Nos | Cast → Stressed (per segment) |
The new per-span erection_method field on the Super Structure form (page 1, change 4) decides this. A precast-launched span keeps the existing Girder cast → stress → launch model and needs nothing new. A form-traveller or precast-segment span has no discrete girders to count — the deck grows segment by segment as each stay cable is stressed — so it needs Deck Segment instead.
This is the CR's biggest cost fork, and making it a per-span field is what keeps it controllable. If Mindhola's four approach spans are precast-launched and only the three 150 m main spans are cantilevered, the segment model is confined to three spans rather than the whole bridge.
Same shape as 18_1 / 18_2 / 18_3 for Girder cast/stress/launch. This pattern is already handled generically — BridgeComponentTargetServices splits on str_contains($componentId, '_'), and weightage keys are JSON strings. So cable stages need no schema change at all.
config/constant/component.php · the matching function in ComponentSectionHelper.php · database/seeders/BridgeComponentSeeder.php. The config IDs are just auto-increment positions from the seeder, so they must stay in sync.BridgesInventoryServices::updateBridgeInventory() needs a 5th bridge_pier_type branch. This is the only place that decides which components a bridge gets.
| Pylon Lower / Upper | 4 |
| Pylon Cross Beam | 8 |
| Stay Cable | 164 |
Pier Wall quantity is currently hardcoded no_of_span - 1. On Mindhola that gives 8 pier walls — but only P1, P6, P7, P8 are actually piers.
Same for Pier Cap. Get this wrong and physical progress is permanently understated on every cable-stayed bridge.
Both mirror something that already exists. Neither is built from scratch.
| Pylon (Lower & Upper) | Stay Cable | |
|---|---|---|
| Progress model | Height-based lifts — same as Pier Wall | Item + stage — same as Girder |
| Location key | p_2 | p_2 |
| Item number | — | us_a1_cable_1 |
| Stage column | — | reuse girder_step (int 1–4) |
| Closest existing screen | progressCreate/single_pier.blade.php | Girder 3-step form |
Lower and Upper pylon are two separate components at the same location p_2. That's new for BMS, but the schema already allows it.
plane → side → index. Lock it before any data entry. Renaming later is expensive — see how much of component.php exists purely for LHS/RHS variants.
Stay cables are routinely force-adjusted as adjacent deck advances. Stage 4 must not hard-lock the cable — allow repeat 161_4 entries with a remark.
BridgeComponentProgressRepository::allCompletedItems() reshapes progress rows into five different shapes depending on component class. Two additions needed:
{'p_2': [3.5, 2.0, …]} — an array of incremental lifts.{'p_2': {'us_a1_cable_1': [1,2,3]}}.Note: height entries are incremental pours, summed — not cumulative snapshots. Match the existing drawPier() math exactly.
bridge_component_weights.weights is a JSONB blob keyed by component identifier string — so 161_1…161_4 slot straight in. No migration.
GirderAgeingReportService. It's the established bulk-query pattern and maps almost one-to-one: planned / installed / tensioned / stressed counts plus a per-cable stage timeline.dropdownLabel() against display-only config arrays — register pylon_shape there or it prints raw keys.The mobile app has no screen for lift-wise pours or cable-stage updates. This is new scope, not a tweak, and should be estimated alongside the web work rather than after it.
Assumption: web-only for phase 1 — needs client confirmation.
Stay cables are typically a distinct, high-value BOQ line — often imported and specialist-subcontracted, with supply billed separately from installation.
Ask the client whether "cable procured / on site" needs tracking as a milestone separate from "installed & stressed" before the BOQ-to-component mapping is built.
Both updateBridgeInventory() and syncApproachComponents() only ever updateOrCreate. If a bridge's config changes so a component stops applying, the stale row persists and the component keeps appearing.
A "Save as Draft" bridge has zero component plan rows — so no components at all. Anyone testing this feature will hit it and think it's broken.
The SVG dispatch is an @if/@elseif chain with no @else, in two places. The moment someone sets a bridge to cable_stayed before the partial exists, they get an empty box with no error. Add a fallback branch first — it's a 5-minute fix that saves a confusing bug report.