Benjamin Harris 2 månader sedan
förälder
incheckning
e9f0a0de0c

BIN
books/05. Albrecht-on-Calcium.pdf


BIN
books/06. Albrecht-on-Pastures.pdf


+ 0 - 0
books/soil-albrecht-on-soil-balancing_compress.pdf → books/07. soil-albrecht-on-soil-balancing_compress.pdf


BIN
books/08. Soil-Fertility-Human-Animal-Health.pdf


BIN
books/Albrecht on Calcium.pdf


BIN
books/Albrecht-on-Calcium.epub


BIN
books/Albrecht-on-Pastures.epub


BIN
books/Conference-2016-Dr-Arden-Anderson.pdf


BIN
books/Soil-Fertility-Human-Animal-Health.epub


+ 239 - 145
dashboard/crop-analysis/soil-test-data/soil-report-pdf.php

@@ -2,14 +2,16 @@
 /**
  * soil-report-pdf.php
  *
- * Printable / PDF-export version of a soil analysis report.
- * NOTE: The [[!soilAnalysisReportCalcs?]] and [[!soilProgramCalcs?]] snippet
- * sections are pending full PHP migration. They currently render as blank.
+ * 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/csrf.php';
+require_once __DIR__ . '/../../../lib/soil_calculations.php';
 
 if (session_status() === PHP_SESSION_NONE) {
     session_start();
@@ -20,21 +22,82 @@ requireLogin();
 $pdo    = getDBConnection();
 $userId = getCurrentUserId();
 
-$recordId = (int)   ($_GET['rid']  ?? 0);
-$randId   = (float) ($_GET['rand'] ?? 0);
+$recordId = (int)  ($_GET['rid']  ?? 0);
+$randId   = trim(  $_GET['rand']  ?? '');
 
 $row  = null;
+$spec = [];
+$savedComments = [];
 $today = date('jS F Y');
 
-if ($recordId > 0) {
-    $stmt = $pdo->prepare(
-        'SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1'
-    );
+if ($recordId > 0 && $randId !== '') {
+    $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1');
     $stmt->execute([$recordId, $randId]);
-    $row = $stmt->fetch();
+    $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');
+$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">
@@ -42,154 +105,216 @@ $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Soil Analysis Report | Crop Monitor</title>
-    <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
     <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" crossorigin="anonymous" rel="stylesheet">
+    <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: 0.5cm; }
-            .d-print-none { display: none !important; }
+            @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;
         }
-        .title th, .title td { padding: 2px 6px; }
+        .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="content">
+<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: ?>
 
-    <div class="row mb-3">
-        <div class="col-md-3">
-            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+    <!-- ── 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 class="col-md-9"></div>
     </div>
 
-    <table class="title w-100 mb-3">
+    <table class="title-table w-100 mb-3 small">
         <tbody>
             <tr>
-                <td class="text-end fw-bold">DATE:</td>
-                <td><?= $h($today) ?></td>
+                <td class="text-end fw-bold text-nowrap">CLIENT:</td>
+                <td><?= $h($row['client_name']) ?></td>
                 <td></td>
-                <td class="text-end fw-bold">SAMPLE ID:</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">CLIENT:</td>
-                <td><?= $h($row['client_name']) ?></td>
+                <td class="text-end fw-bold text-nowrap">ADDRESS:</td>
+                <td><?= $h($row['site_address']) ?></td>
                 <td></td>
-                <td class="text-end fw-bold">DATE SAMPLED:</td>
+                <td class="text-end fw-bold text-nowrap">DATE SAMPLED:</td>
                 <td><?= $h($row['date_sampled']) ?></td>
             </tr>
             <tr>
-                <td class="text-end fw-bold">ADDRESS:</td>
-                <td><?= $h($row['site_address']) ?></td>
                 <td></td>
-                <td class="text-end fw-bold">LAB NUMBER:</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['state_postcode']) ?></td>
+                <td><?= $h($row['email']) ?></td>
                 <td></td>
-                <td class="text-end fw-bold">CROP:</td>
+                <td class="text-end fw-bold text-nowrap">CROP:</td>
                 <td><?= $h($row['sample_id']) ?></td>
             </tr>
             <tr>
                 <td></td>
-                <td><?= $h($row['email']) ?></td>
-                <td colspan="3"></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 button (hidden on print) -->
-    <div class="d-print-none mb-3">
-        <button class="btn btn-success btn-sm downloadPDF">
+    <!-- ── 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">
+            &larr; 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>
-    </div>
-
-    <div class="row">
-        <div class="col-md-12 text-center fw-bold h4">Soil Analysis Summary</div>
-    </div>
-
-    <!-- Analysis calculation sections — pending PHP migration of soilAnalysisReportCalcs -->
-    <div class="row bg-dark text-white p-2 mt-3">
-        <div class="text-center col-md-12 h5">
-            Total kilograms per hectare of each element needed to balance soil in this test
-        </div>
-    </div>
-    <div class="row">
-        <div class="col text-muted fst-italic p-3">
-            [Soil analysis calculation components pending migration]
-        </div>
+        <button class="btn btn-outline-dark btn-sm" onclick="window.print()">
+            <i class="fas fa-print me-1"></i>Print
+        </button>
     </div>
 
     <hr>
 
-    <!-- Report form (auto-saves via AJAX) -->
-    <form class="report-form" method="post">
-        <input type="hidden" name="csrf_token" value="<?= $h(generateCsrfToken()) ?>">
-
-        <div class="row bg-dark text-white p-2 mt-3">
-            <div class="text-center col-md-12 h5">Overview</div>
-        </div>
-        <div class="row">
-            <div class="col-md-12 p-0">
-                <textarea class="form-control rounded-0" rows="5" id="overview" name="overview"></textarea>
+    <!-- ── 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>
 
-        <hr>
-
-        <div class="row bg-dark text-white p-2 mt-3">
-            <div class="text-center col-md-12 h5">
-                Ideal Soil Balancing Program for One Season of a FIVE YEAR Plan
-            </div>
-        </div>
-        <div class="col text-muted fst-italic p-3">
-            [Soil program calculation components pending migration]
-        </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 : '&#10003; 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>
 
-        <hr>
+    <!-- ── 3. Overview ─────────────────────────────────────────────────────── -->
+    <div class="section-header">Overview</div>
+    <div class="section-body report-section">
+        <?= formatReportText($savedComments['overview'] ?? '') ?>
+    </div>
 
-        <div class="row bg-dark text-white p-2 mt-3">
-            <div class="text-center col-md-12 h5">
-                <input type="text" class="text-center form-control-plaintext text-white border-dark bg-dark"
-                       name="header1" id="header1" value="Foliar Program">
-            </div>
-        </div>
-        <div class="row">
-            <div class="col-md-12 p-0">
-                <textarea class="form-control rounded-0" rows="5"
-                          id="foliar_Details" name="foliar_Details"></textarea>
-            </div>
-        </div>
+    <!-- ── 4. AI Soil Interpretation ───────────────────────────────────────── -->
+    <div class="section-header">AI Soil Interpretation</div>
+    <div class="section-body report-section">
+        <?= formatReportText($savedComments['ai_interpretation'] ?? '') ?>
+    </div>
 
-        <div class="row bg-dark text-white p-2 mt-3">
-            <div class="text-center col-md-12 h5">Microbe Program</div>
-        </div>
-        <div class="row">
-            <div class="col-md-12 p-0">
-                <textarea class="form-control rounded-0" rows="5"
-                          id="microbe_Program" name="microbe_Program"></textarea>
-            </div>
-        </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>
 
-        <div class="row mt-2">
-            <p class="small fst-italic text-muted">
-                Any recommendations provided by Cropmonitor are advice only. We are not paid
-                consultants and are not covered to accept responsibility for any of our suggestions.
-            </p>
-        </div>
+    <!-- ── 6. Microbial Program ─────────────────────────────────────────────── -->
+    <div class="section-header">Microbial Program</div>
+    <div class="section-body report-section">
+        <?= formatReportText($savedComments['microbe_program'] ?? '') ?>
+    </div>
 
-    </form>
+    <!-- ── 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; ?>
 
@@ -197,47 +322,16 @@ $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
 
 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
 <script>
-$(document).ready(function () {
-    var timeoutId;
-    var saveUrl = '/dashboard/crop-analysis/updatecomment.php'
-                + '?rid=<?= (int) $recordId ?>&rand=<?= (float) $randId ?>';
-
-    $('form textarea, form input[name="header1"]').on('input propertychange change', function () {
-        clearTimeout(timeoutId);
-        timeoutId = setTimeout(saveToDB, 1000);
-    });
-
-    function saveToDB() {
-        var form = $('.report-form');
-        $.ajax({
-            url: saveUrl,
-            type: 'POST',
-            data: form.serialize(),
-            beforeSend: function () { $('.form-status-holder').html('Saving...'); },
-            success: function (data) {
-                var d = new Date();
-                $('.form-status-holder').html('Saved! Last: ' + d.toLocaleTimeString());
-            }
-        });
-    }
-
-    $('.report-form').submit(function (e) {
-        saveToDB();
-        e.preventDefault();
-    });
-
-    // PDF download
-    $('.downloadPDF').click(function () {
-        var element = document.getElementById('content');
-        var opt = {
-            margin:      3,
-            filename:    'soil-analysis.pdf',
-            image:       { type: 'jpeg', quality: 1.0 },
-            html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
-            jsPDF:       { orientation: 'portrait', unit: 'mm', format: 'a4' }
-        };
-        html2pdf().from(element).set(opt).save();
-    });
+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>