add achats import and hardware analyse
This commit is contained in:
181
backend/internal/handlers/analyse_hardware.go
Normal file
181
backend/internal/handlers/analyse_hardware.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type analyseHardwareRequest struct {
|
||||
Type string `json:"type"`
|
||||
Texte string `json:"texte"`
|
||||
}
|
||||
|
||||
type lspciDevice struct {
|
||||
Slot string `json:"slot"`
|
||||
Class string `json:"class"`
|
||||
Description string `json:"description"`
|
||||
Subsystem string `json:"subsystem,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
Modules []string `json:"modules,omitempty"`
|
||||
Raw []string `json:"raw,omitempty"`
|
||||
}
|
||||
|
||||
type lsusbDevice struct {
|
||||
Bus string `json:"bus"`
|
||||
Device string `json:"device"`
|
||||
VendorID string `json:"vendor_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
Description string `json:"description"`
|
||||
Raw string `json:"raw,omitempty"`
|
||||
}
|
||||
|
||||
type analyseHardwareResponse struct {
|
||||
Type string `json:"type"`
|
||||
Count int `json:"count"`
|
||||
Devices []interface{} `json:"devices"`
|
||||
}
|
||||
|
||||
// @Summary Analyser une sortie hardware (lspci/lsusb)
|
||||
// @Tags Analyse
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body analyseHardwareRequest true "Texte a analyser"
|
||||
// @Success 200 {object} analyseHardwareResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Router /analyse-hardware [post]
|
||||
func (h *Handler) AnalyseHardware(c *gin.Context) {
|
||||
var req analyseHardwareRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "donnees invalides"})
|
||||
return
|
||||
}
|
||||
req.Type = strings.ToLower(strings.TrimSpace(req.Type))
|
||||
req.Texte = strings.TrimSpace(req.Texte)
|
||||
if req.Texte == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "texte vide"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Type == "" {
|
||||
req.Type = detectHardwareType(req.Texte)
|
||||
}
|
||||
|
||||
switch req.Type {
|
||||
case "lspci":
|
||||
devices := parseLspci(req.Texte)
|
||||
c.JSON(http.StatusOK, analyseHardwareResponse{
|
||||
Type: "lspci",
|
||||
Count: len(devices),
|
||||
Devices: toInterfaceSlice(devices),
|
||||
})
|
||||
case "lsusb":
|
||||
devices := parseLsusb(req.Texte)
|
||||
c.JSON(http.StatusOK, analyseHardwareResponse{
|
||||
Type: "lsusb",
|
||||
Count: len(devices),
|
||||
Devices: toInterfaceSlice(devices),
|
||||
})
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "type non supporte"})
|
||||
}
|
||||
}
|
||||
|
||||
func detectHardwareType(text string) string {
|
||||
if strings.Contains(text, "Bus ") && strings.Contains(text, "ID ") {
|
||||
return "lsusb"
|
||||
}
|
||||
return "lspci"
|
||||
}
|
||||
|
||||
func parseLspci(text string) []lspciDevice {
|
||||
lines := strings.Split(text, "\n")
|
||||
var devices []lspciDevice
|
||||
re := regexp.MustCompile(`^(?:[0-9a-fA-F]{4}:)?([0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])\s+([^:]+):\s+(.+)$`)
|
||||
|
||||
var current *lspciDevice
|
||||
for _, raw := range lines {
|
||||
line := strings.TrimRight(raw, "\r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if matches := re.FindStringSubmatch(line); matches != nil {
|
||||
if current != nil {
|
||||
devices = append(devices, *current)
|
||||
}
|
||||
current = &lspciDevice{
|
||||
Slot: matches[1],
|
||||
Class: strings.TrimSpace(matches[2]),
|
||||
Description: strings.TrimSpace(matches[3]),
|
||||
Raw: []string{line},
|
||||
}
|
||||
continue
|
||||
}
|
||||
if current == nil {
|
||||
continue
|
||||
}
|
||||
trimmed := strings.TrimSpace(line)
|
||||
current.Raw = append(current.Raw, line)
|
||||
if strings.HasPrefix(trimmed, "Subsystem:") {
|
||||
current.Subsystem = strings.TrimSpace(strings.TrimPrefix(trimmed, "Subsystem:"))
|
||||
} else if strings.HasPrefix(trimmed, "Kernel driver in use:") {
|
||||
current.Driver = strings.TrimSpace(strings.TrimPrefix(trimmed, "Kernel driver in use:"))
|
||||
} else if strings.HasPrefix(trimmed, "Kernel modules:") {
|
||||
value := strings.TrimSpace(strings.TrimPrefix(trimmed, "Kernel modules:"))
|
||||
if value != "" {
|
||||
current.Modules = splitCSV(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if current != nil {
|
||||
devices = append(devices, *current)
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
func parseLsusb(text string) []lsusbDevice {
|
||||
lines := strings.Split(text, "\n")
|
||||
var devices []lsusbDevice
|
||||
re := regexp.MustCompile(`^Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\s*(.*)$`)
|
||||
for _, raw := range lines {
|
||||
line := strings.TrimRight(raw, "\r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if matches == nil {
|
||||
continue
|
||||
}
|
||||
devices = append(devices, lsusbDevice{
|
||||
Bus: matches[1],
|
||||
Device: matches[2],
|
||||
VendorID: matches[3],
|
||||
ProductID: matches[4],
|
||||
Description: strings.TrimSpace(matches[5]),
|
||||
Raw: line,
|
||||
})
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
func splitCSV(value string) []string {
|
||||
parts := strings.Split(value, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toInterfaceSlice[T any](items []T) []interface{} {
|
||||
out := make([]interface{}, len(items))
|
||||
for i, item := range items {
|
||||
out[i] = item
|
||||
}
|
||||
return out
|
||||
}
|
||||
471
backend/internal/handlers/imports.go
Normal file
471
backend/internal/handlers/imports.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maison43.duckdns.org/gilles/matosbox/internal/data/ent/objet"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type importAchat struct {
|
||||
Nom string `json:"nom"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Quantite int `json:"quantite,omitempty"`
|
||||
PrixAchat float64 `json:"prix_achat,omitempty"`
|
||||
DateAchat string `json:"date_achat,omitempty"`
|
||||
Boutique string `json:"boutique,omitempty"`
|
||||
NumeroSerie string `json:"numero_serie,omitempty"`
|
||||
NumeroModele string `json:"numero_modele,omitempty"`
|
||||
Fabricant string `json:"fabricant,omitempty"`
|
||||
Caracteristiques map[string]any `json:"caracteristiques,omitempty"`
|
||||
}
|
||||
|
||||
type importAchatPayload struct {
|
||||
Boutique string `json:"boutique"`
|
||||
Achats []importAchat `json:"achats"`
|
||||
}
|
||||
|
||||
type importErreur struct {
|
||||
Ligne int `json:"ligne,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Nom string `json:"nom,omitempty"`
|
||||
}
|
||||
|
||||
type importResultat struct {
|
||||
Importes int `json:"importes"`
|
||||
Crees int `json:"crees"`
|
||||
Doublons int `json:"doublons"`
|
||||
Erreurs []importErreur `json:"erreurs"`
|
||||
}
|
||||
|
||||
// @Summary Importer des achats (CSV/JSON)
|
||||
// @Tags Imports
|
||||
// @Accept multipart/form-data,json
|
||||
// @Produce json
|
||||
// @Param boutique formData string false "Boutique (amazon, aliexpress, generic)"
|
||||
// @Param fichier formData file false "Fichier CSV ou JSON"
|
||||
// @Success 200 {object} importResultat
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /imports/achats [post]
|
||||
func (h *Handler) ImportAchats(c *gin.Context) {
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
if strings.Contains(contentType, "application/json") {
|
||||
h.handleImportJSON(c)
|
||||
return
|
||||
}
|
||||
h.handleImportMultipart(c)
|
||||
}
|
||||
|
||||
func (h *Handler) handleImportJSON(c *gin.Context) {
|
||||
raw, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "lecture JSON impossible"})
|
||||
return
|
||||
}
|
||||
raw = bytes.TrimSpace(raw)
|
||||
if len(raw) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "corps JSON vide"})
|
||||
return
|
||||
}
|
||||
|
||||
var payload importAchatPayload
|
||||
if err := json.Unmarshal(raw, &payload); err == nil && len(payload.Achats) > 0 {
|
||||
if payload.Boutique != "" {
|
||||
for i := range payload.Achats {
|
||||
if payload.Achats[i].Boutique == "" {
|
||||
payload.Achats[i].Boutique = payload.Boutique
|
||||
}
|
||||
}
|
||||
}
|
||||
result := h.persistImportAchats(c, payload.Achats)
|
||||
c.JSON(http.StatusOK, result)
|
||||
return
|
||||
}
|
||||
|
||||
var achats []importAchat
|
||||
if err := json.Unmarshal(raw, &achats); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "format JSON invalide"})
|
||||
return
|
||||
}
|
||||
result := h.persistImportAchats(c, achats)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) handleImportMultipart(c *gin.Context) {
|
||||
boutique := strings.TrimSpace(c.PostForm("boutique"))
|
||||
file, header, err := c.Request.FormFile("fichier")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "fichier manquant"})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "lecture fichier impossible"})
|
||||
return
|
||||
}
|
||||
if len(data) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "fichier vide"})
|
||||
return
|
||||
}
|
||||
|
||||
extension := strings.ToLower(header.Filename)
|
||||
if strings.HasSuffix(extension, ".json") {
|
||||
var payload importAchatPayload
|
||||
if err := json.Unmarshal(data, &payload); err == nil && len(payload.Achats) > 0 {
|
||||
if boutique == "" {
|
||||
boutique = payload.Boutique
|
||||
}
|
||||
if boutique != "" {
|
||||
for i := range payload.Achats {
|
||||
if payload.Achats[i].Boutique == "" {
|
||||
payload.Achats[i].Boutique = boutique
|
||||
}
|
||||
}
|
||||
}
|
||||
result := h.persistImportAchats(c, payload.Achats)
|
||||
c.JSON(http.StatusOK, result)
|
||||
return
|
||||
}
|
||||
|
||||
var achats []importAchat
|
||||
if err := json.Unmarshal(data, &achats); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"erreur": "JSON invalide"})
|
||||
return
|
||||
}
|
||||
if boutique != "" {
|
||||
for i := range achats {
|
||||
if achats[i].Boutique == "" {
|
||||
achats[i].Boutique = boutique
|
||||
}
|
||||
}
|
||||
}
|
||||
result := h.persistImportAchats(c, achats)
|
||||
c.JSON(http.StatusOK, result)
|
||||
return
|
||||
}
|
||||
|
||||
achats, erreurs := parseCSVImports(data, boutique)
|
||||
result := h.persistImportAchats(c, achats)
|
||||
result.Erreurs = append(result.Erreurs, erreurs...)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) persistImportAchats(c *gin.Context, achats []importAchat) importResultat {
|
||||
result := importResultat{Importes: len(achats)}
|
||||
ctx := c.Request.Context()
|
||||
|
||||
for index, achat := range achats {
|
||||
line := index + 1
|
||||
achat.Nom = strings.TrimSpace(achat.Nom)
|
||||
if achat.Nom == "" {
|
||||
result.Erreurs = append(result.Erreurs, importErreur{
|
||||
Ligne: line,
|
||||
Message: "nom manquant",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if achat.Quantite <= 0 {
|
||||
achat.Quantite = 1
|
||||
}
|
||||
|
||||
var parsedDate *time.Time
|
||||
if achat.DateAchat != "" {
|
||||
value, err := parseImportDate(achat.DateAchat)
|
||||
if err != nil {
|
||||
result.Erreurs = append(result.Erreurs, importErreur{
|
||||
Ligne: line,
|
||||
Message: "date invalide",
|
||||
Nom: achat.Nom,
|
||||
})
|
||||
continue
|
||||
}
|
||||
parsedDate = &value
|
||||
}
|
||||
|
||||
if achat.PrixAchat < 0 {
|
||||
result.Erreurs = append(result.Erreurs, importErreur{
|
||||
Ligne: line,
|
||||
Message: "prix invalide",
|
||||
Nom: achat.Nom,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
duplicate, err := h.isDuplicateAchat(ctx, achat, parsedDate)
|
||||
if err != nil {
|
||||
result.Erreurs = append(result.Erreurs, importErreur{
|
||||
Ligne: line,
|
||||
Message: "verification doublon impossible",
|
||||
Nom: achat.Nom,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if duplicate {
|
||||
result.Doublons++
|
||||
continue
|
||||
}
|
||||
|
||||
create := h.client.Objet.Create().
|
||||
SetNom(achat.Nom).
|
||||
SetQuantite(achat.Quantite)
|
||||
|
||||
if achat.Description != "" {
|
||||
create.SetDescription(achat.Description)
|
||||
}
|
||||
if achat.PrixAchat > 0 {
|
||||
create.SetPrixAchat(achat.PrixAchat)
|
||||
}
|
||||
if parsedDate != nil {
|
||||
create.SetDateAchat(*parsedDate)
|
||||
}
|
||||
if achat.Boutique != "" {
|
||||
create.SetBoutique(achat.Boutique)
|
||||
}
|
||||
if achat.NumeroSerie != "" {
|
||||
create.SetNumeroSerie(achat.NumeroSerie)
|
||||
}
|
||||
if achat.NumeroModele != "" {
|
||||
create.SetNumeroModele(achat.NumeroModele)
|
||||
}
|
||||
if achat.Fabricant != "" {
|
||||
create.SetFabricant(achat.Fabricant)
|
||||
}
|
||||
if len(achat.Caracteristiques) > 0 {
|
||||
create.SetCaracteristiques(achat.Caracteristiques)
|
||||
}
|
||||
|
||||
if _, err := create.Save(ctx); err != nil {
|
||||
result.Erreurs = append(result.Erreurs, importErreur{
|
||||
Ligne: line,
|
||||
Message: "creation impossible",
|
||||
Nom: achat.Nom,
|
||||
})
|
||||
continue
|
||||
}
|
||||
result.Crees++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *Handler) isDuplicateAchat(ctx context.Context, achat importAchat, date *time.Time) (bool, error) {
|
||||
query := h.client.Objet.Query().Where(objet.NomEQ(achat.Nom))
|
||||
if achat.Boutique != "" {
|
||||
query = query.Where(objet.BoutiqueEQ(achat.Boutique))
|
||||
}
|
||||
if date != nil {
|
||||
query = query.Where(objet.DateAchatEQ(*date))
|
||||
}
|
||||
if achat.PrixAchat > 0 {
|
||||
query = query.Where(objet.PrixAchatEQ(achat.PrixAchat))
|
||||
}
|
||||
count, err := query.Count(ctx)
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func parseCSVImports(data []byte, boutique string) ([]importAchat, []importErreur) {
|
||||
separator := detectCSVSeparator(data)
|
||||
reader := csv.NewReader(bytes.NewReader(data))
|
||||
reader.Comma = separator
|
||||
reader.FieldsPerRecord = -1
|
||||
reader.TrimLeadingSpace = true
|
||||
|
||||
header, err := reader.Read()
|
||||
if err != nil {
|
||||
return nil, []importErreur{{Message: "lecture entete impossible"}}
|
||||
}
|
||||
|
||||
headerIndex := mapCSVHeader(header, boutique)
|
||||
if len(headerIndex) == 0 {
|
||||
return nil, []importErreur{{Message: "entete CSV non reconnue"}}
|
||||
}
|
||||
|
||||
var achats []importAchat
|
||||
var erreurs []importErreur
|
||||
line := 1
|
||||
for {
|
||||
record, err := reader.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
line++
|
||||
if err != nil {
|
||||
erreurs = append(erreurs, importErreur{Ligne: line, Message: "ligne invalide"})
|
||||
continue
|
||||
}
|
||||
achat, err := achatFromCSV(record, headerIndex, boutique)
|
||||
if err != nil {
|
||||
erreurs = append(erreurs, importErreur{Ligne: line, Message: err.Error()})
|
||||
continue
|
||||
}
|
||||
achats = append(achats, achat)
|
||||
}
|
||||
return achats, erreurs
|
||||
}
|
||||
|
||||
func detectCSVSeparator(data []byte) rune {
|
||||
firstLine := string(data)
|
||||
if idx := strings.Index(firstLine, "\n"); idx > 0 {
|
||||
firstLine = firstLine[:idx]
|
||||
}
|
||||
if strings.Count(firstLine, ";") > strings.Count(firstLine, ",") {
|
||||
return ';'
|
||||
}
|
||||
return ','
|
||||
}
|
||||
|
||||
func mapCSVHeader(header []string, boutique string) map[string]int {
|
||||
index := make(map[string]int)
|
||||
for i, col := range header {
|
||||
key := normalizeCSVHeader(col)
|
||||
switch key {
|
||||
case "item name", "product name", "item", "nom", "designation":
|
||||
index["nom"] = i
|
||||
case "quantity", "qty", "quantite":
|
||||
index["quantite"] = i
|
||||
case "item price", "unit price", "price", "prix", "item total", "total price", "total":
|
||||
if _, ok := index["prix"]; !ok {
|
||||
index["prix"] = i
|
||||
}
|
||||
case "order date", "purchase date", "date", "date achat", "order time":
|
||||
index["date"] = i
|
||||
case "order id", "order number", "commande", "numero commande":
|
||||
index["order_id"] = i
|
||||
case "brand", "fabricant", "manufacturer":
|
||||
index["fabricant"] = i
|
||||
case "store name", "seller", "boutique":
|
||||
index["boutique"] = i
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := index["nom"]; !ok {
|
||||
return nil
|
||||
}
|
||||
if boutique == "" {
|
||||
if _, ok := index["boutique"]; !ok {
|
||||
index["boutique"] = -1
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func normalizeCSVHeader(value string) string {
|
||||
value = strings.TrimSpace(strings.ToLower(value))
|
||||
value = strings.ReplaceAll(value, "\ufeff", "")
|
||||
return value
|
||||
}
|
||||
|
||||
func achatFromCSV(record []string, index map[string]int, fallbackBoutique string) (importAchat, error) {
|
||||
nom := csvValue(record, index["nom"])
|
||||
if nom == "" {
|
||||
return importAchat{}, fmt.Errorf("nom manquant")
|
||||
}
|
||||
|
||||
quantite := parseIntOrDefault(csvValue(record, index["quantite"]), 1)
|
||||
prix, err := parsePrice(csvValue(record, index["prix"]))
|
||||
if err != nil && csvValue(record, index["prix"]) != "" {
|
||||
return importAchat{}, fmt.Errorf("prix invalide")
|
||||
}
|
||||
|
||||
dateValue := csvValue(record, index["date"])
|
||||
if dateValue != "" {
|
||||
if _, err := parseImportDate(dateValue); err != nil {
|
||||
return importAchat{}, fmt.Errorf("date invalide")
|
||||
}
|
||||
}
|
||||
|
||||
boutique := fallbackBoutique
|
||||
if boutique == "" {
|
||||
boutique = csvValue(record, index["boutique"])
|
||||
}
|
||||
|
||||
achat := importAchat{
|
||||
Nom: nom,
|
||||
Quantite: quantite,
|
||||
PrixAchat: prix,
|
||||
DateAchat: dateValue,
|
||||
Boutique: boutique,
|
||||
}
|
||||
|
||||
orderID := csvValue(record, index["order_id"])
|
||||
if orderID != "" {
|
||||
achat.Caracteristiques = map[string]any{
|
||||
"order_id": orderID,
|
||||
"source": boutique,
|
||||
}
|
||||
}
|
||||
|
||||
if fabricant := csvValue(record, index["fabricant"]); fabricant != "" {
|
||||
achat.Fabricant = fabricant
|
||||
}
|
||||
return achat, nil
|
||||
}
|
||||
|
||||
func csvValue(record []string, idx int) string {
|
||||
if idx < 0 || idx >= len(record) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(record[idx])
|
||||
}
|
||||
|
||||
func parseIntOrDefault(value string, fallback int) int {
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || parsed <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func parsePrice(value string) (float64, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, nil
|
||||
}
|
||||
value = strings.ReplaceAll(value, "€", "")
|
||||
value = strings.ReplaceAll(value, "$", "")
|
||||
value = strings.ReplaceAll(value, "EUR", "")
|
||||
value = strings.ReplaceAll(value, "USD", "")
|
||||
value = strings.ReplaceAll(value, " ", "")
|
||||
value = strings.ReplaceAll(value, ",", ".")
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
|
||||
func parseImportDate(value string) (time.Time, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
formats := []string{
|
||||
time.RFC3339,
|
||||
"2006-01-02",
|
||||
"02/01/2006",
|
||||
"01/02/2006",
|
||||
"2006/01/02",
|
||||
"02-01-2006",
|
||||
"01-02-2006",
|
||||
}
|
||||
for _, format := range formats {
|
||||
if parsed, err := time.Parse(format, value); err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("format date inconnu")
|
||||
}
|
||||
@@ -28,6 +28,8 @@ func RegisterRoutes(r *gin.Engine, client *ent.Client) {
|
||||
v1.POST("/objets/:id/liens_emplacements", h.CreateLienEmplacement)
|
||||
v1.PUT("/liens_emplacements/:id", h.UpdateLienEmplacement)
|
||||
v1.DELETE("/liens_emplacements/:id", h.DeleteLienEmplacement)
|
||||
v1.POST("/analyse-hardware", h.AnalyseHardware)
|
||||
v1.POST("/imports/achats", h.ImportAchats)
|
||||
v1.GET("/config", h.GetConfig)
|
||||
v1.PUT("/config", h.UpdateConfig)
|
||||
v1.GET("/debug/logs", h.GetDebugLogs)
|
||||
|
||||
Reference in New Issue
Block a user