Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

142 lines
3.7 KiB
Go

package catalog
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
const maxResetProductIDs = 5000
// ResetProductsToUnprocessed resets selected products so they reappear as unprocessed raw items.
// kind "raw" updates raw_products by id; kind "processed" (default) deletes processed rows and
// resets linked raw rows (by raw_product_id and shared product_id/gtin), matching legacy behavior.
func (s *Service) ResetProductsToUnprocessed(ctx context.Context, companyID uuid.UUID, productIDs []uuid.UUID, kind string) (map[string]any, error) {
if len(productIDs) == 0 {
return nil, ClientMsg("product_ids is required")
}
if len(productIDs) > maxResetProductIDs {
return nil, ClientMsg(fmt.Sprintf("at most %d product_ids allowed", maxResetProductIDs))
}
tx, err := s.Pool.Begin(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback(ctx)
var resetCount int64
switch kind {
case "raw":
resetCount, err = resetRawProducts(ctx, tx, companyID, productIDs)
default:
resetCount, err = resetProcessedProducts(ctx, tx, companyID, productIDs)
}
if err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return map[string]any{
"success": true,
"reset_count": resetCount,
"message": fmt.Sprintf("%d product(s) returned to unprocessed state", resetCount),
}, nil
}
func resetRawProducts(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, ids []uuid.UUID) (int64, error) {
ct, err := tx.Exec(ctx, `
UPDATE raw_products
SET is_processed = false,
processing_status = 'unprocessed',
updated_at = now()
WHERE company_id = $1 AND id = ANY($2::uuid[])`, companyID, ids)
if err != nil {
return 0, err
}
_, err = tx.Exec(ctx, `
DELETE FROM processed_products
WHERE company_id = $1 AND raw_product_id = ANY($2::uuid[])`, companyID, ids)
if err != nil {
return 0, err
}
return ct.RowsAffected(), nil
}
func resetProcessedProducts(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, ids []uuid.UUID) (int64, error) {
rows, err := tx.Query(ctx, `
SELECT id, raw_product_id, product_id
FROM processed_products
WHERE company_id = $1 AND id = ANY($2::uuid[])`, companyID, ids)
if err != nil {
return 0, err
}
defer rows.Close()
processedIDs := make([]uuid.UUID, 0, len(ids))
rawIDs := make([]uuid.UUID, 0)
gtins := make([]string, 0)
seenGTIN := map[string]struct{}{}
for rows.Next() {
var id uuid.UUID
var rawID *uuid.UUID
var productID *string
if err := rows.Scan(&id, &rawID, &productID); err != nil {
return 0, err
}
processedIDs = append(processedIDs, id)
if rawID != nil {
rawIDs = append(rawIDs, *rawID)
}
if productID != nil {
g := *productID
if g != "" {
if _, ok := seenGTIN[g]; !ok {
seenGTIN[g] = struct{}{}
gtins = append(gtins, g)
}
}
}
}
if err := rows.Err(); err != nil {
return 0, err
}
if len(processedIDs) == 0 {
return 0, ClientMsg("no products found to return to unprocessed state")
}
if len(gtins) > 0 {
_, err = tx.Exec(ctx, `
UPDATE raw_products
SET is_processed = false,
processing_status = 'unprocessed',
updated_at = now()
WHERE company_id = $1 AND gtin = ANY($2::text[])`, companyID, gtins)
if err != nil {
return 0, err
}
}
if len(rawIDs) > 0 {
_, err = tx.Exec(ctx, `
UPDATE raw_products
SET is_processed = false,
processing_status = 'unprocessed',
updated_at = now()
WHERE company_id = $1 AND id = ANY($2::uuid[])`, companyID, rawIDs)
if err != nil {
return 0, err
}
}
ct, err := tx.Exec(ctx, `
DELETE FROM processed_products
WHERE company_id = $1 AND id = ANY($2::uuid[])`, companyID, processedIDs)
if err != nil {
return 0, err
}
return ct.RowsAffected(), nil
}