Subversion Repositories SmartDukaan

Rev

Rev 36700 | Rev 36728 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36700 Rev 36711
Line 427... Line 427...
427
								state.days.push({
427
								state.days.push({
428
									dayNumber: d.dayNumber,
428
									dayNumber: d.dayNumber,
429
									startLat: state.homeLat, startLng: state.homeLng,
429
									startLat: state.homeLat, startLng: state.homeLng,
430
									startName: state.homeName,
430
									startName: state.homeName,
431
									visits: builtVisits,
431
									visits: builtVisits,
432
									endAction: d.dayNumber < beat.days.length ? 'DAYBREAK' : 'HOME',
432
									endAction: d.endAction || (d.dayNumber < beat.days.length ? 'DAYBREAK' : 'HOME'),
433
									totalKm: 0, totalMins: 0
433
									totalKm: 0, totalMins: 0
434
								});
434
								});
435
							});
435
							});
436
							state.currentDay = 1;
436
							state.currentDay = 1;
437
 
437
 
Line 639... Line 639...
639
                                    dayNumber: d.dayNumber,
639
                                    dayNumber: d.dayNumber,
640
                                    startLat: startLat,
640
                                    startLat: startLat,
641
                                    startLng: startLng,
641
                                    startLng: startLng,
642
                                    startName: startName,
642
                                    startName: startName,
643
                                    visits: builtVisits,
643
                                    visits: builtVisits,
-
 
644
									// Use the actual end_action stored on this schedule row.
-
 
645
									// The dayNumber-based inference is wrong for multi-instance
-
 
646
									// beats (same beat scheduled on several dates) — every
-
 
647
									// instance would have day N but only one was treated HOME.
644
                                    endAction: d.dayNumber < beat.days.length ? 'DAYBREAK' : 'HOME',
648
									endAction: d.endAction || (d.dayNumber < beat.days.length ? 'DAYBREAK' : 'HOME'),
645
                                    totalKm: d.totalKm || 0,
649
                                    totalKm: d.totalKm || 0,
646
                                    totalMins: d.totalMins || 0
650
                                    totalMins: d.totalMins || 0
647
                                });
651
                                });
648
                            });
652
                            });
649
 
653
 
Line 959... Line 963...
959
	var addr = partner.address || '';
963
	var addr = partner.address || '';
960
 
964
 
961
	var html = '<div class="marker-popup">';
965
	var html = '<div class="marker-popup">';
962
	html += '<h4>' + partner.code + ' - ' + name + '</h4>';
966
	html += '<h4>' + partner.code + ' - ' + name + '</h4>';
963
	html += '<div class="popup-meta">' + addr + '</div>';
967
	html += '<div class="popup-meta">' + addr + '</div>';
964
	html += '<div class="popup-distance">~' + dist.toFixed(1) + ' km | ~' + fmtMins(travelMins) + ' travel + ' + VISIT_MINS + 'min visit</div>';
968
	html += '<div class="popup-distance">~' + dist.toFixed(1) + ' km | ~' + fmtMins(travelMins) + ' travel + ' + VISIT_MINS + ' Minutes Discussion Time</div>';
965
	html += '<button class="popup-next" onclick="addVisit(' + partner.fofoId + ',\'next\')">Next Visit</button>';
969
	html += '<button class="popup-next" onclick="addVisit(' + partner.fofoId + ',\'next\')">Next Visit</button>';
966
	html += '<button class="popup-last" onclick="addVisit(' + partner.fofoId + ',\'last\')">Last Visit</button>';
970
	html += '<button class="popup-last" onclick="addVisit(' + partner.fofoId + ',\'last\')">Last Visit</button>';
967
	html += '</div>';
971
	html += '</div>';
968
 
972
 
969
	infoWindow.setContent(html);
973
	infoWindow.setContent(html);
Line 1233... Line 1237...
1233
			if (prevLat && prevLng && v.lat && v.lng) {
1237
			if (prevLat && prevLng && v.lat && v.lng) {
1234
				segDist = haversine(prevLat, prevLng, v.lat, v.lng) * ROAD_FACTOR;
1238
				segDist = haversine(prevLat, prevLng, v.lat, v.lng) * ROAD_FACTOR;
1235
				segTime = (segDist / AVG_SPEED) * 60;
1239
				segTime = (segDist / AVG_SPEED) * 60;
1236
			}
1240
			}
1237
			if (segDist > 0) {
1241
			if (segDist > 0) {
-
 
1242
				// Show drive ETA and discussion-time dwell separately so the
-
 
1243
				// planner can see where the time goes.
1238
				html += '<div class="route-segment"><span class="seg-line"></span><span class="seg-dist">' + segDist.toFixed(1) + ' km | ' + fmtMins(segTime) + '</span></div>';
1244
				html += '<div class="route-segment"><span class="seg-line"></span><span class="seg-dist">'
-
 
1245
					+ segDist.toFixed(1) + ' km | '
-
 
1246
					+ fmtMins(segTime) + ' drive '
-
 
1247
					+ '<span style="color:#94a3b8;">+ ' + VISIT_MINS + ' Minutes Discussion Time</span>'
-
 
1248
					+ '</span></div>';
1239
			}
1249
			}
