| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <?php
- /**
- * soil-report-pdf.php
- *
- * Print / PDF-export version of a completed soil analysis report.
- * Displays the saved AI-generated sections from the reports table alongside
- * the element requirement cards and five-year balancing plan.
- *
- * Access: ?rid=<soil_records.id>&rand=<soil_records.rand>
- */
- require_once __DIR__ . '/../../../config/database.php';
- require_once __DIR__ . '/../../../lib/auth.php';
- require_once __DIR__ . '/../../../lib/soil_calculations.php';
- if (session_status() === PHP_SESSION_NONE) {
- session_start();
- }
- requireLogin();
- $pdo = getDBConnection();
- $userId = getCurrentUserId();
- $recordId = (int) ($_GET['rid'] ?? 0);
- $randId = trim( $_GET['rand'] ?? '');
- $row = null;
- $spec = [];
- $savedComments = [];
- $today = date('jS F Y');
- if ($recordId > 0 && $randId !== '') {
- $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1');
- $stmt->execute([$recordId, $randId]);
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- if ($row) {
- // Load spec ranges for this soil type
- if (!empty($row['soil_type'])) {
- $stmtSpec = $pdo->prepare('SELECT * FROM soil_specifications WHERE soil_type = ? LIMIT 1');
- $stmtSpec->execute([$row['soil_type']]);
- $spec = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
- }
- // Load saved report comments (JSON blob)
- $stmtRpt = $pdo->prepare(
- 'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1'
- );
- $stmtRpt->execute([$recordId, $userId]);
- $savedRow = $stmtRpt->fetchColumn();
- if ($savedRow) {
- $decoded = json_decode($savedRow, true);
- if (is_array($decoded)) {
- $savedComments = $decoded;
- }
- }
- }
- }
- // ── Five-year plan ────────────────────────────────────────────────────────────
- $planElements = [
- ['Calcium', 'BS_ca_ppm', 'ca_ppm_min', 'ca_ppm_max', 'kg/ha'],
- ['Magnesium', 'BS_mg_ppm', 'mg_ppm_min', 'mg_ppm_max', 'kg/ha'],
- ['Potassium', 'BS_k_ppm', 'k_ppm_min', 'k_ppm_max', 'kg/ha'],
- ['Sodium', 'BS_na_ppm', 'na_ppm_min', 'na_ppm_max', 'kg/ha'],
- ['Phosphate', 'p_colwell', '', '', 'kg/ha'],
- ['Sulfur', 's_morgan', '', '', 'kg/ha'],
- ['Boron', 'b_cacl2', '', '', 'kg/ha'],
- ['Manganese', 'mn_dtpa', '', '', 'kg/ha'],
- ['Zinc', 'zn_dtpa', '', '', 'kg/ha'],
- ['Copper', 'cu_dtpa', '', '', 'kg/ha'],
- ];
- $acHa = 2.4710559990832394739;
- function calcDeficitPdf(array $row, array $spec, string $col, string $minCol, string $maxCol): float
- {
- global $acHa;
- $value = (float)($row[$col] ?? 0);
- $maxVal = $maxCol !== '' ? (float)($row[$maxCol] ?? 0) : (float)($spec[$col] ?? 0);
- $deficit = ($maxVal - $value) * $acHa;
- return $deficit > 0 ? round($deficit, 2) : 0.0;
- }
- $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');
- // Format saved text: preserve newlines as paragraphs
- function formatReportText(string $text): string
- {
- if (trim($text) === '') return '<p class="text-muted fst-italic">No content saved.</p>';
- $paragraphs = preg_split('/\n{2,}/', trim($text));
- $out = '';
- foreach ($paragraphs as $para) {
- $para = trim($para);
- if ($para === '') continue;
- $out .= '<p>' . nl2br(htmlspecialchars($para, ENT_QUOTES, 'UTF-8')) . '</p>';
- }
- return $out ?: '<p class="text-muted fst-italic">No content saved.</p>';
- }
- ?>
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>Soil Analysis Report | Crop Monitor</title>
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" rel="stylesheet" crossorigin="anonymous">
- <link href="/client-assets/css/dashboard-2021.css" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
- <style>
- @media print {
- @page { size: A4 portrait; margin: 12mm; }
- .d-print-none { display: none !important; }
- .page-break { page-break-before: always; }
- body { font-size: 11px; }
- .report-section p { font-size: 11px; }
- }
- .report-section p {
- margin-bottom: 0.6rem;
- line-height: 1.6;
- }
- .section-header {
- background: #212529;
- color: #fff;
- padding: 6px 12px;
- font-weight: 600;
- margin-bottom: 0;
- }
- .section-body {
- border: 1px solid #dee2e6;
- border-top: 0;
- padding: 14px 16px;
- margin-bottom: 1.2rem;
- }
- .title-table td, .title-table th { padding: 2px 8px; }
- .element-required-module .col { padding: 0 6px; }
- </style>
- </head>
- <body>
- <div class="container" id="pdf-content">
- <?php if (!$row): ?>
- <div class="alert alert-danger mt-4">Record not found or access denied.</div>
- <?php else: ?>
- <!-- ── Header ──────────────────────────────────────────────────────────── -->
- <div class="row align-items-center mb-3 mt-3">
- <div class="col-3">
- <img class="img-fluid" src="/client-assets/images/crop-monitor.png"
- alt="Crop Monitor" style="max-height:55px;">
- </div>
- <div class="col-9 text-end">
- <div class="fw-bold h5 mb-0">Soil Analysis Report</div>
- <div class="text-muted small"><?= $h($today) ?></div>
- </div>
- </div>
- <table class="title-table w-100 mb-3 small">
- <tbody>
- <tr>
- <td class="text-end fw-bold text-nowrap">CLIENT:</td>
- <td><?= $h($row['client_name']) ?></td>
- <td></td>
- <td class="text-end fw-bold text-nowrap">SAMPLE ID:</td>
- <td><?= $h($row['site_id']) ?></td>
- </tr>
- <tr>
- <td class="text-end fw-bold text-nowrap">ADDRESS:</td>
- <td><?= $h($row['site_address']) ?></td>
- <td></td>
- <td class="text-end fw-bold text-nowrap">DATE SAMPLED:</td>
- <td><?= $h($row['date_sampled']) ?></td>
- </tr>
- <tr>
- <td></td>
- <td><?= $h($row['state_postcode']) ?></td>
- <td></td>
- <td class="text-end fw-bold text-nowrap">LAB NUMBER:</td>
- <td><?= $h($row['lab_no']) ?></td>
- </tr>
- <tr>
- <td></td>
- <td><?= $h($row['email']) ?></td>
- <td></td>
- <td class="text-end fw-bold text-nowrap">CROP:</td>
- <td><?= $h($row['sample_id']) ?></td>
- </tr>
- <tr>
- <td></td>
- <td></td>
- <td></td>
- <td class="text-end fw-bold text-nowrap">SOIL TYPE:</td>
- <td><?= $h($row['soil_type']) ?></td>
- </tr>
- </tbody>
- </table>
- <!-- ── Download / Back buttons ─────────────────────────────────────────── -->
- <div class="d-print-none mb-3 d-flex gap-2">
- <a href="/dashboard/crop-analysis/soil-test-data/soil-report.php?rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>"
- class="btn btn-outline-secondary btn-sm">
- ← Back to Report
- </a>
- <button class="btn btn-success btn-sm" id="btn-download">
- <i class="fas fa-download me-1"></i>Download PDF
- </button>
- <button class="btn btn-outline-dark btn-sm" onclick="window.print()">
- <i class="fas fa-print me-1"></i>Print
- </button>
- </div>
- <hr>
- <!-- ── 1. Element requirements ─────────────────────────────────────────── -->
- <div class="section-header">
- Total kilograms per hectare of each element needed to balance soil
- </div>
- <div class="section-body">
- <div class="element-required-module">
- <div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-6 g-2">
- <?php
- echo soilAnalysisReportCalcs('Ca', 'BS_ca_ppm', 'ca_ppm_min', 'ca_ppm_max', 'Calcium', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Mg', 'BS_mg_ppm', 'mg_ppm_min', 'mg_ppm_max', 'Magnesium', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('K', 'BS_k_ppm', 'k_ppm_min', 'k_ppm_max', 'Potassium', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Na', 'BS_na_ppm', 'na_ppm_min', 'na_ppm_max', 'Sodium', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('P', 'p_colwell', '', '', 'Phosphate', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('S', 's_morgan', '', '', 'Sulfur', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Mn', 'mn_dtpa', '', '', 'Manganese', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Fe', 'fe_dtpa', '', '', 'Iron', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Zn', 'zn_dtpa', '', '', 'Zinc', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('Cu', 'cu_dtpa', '', '', 'Copper', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('AmN', 'NH3_N', '', '', 'Amm. Nitrogen', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('B', 'b_cacl2', '', '', 'Boron', 'kg', 'col', $recordId, $randId);
- echo soilAnalysisReportCalcs('NN', 'NO3_N', '', '', 'Nit. Nitrogen', 'kg', 'col', $recordId, $randId);
- ?>
- </div>
- </div>
- </div>
- <!-- ── 2. Five-year balancing plan ─────────────────────────────────────── -->
- <div class="section-header">
- Ideal Soil Balancing Program — 5-Year Plan (kg/ha per year)
- </div>
- <div class="section-body p-0">
- <table class="table table-sm table-bordered mb-0">
- <thead class="table-light">
- <tr>
- <th>Element</th>
- <th class="text-center">Total Deficit</th>
- <?php for ($y = 1; $y <= 5; $y++): ?>
- <th class="text-center">Year <?= $y ?></th>
- <?php endfor; ?>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($planElements as [$label, $col, $minCol, $maxCol, $unit]):
- $total = calcDeficitPdf($row, $spec, $col, $minCol, $maxCol);
- $perYear = $total > 0 ? round($total / 5, 2) : 0;
- ?>
- <tr>
- <td><?= $h($label) ?></td>
- <td class="text-center <?= $total > 0 ? 'text-danger fw-semibold' : 'text-success' ?>">
- <?= $total > 0 ? $total . ' ' . $unit : '✓ Adequate' ?>
- </td>
- <?php for ($y = 1; $y <= 5; $y++): ?>
- <td class="text-center">
- <?= $perYear > 0 ? $perYear . ' ' . $unit : '—' ?>
- </td>
- <?php endfor; ?>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
- </div>
- <!-- ── 3. Overview ─────────────────────────────────────────────────────── -->
- <div class="section-header">Overview</div>
- <div class="section-body report-section">
- <?= formatReportText($savedComments['overview'] ?? '') ?>
- </div>
- <!-- ── 4. AI Soil Interpretation ───────────────────────────────────────── -->
- <div class="section-header">AI Soil Interpretation</div>
- <div class="section-body report-section">
- <?= formatReportText($savedComments['ai_interpretation'] ?? '') ?>
- </div>
- <!-- ── 5. Foliar Program ────────────────────────────────────────────────── -->
- <div class="section-header">
- <?= $h($savedComments['header1'] ?? 'Foliar Program') ?>
- </div>
- <div class="section-body report-section">
- <?= formatReportText($savedComments['foliar_details'] ?? '') ?>
- </div>
- <!-- ── 6. Microbial Program ─────────────────────────────────────────────── -->
- <div class="section-header">Microbial Program</div>
- <div class="section-body report-section">
- <?= formatReportText($savedComments['microbe_program'] ?? '') ?>
- </div>
- <!-- ── Disclaimer ──────────────────────────────────────────────────────── -->
- <div class="mt-3 pt-3 border-top">
- <p class="text-muted" style="font-size:0.7rem;">
- Any recommendations provided by Crop Monitor are advice only. We are not paid consultants
- and accept no responsibility for any of our suggestions. No control can be exercised over
- storage, handling, mixing, application, or use, or over weather, plant or soil conditions
- before, during or after application — all of which may affect the performance of our
- program. No responsibility for, or liability for, any failure in performance, losses,
- damage or injuries consequential or otherwise arising from such storage, mixing,
- application or use will be accepted under any circumstances whatsoever. The buyer assumes
- all responsibility for the use of any of our products.
- </p>
- </div>
- <?php endif; ?>
- </div><!-- /container -->
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
- <script>
- document.getElementById('btn-download')?.addEventListener('click', function () {
- var element = document.getElementById('pdf-content');
- var opt = {
- margin: 10,
- filename: 'soil-report-<?= $h($row['lab_no'] ?? $recordId) ?>.pdf',
- image: { type: 'jpeg', quality: 1.0 },
- html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024, useCORS: true },
- jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
- };
- html2pdf().from(element).set(opt).save();
- });
- </script>
- </body>
- </html>
|