package main import ( "encoding/json" "fmt" "io" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" ) // --- Helpers --- func parseUUID(c *gin.Context, param string) (uuid.UUID, bool) { id, err := uuid.Parse(c.Param(param)) if err != nil { c.JSON(400, gin.H{"detail": "invalid uuid"}) return uuid.Nil, false } return id, true } func templateAttributeDetails(db *gorm.DB, templateID uuid.UUID) []gin.H { var allAttrs []Attribute db.Find(&allAttrs) var tas []TemplateAttribute db.Where("template_id = ?", templateID).Preload("Attribute").Find(&tas) taMap := map[uuid.UUID]*TemplateAttribute{} for i := range tas { taMap[tas[i].AttributeID] = &tas[i] } result := []gin.H{} for _, a := range allAttrs { detail := gin.H{ "attribute_id": a.ID, "attribute_name": a.Name, "is_required": a.IsRequired, "value": nil, } if ta, ok := taMap[a.ID]; ok { detail["value"] = ta.Value } result = append(result, detail) } return result } func templateWithAttrs(db *gorm.DB, t Template) gin.H { return gin.H{ "id": t.ID, "name": t.Name, "description": t.Description, "elements": json.RawMessage(t.Elements), "group_id": t.GroupID, "attributes": templateAttributeDetails(db, t.ID), "order": t.Order, "created_at": t.CreatedAt, "updated_at": t.UpdatedAt, "deleted_at": t.DeletedAt, } } // --- Templates --- func getTemplates(c *gin.Context, db *gorm.DB) { var templates []Template db.Find(&templates) result := []gin.H{} for _, t := range templates { result = append(result, templateWithAttrs(db, t)) } c.JSON(200, gin.H{"templates": result}) } func getTemplate(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var t Template if db.First(&t, "id = ?", id).Error != nil { c.JSON(200, gin.H{"template": nil}) return } c.JSON(200, gin.H{"template": templateWithAttrs(db, t)}) } func createTemplate(c *gin.Context, db *gorm.DB) { var input struct { Name string `json:"name"` Description string `json:"description"` Elements json.RawMessage `json:"elements"` Attributes []setTemplateAttrInput `json:"attributes"` GroupID *uuid.UUID `json:"group_id"` } if c.BindJSON(&input) != nil { return } t := Template{Name: input.Name, Description: &input.Description, Elements: JSON(input.Elements), GroupID: input.GroupID} db.Create(&t) for _, a := range input.Attributes { db.Create(&TemplateAttribute{TemplateID: t.ID, AttributeID: a.AttributeID, Value: a.Value}) } c.JSON(200, gin.H{"id": t.ID}) } type setTemplateAttrInput struct { AttributeID uuid.UUID `json:"attribute_id"` Value *string `json:"value"` } func patchTemplate(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var t Template if db.First(&t, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Template not found"}) return } var input struct { Name *string `json:"name"` Description *string `json:"description"` Elements *json.RawMessage `json:"elements"` Attributes []setTemplateAttrInput `json:"attributes"` GroupID interface{} `json:"group_id"` Order *float64 `json:"order"` } if c.BindJSON(&input) != nil { return } updates := map[string]interface{}{} if input.Name != nil { updates["name"] = *input.Name } if input.Description != nil { updates["description"] = *input.Description } if input.Elements != nil { updates["elements"] = JSON(*input.Elements) } if input.Order != nil { updates["order"] = *input.Order } if input.GroupID != nil { switch v := input.GroupID.(type) { case string: if v == "ungroup" { updates["group_id"] = nil } else { if uid, err := uuid.Parse(v); err == nil { updates["group_id"] = uid } } } } if len(updates) > 0 { db.Model(&t).Updates(updates) } for _, a := range input.Attributes { res := db.Model(&TemplateAttribute{}).Where("template_id = ? AND attribute_id = ?", id, a.AttributeID).Update("value", a.Value) if res.RowsAffected == 0 { db.Create(&TemplateAttribute{TemplateID: id, AttributeID: a.AttributeID, Value: a.Value}) } } c.JSON(200, gin.H{"id": t.ID}) } func deleteTemplate(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } res := db.Delete(&Template{}, "id = ?", id) if res.RowsAffected == 0 { c.JSON(404, gin.H{"detail": "Template not found"}) return } c.JSON(200, gin.H{"success": true, "template_id": id}) } func copyTemplate(c *gin.Context, db *gorm.DB) { var input struct { TemplateID uuid.UUID `json:"template_id"` NewName string `json:"new_name"` NewDescription *string `json:"new_description"` NewAttributes []setTemplateAttrInput `json:"new_attributes"` GroupID *uuid.UUID `json:"group_id"` CopyFirstSheetEmpty bool `json:"copy_first_sheet_empty"` } if c.BindJSON(&input) != nil { return } var orig Template if db.First(&orig, "id = ?", input.TemplateID).Error != nil { c.JSON(400, gin.H{"detail": "Template not found"}) return } // Update element IDs newElements := updateElementIDs(orig.Elements) newT := Template{Name: input.NewName, Description: input.NewDescription, Elements: newElements, GroupID: input.GroupID} db.Create(&newT) // Copy files var files []File db.Where("template_id = ?", input.TemplateID).Find(&files) for _, f := range files { newF := File{Name: f.Name, Content: f.Content, Cells: f.Cells, TemplateID: &newT.ID} db.Create(&newF) } // Copy sheets var sheets []Sheet db.Where("template_id = ?", input.TemplateID).Find(&sheets) for _, s := range sheets { cells := s.Cells if s.Name == "L" && input.CopyFirstSheetEmpty { cells = JSON("{}") } newS := Sheet{Name: s.Name, Cells: cells, TemplateID: newT.ID} db.Create(&newS) } // Copy or set attributes if len(input.NewAttributes) > 0 { for _, a := range input.NewAttributes { db.Create(&TemplateAttribute{TemplateID: newT.ID, AttributeID: a.AttributeID, Value: a.Value}) } } else { var origAttrs []TemplateAttribute db.Where("template_id = ?", input.TemplateID).Find(&origAttrs) for _, a := range origAttrs { db.Create(&TemplateAttribute{TemplateID: newT.ID, AttributeID: a.AttributeID, Value: a.Value}) } } c.JSON(200, gin.H{"new_template_id": newT.ID}) } func updateElementIDs(elements JSON) JSON { var parsed map[string]interface{} if json.Unmarshal(elements, &parsed) != nil || parsed == nil { return elements } newElements := map[string]interface{}{} for _, data := range parsed { newID := uuid.New().String() if m, ok := data.(map[string]interface{}); ok { m["id"] = newID } newElements[newID] = data } result, _ := json.Marshal(newElements) return JSON(result) } // --- Files --- func getFiles(c *gin.Context, db *gorm.DB) { var files []File q := db.Model(&File{}) if tid := c.Query("template_id"); tid != "" { q = q.Where("template_id = ?", tid) } q.Find(&files) c.JSON(200, gin.H{"files": files}) } func getFile(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var f File if db.First(&f, "id = ?", id).Error != nil { c.JSON(200, gin.H{"file": nil}) return } c.JSON(200, gin.H{"file": f}) } func uploadFile(c *gin.Context, db *gorm.DB) { templateID, err := uuid.Parse(c.Query("template_id")) if err != nil { c.JSON(400, gin.H{"detail": "invalid template_id"}) return } fileHeader, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"detail": "file required"}) return } f, _ := fileHeader.Open() defer f.Close() content, _ := io.ReadAll(f) cellsData, err := ReadCells(content) if err != nil { c.JSON(500, gin.H{"detail": fmt.Sprintf("failed to read cells: %v", err)}) return } cellsJSON, _ := json.Marshal(cellsData) file := File{Name: fileHeader.Filename, Content: content, Cells: JSON(cellsJSON), TemplateID: &templateID} db.Create(&file) c.JSON(200, gin.H{"file_id": file.ID, "cells": cellsData}) } // --- Sheets --- func getSheets(c *gin.Context, db *gorm.DB) { var sheets []Sheet q := db.Model(&Sheet{}) if tid := c.Query("template_id"); tid != "" { q = q.Where("template_id = ?", tid) } q.Find(&sheets) c.JSON(200, gin.H{"sheets": sheets}) } func getSheet(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var s Sheet if db.First(&s, "id = ?", id).Error != nil { c.JSON(200, gin.H{"sheet": nil}) return } c.JSON(200, gin.H{"sheet": s}) } func createSheet(c *gin.Context, db *gorm.DB) { var input struct { Name string `json:"name"` Cells json.RawMessage `json:"cells"` TemplateID uuid.UUID `json:"template_id"` } if c.BindJSON(&input) != nil { return } s := Sheet{Name: input.Name, Cells: JSON(input.Cells), TemplateID: input.TemplateID} db.Create(&s) c.JSON(200, gin.H{"id": s.ID}) } func patchSheet(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var s Sheet if db.First(&s, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Sheet not found"}) return } var input struct { Name *string `json:"name"` Cells *json.RawMessage `json:"cells"` } if c.BindJSON(&input) != nil { return } updates := map[string]interface{}{} if input.Name != nil { updates["name"] = *input.Name } if input.Cells != nil { updates["cells"] = JSON(*input.Cells) } if len(updates) > 0 { db.Model(&s).Updates(updates) } c.JSON(200, gin.H{"id": s.ID}) } // --- Attributes --- func getAttributes(c *gin.Context, db *gorm.DB) { var attrs []Attribute db.Find(&attrs) c.JSON(200, gin.H{"attributes": attrs}) } func createAttribute(c *gin.Context, db *gorm.DB) { var input struct { Name string `json:"name"` IsRequired bool `json:"is_required"` } if c.BindJSON(&input) != nil { return } var existing Attribute if db.Where("name = ?", input.Name).First(&existing).Error == nil { c.JSON(400, gin.H{"detail": fmt.Sprintf("Атрибут с именем '%s' уже существует", input.Name)}) return } a := Attribute{Name: input.Name, IsRequired: input.IsRequired} db.Create(&a) c.JSON(200, gin.H{"id": a.ID}) } func getAttributeValues(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var attr Attribute if db.First(&attr, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Attribute not found"}) return } type valCount struct { Value string `json:"value"` UsageCount int `json:"usage_count"` } var values []valCount db.Model(&TemplateAttribute{}). Select("value, count(template_id) as usage_count"). Where("attribute_id = ? AND value IS NOT NULL", id). Group("value"). Order("usage_count DESC"). Find(&values) c.JSON(200, gin.H{ "attribute_id": attr.ID, "attribute_name": attr.Name, "values": values, "total_count": len(values), }) } // --- Standards --- func getStandards(c *gin.Context, db *gorm.DB) { var standards []Standard db.Find(&standards) c.JSON(200, gin.H{"standards": standards}) } func getStandard(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var s Standard if db.First(&s, "id = ?", id).Error != nil { c.JSON(200, gin.H{"standard": nil}) return } c.JSON(200, gin.H{"standard": s}) } func createStandard(c *gin.Context, db *gorm.DB) { var input struct { Name string `json:"name"` ProtocolName string `json:"protocol_name"` RegistryNumber string `json:"registry_number"` Range string `json:"range"` Accuracy string `json:"accuracy"` ValidUntil string `json:"valid_until"` } if c.BindJSON(&input) != nil { return } validUntil, _ := time.Parse("2006-01-02", input.ValidUntil) s := Standard{ Name: input.Name, ProtocolName: input.ProtocolName, RegistryNumber: input.RegistryNumber, Range: input.Range, Accuracy: input.Accuracy, ValidUntil: validUntil, } db.Create(&s) c.JSON(200, gin.H{"id": s.ID}) } func patchStandard(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var s Standard if db.First(&s, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Standard not found"}) return } var raw map[string]interface{} if c.BindJSON(&raw) != nil { return } db.Model(&s).Updates(raw) c.JSON(200, gin.H{"id": s.ID}) } func deleteStandard(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } res := db.Delete(&Standard{}, "id = ?", id) if res.RowsAffected == 0 { c.JSON(404, gin.H{"detail": "Standard not found"}) return } c.JSON(200, gin.H{"message": "Standard deleted successfully"}) } // --- Laboratories --- func getLaboratories(c *gin.Context, db *gorm.DB) { var labs []Laboratory db.Preload("ConditionTypes").Find(&labs) c.JSON(200, gin.H{"laboratories": labs}) } func getLaboratory(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var lab Laboratory if db.Preload("ConditionTypes").First(&lab, "id = ?", id).Error != nil { c.JSON(200, gin.H{"laboratory": nil}) return } c.JSON(200, gin.H{"laboratory": lab}) } // --- Daily Conditions --- func getDailyConditions(c *gin.Context, db *gorm.DB) { q := db.Model(&DailyCondition{}) if lid := c.Query("laboratory_id"); lid != "" { q = q.Where("laboratory_id = ?", lid) } if sd := c.Query("start_date"); sd != "" { q = q.Where("measurement_date >= ?", sd) } if ed := c.Query("end_date"); ed != "" { q = q.Where("measurement_date <= ?", ed) } var dcs []DailyCondition q.Find(&dcs) c.JSON(200, gin.H{"daily_conditions": dcs}) } func getDailyCondition(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var dc DailyCondition if db.First(&dc, "id = ?", id).Error != nil { c.JSON(200, gin.H{"daily_condition": nil}) return } c.JSON(200, gin.H{"daily_condition": dc}) } func createDailyCondition(c *gin.Context, db *gorm.DB) { var input struct { LaboratoryID uuid.UUID `json:"laboratory_id"` MeasurementDate string `json:"measurement_date"` Conditions json.RawMessage `json:"conditions"` } if c.BindJSON(&input) != nil { return } var lab Laboratory if db.First(&lab, "id = ?", input.LaboratoryID).Error != nil { c.JSON(404, gin.H{"detail": "Laboratory not found"}) return } md, _ := time.Parse("2006-01-02", input.MeasurementDate) dc := DailyCondition{LaboratoryID: input.LaboratoryID, MeasurementDate: md, Conditions: JSON(input.Conditions)} db.Create(&dc) c.JSON(200, gin.H{"id": dc.ID}) } func updateDailyCondition(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var dc DailyCondition if db.First(&dc, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Daily condition not found"}) return } var raw map[string]interface{} if c.BindJSON(&raw) != nil { return } db.Model(&dc).Updates(raw) c.JSON(200, gin.H{"id": dc.ID}) } func deleteDailyCondition(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } res := db.Delete(&DailyCondition{}, "id = ?", id) if res.RowsAffected == 0 { c.JSON(404, gin.H{"detail": "Daily condition not found"}) return } c.JSON(200, gin.H{"message": "Daily condition deleted successfully"}) } // --- Groups --- func getGroups(c *gin.Context, db *gorm.DB) { var groups []Group db.Order("\"order\", name").Find(&groups) c.JSON(200, gin.H{"groups": groups}) } func getGroup(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var g Group if db.First(&g, "id = ?", id).Error != nil { c.JSON(200, gin.H{"group": nil}) return } c.JSON(200, gin.H{"group": g}) } func createGroup(c *gin.Context, db *gorm.DB) { var input struct { Name string `json:"name"` Order float64 `json:"order"` IsVisible *bool `json:"is_visible"` } if c.BindJSON(&input) != nil { return } isVisible := true if input.IsVisible != nil { isVisible = *input.IsVisible } g := Group{Name: input.Name, Order: input.Order, IsVisible: isVisible} db.Create(&g) c.JSON(200, gin.H{"id": g.ID}) } func patchGroup(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } var g Group if db.First(&g, "id = ?", id).Error != nil { c.JSON(404, gin.H{"detail": "Group not found"}) return } var raw map[string]interface{} if c.BindJSON(&raw) != nil { return } db.Model(&g).Updates(raw) db.First(&g, "id = ?", id) // reload c.JSON(200, g) } func deleteGroup(c *gin.Context, db *gorm.DB) { id, ok := parseUUID(c, "id") if !ok { return } res := db.Delete(&Group{}, "id = ?", id) c.JSON(200, gin.H{"success": res.RowsAffected > 0}) } // --- Protocols --- func createProtocol(c *gin.Context, db *gorm.DB) { var input struct { TemplateID uuid.UUID `json:"template_id"` FileID uuid.UUID `json:"file_id"` CellsToUpdate map[string]interface{} `json:"cells_to_update"` PrintArea string `json:"print_area"` } if c.BindJSON(&input) != nil { return } var file File if db.First(&file, "id = ?", input.FileID).Error != nil { c.JSON(404, gin.H{"detail": "File not found"}) return } modified, err := FillCells(input.CellsToUpdate, file.Content, input.PrintArea) if err != nil { c.JSON(500, gin.H{"detail": fmt.Sprintf("Excel error: %v", err)}) return } protocolID := uuid.New() c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=protocol_%s.xlsx", protocolID)) c.Data(200, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", modified) }