Beacon delete fix, price extraction, tax rate lookup, add modifiers form
This commit is contained in:
parent
0bdf9d60b7
commit
b360284e56
5 changed files with 256 additions and 104 deletions
|
|
@ -40,46 +40,32 @@ if (bizId LTE 0) {
|
|||
apiAbort({ OK=false, ERROR="no_business_selected" });
|
||||
}
|
||||
|
||||
if (!structKeyExists(data, "BeaconID") || !isNumeric(data.BeaconID) || int(data.BeaconID) LTE 0) {
|
||||
apiAbort({ OK=false, ERROR="missing_beacon_id", MESSAGE="BeaconID is required" });
|
||||
}
|
||||
// Get ServicePointID (sharding beacons are service points with BeaconMinor)
|
||||
servicePointId = structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID) ? int(data.ServicePointID) : 0;
|
||||
|
||||
beaconId = int(data.BeaconID);
|
||||
if (servicePointId LTE 0) {
|
||||
apiAbort({ OK=false, ERROR="missing_service_point_id", MESSAGE="ServicePointID is required" });
|
||||
}
|
||||
</cfscript>
|
||||
|
||||
<!--- Clear BeaconMinor on service point to remove the beacon --->
|
||||
<cfquery datasource="payfrit">
|
||||
UPDATE Beacons
|
||||
SET IsActive = 0
|
||||
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
|
||||
AND (
|
||||
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
OR ID IN (
|
||||
SELECT lt.BeaconID FROM lt_BeaconsID_BusinessesID lt
|
||||
WHERE lt.BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
|
||||
AND lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
)
|
||||
)
|
||||
UPDATE ServicePoints
|
||||
SET BeaconMinor = NULL
|
||||
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#servicePointId#">
|
||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
</cfquery>
|
||||
|
||||
<!--- confirm --->
|
||||
<cfquery name="qCheck" datasource="payfrit">
|
||||
SELECT ID, IsActive
|
||||
FROM Beacons
|
||||
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
|
||||
AND (
|
||||
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM lt_BeaconsID_BusinessesID lt
|
||||
WHERE lt.BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
|
||||
AND lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
)
|
||||
)
|
||||
SELECT ID FROM ServicePoints
|
||||
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#servicePointId#">
|
||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
LIMIT 1
|
||||
</cfquery>
|
||||
|
||||
<cfif qCheck.recordCount EQ 0>
|
||||
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found" })#</cfoutput>
|
||||
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found", DEBUG_ServicePointID=servicePointId, DEBUG_BusinessID=bizId })#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", BeaconID=beaconId })#</cfoutput>
|
||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", ServicePointID=servicePointId })#</cfoutput>
|
||||
|
|
|
|||
|
|
@ -121,13 +121,15 @@
|
|||
<cfset itemStruct["name"] = itemName>
|
||||
<cfset itemStruct["modifiers"] = arrayNew(1)>
|
||||
|
||||
<!--- Extract price --->
|
||||
<cfset priceMatch = reMatchNoCase('<span[^>]*class="price"[^>]*>\$?([0-9.]+)</span>', block)>
|
||||
<!--- Extract price - look for any dollar amount in the block --->
|
||||
<cfset itemStruct["price"] = 0>
|
||||
<cfset priceMatch = reMatchNoCase('\$([0-9]+\.?[0-9]*)', block)>
|
||||
<cfif arrayLen(priceMatch)>
|
||||
<cfset priceStr = reReplaceNoCase(priceMatch[1], '.*>\\$?([0-9.]+)</span>.*', '\1')>
|
||||
<cfset itemStruct["price"] = val(priceStr)>
|
||||
<cfelse>
|
||||
<cfset itemStruct["price"] = 0>
|
||||
<!--- priceMatch[1] is like "$12.99", strip the $ --->
|
||||
<cfset priceStr = replace(priceMatch[1], "$", "")>
|
||||
<cfif isNumeric(priceStr) AND val(priceStr) GT 0>
|
||||
<cfset itemStruct["price"] = val(priceStr)>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- Extract description --->
|
||||
|
|
@ -334,17 +336,43 @@
|
|||
</cfif>
|
||||
</cfif>
|
||||
<cfif structKeyExists(group, "items") AND isArray(group.items)>
|
||||
<!--- Debug: log first item's structure --->
|
||||
<cfif arrayLen(group.items) GT 0 AND NOT structKeyExists(variables, "loggedItemKeys")>
|
||||
<cfset variables.loggedItemKeys = true>
|
||||
<cfset firstItem = group.items[1]>
|
||||
<cfif isStruct(firstItem)>
|
||||
<cfset arrayAppend(response.steps, "First item keys: " & structKeyList(firstItem))>
|
||||
<!--- Log a few specific values for debugging --->
|
||||
<cfif structKeyExists(firstItem, "price")>
|
||||
<cfset arrayAppend(response.steps, "item.price = " & firstItem.price)>
|
||||
</cfif>
|
||||
<cfif structKeyExists(firstItem, "basePrice")>
|
||||
<cfset arrayAppend(response.steps, "item.basePrice = " & firstItem.basePrice)>
|
||||
</cfif>
|
||||
<cfif structKeyExists(firstItem, "displayPrice")>
|
||||
<cfset arrayAppend(response.steps, "item.displayPrice = " & firstItem.displayPrice)>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfloop array="#group.items#" index="item">
|
||||
<cfif structKeyExists(item, "name")>
|
||||
<!--- Map item name to category --->
|
||||
<cfif len(groupName)>
|
||||
<cfset itemCategoryMap[item.name] = groupName>
|
||||
</cfif>
|
||||
<!--- Extract price --->
|
||||
<!--- Extract price - try multiple field names --->
|
||||
<cfif structKeyExists(item, "price") AND isNumeric(item.price)>
|
||||
<cfset itemPriceMap[item.name] = val(item.price)>
|
||||
<cfelseif structKeyExists(item, "unitPrice") AND isNumeric(item.unitPrice)>
|
||||
<cfset itemPriceMap[item.name] = val(item.unitPrice)>
|
||||
<cfelseif structKeyExists(item, "basePrice") AND isNumeric(item.basePrice)>
|
||||
<cfset itemPriceMap[item.name] = val(item.basePrice)>
|
||||
<cfelseif structKeyExists(item, "displayPrice")>
|
||||
<!--- displayPrice might be a string like "$12.99" --->
|
||||
<cfset priceStr = reReplace(item.displayPrice, "[^0-9.]", "", "all")>
|
||||
<cfif len(priceStr) AND isNumeric(priceStr)>
|
||||
<cfset itemPriceMap[item.name] = val(priceStr)>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- Extract image URLs --->
|
||||
<cfif structKeyExists(item, "imageUrls")>
|
||||
|
|
|
|||
|
|
@ -3,32 +3,19 @@
|
|||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
<cfheader name="Cache-Control" value="no-store">
|
||||
|
||||
<cfset response = { "OK": false, "taxRate": 0, "message": "", "state": "" }>
|
||||
|
||||
<!--- State base sales tax rates (2024) - local rates may add 1-5% more --->
|
||||
<cfset stateTaxRates = {
|
||||
"AL": 4, "AK": 0, "AZ": 5.6, "AR": 6.5, "CA": 7.25,
|
||||
"CO": 2.9, "CT": 6.35, "DE": 0, "FL": 6, "GA": 4,
|
||||
"HI": 4, "ID": 6, "IL": 6.25, "IN": 7, "IA": 6,
|
||||
"KS": 6.5, "KY": 6, "LA": 4.45, "ME": 5.5, "MD": 6,
|
||||
"MA": 6.25, "MI": 6, "MN": 6.875, "MS": 7, "MO": 4.225,
|
||||
"MT": 0, "NE": 5.5, "NV": 6.85, "NH": 0, "NJ": 6.625,
|
||||
"NM": 4.875, "NY": 4, "NC": 4.75, "ND": 5, "OH": 5.75,
|
||||
"OK": 4.5, "OR": 0, "PA": 6, "RI": 7, "SC": 6,
|
||||
"SD": 4.2, "TN": 7, "TX": 6.25, "UT": 6.1, "VT": 6,
|
||||
"VA": 5.3, "WA": 6.5, "WV": 6, "WI": 5, "WY": 4, "DC": 6
|
||||
}>
|
||||
|
||||
<!--- Estimated average local rate addition by state (rough estimates) --->
|
||||
<cfset stateLocalAdd = {
|
||||
"AL": 5.2, "AZ": 2.8, "AR": 3, "CA": 1.5, "CO": 4.8,
|
||||
"GA": 4, "IL": 2.5, "KS": 2.5, "LA": 5.5, "MO": 4,
|
||||
"NV": 1.4, "NM": 2.7, "NY": 4.5, "NC": 2.3, "OH": 1.5,
|
||||
"OK": 4.5, "SC": 2, "SD": 2, "TN": 2.5, "TX": 2,
|
||||
"UT": 1.3, "WA": 3, "WI": 0.6
|
||||
}>
|
||||
<cfset response = { "OK": false, "taxRate": 0, "message": "", "city": "", "state": "" }>
|
||||
|
||||
<cftry>
|
||||
<!--- Load Claude API Key --->
|
||||
<cfset CLAUDE_API_KEY = "">
|
||||
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
|
||||
<cfif fileExists(configPath)>
|
||||
<cfset configData = deserializeJSON(fileRead(configPath))>
|
||||
<cfif structKeyExists(configData, "apiKey")>
|
||||
<cfset CLAUDE_API_KEY = configData.apiKey>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- Get ZIP code from URL or form --->
|
||||
<cfset zipCode = "">
|
||||
<cfif structKeyExists(url, "zip")>
|
||||
|
|
@ -47,43 +34,87 @@
|
|||
<!--- Use just the 5-digit portion --->
|
||||
<cfset zipCode = left(zipCode, 5)>
|
||||
|
||||
<!--- Use Zippopotam.us to get state from ZIP (free, no API key) --->
|
||||
<!--- Use Zippopotam.us to get city/state from ZIP --->
|
||||
<cfhttp url="https://api.zippopotam.us/us/#zipCode#" method="GET" timeout="10" result="httpResult">
|
||||
</cfhttp>
|
||||
|
||||
<cfif httpResult.statusCode CONTAINS "200">
|
||||
<cfset zipData = deserializeJSON(httpResult.fileContent)>
|
||||
<cfif NOT httpResult.statusCode CONTAINS "200">
|
||||
<cfset response.message = "ZIP lookup failed">
|
||||
<cfoutput>#serializeJSON(response)#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(zipData, "places") AND isArray(zipData.places) AND arrayLen(zipData.places) GT 0>
|
||||
<cfset place = zipData.places[1]>
|
||||
<cfset stateAbbr = uCase(place["state abbreviation"])>
|
||||
<cfset response.state = stateAbbr>
|
||||
<cfset zipData = deserializeJSON(httpResult.fileContent)>
|
||||
<cfif NOT structKeyExists(zipData, "places") OR NOT isArray(zipData.places) OR arrayLen(zipData.places) EQ 0>
|
||||
<cfset response.message = "No location data for ZIP">
|
||||
<cfoutput>#serializeJSON(response)#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<!--- Look up state base rate --->
|
||||
<cfif structKeyExists(stateTaxRates, stateAbbr)>
|
||||
<cfset baseRate = stateTaxRates[stateAbbr]>
|
||||
<cfset place = zipData.places[1]>
|
||||
<cfset cityName = place["place name"]>
|
||||
<cfset stateAbbr = uCase(place["state abbreviation"])>
|
||||
<cfset response.city = cityName>
|
||||
<cfset response.state = stateAbbr>
|
||||
|
||||
<!--- Add estimated local rate if available --->
|
||||
<cfset localAdd = 0>
|
||||
<cfif structKeyExists(stateLocalAdd, stateAbbr)>
|
||||
<cfset localAdd = stateLocalAdd[stateAbbr]>
|
||||
</cfif>
|
||||
<!--- If no Claude API key, return state-only estimate --->
|
||||
<cfif NOT len(CLAUDE_API_KEY)>
|
||||
<!--- Fallback: state base rates --->
|
||||
<cfset stateTaxRates = {
|
||||
"AL": 4, "AK": 0, "AZ": 5.6, "AR": 6.5, "CA": 7.25,
|
||||
"CO": 2.9, "CT": 6.35, "DE": 0, "FL": 6, "GA": 4,
|
||||
"HI": 4, "ID": 6, "IL": 6.25, "IN": 7, "IA": 6,
|
||||
"KS": 6.5, "KY": 6, "LA": 4.45, "ME": 5.5, "MD": 6,
|
||||
"MA": 6.25, "MI": 6, "MN": 6.875, "MS": 7, "MO": 4.225,
|
||||
"MT": 0, "NE": 5.5, "NV": 6.85, "NH": 0, "NJ": 6.625,
|
||||
"NM": 4.875, "NY": 4, "NC": 4.75, "ND": 5, "OH": 5.75,
|
||||
"OK": 4.5, "OR": 0, "PA": 6, "RI": 7, "SC": 6,
|
||||
"SD": 4.2, "TN": 7, "TX": 6.25, "UT": 6.1, "VT": 6,
|
||||
"VA": 5.3, "WA": 6.5, "WV": 6, "WI": 5, "WY": 4, "DC": 6
|
||||
}>
|
||||
<cfif structKeyExists(stateTaxRates, stateAbbr)>
|
||||
<cfset response.taxRate = stateTaxRates[stateAbbr]>
|
||||
<cfset response.OK = true>
|
||||
<cfset response.message = "State base rate only (no API key)">
|
||||
</cfif>
|
||||
<cfoutput>#serializeJSON(response)#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<cfset totalRate = baseRate + localAdd>
|
||||
<!--- Round to 2 decimal places --->
|
||||
<cfset response.taxRate = round(totalRate * 100) / 100>
|
||||
<!--- Ask Claude for the exact tax rate --->
|
||||
<cfset requestBody = {
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"max_tokens": 100,
|
||||
"temperature": 0,
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": "What is the current combined sales tax rate (state + county + city + special districts) for #cityName#, #stateAbbr# (ZIP code #zipCode#)? Return ONLY a single number representing the percentage, like 10.25 for 10.25%. No text, no explanation, just the number."
|
||||
}]
|
||||
}>
|
||||
|
||||
<cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="30" result="claudeResult">
|
||||
<cfhttpparam type="header" name="Content-Type" value="application/json">
|
||||
<cfhttpparam type="header" name="x-api-key" value="#CLAUDE_API_KEY#">
|
||||
<cfhttpparam type="header" name="anthropic-version" value="2023-06-01">
|
||||
<cfhttpparam type="body" value="#serializeJSON(requestBody)#">
|
||||
</cfhttp>
|
||||
|
||||
<cfif claudeResult.statusCode CONTAINS "200">
|
||||
<cfset claudeResponse = deserializeJSON(claudeResult.fileContent)>
|
||||
<cfif structKeyExists(claudeResponse, "content") AND arrayLen(claudeResponse.content)>
|
||||
<cfset rateText = trim(claudeResponse.content[1].text)>
|
||||
<!--- Extract just the number --->
|
||||
<cfset rateText = reReplace(rateText, "[^0-9.]", "", "all")>
|
||||
<cfif isNumeric(rateText) AND val(rateText) GT 0 AND val(rateText) LT 20>
|
||||
<cfset response.taxRate = val(rateText)>
|
||||
<cfset response.OK = true>
|
||||
<cfset response.stateRate = baseRate>
|
||||
<cfset response.localRate = localAdd>
|
||||
<cfset response.message = "Estimated rate for #place['place name']#, #stateAbbr#">
|
||||
<cfset response.message = "Tax rate for #cityName#, #stateAbbr#">
|
||||
<cfelse>
|
||||
<cfset response.message = "Unknown state: #stateAbbr#">
|
||||
<cfset response.message = "Could not parse tax rate">
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset response.message = "No location data for ZIP">
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset response.message = "ZIP lookup failed: #httpResult.statusCode#">
|
||||
<cfset response.message = "Claude API error">
|
||||
</cfif>
|
||||
|
||||
<cfcatch type="any">
|
||||
|
|
|
|||
|
|
@ -1696,18 +1696,21 @@ const Portal = {
|
|||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = this.beacons.map(b => `
|
||||
<div class="list-group-item ${b.IsActive ? '' : 'inactive'}">
|
||||
<div class="item-info">
|
||||
<div class="item-name">${this.escapeHtml(b.Name)}</div>
|
||||
<div class="item-detail">${b.UUID || b.NamespaceId || 'No UUID'}</div>
|
||||
container.innerHTML = this.beacons.map(b => {
|
||||
const minorInfo = b.Minor ? ` (Minor: ${b.Minor})` : '';
|
||||
return `
|
||||
<div class="list-group-item ${b.IsActive ? '' : 'inactive'}">
|
||||
<div class="item-info">
|
||||
<div class="item-name">${this.escapeHtml(b.Name)}${minorInfo}</div>
|
||||
<div class="item-detail">${b.UUID || 'No UUID'}</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<button class="btn btn-sm btn-secondary" onclick="Portal.editBeacon(${b.ServicePointID})">Edit</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="Portal.deleteBeacon(${b.ServicePointID})">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<button class="btn btn-sm btn-secondary" onclick="Portal.editBeacon(${b.BeaconID})">Edit</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="Portal.deleteBeacon(${b.BeaconID})">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
// Load service points list
|
||||
|
|
@ -1879,9 +1882,9 @@ const Portal = {
|
|||
this.showBeaconModal(beaconId);
|
||||
},
|
||||
|
||||
// Delete beacon
|
||||
async deleteBeacon(beaconId) {
|
||||
if (!confirm('Are you sure you want to deactivate this beacon?')) return;
|
||||
// Delete beacon (removes BeaconMinor from service point)
|
||||
async deleteBeacon(servicePointId) {
|
||||
if (!confirm('Are you sure you want to remove this beacon?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.config.apiBaseUrl}/beacons/delete.cfm`, {
|
||||
|
|
@ -1891,19 +1894,19 @@ const Portal = {
|
|||
'X-User-Token': this.config.token,
|
||||
'X-Business-ID': this.config.businessId
|
||||
},
|
||||
body: JSON.stringify({ BeaconID: beaconId })
|
||||
body: JSON.stringify({ ServicePointID: servicePointId })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.OK) {
|
||||
this.toast('Beacon deactivated', 'success');
|
||||
this.toast('Beacon removed', 'success');
|
||||
await this.loadBeacons();
|
||||
} else {
|
||||
this.toast(data.ERROR || 'Failed to delete beacon', 'error');
|
||||
this.toast(data.ERROR || 'Failed to remove beacon', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Portal] Error deleting beacon:', err);
|
||||
this.toast('Error deleting beacon', 'error');
|
||||
console.error('[Portal] Error removing beacon:', err);
|
||||
this.toast('Error removing beacon', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -2509,6 +2509,110 @@
|
|||
}
|
||||
}
|
||||
|
||||
function showAddModifierForm() {
|
||||
// Initialize modifiers array if needed
|
||||
if (!config.extractedData.modifiers) {
|
||||
config.extractedData.modifiers = [];
|
||||
}
|
||||
|
||||
addMessage('ai', `
|
||||
<div class="add-modifier-form">
|
||||
<h4>Add Modifier Template</h4>
|
||||
<div class="form-group">
|
||||
<label>Modifier Name</label>
|
||||
<input type="text" id="newModName" class="form-control" placeholder="e.g., Size, Add-ons, Spice Level">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="newModRequired"> Required (customer must choose)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Options</label>
|
||||
<div id="newModOptions">
|
||||
<div class="modifier-option-row">
|
||||
<input type="text" class="form-control option-name" placeholder="Option name">
|
||||
<input type="number" class="form-control option-price" placeholder="Price" step="0.01" min="0">
|
||||
<button class="btn btn-sm btn-outline" onclick="removeModifierOption(this)">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline" onclick="addModifierOption()">+ Add Option</button>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-outline" onclick="showItemsStep()">Cancel</button>
|
||||
<button class="btn btn-outline" onclick="saveModifierAndAddAnother()">Save & Add Another</button>
|
||||
<button class="btn btn-primary" onclick="saveModifierAndContinue()">Save & Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
function addModifierOption() {
|
||||
const container = document.getElementById('newModOptions');
|
||||
const row = document.createElement('div');
|
||||
row.className = 'modifier-option-row';
|
||||
row.innerHTML = \`
|
||||
<input type="text" class="form-control option-name" placeholder="Option name">
|
||||
<input type="number" class="form-control option-price" placeholder="Price" step="0.01" min="0">
|
||||
<button class="btn btn-sm btn-outline" onclick="removeModifierOption(this)">×</button>
|
||||
\`;
|
||||
container.appendChild(row);
|
||||
}
|
||||
|
||||
function removeModifierOption(btn) {
|
||||
const row = btn.closest('.modifier-option-row');
|
||||
const container = document.getElementById('newModOptions');
|
||||
if (container.children.length > 1) {
|
||||
row.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function saveCurrentModifier() {
|
||||
const name = document.getElementById('newModName').value.trim();
|
||||
if (!name) {
|
||||
alert('Please enter a modifier name');
|
||||
return false;
|
||||
}
|
||||
|
||||
const required = document.getElementById('newModRequired').checked;
|
||||
const optionRows = document.querySelectorAll('#newModOptions .modifier-option-row');
|
||||
const options = [];
|
||||
|
||||
optionRows.forEach(row => {
|
||||
const optName = row.querySelector('.option-name').value.trim();
|
||||
const optPrice = parseFloat(row.querySelector('.option-price').value) || 0;
|
||||
if (optName) {
|
||||
options.push({ name: optName, price: optPrice });
|
||||
}
|
||||
});
|
||||
|
||||
if (options.length === 0) {
|
||||
alert('Please add at least one option');
|
||||
return false;
|
||||
}
|
||||
|
||||
config.extractedData.modifiers.push({
|
||||
name: name,
|
||||
required: required,
|
||||
options: options,
|
||||
appliesTo: 'uncertain'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveModifierAndAddAnother() {
|
||||
if (saveCurrentModifier()) {
|
||||
showAddModifierForm();
|
||||
}
|
||||
}
|
||||
|
||||
function saveModifierAndContinue() {
|
||||
if (saveCurrentModifier()) {
|
||||
showItemsStep();
|
||||
}
|
||||
}
|
||||
|
||||
function confirmModifiers() {
|
||||
const list = document.getElementById('modifiersList');
|
||||
const templates = list.querySelectorAll('.modifier-template');
|
||||
|
|
|
|||
Reference in a new issue