fix : added restriction for the multileg pools (row)
This commit is contained in:
parent
148de807f7
commit
102b6d57df
@ -1,4 +1,3 @@
|
||||
// label-restriction.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SelectionData } from '../selection.service/selection.service';
|
||||
|
||||
@ -22,15 +21,16 @@ export class LabelRestrictionService {
|
||||
// accept optional currentLabel
|
||||
getBlockedLabels(selections: SelectionData[], currentLabel?: string | null): Set<string> {
|
||||
const selectedGroups = new Set<string>();
|
||||
const multiLegLabels = ['TBP', 'MJP', 'JPP'];
|
||||
|
||||
// existing finalized selections
|
||||
// Existing finalized selections
|
||||
for (const row of selections) {
|
||||
if (row.label && LABEL_TO_GROUP[row.label]) {
|
||||
selectedGroups.add(LABEL_TO_GROUP[row.label]);
|
||||
}
|
||||
}
|
||||
|
||||
// also consider in-progress/current label (if provided)
|
||||
// Also consider in-progress/current label (if provided)
|
||||
if (currentLabel && LABEL_TO_GROUP[currentLabel]) {
|
||||
selectedGroups.add(LABEL_TO_GROUP[currentLabel]);
|
||||
}
|
||||
@ -52,6 +52,12 @@ export class LabelRestrictionService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// New rule: if any multi-leg label is finalized, block all other multi-leg labels
|
||||
const hasMultiLeg = selections.some(sel => multiLegLabels.includes(sel.label));
|
||||
if (hasMultiLeg) {
|
||||
multiLegLabels.forEach(label => blockLabels.add(label));
|
||||
}
|
||||
}
|
||||
|
||||
return blockLabels;
|
||||
@ -71,18 +77,18 @@ export class LabelRestrictionService {
|
||||
): boolean {
|
||||
const boxedLabels = new Set(['FRP', 'QNP', 'TNP']);
|
||||
|
||||
// check finalized selections
|
||||
// Check finalized selections
|
||||
for (const s of selections) {
|
||||
if (s && s.label && boxedLabels.has(s.label) && !!s.isBoxed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// check the in-progress/current row if provided
|
||||
// Check the in-progress/current row if provided
|
||||
if (current && current.label && boxedLabels.has(String(current.label)) && !!current.isBoxed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +53,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
maxRowsReached: boolean = false;
|
||||
totalAmountLimitReached: boolean = false;
|
||||
showLimitPopup: boolean = false;
|
||||
disabledLabels: string[] = ['SHW', 'SJP', '.','EXA'];
|
||||
disabledLabels: string[] = ['SHW', 'SJP', '.', 'EXA'];
|
||||
private hasFinalizedMultiLeg: boolean = false; // Tracks if a multi-leg pool is finalized
|
||||
|
||||
// TNP logic
|
||||
tanGroupStage = 0;
|
||||
@ -113,7 +114,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Always prefer rpinfo.structuredRaceCard if present, else fall back to raceCardData key (but still use only structuredRaceCard content)
|
||||
// Always prefer rpinfo.structuredRaceCard if present, else fall back to raceCardData key
|
||||
const rpinfo = this.safeGetJSON('rpinfo');
|
||||
if (rpinfo && rpinfo.structuredRaceCard) {
|
||||
this.structuredRaceCard = rpinfo.structuredRaceCard;
|
||||
@ -145,7 +146,8 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.selectionsSubscription = this.selectionService.selections$.subscribe((selections: SelectionData[]) => {
|
||||
this.currentSelections = selections;
|
||||
this.maxRowsReached = selections.length >= 5;
|
||||
// Update maxRowsReached to account for multi-leg finalization
|
||||
this.maxRowsReached = this.hasFinalizedMultiLeg || selections.length >= 5;
|
||||
const totalAmount = selections.reduce((sum: number, selection: SelectionData) => sum + (selection.total || 0), 0);
|
||||
this.totalAmountLimitReached = totalAmount >= 5000;
|
||||
this.refreshBlockedLabels(this.selectedLabel);
|
||||
@ -175,7 +177,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
|
||||
// Keep raw storage copy if present (but all logic below uses structuredRaceCard)
|
||||
// Keep raw storage copy if present
|
||||
const data = localStorage.getItem('raceCardData');
|
||||
if (data) {
|
||||
try { this.raceCardData = JSON.parse(data); } catch { this.raceCardData = this.raceCardData || {}; }
|
||||
@ -410,10 +412,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
isNumberDisabled(number: number): boolean {
|
||||
// Disable if number not present in actualRunners
|
||||
if (!this.actualRunners.has(number)) return true;
|
||||
// Disable if total amount limit reached
|
||||
// Disable if total amount limit reached
|
||||
if (this.totalAmountLimitReached) return true;
|
||||
// Allow all numbers for TNP when boxed, but disable selected numbers
|
||||
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||
// Allow all numbers for TNP when boxed, but disable selected numbers
|
||||
if (this.selectedLabel === 'TNP' && this.isBoxed) {
|
||||
return this.selectedNumbers.includes(number);
|
||||
}
|
||||
// TNP (unboxed): Disable numbers already selected in the current group
|
||||
@ -442,6 +444,12 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent selecting multi-leg labels if one is already finalized
|
||||
if (this.hasFinalizedMultiLeg && this.multiLegLabels.includes(label)) {
|
||||
console.log('[DEBUG] Cannot select another multi-leg pool until selections are cleared');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedLabel = label;
|
||||
this.selectedNumbers = [];
|
||||
this.padValue = '';
|
||||
@ -497,7 +505,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
this.selectionService.finalizeCurrentRow();
|
||||
const currentSelections = this.selectionService.getSelections();
|
||||
const existingWSP = currentSelections.filter(sel => wspLabels.includes(sel.label));
|
||||
if (existingWSP.length === 0) {
|
||||
if (existingWSP.length === 0 && !this.hasFinalizedMultiLeg) {
|
||||
const blankRows = wspLabels.map(lbl => ({
|
||||
label: lbl,
|
||||
numbers: [],
|
||||
@ -704,7 +712,17 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onPadEnter() {
|
||||
if (this.maxRowsReached) return;
|
||||
if (this.maxRowsReached || this.hasFinalizedMultiLeg) return;
|
||||
|
||||
// Prevent ENTER action for multi-leg pools if current row already has a value.
|
||||
// This ensures "Enter" won't re-run and cause race/leg selection clashes once the value is set.
|
||||
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
if (currentRow && typeof currentRow.value === 'number' && currentRow.value > 0) {
|
||||
// do nothing (ENTER is disabled for multi-leg after entering one value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.canPrint) {
|
||||
this.print();
|
||||
@ -715,8 +733,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
const labels = ['WNP', 'SHP', 'PLP'];
|
||||
const targetLabel = labels[this.wspTicketStage];
|
||||
const selections = this.selectionService.getSelections();
|
||||
// Find the current WSP row to ensure numbers are synchronized
|
||||
|
||||
// Find the current WSP row to ensure numbers are synchronized
|
||||
const currentWSPRow = selections.find(sel => sel.label === targetLabel);
|
||||
if (currentWSPRow) {
|
||||
this.selectedNumbers = [...currentWSPRow.numbers];
|
||||
@ -733,11 +750,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
this.selectionService.setSelections(updatedSelections);
|
||||
// Call after setSelections
|
||||
this.refreshBlockedLabels(this.selectedLabel);
|
||||
// Only increment stage if not at the last stage (PLP)
|
||||
|
||||
// Only increment stage if not at the last stage (PLP)
|
||||
if (this.wspTicketStage < 2) {
|
||||
this.wspTicketStage++;
|
||||
// Update selectedNumbers for the next stage
|
||||
// Update selectedNumbers for the next stage
|
||||
const nextLabel = labels[this.wspTicketStage];
|
||||
const nextWSPRow = updatedSelections.find(sel => sel.label === nextLabel);
|
||||
if (nextWSPRow) this.selectedNumbers = [...nextWSPRow.numbers];
|
||||
@ -981,7 +997,10 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
updateCanPrint() {
|
||||
if (this.maxRowsReached) { this.canPrint = false; return; }
|
||||
if (this.maxRowsReached || this.hasFinalizedMultiLeg) {
|
||||
this.canPrint = false;
|
||||
return;
|
||||
}
|
||||
this.canPrint = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
|
||||
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
||||
@ -991,19 +1010,17 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Add this getter for print button enable logic
|
||||
get canPrintTicket(): boolean {
|
||||
// At least one valid row in finalized selections or current row
|
||||
const selections = this.selectionService.getSelections();
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
if (this.selectedLabel === 'WSP') {
|
||||
// For WSP, require all three rows (WNP, SHP, PLP) to have valid numbers and values >= 1
|
||||
const wspLabels = ['WNP', 'SHP', 'PLP'];
|
||||
const wspSelections = selections.filter(sel => wspLabels.includes(sel.label));
|
||||
const wspSelections = this.selectionService.getSelections().filter(sel => wspLabels.includes(sel.label));
|
||||
const allWSPRowsValid = wspSelections.length === 3 && wspSelections.every(row =>
|
||||
row.label && row.numbers && row.numbers.length > 0 && row.value >= 1 && row.total > 0
|
||||
);
|
||||
return this.wspTicketStage === 2 && allWSPRowsValid;
|
||||
}
|
||||
// For non-WSP, keep existing logic: any valid row enables printing
|
||||
const selections = this.selectionService.getSelections();
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
const hasValidRow = selections.some(
|
||||
row => !!row.label && !!row.numbers && row.numbers.length > 0 && row.value > 0 && row.total > 0
|
||||
) || (
|
||||
@ -1012,9 +1029,49 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
return Boolean(hasValidRow);
|
||||
}
|
||||
|
||||
// ---------- Updated canEnterRow getter ----------
|
||||
get canEnterRow(): boolean {
|
||||
if (this.maxRowsReached) return false;
|
||||
|
||||
// If multi-leg pool (TBP/MJP/JPP): disable "Enter" once a value is already recorded
|
||||
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
// If a value already entered (>0) then Enter should be disabled; only PRINT allowed.
|
||||
if (currentRow && typeof currentRow.value === 'number' && currentRow.value > 0) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise allow Enter only when value is valid (1..100) and numbers exist
|
||||
return !!currentRow.label &&
|
||||
!!currentRow.numbers &&
|
||||
currentRow.numbers.length > 0 &&
|
||||
typeof currentRow.value === 'number' &&
|
||||
currentRow.value >= 1 &&
|
||||
currentRow.value <= 100 &&
|
||||
currentRow.total > 0;
|
||||
}
|
||||
|
||||
// WSP special-case (existing behavior)
|
||||
if (this.selectedLabel === 'WSP') {
|
||||
const isValidPadValue = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
|
||||
const hasNumbers = this.selectedNumbers.length > 0;
|
||||
return isValidPadValue && hasNumbers;
|
||||
}
|
||||
|
||||
// Default logic for non-multi, non-WSP labels
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
return !!currentRow.label &&
|
||||
!!currentRow.numbers &&
|
||||
currentRow.numbers.length > 0 &&
|
||||
typeof currentRow.value === 'number' &&
|
||||
currentRow.value >= 1 &&
|
||||
currentRow.value <= 100 &&
|
||||
currentRow.total > 0;
|
||||
}
|
||||
|
||||
print() {
|
||||
const selectionsTotal = this.currentSelections.reduce((sum, sel) => sum + (sel.total || 0), 0);
|
||||
let currentRowAmount = 0;
|
||||
|
||||
if (this.multiLegLabels.includes(this.selectedLabel || '')) {
|
||||
const maxLegs = this.getMaxLegs(this.currentPool || '');
|
||||
const horsesPerLeg = this.multiLegGroups.map((group, index) => {
|
||||
@ -1032,6 +1089,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
// Ensure all legs have selections
|
||||
if (horsesPerLeg.some(count => count === 0)) return;
|
||||
this.hasFinalizedMultiLeg = true; // Mark multi-leg pool as finalized
|
||||
} else {
|
||||
currentRowAmount = this.currentTotal;
|
||||
if (selectionsTotal + currentRowAmount > 5000) {
|
||||
@ -1098,7 +1156,7 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (allRows.length === 0) {
|
||||
console.warn("No valid rows to print.");
|
||||
this.cdr.markForCheck(); // <-- Ensure UI updates
|
||||
this.cdr.markForCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1183,34 +1241,33 @@ export class TouchPadMenuComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
let numbersStr = '';
|
||||
if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
if (row.isBoxed) {
|
||||
// Keep only numeric tokens; stringify when comparing to '#' to avoid number vs string compare.
|
||||
const actualNumbers = displayNumbers
|
||||
.filter((n): n is number => typeof n === 'number' && String(n) !== '#')
|
||||
.map(n => String(n).padStart(2, '0'))
|
||||
.join(',');
|
||||
numbersStr = `<<${actualNumbers}>>`;
|
||||
} else if (['FRP', 'QNP'].includes(row.label)) {
|
||||
// Convert every token to string for safe comparisons and joining.
|
||||
const actualNumbers = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
// Safe check for '#' presence in row.numbers by stringifying elements
|
||||
const rowHasHash = Array.isArray(row.numbers) && row.numbers.some(x => String(x) === '#');
|
||||
if (rowHasHash || row.isBoxed) {
|
||||
numbersStr = `${actualNumbers} - ${actualNumbers}`;
|
||||
} else {
|
||||
numbersStr = actualNumbers;
|
||||
}
|
||||
} else {
|
||||
// case: TNP but not boxed (falls through here). Use string comparisons consistently.
|
||||
numbersStr = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
}
|
||||
} else {
|
||||
// Default: non-FRP/QNP/TNP labels
|
||||
numbersStr = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
}
|
||||
|
||||
let numbersStr = '';
|
||||
if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
if (row.isBoxed) {
|
||||
// Keep only numeric tokens; stringify when comparing to '#' to avoid number vs string compare.
|
||||
const actualNumbers = displayNumbers
|
||||
.filter((n): n is number => typeof n === 'number' && String(n) !== '#')
|
||||
.map(n => String(n).padStart(2, '0'))
|
||||
.join(',');
|
||||
numbersStr = `<<${actualNumbers}>>`;
|
||||
} else if (['FRP', 'QNP'].includes(row.label)) {
|
||||
// Convert every token to string for safe comparisons and joining.
|
||||
const actualNumbers = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
// Safe check for '#' presence in row.numbers by stringifying elements
|
||||
const rowHasHash = Array.isArray(row.numbers) && row.numbers.some(x => String(x) === '#');
|
||||
if (rowHasHash || row.isBoxed) {
|
||||
numbersStr = `${actualNumbers} - ${actualNumbers}`;
|
||||
} else {
|
||||
numbersStr = actualNumbers;
|
||||
}
|
||||
} else {
|
||||
// case: TNP but not boxed (falls through here). Use string comparisons consistently.
|
||||
numbersStr = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
}
|
||||
} else {
|
||||
// Default: non-FRP/QNP/TNP labels
|
||||
numbersStr = displayNumbers.map(n => String(n)).filter(s => s !== '#').join(',');
|
||||
}
|
||||
|
||||
const label = displayLabel.padEnd(10);
|
||||
const numbers = numbersStr.padEnd(15);
|
||||
@ -1231,7 +1288,7 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
return legs.join('/');
|
||||
}
|
||||
|
||||
// helper to pad ticket count to 3 digits (001..999)
|
||||
// helper to pad ticket count to 3 digits (001..999)
|
||||
function padTicketCountToThree(n: number) {
|
||||
const num = Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
||||
return String(num).padStart(3, '0');
|
||||
@ -1276,15 +1333,15 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
return `${label} ${groupStrs.join('-')}${starPart}`;
|
||||
}
|
||||
|
||||
// 🔹 NEW RULE: check for "- -" style duplicate groups
|
||||
if (filteredGroups.length === 2) {
|
||||
const g1 = filteredGroups[0].map(normalizeToken);
|
||||
const g2 = filteredGroups[1].map(normalizeToken);
|
||||
if (g1.join(',') === g2.join(',')) {
|
||||
return `${label} <<${g1.join(',')}>>${starPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 NEW RULE: check for "- -" style duplicate groups
|
||||
if (filteredGroups.length === 2) {
|
||||
const g1 = filteredGroups[0].map(normalizeToken);
|
||||
const g2 = filteredGroups[1].map(normalizeToken);
|
||||
if (g1.join(',') === g2.join(',')) {
|
||||
return `${label} <<${g1.join(',')}>>${starPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
const singleTokens = filteredGroups[0] || [];
|
||||
if (singleTokens.length >= 2 && singleTokens.length % 2 === 0) {
|
||||
const half = singleTokens.length / 2;
|
||||
@ -1458,14 +1515,27 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
|
||||
console.log('Printer payload:', payload);
|
||||
|
||||
fetch('http://localhost:9100/print', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
// ---------------------sending data to backend ---------------------------------
|
||||
|
||||
fetch('http://localhost:9100/print', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
// ---------------------sending data to backend ---------------------------------
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Printer error: ${response.status}`);
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(result => {
|
||||
console.log("✅ Print successful:", result);
|
||||
this.erase();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("❌ Print failed:", error);
|
||||
this.erase();
|
||||
});
|
||||
|
||||
try {
|
||||
const existingTicketsStr = localStorage.getItem('localTickets');
|
||||
@ -1542,6 +1612,7 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
erase() {
|
||||
this.selectionService.clearSelections();
|
||||
this.resetSelections();
|
||||
this.hasFinalizedMultiLeg = false; // Reset multi-leg finalization flag
|
||||
this.refreshBlockedLabels(null);
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
@ -1568,7 +1639,7 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
this.fieldInput = '';
|
||||
this.fieldFEntered = false;
|
||||
this.wspTicketStage = 0;
|
||||
// Explicitly reset blocked labels (no current label)
|
||||
this.hasFinalizedMultiLeg = false; // Reset multi-leg finalization flag
|
||||
this.refreshBlockedLabels(null);
|
||||
this.updateCanPrint();
|
||||
this.sharedStateService.updateSharedData({ type: 'multiLegPoolEnd', value: null });
|
||||
@ -2002,25 +2073,6 @@ if (['FRP', 'QNP', 'TNP'].includes(row.label)) {
|
||||
get dedupedEnabledHorseNumbers(): number[] {
|
||||
return _.uniq(this.enabledHorseNumbers);
|
||||
}
|
||||
// Update canEnterRow to require value between 1 and 100
|
||||
get canEnterRow(): boolean {
|
||||
if (this.maxRowsReached) return false;
|
||||
if (this.selectedLabel === 'WSP') {
|
||||
const isValidPadValue = this.padValue.trim().length > 0 && /^[0-9]+$/.test(this.padValue);
|
||||
const hasNumbers = this.selectedNumbers.length > 0;
|
||||
return isValidPadValue && hasNumbers;
|
||||
}
|
||||
// Default logic for non-WSP
|
||||
const currentRow = this.selectionService.getCurrentRow();
|
||||
return !!currentRow.label &&
|
||||
!!currentRow.numbers &&
|
||||
currentRow.numbers.length > 0 &&
|
||||
typeof currentRow.value === 'number' &&
|
||||
currentRow.value >= 1 &&
|
||||
currentRow.value <= 100 &&
|
||||
currentRow.total > 0;
|
||||
}
|
||||
|
||||
// add this in the component class (near other helpers)
|
||||
private refreshBlockedLabels(currentLabel?: string | null) {
|
||||
// Pass finalized selections and optionally the in-progress/current label
|
||||
|
||||
Loading…
Reference in New Issue
Block a user