patients.go
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "io" |
| 7 | "net/http" |
| 8 | "regexp" |
| 9 | "strings" |
| 10 | "time" |
| 11 | |
| 12 | "forge.gpl-nas.de/physiobase/internal/befund" |
| 13 | "forge.gpl-nas.de/physiobase/internal/mailer" |
| 14 | "forge.gpl-nas.de/physiobase/internal/store" |
| 15 | ) |
| 16 | |
| 17 | var isoDate = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) |
| 18 | |
| 19 | func validDate(s string) string { |
| 20 | if isoDate.MatchString(s) { |
| 21 | return s |
| 22 | } |
| 23 | return time.Now().Format("2006-01-02") |
| 24 | } |
| 25 | |
| 26 | // ---------- Patientenliste ---------- |
| 27 | |
| 28 | func (s *Server) listPatients(w http.ResponseWriter, r *http.Request) { |
| 29 | q := strings.TrimSpace(r.URL.Query().Get("q")) |
| 30 | archived := r.URL.Query().Get("archiv") == "1" |
| 31 | ps, err := s.st.Patients(r.Context(), q, archived) |
| 32 | if err != nil { |
| 33 | s.fail(w, r, err) |
| 34 | return |
| 35 | } |
| 36 | s.render(w, r, "patients", http.StatusOK, map[string]any{ |
| 37 | "Patients": ps, "Q": q, "Archived": archived, "Imported": r.URL.Query().Get("importiert"), |
| 38 | }) |
| 39 | } |
| 40 | |
| 41 | func (s *Server) createPatient(w http.ResponseWriter, r *http.Request) { |
| 42 | if !limitForm(w, r) { |
| 43 | return |
| 44 | } |
| 45 | p := store.Patient{ |
| 46 | Name: formText(r, "name"), Dob: formText(r, "dob"), Phone: formText(r, "phone"), |
| 47 | Email: formText(r, "email"), EmailConsent: r.FormValue("email_consent") == "1", |
| 48 | } |
| 49 | if p.Name == "" { |
| 50 | http.Error(w, "Name fehlt", http.StatusBadRequest) |
| 51 | return |
| 52 | } |
| 53 | if p.Email != "" && !mailer.ValidAddress(p.Email) { |
| 54 | http.Error(w, "Ungültige E-Mail-Adresse", http.StatusBadRequest) |
| 55 | return |
| 56 | } |
| 57 | id, err := s.st.CreatePatient(r.Context(), p) |
| 58 | if err != nil { |
| 59 | s.fail(w, r, err) |
| 60 | return |
| 61 | } |
| 62 | redirect(w, r, "/patient/%d", id) |
| 63 | } |
| 64 | |
| 65 | // ---------- Patient ---------- |
| 66 | |
| 67 | func (s *Server) showPatient(w http.ResponseWriter, r *http.Request) { |
| 68 | ctx := r.Context() |
| 69 | id, ok := pathID(r, "id") |
| 70 | if !ok { |
| 71 | http.NotFound(w, r) |
| 72 | return |
| 73 | } |
| 74 | p, err := s.st.Patient(ctx, id) |
| 75 | if err != nil { |
| 76 | s.fail(w, r, err) |
| 77 | return |
| 78 | } |
| 79 | bs, err := s.st.Befunde(ctx, id) |
| 80 | if err != nil { |
| 81 | s.fail(w, r, err) |
| 82 | return |
| 83 | } |
| 84 | sessions, err := s.st.Sessions(ctx, id) |
| 85 | if err != nil { |
| 86 | s.fail(w, r, err) |
| 87 | return |
| 88 | } |
| 89 | linked, err := s.st.PatientEntries(ctx, id) |
| 90 | if err != nil { |
| 91 | s.fail(w, r, err) |
| 92 | return |
| 93 | } |
| 94 | linkable, err := s.st.EntriesOfKind(ctx, "krankheitsbild", "protokoll", "notiz") |
| 95 | if err != nil { |
| 96 | s.fail(w, r, err) |
| 97 | return |
| 98 | } |
| 99 | handouts, err := s.st.HandoutsForPatient(ctx, id) |
| 100 | if err != nil { |
| 101 | s.fail(w, r, err) |
| 102 | return |
| 103 | } |
| 104 | docs, err := s.st.Docs(ctx, id) |
| 105 | if err != nil { |
| 106 | s.fail(w, r, err) |
| 107 | return |
| 108 | } |
| 109 | summaries, err := s.st.Summaries(ctx, id) |
| 110 | if err != nil { |
| 111 | s.fail(w, r, err) |
| 112 | return |
| 113 | } |
| 114 | // Verlauf der Kernwerte über alle Befunde (älteste zuerst) |
| 115 | type trendRow struct { |
| 116 | Date, Title string |
| 117 | Now, Worst, PSFS, Tests string |
| 118 | ID int64 |
| 119 | } |
| 120 | var trend []trendRow |
| 121 | for i := len(bs) - 1; i >= 0; i-- { |
| 122 | d, _ := befund.Parse(bs[i].Data) |
| 123 | trend = append(trend, trendRow{ |
| 124 | ID: bs[i].ID, Date: bs[i].Date, Title: bs[i].Title, |
| 125 | Now: d.Get("pain_now"), Worst: d.Get("pain_worst"), PSFS: befund.PSFSMean(d), |
| 126 | Tests: fmt.Sprint(len(d.Tables["measures"])), |
| 127 | }) |
| 128 | } |
| 129 | s.render(w, r, "patient", http.StatusOK, map[string]any{ |
| 130 | "P": p, "Befunde": bs, "Sessions": sessions, "Linked": linked, "Linkable": linkable, |
| 131 | "Handouts": handouts, "Trend": trend, "Today": time.Now().Format("2006-01-02"), |
| 132 | "Docs": docs, "DocCategories": store.DocCategories, "Summaries": summaries, |
| 133 | }) |
| 134 | } |
| 135 | |
| 136 | func (s *Server) updatePatient(w http.ResponseWriter, r *http.Request) { |
| 137 | if !limitForm(w, r) { |
| 138 | return |
| 139 | } |
| 140 | id, ok := pathID(r, "id") |
| 141 | if !ok { |
| 142 | http.NotFound(w, r) |
| 143 | return |
| 144 | } |
| 145 | p := store.Patient{ |
| 146 | ID: id, Name: formText(r, "name"), Dob: formText(r, "dob"), Phone: formText(r, "phone"), Notes: formText(r, "notes"), |
| 147 | Email: formText(r, "email"), EmailConsent: r.FormValue("email_consent") == "1", |
| 148 | } |
| 149 | if p.Name == "" { |
| 150 | http.Error(w, "Name fehlt", http.StatusBadRequest) |
| 151 | return |
| 152 | } |
| 153 | if p.Email != "" && !mailer.ValidAddress(p.Email) { |
| 154 | http.Error(w, "Ungültige E-Mail-Adresse", http.StatusBadRequest) |
| 155 | return |
| 156 | } |
| 157 | if err := s.st.UpdatePatient(r.Context(), p); err != nil { |
| 158 | s.fail(w, r, err) |
| 159 | return |
| 160 | } |
| 161 | redirect(w, r, "/patient/%d", id) |
| 162 | } |
| 163 | |
| 164 | func (s *Server) archivePatient(w http.ResponseWriter, r *http.Request) { |
| 165 | id, ok := pathID(r, "id") |
| 166 | if !ok { |
| 167 | http.NotFound(w, r) |
| 168 | return |
| 169 | } |
| 170 | if err := s.st.SetArchived(r.Context(), id, r.FormValue("archiv") == "1"); err != nil { |
| 171 | s.fail(w, r, err) |
| 172 | return |
| 173 | } |
| 174 | redirect(w, r, "/patient/%d", id) |
| 175 | } |
| 176 | |
| 177 | func (s *Server) deletePatient(w http.ResponseWriter, r *http.Request) { |
| 178 | id, ok := pathID(r, "id") |
| 179 | if !ok { |
| 180 | http.NotFound(w, r) |
| 181 | return |
| 182 | } |
| 183 | files, err := s.st.DocFiles(r.Context(), id) |
| 184 | if err != nil { |
| 185 | s.fail(w, r, err) |
| 186 | return |
| 187 | } |
| 188 | if err := s.st.DeletePatient(r.Context(), id); err != nil { |
| 189 | s.fail(w, r, err) |
| 190 | return |
| 191 | } |
| 192 | for _, f := range files { |
| 193 | s.removeDocFile(f) |
| 194 | } |
| 195 | redirect(w, r, "/patienten") |
| 196 | } |
| 197 | |
| 198 | func (s *Server) addSession(w http.ResponseWriter, r *http.Request) { |
| 199 | if !limitForm(w, r) { |
| 200 | return |
| 201 | } |
| 202 | id, ok := pathID(r, "id") |
| 203 | if !ok { |
| 204 | http.NotFound(w, r) |
| 205 | return |
| 206 | } |
| 207 | x := store.Session{PatientID: id, Date: validDate(formText(r, "date")), Goal: formText(r, "goal"), Note: formText(r, "note")} |
| 208 | if x.Goal != "" || x.Note != "" { |
| 209 | if err := s.st.AddSession(r.Context(), x); err != nil { |
| 210 | s.fail(w, r, err) |
| 211 | return |
| 212 | } |
| 213 | } |
| 214 | redirect(w, r, "/patient/%d#verlauf", id) |
| 215 | } |
| 216 | |
| 217 | func (s *Server) deleteSession(w http.ResponseWriter, r *http.Request) { |
| 218 | id, ok := pathID(r, "id") |
| 219 | if !ok { |
| 220 | http.NotFound(w, r) |
| 221 | return |
| 222 | } |
| 223 | pid, err := s.st.DeleteSession(r.Context(), id) |
| 224 | if err != nil { |
| 225 | s.fail(w, r, err) |
| 226 | return |
| 227 | } |
| 228 | redirect(w, r, "/patient/%d#verlauf", pid) |
| 229 | } |
| 230 | |
| 231 | func (s *Server) linkPatientEntry(w http.ResponseWriter, r *http.Request) { |
| 232 | if !limitForm(w, r) { |
| 233 | return |
| 234 | } |
| 235 | id, ok1 := pathID(r, "id") |
| 236 | eid, ok2 := formID(r, "entry") |
| 237 | if !ok1 || !ok2 { |
| 238 | http.Error(w, "Bitte einen Eintrag auswählen", http.StatusBadRequest) |
| 239 | return |
| 240 | } |
| 241 | if err := s.st.LinkPatientEntry(r.Context(), id, eid); err != nil { |
| 242 | s.fail(w, r, err) |
| 243 | return |
| 244 | } |
| 245 | redirect(w, r, "/patient/%d#wissen", id) |
| 246 | } |
| 247 | |
| 248 | func (s *Server) unlinkPatientEntry(w http.ResponseWriter, r *http.Request) { |
| 249 | id, ok1 := pathID(r, "id") |
| 250 | eid, ok2 := pathID(r, "eid") |
| 251 | if !ok1 || !ok2 { |
| 252 | http.NotFound(w, r) |
| 253 | return |
| 254 | } |
| 255 | if err := s.st.UnlinkPatientEntry(r.Context(), id, eid); err != nil { |
| 256 | s.fail(w, r, err) |
| 257 | return |
| 258 | } |
| 259 | redirect(w, r, "/patient/%d#wissen", id) |
| 260 | } |
| 261 | |
| 262 | func (s *Server) createPatientHandout(w http.ResponseWriter, r *http.Request) { |
| 263 | if !limitForm(w, r) { |
| 264 | return |
| 265 | } |
| 266 | id, ok := pathID(r, "id") |
| 267 | if !ok { |
| 268 | http.NotFound(w, r) |
| 269 | return |
| 270 | } |
| 271 | p, err := s.st.Patient(r.Context(), id) |
| 272 | if err != nil { |
| 273 | s.fail(w, r, err) |
| 274 | return |
| 275 | } |
| 276 | title := formText(r, "title") |
| 277 | if title == "" { |
| 278 | title = "Heimübungsprogramm" |
| 279 | } |
| 280 | hid, err := s.st.CreateHandoutFor(r.Context(), title, p) |
| 281 | if err != nil { |
| 282 | s.fail(w, r, err) |
| 283 | return |
| 284 | } |
| 285 | redirect(w, r, "/handout/%d", hid) |
| 286 | } |
| 287 | |
| 288 | // ---------- Befund ---------- |
| 289 | |
| 290 | // newBefund: leer oder als Kopie des letzten Befunds (Wiederbefund – nur Werte neu messen). |
| 291 | func (s *Server) newBefund(w http.ResponseWriter, r *http.Request) { |
| 292 | if !limitForm(w, r) { |
| 293 | return |
| 294 | } |
| 295 | ctx := r.Context() |
| 296 | pid, ok := pathID(r, "id") |
| 297 | if !ok { |
| 298 | http.NotFound(w, r) |
| 299 | return |
| 300 | } |
| 301 | if _, err := s.st.Patient(ctx, pid); err != nil { |
| 302 | s.fail(w, r, err) |
| 303 | return |
| 304 | } |
| 305 | b := store.Befund{PatientID: pid, Date: time.Now().Format("2006-01-02"), Title: "Erstbefund"} |
| 306 | if src, ok := formID(r, "kopie"); ok { |
| 307 | old, err := s.st.Befund(ctx, src) |
| 308 | if err != nil || old.PatientID != pid { |
| 309 | http.Error(w, "Vorlage nicht gefunden", http.StatusBadRequest) |
| 310 | return |
| 311 | } |
| 312 | b.Data, b.Title = old.Data, "Wiederbefund" |
| 313 | } |
| 314 | if t := formText(r, "title"); t != "" { |
| 315 | b.Title = t |
| 316 | } |
| 317 | id, err := s.st.CreateBefund(ctx, b) |
| 318 | if err != nil { |
| 319 | s.fail(w, r, err) |
| 320 | return |
| 321 | } |
| 322 | redirect(w, r, "/befund/%d", id) |
| 323 | } |
| 324 | |
| 325 | func (s *Server) loadBefund(w http.ResponseWriter, r *http.Request) (store.Befund, store.Patient, befund.Data, bool) { |
| 326 | ctx := r.Context() |
| 327 | id, ok := pathID(r, "id") |
| 328 | if !ok { |
| 329 | http.NotFound(w, r) |
| 330 | return store.Befund{}, store.Patient{}, befund.Data{}, false |
| 331 | } |
| 332 | b, err := s.st.Befund(ctx, id) |
| 333 | if err != nil { |
| 334 | s.fail(w, r, err) |
| 335 | return b, store.Patient{}, befund.Data{}, false |
| 336 | } |
| 337 | p, err := s.st.Patient(ctx, b.PatientID) |
| 338 | if err != nil { |
| 339 | s.fail(w, r, err) |
| 340 | return b, p, befund.Data{}, false |
| 341 | } |
| 342 | d, err := befund.Parse(b.Data) |
| 343 | if err != nil { |
| 344 | s.fail(w, r, err) |
| 345 | return b, p, d, false |
| 346 | } |
| 347 | return b, p, d, true |
| 348 | } |
| 349 | |
| 350 | func (s *Server) showBefund(w http.ResponseWriter, r *http.Request) { |
| 351 | b, p, d, ok := s.loadBefund(w, r) |
| 352 | if !ok { |
| 353 | return |
| 354 | } |
| 355 | tests, err := s.st.EntriesOfKind(r.Context(), "test") |
| 356 | if err != nil { |
| 357 | s.fail(w, r, err) |
| 358 | return |
| 359 | } |
| 360 | s.render(w, r, "befund", http.StatusOK, map[string]any{ |
| 361 | "B": b, "P": p, "D": d, "Sections": befund.Sections, "Symbols": befund.Symbols, "Views": befund.Views, |
| 362 | "Tests": tests, "Measures": befund.Measures, "Saved": r.URL.Query().Get("gespeichert") == "1", |
| 363 | }) |
| 364 | } |
| 365 | |
| 366 | func (s *Server) saveBefund(w http.ResponseWriter, r *http.Request) { |
| 367 | r.Body = http.MaxBytesReader(w, r.Body, 4<<20) |
| 368 | if err := r.ParseForm(); err != nil { |
| 369 | http.Error(w, "Formular ungültig oder zu groß", http.StatusBadRequest) |
| 370 | return |
| 371 | } |
| 372 | b, _, _, ok := s.loadBefund(w, r) |
| 373 | if !ok { |
| 374 | return |
| 375 | } |
| 376 | b.Date = validDate(formText(r, "date")) |
| 377 | b.Title = formText(r, "title") |
| 378 | if b.Title == "" { |
| 379 | b.Title = "Befund" |
| 380 | } |
| 381 | b.Data = befund.FromForm(r.PostForm).JSON() |
| 382 | if err := s.st.UpdateBefund(r.Context(), b); err != nil { |
| 383 | s.fail(w, r, err) |
| 384 | return |
| 385 | } |
| 386 | if r.FormValue("next") == "druck" { |
| 387 | redirect(w, r, "/befund/%d/druck", b.ID) |
| 388 | return |
| 389 | } |
| 390 | redirect(w, r, "/befund/%d?gespeichert=1", b.ID) |
| 391 | } |
| 392 | |
| 393 | func (s *Server) deleteBefund(w http.ResponseWriter, r *http.Request) { |
| 394 | id, ok := pathID(r, "id") |
| 395 | if !ok { |
| 396 | http.NotFound(w, r) |
| 397 | return |
| 398 | } |
| 399 | pid, err := s.st.DeleteBefund(r.Context(), id) |
| 400 | if err != nil { |
| 401 | s.fail(w, r, err) |
| 402 | return |
| 403 | } |
| 404 | redirect(w, r, "/patient/%d", pid) |
| 405 | } |
| 406 | |
| 407 | func (s *Server) printBefund(w http.ResponseWriter, r *http.Request) { |
| 408 | b, p, d, ok := s.loadBefund(w, r) |
| 409 | if !ok { |
| 410 | return |
| 411 | } |
| 412 | var sessions []store.Session |
| 413 | if r.URL.Query().Get("verlauf") == "1" { |
| 414 | var err error |
| 415 | if sessions, err = s.st.Sessions(r.Context(), p.ID); err != nil { |
| 416 | s.fail(w, r, err) |
| 417 | return |
| 418 | } |
| 419 | } |
| 420 | s.render(w, r, "befund_print", http.StatusOK, map[string]any{ |
| 421 | "B": b, "P": p, "D": d, "Sections": befund.Sections, "Views": befund.Views, "Sessions": sessions, "Today": time.Now(), |
| 422 | }) |
| 423 | } |
| 424 | |
| 425 | // Export im eigenen Format (lässt sich wieder importieren). |
| 426 | type exportFile struct { |
| 427 | Format string `json:"format"` |
| 428 | Patient exportPatient `json:"patient"` |
| 429 | Date string `json:"date"` |
| 430 | Title string `json:"title"` |
| 431 | Data json.RawMessage `json:"data"` |
| 432 | } |
| 433 | type exportPatient struct { |
| 434 | Name string `json:"name"` |
| 435 | Dob string `json:"dob"` |
| 436 | } |
| 437 | |
| 438 | const exportFormat = "physiobase-befund-1" |
| 439 | |
| 440 | func (s *Server) exportBefund(w http.ResponseWriter, r *http.Request) { |
| 441 | b, p, _, ok := s.loadBefund(w, r) |
| 442 | if !ok { |
| 443 | return |
| 444 | } |
| 445 | name := strings.Map(func(c rune) rune { |
| 446 | if c == ' ' || c == '/' || c == '\\' || c == '"' { |
| 447 | return '_' |
| 448 | } |
| 449 | return c |
| 450 | }, p.Name) |
| 451 | w.Header().Set("Content-Type", "application/json") |
| 452 | w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="Befund_%s_%s.json"`, name, b.Date)) |
| 453 | enc := json.NewEncoder(w) |
| 454 | enc.SetIndent("", " ") |
| 455 | if err := enc.Encode(exportFile{Format: exportFormat, Patient: exportPatient{p.Name, p.Dob}, Date: b.Date, Title: b.Title, Data: json.RawMessage(b.Data)}); err != nil { |
| 456 | s.log.Error("export", "befund", b.ID, "err", err) |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | // ---------- Vergleich ---------- |
| 461 | |
| 462 | func (s *Server) compareBefunde(w http.ResponseWriter, r *http.Request) { |
| 463 | ctx := r.Context() |
| 464 | pid, ok := pathID(r, "id") |
| 465 | if !ok { |
| 466 | http.NotFound(w, r) |
| 467 | return |
| 468 | } |
| 469 | p, err := s.st.Patient(ctx, pid) |
| 470 | if err != nil { |
| 471 | s.fail(w, r, err) |
| 472 | return |
| 473 | } |
| 474 | bs, err := s.st.Befunde(ctx, pid) |
| 475 | if err != nil { |
| 476 | s.fail(w, r, err) |
| 477 | return |
| 478 | } |
| 479 | data := map[string]any{"P": p, "Befunde": bs} |
| 480 | if len(bs) >= 2 { |
| 481 | // Standard: ältester (Baseline) gegen neuesten |
| 482 | aID, bID := bs[len(bs)-1].ID, bs[0].ID |
| 483 | if v, ok := formID(r, "a"); ok { |
| 484 | aID = v |
| 485 | } |
| 486 | if v, ok := formID(r, "b"); ok { |
| 487 | bID = v |
| 488 | } |
| 489 | var A, B *store.Befund |
| 490 | for i := range bs { |
| 491 | if bs[i].ID == aID { |
| 492 | A = &bs[i] |
| 493 | } |
| 494 | if bs[i].ID == bID { |
| 495 | B = &bs[i] |
| 496 | } |
| 497 | } |
| 498 | if A != nil && B != nil { |
| 499 | da, _ := befund.Parse(A.Data) |
| 500 | db, _ := befund.Parse(B.Data) |
| 501 | fields, tables := befund.Compare(da, db) |
| 502 | data["A"], data["B"], data["Fields"], data["Tables"] = A, B, fields, tables |
| 503 | } |
| 504 | } |
| 505 | s.render(w, r, "compare", http.StatusOK, data) |
| 506 | } |
| 507 | |
| 508 | // ---------- Import alter Befunde ---------- |
| 509 | |
| 510 | func (s *Server) importBefunde(w http.ResponseWriter, r *http.Request) { |
| 511 | ctx := r.Context() |
| 512 | r.Body = http.MaxBytesReader(w, r.Body, 50<<20) |
| 513 | if err := r.ParseMultipartForm(8 << 20); err != nil { //nolint:gosec // G120: Größe oben per MaxBytesReader begrenzt |
| 514 | http.Error(w, "Upload fehlgeschlagen", http.StatusBadRequest) |
| 515 | return |
| 516 | } |
| 517 | defer func() { _ = r.MultipartForm.RemoveAll() }() |
| 518 | var count int |
| 519 | var errs []string |
| 520 | for _, fh := range r.MultipartForm.File["files"] { |
| 521 | f, err := fh.Open() |
| 522 | if err != nil { |
| 523 | errs = append(errs, fh.Filename+": "+err.Error()) |
| 524 | continue |
| 525 | } |
| 526 | raw, err := io.ReadAll(io.LimitReader(f, 10<<20)) |
| 527 | _ = f.Close() |
| 528 | if err != nil { |
| 529 | errs = append(errs, fh.Filename+": "+err.Error()) |
| 530 | continue |
| 531 | } |
| 532 | if err := s.importOne(r, raw); err != nil { |
| 533 | errs = append(errs, fh.Filename+": "+err.Error()) |
| 534 | continue |
| 535 | } |
| 536 | count++ |
| 537 | } |
| 538 | if len(errs) > 0 { |
| 539 | s.log.Warn("import teilweise fehlgeschlagen", "fehler", errs) |
| 540 | ps, _ := s.st.Patients(ctx, "", false) |
| 541 | s.render(w, r, "patients", http.StatusBadRequest, map[string]any{ |
| 542 | "Patients": ps, "Imported": fmt.Sprint(count), "ImportErrors": errs, |
| 543 | }) |
| 544 | return |
| 545 | } |
| 546 | redirect(w, r, "/patienten?importiert=%d", count) |
| 547 | } |
| 548 | |
| 549 | func (s *Server) importOne(r *http.Request, raw []byte) error { |
| 550 | ctx := r.Context() |
| 551 | var probe struct { |
| 552 | Format string `json:"format"` |
| 553 | } |
| 554 | _ = json.Unmarshal(raw, &probe) // kein eigenes Format → alte Befundseite |
| 555 | |
| 556 | var name, dob, date, title string |
| 557 | var data befund.Data |
| 558 | var sessions []befund.Row |
| 559 | if probe.Format == exportFormat { |
| 560 | var ef exportFile |
| 561 | if err := json.Unmarshal(raw, &ef); err != nil { |
| 562 | return err |
| 563 | } |
| 564 | d, err := befund.Parse(string(ef.Data)) |
| 565 | if err != nil { |
| 566 | return err |
| 567 | } |
| 568 | d.Stamps = befund.CleanStamps(d.Stamps) |
| 569 | name, dob, date, title, data = ef.Patient.Name, ef.Patient.Dob, ef.Date, ef.Title, d |
| 570 | } else { |
| 571 | l, err := befund.ParseLegacy(raw) |
| 572 | if err != nil { |
| 573 | return err |
| 574 | } |
| 575 | name, dob, date, title, data, sessions = l.Name, l.Dob, l.Date, "Befund (Import)", l.Data, l.Sessions |
| 576 | } |
| 577 | if strings.TrimSpace(name) == "" { |
| 578 | name = "Unbekannt (Import)" |
| 579 | } |
| 580 | pid, found, err := s.st.FindPatient(ctx, name, dob) |
| 581 | if err != nil { |
| 582 | return err |
| 583 | } |
| 584 | if !found { |
| 585 | if pid, err = s.st.CreatePatient(ctx, store.Patient{Name: name, Dob: dob}); err != nil { |
| 586 | return err |
| 587 | } |
| 588 | } |
| 589 | if _, err := s.st.CreateBefund(ctx, store.Befund{PatientID: pid, Date: validDate(date), Title: title, Data: data.JSON()}); err != nil { |
| 590 | return err |
| 591 | } |
| 592 | for _, x := range sessions { |
| 593 | if x["goal"] == "" && x["note"] == "" { |
| 594 | continue |
| 595 | } |
| 596 | if err := s.st.AddSession(ctx, store.Session{PatientID: pid, Date: validDate(x["date"]), Goal: x["goal"], Note: x["note"]}); err != nil { |
| 597 | return err |
| 598 | } |
| 599 | } |
| 600 | return nil |
| 601 | } |
| 602 |