1240
 
1250
 
1241
            var isLeadVisit = v.isLead || v.type === 'lead';
1251
            var isLeadVisit = v.isLead || v.type === 'lead';
-
 
1252
			// Outlet/business name is the primary label; code is the muted subtitle.
-
 
1253
			// v.name is stored as "CODE - Outlet Name" — strip the code prefix.
-
 
1254
			var outletLabel = v.name || '';
-
 
1255
			if (v.code && outletLabel.indexOf(v.code + ' - ') === 0) {
-
 
1256
				outletLabel = outletLabel.substring(v.code.length + 3);
-
 
1257
			}
-
 
1258
			if (!outletLabel) outletLabel = v.code || '';
1242
            html += '<div class="route-stop route-stop-clickable" data-fofoid="' + v.id + '" data-daynum="' + day.dayNumber + '" style="cursor:pointer;' + (isLeadVisit ? 'background:#fff3cd;border-left:3px solid #e67e22;' : '') + '">';
1259
            html += '<div class="route-stop route-stop-clickable" data-fofoid="' + v.id + '" data-daynum="' + day.dayNumber + '" style="cursor:pointer;' + (isLeadVisit ? 'background:#fff3cd;border-left:3px solid #e67e22;' : '') + '">';
1243
            html += '<span class="stop-num" style="' + (isLeadVisit ? 'background:#e67e22;' : '') + '">' + (i + 1) + '</span>';
1260
            html += '<span class="stop-num" style="' + (isLeadVisit ? 'background:#e67e22;' : '') + '">' + (i + 1) + '</span>';
1244
            html += '<div class="stop-info"><div class="stop-name">' + (v.code || '') + (isLeadVisit ? ' <span style="color:#e67e22;font-size:10px;">LEAD VISIT</span>' : '') + '</div>';
1261
			html += '<div class="stop-info"><div class="stop-name">' + outletLabel + (isLeadVisit ? ' <span style="color:#e67e22;font-size:10px;">LEAD VISIT</span>' : '') + '</div>';
1245
            html += '<div class="stop-meta">' + (v.name || '') + '</div>';
1262
			html += '<div class="stop-meta">' + (v.code || '') + '</div>';
1246
 
1263
 
1247
            // Show "Last Visit" + "Day Break" buttons on last stop of current day
1264
            // Show "Last Visit" + "Day Break" buttons on last stop of current day
1248
            if (isLastStop && isCurrent && !day.endAction) {
1265
            if (isLastStop && isCurrent && !day.endAction) {
1249
                html += '<div style="margin-top:4px;display:flex;gap:4px;">';
1266
                html += '<div style="margin-top:4px;display:flex;gap:4px;">';
1250
                html += '<button class="btn-last-visit" data-daynum="' + day.dayNumber + '" style="font-size:10px;padding:2px 8px;border:none;border-radius:3px;background:#22c55e;color:#fff;cursor:pointer;font-weight:600;">Last Visit</button>';
1267
                html += '<button class="btn-last-visit" data-daynum="' + day.dayNumber + '" style="font-size:10px;padding:2px 8px;border:none;border-radius:3px;background:#22c55e;color:#fff;cursor:pointer;font-weight:600;">Last Visit</button>';
Line 1269... Line 1286...
1269
			if (lastV.lat && lastV.lng && state.homeLat && state.homeLng) {
1286
			if (lastV.lat && lastV.lng && state.homeLat && state.homeLng) {
1270
				retDist = haversine(lastV.lat, lastV.lng, state.homeLat, state.homeLng) * ROAD_FACTOR;
1287
				retDist = haversine(lastV.lat, lastV.lng, state.homeLat, state.homeLng) * ROAD_FACTOR;
1271
				retTime = (retDist / AVG_SPEED) * 60;
1288
				retTime = (retDist / AVG_SPEED) * 60;
1272
			}
1289
			}
1273
			if (retDist > 0) {
1290
			if (retDist > 0) {
-
 
1291
				// Return-home leg = travel time only (no discussion at home).
1274
                html += '<div class="route-segment"><span class="seg-line seg-line-home"></span><span class="seg-dist">' + retDist.toFixed(1) + ' km | ' + fmtMins(retTime) + ' (return)</span></div>';
1292
				html += '<div class="route-segment"><span class="seg-line seg-line-home"></span><span class="seg-dist">'
-
 
1293
					+ retDist.toFixed(1) + ' km | ' + fmtMins(retTime) + ' drive (return)'
-
 
1294
					+ '</span></div>';
1275
			}
1295
			}
1276
			html += '<div class="route-stop route-stop-home"><span class="stop-num">H</span><div class="stop-info"><div class="stop-name">Home</div></div></div>';
1296
			html += '<div class="route-stop route-stop-home"><span class="stop-num">H</span><div class="stop-info"><div class="stop-name">Home</div></div></div>';
1277
		}
1297
		}
1278
 
1298
 
1279
        // Day break indicator
1299
        // Day break indicator
Line 1422... Line 1442...
1422
			+ 'Current:  ' + state.days.length + ' day(s)\n\n'
