Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
/**
* Orchestrates the combined Print Schedules PDF (Epic 20.2). Given a validated mill/year context
* and a {@link PrintRequest}, it fills each SELECTED in-scope schedule section in the FIXED legacy
* order (5 → 6 → 7A → 7B → 9 → 11, the {@link ScheduleKey} declaration order), SKIPS any section
* with no data (BR-09 skip-empty), and exports the accumulated sections to ONE bookmarked PDF
* (BR-08) — one top-level bookmark per rendered schedule. When no selected content yields any data
* the result is all-empty and no PDF is produced: {@link ScheduleNotFoundException} (→ 404
* order (2 → 5 → 6 → 7A → 7B → 9 → 10 → 11, the {@link ScheduleKey} declaration order), SKIPS any
* section with no data (BR-09 skip-empty), and exports the accumulated sections to ONE bookmarked
* PDF (BR-08) — one top-level bookmark per rendered schedule. When no selected content yields any
* data the result is all-empty and no PDF is produced: {@link ScheduleNotFoundException} (→ 404
* ERR-005).
*
* <p>Selected-but-unimplemented schedules (1/2/3/8/10) and the mill-information-report option are
* <p>Selected-but-unimplemented schedules (1/3/8) and the mill-information-report option are
* accepted for forward-compatibility but produce no section yet — they are skipped-with-a-log
* (documented interim gap) until their story lands. Selection VALIDATION (ERR-002/003/004) is the
* controller's responsibility and runs before this.
Expand Down Expand Up @@ -176,6 +176,7 @@ private static Predicate<ScheduleKey> selectionOf(PrintRequest request) {
}
return key ->
switch (key) {
case SCHEDULE_2 -> request.schedule2();
case SCHEDULE_5 -> request.schedule5();
case SCHEDULE_6 -> request.schedule6();
case SCHEDULE_7A -> request.schedule7a();
Expand All @@ -194,7 +195,6 @@ private void logUnimplementedSelections(PrintRequest request) {
boolean anyUnimplemented =
request.allSchedules()
|| request.schedule1()
|| request.schedule2()
|| request.schedule3()
|| request.schedule4()
|| request.schedule8()
Expand All @@ -207,7 +207,7 @@ private void logUnimplementedSelections(PrintRequest request) {
// them.
log.debug(
"Print selection includes schedules/options not yet implemented in Epic 20 "
+ "(1/2/3/4/8 and/or the Mill Information report); those are skipped for now");
+ "(1/3/4/8 and/or the Mill Information report); those are skipped for now");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ca.bc.gov.nrs.ilcr.millcontext.ScheduleNotFoundException;
import ca.bc.gov.nrs.ilcr.schedule10.Schedule10Service;
import ca.bc.gov.nrs.ilcr.schedule11.Schedule11Service;
import ca.bc.gov.nrs.ilcr.schedule2.Schedule2Service;
import ca.bc.gov.nrs.ilcr.schedule5.Schedule5Service;
import ca.bc.gov.nrs.ilcr.schedule6.Schedule6Service;
import ca.bc.gov.nrs.ilcr.schedule7a.Schedule7aService;
Expand Down Expand Up @@ -60,6 +61,7 @@ public class ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportService.class);

private final DataSource dataSource;
private final Schedule2Service schedule2Service;
private final Schedule5Service schedule5Service;
private final Schedule6Service schedule6Service;
private final Schedule7aService schedule7aService;
Expand All @@ -81,6 +83,7 @@ public class ReportService {
* from — its own small pool, isolated from the {@code @Primary} transactional pool so a burst
* of report renders cannot starve ordinary schedule requests (its connections are read-only
* as a hint, not an enforced privilege)
* @param schedule2Service the Schedule 2 read (bean-datasource feed, Story 20.6)
* @param schedule5Service the Schedule 5 read (bean-datasource feed)
* @param schedule6Service the Schedule 6 read (bean-datasource feed)
* @param schedule7aService the Schedule 7A read (bean-datasource feed)
Expand All @@ -94,6 +97,7 @@ public class ReportService {
*/
public ReportService(
@Qualifier("reportingDataSource") DataSource dataSource,
Schedule2Service schedule2Service,
Schedule5Service schedule5Service,
Schedule6Service schedule6Service,
Schedule7aService schedule7aService,
Expand All @@ -103,6 +107,7 @@ public ReportService(
Schedule11Service schedule11Service,
ReportVirtualizerFactory virtualizerFactory) {
this.dataSource = dataSource;
this.schedule2Service = schedule2Service;
this.schedule5Service = schedule5Service;
this.schedule6Service = schedule6Service;
this.schedule7aService = schedule7aService;
Expand Down Expand Up @@ -183,6 +188,16 @@ public JasperPrint fillSection(
String bookmarkTitle,
JRSwapFileVirtualizer virtualizer) {
return switch (key) {
case SCHEDULE_2 ->
fillBean(
key,
millId,
year,
options,
millTitleBlock,
bookmarkTitle,
Schedule2SectionMapper.map(schedule2Service.getSchedule2(millId, year, false)),
virtualizer);
case SCHEDULE_5 ->
fillBean(
key,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ca.bc.gov.nrs.ilcr.reporting;

import ca.bc.gov.nrs.ilcr.schedule2.dto.CostBlock;
import ca.bc.gov.nrs.ilcr.schedule2.dto.Schedule2Response;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* Maps the Schedule 2 (Purchased/Private Log Costs and Sales) read document to the print section
* datasource. Unlike the per-record fan-out sections (Schedule 5's one-row-per-camp), Schedule 2 is
* a SINGLE aggregate document, so this emits exactly ONE row holding the seven fixed cost blocks —
* {@code purchasedLogCost}, {@code purchasedWoodOverhead}, {@code subtotal}, {@code lessLogSales},
* {@code netPurchased}, {@code totalCompanyLogging}, {@code totalAverage} — each as a
* volume/cost/$-per-m³ triple. Every derived figure (the computed subtotal / net / totals and every
* {@code perUnit}, plus the carried Schedule-1/3 terms) is already computed by {@code
* Schedule2Service}; this only formats: whole-dollar cost, fractional volume + $-per-m³ at two
* decimals, and {@code "-"} for null.
*
* <p>Skip-empty (BR-09) keys off the DATA, not a detail-row count: when every cost block is null
* (an unsaved Schedule 2 with no carried Schedule-3 figures) this returns {@code null} so the
* orchestrator omits the section.
*/
final class Schedule2SectionMapper {

private Schedule2SectionMapper() {}

static SectionData map(Schedule2Response response) {
if (response == null || isEmpty(response)) {
return null;
}
Map<String, Object> row = new LinkedHashMap<>();
putBlock(row, "purchasedLogCost", response.purchasedLogCost());
putBlock(row, "purchasedWoodOverhead", response.purchasedWoodOverhead());
putBlock(row, "subtotal", response.subtotal());
putBlock(row, "lessLogSales", response.lessLogSales());
putBlock(row, "netPurchased", response.netPurchased());
putBlock(row, "totalCompanyLogging", response.totalCompanyLogging());
putBlock(row, "totalAverage", response.totalAverage());
row.put("comments", SectionFormat.text(response.comments()));
return new SectionData(List.of(row), Map.of());
}

/** Emit a block's three cells ({@code <name>Vol/Cos/Cal}), pre-formatted with "-" for null. */
private static void putBlock(Map<String, Object> row, String name, CostBlock block) {
row.put(name + "Vol", SectionFormat.decimal(block == null ? null : block.volume()));
row.put(name + "Cos", SectionFormat.money(block == null ? null : block.cost()));
row.put(name + "Cal", SectionFormat.decimal(block == null ? null : block.perUnit()));
}

/** True when no cost block carries any figure — the skip-empty signal (BR-09). */
private static boolean isEmpty(Schedule2Response r) {
return allNull(r.purchasedLogCost())
&& allNull(r.purchasedWoodOverhead())
&& allNull(r.subtotal())
&& allNull(r.lessLogSales())
&& allNull(r.netPurchased())
&& allNull(r.totalCompanyLogging())
&& allNull(r.totalAverage());
}

private static boolean allNull(CostBlock block) {
return block == null
|| (block.volume() == null && block.cost() == null && block.perUnit() == null);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package ca.bc.gov.nrs.ilcr.reporting;

/**
* The schedules Epic 20.2 can render, each bound to its classpath template and its bookmark title.
* Declared in the FIXED legacy print order (5 → 6 → 7A → 7B → 9 → 10 → 11 within the 1..11
* The schedules Epic 20 can render, each bound to its classpath template and its bookmark title.
* Declared in the FIXED legacy print order (2 → 5 → 6 → 7A → 7B → 9 → 10 → 11 within the 1..11
* sequence), so iterating the enum yields the combined PDF's section order (BR-08) without a
* separate ordering table. Schedules 1/2/3/8 and the Mill Information report are out of scope (no
* separate ordering table. Schedule 2 sits before Schedule 5 in the legacy sequence, so its
* constant is declared FIRST. Schedules 1/3/8 and the Mill Information report are out of scope (no
* enum constant) — a selected-but-unimplemented schedule is skipped-with-a-log by the orchestrator.
*/
public enum ScheduleKey {
SCHEDULE_2("reports/schedule2.jrxml", "Schedule 2: Purchased and Private Log Costs and Sales"),
SCHEDULE_5("reports/schedule5.jrxml", "Schedule 5: Camp and Access Expenses"),
SCHEDULE_6("reports/schedule6.jrxml", "Schedule 6: Road Management Costs"),
SCHEDULE_7A("reports/schedule7a.jrxml", "Schedule 7A: Bridge Costs"),
Expand Down
Loading
Loading