Members: Export CSV
This commit is contained in:
parent
c98030a197
commit
f2afffd818
3 changed files with 253 additions and 7 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
"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 {
|
func MemberShow(c *fiber.Ctx) error {
|
||||||
id := c.Params("id")
|
id := c.Params("id")
|
||||||
|
|
||||||
|
|
|
||||||
1
main.go
1
main.go
|
|
@ -123,6 +123,7 @@ func main() {
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
app.Get("/members", controllers.Members)
|
app.Get("/members", controllers.Members)
|
||||||
|
app.Get("/members/export", controllers.MembersExport)
|
||||||
app.Get("/members/:id<int;min(0)>", controllers.MemberShow)
|
app.Get("/members/:id<int;min(0)>", controllers.MemberShow)
|
||||||
app.Get("/members/add", controllers.MemberAdd)
|
app.Get("/members/add", controllers.MemberAdd)
|
||||||
app.Post("/members/add", controllers.MemberAdd)
|
app.Post("/members/add", controllers.MemberAdd)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<i class="bi-plus-lg"></i>
|
<i class="bi-plus-lg"></i>
|
||||||
Ajouter
|
Ajouter
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-primary" href="javascript:;">
|
<a class="btn btn-primary" href="/members/export?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}">
|
||||||
<i class="bi-filetype-csv"></i>
|
<i class="bi-filetype-csv"></i>
|
||||||
Exporter
|
Exporter
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<i class="bi-plus-lg"></i>
|
<i class="bi-plus-lg"></i>
|
||||||
Ajouter
|
Ajouter
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-primary" href="javascript:;">
|
<a class="btn btn-primary" href="/contacts/export?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}">
|
||||||
<i class="bi-filetype-csv"></i>
|
<i class="bi-filetype-csv"></i>
|
||||||
Exporter
|
Exporter
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -45,6 +45,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="filters" method="get" class="my-3">
|
<form id="filters" method="get" class="my-3">
|
||||||
|
<input type="hidden" name="p" value="{{ Page }}">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-lg-3 mb-2">
|
<div class="col-6 col-lg-3 mb-2">
|
||||||
|
|
||||||
|
|
@ -192,12 +194,12 @@
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?p=1">
|
<a class="page-link" href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p=1">
|
||||||
<i class="bi-rewind"></i>
|
<i class="bi-rewind"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?p={{ Page - 1 }}">
|
<a class="page-link" href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ Page - 1 }}">
|
||||||
<i class="bi-caret-left"></i>
|
<i class="bi-caret-left"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -212,7 +214,7 @@
|
||||||
class="page-link"
|
class="page-link"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
href="?p={{ i }}"
|
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ i }}"
|
||||||
>
|
>
|
||||||
{{ i }}
|
{{ i }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -232,12 +234,12 @@
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?p={{ Page + 1 }}">
|
<a class="page-link" href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ Page + 1 }}">
|
||||||
<i class="bi-caret-right"></i>
|
<i class="bi-caret-right"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?p={{ MaxPages }}">
|
<a class="page-link" href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ MaxPages }}">
|
||||||
<i class="bi-fast-forward"></i>
|
<i class="bi-fast-forward"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue