Spaces:
Running
Running
Guardado de datos napping con sort
Browse files
tecnicas/controllers/api_controller/rating_napping_controller.py
CHANGED
|
@@ -1,35 +1,237 @@
|
|
| 1 |
from django.http import JsonResponse
|
| 2 |
from django.http import HttpRequest
|
| 3 |
from django.db import transaction
|
| 4 |
-
from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion, Palabra
|
| 5 |
from tecnicas.forms import ListWordsForm
|
| 6 |
|
| 7 |
|
| 8 |
class RatingNappingController:
|
| 9 |
@staticmethod
|
| 10 |
-
def saveRatingCoordinates(request: HttpRequest, data: list):
|
| 11 |
participation = Participacion.objects.get(
|
| 12 |
id=request.session["id_participation"]
|
| 13 |
)
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
try:
|
| 18 |
with transaction.atomic():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
products_map = RatingNappingController.getProductsMap(
|
| 20 |
participation.tecnica)
|
| 21 |
|
|
|
|
| 22 |
existing_ratings_map = RatingNappingController.getExistingRatingsMap(
|
| 23 |
-
participation.tecnica, participation.catador
|
| 24 |
-
)
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
|
|
|
|
| 30 |
new_ratings = []
|
| 31 |
ids_products = products_map.keys()
|
| 32 |
-
for item in
|
| 33 |
product_id = int(item["idProduct"])
|
| 34 |
if product_id not in existing_ratings_map and product_id in ids_products:
|
| 35 |
new_ratings.append(
|
|
@@ -44,16 +246,16 @@ class RatingNappingController:
|
|
| 44 |
if new_ratings:
|
| 45 |
Calificacion.objects.bulk_create(new_ratings)
|
| 46 |
existing_ratings_map = RatingNappingController.getExistingRatingsMap(
|
| 47 |
-
participation.tecnica, participation.catador
|
| 48 |
-
)
|
| 49 |
|
|
|
|
| 50 |
existing_points_map = RatingNappingController.getExistingPointsMap(
|
| 51 |
existing_ratings_map.values())
|
| 52 |
|
| 53 |
points_to_create = []
|
| 54 |
points_to_update = []
|
| 55 |
|
| 56 |
-
for item in
|
| 57 |
product_id = int(item["idProduct"])
|
| 58 |
rating = existing_ratings_map.get(product_id)
|
| 59 |
|
|
@@ -78,20 +280,14 @@ class RatingNappingController:
|
|
| 78 |
if points_to_update:
|
| 79 |
DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
|
| 80 |
|
| 81 |
-
|
| 82 |
-
data, existing_ratings_map
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
return JsonResponse({"message": "Datos guardados exitosamente"})
|
| 86 |
|
| 87 |
except Exception as e:
|
| 88 |
-
print(
|
| 89 |
-
return JsonResponse({"error": "Error al
|
| 90 |
|
| 91 |
@staticmethod
|
| 92 |
def validateWords(data: list):
|
| 93 |
-
"""Valida todas las palabras de todos los items usando ListWordsForm.
|
| 94 |
-
Retorna JsonResponse con error si hay problemas, None si todo está bien."""
|
| 95 |
for item in data:
|
| 96 |
words = item.get("words", [])
|
| 97 |
if words:
|
|
@@ -109,9 +305,6 @@ class RatingNappingController:
|
|
| 109 |
|
| 110 |
@staticmethod
|
| 111 |
def processWordsForRatings(data: list, existing_ratings_map: dict):
|
| 112 |
-
"""Procesa y asocia palabras a las calificaciones.
|
| 113 |
-
Crea palabras que no existan y las asocia a las calificaciones correspondientes."""
|
| 114 |
-
|
| 115 |
all_words = set()
|
| 116 |
for item in data:
|
| 117 |
words = item.get("words", [])
|
|
|
|
| 1 |
from django.http import JsonResponse
|
| 2 |
from django.http import HttpRequest
|
| 3 |
from django.db import transaction
|
| 4 |
+
from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion, Palabra, GrupoProducto, TecnicaModalidad
|
| 5 |
from tecnicas.forms import ListWordsForm
|
| 6 |
|
| 7 |
|
| 8 |
class RatingNappingController:
|
| 9 |
@staticmethod
|
| 10 |
+
def saveRatingCoordinates(request: HttpRequest, data: list | dict):
|
| 11 |
participation = Participacion.objects.get(
|
| 12 |
id=request.session["id_participation"]
|
| 13 |
)
|
| 14 |
|
| 15 |
+
name_mod = TecnicaModalidad.objects.get(
|
| 16 |
+
tecnica=participation.tecnica
|
| 17 |
+
).modalidad.nombre.lower()
|
| 18 |
|
| 19 |
+
# Branch based on modality
|
| 20 |
+
if name_mod == 'sorting':
|
| 21 |
+
print(data)
|
| 22 |
+
return RatingNappingController.processSortingMode(
|
| 23 |
+
data, participation
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
if name_mod in ['sin modalidad', 'perfil ultra flash']:
|
| 27 |
+
return RatingNappingController.processNappOrPUF(
|
| 28 |
+
data, participation
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
else:
|
| 32 |
+
return JsonResponse({"error": "Modalidad no soportada"})
|
| 33 |
+
|
| 34 |
+
@staticmethod
|
| 35 |
+
def processSortingMode(data: dict, participation):
|
| 36 |
+
try:
|
| 37 |
+
with transaction.atomic():
|
| 38 |
+
# Extract products array (always present)
|
| 39 |
+
products = data.get("products", [])
|
| 40 |
+
if not products:
|
| 41 |
+
return JsonResponse({"error": "No se proporcionaron productos"})
|
| 42 |
+
|
| 43 |
+
existing_ratings_map, products_map = RatingNappingController.savePoints(
|
| 44 |
+
isSorting=True, products=products, participation=participation
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# Process groups if they exist
|
| 48 |
+
groups = data.get("groups", {})
|
| 49 |
+
if groups:
|
| 50 |
+
RatingNappingController.processGroupsForSorting(
|
| 51 |
+
products, groups, participation, existing_ratings_map, products_map
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
return JsonResponse({"message": "Datos guardados exitosamente"})
|
| 55 |
+
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print("ERROR:", e)
|
| 58 |
+
import traceback
|
| 59 |
+
traceback.print_exc()
|
| 60 |
+
return JsonResponse({"error": f"Error al procesar datos: {str(e)}"})
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def processNappOrPUF(data: list, participation):
|
| 64 |
try:
|
| 65 |
with transaction.atomic():
|
| 66 |
+
existing_ratings_map, products_map = RatingNappingController.savePoints(
|
| 67 |
+
isSorting=False, products=data, participation=participation
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
RatingNappingController.processWordsForRatings(
|
| 71 |
+
data, existing_ratings_map
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
return JsonResponse({"message": "Datos guardados exitosamente"})
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print("ERROR:", e)
|
| 78 |
+
return JsonResponse({"error": "Error al procesar datos"})
|
| 79 |
+
|
| 80 |
+
@staticmethod
|
| 81 |
+
def processGroupsForSorting(products, groups, participation, existing_ratings_map, products_map):
|
| 82 |
+
"""Process groups for sorting mode
|
| 83 |
+
- Creates/updates GrupoProducto instances
|
| 84 |
+
- Ensures products don't belong to multiple groups
|
| 85 |
+
- Associates words with groups
|
| 86 |
+
"""
|
| 87 |
+
# Build mapping of product_id to group_id from products array
|
| 88 |
+
product_to_group = {}
|
| 89 |
+
for product_item in products:
|
| 90 |
+
group_code = product_item.get("group", "")
|
| 91 |
+
if group_code: # Only if product has a group assigned
|
| 92 |
+
product_id = int(product_item["idProduct"])
|
| 93 |
+
if product_id in product_to_group:
|
| 94 |
+
raise ValueError(
|
| 95 |
+
f"Producto {product_id} pertenece a múltiples grupos")
|
| 96 |
+
product_to_group[product_id] = group_code
|
| 97 |
+
|
| 98 |
+
# Get existing groups for this catador and technique
|
| 99 |
+
existing_groups = GrupoProducto.objects.filter(
|
| 100 |
+
tecnica=participation.tecnica,
|
| 101 |
+
catador=participation.catador
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# Create a map of existing groups by their product composition
|
| 105 |
+
# We'll identify groups by the set of products they contain
|
| 106 |
+
existing_groups_map = {}
|
| 107 |
+
for group in existing_groups:
|
| 108 |
+
product_ids = set(group.productos.values_list('id', flat=True))
|
| 109 |
+
key = frozenset(product_ids)
|
| 110 |
+
existing_groups_map[key] = group
|
| 111 |
+
|
| 112 |
+
# Build new groups structure
|
| 113 |
+
groups_to_create = []
|
| 114 |
+
groups_to_update = []
|
| 115 |
+
group_products_map = {} # group_id -> [product_ids]
|
| 116 |
+
|
| 117 |
+
# Organize products by group
|
| 118 |
+
for product_id, group_code in product_to_group.items():
|
| 119 |
+
if group_code not in group_products_map:
|
| 120 |
+
group_products_map[group_code] = []
|
| 121 |
+
group_products_map[group_code].append(product_id)
|
| 122 |
+
|
| 123 |
+
# Process each group
|
| 124 |
+
for group_code, product_ids in group_products_map.items():
|
| 125 |
+
product_set = frozenset(product_ids)
|
| 126 |
+
|
| 127 |
+
# Check if this group already exists
|
| 128 |
+
if product_set in existing_groups_map:
|
| 129 |
+
group = existing_groups_map[product_set]
|
| 130 |
+
groups_to_update.append((group, group_code))
|
| 131 |
+
else:
|
| 132 |
+
# Create new group
|
| 133 |
+
group = GrupoProducto(
|
| 134 |
+
tecnica=participation.tecnica,
|
| 135 |
+
catador=participation.catador
|
| 136 |
+
)
|
| 137 |
+
groups_to_create.append((group, product_ids, group_code))
|
| 138 |
+
|
| 139 |
+
# Create new groups
|
| 140 |
+
created_groups = []
|
| 141 |
+
for group, product_ids, group_code in groups_to_create:
|
| 142 |
+
group.save() # Save first to get ID for M2M
|
| 143 |
+
|
| 144 |
+
# Add products to group
|
| 145 |
+
productos = [products_map[pid]
|
| 146 |
+
for pid in product_ids if pid in products_map]
|
| 147 |
+
group.productos.set(productos)
|
| 148 |
+
|
| 149 |
+
created_groups.append((group, group_code))
|
| 150 |
+
|
| 151 |
+
# Combine created and existing groups for word processing
|
| 152 |
+
all_groups_for_words = created_groups + groups_to_update
|
| 153 |
+
|
| 154 |
+
# Delete groups that no longer exist
|
| 155 |
+
current_group_sets = set(frozenset(pids)
|
| 156 |
+
for pids in group_products_map.values())
|
| 157 |
+
for product_set, group in existing_groups_map.items():
|
| 158 |
+
if product_set not in current_group_sets:
|
| 159 |
+
group.delete()
|
| 160 |
+
|
| 161 |
+
# Process words for groups
|
| 162 |
+
if groups:
|
| 163 |
+
RatingNappingController.processWordsForGroups(
|
| 164 |
+
groups, all_groups_for_words
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
@staticmethod
|
| 168 |
+
def processWordsForGroups(groups_data, groups_list):
|
| 169 |
+
"""Process and associate words to groups
|
| 170 |
+
- Creates words that don't exist
|
| 171 |
+
- Associates words to GrupoProducto instances
|
| 172 |
+
- Handles concurrency
|
| 173 |
+
"""
|
| 174 |
+
# Collect all unique words from all groups
|
| 175 |
+
all_words = set()
|
| 176 |
+
for group_id, words in groups_data.items():
|
| 177 |
+
if words:
|
| 178 |
+
all_words.update(words)
|
| 179 |
+
|
| 180 |
+
if not all_words:
|
| 181 |
+
# No words to process, just clear existing words from groups
|
| 182 |
+
for grupo, _ in groups_list:
|
| 183 |
+
grupo.palabras.clear()
|
| 184 |
+
return
|
| 185 |
+
|
| 186 |
+
# Get existing words
|
| 187 |
+
existing_words = Palabra.objects.filter(
|
| 188 |
+
nombre_palabra__in=all_words
|
| 189 |
+
)
|
| 190 |
+
existing_words_map = {w.nombre_palabra: w for w in existing_words}
|
| 191 |
+
|
| 192 |
+
# Create missing words with concurrency handling
|
| 193 |
+
word_objects = {}
|
| 194 |
+
for word_name in all_words:
|
| 195 |
+
if word_name in existing_words_map:
|
| 196 |
+
word_objects[word_name] = existing_words_map[word_name]
|
| 197 |
+
else:
|
| 198 |
+
word_obj, created = Palabra.objects.get_or_create(
|
| 199 |
+
nombre_palabra=word_name
|
| 200 |
+
)
|
| 201 |
+
word_objects[word_name] = word_obj
|
| 202 |
+
|
| 203 |
+
# Associate words with groups
|
| 204 |
+
for grupo, group_id in groups_list:
|
| 205 |
+
words = groups_data.get(group_id, [])
|
| 206 |
+
if words:
|
| 207 |
+
words_to_set = [word_objects[word_name]
|
| 208 |
+
for word_name in words if word_name in word_objects]
|
| 209 |
+
grupo.palabras.set(words_to_set)
|
| 210 |
+
else:
|
| 211 |
+
grupo.palabras.clear()
|
| 212 |
+
|
| 213 |
+
@staticmethod
|
| 214 |
+
def savePoints(isSorting: bool, products, participation):
|
| 215 |
+
try:
|
| 216 |
+
with transaction.atomic():
|
| 217 |
+
# Get products map for validation
|
| 218 |
products_map = RatingNappingController.getProductsMap(
|
| 219 |
participation.tecnica)
|
| 220 |
|
| 221 |
+
# Get existing ratings map
|
| 222 |
existing_ratings_map = RatingNappingController.getExistingRatingsMap(
|
| 223 |
+
participation.tecnica, participation.catador)
|
|
|
|
| 224 |
|
| 225 |
+
if not isSorting:
|
| 226 |
+
validation_result = RatingNappingController.validateWords(
|
| 227 |
+
products)
|
| 228 |
+
if validation_result is not None:
|
| 229 |
+
return validation_result
|
| 230 |
|
| 231 |
+
# Create new ratings for products that don't have them
|
| 232 |
new_ratings = []
|
| 233 |
ids_products = products_map.keys()
|
| 234 |
+
for item in products:
|
| 235 |
product_id = int(item["idProduct"])
|
| 236 |
if product_id not in existing_ratings_map and product_id in ids_products:
|
| 237 |
new_ratings.append(
|
|
|
|
| 246 |
if new_ratings:
|
| 247 |
Calificacion.objects.bulk_create(new_ratings)
|
| 248 |
existing_ratings_map = RatingNappingController.getExistingRatingsMap(
|
| 249 |
+
participation.tecnica, participation.catador)
|
|
|
|
| 250 |
|
| 251 |
+
# Process DatoPunto instances from products array
|
| 252 |
existing_points_map = RatingNappingController.getExistingPointsMap(
|
| 253 |
existing_ratings_map.values())
|
| 254 |
|
| 255 |
points_to_create = []
|
| 256 |
points_to_update = []
|
| 257 |
|
| 258 |
+
for item in products:
|
| 259 |
product_id = int(item["idProduct"])
|
| 260 |
rating = existing_ratings_map.get(product_id)
|
| 261 |
|
|
|
|
| 280 |
if points_to_update:
|
| 281 |
DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
|
| 282 |
|
| 283 |
+
return (existing_ratings_map, products_map)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
except Exception as e:
|
| 286 |
+
print(e)
|
| 287 |
+
return JsonResponse({"error": "Error al guardar los puntos"})
|
| 288 |
|
| 289 |
@staticmethod
|
| 290 |
def validateWords(data: list):
|
|
|
|
|
|
|
| 291 |
for item in data:
|
| 292 |
words = item.get("words", [])
|
| 293 |
if words:
|
|
|
|
| 305 |
|
| 306 |
@staticmethod
|
| 307 |
def processWordsForRatings(data: list, existing_ratings_map: dict):
|
|
|
|
|
|
|
|
|
|
| 308 |
all_words = set()
|
| 309 |
for item in data:
|
| 310 |
words = item.get("words", [])
|
tecnicas/static/js/test-napping-sort.js
CHANGED
|
@@ -98,7 +98,7 @@ function initSortMode() {
|
|
| 98 |
spanNotifaction("Fase de agrupación: Selecciona puntos y crea grupos.", false);
|
| 99 |
|
| 100 |
// Auto-save points when transitioning to Phase 2
|
| 101 |
-
|
| 102 |
|
| 103 |
// Enable point selection
|
| 104 |
enablePointSelection();
|
|
@@ -285,7 +285,6 @@ function initSortMode() {
|
|
| 285 |
const icon = iconsDisolveGroup.item(index);
|
| 286 |
icon.remove();
|
| 287 |
}
|
| 288 |
-
|
| 289 |
|
| 290 |
spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false);
|
| 291 |
|
|
@@ -423,16 +422,101 @@ function initSortMode() {
|
|
| 423 |
return true;
|
| 424 |
};
|
| 425 |
|
| 426 |
-
//
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
}
|
|
|
|
| 98 |
spanNotifaction("Fase de agrupación: Selecciona puntos y crea grupos.", false);
|
| 99 |
|
| 100 |
// Auto-save points when transitioning to Phase 2
|
| 101 |
+
sortModeSaveData(false);
|
| 102 |
|
| 103 |
// Enable point selection
|
| 104 |
enablePointSelection();
|
|
|
|
| 285 |
const icon = iconsDisolveGroup.item(index);
|
| 286 |
icon.remove();
|
| 287 |
}
|
|
|
|
| 288 |
|
| 289 |
spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false);
|
| 290 |
|
|
|
|
| 422 |
return true;
|
| 423 |
};
|
| 424 |
|
| 425 |
+
// Override save-progress button to use sort mode save function
|
| 426 |
+
const saveProgressBtn = document.getElementById('save-progress');
|
| 427 |
+
// Remove existing event listener by cloning and replacing
|
| 428 |
+
const newSaveProgressBtn = saveProgressBtn.cloneNode(true);
|
| 429 |
+
newSaveProgressBtn.textContent = 'Guardar Progreso Sort';
|
| 430 |
+
saveProgressBtn.parentNode.replaceChild(newSaveProgressBtn, saveProgressBtn);
|
| 431 |
+
|
| 432 |
+
newSaveProgressBtn.addEventListener('click', async () => {
|
| 433 |
+
await sortModeSaveData(false);
|
| 434 |
+
});
|
| 435 |
+
|
| 436 |
+
// Override finish-session button to use sort mode save function
|
| 437 |
+
document.getElementById('finish-session').addEventListener('click', async (e) => {
|
| 438 |
+
e.preventDefault();
|
| 439 |
+
e.stopPropagation();
|
| 440 |
+
const success = await sortModeSaveData(true);
|
| 441 |
+
if (success) {
|
| 442 |
+
const formFinish = document.getElementById("form-finish-session");
|
| 443 |
+
formFinish.action = "";
|
| 444 |
+
formFinish.submit();
|
| 445 |
+
}
|
| 446 |
+
});
|
| 447 |
+
|
| 448 |
+
// Sort mode specific save function
|
| 449 |
+
async function sortModeSaveData(isFinishSession = false) {
|
| 450 |
+
// Run validation callback if it exists
|
| 451 |
+
if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
|
| 452 |
+
const validationResult = window.beforeSaveData(isFinishSession);
|
| 453 |
+
if (validationResult === false) {
|
| 454 |
+
return false;
|
| 455 |
+
}
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
// Build products array with basic position info and group assignment
|
| 459 |
+
const products = [];
|
| 460 |
+
for (const [code, point] of Object.entries(window.placedPoints)) {
|
| 461 |
+
const groupId = productToGroup[code];
|
| 462 |
+
products.push({
|
| 463 |
+
code: code,
|
| 464 |
+
x: point.x,
|
| 465 |
+
y: point.y,
|
| 466 |
+
idProduct: point.id,
|
| 467 |
+
group: groupId || "" // Empty string if no group assigned
|
| 468 |
+
});
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
// Build groups object with word arrays
|
| 472 |
+
const groups = {};
|
| 473 |
+
const groupIds = Object.keys(productGroups);
|
| 474 |
+
|
| 475 |
+
// Only include groups if they exist
|
| 476 |
+
if (groupIds.length > 0) {
|
| 477 |
+
for (const groupId of groupIds) {
|
| 478 |
+
groups[groupId] = groupWords[groupId] || [];
|
| 479 |
+
}
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
// Build the data structure
|
| 483 |
+
const data = { products: products };
|
| 484 |
+
|
| 485 |
+
// Only add groups if they exist
|
| 486 |
+
if (Object.keys(groups).length > 0) {
|
| 487 |
+
data.groups = groups;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
const URL = "/cata/testers/api/rating-napping";
|
| 491 |
+
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
| 492 |
+
|
| 493 |
+
try {
|
| 494 |
+
const response = await fetch(URL, {
|
| 495 |
+
method: "POST",
|
| 496 |
+
headers: {
|
| 497 |
+
"Content-Type": "application/json",
|
| 498 |
+
"X-CSRFToken": csrfToken,
|
| 499 |
+
},
|
| 500 |
+
body: JSON.stringify(data),
|
| 501 |
+
});
|
| 502 |
+
|
| 503 |
+
if (!response.ok) {
|
| 504 |
+
spanNotifaction("Error en la respuesta del servidor");
|
| 505 |
+
return false;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
const result = await response.json();
|
| 509 |
+
|
| 510 |
+
if (result.error) {
|
| 511 |
+
spanNotifaction(result.error);
|
| 512 |
+
return false;
|
| 513 |
+
} else {
|
| 514 |
+
spanNotifaction(result.message, false);
|
| 515 |
+
return true;
|
| 516 |
+
}
|
| 517 |
+
} catch (error) {
|
| 518 |
+
spanNotifaction("Error en proceso de guardar los datos");
|
| 519 |
+
return false;
|
| 520 |
+
}
|
| 521 |
+
}
|
| 522 |
}
|