Import initail de calcul-astreintes v0.9.4 pour passage en paycheck 1.0
This commit is contained in:
214
app/app.go
Normal file
214
app/app.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"github.com/xuri/excelize/v2"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/calc"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/importer"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/pdf"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/profiles"
|
||||
)
|
||||
|
||||
var ymRe = regexp.MustCompile(`^(\d{4})-(\d{2})$|^(\d{2})-(\d{4})$`)
|
||||
|
||||
func normalizeYearMonth(sheet string) (string, bool) {
|
||||
sheet = strings.TrimSpace(sheet)
|
||||
m := ymRe.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
|
||||
}
|
||||
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
|
||||
repo *profiles.Repo
|
||||
rules *models.GlobalRules
|
||||
|
||||
importMu sync.RWMutex
|
||||
agents []models.Agent
|
||||
totals map[string]models.AgentTotals
|
||||
|
||||
lastImportYearMonth string
|
||||
lastImportFileBase string
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
repo, rules, err := profiles.NewEmbeddedRepo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &App{
|
||||
repo: repo,
|
||||
rules: rules,
|
||||
totals: map[string]models.AgentTotals{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
}
|
||||
|
||||
func (a *App) ListProfiles() ([]models.ProfileListItem, error) {
|
||||
return a.repo.List()
|
||||
}
|
||||
|
||||
func (a *App) Calculate(req models.CalculateRequest) (models.CalculateResponse, error) {
|
||||
profile, err := a.repo.Get(req.ProfileID)
|
||||
if err != nil {
|
||||
return models.CalculateResponse{}, err
|
||||
}
|
||||
return calc.Compute(req, profile, *a.rules), nil
|
||||
}
|
||||
|
||||
// ImportMonthlyXLSX : import Excel + stockage en mémoire
|
||||
// ✅ Mois = nom de feuille (YYYY-MM ou MM-YYYY) normalisé en YYYY-MM
|
||||
func (a *App) ImportMonthlyXLSX() ([]models.Agent, error) {
|
||||
if a.ctx == nil {
|
||||
return nil, fmt.Errorf("contexte Wails non initialisé")
|
||||
}
|
||||
|
||||
path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
|
||||
Title: "Importer le fichier Excel des astreintes",
|
||||
Filters: []runtime.FileFilter{
|
||||
{DisplayName: "Excel (.xlsx)", Pattern: "*.xlsx"},
|
||||
},
|
||||
})
|
||||
if err != nil || path == "" {
|
||||
return nil, fmt.Errorf("import annulé")
|
||||
}
|
||||
|
||||
// Détection feuille
|
||||
f, err := excelize.OpenFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
sheet, err := importer.FindMonthSheet(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yearMonth, ok := normalizeYearMonth(sheet)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("nom de feuille invalide pour le mois: %s (attendu YYYY-MM ou MM-YYYY)", sheet)
|
||||
}
|
||||
|
||||
agents, totals, err := importer.ParseAgentsAndTotals(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.importMu.Lock()
|
||||
a.agents = agents
|
||||
a.totals = totals
|
||||
a.lastImportYearMonth = yearMonth // ✅ toujours en YYYY-MM
|
||||
a.lastImportFileBase = filepath.Base(path)
|
||||
a.importMu.Unlock()
|
||||
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
func (a *App) GetImportedTotals(matricule string) (models.AgentTotals, error) {
|
||||
a.importMu.RLock()
|
||||
defer a.importMu.RUnlock()
|
||||
|
||||
t, ok := a.totals[matricule]
|
||||
if !ok {
|
||||
return models.AgentTotals{}, fmt.Errorf("matricule introuvable: %s", matricule)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) GetLastImportInfo() (map[string]string, error) {
|
||||
a.importMu.RLock()
|
||||
defer a.importMu.RUnlock()
|
||||
|
||||
return map[string]string{
|
||||
"yearMonth": a.lastImportYearMonth, // ✅ normalisé
|
||||
"fileBase": a.lastImportFileBase,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) ExportPDF(payload models.ExportPDFRequest) (models.ExportPDFResponse, error) {
|
||||
profile, err := a.repo.Get(payload.Request.ProfileID)
|
||||
if err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
res := calc.Compute(payload.Request, profile, *a.rules)
|
||||
defaultName := defaultPDFNameV051(payload.Meta)
|
||||
|
||||
savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
|
||||
Title: "Exporter le calcul en PDF",
|
||||
DefaultFilename: defaultName,
|
||||
Filters: []runtime.FileFilter{
|
||||
{DisplayName: "PDF (*.pdf)", Pattern: "*.pdf"},
|
||||
},
|
||||
})
|
||||
if err != nil || savePath == "" {
|
||||
return models.ExportPDFResponse{}, fmt.Errorf("export annulé")
|
||||
}
|
||||
|
||||
if filepath.Ext(strings.ToLower(savePath)) != ".pdf" {
|
||||
savePath += ".pdf"
|
||||
}
|
||||
|
||||
data, err := pdf.BuildPDF(payload.Meta, profile.Label, payload.Request, res)
|
||||
if err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(savePath, data, 0o644); err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
_ = openWithDefaultApp(savePath)
|
||||
return models.ExportPDFResponse{Path: savePath}, nil
|
||||
}
|
||||
|
||||
func defaultPDFNameV051(meta models.ExportPDFMeta) string {
|
||||
ts := time.Now().Format("2006-01-02-15h04")
|
||||
return fmt.Sprintf(
|
||||
"%s-%s-%s-%s.pdf",
|
||||
ts,
|
||||
pdf.SanitizeFilename(meta.Nom),
|
||||
pdf.SanitizeFilename(meta.Prenom),
|
||||
pdf.SanitizeFilename(meta.Matricule),
|
||||
)
|
||||
}
|
||||
|
||||
func openWithDefaultApp(path string) error {
|
||||
switch goruntime.GOOS {
|
||||
case "windows":
|
||||
return exec.Command("cmd", "/c", "start", "", path).Start()
|
||||
case "darwin":
|
||||
return exec.Command("open", path).Start()
|
||||
default:
|
||||
return exec.Command("xdg-open", path).Start()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user