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:
+39
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user