diff --git a/controllers/members.go b/controllers/members.go index c28b019..93cd7f6 100644 --- a/controllers/members.go +++ b/controllers/members.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "time" "git.readonly.ch/bouzoure/pop-camarades/helpers" @@ -205,6 +206,248 @@ func Members(c *fiber.Ctx) error { }) } +func MembersExport(c *fiber.Ctx) error { + userid, err := helpers.GetSessionUserId(c) + if err != nil { + return err + } + + allowedSections, err := helpers.PermissionsGetSections( + userid, "show_member", + ) + if err != nil { + return err + } + + allowedSectionsArchived, err := helpers.PermissionsGetSections( + userid, "show_archived_member", + ) + if err != nil { + return err + } + + permShow := (len(allowedSections) > 0) + permShowArchived := (len(allowedSectionsArchived) > 0) + + if !permShow && !permShowArchived { + return fiber.NewError(fiber.StatusForbidden, "Forbidden") + } + + db, err := helpers.GetDatabase() + if err != nil { + return err + } + + filterSection := c.Query("se") + filterArchive := c.Query("a") + filterSearch := c.Query("s") + + sqlFilterSections := allowedSections + sqlFilterAppend := "IS NULL" + if filterArchive == "1" { + sqlFilterAppend = "IS NOT NULL" + sqlFilterSections = allowedSectionsArchived + } + + var filterSectionUint uint + if len(filterSection) > 0 { + filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) + if err == nil { + for _, s := range sqlFilterSections { + if s == uint(filterSectionUint64) { + filterSectionUint = uint(filterSectionUint64) + } + } + } + } + + if filterSectionUint > 0 { + sqlFilterAppend = fmt.Sprintf( + "%s AND section_id = %d", + sqlFilterAppend, + filterSectionUint, + ) + } + + var sqlFilterSearch string + if len(filterSearch) > 0 { + sqlFilterSearch = fmt.Sprintf( + "%%%s%%", filterSearch, + ) + } + + var people []models.Person + if len(filterSearch) > 0 { + result := db.Unscoped().Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload("Section").Find( + &people, + fmt.Sprintf( + "is_member = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } else { + result := db.Unscoped().Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload("Section").Find( + &people, + fmt.Sprintf( + "is_member = ? AND section_id IN ? AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } + + var fields []models.Field + db.Order("name collate nocase asc").Preload( + "List", + ).Find( + &fields, "person_type = ?", "member", + ) + + var fieldValues []models.FieldValue + db.Preload("ListItem").Find(&fieldValues) + + c.Set("Content-Type", "text/csv") + c.Set("Content-Disposition", fmt.Sprintf( + "attachment;filename=members_%s.csv", + time.Now().Format(time.RFC3339), + )) + + csvFields := []string{ + "first_name", + "last_name", + "email", + "phone", + "mobile", + "address1", + "address2", + "postal_code", + "city", + "section_id", + "section_name", + } + + for _, field := range fields { + csvFields = append(csvFields, field.Name) + } + + for key, csvField := range csvFields { + csvField = strings.ReplaceAll(csvField, "\r", "") + csvField = strings.ReplaceAll(csvField, "\n", " ") + csvField = strings.ReplaceAll(csvField, ",", "_") + csvField = strings.ReplaceAll(csvField, ";", "_") + + if key+1 == len(csvFields) { + c.Writef("\"%s\"\n", csvField) + } else { + c.Writef("\"%s\";", csvField) + } + } + + for _, person := range people { + c.Writef("\"%s\";", person.FirstName) + c.Writef("\"%s\";", person.LastName) + c.Writef("\"%s\";", person.Email) + c.Writef("\"%s\";", person.Phone) + c.Writef("\"%s\";", person.Mobile) + c.Writef("\"%s\";", person.Address1) + c.Writef("\"%s\";", person.Address2) + c.Writef("\"%s\";", person.PostalCode) + c.Writef("\"%s\";", person.City) + c.Writef("\"%d\";", person.SectionID) + + if len(fields) > 0 { + c.Writef("\"%s\";", person.Section.Name) + } else { + c.Writef("\"%s\"\n", person.Section.Name) + } + + for key, field := range fields { + endLine := ";" + if key+1 == len(fields) { + endLine = "\n" + } + + countMulti := 0 + found := false + for _, value := range fieldValues { + if value.FieldID == field.ID && value.PersonID == person.ID { + found = true + + if field.FieldType == "text" || field.FieldType == "longtext" { + + text := value.ValueString.String + text = strings.ReplaceAll(text, "\r", "") + text = strings.ReplaceAll(text, "\n", " ") + text = strings.ReplaceAll(text, ",", "_") + text = strings.ReplaceAll(text, ";", "_") + + c.Writef("\"%s\"%s", text, endLine) + + } else if field.FieldType == "number" { + + if value.ValueInt.Valid { + c.Writef("\"%d\"%s", value.ValueInt.Int64, endLine) + } else { + c.Writef("\"\"%s", endLine) + } + + } else if field.FieldType == "date" { + + if value.ValueDate.Valid { + date := value.ValueDate.Time.Format("2006-01-02") + c.Writef("\"%s\"%s", date, endLine) + } else { + c.Writef("\"\"%s", endLine) + } + + } else if field.FieldType == "list" { + + if field.List.Multi { + + if countMulti == 0 { + c.Writef("\"") + } else { + c.Writef(",") + } + + c.Writef("%s", value.ListItem.Value) + countMulti++ + + } else { + c.Writef("\"%s\"%s", value.ListItem.Value, endLine) + } + + } + + } + } + + if countMulti > 0 { + c.Writef("\"%s", endLine) + } + + if !found { + c.Writef("\"\"%s", endLine) + } + } + } + + return nil +} + func MemberShow(c *fiber.Ctx) error { id := c.Params("id") diff --git a/main.go b/main.go index cd7a745..d1ce997 100644 --- a/main.go +++ b/main.go @@ -123,6 +123,7 @@ func main() { // Members app.Get("/members", controllers.Members) + app.Get("/members/export", controllers.MembersExport) app.Get("/members/:id", controllers.MemberShow) app.Get("/members/add", controllers.MemberAdd) app.Post("/members/add", controllers.MemberAdd) diff --git a/views/people.html b/views/people.html index 1a2d211..1dfc381 100644 --- a/views/people.html +++ b/views/people.html @@ -25,7 +25,7 @@ Ajouter - + Exporter @@ -36,7 +36,7 @@ Ajouter - + Exporter @@ -45,6 +45,8 @@
+ +
@@ -192,12 +194,12 @@ {% else %}
  • - +
  • - +
  • @@ -212,7 +214,7 @@ class="page-link" {% endif %} - href="?p={{ i }}" + href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ i }}" > {{ i }} @@ -232,12 +234,12 @@ {% else %}
  • - +
  • - +