webapp
Some checks failed
Docker Build and Publish / publish (push) Failing after 1m33s

This commit is contained in:
Eugene Howe
2026-02-17 09:47:30 -05:00
parent af09672ee3
commit b0957bfa49
102 changed files with 4213 additions and 378 deletions

View File

@@ -0,0 +1,139 @@
package negative_points_processor
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"strings"
"clintonambulance.com/calculate_negative_points/internal/api/middleware"
"clintonambulance.com/calculate_negative_points/internal/config"
"clintonambulance.com/calculate_negative_points/internal/nocodb"
"clintonambulance.com/calculate_negative_points/internal/utils"
"github.com/samber/lo"
"github.com/swaggest/usecase"
"go.uber.org/zap"
)
type negativePointsProcessorInput struct {
File *multipart.FileHeader `formData:"file" description:"XLS schedule file to process"`
}
type negativePointsProcessorOutput struct {
Employees []string `json:"employees" description:"List of employees who had negative points" nullable:"false" required:"true"`
}
func hasMatch(record nocodb.NocoDBRecord, candidates []string, threshold int) bool {
normalizedName := utils.NormalizeName(utils.ToTitleCase(record.Name))
bestDistance := threshold + 1
for _, candidate := range candidates {
normalizedCandidate := utils.NormalizeName(candidate)
distance := utils.LevenshteinDistance(normalizedName, normalizedCandidate)
if distance < bestDistance {
bestDistance = distance
}
}
if bestDistance <= threshold {
return true
}
return false
}
func NegativePointsProcessor(config *config.ApplicationConfig, logger *zap.Logger) usecase.Interactor {
u := usecase.NewInteractor(func(ctx context.Context, input negativePointsProcessorInput, output *negativePointsProcessorOutput) error {
ctxUser := ctx.Value("claims").(middleware.Claims)
file, err := input.File.Open()
if err != nil {
return err
}
defer file.Close()
employees, err := utils.ParseUploadedXLSFile(input.File)
if err != nil {
return err
}
records, err := nocodb.Fetch(config)
if err != nil {
return err
}
employees = lo.Filter(employees, func(e utils.Employee, _ int) bool { return e.Worked() })
names := lo.Map(employees, func(e utils.Employee, _ int) string { return e.Name })
// Convert XLS names to same format as API names (FirstName LastName) and normalize casing
xlsNamesConverted := lo.Map(names, func(name string, _ int) string {
return utils.ToTitleCase(utils.ConvertNameFormat(name))
})
overlaps := lo.Filter(records, func(r nocodb.NocoDBRecord, _ int) bool {
return hasMatch(r, xlsNamesConverted, config.MatchThreshold)
})
currentNocodbUser, found := lo.Find(records, func(r nocodb.NocoDBRecord) bool { return hasMatch(r, []string{ctxUser.Name}, config.MatchThreshold) })
if !found {
return errors.New("Unable to match API user to NocoDB user")
}
requestObjects := lo.Map(overlaps, func(r nocodb.NocoDBRecord, _ int) nocodb.NocoDBRequest {
return nocodb.NocoDBRequest{
EmployeeId: r.ID,
ReportedBy: currentNocodbUser.ID,
InfractionId: config.NocoDBConfig.NegativeInfractionId,
Date: utils.FirstDayOfMonth(),
}
})
// Marshal request objects to JSON
jsonData, err := json.Marshal(requestObjects)
if err != nil {
return err
}
// Create POST request
req, err := http.NewRequest("POST", nocodb.AddInfractionsUrl(config), strings.NewReader(string(jsonData)))
if err != nil {
return err
}
// Add authorization header
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.NocoDBConfig.ApiToken.Value()))
req.Header.Set("Content-Type", "application/json")
// Make the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check response status
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Request failed with status %d: %s\n", resp.StatusCode, string(body))
os.Exit(1)
}
output.Employees = lo.Map(overlaps, func(r nocodb.NocoDBRecord, _ int) string { return r.Name })
return nil
})
u.SetDescription("Process Negative Points")
u.SetTags("Negative Points Processor")
return u
}