1442
			+ 'Current:  ' + state.days.length + ' day(s)\n\n'
1423
			+ 'Please create a new beat for additional days.');
1443
			+ 'Please create a new beat for additional days.');
1424
		return;
1444
		return;
1425
	}
1445
	}
1426
 
1446
 
-
 
1447
	// Build plan data — recompute every day's totals + per-leg distance/time
-
 
1448
	// fresh from the current state. recalcDay() only refreshes the CURRENT day,
-
 
1449
	// so older days could carry stale values; doing it here guarantees DB rows
1427
	// Build plan data
1450
	// match what the user sees in the panel.
1428
	var daysData = [];
1451
	var daysData = [];
1429
	state.days.forEach(function (day) {
1452
	state.days.forEach(function (day) {
1430
		if (day.visits.length === 0) return;
1453
		if (day.visits.length === 0) return;
-
 
1454
 
-
 
1455
		// Walk the visits in order, computing each leg from the previous point.
-
 
1456
		// Day always starts at (startLat, startLng); for the LAST stop, if
-
 
1457
		// endAction === 'HOME' we ALSO add the return-to-home leg into the day total
-
 
1458
		// (but not stored per-visit, just rolled into totalDistanceKm/totalTimeMins).
-
 
1459
		var prevLat = day.startLat, prevLng = day.startLng;
-
 
1460
		var dayKm = 0, dayMins = 0;
-
 
1461
		var visitsOut = [];
-
 
1462
		day.visits.forEach(function (v) {
-
 
1463
			var legKm = null, legMins = null;
-
 
1464
			if (prevLat != null && prevLng != null && v.lat != null && v.lng != null) {
-
 
1465
				legKm = haversine(prevLat, prevLng, v.lat, v.lng) * ROAD_FACTOR;
-
 
1466
				legMins = Math.round((legKm / AVG_SPEED) * 60);
-
 
1467
				dayKm += legKm;
-
 
1468
				dayMins += legMins;
-
 
1469
			}
-
 
1470
			visitsOut.push({
-
 
1471
				id: v.id,
-
 
1472
				type: v.type || 'partner',
-
 
1473
				distanceFromPrevKm: legKm != null ? Number(legKm.toFixed(3)) : null,
-
 
1474
				timeFromPrevMins: legMins
-
 
1475
			});
-
 
1476
			if (v.lat != null && v.lng != null) {
-
 
1477
				prevLat = v.lat;
-
 
1478
				prevLng = v.lng;
-
 
1479
			}
-
 
1480
		});
-
 
1481
		// Return-home leg if applicable
-
 
1482
		if (day.endAction === 'HOME' && day.visits.length > 0
-
 
1483
			&& state.homeLat != null && state.homeLng != null) {
-
 
1484
			var last = day.visits[day.visits.length - 1];
-
 
1485
			if (last.lat != null && last.lng != null) {
-
 
1486
				var retKm = haversine(last.lat, last.lng, state.homeLat, state.homeLng) * ROAD_FACTOR;
-
 
1487
				dayKm += retKm;
-
 
1488
				dayMins += (retKm / AVG_SPEED) * 60;
-
 
1489
			}
-
 
1490
		}
-
 
1491
		// Add per-stop dwell time
-
 
1492
		dayMins += day.visits.length * VISIT_MINS;
-
 
1493
 
1431
		daysData.push({
1494
		daysData.push({
1432
			dayNumber: day.dayNumber,
1495
			dayNumber: day.dayNumber,
1433
			startLocationName: day.startName,
1496
			startLocationName: day.startName,
1434
			startLatitude: day.startLat ? day.startLat.toString() : null,
1497
			startLatitude: day.startLat ? day.startLat.toString() : null,
1435
			startLongitude: day.startLng ? day.startLng.toString() : null,
1498
			startLongitude: day.startLng ? day.startLng.toString() : null,
1436
			endAction: day.endAction,
1499
			endAction: day.endAction,
1437
			stayLocationName: day.stayName,
1500
			stayLocationName: day.stayName,
1438
			stayLatitude: day.stayLat ? day.stayLat.toString() : null,
1501
			stayLatitude: day.stayLat ? day.stayLat.toString() : null,
1439
			stayLongitude: day.stayLng ? day.stayLng.toString() : null,
1502
			stayLongitude: day.stayLng ? day.stayLng.toString() : null,
1440
			totalDistanceKm: day.totalKm,
1503
			totalDistanceKm: Number(dayKm.toFixed(3)),
1441
			totalTimeMins: Math.round(day.totalMins),
1504
			totalTimeMins: Math.round(dayMins),
1442
			visits: day.visits.map(function (v) {
1505
			visits: visitsOut
1443
				return {id: v.id, type: v.type || 'partner'};
-
 
1444
			})
-
 
1445
		});
1506
		});
1446
	});
1507
	});
1447
 
1508
 
1448
	// EDIT RULE: detect removed leads → show popup with cancel/reschedule per lead
1509
	// EDIT RULE: detect removed leads → show popup with cancel/reschedule per lead
1449
	var removedLeads = [];
1510
	var removedLeads = [];