Files
paycheck/internal/importer/xlsx.go

206 lines
4.6 KiB
Go

package importer
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/xuri/excelize/v2"
"git.dumerain.org/alban/paycheck/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
}