Import initail de calcul-astreintes v0.9.4 pour passage en paycheck 1.0
This commit is contained in:
205
internal/importer/xlsx.go
Normal file
205
internal/importer/xlsx.go
Normal file
@@ -0,0 +1,205 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user