Health records: typed fields per record type, document upload, delete button

- HealthRecordForm: per-type field configs (vaccination/exam/surgery/medication),
  expanded OFA test catalog with result scales, min-age validation, expiry rules
- Document upload (PDF/image, 15MB) via new POST /api/health/upload
- Delete button for health records on DogDetail
- Fold legacy vet_name into performed_by; normalize payload per record type

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jason Stedwell
2026-07-06 14:39:45 -05:00
parent e2e8e00dff
commit c9d2aa734e
3 changed files with 281 additions and 62 deletions
+39 -1
View File
@@ -1,6 +1,31 @@
const express = require('express');
const router = express.Router();
const { getDatabase } = require('../db/init');
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadPath = process.env.UPLOAD_PATH || path.join(__dirname, '../../uploads');
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.random().toString(36).substring(7)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
const upload = multer({
storage,
limits: { fileSize: 15 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = /pdf|jpeg|jpg|png|gif|webp/;
const extOk = allowed.test(path.extname(file.originalname).toLowerCase());
const mimeOk = allowed.test(file.mimetype);
if (extOk && mimeOk) cb(null, true);
else cb(new Error('Only PDF or image files are allowed'));
}
});
// OFA tests that count toward GRCA eligibility
const GRCA_REQUIRED = ['hip_ofa', 'hip_pennhip', 'elbow_ofa', 'heart_ofa', 'heart_echo', 'eye_caer'];
@@ -10,7 +35,9 @@ const GRCA_CORE = {
heart: ['heart_ofa', 'heart_echo'],
eye: ['eye_caer'],
};
const VALID_TEST_TYPES = ['hip_ofa', 'hip_pennhip', 'elbow_ofa', 'heart_ofa', 'heart_echo', 'eye_caer', 'thyroid_ofa', 'dna_panel'];
// DNA is owned exclusively by the genetics module (genetic_tests table), so it is
// intentionally NOT a valid health test type.
const VALID_TEST_TYPES = ['hip_ofa', 'hip_pennhip', 'elbow_ofa', 'patella_ofa', 'heart_ofa', 'heart_echo', 'eye_caer', 'thyroid_ofa'];
// Helper: compute clearance summary for a dog
function getClearanceSummary(db, dogId) {
@@ -142,6 +169,17 @@ router.post('/', (req, res) => {
}
});
// POST upload a supporting document (PDF/image). Returns a URL to store in
// document_url. Works for new or existing records — the URL is saved via the
// record's normal POST/PUT, so no record id is needed here.
router.post('/upload', (req, res) => {
upload.single('document')(req, res, (err) => {
if (err) return res.status(400).json({ error: err.message });
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
res.json({ url: `/uploads/${req.file.filename}` });
});
});
// PUT update health record
router.put('/:id', (req, res) => {
try {