206 lines
4.6 KiB
Go
206 lines
4.6 KiB
Go
package importer
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/xuri/excelize/v2"
|
|
|
|
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
|
)
|
|
|
|
// Feuille de mois acceptée :
|
|
// - YYYY-MM (ex: 2025-01)
|
|
// - MM-YYYY (ex: 01-2025)
|
|
var sheetRe = regexp.MustCompile(`^(\d{4})-(\d{2})$|^(\d{2})-(\d{4})$`)
|
|
|
|
func normalizeYearMonth(sheet string) (string, bool) {
|
|
sheet = strings.TrimSpace(sheet)
|
|
m := sheetRe.FindStringSubmatch(sheet)
|
|
if m == nil {
|
|
return "", false
|
|
}
|
|
// YYYY-MM
|
|
if m[1] != "" && m[2] != "" {
|
|
return fmt.Sprintf("%s-%s", m[1], m[2]), true
|
|
}
|
|
// MM-YYYY
|
|
if m[3] != "" && m[4] != "" {
|
|
return fmt.Sprintf("%s-%s", m[4], m[3]), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// parseNumberFR convertit "1,92" / "1.92" / "" -> float64
|
|
func parseNumberFR(s string) float64 {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
s = strings.ReplaceAll(s, "\u00a0", "")
|
|
s = strings.ReplaceAll(s, " ", "")
|
|
s = strings.ReplaceAll(s, ",", ".")
|
|
f, _ := strconv.ParseFloat(s, 64)
|
|
return f
|
|
}
|
|
|
|
func isMatriculeCell(s string) bool {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return false
|
|
}
|
|
for _, r := range s {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FindMonthSheet: prend la première feuille qui match YYYY-MM ou MM-YYYY, sinon la première feuille.
|
|
func FindMonthSheet(f *excelize.File) (string, error) {
|
|
sheets := f.GetSheetList()
|
|
for _, sh := range sheets {
|
|
if _, ok := normalizeYearMonth(sh); ok {
|
|
return strings.TrimSpace(sh), nil
|
|
}
|
|
}
|
|
if len(sheets) == 0 {
|
|
return "", fmt.Errorf("aucune feuille trouvée dans le fichier")
|
|
}
|
|
return sheets[0], nil
|
|
}
|
|
|
|
// ParseAgentsAndTotals lit la feuille détectée et renvoie agents + totaux.
|
|
// (Le mois est géré côté app/app.go via normalizeYearMonth)
|
|
func ParseAgentsAndTotals(path string) ([]models.Agent, map[string]models.AgentTotals, error) {
|
|
f, err := excelize.OpenFile(path)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
sheet, err := FindMonthSheet(f)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
rows, err := f.GetRows(sheet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
agents := []models.Agent{}
|
|
totals := map[string]models.AgentTotals{}
|
|
|
|
// Dedup P_DIM-JF par agent (par date)
|
|
dimDates := map[string]map[string]struct{}{}
|
|
currentMatricule := ""
|
|
|
|
cell := func(col string, excelRow int) string {
|
|
v, _ := f.GetCellValue(sheet, fmt.Sprintf("%s%d", col, excelRow))
|
|
return v
|
|
}
|
|
|
|
for i := 0; i < len(rows); i++ {
|
|
row := rows[i]
|
|
|
|
colA := ""
|
|
if len(row) >= 1 {
|
|
colA = row[0]
|
|
}
|
|
|
|
if isMatriculeCell(colA) {
|
|
currentMatricule = strings.TrimSpace(colA)
|
|
|
|
if _, ok := totals[currentMatricule]; !ok {
|
|
fullName := ""
|
|
if len(row) >= 2 {
|
|
fullName = strings.TrimSpace(row[1]) // Col B = "NOM Prenom"
|
|
}
|
|
|
|
nom, prenom := "", ""
|
|
if fullName != "" {
|
|
parts := strings.Fields(fullName)
|
|
if len(parts) >= 1 {
|
|
nom = parts[0]
|
|
}
|
|
if len(parts) >= 2 {
|
|
prenom = strings.Join(parts[1:], " ")
|
|
}
|
|
}
|
|
|
|
displayName := strings.TrimSpace(strings.Join([]string{nom, prenom}, " "))
|
|
if displayName == "" {
|
|
displayName = currentMatricule
|
|
}
|
|
display := fmt.Sprintf("%s (%s)", displayName, currentMatricule)
|
|
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
|
|
totalExcelRow := i // ligne au-dessus en Excel (cf logique actuelle)
|
|
|
|
q471 := parseNumberFR(cell("G", totalExcelRow))
|
|
q456 := parseNumberFR(cell("L", totalExcelRow))
|
|
q459 := parseNumberFR(cell("M", totalExcelRow))
|
|
q458 := parseNumberFR(cell("N", totalExcelRow))
|
|
|
|
agents = append(agents, models.Agent{
|
|
Matricule: currentMatricule,
|
|
Nom: nom,
|
|
Prenom: prenom,
|
|
Display: display,
|
|
})
|
|
|
|
totals[currentMatricule] = models.AgentTotals{
|
|
Matricule: currentMatricule,
|
|
Q471: q471,
|
|
Q456: q456,
|
|
Q459: q459,
|
|
Q458: q458,
|
|
NbDimFerie: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
if currentMatricule != "" {
|
|
typeLigne := ""
|
|
if len(row) >= 3 {
|
|
typeLigne = strings.TrimSpace(row[2]) // Col C
|
|
}
|
|
if typeLigne == "P_DIM-JF" {
|
|
dateStr := ""
|
|
if len(row) >= 4 {
|
|
dateStr = strings.TrimSpace(row[3]) // Col D
|
|
}
|
|
if _, ok := dimDates[currentMatricule]; !ok {
|
|
dimDates[currentMatricule] = map[string]struct{}{}
|
|
}
|
|
if dateStr != "" {
|
|
dimDates[currentMatricule][dateStr] = struct{}{}
|
|
} else {
|
|
dimDates[currentMatricule][fmt.Sprintf("row-%d", i+1)] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for mat, t := range totals {
|
|
if s, ok := dimDates[mat]; ok {
|
|
t.NbDimFerie = len(s)
|
|
totals[mat] = t
|
|
}
|
|
}
|
|
|
|
sort.Slice(agents, func(i, j int) bool {
|
|
return strings.ToLower(agents[i].Display) < strings.ToLower(agents[j].Display)
|
|
})
|
|
|
|
return agents, totals, nil
|
|
}
|