Compare commits

..

No commits in common. "1210249f54d2c2175060155d9f6e64f5c6eb5eaf" and "c5ebb24b391ec654a0b2be4b544f4d57866321f8" have entirely different histories.

253 changed files with 5508 additions and 11058 deletions

View file

@ -24,9 +24,9 @@
<cfif request.UserID NEQ 0> <cfif request.UserID NEQ 0>
<cfquery name="check_user" datasource="#application.datasource#"> <cfquery name="check_user" datasource="#application.datasource#">
SELECT FirstName, Balance, ImageExtension SELECT UserFirstName, UserBalance, UserImageExtension
FROM Users FROM Users
WHERE ID = #request.UserID# WHERE UserID = #request.UserID#
</cfquery> </cfquery>
</cfif> </cfif>
@ -320,8 +320,8 @@
<cfif find("logout.cfm", request.cgiPath) EQ 0> <cfif find("logout.cfm", request.cgiPath) EQ 0>
<cfif request.UserID NEQ 0> <cfif request.UserID NEQ 0>
<cfif check_user.ImageExtension gt ""><img src="#application.image_display_prefix#users/thumbs/#request.UserID#.#check_user.ImageExtension#" border="0" alt=""><br></cfif> <cfif check_user.UserImageExtension gt ""><img src="#application.image_display_prefix#users/thumbs/#request.UserID#.#check_user.UserImageExtension#" border="0" alt=""><br></cfif>
Hi, <cfif check_user.FirstName gt "">#check_user.FirstName#<br>#dollarformat(check_user.Balance)#<cfelse>Payfrit User</cfif><br> Hi, <cfif check_user.UserFirstName gt "">#check_user.UserFirstName#<br>#dollarformat(check_user.UserBalance)#<cfelse>Payfrit User</cfif><br>
<cfelse> <cfelse>
<form action="#application.wwwrootprefix#index.cfm" method="post" name="login_form" id="login_form"> <form action="#application.wwwrootprefix#index.cfm" method="post" name="login_form" id="login_form">

View file

@ -1,5 +0,0 @@
# Payfrit Portal Updates
## Week of 2026-01-26
- Fixed saved cart not being detected when entering child business

View file

@ -116,13 +116,13 @@
<cfset cart_total = 0> <cfset cart_total = 0>
<CFQUERY name="get_queued_food" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="get_queued_food" datasource="#application.datasource#" dbtype="ODBC">
SELECT A.CartID, A.AddedOn, A.Quantity, A.SpecialRemark, B.Name, B.UserID, C.Name, A.Price, D.FirstName, D.LaerFirstName, D.Balance SELECT A.CartID, A.AddedOn, A.Quantity, A.SpecialRemark, B.BusinessName, B.UserID, C.ItemName, A.Price, D.UserFirstName, D.LaerFirstName, D.Balance
FROM dbo.Business_CartMaster A, dbo.BusinessMaster B, dbo.Business_ItemMaster C, Users D FROM dbo.Business_CartMaster A, dbo.BusinessMaster B, dbo.Business_ItemMaster C, Users D
WHERE A.UserID = D.UserID WHERE A.UserID = D.UserID
AND AND
A.ItemID = C.ItemID A.ItemID = C.ItemID
AND AND
B.ID = C.BusinessID B.BusinessID = C.BusinessID
AND AND
C.BusinessID = #form.bizid# C.BusinessID = #form.bizid#
AND AND
@ -170,11 +170,11 @@
</CFQUERY> </CFQUERY>
<CFQUERY name="get_last_inserted" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="get_last_inserted" datasource="#application.datasource#" dbtype="ODBC">
SELECT TOP 1 O.ID, M.UserID as person_to_pay_for_orderID, U.Balance SELECT TOP 1 O.OrderID, M.UserID as person_to_pay_for_orderID, U.Balance
FROM dbo.Business_OrderMaster O, dbo.BusinessMaster M, Users U FROM dbo.Business_OrderMaster O, dbo.BusinessMaster M, Users U
WHERE O.BusinessID = M.BusinessID WHERE O.BusinessID = M.BusinessID
AND AND
M.UserID = U.ID M.UserID = U.UserID
ORDER BY O.AddedOn DESC ORDER BY O.AddedOn DESC
</CFQUERY> </CFQUERY>
@ -185,7 +185,7 @@
) )
VALUES VALUES
( (
#get_last_inserted.ID#, #get_last_inserted.OrderID#,
#get_queued_food.CartID# #get_queued_food.CartID#
) )
</CFQUERY> </CFQUERY>
@ -268,7 +268,7 @@
<CFQUERY name="get_user_104_balance" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="get_user_104_balance" datasource="#application.datasource#" dbtype="ODBC">
SELECT balance SELECT balance
FROM Users FROM Users
WHERE ID = 104 WHERE UserID = 104
</CFQUERY> </CFQUERY>
<CFQUERY name="transfer_fees_to_UserID_104" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="transfer_fees_to_UserID_104" datasource="#application.datasource#" dbtype="ODBC">
@ -346,7 +346,7 @@
<CFQUERY name="get_user_104_balance" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="get_user_104_balance" datasource="#application.datasource#" dbtype="ODBC">
SELECT balance SELECT balance
FROM Users FROM Users
WHERE ID = 104 WHERE UserID = 104
</CFQUERY> </CFQUERY>
<CFQUERY name="transfer_fees_to_UserID_104" datasource="#application.datasource#" dbtype="ODBC"> <CFQUERY name="transfer_fees_to_UserID_104" datasource="#application.datasource#" dbtype="ODBC">

View file

@ -164,8 +164,8 @@ async function refreshAssignments(){
const tr = document.createElement("tr"); const tr = document.createElement("tr");
tr.innerHTML = ` tr.innerHTML = `
<td>${a.lt_Beacon_Businesses_ServicePointID}</td> <td>${a.lt_Beacon_Businesses_ServicePointID}</td>
<td>${escapeHtml((a.Name || "") + " (ID " + a.BeaconID + ")")}</td> <td>${escapeHtml((a.BeaconName || "") + " (ID " + a.BeaconID + ")")}</td>
<td>${escapeHtml((a.Name || "") + " (ID " + a.ServicePointID + ")")}</td> <td>${escapeHtml((a.ServicePointName || "") + " (ID " + a.ServicePointID + ")")}</td>
<td>${escapeHtml(a.lt_Beacon_Businesses_ServicePointNotes || "")}</td> <td>${escapeHtml(a.lt_Beacon_Businesses_ServicePointNotes || "")}</td>
<td>${escapeHtml(a.CreatedAt || "")}</td> <td>${escapeHtml(a.CreatedAt || "")}</td>
`; `;
@ -183,12 +183,12 @@ async function refreshBeacons(assignedBeaconIDs, keepSelectedBeaconID){
setSelectPlaceholder(sel, "-- Select Beacon --"); setSelectPlaceholder(sel, "-- Select Beacon --");
(out.BEACONS || []).forEach(b => { (out.BEACONS || []).forEach(b => {
const isAssigned = assignedBeaconIDs.has(String(b.ID)); const isAssigned = assignedBeaconIDs.has(String(b.BeaconID));
if (HIDE_ASSIGNED_BEACONS && isAssigned) return; if (HIDE_ASSIGNED_BEACONS && isAssigned) return;
const opt = document.createElement("option"); const opt = document.createElement("option");
opt.value = b.ID; opt.value = b.BeaconID;
opt.textContent = String(b.ID) + " - " + (b.Name || ""); opt.textContent = String(b.BeaconID) + " - " + (b.BeaconName || "");
sel.appendChild(opt); sel.appendChild(opt);
}); });
@ -203,12 +203,12 @@ async function refreshServicePoints(assignedServicePointIDs, keepSelectedService
setSelectPlaceholder(sel, "-- Select ServicePoint --"); setSelectPlaceholder(sel, "-- Select ServicePoint --");
(out.SERVICEPOINTS || []).forEach(sp => { (out.SERVICEPOINTS || []).forEach(sp => {
const isAssigned = assignedServicePointIDs.has(String(sp.ID)); const isAssigned = assignedServicePointIDs.has(String(sp.ServicePointID));
if (HIDE_ASSIGNED_SERVICEPOINTS && isAssigned) return; if (HIDE_ASSIGNED_SERVICEPOINTS && isAssigned) return;
const opt = document.createElement("option"); const opt = document.createElement("option");
opt.value = sp.ID; opt.value = sp.ServicePointID;
opt.textContent = String(sp.ID) + " - " + (sp.Name || ""); opt.textContent = String(sp.ServicePointID) + " - " + (sp.ServicePointName || "");
sel.appendChild(opt); sel.appendChild(opt);
}); });

View file

@ -25,7 +25,7 @@
</head> </head>
<body> <body>
<h2>Beacons</h2> <h2>Beacons</h2>
<div class="warn">Required: Name</div> <div class="warn">Required: BeaconName</div>
<div class="ok" id="jsStatus">(JS not loaded yet)</div> <div class="ok" id="jsStatus">(JS not loaded yet)</div>
<div class="row"> <div class="row">
@ -38,8 +38,8 @@
</div> </div>
<div> <div>
<label>Name (required)</label><br> <label>BeaconName (required)</label><br>
<input id="Name" placeholder="Front Door" required> <input id="BeaconName" placeholder="Front Door" required>
</div> </div>
<div> <div>
@ -160,13 +160,13 @@ function escapeHtml(s){
} }
function loadIntoForm(b){ function loadIntoForm(b){
document.getElementById("BeaconID").value = b.ID || ""; document.getElementById("BeaconID").value = b.BeaconID || "";
document.getElementById("Name").value = b.Name || ""; document.getElementById("BeaconName").value = b.BeaconName || "";
document.getElementById("UUID").value = b.UUID || ""; document.getElementById("UUID").value = b.UUID || "";
document.getElementById("NamespaceId").value = b.NamespaceId || ""; document.getElementById("NamespaceId").value = b.NamespaceId || "";
document.getElementById("InstanceId").value = b.InstanceId || ""; document.getElementById("InstanceId").value = b.InstanceId || "";
document.getElementById("IsActive").value = ("" + (b.IsActive ?? 1)); document.getElementById("IsActive").value = ("" + (b.IsActive ?? 1));
document.getElementById("DelBeaconID").value = b.ID || ""; document.getElementById("DelBeaconID").value = b.BeaconID || "";
} }
async function refresh() { async function refresh() {
@ -180,8 +180,8 @@ async function refresh() {
for (const b of items) { for (const b of items) {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
tr.innerHTML = ` tr.innerHTML = `
<td>${b.ID}</td> <td>${b.BeaconID}</td>
<td>${escapeHtml(b.Name||"")}</td> <td>${escapeHtml(b.BeaconName||"")}</td>
<td>${escapeHtml(b.UUID||"")}</td> <td>${escapeHtml(b.UUID||"")}</td>
<td>${escapeHtml(b.NamespaceId||"")}</td> <td>${escapeHtml(b.NamespaceId||"")}</td>
<td>${escapeHtml(b.InstanceId||"")}</td> <td>${escapeHtml(b.InstanceId||"")}</td>
@ -194,15 +194,15 @@ async function refresh() {
} }
async function saveBeacon() { async function saveBeacon() {
const name = (document.getElementById("Name").value || "").trim(); const name = (document.getElementById("BeaconName").value || "").trim();
if (!name) { if (!name) {
show({ OK:false, ERROR:"missing_beacon_name", MESSAGE:"Name is required" }); show({ OK:false, ERROR:"missing_beacon_name", MESSAGE:"BeaconName is required" });
return; return;
} }
const body = { const body = {
BeaconID: valIntOrNull("BeaconID"), BeaconID: valIntOrNull("BeaconID"),
Name: name, BeaconName: name,
UUID: (document.getElementById("UUID").value || "").trim(), UUID: (document.getElementById("UUID").value || "").trim(),
NamespaceId: (document.getElementById("NamespaceId").value || "").trim(), NamespaceId: (document.getElementById("NamespaceId").value || "").trim(),
InstanceId: (document.getElementById("InstanceId").value || "").trim(), InstanceId: (document.getElementById("InstanceId").value || "").trim(),

View file

@ -7,16 +7,16 @@
<cfparam name="users_to_email" default=""> <cfparam name="users_to_email" default="">
<cfquery name="select_users_to_email" datasource="#application.datasource#"> <cfquery name="select_users_to_email" datasource="#application.datasource#">
SELECT U.EmailAddress SELECT U.UserEmailAddress
FROM Users U FROM Users U
WHERE UserID in (0,1,2) WHERE UserID in (0,1,2)
</cfquery> </cfquery>
<cfoutput query="select_users_to_email"> <cfoutput query="select_users_to_email">
#EmailAddress#, #UserEmailAddress#,
<cfset users_to_email=listappend(users_to_email, #EmailAddress#)> <cfset users_to_email=listappend(users_to_email, #UserEmailAddress#)>
</cfoutput><br><br> </cfoutput><br><br>
@ -95,18 +95,18 @@
<cfloop index="the_email_address" list="#users_to_email#"> <cfloop index="the_email_address" list="#users_to_email#">
<cfquery name="get_user_email" datasource="#application.datasource#"> <cfquery name="get_user_email" datasource="#application.datasource#">
SELECT UUID SELECT UserUUID
FROM Users FROM Users
WHERE EmailAddress = '#the_email_address#' WHERE UserEmailAddress = '#the_email_address#'
AND AND
UserIsEmailverified = 1 UserIsEmailverified = 1
AND AND
IsContactVerified > 0 UserIsContactVerified > 0
</cfquery> </cfquery>
<cfset form.this_email_body = form.email_body & " <cfset form.this_email_body = form.email_body & "
instant unsubscribe link: https://www.payfrit.com/remove_me.cfm?UUID="&#get_user_email.UUID#> instant unsubscribe link: https://www.payfrit.com/remove_me.cfm?UserUUID="&#get_user_email.UserUUID#>
<cfmail to="#the_email_address#" from="admin@payfrit.com" subject="#form.email_subject#" type="HTML"> <cfmail to="#the_email_address#" from="admin@payfrit.com" subject="#form.email_subject#" type="HTML">
#HTMLCodeFormat(form.this_email_body)#</cfmail> #HTMLCodeFormat(form.this_email_body)#</cfmail>

View file

@ -5,14 +5,14 @@
<cfif form.mode eq "start"> <cfif form.mode eq "start">
<CFQUERY name="get_verified_users" datasource="#application.datasource#"> <CFQUERY name="get_verified_users" datasource="#application.datasource#">
SELECT U.ID, U.EmailAddress, U.ContactNumber,U.AddedOn SELECT U.UserID, U.UserEmailAddress, U.UserContactNumber,U.UserAddedOn
FROM Users U FROM Users U
WHERE U.IsEmailVerified = 1 WHERE U.UserIsEmailVerified = 1
AND AND
U.UserIsCOntactVerified > 0 U.UserIsCOntactVerified > 0
AND AND
U.ID > 435 U.UserID > 435
ORDER BY U.ID DESC ORDER BY U.UserID DESC
</cfquery> </cfquery>
@ -48,16 +48,16 @@
</form> </form>
</td> </td>
<td>#EmailAddress#</td> <td>#UserEmailAddress#</td>
<td>#ContactNumber#</td> <td>#UserContactNumber#</td>
<td>#dateformat(AddedOn, "mmmm dd, YYYY")# at #timeformat(AddedOn, "hh:nn tt")#</td> <td>#dateformat(UserAddedOn, "mmmm dd, YYYY")# at #timeformat(UserAddedOn, "hh:nn tt")#</td>
<td> <td>
<CFQUERY name="get_orders" datasource="#application.datasource#"> <CFQUERY name="get_orders" datasource="#application.datasource#">
SELECT O.UUID SELECT O.OrderUUID
FROM Orders O FROM Orders O
WHERE O.UserID = #get_verified_users.ID# WHERE O.OrderUserID = #get_verified_users.UserID#
ORDER BY O.ID DESC ORDER BY O.OrderID DESC
</cfquery> </cfquery>
<cfparam name="looper" default=""> <cfparam name="looper" default="">
@ -66,7 +66,7 @@
<cfloop query="get_orders"> <cfloop query="get_orders">
<cfset looper=incrementvalue(looper)> <cfset looper=incrementvalue(looper)>
<a href="https://payfr.it/show_order.cfm?UUID=#get_orders.UUID#&is_admin_view=1" target="_blank">#looper#</a>, &nbsp; <a href="https://payfr.it/show_order.cfm?OrderUUID=#get_orders.OrderUUID#&is_admin_view=1" target="_blank">#looper#</a>, &nbsp;
</cfloop> </cfloop>
</td> </td>
@ -79,10 +79,10 @@
<cfelseif form.mode eq "user_traffic"> <cfelseif form.mode eq "user_traffic">
<CFQUERY name="get_user_traffic" datasource="#application.datasource#"> <CFQUERY name="get_user_traffic" datasource="#application.datasource#">
SELECT V.PageMode, V.AddedOn SELECT V.VisitorTrackingPageMode, V.VisitorTrackingAddedOn
FROM VisitorTrackings V FROM VisitorTracking V
WHERE V.UserID = #form.chip# WHERE V.VisitorTrackingUserID = #form.chip#
ORDER BY V.AddedOn DESC ORDER BY V.VisitorTrackingAddedOn DESC
</cfquery> </cfquery>
<table> <table>
@ -92,8 +92,8 @@
</tr> </tr>
<cfoutput query="get_user_traffic"> <cfoutput query="get_user_traffic">
<tr> <tr>
<td>#PageMode#</td> <td>#VisitorTrackingPageMode#</td>
<td>#AddedOn#</td> <td>#VisitorTrackingAddedOn#</td>
</tr> </tr>
</cfoutput> </cfoutput>
</table> </table>

View file

@ -25,7 +25,7 @@
</head> </head>
<body> <body>
<h2>ServicePoints</h2> <h2>ServicePoints</h2>
<div class="warn">Required: Name</div> <div class="warn">Required: ServicePointName</div>
<div class="ok" id="jsStatus">(JS not loaded yet)</div> <div class="ok" id="jsStatus">(JS not loaded yet)</div>
<div class="row"> <div class="row">
@ -38,18 +38,18 @@
</div> </div>
<div> <div>
<label>Name (required)</label><br> <label>ServicePointName (required)</label><br>
<input id="Name" placeholder="Front Counter" required> <input id="ServicePointName" placeholder="Front Counter" required>
</div> </div>
<div> <div>
<label>TypeID</label><br> <label>ServicePointTypeID</label><br>
<input id="TypeID" placeholder="0"> <input id="ServicePointTypeID" placeholder="0">
</div> </div>
<div> <div>
<label>Code</label><br> <label>ServicePointCode</label><br>
<input id="Code" placeholder="COUNTER"> <input id="ServicePointCode" placeholder="COUNTER">
</div> </div>
<div> <div>
@ -160,14 +160,14 @@ function escapeHtml(s){
} }
function loadIntoForm(sp){ function loadIntoForm(sp){
document.getElementById("ServicePointID").value = sp.ID || ""; document.getElementById("ServicePointID").value = sp.ServicePointID || "";
document.getElementById("Name").value = sp.Name || ""; document.getElementById("ServicePointName").value = sp.ServicePointName || "";
document.getElementById("TypeID").value = (sp.TypeID ?? 0); document.getElementById("ServicePointTypeID").value = (sp.ServicePointTypeID ?? 0);
document.getElementById("Code").value = sp.Code || ""; document.getElementById("ServicePointCode").value = sp.ServicePointCode || "";
document.getElementById("Description").value = sp.Description || ""; document.getElementById("Description").value = sp.Description || "";
document.getElementById("SortOrder").value = (sp.SortOrder ?? 0); document.getElementById("SortOrder").value = (sp.SortOrder ?? 0);
document.getElementById("IsActive").value = ("" + (sp.IsActive ?? 1)); document.getElementById("IsActive").value = ("" + (sp.IsActive ?? 1));
document.getElementById("DelServicePointID").value = sp.ID || ""; document.getElementById("DelServicePointID").value = sp.ServicePointID || "";
} }
async function refresh() { async function refresh() {
@ -181,10 +181,10 @@ async function refresh() {
for (const sp of items) { for (const sp of items) {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
tr.innerHTML = ` tr.innerHTML = `
<td>${sp.ID}</td> <td>${sp.ServicePointID}</td>
<td>${escapeHtml(sp.Name||"")}</td> <td>${escapeHtml(sp.ServicePointName||"")}</td>
<td>${sp.TypeID}</td> <td>${sp.ServicePointTypeID}</td>
<td>${escapeHtml(sp.Code||"")}</td> <td>${escapeHtml(sp.ServicePointCode||"")}</td>
<td>${sp.SortOrder}</td> <td>${sp.SortOrder}</td>
<td>${sp.IsActive}</td> <td>${sp.IsActive}</td>
`; `;
@ -195,17 +195,17 @@ async function refresh() {
} }
async function saveSP() { async function saveSP() {
const name = (document.getElementById("Name").value || "").trim(); const name = (document.getElementById("ServicePointName").value || "").trim();
if (!name) { if (!name) {
show({ OK:false, ERROR:"missing_servicepoint_name", MESSAGE:"Name is required" }); show({ OK:false, ERROR:"missing_servicepoint_name", MESSAGE:"ServicePointName is required" });
return; return;
} }
const body = { const body = {
ServicePointID: valIntOrNull("ServicePointID"), ServicePointID: valIntOrNull("ServicePointID"),
Name: name, ServicePointName: name,
TypeID: valIntOrZero("TypeID"), ServicePointTypeID: valIntOrZero("ServicePointTypeID"),
Code: (document.getElementById("Code").value || "").trim(), ServicePointCode: (document.getElementById("ServicePointCode").value || "").trim(),
Description: (document.getElementById("Description").value || "").trim(), Description: (document.getElementById("Description").value || "").trim(),
SortOrder: valIntOrZero("SortOrder"), SortOrder: valIntOrZero("SortOrder"),
IsActive: parseInt(document.getElementById("IsActive").value, 10) IsActive: parseInt(document.getElementById("IsActive").value, 10)

View file

@ -173,11 +173,6 @@ if (len(request._api_path)) {
// Worker app endpoints // Worker app endpoints
if (findNoCase("/api/workers/myBusinesses.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/workers/myBusinesses.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/workers/tierStatus.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/workers/createAccount.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/workers/onboardingLink.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/workers/earlyUnlock.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/workers/ledger.cfm", request._api_path)) request._api_isPublic = true;
// Portal endpoints // Portal endpoints
if (findNoCase("/api/portal/stats.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/portal/stats.cfm", request._api_path)) request._api_isPublic = true;
@ -198,7 +193,6 @@ if (len(request._api_path)) {
if (findNoCase("/api/menu/getForBuilder.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/getForBuilder.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/saveFromBuilder.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/saveFromBuilder.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/updateStations.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/updateStations.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/menus.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/uploadHeader.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/uploadHeader.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/listCategories.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/listCategories.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/saveCategory.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/menu/saveCategory.cfm", request._api_path)) request._api_isPublic = true;
@ -265,9 +259,6 @@ if (len(request._api_path)) {
if (findNoCase("/api/ratings/setup.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/ratings/setup.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/ratings/submit.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/ratings/submit.cfm", request._api_path)) request._api_isPublic = true;
// App info endpoints (public, no auth needed)
if (findNoCase("/api/app/about.cfm", request._api_path)) request._api_isPublic = true;
// Stripe endpoints // Stripe endpoints
if (findNoCase("/api/stripe/onboard.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/stripe/onboard.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/stripe/status.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/stripe/status.cfm", request._api_path)) request._api_isPublic = true;

View file

@ -38,11 +38,9 @@ try {
// Optional fields // Optional fields
line2 = trim(data.Line2 ?: ""); line2 = trim(data.Line2 ?: "");
label = trim(data.Label ?: "");
setAsDefault = (data.SetAsDefault ?: false) == true; setAsDefault = (data.SetAsDefault ?: false) == true;
// Hardcoded to delivery address type
typeId = 2;
// Validation // Validation
if (len(line1) == 0 || len(city) == 0 || stateId <= 0 || len(zipCode) == 0) { if (len(line1) == 0 || len(city) == 0 || stateId <= 0 || len(zipCode) == 0) {
writeOutput(serializeJSON({ writeOutput(serializeJSON({
@ -53,17 +51,16 @@ try {
abort; abort;
} }
// If setting as default, clear other defaults first (for same type) // If setting as default, clear other defaults first
if (setAsDefault) { if (setAsDefault) {
queryExecute(" queryExecute("
UPDATE Addresses UPDATE Addresses
SET AddressIsDefaultDelivery = 0 SET AddressIsDefaultDelivery = 0
WHERE UserID = :userId WHERE AddressUserID = :userId
AND (BusinessID = 0 OR BusinessID IS NULL) AND (AddressBusinessID = 0 OR AddressBusinessID IS NULL)
AND AddressTypeID = :typeId AND AddressTypeID LIKE '%2%'
", { ", {
userId: { value: userId, cfsqltype: "cf_sql_integer" }, userId: { value: userId, cfsqltype: "cf_sql_integer" }
typeId: { value: typeId, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
} }
@ -75,23 +72,23 @@ try {
queryExecute(" queryExecute("
INSERT INTO Addresses ( INSERT INTO Addresses (
AddressID, AddressID,
UserID, AddressUserID,
BusinessID, AddressBusinessID,
AddressTypeID, AddressTypeID,
AddressLabel, AddressLabel,
AddressIsDefaultDelivery, AddressIsDefaultDelivery,
Line1, AddressLine1,
Line2, AddressLine2,
City, AddressCity,
StateID, AddressStateID,
ZIPCode, AddressZIPCode,
IsDeleted, AddressIsDeleted,
AddedOn AddressAddedOn
) VALUES ( ) VALUES (
:addressId, :addressId,
:userId, :userId,
0, 0,
:typeId, '2',
:label, :label,
:isDefault, :isDefault,
:line1, :line1,
@ -105,7 +102,6 @@ try {
", { ", {
addressId: { value: newAddressId, cfsqltype: "cf_sql_integer" }, addressId: { value: newAddressId, cfsqltype: "cf_sql_integer" },
userId: { value: userId, cfsqltype: "cf_sql_integer" }, userId: { value: userId, cfsqltype: "cf_sql_integer" },
typeId: { value: typeId, cfsqltype: "cf_sql_integer" },
label: { value: label, cfsqltype: "cf_sql_varchar" }, label: { value: label, cfsqltype: "cf_sql_varchar" },
isDefault: { value: setAsDefault ? 1 : 0, cfsqltype: "cf_sql_integer" }, isDefault: { value: setAsDefault ? 1 : 0, cfsqltype: "cf_sql_integer" },
line1: { value: line1, cfsqltype: "cf_sql_varchar" }, line1: { value: line1, cfsqltype: "cf_sql_varchar" },
@ -117,7 +113,7 @@ try {
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
// Get state info for response // Get state info for response
qState = queryExecute("SELECT Abbreviation as StateAbbreviation, Name as StateName FROM tt_States WHERE ID = :stateId", { qState = queryExecute("SELECT tt_StateAbbreviation as StateAbbreviation, tt_StateName as StateName FROM tt_States WHERE tt_StateID = :stateId", {
stateId: { value: stateId, cfsqltype: "cf_sql_integer" } stateId: { value: stateId, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
@ -128,7 +124,6 @@ try {
"OK": true, "OK": true,
"ADDRESS": { "ADDRESS": {
"AddressID": newAddressId, "AddressID": newAddressId,
"TypeID": typeId,
"Label": len(label) ? label : "Address", "Label": len(label) ? label : "Address",
"IsDefault": setAsDefault, "IsDefault": setAsDefault,
"Line1": line1, "Line1": line1,

View file

@ -76,11 +76,11 @@ if (addressId <= 0) {
try { try {
// First, get the address details so we can find all matching duplicates // First, get the address details so we can find all matching duplicates
qAddr = queryExecute(" qAddr = queryExecute("
SELECT Line1, Line2, City, StateID, ZIPCode SELECT AddressLine1, AddressLine2, AddressCity, AddressStateID, AddressZIPCode
FROM Addresses FROM Addresses
WHERE ID = :addressId WHERE AddressID = :addressId
AND UserID = :userId AND AddressUserID = :userId
AND IsDeleted = 0 AND AddressIsDeleted = 0
", { ", {
addressId: { value = addressId, cfsqltype = "cf_sql_integer" }, addressId: { value = addressId, cfsqltype = "cf_sql_integer" },
userId: { value = userId, cfsqltype = "cf_sql_integer" } userId: { value = userId, cfsqltype = "cf_sql_integer" }
@ -93,21 +93,21 @@ try {
// Soft-delete ALL addresses that match the same Line1, Line2, City, StateID, ZIPCode // Soft-delete ALL addresses that match the same Line1, Line2, City, StateID, ZIPCode
qDelete = queryExecute(" qDelete = queryExecute("
UPDATE Addresses UPDATE Addresses
SET IsDeleted = 1 SET AddressIsDeleted = 1
WHERE UserID = :userId WHERE AddressUserID = :userId
AND Line1 = :line1 AND AddressLine1 = :line1
AND Line2 = :line2 AND AddressLine2 = :line2
AND City = :city AND AddressCity = :city
AND StateID = :stateId AND AddressStateID = :stateId
AND ZIPCode = :zip AND AddressZIPCode = :zip
AND IsDeleted = 0 AND AddressIsDeleted = 0
", { ", {
userId: { value = userId, cfsqltype = "cf_sql_integer" }, userId: { value = userId, cfsqltype = "cf_sql_integer" },
line1: { value = qAddr.Line1, cfsqltype = "cf_sql_varchar", null = !len(qAddr.Line1) }, line1: { value = qAddr.AddressLine1, cfsqltype = "cf_sql_varchar", null = !len(qAddr.AddressLine1) },
line2: { value = qAddr.Line2, cfsqltype = "cf_sql_varchar", null = !len(qAddr.Line2) }, line2: { value = qAddr.AddressLine2, cfsqltype = "cf_sql_varchar", null = !len(qAddr.AddressLine2) },
city: { value = qAddr.City, cfsqltype = "cf_sql_varchar", null = !len(qAddr.City) }, city: { value = qAddr.AddressCity, cfsqltype = "cf_sql_varchar", null = !len(qAddr.AddressCity) },
stateId: { value = qAddr.StateID, cfsqltype = "cf_sql_integer" }, stateId: { value = qAddr.AddressStateID, cfsqltype = "cf_sql_integer" },
zip: { value = qAddr.ZIPCode, cfsqltype = "cf_sql_varchar", null = !len(qAddr.ZIPCode) } zip: { value = qAddr.AddressZIPCode, cfsqltype = "cf_sql_varchar", null = !len(qAddr.AddressZIPCode) }
}); });
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -46,25 +46,26 @@ if (userId <= 0) {
} }
try { try {
// Get user's delivery addresses // Get user's delivery addresses with GROUP BY to show unique addresses only
qAddresses = queryExecute(" qAddresses = queryExecute("
SELECT SELECT
a.ID, MIN(a.AddressID) as AddressID,
a.IsDefaultDelivery, MAX(a.AddressLabel) as AddressLabel,
a.Line1, MAX(a.AddressIsDefaultDelivery) as AddressIsDefaultDelivery,
a.Line2, a.AddressLine1,
a.City, a.AddressLine2,
a.StateID, a.AddressCity,
s.Abbreviation as StateAbbreviation, a.AddressStateID,
s.Name as StateName, MAX(s.tt_StateAbbreviation) as StateAbbreviation,
a.ZIPCode MAX(s.tt_StateName) as StateName,
a.AddressZIPCode
FROM Addresses a FROM Addresses a
LEFT JOIN tt_States s ON a.StateID = s.ID LEFT JOIN tt_States s ON a.AddressStateID = s.tt_StateID
WHERE a.UserID = :userId WHERE a.AddressUserID = :userId
AND (a.BusinessID = 0 OR a.BusinessID IS NULL) AND a.AddressTypeID LIKE '%2%'
AND a.AddressTypeID = 2 AND a.AddressIsDeleted = 0
AND a.IsDeleted = 0 GROUP BY a.AddressLine1, a.AddressLine2, a.AddressCity, a.AddressStateID, a.AddressZIPCode
ORDER BY a.IsDefaultDelivery DESC, a.ID DESC ORDER BY MAX(a.AddressIsDefaultDelivery) DESC, MIN(a.AddressID) DESC
", { ", {
userId: { value = userId, cfsqltype = "cf_sql_integer" } userId: { value = userId, cfsqltype = "cf_sql_integer" }
}); });
@ -72,15 +73,17 @@ try {
addresses = []; addresses = [];
for (row in qAddresses) { for (row in qAddresses) {
arrayAppend(addresses, { arrayAppend(addresses, {
"AddressID": row.ID, "AddressID": row.AddressID,
"IsDefault": row.IsDefaultDelivery == 1, "Label": len(row.AddressLabel) ? row.AddressLabel : "Address",
"Line1": row.Line1, "IsDefault": row.AddressIsDefaultDelivery == 1,
"Line2": row.Line2 ?: "", "Line1": row.AddressLine1,
"City": row.City, "Line2": row.AddressLine2 ?: "",
"StateID": row.StateID, "City": row.AddressCity,
"StateID": row.AddressStateID,
"StateAbbr": row.StateAbbreviation ?: "", "StateAbbr": row.StateAbbreviation ?: "",
"ZIPCode": row.ZIPCode, "StateName": row.StateName ?: "",
"DisplayText": row.Line1 & (len(row.Line2) ? ", " & row.Line2 : "") & ", " & row.City & ", " & (row.StateAbbreviation ?: "") & " " & row.ZIPCode "ZIPCode": row.AddressZIPCode,
"DisplayText": row.AddressLine1 & (len(row.AddressLine2) ? ", " & row.AddressLine2 : "") & ", " & row.AddressCity & ", " & (row.StateAbbreviation ?: "") & " " & row.AddressZIPCode
}); });
} }
@ -93,7 +96,8 @@ try {
apiAbort({ apiAbort({
"OK": false, "OK": false,
"ERROR": "server_error", "ERROR": "server_error",
"MESSAGE": e.message "MESSAGE": e.message,
"LINE": e.tagContext[1].line ?: 0
}); });
} }
</cfscript> </cfscript>

View file

@ -41,11 +41,11 @@ try {
// Verify address belongs to user // Verify address belongs to user
qCheck = queryExecute(" qCheck = queryExecute("
SELECT ID SELECT AddressID
FROM Addresses FROM Addresses
WHERE ID = :addressId WHERE AddressID = :addressId
AND UserID = :userId AND AddressUserID = :userId
AND IsDeleted = 0 AND AddressIsDeleted = 0
", { ", {
addressId: { value: addressId, cfsqltype: "cf_sql_integer" }, addressId: { value: addressId, cfsqltype: "cf_sql_integer" },
userId: { value: userId, cfsqltype: "cf_sql_integer" } userId: { value: userId, cfsqltype: "cf_sql_integer" }
@ -64,8 +64,8 @@ try {
queryExecute(" queryExecute("
UPDATE Addresses UPDATE Addresses
SET AddressIsDefaultDelivery = 0 SET AddressIsDefaultDelivery = 0
WHERE UserID = :userId WHERE AddressUserID = :userId
AND (BusinessID = 0 OR BusinessID IS NULL) AND (AddressBusinessID = 0 OR AddressBusinessID IS NULL)
AND AddressTypeID LIKE '%2%' AND AddressTypeID LIKE '%2%'
", { ", {
userId: { value: userId, cfsqltype: "cf_sql_integer" } userId: { value: userId, cfsqltype: "cf_sql_integer" }
@ -75,7 +75,7 @@ try {
queryExecute(" queryExecute("
UPDATE Addresses UPDATE Addresses
SET AddressIsDefaultDelivery = 1 SET AddressIsDefaultDelivery = 1
WHERE ID = :addressId WHERE AddressID = :addressId
", { ", {
addressId: { value: addressId, cfsqltype: "cf_sql_integer" } addressId: { value: addressId, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });

View file

@ -6,9 +6,9 @@
<cfscript> <cfscript>
try { try {
qStates = queryExecute(" qStates = queryExecute("
SELECT tt_StateID as StateID, Abbreviation as StateAbbreviation, Name as StateName SELECT tt_StateID as StateID, tt_StateAbbreviation as StateAbbreviation, tt_StateName as StateName
FROM tt_States FROM tt_States
ORDER BY Name ORDER BY tt_StateName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
states = []; states = [];

View file

@ -1,40 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="max-age=3600">
<cfscript>
/**
* Get list of address types
* GET: /api/addresses/types.cfm
* Returns: { OK: true, TYPES: [{ ID: 1, Label: "Billing" }, ...] }
*/
try {
qTypes = queryExecute("
SELECT tt_AddressTypeID as ID, tt_AddressType as Label
FROM tt_AddressTypes
ORDER BY tt_AddressTypeID
", {}, { datasource: "payfrit" });
types = [];
for (row in qTypes) {
arrayAppend(types, {
"ID": row.ID,
"Label": row.Label
});
}
writeOutput(serializeJSON({
"OK": true,
"TYPES": types
}));
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message
}));
}
</cfscript>

View file

@ -1,37 +0,0 @@
<cfsetting showdebugoutput="false">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Add IsActive column to TaskCategories table
try {
// Check if column exists
qCheck = queryExecute("
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'TaskCategories'
AND COLUMN_NAME = 'IsActive'
", [], { datasource: "payfrit" });
if (qCheck.recordCount == 0) {
queryExecute("
ALTER TABLE TaskCategories
ADD COLUMN IsActive TINYINT(1) NOT NULL DEFAULT 1
", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column IsActive added to TaskCategories"
}));
} else {
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column already exists"
}));
}
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -8,9 +8,9 @@
* Add Schedule Fields to Categories Table * Add Schedule Fields to Categories Table
* *
* Adds time-based scheduling fields: * Adds time-based scheduling fields:
* - ScheduleStart: TIME - Start time when category is available (e.g., 06:00:00 for breakfast) * - CategoryScheduleStart: TIME - Start time when category is available (e.g., 06:00:00 for breakfast)
* - ScheduleEnd: TIME - End time when category stops being available (e.g., 11:00:00) * - CategoryScheduleEnd: TIME - End time when category stops being available (e.g., 11:00:00)
* - ScheduleDays: VARCHAR(20) - Comma-separated list of day IDs (1=Sun, 2=Mon, etc.) or NULL for all days * - CategoryScheduleDays: VARCHAR(20) - Comma-separated list of day IDs (1=Sun, 2=Mon, etc.) or NULL for all days
* *
* Run this once to migrate the schema. * Run this once to migrate the schema.
*/ */
@ -24,38 +24,38 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Categories' AND TABLE_NAME = 'Categories'
AND COLUMN_NAME IN ('ScheduleStart', 'ScheduleEnd', 'ScheduleDays') AND COLUMN_NAME IN ('CategoryScheduleStart', 'CategoryScheduleEnd', 'CategoryScheduleDays')
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
existingCols = valueList(qCheck.COLUMN_NAME); existingCols = valueList(qCheck.COLUMN_NAME);
added = []; added = [];
// Add ScheduleStart if not exists // Add CategoryScheduleStart if not exists
if (!listFindNoCase(existingCols, "ScheduleStart")) { if (!listFindNoCase(existingCols, "CategoryScheduleStart")) {
queryExecute(" queryExecute("
ALTER TABLE Categories ALTER TABLE Categories
ADD COLUMN ScheduleStart TIME NULL ADD COLUMN CategoryScheduleStart TIME NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(added, "ScheduleStart"); arrayAppend(added, "CategoryScheduleStart");
} }
// Add ScheduleEnd if not exists // Add CategoryScheduleEnd if not exists
if (!listFindNoCase(existingCols, "ScheduleEnd")) { if (!listFindNoCase(existingCols, "CategoryScheduleEnd")) {
queryExecute(" queryExecute("
ALTER TABLE Categories ALTER TABLE Categories
ADD COLUMN ScheduleEnd TIME NULL ADD COLUMN CategoryScheduleEnd TIME NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(added, "ScheduleEnd"); arrayAppend(added, "CategoryScheduleEnd");
} }
// Add ScheduleDays if not exists // Add CategoryScheduleDays if not exists
if (!listFindNoCase(existingCols, "ScheduleDays")) { if (!listFindNoCase(existingCols, "CategoryScheduleDays")) {
queryExecute(" queryExecute("
ALTER TABLE Categories ALTER TABLE Categories
ADD COLUMN ScheduleDays VARCHAR(20) NULL ADD COLUMN CategoryScheduleDays VARCHAR(20) NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(added, "ScheduleDays"); arrayAppend(added, "CategoryScheduleDays");
} }
response["OK"] = true; response["OK"] = true;

View file

@ -15,8 +15,8 @@ try {
// Find the Fountain Soda item we created // Find the Fountain Soda item we created
qFountain = queryExecute(" qFountain = queryExecute("
SELECT ID, Name FROM Items SELECT ItemID, ItemName FROM Items
WHERE BusinessID = :bizId AND Name = 'Fountain Soda' WHERE ItemBusinessID = :bizId AND ItemName = 'Fountain Soda'
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" }); ", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
if (qFountain.recordCount == 0) { if (qFountain.recordCount == 0) {
@ -31,13 +31,13 @@ try {
// Update Fountain Soda to require child selection and be collapsible // Update Fountain Soda to require child selection and be collapsible
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET RequiresChildSelection = 1, IsCollapsible = 1 SET ItemRequiresChildSelection = 1, ItemIsCollapsible = 1
WHERE ItemID = :itemId WHERE ItemID = :itemId
", { itemId: fountainId }, { datasource: "payfrit" }); ", { itemId: fountainId }, { datasource: "payfrit" });
// Check if modifiers already exist // Check if modifiers already exist
qExisting = queryExecute(" qExisting = queryExecute("
SELECT COUNT(*) as cnt FROM Items WHERE ParentItemID = :parentId SELECT COUNT(*) as cnt FROM Items WHERE ItemParentItemID = :parentId
", { parentId: fountainId }, { datasource: "payfrit" }); ", { parentId: fountainId }, { datasource: "payfrit" });
if (qExisting.cnt > 0) { if (qExisting.cnt > 0) {
@ -53,10 +53,10 @@ try {
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, RequiresChildSelection, ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
MaxNumSelectionReq, AddedOn ItemMaxNumSelectionReq, ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, 0, :parentId, :itemId, :bizId, 0, :parentId,
'Size', 'Choose your size', 0, 1, 'Size', 'Choose your size', 0, 1,
@ -80,10 +80,10 @@ try {
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" }); qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, IsCheckedByDefault, ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, 0, :parentId, :itemId, :bizId, 0, :parentId,
:name, '', :price, 1, :name, '', :price, 1,
@ -108,10 +108,10 @@ try {
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, RequiresChildSelection, ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
MaxNumSelectionReq, AddedOn ItemMaxNumSelectionReq, ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, 0, :parentId, :itemId, :bizId, 0, :parentId,
'Flavor', 'Choose your drink', 0, 1, 'Flavor', 'Choose your drink', 0, 1,
@ -139,10 +139,10 @@ try {
qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" }); qMaxItem = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, IsCheckedByDefault, ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, 0, :parentId, :itemId, :bizId, 0, :parentId,
:name, '', 0, 1, :name, '', 0, 1,

View file

@ -5,7 +5,7 @@
<cfscript> <cfscript>
/** /**
* Add CategoryID column to Items table * Add ItemCategoryID column to Items table
*/ */
response = { "OK": false }; response = { "OK": false };
@ -17,30 +17,30 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Items' AND TABLE_NAME = 'Items'
AND COLUMN_NAME = 'CategoryID' AND COLUMN_NAME = 'ItemCategoryID'
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
if (qCheck.recordCount > 0) { if (qCheck.recordCount > 0) {
response["OK"] = true; response["OK"] = true;
response["MESSAGE"] = "CategoryID column already exists"; response["MESSAGE"] = "ItemCategoryID column already exists";
} else { } else {
// Add the column // Add the column
queryExecute(" queryExecute("
ALTER TABLE Items ALTER TABLE Items
ADD COLUMN CategoryID INT NULL DEFAULT 0 AFTER ParentItemID ADD COLUMN ItemCategoryID INT NULL DEFAULT 0 AFTER ItemParentItemID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
// Add index for performance // Add index for performance
try { try {
queryExecute(" queryExecute("
CREATE INDEX idx_items_categoryid ON Items(CategoryID) CREATE INDEX idx_items_categoryid ON Items(ItemCategoryID)
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} catch (any indexErr) { } catch (any indexErr) {
// Index might already exist // Index might already exist
} }
response["OK"] = true; response["OK"] = true;
response["MESSAGE"] = "CategoryID column added successfully"; response["MESSAGE"] = "ItemCategoryID column added successfully";
} }
} catch (any e) { } catch (any e) {

View file

@ -6,7 +6,7 @@
try { try {
// Check if columns already exist // Check if columns already exist
checkCols = queryExecute( checkCols = queryExecute(
"SHOW COLUMNS FROM Addresses LIKE 'Latitude'", "SHOW COLUMNS FROM Addresses LIKE 'AddressLat'",
[], [],
{ datasource = "payfrit" } { datasource = "payfrit" }
); );
@ -15,8 +15,8 @@ try {
// Add the columns // Add the columns
queryExecute( queryExecute(
"ALTER TABLE Addresses "ALTER TABLE Addresses
ADD COLUMN Latitude DECIMAL(10,7) NULL, ADD COLUMN AddressLat DECIMAL(10,7) NULL,
ADD COLUMN Longitude DECIMAL(10,7) NULL", ADD COLUMN AddressLng DECIMAL(10,7) NULL",
[], [],
{ datasource = "payfrit" } { datasource = "payfrit" }
); );

View file

@ -1,37 +0,0 @@
<cfsetting showdebugoutput="false">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Add CategoryID column to tt_TaskTypes (Services) table
try {
// Check if column exists
qCheck = queryExecute("
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'tt_TaskTypes'
AND COLUMN_NAME = 'TaskCategoryID'
", [], { datasource: "payfrit" });
if (qCheck.recordCount == 0) {
queryExecute("
ALTER TABLE tt_TaskTypes
ADD COLUMN TaskCategoryID INT NULL
", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column TaskCategoryID added to tt_TaskTypes"
}));
} else {
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column already exists"
}));
}
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -3,7 +3,7 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript> <cfscript>
// Add SourceType and SourceID columns to Tasks table // Add TaskSourceType and TaskSourceID columns to Tasks table
// These are needed for chat persistence feature // These are needed for chat persistence feature
result = { "OK": true, "STEPS": [] }; result = { "OK": true, "STEPS": [] };
@ -15,30 +15,30 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Tasks' AND TABLE_NAME = 'Tasks'
AND COLUMN_NAME IN ('SourceType', 'SourceID') AND COLUMN_NAME IN ('TaskSourceType', 'TaskSourceID')
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
existingCols = valueList(cols.COLUMN_NAME); existingCols = valueList(cols.COLUMN_NAME);
arrayAppend(result.STEPS, "Existing columns: #existingCols#"); arrayAppend(result.STEPS, "Existing columns: #existingCols#");
// Add SourceType if missing // Add TaskSourceType if missing
if (!listFindNoCase(existingCols, "SourceType")) { if (!listFindNoCase(existingCols, "TaskSourceType")) {
queryExecute(" queryExecute("
ALTER TABLE Tasks ADD COLUMN SourceType VARCHAR(50) NULL ALTER TABLE Tasks ADD COLUMN TaskSourceType VARCHAR(50) NULL
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
arrayAppend(result.STEPS, "Added SourceType column"); arrayAppend(result.STEPS, "Added TaskSourceType column");
} else { } else {
arrayAppend(result.STEPS, "SourceType already exists"); arrayAppend(result.STEPS, "TaskSourceType already exists");
} }
// Add SourceID if missing // Add TaskSourceID if missing
if (!listFindNoCase(existingCols, "SourceID")) { if (!listFindNoCase(existingCols, "TaskSourceID")) {
queryExecute(" queryExecute("
ALTER TABLE Tasks ADD COLUMN SourceID INT NULL ALTER TABLE Tasks ADD COLUMN TaskSourceID INT NULL
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
arrayAppend(result.STEPS, "Added SourceID column"); arrayAppend(result.STEPS, "Added TaskSourceID column");
} else { } else {
arrayAppend(result.STEPS, "SourceID already exists"); arrayAppend(result.STEPS, "TaskSourceID already exists");
} }
// Verify columns now exist // Verify columns now exist
@ -47,7 +47,7 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Tasks' AND TABLE_NAME = 'Tasks'
AND COLUMN_NAME IN ('SourceType', 'SourceID') AND COLUMN_NAME IN ('TaskSourceType', 'TaskSourceID')
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
result.COLUMNS = []; result.COLUMNS = [];

View file

@ -6,49 +6,49 @@
// Show all beacons with their current business/service point assignments // Show all beacons with their current business/service point assignments
q = queryExecute(" q = queryExecute("
SELECT SELECT
b.ID, b.BeaconID,
b.UUID, b.BeaconUUID,
b.Name, b.BeaconName,
sp_link.BusinessID, lt.BusinessID,
sp_link.ID, lt.ServicePointID,
biz.Name, biz.BusinessName,
sp.Name sp.ServicePointName
FROM Beacons b FROM Beacons b
LEFT JOIN ServicePoints sp_link ON sp_link.BeaconID = b.ID LEFT JOIN lt_Beacon_Businesses_ServicePoints lt ON lt.BeaconID = b.BeaconID
LEFT JOIN Businesses biz ON biz.ID = sp_link.BusinessID LEFT JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
LEFT JOIN ServicePoints sp ON sp.ID = sp_link.ID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
WHERE b.IsActive = 1 WHERE b.BeaconIsActive = 1
ORDER BY b.ID ORDER BY b.BeaconID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
rows = []; rows = [];
for (row in q) { for (row in q) {
arrayAppend(rows, { arrayAppend(rows, {
"BeaconID": row.ID, "BeaconID": row.BeaconID,
"UUID": row.UUID, "BeaconUUID": row.BeaconUUID,
"Name": row.Name ?: "", "BeaconName": row.BeaconName ?: "",
"BusinessID": row.BusinessID ?: 0, "BusinessID": row.BusinessID ?: 0,
"Name": row.Name ?: "", "BusinessName": row.BusinessName ?: "",
"ServicePointID": row.ServicePointID ?: 0, "ServicePointID": row.ServicePointID ?: 0,
"Name": row.Name ?: "" "ServicePointName": row.ServicePointName ?: ""
}); });
} }
// Also get service points for reference // Also get service points for reference
spQuery = queryExecute(" spQuery = queryExecute("
SELECT sp.ID, sp.Name, sp.BusinessID, b.Name SELECT sp.ServicePointID, sp.ServicePointName, sp.ServicePointBusinessID, b.BusinessName
FROM ServicePoints sp FROM ServicePoints sp
JOIN Businesses b ON b.ID = sp.BusinessID JOIN Businesses b ON b.BusinessID = sp.ServicePointBusinessID
ORDER BY sp.BusinessID, sp.ID ORDER BY sp.ServicePointBusinessID, sp.ServicePointID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
servicePoints = []; servicePoints = [];
for (sp in spQuery) { for (sp in spQuery) {
arrayAppend(servicePoints, { arrayAppend(servicePoints, {
"ServicePointID": sp.ID, "ServicePointID": sp.ServicePointID,
"Name": sp.Name, "ServicePointName": sp.ServicePointName,
"BusinessID": sp.BusinessID, "BusinessID": sp.ServicePointBusinessID,
"Name": sp.Name "BusinessName": sp.BusinessName
}); });
} }

View file

@ -5,16 +5,16 @@
<cfscript> <cfscript>
// Check Big Dean's owner // Check Big Dean's owner
q = queryExecute(" q = queryExecute("
SELECT b.ID, b.Name, b.UserID SELECT b.BusinessID, b.BusinessName, b.BusinessUserID
FROM Businesses b FROM Businesses b
WHERE b.ID = 27 WHERE b.BusinessID = 27
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
// Get users // Get users
users = queryExecute(" users = queryExecute("
SELECT * SELECT *
FROM Users FROM Users
ORDER BY ID ORDER BY UserID
LIMIT 20 LIMIT 20
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
@ -23,9 +23,9 @@ colNames = users.getColumnNames();
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"BigDeans": { "BigDeans": {
"BusinessID": q.ID, "BusinessID": q.BusinessID,
"Name": q.Name, "BusinessName": q.BusinessName,
"UserID": q.UserID "BusinessUserID": q.BusinessUserID
}, },
"UserColumns": colNames, "UserColumns": colNames,
"UserCount": users.recordCount "UserCount": users.recordCount

View file

@ -12,9 +12,9 @@ if (!len(phone)) {
} }
q = queryExecute(" q = queryExecute("
SELECT ID, FirstName, LastName, EmailAddress, ContactNumber, IsContactVerified SELECT UserID, UserFirstName, UserLastName, UserEmail, UserPhone, UserIsContactVerified
FROM Users FROM Users
WHERE ContactNumber = :phone OR EmailAddress = :phone WHERE UserPhone = :phone OR UserEmail = :phone
LIMIT 1 LIMIT 1
", { phone: phone }, { datasource: "payfrit" }); ", { phone: phone }, { datasource: "payfrit" });
@ -25,11 +25,11 @@ if (q.recordCount EQ 0) {
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"UserID": q.ID, "UserID": q.UserID,
"FirstName": q.FirstName, "FirstName": q.UserFirstName,
"LastName": q.LastName, "LastName": q.UserLastName,
"Email": q.EmailAddress, "Email": q.UserEmail,
"Phone": q.ContactNumber, "Phone": q.UserPhone,
"Verified": q.IsContactVerified "Verified": q.UserIsContactVerified
})); }));
</cfscript> </cfscript>

View file

@ -5,8 +5,7 @@
<cfscript> <cfscript>
/** /**
* Cleanup Lazy Daisy Beacons * Cleanup Lazy Daisy Beacons
* - Unassigns beacons 7, 8, 9 from service points * - Removes duplicate beacons created by setupBeaconTables
* - Deletes beacons 7, 8, 9
* - Updates original beacons with proper names * - Updates original beacons with proper names
*/ */
response = { "OK": false, "steps": [] }; response = { "OK": false, "steps": [] };
@ -14,50 +13,52 @@ response = { "OK": false, "steps": [] };
try { try {
lazyDaisyID = 37; lazyDaisyID = 37;
// Unassign beacons 7, 8, 9 from any service points // Delete duplicate assignments for beacons 7, 8, 9
queryExecute(" queryExecute("
UPDATE ServicePoints DELETE FROM lt_Beacon_Businesses_ServicePoints
SET BeaconID = NULL, AssignedByUserID = NULL
WHERE BeaconID IN (7, 8, 9) AND BusinessID = :bizId WHERE BeaconID IN (7, 8, 9) AND BusinessID = :bizId
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Unassigned beacons 7, 8, 9 from service points"); response.steps.append("Deleted duplicate assignments for beacons 7, 8, 9");
// Delete duplicate beacons 7, 8, 9 // Delete duplicate beacons 7, 8, 9
queryExecute(" queryExecute("
DELETE FROM Beacons DELETE FROM Beacons
WHERE ID IN (7, 8, 9) AND BusinessID = :bizId WHERE BeaconID IN (7, 8, 9) AND BeaconBusinessID = :bizId
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Deleted duplicate beacons 7, 8, 9"); response.steps.append("Deleted duplicate beacons 7, 8, 9");
// Update original beacons with names based on their service point assignments // Update original beacons with names based on their service point assignments
// Beacon 4 -> Table 1 (ServicePointID 4)
// Beacon 5 -> Table 2 (ServicePointID 5)
// Beacon 6 -> Table 3 (ServicePointID 6)
queryExecute(" queryExecute("
UPDATE Beacons SET Name = 'Beacon - Table 1' UPDATE Beacons SET BeaconName = 'Beacon - Table 1'
WHERE ID = 4 AND BusinessID = :bizId WHERE BeaconID = 4 AND BeaconBusinessID = :bizId
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Updated Beacon 4 name to 'Beacon - Table 1'"); response.steps.append("Updated Beacon 4 name to 'Beacon - Table 1'");
queryExecute(" queryExecute("
UPDATE Beacons SET Name = 'Beacon - Table 2' UPDATE Beacons SET BeaconName = 'Beacon - Table 2'
WHERE ID = 5 AND BusinessID = :bizId WHERE BeaconID = 5 AND BeaconBusinessID = :bizId
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Updated Beacon 5 name to 'Beacon - Table 2'"); response.steps.append("Updated Beacon 5 name to 'Beacon - Table 2'");
queryExecute(" queryExecute("
UPDATE Beacons SET Name = 'Beacon - Table 3' UPDATE Beacons SET BeaconName = 'Beacon - Table 3'
WHERE ID = 6 AND BusinessID = :bizId WHERE BeaconID = 6 AND BeaconBusinessID = :bizId
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Updated Beacon 6 name to 'Beacon - Table 3'"); response.steps.append("Updated Beacon 6 name to 'Beacon - Table 3'");
// Get final status // Get final status
qFinal = queryExecute(" qFinal = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.BeaconID, b.BeaconUUID, b.BeaconName, lt.BusinessID, biz.BusinessName, lt.ServicePointID, sp.ServicePointName
b.Name AS BeaconName, b.UUID, sp.Name AS ServicePointName, FROM lt_Beacon_Businesses_ServicePoints lt
biz.Name AS BusinessName JOIN Beacons b ON b.BeaconID = lt.BeaconID
FROM ServicePoints sp JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
JOIN Beacons b ON b.ID = sp.BeaconID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
JOIN Businesses biz ON biz.ID = sp.BusinessID WHERE lt.BusinessID = :bizId
WHERE sp.BusinessID = :bizId AND sp.BeaconID IS NOT NULL ORDER BY lt.BeaconID
ORDER BY sp.BeaconID
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
beacons = []; beacons = [];
@ -65,9 +66,8 @@ try {
arrayAppend(beacons, { arrayAppend(beacons, {
"BeaconID": qFinal.BeaconID[i], "BeaconID": qFinal.BeaconID[i],
"BeaconName": qFinal.BeaconName[i], "BeaconName": qFinal.BeaconName[i],
"UUID": qFinal.UUID[i], "UUID": qFinal.BeaconUUID[i],
"BusinessName": qFinal.BusinessName[i], "BusinessName": qFinal.BusinessName[i],
"ServicePointID": qFinal.ServicePointID[i],
"ServicePointName": qFinal.ServicePointName[i] "ServicePointName": qFinal.ServicePointName[i]
}); });
} }

View file

@ -13,10 +13,10 @@
* Cleanup Categories - Final step after migration verification * Cleanup Categories - Final step after migration verification
* *
* This script: * This script:
* 1. Verifies all Items have BusinessID set * 1. Verifies all Items have ItemBusinessID set
* 2. Finds orphan items (ParentID=0, no children, not in links) * 2. Finds orphan items (ParentID=0, no children, not in links)
* 3. Drops CategoryID column * 3. Drops ItemCategoryID column
* 4. Drops IsModifierTemplate column (derived from lt_ItemID_TemplateItemID now) * 4. Drops ItemIsModifierTemplate column (derived from ItemTemplateLinks now)
* 5. Drops Categories table * 5. Drops Categories table
* *
* Query param: ?confirm=YES to actually execute (otherwise shows verification only) * Query param: ?confirm=YES to actually execute (otherwise shows verification only)
@ -30,7 +30,7 @@ try {
// Verification Step 1: Check for items without BusinessID // Verification Step 1: Check for items without BusinessID
qNoBusinessID = queryExecute(" qNoBusinessID = queryExecute("
SELECT COUNT(*) as cnt FROM Items SELECT COUNT(*) as cnt FROM Items
WHERE BusinessID IS NULL OR BusinessID = 0 WHERE ItemBusinessID IS NULL OR ItemBusinessID = 0
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response.verification["itemsWithoutBusinessID"] = qNoBusinessID.cnt; response.verification["itemsWithoutBusinessID"] = qNoBusinessID.cnt;
@ -46,38 +46,38 @@ try {
qCategoryItems = queryExecute(" qCategoryItems = queryExecute("
SELECT COUNT(DISTINCT p.ItemID) as cnt SELECT COUNT(DISTINCT p.ItemID) as cnt
FROM Items p FROM Items p
INNER JOIN Items c ON c.ParentItemID = p.ItemID INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.ParentItemID = 0 WHERE p.ItemParentItemID = 0
AND p.BusinessID > 0 AND p.ItemBusinessID > 0
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = p.ItemID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = p.ItemID
) )
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response.verification["categoryItemsCreated"] = qCategoryItems.cnt; response.verification["categoryItemsCreated"] = qCategoryItems.cnt;
// Verification Step 4: Check templates exist (in lt_ItemID_TemplateItemID) // Verification Step 4: Check templates exist (in ItemTemplateLinks)
qTemplates = queryExecute(" qTemplates = queryExecute("
SELECT COUNT(DISTINCT tl.TemplateItemID) as cnt SELECT COUNT(DISTINCT tl.TemplateItemID) as cnt
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
INNER JOIN Items t ON t.ItemID = tl.TemplateItemID INNER JOIN Items t ON t.ItemID = tl.TemplateItemID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response.verification["templatesInLinks"] = qTemplates.cnt; response.verification["templatesInLinks"] = qTemplates.cnt;
// Verification Step 5: Find orphans at ParentID=0 // Verification Step 5: Find orphans at ParentID=0
// Orphan = ParentID=0, no children pointing to it, not in lt_ItemID_TemplateItemID // Orphan = ParentID=0, no children pointing to it, not in ItemTemplateLinks
qOrphans = queryExecute(" qOrphans = queryExecute("
SELECT i.ID, i.Name, i.BusinessID SELECT i.ItemID, i.ItemName, i.ItemBusinessID
FROM Items i FROM Items i
WHERE i.ParentItemID = 0 WHERE i.ItemParentItemID = 0
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM Items child WHERE child.ParentItemID = i.ID SELECT 1 FROM Items child WHERE child.ItemParentItemID = i.ItemID
) )
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID
) )
ORDER BY i.BusinessID, i.Name ORDER BY i.ItemBusinessID, i.ItemName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response.verification["orphanCount"] = qOrphans.recordCount; response.verification["orphanCount"] = qOrphans.recordCount;
@ -85,8 +85,8 @@ try {
for (orphan in qOrphans) { for (orphan in qOrphans) {
arrayAppend(response.orphans, { arrayAppend(response.orphans, {
"ItemID": orphan.ItemID, "ItemID": orphan.ItemID,
"Name": orphan.Name, "ItemName": orphan.ItemName,
"BusinessID": orphan.BusinessID "BusinessID": orphan.ItemBusinessID
}); });
} }
@ -96,7 +96,7 @@ try {
if (!safeToCleanup) { if (!safeToCleanup) {
arrayAppend(response.steps, "VERIFICATION FAILED - Cannot cleanup yet"); arrayAppend(response.steps, "VERIFICATION FAILED - Cannot cleanup yet");
arrayAppend(response.steps, "- " & qNoBusinessID.cnt & " items still missing BusinessID"); arrayAppend(response.steps, "- " & qNoBusinessID.cnt & " items still missing ItemBusinessID");
response["OK"] = false; response["OK"] = false;
writeOutput(serializeJSON(response)); writeOutput(serializeJSON(response));
abort; abort;
@ -119,31 +119,31 @@ try {
// Execute cleanup // Execute cleanup
arrayAppend(response.steps, "Executing cleanup..."); arrayAppend(response.steps, "Executing cleanup...");
// Step 1: Drop CategoryID column // Step 1: Drop ItemCategoryID column
try { try {
queryExecute(" queryExecute("
ALTER TABLE Items DROP COLUMN CategoryID ALTER TABLE Items DROP COLUMN ItemCategoryID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Dropped CategoryID column from Items"); arrayAppend(response.steps, "Dropped ItemCategoryID column from Items");
} catch (any e) { } catch (any e) {
if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) { if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) {
arrayAppend(response.steps, "CategoryID column already dropped"); arrayAppend(response.steps, "ItemCategoryID column already dropped");
} else { } else {
arrayAppend(response.steps, "Warning dropping CategoryID: " & e.message); arrayAppend(response.steps, "Warning dropping ItemCategoryID: " & e.message);
} }
} }
// Step 2: Drop IsModifierTemplate column (now derived from lt_ItemID_TemplateItemID) // Step 2: Drop ItemIsModifierTemplate column (now derived from ItemTemplateLinks)
try { try {
queryExecute(" queryExecute("
ALTER TABLE Items DROP COLUMN IsModifierTemplate ALTER TABLE Items DROP COLUMN ItemIsModifierTemplate
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Dropped IsModifierTemplate column from Items"); arrayAppend(response.steps, "Dropped ItemIsModifierTemplate column from Items");
} catch (any e) { } catch (any e) {
if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) { if (findNoCase("check that column", e.message) || findNoCase("Unknown column", e.message)) {
arrayAppend(response.steps, "IsModifierTemplate column already dropped"); arrayAppend(response.steps, "ItemIsModifierTemplate column already dropped");
} else { } else {
arrayAppend(response.steps, "Warning dropping IsModifierTemplate: " & e.message); arrayAppend(response.steps, "Warning dropping ItemIsModifierTemplate: " & e.message);
} }
} }

View file

@ -34,8 +34,8 @@ if (businessId <= 0) {
try { try {
// Find duplicate UserIDs for this business (keep the one with highest status or oldest) // Find duplicate UserIDs for this business (keep the one with highest status or oldest)
qDupes = queryExecute(" qDupes = queryExecute("
SELECT ID, COUNT(*) as cnt, MIN(ID) as keepId SELECT UserID, COUNT(*) as cnt, MIN(EmployeeID) as keepId
FROM Employees FROM lt_Users_Businesses_Employees
WHERE BusinessID = ? WHERE BusinessID = ?
GROUP BY UserID GROUP BY UserID
HAVING COUNT(*) > 1 HAVING COUNT(*) > 1
@ -45,8 +45,8 @@ try {
for (row in qDupes) { for (row in qDupes) {
// Delete all but the one we want to keep (the one with lowest EmployeeID) // Delete all but the one we want to keep (the one with lowest EmployeeID)
qDel = queryExecute(" qDel = queryExecute("
DELETE FROM Employees DELETE FROM lt_Users_Businesses_Employees
WHERE BusinessID = ? AND UserID = ? AND ID != ? WHERE BusinessID = ? AND UserID = ? AND EmployeeID != ?
", [ ", [
{ value: businessId, cfsqltype: "cf_sql_integer" }, { value: businessId, cfsqltype: "cf_sql_integer" },
{ value: row.UserID, cfsqltype: "cf_sql_integer" }, { value: row.UserID, cfsqltype: "cf_sql_integer" },
@ -57,19 +57,19 @@ try {
// Get remaining employees // Get remaining employees
qRemaining = queryExecute(" qRemaining = queryExecute("
SELECT e.ID, e.UserID, u.FirstName, u.LastName SELECT e.EmployeeID, e.UserID, u.UserFirstName, u.UserLastName
FROM Employees e FROM lt_Users_Businesses_Employees e
JOIN Users u ON e.UserID = u.ID JOIN Users u ON e.UserID = u.UserID
WHERE e.BusinessID = ? WHERE e.BusinessID = ?
ORDER BY e.ID ORDER BY e.EmployeeID
", [{ value: businessId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" }); ", [{ value: businessId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" });
remaining = []; remaining = [];
for (r in qRemaining) { for (r in qRemaining) {
arrayAppend(remaining, { arrayAppend(remaining, {
"EmployeeID": r.ID, "EmployeeID": r.EmployeeID,
"UserID": r.UserID, "UserID": r.UserID,
"Name": trim(r.FirstName & " " & r.LastName) "Name": trim(r.UserFirstName & " " & r.UserLastName)
}); });
} }

View file

@ -9,120 +9,106 @@ try {
// Keep only Lazy Daisy (BusinessID 37) // Keep only Lazy Daisy (BusinessID 37)
keepBusinessID = 37; keepBusinessID = 37;
// Unassign all beacons from service points of other businesses // First, reassign all beacons to Lazy Daisy
queryExecute(" queryExecute("
UPDATE ServicePoints UPDATE lt_Beacon_Businesses_ServicePoints
SET BeaconID = NULL, AssignedByUserID = NULL SET BusinessID = :keepID
WHERE BusinessID != :keepID AND BeaconID IS NOT NULL
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Unassigned beacons from other businesses' service points"); response.steps.append("Reassigned all beacons to Lazy Daisy");
// Get list of businesses to delete // Get list of businesses to delete
qBiz = queryExecute(" qBiz = queryExecute("
SELECT ID, Name FROM Businesses WHERE ID != :keepID SELECT BusinessID, BusinessName FROM Businesses WHERE BusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
deletedBusinesses = []; deletedBusinesses = [];
for (i = 1; i <= qBiz.recordCount; i++) { for (i = 1; i <= qBiz.recordCount; i++) {
arrayAppend(deletedBusinesses, qBiz.Name[i]); arrayAppend(deletedBusinesses, qBiz.BusinessName[i]);
} }
response.steps.append("Found " & qBiz.recordCount & " businesses to delete"); response.steps.append("Found " & qBiz.recordCount & " businesses to delete");
// Delete related data first (foreign key constraints) // Delete related data first (foreign key constraints)
// Delete lt_ItemID_TemplateItemID for items from other businesses // Delete ItemTemplateLinks for items from other businesses
queryExecute(" queryExecute("
DELETE itl FROM lt_ItemID_TemplateItemID itl DELETE itl FROM ItemTemplateLinks itl
JOIN Items i ON i.ID = itl.ItemID JOIN Items i ON i.ItemID = itl.ItemID
WHERE i.BusinessID != :keepID WHERE i.ItemBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted lt_ItemID_TemplateItemID for other businesses"); response.steps.append("Deleted ItemTemplateLinks for other businesses");
// Delete Items for other businesses // Delete Items for other businesses
qItems = queryExecute(" qItems = queryExecute("
SELECT COUNT(*) as cnt FROM Items WHERE BusinessID != :keepID SELECT COUNT(*) as cnt FROM Items WHERE ItemBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
queryExecute(" queryExecute("
DELETE FROM Items WHERE BusinessID != :keepID DELETE FROM Items WHERE ItemBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted " & qItems.cnt & " items from other businesses"); response.steps.append("Deleted " & qItems.cnt & " items from other businesses");
// Delete Categories for other businesses // Delete Categories for other businesses
queryExecute(" queryExecute("
DELETE FROM Categories WHERE BusinessID != :keepID DELETE FROM Categories WHERE CategoryBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted categories from other businesses"); response.steps.append("Deleted categories from other businesses");
// Delete Hours for other businesses // Delete Hours for other businesses
queryExecute(" queryExecute("
DELETE FROM Hours WHERE BusinessID != :keepID DELETE FROM Hours WHERE HoursBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted hours from other businesses"); response.steps.append("Deleted hours from other businesses");
// Delete Employees for other businesses // Delete Employees for other businesses (skip if table doesn't exist)
try { try {
queryExecute(" queryExecute("
DELETE FROM Employees WHERE BusinessID != :keepID DELETE FROM Employees WHERE EmployeeBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted employees from other businesses"); response.steps.append("Deleted employees from other businesses");
} catch (any e) { } catch (any e) {
response.steps.append("Skipped employees (table may not exist)"); response.steps.append("Skipped employees (table may not exist)");
} }
// Delete ServicePoints for other businesses // Delete ServicePoints for other businesses (skip if table doesn't exist)
try { try {
queryExecute(" queryExecute("
DELETE FROM ServicePoints WHERE BusinessID != :keepID DELETE FROM ServicePoints WHERE ServicePointBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted service points from other businesses"); response.steps.append("Deleted service points from other businesses");
} catch (any e) { } catch (any e) {
response.steps.append("Skipped service points (table may not exist)"); response.steps.append("Skipped service points (table may not exist)");
} }
// Delete Stations for other businesses // Delete Stations for other businesses (skip if table doesn't exist)
try { try {
queryExecute(" queryExecute("
DELETE FROM Stations WHERE BusinessID != :keepID DELETE FROM Stations WHERE StationBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted stations from other businesses"); response.steps.append("Deleted stations from other businesses");
} catch (any e) { } catch (any e) {
response.steps.append("Skipped stations (table may not exist)"); response.steps.append("Skipped stations (table may not exist)");
} }
// Delete beacon-business mappings for other businesses
try {
queryExecute("
DELETE FROM lt_BeaconsID_BusinessesID WHERE BusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted beacon mappings for other businesses");
} catch (any e) {
response.steps.append("Skipped beacon mappings (table may not exist)");
}
// Finally delete the businesses themselves // Finally delete the businesses themselves
queryExecute(" queryExecute("
DELETE FROM Businesses WHERE ID != :keepID DELETE FROM Businesses WHERE BusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" }); ", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted " & arrayLen(deletedBusinesses) & " businesses"); response.steps.append("Deleted " & arrayLen(deletedBusinesses) & " businesses");
// Get beacon status // Get beacon status
qBeacons = queryExecute(" qBeacons = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.BeaconID, b.BeaconUUID, lt.BusinessID, biz.BusinessName, lt.ServicePointID
b.UUID, biz.Name AS BusinessName, sp.Name AS ServicePointName FROM lt_Beacon_Businesses_ServicePoints lt
FROM ServicePoints sp JOIN Beacons b ON b.BeaconID = lt.BeaconID
JOIN Beacons b ON b.ID = sp.BeaconID JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
JOIN Businesses biz ON biz.ID = sp.BusinessID
WHERE sp.BeaconID IS NOT NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
beacons = []; beacons = [];
for (i = 1; i <= qBeacons.recordCount; i++) { for (i = 1; i <= qBeacons.recordCount; i++) {
arrayAppend(beacons, { arrayAppend(beacons, {
"BeaconID": qBeacons.BeaconID[i], "BeaconID": qBeacons.BeaconID[i],
"UUID": qBeacons.UUID[i], "UUID": qBeacons.BeaconUUID[i],
"BusinessID": qBeacons.BusinessID[i], "BusinessID": qBeacons.BusinessID[i],
"BusinessName": qBeacons.BusinessName[i], "BusinessName": qBeacons.BusinessName[i],
"ServicePointID": qBeacons.ServicePointID[i], "ServicePointID": qBeacons.ServicePointID[i]
"ServicePointName": qBeacons.ServicePointName[i]
}); });
} }

View file

@ -7,9 +7,9 @@ param name="url.action" default="check"; // "check" or "deactivate"
// Check the item first // Check the item first
qItem = queryExecute(" qItem = queryExecute("
SELECT ID, Name, ParentItemID, IsActive, IsCollapsible SELECT ItemID, ItemName, ItemParentItemID, ItemIsActive, ItemIsCollapsible
FROM Items FROM Items
WHERE ID = :itemId WHERE ItemID = :itemId
", { itemId: url.itemId }); ", { itemId: url.itemId });
if (qItem.recordCount == 0) { if (qItem.recordCount == 0) {
@ -19,25 +19,25 @@ if (qItem.recordCount == 0) {
// Get all children (direct only for display) // Get all children (direct only for display)
qChildren = queryExecute(" qChildren = queryExecute("
SELECT ID, Name SELECT ItemID, ItemName
FROM Items FROM Items
WHERE ParentItemID = :itemId WHERE ItemParentItemID = :itemId
", { itemId: url.itemId }); ", { itemId: url.itemId });
childList = []; childList = [];
for (row in qChildren) { for (row in qChildren) {
arrayAppend(childList, { "ItemID": row.ID, "Name": row.Name }); arrayAppend(childList, { "ItemID": row.ItemID, "ItemName": row.ItemName });
} }
result = { result = {
"OK": true, "OK": true,
"ACTION": url.action, "ACTION": url.action,
"ITEM": { "ITEM": {
"ItemID": qItem.ID, "ItemID": qItem.ItemID,
"Name": qItem.Name, "ItemName": qItem.ItemName,
"ParentItemID": qItem.ParentItemID, "ItemParentItemID": qItem.ItemParentItemID,
"IsActive": qItem.IsActive, "ItemIsActive": qItem.ItemIsActive,
"IsCollapsible": qItem.IsCollapsible "ItemIsCollapsible": qItem.ItemIsCollapsible
}, },
"HAS_CHILDREN": qChildren.recordCount > 0, "HAS_CHILDREN": qChildren.recordCount > 0,
"CHILD_COUNT": qChildren.recordCount, "CHILD_COUNT": qChildren.recordCount,
@ -48,14 +48,14 @@ if (url.action == "deactivate") {
// Deactivate children first // Deactivate children first
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET IsActive = 0 SET ItemIsActive = 0
WHERE ParentItemID = :itemId WHERE ItemParentItemID = :itemId
", { itemId: url.itemId }); ", { itemId: url.itemId });
// Then deactivate the parent // Then deactivate the parent
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET IsActive = 0 SET ItemIsActive = 0
WHERE ItemID = :itemId WHERE ItemID = :itemId
", { itemId: url.itemId }); ", { itemId: url.itemId });

View file

@ -5,13 +5,13 @@
// Delete cart orders (status 0) to reset for testing // Delete cart orders (status 0) to reset for testing
result = queryExecute(" result = queryExecute("
DELETE FROM OrderLineItems DELETE FROM OrderLineItems
WHERE OrderID IN ( WHERE OrderLineItemOrderID IN (
SELECT ID FROM Orders WHERE StatusID = 0 SELECT OrderID FROM Orders WHERE OrderStatusID = 0
) )
", {}, { datasource = "payfrit" }); ", {}, { datasource = "payfrit" });
result2 = queryExecute(" result2 = queryExecute("
DELETE FROM Orders WHERE StatusID = 0 DELETE FROM Orders WHERE OrderStatusID = 0
", {}, { datasource = "payfrit" }); ", {}, { datasource = "payfrit" });
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -10,27 +10,27 @@ try {
businessIDs = [38, 39, 40, 41, 42]; businessIDs = [38, 39, 40, 41, 42];
for (bizID in businessIDs) { for (bizID in businessIDs) {
// Delete lt_ItemID_TemplateItemID for items belonging to this business // Delete ItemTemplateLinks for items belonging to this business
queryExecute(" queryExecute("
DELETE itl FROM lt_ItemID_TemplateItemID itl DELETE itl FROM ItemTemplateLinks itl
INNER JOIN Items i ON i.ID = itl.ItemID INNER JOIN Items i ON i.ItemID = itl.ItemID
WHERE i.BusinessID = :bizID WHERE i.ItemBusinessID = :bizID
", { bizID: bizID }, { datasource: "payfrit" }); ", { bizID: bizID }, { datasource: "payfrit" });
// Delete Items // Delete Items
queryExecute("DELETE FROM Items WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" }); queryExecute("DELETE FROM Items WHERE ItemBusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
// Delete Categories // Delete Categories
queryExecute("DELETE FROM Categories WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" }); queryExecute("DELETE FROM Categories WHERE CategoryBusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
// Delete Hours // Delete Hours
queryExecute("DELETE FROM Hours WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" }); queryExecute("DELETE FROM Hours WHERE HoursBusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
// Delete Addresses linked to this business // Delete Addresses linked to this business
queryExecute("DELETE FROM Addresses WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" }); queryExecute("DELETE FROM Addresses WHERE AddressBusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
// Delete the Business itself // Delete the Business itself
queryExecute("DELETE FROM Businesses WHERE ID = :bizID", { bizID: bizID }, { datasource: "payfrit" }); queryExecute("DELETE FROM Businesses WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
response.steps.append("Deleted business " & bizID & " and all related data"); response.steps.append("Deleted business " & bizID & " and all related data");
} }

View file

@ -5,9 +5,9 @@
try { try {
result = queryExecute(" result = queryExecute("
UPDATE Tasks UPDATE Tasks
SET CompletedOn = NOW() SET TaskCompletedOn = NOW()
WHERE TaskTypeID = 2 WHERE TaskTypeID = 2
AND CompletedOn IS NULL AND TaskCompletedOn IS NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
affected = result.recordCount ?: 0; affected = result.recordCount ?: 0;

View file

@ -15,24 +15,24 @@ try {
// First, check if Big Dean's has a Beverages/Drinks category // First, check if Big Dean's has a Beverages/Drinks category
qExistingCat = queryExecute(" qExistingCat = queryExecute("
SELECT ID, Name FROM Categories SELECT CategoryID, CategoryName FROM Categories
WHERE BusinessID = :bizId AND (Name LIKE '%Drink%' OR Name LIKE '%Beverage%') WHERE CategoryBusinessID = :bizId AND (CategoryName LIKE '%Drink%' OR CategoryName LIKE '%Beverage%')
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" }); ", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
if (qExistingCat.recordCount > 0) { if (qExistingCat.recordCount > 0) {
drinksCategoryId = qExistingCat.CategoryID; drinksCategoryId = qExistingCat.CategoryID;
response["CategoryNote"] = "Using existing category: " & qExistingCat.Name; response["CategoryNote"] = "Using existing category: " & qExistingCat.CategoryName;
} else { } else {
// Create a new Beverages category for Big Dean's // Create a new Beverages category for Big Dean's
qMaxCat = queryExecute("SELECT COALESCE(MAX(CategoryID), 0) + 1 as nextId FROM Categories", {}, { datasource: "payfrit" }); qMaxCat = queryExecute("SELECT COALESCE(MAX(CategoryID), 0) + 1 as nextId FROM Categories", {}, { datasource: "payfrit" });
drinksCategoryId = qMaxCat.nextId; drinksCategoryId = qMaxCat.nextId;
qMaxSort = queryExecute(" qMaxSort = queryExecute("
SELECT COALESCE(MAX(SortOrder), 0) + 1 as nextSort FROM Categories WHERE BusinessID = :bizId SELECT COALESCE(MAX(CategorySortOrder), 0) + 1 as nextSort FROM Categories WHERE CategoryBusinessID = :bizId
", { bizId: bigDeansBusinessId }, { datasource: "payfrit" }); ", { bizId: bigDeansBusinessId }, { datasource: "payfrit" });
queryExecute(" queryExecute("
INSERT INTO Categories (CategoryID, BusinessID, ParentCategoryID, Name, SortOrder, AddedOn) INSERT INTO Categories (CategoryID, CategoryBusinessID, CategoryParentCategoryID, CategoryName, CategorySortOrder, CategoryAddedOn)
VALUES (:catId, :bizId, 0, 'Beverages', :sortOrder, NOW()) VALUES (:catId, :bizId, 0, 'Beverages', :sortOrder, NOW())
", { ", {
catId: drinksCategoryId, catId: drinksCategoryId,
@ -61,8 +61,8 @@ try {
for (drink in drinks) { for (drink in drinks) {
// Check if item already exists // Check if item already exists
qExists = queryExecute(" qExists = queryExecute("
SELECT ID FROM Items SELECT ItemID FROM Items
WHERE BusinessID = :bizId AND Name = :name AND CategoryID = :catId WHERE ItemBusinessID = :bizId AND ItemName = :name AND ItemCategoryID = :catId
", { bizId: bigDeansBusinessId, name: drink.name, catId: drinksCategoryId }, { datasource: "payfrit" }); ", { bizId: bigDeansBusinessId, name: drink.name, catId: drinksCategoryId }, { datasource: "payfrit" });
if (qExists.recordCount == 0) { if (qExists.recordCount == 0) {
@ -72,10 +72,10 @@ try {
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, RequiresChildSelection, ItemSortOrder, ItemIsCollapsible, ItemRequiresChildSelection,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, :catId, 0, :itemId, :bizId, :catId, 0,
:name, :desc, :price, 1, :name, :desc, :price, 1,
@ -103,10 +103,10 @@ try {
qMaxOpt = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" }); qMaxOpt = queryExecute("SELECT COALESCE(MAX(ItemID), 0) + 1 as nextId FROM Items", {}, { datasource: "payfrit" });
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
ItemID, BusinessID, CategoryID, ParentItemID, ItemID, ItemBusinessID, ItemCategoryID, ItemParentItemID,
Name, Description, Price, IsActive, ItemName, ItemDescription, ItemPrice, ItemIsActive,
SortOrder, IsCollapsible, IsCheckedByDefault, ItemSortOrder, ItemIsCollapsible, ItemIsCheckedByDefault,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:itemId, :bizId, 0, :parentId, :itemId, :bizId, 0, :parentId,
:name, '', 0, 1, :name, '', 0, 1,

View file

@ -20,74 +20,69 @@ try {
uuid = beaconUUIDs[i]; uuid = beaconUUIDs[i];
// Check if beacon exists // Check if beacon exists
qB = queryExecute("SELECT ID FROM Beacons WHERE UUID = :uuid", { uuid: uuid }, { datasource: "payfrit" }); qB = queryExecute("SELECT BeaconID FROM Beacons WHERE BeaconUUID = :uuid", { uuid: uuid }, { datasource: "payfrit" });
if (qB.recordCount == 0) { if (qB.recordCount == 0) {
queryExecute("INSERT INTO Beacons (UUID, BusinessID) VALUES (:uuid, :bizID)", { uuid: uuid, bizID: lazyDaisyID }, { datasource: "payfrit" }); queryExecute("INSERT INTO Beacons (BeaconUUID, BeaconBusinessID) VALUES (:uuid, :bizID)", { uuid: uuid, bizID: lazyDaisyID }, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
beaconID = qNew.id; beaconID = qNew.id;
response.steps.append("Created beacon " & beaconID & " with UUID: " & uuid); response.steps.append("Created beacon " & beaconID & " with UUID: " & uuid);
} else { } else {
beaconID = qB.ID; beaconID = qB.BeaconID;
response.steps.append("Beacon exists: " & beaconID & " with UUID: " & uuid); response.steps.append("Beacon exists: " & beaconID & " with UUID: " & uuid);
} }
} }
// Get service point Table 1 // Get service point Table 1
qSP = queryExecute(" qSP = queryExecute("
SELECT ID FROM ServicePoints SELECT ServicePointID FROM ServicePoints
WHERE BusinessID = :bizID AND Name = 'Table 1' WHERE ServicePointBusinessID = :bizID AND ServicePointName = 'Table 1'
", { bizID: lazyDaisyID }, { datasource: "payfrit" }); ", { bizID: lazyDaisyID }, { datasource: "payfrit" });
if (qSP.recordCount == 0) { if (qSP.recordCount == 0) {
queryExecute(" queryExecute("
INSERT INTO ServicePoints (BusinessID, Name) INSERT INTO ServicePoints (ServicePointBusinessID, ServicePointName, ServicePointTypeID)
VALUES (:bizID, 'Table 1') VALUES (:bizID, 'Table 1', 1)
", { bizID: lazyDaisyID }, { datasource: "payfrit" }); ", { bizID: lazyDaisyID }, { datasource: "payfrit" });
qSP = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); qSP = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
servicePointID = qSP.id; servicePointID = qSP.id;
response.steps.append("Created service point 'Table 1' (ID: " & servicePointID & ")"); response.steps.append("Created service point 'Table 1' (ID: " & servicePointID & ")");
} else { } else {
servicePointID = qSP.ID; servicePointID = qSP.ServicePointID;
response.steps.append("Found service point 'Table 1' (ID: " & servicePointID & ")"); response.steps.append("Found service point 'Table 1' (ID: " & servicePointID & ")");
} }
// Assign all beacons to the Table 1 service point // Get all beacons and map them
qBeacons = queryExecute("SELECT ID, UUID FROM Beacons WHERE BusinessID = :bizID", { bizID: lazyDaisyID }, { datasource: "payfrit" }); qBeacons = queryExecute("SELECT BeaconID, BeaconUUID FROM Beacons", {}, { datasource: "payfrit" });
for (i = 1; i <= qBeacons.recordCount; i++) { for (i = 1; i <= qBeacons.recordCount; i++) {
beaconID = qBeacons.ID[i]; beaconID = qBeacons.BeaconID[i];
// Unassign this beacon from any existing service point // Delete old mapping if exists
queryExecute(" queryExecute("DELETE FROM lt_Beacon_Businesses_ServicePoints WHERE BeaconID = :beaconID", { beaconID: beaconID }, { datasource: "payfrit" });
UPDATE ServicePoints SET BeaconID = NULL, AssignedByUserID = NULL
WHERE BeaconID = :beaconID
", { beaconID: beaconID }, { datasource: "payfrit" });
// Assign beacon to Table 1 service point // Create new mapping
queryExecute(" queryExecute("
UPDATE ServicePoints SET BeaconID = :beaconID, AssignedByUserID = 1 INSERT INTO lt_Beacon_Businesses_ServicePoints (BeaconID, BusinessID, ServicePointID)
WHERE ID = :spID AND BusinessID = :bizID VALUES (:beaconID, :bizID, :spID)
", { beaconID: beaconID, bizID: lazyDaisyID, spID: servicePointID }, { datasource: "payfrit" }); ", { beaconID: beaconID, bizID: lazyDaisyID, spID: servicePointID }, { datasource: "payfrit" });
response.steps.append("Assigned beacon " & beaconID & " to Table 1"); response.steps.append("Mapped beacon " & beaconID & " to Lazy Daisy, Table 1");
} }
// Get final status // Get final status
qFinal = queryExecute(" qFinal = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.BeaconID, b.BeaconUUID, lt.BusinessID, biz.BusinessName, lt.ServicePointID, sp.ServicePointName
b.Name AS BeaconName, b.UUID, sp.Name AS ServicePointName, FROM lt_Beacon_Businesses_ServicePoints lt
biz.Name AS BusinessName JOIN Beacons b ON b.BeaconID = lt.BeaconID
FROM ServicePoints sp JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
JOIN Beacons b ON b.ID = sp.BeaconID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
JOIN Businesses biz ON biz.ID = sp.BusinessID
WHERE sp.BeaconID IS NOT NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
beacons = []; beacons = [];
for (i = 1; i <= qFinal.recordCount; i++) { for (i = 1; i <= qFinal.recordCount; i++) {
arrayAppend(beacons, { arrayAppend(beacons, {
"BeaconID": qFinal.BeaconID[i], "BeaconID": qFinal.BeaconID[i],
"UUID": qFinal.UUID[i], "UUID": qFinal.BeaconUUID[i],
"BusinessID": qFinal.BusinessID[i], "BusinessID": qFinal.BusinessID[i],
"BusinessName": qFinal.BusinessName[i], "BusinessName": qFinal.BusinessName[i],
"ServicePointID": qFinal.ServicePointID[i], "ServicePointID": qFinal.ServicePointID[i],

View file

@ -5,11 +5,11 @@ try {
// Create ChatMessages table // Create ChatMessages table
queryExecute(" queryExecute("
CREATE TABLE IF NOT EXISTS ChatMessages ( CREATE TABLE IF NOT EXISTS ChatMessages (
ID INT AUTO_INCREMENT PRIMARY KEY, MessageID INT AUTO_INCREMENT PRIMARY KEY,
TaskID INT NOT NULL, TaskID INT NOT NULL,
SenderUserID INT NOT NULL, SenderUserID INT NOT NULL,
SenderType ENUM('customer', 'worker') NOT NULL, SenderType ENUM('customer', 'worker') NOT NULL,
MessageBody TEXT NOT NULL, MessageText TEXT NOT NULL,
IsRead TINYINT(1) DEFAULT 0, IsRead TINYINT(1) DEFAULT 0,
CreatedOn DATETIME DEFAULT NOW(), CreatedOn DATETIME DEFAULT NOW(),
@ -21,13 +21,13 @@ try {
// Also add a "Chat" category if it doesn't exist for business 17 // Also add a "Chat" category if it doesn't exist for business 17
existing = queryExecute(" existing = queryExecute("
SELECT ID FROM TaskCategories SELECT TaskCategoryID FROM TaskCategories
WHERE BusinessID = 17 AND Name = 'Chat' WHERE TaskCategoryBusinessID = 17 AND TaskCategoryName = 'Chat'
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
if (existing.recordCount == 0) { if (existing.recordCount == 0) {
queryExecute(" queryExecute("
INSERT INTO TaskCategories (BusinessID, Name, Color) INSERT INTO TaskCategories (TaskCategoryBusinessID, TaskCategoryName, TaskCategoryColor)
VALUES (17, 'Chat', '##2196F3') VALUES (17, 'Chat', '##2196F3')
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} }

View file

@ -32,47 +32,47 @@ try {
queryExecute(" queryExecute("
CREATE TABLE Menus ( CREATE TABLE Menus (
MenuID INT AUTO_INCREMENT PRIMARY KEY, MenuID INT AUTO_INCREMENT PRIMARY KEY,
BusinessID INT NOT NULL, MenuBusinessID INT NOT NULL,
Name VARCHAR(100) NOT NULL, MenuName VARCHAR(100) NOT NULL,
Description VARCHAR(500) NULL, MenuDescription VARCHAR(500) NULL,
DaysActive INT NOT NULL DEFAULT 127, MenuDaysActive INT NOT NULL DEFAULT 127,
StartTime TIME NULL, MenuStartTime TIME NULL,
EndTime TIME NULL, MenuEndTime TIME NULL,
SortOrder INT NOT NULL DEFAULT 0, MenuSortOrder INT NOT NULL DEFAULT 0,
IsActive TINYINT NOT NULL DEFAULT 1, MenuIsActive TINYINT NOT NULL DEFAULT 1,
AddedOn DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, MenuAddedOn DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_menus_business (BusinessID), INDEX idx_menus_business (MenuBusinessID),
INDEX idx_menus_active (BusinessID, IsActive) INDEX idx_menus_active (MenuBusinessID, MenuIsActive)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response["OK"] = true; response["OK"] = true;
response["MESSAGE"] = "Menus table created successfully"; response["MESSAGE"] = "Menus table created successfully";
response["SCHEMA"] = { response["SCHEMA"] = {
"DaysActive": "Bitmask: 1=Sun, 2=Mon, 4=Tue, 8=Wed, 16=Thu, 32=Fri, 64=Sat (127 = all days)" "MenuDaysActive": "Bitmask: 1=Sun, 2=Mon, 4=Tue, 8=Wed, 16=Thu, 32=Fri, 64=Sat (127 = all days)"
}; };
} }
// Check if MenuID column exists in Categories table // Check if CategoryMenuID column exists in Categories table
qCatCol = queryExecute(" qCatCol = queryExecute("
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Categories' AND TABLE_NAME = 'Categories'
AND COLUMN_NAME = 'MenuID' AND COLUMN_NAME = 'CategoryMenuID'
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
if (qCatCol.recordCount == 0) { if (qCatCol.recordCount == 0) {
// Add MenuID column to Categories table // Add CategoryMenuID column to Categories table
queryExecute(" queryExecute("
ALTER TABLE Categories ALTER TABLE Categories
ADD COLUMN MenuID INT NULL DEFAULT NULL AFTER BusinessID, ADD COLUMN CategoryMenuID INT NULL DEFAULT NULL AFTER CategoryBusinessID,
ADD INDEX idx_categories_menu (MenuID) ADD INDEX idx_categories_menu (CategoryMenuID)
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response["CATEGORIES_UPDATED"] = true; response["CATEGORIES_UPDATED"] = true;
response["CATEGORIES_MESSAGE"] = "Added MenuID column to Categories table"; response["CATEGORIES_MESSAGE"] = "Added CategoryMenuID column to Categories table";
} else { } else {
response["CATEGORIES_UPDATED"] = false; response["CATEGORIES_UPDATED"] = false;
response["CATEGORIES_MESSAGE"] = "MenuID column already exists"; response["CATEGORIES_MESSAGE"] = "CategoryMenuID column already exists";
} }
} catch (any e) { } catch (any e) {

View file

@ -12,7 +12,7 @@
* *
* POST body: * POST body:
* { * {
* "Name": "Century Casino", * "BusinessName": "Century Casino",
* "UserID": 1, * "UserID": 1,
* "ChildBusinessIDs": [47, 48] // Optional: link existing businesses as children * "ChildBusinessIDs": [47, 48] // Optional: link existing businesses as children
* } * }
@ -36,13 +36,13 @@ response = { "OK": false };
try { try {
data = readJsonBody(); data = readJsonBody();
Name = structKeyExists(data, "Name") ? trim(data.Name) : ""; BusinessName = structKeyExists(data, "BusinessName") ? trim(data.BusinessName) : "";
UserID = structKeyExists(data, "UserID") ? val(data.UserID) : 0; UserID = structKeyExists(data, "UserID") ? val(data.UserID) : 0;
ChildBusinessIDs = structKeyExists(data, "ChildBusinessIDs") && isArray(data.ChildBusinessIDs) ? data.ChildBusinessIDs : []; ChildBusinessIDs = structKeyExists(data, "ChildBusinessIDs") && isArray(data.ChildBusinessIDs) ? data.ChildBusinessIDs : [];
if (!len(Name)) { if (!len(BusinessName)) {
response["ERROR"] = "missing_name"; response["ERROR"] = "missing_name";
response["MESSAGE"] = "Name is required"; response["MESSAGE"] = "BusinessName is required";
writeOutput(serializeJSON(response)); writeOutput(serializeJSON(response));
abort; abort;
} }
@ -56,7 +56,7 @@ try {
// Create minimal address record (just a placeholder) // Create minimal address record (just a placeholder)
queryExecute(" queryExecute("
INSERT INTO Addresses (Line1, UserID, AddressTypeID, AddedOn) INSERT INTO Addresses (AddressLine1, AddressUserID, AddressTypeID, AddressAddedOn)
VALUES ('Parent Business - No Physical Location', :userID, 2, NOW()) VALUES ('Parent Business - No Physical Location', :userID, 2, NOW())
", { ", {
userID: UserID userID: UserID
@ -67,10 +67,10 @@ try {
// Create parent business (no menu, no hours, just a shell) // Create parent business (no menu, no hours, just a shell)
queryExecute(" queryExecute("
INSERT INTO Businesses (Name, UserID, AddressID, ParentBusinessID, BusinessDeliveryZipCodes, AddedOn) INSERT INTO Businesses (BusinessName, BusinessUserID, BusinessAddressID, BusinessParentBusinessID, BusinessDeliveryZipCodes, BusinessAddedOn)
VALUES (:name, :userId, :addressId, NULL, '', NOW()) VALUES (:name, :userId, :addressId, NULL, '', NOW())
", { ", {
name: Name, name: BusinessName,
userId: UserID, userId: UserID,
addressId: addressId addressId: addressId
}, { datasource = "payfrit" }); }, { datasource = "payfrit" });
@ -80,7 +80,7 @@ try {
// Link address back to business // Link address back to business
queryExecute(" queryExecute("
UPDATE Addresses SET BusinessID = :bizId WHERE ID = :addrId UPDATE Addresses SET AddressBusinessID = :bizId WHERE AddressID = :addrId
", { ", {
bizId: newBusinessID, bizId: newBusinessID,
addrId: addressId addrId: addressId
@ -92,7 +92,7 @@ try {
childID = val(childID); childID = val(childID);
if (childID > 0) { if (childID > 0) {
queryExecute(" queryExecute("
UPDATE Businesses SET ParentBusinessID = :parentId WHERE ID = :childId UPDATE Businesses SET BusinessParentBusinessID = :parentId WHERE BusinessID = :childId
", { ", {
parentId: newBusinessID, parentId: newBusinessID,
childId: childID childId: childID
@ -103,7 +103,7 @@ try {
response["OK"] = true; response["OK"] = true;
response["BusinessID"] = newBusinessID; response["BusinessID"] = newBusinessID;
response["Name"] = Name; response["BusinessName"] = BusinessName;
response["MESSAGE"] = "Parent business created"; response["MESSAGE"] = "Parent business created";
if (arrayLen(linkedChildren) > 0) { if (arrayLen(linkedChildren) > 0) {
response["LinkedChildren"] = linkedChildren; response["LinkedChildren"] = linkedChildren;

View file

@ -9,22 +9,22 @@ bizId = 27;
deactivatedIds = [11177, 11180, 11183, 11186, 11190, 11193, 11196, 11199, 11204, 11212, 11220, 11259]; deactivatedIds = [11177, 11180, 11183, 11186, 11190, 11193, 11196, 11199, 11204, 11212, 11220, 11259];
qDeactivated = queryExecute(" qDeactivated = queryExecute("
SELECT i.ID, i.Name, i.ParentItemID, i.IsActive, i.IsCollapsible, SELECT i.ItemID, i.ItemName, i.ItemParentItemID, i.ItemIsActive, i.ItemIsCollapsible,
(SELECT COUNT(*) FROM Items c WHERE c.ParentItemID = i.ID) as ChildCount, (SELECT COUNT(*) FROM Items c WHERE c.ItemParentItemID = i.ItemID) as ChildCount,
(SELECT GROUP_CONCAT(c.Name) FROM Items c WHERE c.ParentItemID = i.ID) as Children (SELECT GROUP_CONCAT(c.ItemName) FROM Items c WHERE c.ItemParentItemID = i.ItemID) as Children
FROM Items i FROM Items i
WHERE i.ID IN (:ids) WHERE i.ItemID IN (:ids)
ORDER BY i.ID ORDER BY i.ItemID
", { ids: { value: arrayToList(deactivatedIds), list: true } }, { datasource: "payfrit" }); ", { ids: { value: arrayToList(deactivatedIds), list: true } }, { datasource: "payfrit" });
items = []; items = [];
for (row in qDeactivated) { for (row in qDeactivated) {
arrayAppend(items, { arrayAppend(items, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"ParentID": row.ParentItemID, "ParentID": row.ItemParentItemID,
"IsActive": row.IsActive, "IsActive": row.ItemIsActive,
"IsCollapsible": row.IsCollapsible, "IsCollapsible": row.ItemIsCollapsible,
"ChildCount": row.ChildCount, "ChildCount": row.ChildCount,
"Children": row.Children "Children": row.Children
}); });

View file

@ -9,24 +9,24 @@ bizId = 27;
qLinks = queryExecute(" qLinks = queryExecute("
SELECT SELECT
tl.ItemID as MenuItemID, tl.ItemID as MenuItemID,
mi.Name as MenuName, mi.ItemName as MenuItemName,
mi.ParentItemID, mi.ItemParentItemID,
tl.TemplateItemID, tl.TemplateItemID,
t.Name as TemplateName, t.ItemName as TemplateName,
tl.SortOrder tl.SortOrder
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items mi ON mi.ID = tl.ItemID JOIN Items mi ON mi.ItemID = tl.ItemID
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE mi.BusinessID = :bizId WHERE mi.ItemBusinessID = :bizId
ORDER BY mi.ParentItemID, mi.Name, tl.SortOrder ORDER BY mi.ItemParentItemID, mi.ItemName, tl.SortOrder
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
links = []; links = [];
for (row in qLinks) { for (row in qLinks) {
arrayAppend(links, { arrayAppend(links, {
"MenuItemID": row.MenuItemID, "MenuItemID": row.MenuItemID,
"MenuName": row.MenuName, "MenuItemName": row.MenuItemName,
"ParentItemID": row.ParentItemID, "ParentItemID": row.ItemParentItemID,
"TemplateItemID": row.TemplateItemID, "TemplateItemID": row.TemplateItemID,
"TemplateName": row.TemplateName "TemplateName": row.TemplateName
}); });
@ -34,20 +34,20 @@ for (row in qLinks) {
// Get burgers specifically (parent = 11271) // Get burgers specifically (parent = 11271)
qBurgers = queryExecute(" qBurgers = queryExecute("
SELECT ID, Name FROM Items SELECT ItemID, ItemName FROM Items
WHERE BusinessID = :bizId AND ParentItemID = 11271 AND IsActive = 1 WHERE ItemBusinessID = :bizId AND ItemParentItemID = 11271 AND ItemIsActive = 1
ORDER BY SortOrder ORDER BY ItemSortOrder
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
burgers = []; burgers = [];
for (row in qBurgers) { for (row in qBurgers) {
// Get templates for this burger // Get templates for this burger
qBurgerTemplates = queryExecute(" qBurgerTemplates = queryExecute("
SELECT tl.TemplateItemID, t.Name as TemplateName SELECT tl.TemplateItemID, t.ItemName as TemplateName
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE tl.ItemID = :itemId WHERE tl.ItemID = :itemId
", { itemId: row.ID }, { datasource: "payfrit" }); ", { itemId: row.ItemID }, { datasource: "payfrit" });
templates = []; templates = [];
for (t in qBurgerTemplates) { for (t in qBurgerTemplates) {
@ -55,8 +55,8 @@ for (row in qBurgers) {
} }
arrayAppend(burgers, { arrayAppend(burgers, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"Templates": templates "Templates": templates
}); });
} }

View file

@ -9,38 +9,38 @@ businessID = 27;
qCategories = queryExecute(" qCategories = queryExecute("
SELECT DISTINCT SELECT DISTINCT
p.ItemID as CategoryID, p.ItemID as CategoryID,
p.Name as Name, p.ItemName as CategoryName,
p.SortOrder p.ItemSortOrder
FROM Items p FROM Items p
INNER JOIN Items c ON c.ParentItemID = p.ItemID INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.BusinessID = :businessID WHERE p.ItemBusinessID = :businessID
AND p.ParentItemID = 0 AND p.ItemParentItemID = 0
AND p.IsActive = 1 AND p.ItemIsActive = 1
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = p.ItemID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = p.ItemID
) )
ORDER BY p.SortOrder, p.Name ORDER BY p.ItemSortOrder, p.ItemName
", { businessID: businessID }); ", { businessID: businessID });
cats = []; cats = [];
for (c in qCategories) { for (c in qCategories) {
arrayAppend(cats, { arrayAppend(cats, {
"CategoryID": c.ID, "CategoryID": c.CategoryID,
"Name": c.Name "CategoryName": c.CategoryName
}); });
} }
// Also check raw counts // Also check raw counts
rawCount = queryExecute(" rawCount = queryExecute("
SELECT COUNT(*) as cnt FROM Items SELECT COUNT(*) as cnt FROM Items
WHERE BusinessID = :bizId AND ParentItemID = 0 AND IsActive = 1 WHERE ItemBusinessID = :bizId AND ItemParentItemID = 0 AND ItemIsActive = 1
", { bizId: businessID }); ", { bizId: businessID });
childrenCount = queryExecute(" childrenCount = queryExecute("
SELECT COUNT(DISTINCT c.ParentItemID) as cnt SELECT COUNT(DISTINCT c.ItemParentItemID) as cnt
FROM Items c FROM Items c
INNER JOIN Items p ON p.ItemID = c.ParentItemID INNER JOIN Items p ON p.ItemID = c.ItemParentItemID
WHERE p.BusinessID = :bizId AND p.ParentItemID = 0 WHERE p.ItemBusinessID = :bizId AND p.ItemParentItemID = 0
", { bizId: businessID }); ", { bizId: businessID });
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -9,24 +9,24 @@ bizId = 27;
qLinks = queryExecute(" qLinks = queryExecute("
SELECT SELECT
tl.ItemID as MenuItemID, tl.ItemID as MenuItemID,
mi.Name as MenuName, mi.ItemName as MenuItemName,
mi.ParentItemID as MenuItemParentID, mi.ItemParentItemID as MenuItemParentID,
tl.TemplateItemID, tl.TemplateItemID,
t.Name as TemplateName, t.ItemName as TemplateName,
t.IsActive as TemplateActive, t.ItemIsActive as TemplateActive,
tl.SortOrder tl.SortOrder
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items mi ON mi.ID = tl.ItemID JOIN Items mi ON mi.ItemID = tl.ItemID
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE mi.BusinessID = :bizId WHERE mi.ItemBusinessID = :bizId
ORDER BY mi.Name, tl.SortOrder ORDER BY mi.ItemName, tl.SortOrder
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
links = []; links = [];
for (row in qLinks) { for (row in qLinks) {
arrayAppend(links, { arrayAppend(links, {
"MenuItemID": row.MenuItemID, "MenuItemID": row.MenuItemID,
"MenuName": row.MenuName, "MenuItemName": row.MenuItemName,
"MenuItemParentID": row.MenuItemParentID, "MenuItemParentID": row.MenuItemParentID,
"TemplateItemID": row.TemplateItemID, "TemplateItemID": row.TemplateItemID,
"TemplateName": row.TemplateName, "TemplateName": row.TemplateName,
@ -37,20 +37,20 @@ for (row in qLinks) {
// Get all templates that exist for this business // Get all templates that exist for this business
qTemplates = queryExecute(" qTemplates = queryExecute("
SELECT ID, Name, IsActive, ParentItemID SELECT ItemID, ItemName, ItemIsActive, ItemParentItemID
FROM Items FROM Items
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND IsCollapsible = 1 AND ItemIsCollapsible = 1
ORDER BY Name ORDER BY ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
templates = []; templates = [];
for (row in qTemplates) { for (row in qTemplates) {
arrayAppend(templates, { arrayAppend(templates, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"IsActive": row.IsActive, "IsActive": row.ItemIsActive,
"ParentID": row.ParentItemID "ParentID": row.ItemParentItemID
}); });
} }

View file

@ -5,67 +5,67 @@
<cfscript> <cfscript>
bizId = 27; bizId = 27;
// Check the template items themselves (IDs from lt_ItemID_TemplateItemID) // Check the template items themselves (IDs from ItemTemplateLinks)
templateIds = "11267, 11251, 11246, 11224, 11233, 11230, 11240, 11243, 11237, 11227"; templateIds = "11267, 11251, 11246, 11224, 11233, 11230, 11240, 11243, 11237, 11227";
qTemplates = queryExecute(" qTemplates = queryExecute("
SELECT ID, Name, IsCollapsible, IsActive, ParentItemID, BusinessID SELECT ItemID, ItemName, ItemIsCollapsible, ItemIsActive, ItemParentItemID, ItemBusinessID
FROM Items FROM Items
WHERE ID IN (#templateIds#) WHERE ItemID IN (#templateIds#)
ORDER BY Name ORDER BY ItemName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
templates = []; templates = [];
for (row in qTemplates) { for (row in qTemplates) {
arrayAppend(templates, { arrayAppend(templates, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"IsCollapsible": row.IsCollapsible, "IsCollapsible": row.ItemIsCollapsible,
"IsActive": row.IsActive, "IsActive": row.ItemIsActive,
"ParentID": row.ParentItemID, "ParentID": row.ItemParentItemID,
"BusinessID": row.BusinessID "BusinessID": row.ItemBusinessID
}); });
} }
// Also check what other templates might exist for burgers // Also check what other templates might exist for burgers
// Look for items that are in lt_ItemID_TemplateItemID but NOT linked to burgers // Look for items that are in ItemTemplateLinks but NOT linked to burgers
qMissingTemplates = queryExecute(" qMissingTemplates = queryExecute("
SELECT DISTINCT t.ItemID, t.Name, t.IsCollapsible, t.IsActive SELECT DISTINCT t.ItemID, t.ItemName, t.ItemIsCollapsible, t.ItemIsActive
FROM Items t FROM Items t
WHERE t.BusinessID = :bizId WHERE t.ItemBusinessID = :bizId
AND t.ParentItemID = 0 AND t.ItemParentItemID = 0
AND t.ItemID NOT IN ( AND t.ItemID NOT IN (
SELECT i.ID FROM Items i WHERE i.BusinessID = :bizId AND i.CategoryID > 0 SELECT i.ItemID FROM Items i WHERE i.ItemBusinessID = :bizId AND i.ItemCategoryID > 0
) )
AND EXISTS (SELECT 1 FROM Items child WHERE child.ParentItemID = t.ItemID) AND EXISTS (SELECT 1 FROM Items child WHERE child.ItemParentItemID = t.ItemID)
ORDER BY t.Name ORDER BY t.ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
potentialTemplates = []; potentialTemplates = [];
for (row in qMissingTemplates) { for (row in qMissingTemplates) {
arrayAppend(potentialTemplates, { arrayAppend(potentialTemplates, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"IsCollapsible": row.IsCollapsible, "IsCollapsible": row.ItemIsCollapsible,
"IsActive": row.IsActive "IsActive": row.ItemIsActive
}); });
} }
// What templates SHOULD burgers have? Let's see all templates used by ANY item // What templates SHOULD burgers have? Let's see all templates used by ANY item
qAllTemplateUsage = queryExecute(" qAllTemplateUsage = queryExecute("
SELECT t.ItemID, t.Name, COUNT(tl.ItemID) as UsageCount SELECT t.ItemID, t.ItemName, COUNT(tl.ItemID) as UsageCount
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
JOIN Items mi ON mi.ID = tl.ItemID AND mi.BusinessID = :bizId JOIN Items mi ON mi.ItemID = tl.ItemID AND mi.ItemBusinessID = :bizId
GROUP BY t.ItemID, t.Name GROUP BY t.ItemID, t.ItemName
ORDER BY t.Name ORDER BY t.ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
allTemplates = []; allTemplates = [];
for (row in qAllTemplateUsage) { for (row in qAllTemplateUsage) {
arrayAppend(allTemplates, { arrayAppend(allTemplates, {
"TemplateID": row.ID, "TemplateID": row.ItemID,
"TemplateName": row.Name, "TemplateName": row.ItemName,
"UsageCount": row.UsageCount "UsageCount": row.UsageCount
}); });
} }

View file

@ -7,41 +7,41 @@ bizId = 27;
// Get the template items themselves // Get the template items themselves
qTemplates = queryExecute(" qTemplates = queryExecute("
SELECT ID, Name, IsCollapsible, IsActive, ParentItemID, BusinessID SELECT ItemID, ItemName, ItemIsCollapsible, ItemIsActive, ItemParentItemID, ItemBusinessID
FROM Items FROM Items
WHERE ID IN (11267, 11251, 11246, 11224, 11233, 11230, 11240, 11243, 11237, 11227) WHERE ItemID IN (11267, 11251, 11246, 11224, 11233, 11230, 11240, 11243, 11237, 11227)
ORDER BY Name ORDER BY ItemName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
templates = []; templates = [];
for (row in qTemplates) { for (row in qTemplates) {
arrayAppend(templates, { arrayAppend(templates, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"IsCollapsible": row.IsCollapsible, "IsCollapsible": row.ItemIsCollapsible,
"IsActive": row.IsActive, "IsActive": row.ItemIsActive,
"ParentID": row.ParentItemID, "ParentID": row.ItemParentItemID,
"BusinessID": row.BusinessID "BusinessID": row.ItemBusinessID
}); });
} }
// What templates are used by burgers vs all items? // What templates are used by burgers vs all items?
qBurgerLinks = queryExecute(" qBurgerLinks = queryExecute("
SELECT mi.ID, mi.Name, GROUP_CONCAT(t.Name ORDER BY tl.SortOrder) as Templates SELECT mi.ItemID, mi.ItemName, GROUP_CONCAT(t.ItemName ORDER BY tl.SortOrder) as Templates
FROM Items mi FROM Items mi
JOIN lt_ItemID_TemplateItemID tl ON tl.ItemID = mi.ID JOIN ItemTemplateLinks tl ON tl.ItemID = mi.ItemID
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE mi.BusinessID = :bizId WHERE mi.ItemBusinessID = :bizId
AND mi.ParentItemID = 11271 AND mi.ItemParentItemID = 11271
GROUP BY mi.ID, mi.Name GROUP BY mi.ItemID, mi.ItemName
ORDER BY mi.Name ORDER BY mi.ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
burgerLinks = []; burgerLinks = [];
for (row in qBurgerLinks) { for (row in qBurgerLinks) {
arrayAppend(burgerLinks, { arrayAppend(burgerLinks, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"Templates": row.Templates "Templates": row.Templates
}); });
} }
@ -49,20 +49,20 @@ for (row in qBurgerLinks) {
// Also check: are there templates that SHOULD be linked to burgers? // Also check: are there templates that SHOULD be linked to burgers?
// (e.g., Add Cheese, etc.) // (e.g., Add Cheese, etc.)
qCheeseTemplate = queryExecute(" qCheeseTemplate = queryExecute("
SELECT ID, Name, ParentItemID, IsActive SELECT ItemID, ItemName, ItemParentItemID, ItemIsActive
FROM Items FROM Items
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND Name LIKE '%Cheese%' AND ItemName LIKE '%Cheese%'
ORDER BY Name ORDER BY ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
cheeseItems = []; cheeseItems = [];
for (row in qCheeseTemplate) { for (row in qCheeseTemplate) {
arrayAppend(cheeseItems, { arrayAppend(cheeseItems, {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"ParentID": row.ParentItemID, "ParentID": row.ItemParentItemID,
"IsActive": row.IsActive "IsActive": row.ItemIsActive
}); });
} }

View file

@ -26,36 +26,36 @@
<cftry> <cftry>
<!--- Get raw employee records --- > <!--- Get raw employee records --- >
<cfset qEmployees = queryExecute(" <cfset qEmployees = queryExecute("
SELECT e.*, b.Name SELECT e.*, b.BusinessName
FROM Employees e FROM lt_Users_Businesses_Employees e
INNER JOIN Businesses b ON b.ID = e.BusinessID INNER JOIN Businesses b ON b.BusinessID = e.BusinessID
WHERE e.UserID = ? WHERE e.UserID = ?
ORDER BY b.Name ASC ORDER BY b.BusinessName ASC
", [ { value = UserID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })> ", [ { value = UserID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
<cfset employees = []> <cfset employees = []>
<cfloop query="qEmployees"> <cfloop query="qEmployees">
<cfset arrayAppend(employees, { <cfset arrayAppend(employees, {
"EmployeeID": qEmployees.ID, "EmployeeID": qEmployees.EmployeeID,
"UserID": qEmployees.UserID, "UserID": qEmployees.UserID,
"BusinessID": qEmployees.BusinessID, "BusinessID": qEmployees.BusinessID,
"Name": qEmployees.Name, "BusinessName": qEmployees.BusinessName,
"IsActive": qEmployees.IsActive "EmployeeIsActive": qEmployees.EmployeeIsActive
})> })>
</cfloop> </cfloop>
<!--- Check for duplicate businesses --- > <!--- Check for duplicate businesses --- >
<cfset qDuplicates = queryExecute(" <cfset qDuplicates = queryExecute("
SELECT Name, COUNT(*) AS cnt SELECT BusinessName, COUNT(*) AS cnt
FROM Businesses FROM Businesses
GROUP BY Name GROUP BY BusinessName
HAVING COUNT(*) > 1 HAVING COUNT(*) > 1
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfset duplicates = []> <cfset duplicates = []>
<cfloop query="qDuplicates"> <cfloop query="qDuplicates">
<cfset arrayAppend(duplicates, { <cfset arrayAppend(duplicates, {
"Name": qDuplicates.Name, "BusinessName": qDuplicates.BusinessName,
"Count": qDuplicates.cnt "Count": qDuplicates.cnt
})> })>
</cfloop> </cfloop>

View file

@ -10,11 +10,11 @@ try {
messages = []; messages = [];
for (row in qAll) { for (row in qAll) {
arrayAppend(messages, { arrayAppend(messages, {
"MessageID": row.ID, "MessageID": row.MessageID,
"TaskID": row.TaskID, "TaskID": row.TaskID,
"SenderUserID": row.SenderUserID, "SenderUserID": row.SenderUserID,
"SenderType": row.SenderType, "SenderType": row.SenderType,
"MessageBody": left(row.MessageBody, 100), "MessageText": left(row.MessageText, 100),
"CreatedOn": row.CreatedOn "CreatedOn": row.CreatedOn
}); });
} }

View file

@ -10,54 +10,54 @@ response = { "OK": true };
try { try {
// Get Fountain Drinks item // Get Fountain Drinks item
qFountain = queryExecute(" qFountain = queryExecute("
SELECT ID, Name, ParentItemID, Price, IsCollapsible, RequiresChildSelection SELECT ItemID, ItemName, ItemParentItemID, ItemPrice, ItemIsCollapsible, ItemRequiresChildSelection
FROM Items FROM Items
WHERE BusinessID = 17 AND Name LIKE '%Fountain%' WHERE ItemBusinessID = 17 AND ItemName LIKE '%Fountain%'
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response["FountainDrinks"] = []; response["FountainDrinks"] = [];
for (row in qFountain) { for (row in qFountain) {
fountainItem = { fountainItem = {
"ItemID": row.ID, "ItemID": row.ItemID,
"Name": row.Name, "ItemName": row.ItemName,
"Price": row.Price, "ItemPrice": row.ItemPrice,
"IsCollapsible": row.IsCollapsible, "ItemIsCollapsible": row.ItemIsCollapsible,
"RequiresChildSelection": row.RequiresChildSelection, "ItemRequiresChildSelection": row.ItemRequiresChildSelection,
"Children": [] "Children": []
}; };
// Get children of this item // Get children of this item
qChildren = queryExecute(" qChildren = queryExecute("
SELECT ID, Name, ParentItemID, Price, IsCollapsible, RequiresChildSelection, IsCheckedByDefault SELECT ItemID, ItemName, ItemParentItemID, ItemPrice, ItemIsCollapsible, ItemRequiresChildSelection, ItemIsCheckedByDefault
FROM Items FROM Items
WHERE ParentItemID = :parentId WHERE ItemParentItemID = :parentId
ORDER BY SortOrder ORDER BY ItemSortOrder
", { parentId: row.ID }, { datasource: "payfrit" }); ", { parentId: row.ItemID }, { datasource: "payfrit" });
for (child in qChildren) { for (child in qChildren) {
childItem = { childItem = {
"ItemID": child.ItemID, "ItemID": child.ItemID,
"Name": child.Name, "ItemName": child.ItemName,
"Price": child.Price, "ItemPrice": child.ItemPrice,
"IsCollapsible": child.IsCollapsible, "ItemIsCollapsible": child.ItemIsCollapsible,
"IsCheckedByDefault": child.IsCheckedByDefault, "ItemIsCheckedByDefault": child.ItemIsCheckedByDefault,
"Grandchildren": [] "Grandchildren": []
}; };
// Get grandchildren // Get grandchildren
qGrandchildren = queryExecute(" qGrandchildren = queryExecute("
SELECT ID, Name, Price, IsCheckedByDefault SELECT ItemID, ItemName, ItemPrice, ItemIsCheckedByDefault
FROM Items FROM Items
WHERE ParentItemID = :parentId WHERE ItemParentItemID = :parentId
ORDER BY SortOrder ORDER BY ItemSortOrder
", { parentId: child.ItemID }, { datasource: "payfrit" }); ", { parentId: child.ItemID }, { datasource: "payfrit" });
for (gc in qGrandchildren) { for (gc in qGrandchildren) {
arrayAppend(childItem.Grandchildren, { arrayAppend(childItem.Grandchildren, {
"ItemID": gc.ItemID, "ItemID": gc.ItemID,
"Name": gc.Name, "ItemName": gc.ItemName,
"Price": gc.Price, "ItemPrice": gc.ItemPrice,
"IsCheckedByDefault": gc.IsCheckedByDefault "ItemIsCheckedByDefault": gc.ItemIsCheckedByDefault
}); });
} }

View file

@ -14,10 +14,10 @@ if (structKeyExists(data, "Phone") && len(data.Phone)) {
phone = reReplace(data.Phone, "[^0-9]", "", "all"); phone = reReplace(data.Phone, "[^0-9]", "", "all");
qUser = queryExecute(" qUser = queryExecute("
SELECT ID, FirstName, LastName, EmailAddress, ContactNumber SELECT UserID, UserFirstName, UserLastName, UserEmailAddress, UserContactNumber
FROM Users FROM Users
WHERE REPLACE(REPLACE(REPLACE(ContactNumber, '-', ''), '(', ''), ')', '') LIKE ? WHERE REPLACE(REPLACE(REPLACE(UserContactNumber, '-', ''), '(', ''), ')', '') LIKE ?
OR ContactNumber LIKE ? OR UserContactNumber LIKE ?
", [ ", [
{ value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" }, { value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" },
{ value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" } { value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" }
@ -28,35 +28,35 @@ if (structKeyExists(data, "Phone") && len(data.Phone)) {
abort; abort;
} }
userId = qUser.ID; userId = qUser.UserID;
qEmployees = queryExecute(" qEmployees = queryExecute("
SELECT e.ID, e.BusinessID, e.StatusID, SELECT e.EmployeeID, e.BusinessID, e.EmployeeStatusID,
CAST(e.IsActive AS UNSIGNED) AS IsActive, CAST(e.EmployeeIsActive AS UNSIGNED) AS EmployeeIsActive,
b.Name b.BusinessName
FROM Employees e FROM lt_Users_Businesses_Employees e
JOIN Businesses b ON e.BusinessID = b.ID JOIN Businesses b ON e.BusinessID = b.BusinessID
WHERE e.UserID = ? WHERE e.UserID = ?
", [{ value: userId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" }); ", [{ value: userId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" });
employees = []; employees = [];
for (row in qEmployees) { for (row in qEmployees) {
arrayAppend(employees, { arrayAppend(employees, {
"EmployeeID": row.ID, "EmployeeID": row.EmployeeID,
"BusinessID": row.BusinessID, "BusinessID": row.BusinessID,
"Name": row.Name, "BusinessName": row.BusinessName,
"StatusID": row.StatusID, "StatusID": row.EmployeeStatusID,
"IsActive": row.IsActive "IsActive": row.EmployeeIsActive
}); });
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"USER": { "USER": {
"UserID": qUser.ID, "UserID": qUser.UserID,
"Name": trim(qUser.FirstName & " " & qUser.LastName), "Name": trim(qUser.UserFirstName & " " & qUser.UserLastName),
"Email": qUser.EmailAddress, "Email": qUser.UserEmailAddress,
"Phone": qUser.ContactNumber "Phone": qUser.UserContactNumber
}, },
"EMPLOYEES": employees "EMPLOYEES": employees
})); }));
@ -67,23 +67,23 @@ if (structKeyExists(data, "Phone") && len(data.Phone)) {
businessId = structKeyExists(data, "BusinessID") ? val(data.BusinessID) : 17; businessId = structKeyExists(data, "BusinessID") ? val(data.BusinessID) : 17;
q = queryExecute(" q = queryExecute("
SELECT ID, UserID, StatusID, IsActive, SELECT EmployeeID, UserID, EmployeeStatusID, EmployeeIsActive,
CAST(IsActive AS UNSIGNED) AS IsActiveInt CAST(EmployeeIsActive AS UNSIGNED) AS IsActiveInt
FROM Employees FROM lt_Users_Businesses_Employees
WHERE BusinessID = ? WHERE BusinessID = ?
", [{ value: businessId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" }); ", [{ value: businessId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" });
rows = []; rows = [];
for (r in q) { for (r in q) {
arrayAppend(rows, { arrayAppend(rows, {
"EmployeeID": r.ID, "EmployeeID": r.EmployeeID,
"UserID": r.UserID, "UserID": r.UserID,
"StatusID": r.StatusID, "StatusID": r.EmployeeStatusID,
"RawIsActive": r.IsActive, "RawIsActive": r.EmployeeIsActive,
"CastIsActive": r.IsActiveInt, "CastIsActive": r.IsActiveInt,
"ValRaw": val(r.IsActive), "ValRaw": val(r.EmployeeIsActive),
"ValCast": val(r.IsActiveInt), "ValCast": val(r.IsActiveInt),
"EqRaw1": r.IsActive == 1, "EqRaw1": r.EmployeeIsActive == 1,
"EqCast1": r.IsActiveInt == 1 "EqCast1": r.IsActiveInt == 1
}); });
} }

View file

@ -13,8 +13,8 @@ try {
// Close all open chats action // Close all open chats action
if (structKeyExists(data, "action") && data.action == "closeAllChats") { if (structKeyExists(data, "action") && data.action == "closeAllChats") {
queryExecute(" queryExecute("
UPDATE Tasks SET CompletedOn = NOW() UPDATE Tasks SET TaskCompletedOn = NOW()
WHERE TaskTypeID = 2 AND CompletedOn IS NULL WHERE TaskTypeID = 2 AND TaskCompletedOn IS NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
writeOutput(serializeJSON({ "OK": true, "MESSAGE": "All open chats closed" })); writeOutput(serializeJSON({ "OK": true, "MESSAGE": "All open chats closed" }));
abort; abort;
@ -24,38 +24,38 @@ if (structKeyExists(data, "action") && data.action == "closeAllChats") {
<cftry> <cftry>
<cfset qTasks = queryExecute(" <cfset qTasks = queryExecute("
SELECT SELECT
t.ID, t.TaskID,
t.BusinessID, t.TaskBusinessID,
t.OrderID, t.TaskOrderID,
t.ClaimedByUserID, t.TaskClaimedByUserID,
t.ClaimedOn, t.TaskClaimedOn,
t.CompletedOn, t.TaskCompletedOn,
o.StatusID o.OrderStatusID
FROM Tasks t FROM Tasks t
LEFT JOIN Orders o ON o.ID = t.OrderID LEFT JOIN Orders o ON o.OrderID = t.TaskOrderID
ORDER BY t.ID DESC ORDER BY t.TaskID DESC
LIMIT 20 LIMIT 20
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfset tasks = []> <cfset tasks = []>
<cfloop query="qTasks"> <cfloop query="qTasks">
<cfset arrayAppend(tasks, { <cfset arrayAppend(tasks, {
"TaskID": qTasks.ID, "TaskID": qTasks.TaskID,
"BusinessID": qTasks.BusinessID, "TaskBusinessID": qTasks.TaskBusinessID,
"OrderID": qTasks.OrderID, "TaskOrderID": qTasks.TaskOrderID,
"ClaimedByUserID": qTasks.ClaimedByUserID, "TaskClaimedByUserID": qTasks.TaskClaimedByUserID,
"ClaimedOn": isNull(qTasks.ClaimedOn) ? "NULL" : dateTimeFormat(qTasks.ClaimedOn, "yyyy-mm-dd HH:nn:ss"), "TaskClaimedOn": isNull(qTasks.TaskClaimedOn) ? "NULL" : dateTimeFormat(qTasks.TaskClaimedOn, "yyyy-mm-dd HH:nn:ss"),
"CompletedOn": isNull(qTasks.CompletedOn) ? "NULL" : dateTimeFormat(qTasks.CompletedOn, "yyyy-mm-dd HH:nn:ss"), "TaskCompletedOn": isNull(qTasks.TaskCompletedOn) ? "NULL" : dateTimeFormat(qTasks.TaskCompletedOn, "yyyy-mm-dd HH:nn:ss"),
"StatusID": isNull(qTasks.StatusID) ? "NULL" : qTasks.StatusID "OrderStatusID": isNull(qTasks.OrderStatusID) ? "NULL" : qTasks.OrderStatusID
})> })>
</cfloop> </cfloop>
<cfset qStats = queryExecute(" <cfset qStats = queryExecute("
SELECT SELECT
COUNT(*) as Total, COUNT(*) as Total,
SUM(CASE WHEN ClaimedByUserID > 0 AND CompletedOn IS NULL THEN 1 ELSE 0 END) as ClaimedNotCompleted, SUM(CASE WHEN TaskClaimedByUserID > 0 AND TaskCompletedOn IS NULL THEN 1 ELSE 0 END) as ClaimedNotCompleted,
SUM(CASE WHEN ClaimedByUserID = 0 OR ClaimedByUserID IS NULL THEN 1 ELSE 0 END) as Unclaimed, SUM(CASE WHEN TaskClaimedByUserID = 0 OR TaskClaimedByUserID IS NULL THEN 1 ELSE 0 END) as Unclaimed,
SUM(CASE WHEN CompletedOn IS NOT NULL THEN 1 ELSE 0 END) as Completed SUM(CASE WHEN TaskCompletedOn IS NOT NULL THEN 1 ELSE 0 END) as Completed
FROM Tasks FROM Tasks
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>

View file

@ -4,19 +4,19 @@
<cftry> <cftry>
<cfset qAll = queryExecute(" <cfset qAll = queryExecute("
SELECT ID, ClaimedByUserID, CompletedOn, OrderID, SELECT TaskID, TaskClaimedByUserID, TaskCompletedOn, TaskOrderID,
CASE WHEN CompletedOn IS NULL THEN 'YES_NULL' ELSE 'NOT_NULL' END AS IsNull CASE WHEN TaskCompletedOn IS NULL THEN 'YES_NULL' ELSE 'NOT_NULL' END AS IsNull
FROM Tasks FROM Tasks
ORDER BY ID DESC ORDER BY TaskID DESC
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfset tasks = []> <cfset tasks = []>
<cfloop query="qAll"> <cfloop query="qAll">
<cfset arrayAppend(tasks, { <cfset arrayAppend(tasks, {
"TaskID": qAll.ID, "TaskID": qAll.TaskID,
"ClaimedByUserID": qAll.ClaimedByUserID, "TaskClaimedByUserID": qAll.TaskClaimedByUserID,
"OrderID": qAll.OrderID, "TaskOrderID": qAll.TaskOrderID,
"CompletedOn": len(trim(qAll.CompletedOn)) ? toString(qAll.CompletedOn) : "", "TaskCompletedOn": len(trim(qAll.TaskCompletedOn)) ? toString(qAll.TaskCompletedOn) : "",
"IsNull": qAll.IsNull "IsNull": qAll.IsNull
})> })>
</cfloop> </cfloop>

View file

@ -3,39 +3,39 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript> <cfscript>
// Check lt_ItemID_TemplateItemID for Big Dean's (BusinessID 27) // Check ItemTemplateLinks for Big Dean's (BusinessID 27)
bizId = 27; bizId = 27;
// Count total links // Count total links
qCount = queryExecute("SELECT COUNT(*) as cnt FROM lt_ItemID_TemplateItemID", {}, { datasource: "payfrit" }); qCount = queryExecute("SELECT COUNT(*) as cnt FROM ItemTemplateLinks", {}, { datasource: "payfrit" });
// Get template item IDs for this business // Get template item IDs for this business
qTemplates = queryExecute(" qTemplates = queryExecute("
SELECT DISTINCT tl.TemplateItemID, i.Name SELECT DISTINCT tl.TemplateItemID, i.ItemName
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items i ON i.ID = tl.TemplateItemID JOIN Items i ON i.ItemID = tl.TemplateItemID
WHERE i.BusinessID = :bizId WHERE i.ItemBusinessID = :bizId
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
templates = []; templates = [];
for (row in qTemplates) { for (row in qTemplates) {
arrayAppend(templates, { "TemplateItemID": row.TemplateItemID, "Name": row.Name }); arrayAppend(templates, { "TemplateItemID": row.TemplateItemID, "ItemName": row.ItemName });
} }
// Get items that should be categories (ParentItemID=0, not templates) // Get items that should be categories (ParentItemID=0, not templates)
qCategories = queryExecute(" qCategories = queryExecute("
SELECT i.ID, i.Name, i.ParentItemID, i.IsCollapsible SELECT i.ItemID, i.ItemName, i.ItemParentItemID, i.ItemIsCollapsible
FROM Items i FROM Items i
WHERE i.BusinessID = :bizId WHERE i.ItemBusinessID = :bizId
AND i.ParentItemID = 0 AND i.ItemParentItemID = 0
AND i.IsCollapsible = 0 AND i.ItemIsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID) AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
ORDER BY i.SortOrder ORDER BY i.ItemSortOrder
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
categories = []; categories = [];
for (row in qCategories) { for (row in qCategories) {
arrayAppend(categories, { "ItemID": row.ID, "Name": row.Name }); arrayAppend(categories, { "ItemID": row.ItemID, "ItemName": row.ItemName });
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -20,10 +20,10 @@ if (len(phone) == 0) {
// Find user by phone // Find user by phone
qUser = queryExecute(" qUser = queryExecute("
SELECT ID, FirstName, LastName, EmailAddress, ContactNumber SELECT UserID, UserFirstName, UserLastName, UserEmailAddress, UserContactNumber
FROM Users FROM Users
WHERE REPLACE(REPLACE(REPLACE(ContactNumber, '-', ''), '(', ''), ')', '') LIKE ? WHERE REPLACE(REPLACE(REPLACE(UserContactNumber, '-', ''), '(', ''), ')', '') LIKE ?
OR ContactNumber LIKE ? OR UserContactNumber LIKE ?
", [ ", [
{ value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" }, { value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" },
{ value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" } { value: "%" & phone & "%", cfsqltype: "cf_sql_varchar" }
@ -34,36 +34,36 @@ if (qUser.recordCount == 0) {
abort; abort;
} }
userId = qUser.ID; userId = qUser.UserID;
// Get all employee records for this user // Get all employee records for this user
qEmployees = queryExecute(" qEmployees = queryExecute("
SELECT e.ID, e.BusinessID, e.StatusID, SELECT e.EmployeeID, e.BusinessID, e.EmployeeStatusID,
CAST(e.IsActive AS UNSIGNED) AS IsActive, CAST(e.EmployeeIsActive AS UNSIGNED) AS EmployeeIsActive,
b.Name b.BusinessName
FROM Employees e FROM lt_Users_Businesses_Employees e
JOIN Businesses b ON e.BusinessID = b.ID JOIN Businesses b ON e.BusinessID = b.BusinessID
WHERE e.UserID = ? WHERE e.UserID = ?
", [{ value: userId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" }); ", [{ value: userId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" });
employees = []; employees = [];
for (row in qEmployees) { for (row in qEmployees) {
arrayAppend(employees, { arrayAppend(employees, {
"EmployeeID": row.ID, "EmployeeID": row.EmployeeID,
"BusinessID": row.BusinessID, "BusinessID": row.BusinessID,
"Name": row.Name, "BusinessName": row.BusinessName,
"StatusID": row.StatusID, "StatusID": row.EmployeeStatusID,
"IsActive": row.IsActive "IsActive": row.EmployeeIsActive
}); });
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"USER": { "USER": {
"UserID": qUser.ID, "UserID": qUser.UserID,
"Name": trim(qUser.FirstName & " " & qUser.LastName), "Name": trim(qUser.UserFirstName & " " & qUser.UserLastName),
"Email": qUser.EmailAddress, "Email": qUser.UserEmailAddress,
"Phone": qUser.ContactNumber "Phone": qUser.UserContactNumber
}, },
"EMPLOYEES": employees "EMPLOYEES": employees
})); }));

View file

@ -28,19 +28,19 @@ try {
response["ERROR"] = "BusinessID required"; response["ERROR"] = "BusinessID required";
} else { } else {
// Get address ID first // Get address ID first
qBiz = queryExecute("SELECT AddressID FROM Businesses WHERE ID = :id", { id: bizID }, { datasource = "payfrit" }); qBiz = queryExecute("SELECT BusinessAddressID FROM Businesses WHERE BusinessID = :id", { id: bizID }, { datasource = "payfrit" });
if (qBiz.recordCount == 0) { if (qBiz.recordCount == 0) {
response["ERROR"] = "Business not found"; response["ERROR"] = "Business not found";
} else { } else {
addrID = qBiz.AddressID; addrID = qBiz.BusinessAddressID;
// Delete business // Delete business
queryExecute("DELETE FROM Businesses WHERE ID = :id", { id: bizID }, { datasource = "payfrit" }); queryExecute("DELETE FROM Businesses WHERE BusinessID = :id", { id: bizID }, { datasource = "payfrit" });
// Delete address if exists // Delete address if exists
if (val(addrID) > 0) { if (val(addrID) > 0) {
queryExecute("DELETE FROM Addresses WHERE ID = :id", { id: addrID }, { datasource = "payfrit" }); queryExecute("DELETE FROM Addresses WHERE AddressID = :id", { id: addrID }, { datasource = "payfrit" });
} }
response["OK"] = true; response["OK"] = true;

View file

@ -13,7 +13,7 @@
* Delete Orphan Modifiers for In and Out Burger (BusinessID=17) * Delete Orphan Modifiers for In and Out Burger (BusinessID=17)
* *
* This script deletes duplicate modifier items that are no longer needed * This script deletes duplicate modifier items that are no longer needed
* because we now use lt_ItemID_TemplateItemID. * because we now use ItemTemplateLinks.
* *
* The orphan items are level-1 modifiers (direct children of parent items) * The orphan items are level-1 modifiers (direct children of parent items)
* that have been replaced by template links. * that have been replaced by template links.
@ -29,18 +29,18 @@ try {
qOrphans = queryExecute(" qOrphans = queryExecute("
SELECT SELECT
m.ItemID, m.ItemID,
m.Name, m.ItemName,
m.ParentItemID, m.ItemParentItemID,
p.Name as ParentName p.ItemName as ParentName
FROM Items m FROM Items m
INNER JOIN Items p ON p.ItemID = m.ParentItemID INNER JOIN Items p ON p.ItemID = m.ItemParentItemID
INNER JOIN Categories c ON c.ID = p.CategoryID INNER JOIN Categories c ON c.CategoryID = p.ItemCategoryID
WHERE c.BusinessID = :businessID WHERE c.CategoryBusinessID = :businessID
AND m.ParentItemID > 0 AND m.ItemParentItemID > 0
AND p.ParentItemID = 0 AND p.ItemParentItemID = 0
AND (m.IsModifierTemplate IS NULL OR m.IsModifierTemplate = 0) AND (m.ItemIsModifierTemplate IS NULL OR m.ItemIsModifierTemplate = 0)
AND m.IsActive = 1 AND m.ItemIsActive = 1
ORDER BY m.Name ORDER BY m.ItemName
", { businessID: businessID }, { datasource: "payfrit" }); ", { businessID: businessID }, { datasource: "payfrit" });
arrayAppend(response.deleted, "Found " & qOrphans.recordCount & " orphan modifiers to delete"); arrayAppend(response.deleted, "Found " & qOrphans.recordCount & " orphan modifiers to delete");
@ -50,23 +50,23 @@ try {
try { try {
// Delete children of this orphan (options within the modifier group) // Delete children of this orphan (options within the modifier group)
qDeleteChildren = queryExecute(" qDeleteChildren = queryExecute("
DELETE FROM Items WHERE ParentItemID = :orphanID DELETE FROM Items WHERE ItemParentItemID = :orphanID
", { orphanID: orphan.ItemID }, { datasource: "payfrit" }); ", { orphanID: orphan.ItemID }, { datasource: "payfrit" });
// Delete the orphan itself // Delete the orphan itself
qDeleteOrphan = queryExecute(" qDeleteOrphan = queryExecute("
DELETE FROM Items WHERE ID = :orphanID DELETE FROM Items WHERE ItemID = :orphanID
", { orphanID: orphan.ItemID }, { datasource: "payfrit" }); ", { orphanID: orphan.ItemID }, { datasource: "payfrit" });
arrayAppend(response.deleted, { arrayAppend(response.deleted, {
"ItemID": orphan.ItemID, "ItemID": orphan.ItemID,
"Name": orphan.Name, "ItemName": orphan.ItemName,
"WasUnder": orphan.ParentName "WasUnder": orphan.ParentName
}); });
} catch (any deleteErr) { } catch (any deleteErr) {
arrayAppend(response.errors, { arrayAppend(response.errors, {
"ItemID": orphan.ItemID, "ItemID": orphan.ItemID,
"Name": orphan.Name, "ItemName": orphan.ItemName,
"Error": deleteErr.message "Error": deleteErr.message
}); });
} }

View file

@ -11,7 +11,7 @@
<cfscript> <cfscript>
/** /**
* Delete orphan Items at ParentID=0 * Delete orphan Items at ParentID=0
* Orphan = ParentID=0, no children, not in lt_ItemID_TemplateItemID * Orphan = ParentID=0, no children, not in ItemTemplateLinks
*/ */
response = { "OK": false, "deleted": 0, "orphans": [] }; response = { "OK": false, "deleted": 0, "orphans": [] };
@ -19,24 +19,24 @@ response = { "OK": false, "deleted": 0, "orphans": [] };
try { try {
// Find orphans // Find orphans
qOrphans = queryExecute(" qOrphans = queryExecute("
SELECT i.ID, i.Name, i.BusinessID SELECT i.ItemID, i.ItemName, i.ItemBusinessID
FROM Items i FROM Items i
WHERE i.ParentItemID = 0 WHERE i.ItemParentItemID = 0
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM Items child WHERE child.ParentItemID = i.ID SELECT 1 FROM Items child WHERE child.ItemParentItemID = i.ItemID
) )
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID
) )
ORDER BY i.BusinessID, i.Name ORDER BY i.ItemBusinessID, i.ItemName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
orphanIDs = []; orphanIDs = [];
for (orphan in qOrphans) { for (orphan in qOrphans) {
arrayAppend(response.orphans, { arrayAppend(response.orphans, {
"ItemID": orphan.ItemID, "ItemID": orphan.ItemID,
"Name": orphan.Name, "ItemName": orphan.ItemName,
"BusinessID": orphan.BusinessID "BusinessID": orphan.ItemBusinessID
}); });
arrayAppend(orphanIDs, orphan.ItemID); arrayAppend(orphanIDs, orphan.ItemID);
} }
@ -44,7 +44,7 @@ try {
// Delete them by ID list // Delete them by ID list
if (arrayLen(orphanIDs) > 0) { if (arrayLen(orphanIDs) > 0) {
queryExecute(" queryExecute("
DELETE FROM Items WHERE ID IN (#arrayToList(orphanIDs)#) DELETE FROM Items WHERE ItemID IN (#arrayToList(orphanIDs)#)
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} }

View file

@ -14,16 +14,16 @@
* Eliminate Categories Table - Schema Migration * Eliminate Categories Table - Schema Migration
* *
* Final unified schema: * Final unified schema:
* - Everything is an Item with BusinessID * - Everything is an Item with ItemBusinessID
* - ParentID=0 items are either categories or templates (derived from usage) * - ParentID=0 items are either categories or templates (derived from usage)
* - Categories = items at ParentID=0 that have menu items as children * - Categories = items at ParentID=0 that have menu items as children
* - Templates = items at ParentID=0 that appear in lt_ItemID_TemplateItemID * - Templates = items at ParentID=0 that appear in ItemTemplateLinks
* - Orphans = ParentID=0 items that are neither (cleanup candidates) * - Orphans = ParentID=0 items that are neither (cleanup candidates)
* *
* Steps: * Steps:
* 1. Add BusinessID column to Items * 1. Add ItemBusinessID column to Items
* 2. For each Category: create Item, re-parent menu items, set BusinessID * 2. For each Category: create Item, re-parent menu items, set BusinessID
* 3. Set BusinessID on templates based on linked items * 3. Set ItemBusinessID on templates based on linked items
* *
* Query param: ?dryRun=1 to preview without making changes * Query param: ?dryRun=1 to preview without making changes
*/ */
@ -34,30 +34,30 @@ try {
dryRun = structKeyExists(url, "dryRun") && url.dryRun == 1; dryRun = structKeyExists(url, "dryRun") && url.dryRun == 1;
response["dryRun"] = dryRun; response["dryRun"] = dryRun;
// Step 1: Add BusinessID column if it doesn't exist // Step 1: Add ItemBusinessID column if it doesn't exist
try { try {
if (!dryRun) { if (!dryRun) {
queryExecute(" queryExecute("
ALTER TABLE Items ADD COLUMN BusinessID INT DEFAULT 0 AFTER ItemID ALTER TABLE Items ADD COLUMN ItemBusinessID INT DEFAULT 0 AFTER ItemID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} }
arrayAppend(response.steps, "Added BusinessID column to Items table"); arrayAppend(response.steps, "Added ItemBusinessID column to Items table");
} catch (any e) { } catch (any e) {
if (findNoCase("Duplicate column", e.message)) { if (findNoCase("Duplicate column", e.message)) {
arrayAppend(response.steps, "BusinessID column already exists"); arrayAppend(response.steps, "ItemBusinessID column already exists");
} else { } else {
throw(e); throw(e);
} }
} }
// Step 2: Add index on BusinessID // Step 2: Add index on ItemBusinessID
try { try {
if (!dryRun) { if (!dryRun) {
queryExecute(" queryExecute("
CREATE INDEX idx_item_business ON Items (BusinessID) CREATE INDEX idx_item_business ON Items (ItemBusinessID)
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} }
arrayAppend(response.steps, "Added index on BusinessID"); arrayAppend(response.steps, "Added index on ItemBusinessID");
} catch (any e) { } catch (any e) {
if (findNoCase("Duplicate key name", e.message)) { if (findNoCase("Duplicate key name", e.message)) {
arrayAppend(response.steps, "Index idx_item_business already exists"); arrayAppend(response.steps, "Index idx_item_business already exists");
@ -66,7 +66,7 @@ try {
} }
} }
// Step 3: Drop foreign key constraint on CategoryID if it exists // Step 3: Drop foreign key constraint on ItemCategoryID if it exists
try { try {
if (!dryRun) { if (!dryRun) {
queryExecute(" queryExecute("
@ -84,9 +84,9 @@ try {
// Step 4: Get all Categories // Step 4: Get all Categories
qCategories = queryExecute(" qCategories = queryExecute("
SELECT ID, BusinessID, Name SELECT CategoryID, CategoryBusinessID, CategoryName
FROM Categories FROM Categories
ORDER BY BusinessID, Name ORDER BY CategoryBusinessID, CategoryName
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Found " & qCategories.recordCount & " categories to migrate"); arrayAppend(response.steps, "Found " & qCategories.recordCount & " categories to migrate");
@ -94,29 +94,29 @@ try {
// Step 4: Migrate each category // Step 4: Migrate each category
for (cat in qCategories) { for (cat in qCategories) {
migration = { migration = {
"oldCategoryID": cat.ID, "oldCategoryID": cat.CategoryID,
"categoryName": cat.Name, "categoryName": cat.CategoryName,
"businessID": cat.BusinessID, "businessID": cat.CategoryBusinessID,
"newItemID": 0, "newItemID": 0,
"itemsUpdated": 0 "itemsUpdated": 0
}; };
if (!dryRun) { if (!dryRun) {
// Create new Item for this category (ParentID=0, no template flag needed) // Create new Item for this category (ParentID=0, no template flag needed)
// Note: CategoryID set to 0 temporarily until we drop that column // Note: ItemCategoryID set to 0 temporarily until we drop that column
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
BusinessID, ItemBusinessID,
CategoryID, ItemCategoryID,
Name, ItemName,
Description, ItemDescription,
ParentItemID, ItemParentItemID,
Price, ItemPrice,
IsActive, ItemIsActive,
IsCheckedByDefault, ItemIsCheckedByDefault,
RequiresChildSelection, ItemRequiresChildSelection,
SortOrder, ItemSortOrder,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:businessID, :businessID,
0, 0,
@ -131,56 +131,56 @@ try {
NOW() NOW()
) )
", { ", {
businessID: cat.BusinessID, businessID: cat.CategoryBusinessID,
categoryName: cat.Name categoryName: cat.CategoryName
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
// Get the new Item ID // Get the new Item ID
qNewItem = queryExecute(" qNewItem = queryExecute("
SELECT ID FROM Items SELECT ItemID FROM Items
WHERE BusinessID = :businessID WHERE ItemBusinessID = :businessID
AND Name = :categoryName AND ItemName = :categoryName
AND ParentItemID = 0 AND ItemParentItemID = 0
ORDER BY ID DESC ORDER BY ItemID DESC
LIMIT 1 LIMIT 1
", { ", {
businessID: cat.BusinessID, businessID: cat.CategoryBusinessID,
categoryName: cat.Name categoryName: cat.CategoryName
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
newItemID = qNewItem.ID; newItemID = qNewItem.ItemID;
migration["newItemID"] = newItemID; migration["newItemID"] = newItemID;
// Update menu items in this category: // Update menu items in this category:
// - Set ParentItemID = newItemID (for top-level items only) // - Set ItemParentItemID = newItemID (for top-level items only)
// - Set BusinessID = businessID (for all items) // - Set ItemBusinessID = businessID (for all items)
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET BusinessID = :businessID, SET ItemBusinessID = :businessID,
ParentItemID = :newItemID ItemParentItemID = :newItemID
WHERE CategoryID = :categoryID WHERE ItemCategoryID = :categoryID
AND ParentItemID = 0 AND ItemParentItemID = 0
", { ", {
businessID: cat.BusinessID, businessID: cat.CategoryBusinessID,
newItemID: newItemID, newItemID: newItemID,
categoryID: cat.ID categoryID: cat.CategoryID
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
// Set BusinessID on ALL items in this category (including nested) // Set ItemBusinessID on ALL items in this category (including nested)
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET BusinessID = :businessID SET ItemBusinessID = :businessID
WHERE CategoryID = :categoryID WHERE ItemCategoryID = :categoryID
AND (BusinessID IS NULL OR BusinessID = 0) AND (ItemBusinessID IS NULL OR ItemBusinessID = 0)
", { ", {
businessID: cat.BusinessID, businessID: cat.CategoryBusinessID,
categoryID: cat.ID categoryID: cat.CategoryID
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
// Count how many were updated // Count how many were updated
qCount = queryExecute(" qCount = queryExecute("
SELECT COUNT(*) as cnt FROM Items SELECT COUNT(*) as cnt FROM Items
WHERE ParentItemID = :newItemID WHERE ItemParentItemID = :newItemID
", { newItemID: newItemID }, { datasource: "payfrit" }); ", { newItemID: newItemID }, { datasource: "payfrit" });
migration["itemsUpdated"] = qCount.cnt; migration["itemsUpdated"] = qCount.cnt;
@ -188,9 +188,9 @@ try {
// Dry run - count what would be updated // Dry run - count what would be updated
qCount = queryExecute(" qCount = queryExecute("
SELECT COUNT(*) as cnt FROM Items SELECT COUNT(*) as cnt FROM Items
WHERE CategoryID = :categoryID WHERE ItemCategoryID = :categoryID
AND ParentItemID = 0 AND ItemParentItemID = 0
", { categoryID: cat.ID }, { datasource: "payfrit" }); ", { categoryID: cat.CategoryID }, { datasource: "payfrit" });
migration["itemsToUpdate"] = qCount.cnt; migration["itemsToUpdate"] = qCount.cnt;
} }
@ -198,37 +198,37 @@ try {
arrayAppend(response.migrations, migration); arrayAppend(response.migrations, migration);
} }
// Step 5: Set BusinessID for templates (items in lt_ItemID_TemplateItemID) // Step 5: Set ItemBusinessID for templates (items in ItemTemplateLinks)
// Templates get their BusinessID from the items they're linked to // Templates get their BusinessID from the items they're linked to
if (!dryRun) { if (!dryRun) {
queryExecute(" queryExecute("
UPDATE Items t UPDATE Items t
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
INNER JOIN Items i ON i.ID = tl.ItemID INNER JOIN Items i ON i.ItemID = tl.ItemID
SET t.BusinessID = i.BusinessID SET t.ItemBusinessID = i.ItemBusinessID
WHERE (t.BusinessID IS NULL OR t.BusinessID = 0) WHERE (t.ItemBusinessID IS NULL OR t.ItemBusinessID = 0)
AND i.BusinessID > 0 AND i.ItemBusinessID > 0
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Set BusinessID on templates from linked items"); arrayAppend(response.steps, "Set ItemBusinessID on templates from linked items");
// Set BusinessID on template children (options) // Set ItemBusinessID on template children (options)
queryExecute(" queryExecute("
UPDATE Items c UPDATE Items c
INNER JOIN Items t ON t.ItemID = c.ParentItemID INNER JOIN Items t ON t.ItemID = c.ItemParentItemID
SET c.BusinessID = t.BusinessID SET c.ItemBusinessID = t.ItemBusinessID
WHERE t.BusinessID > 0 WHERE t.ItemBusinessID > 0
AND (c.BusinessID IS NULL OR c.BusinessID = 0) AND (c.ItemBusinessID IS NULL OR c.ItemBusinessID = 0)
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Set BusinessID on template children"); arrayAppend(response.steps, "Set ItemBusinessID on template children");
// Make sure templates have ParentID=0 (they live at top level) // Make sure templates have ParentID=0 (they live at top level)
queryExecute(" queryExecute("
UPDATE Items t UPDATE Items t
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
SET t.ParentItemID = 0 SET t.ItemParentItemID = 0
WHERE t.ParentItemID != 0 WHERE t.ItemParentItemID != 0
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Ensured templates have ParentItemID=0"); arrayAppend(response.steps, "Ensured templates have ParentItemID=0");

View file

@ -14,32 +14,32 @@ fakeCategories = [11177, 11180, 11183, 11186, 11190, 11193, 11196, 11199, 11204,
// Deactivate these items (or we could delete them, but deactivate is safer) // Deactivate these items (or we could delete them, but deactivate is safer)
for (itemId in fakeCategories) { for (itemId in fakeCategories) {
queryExecute(" queryExecute("
UPDATE Items SET IsActive = 0 WHERE ItemID = :itemId AND BusinessID = :bizId UPDATE Items SET ItemIsActive = 0 WHERE ItemID = :itemId AND ItemBusinessID = :bizId
", { itemId: itemId, bizId: bizId }, { datasource: "payfrit" }); ", { itemId: itemId, bizId: bizId }, { datasource: "payfrit" });
} }
// Also deactivate their children (modifier options that belong to these fake parents) // Also deactivate their children (modifier options that belong to these fake parents)
for (itemId in fakeCategories) { for (itemId in fakeCategories) {
queryExecute(" queryExecute("
UPDATE Items SET IsActive = 0 WHERE ParentItemID = :itemId AND BusinessID = :bizId UPDATE Items SET ItemIsActive = 0 WHERE ItemParentItemID = :itemId AND ItemBusinessID = :bizId
", { itemId: itemId, bizId: bizId }, { datasource: "payfrit" }); ", { itemId: itemId, bizId: bizId }, { datasource: "payfrit" });
} }
// Now verify what categories remain // Now verify what categories remain
qCategories = queryExecute(" qCategories = queryExecute("
SELECT i.ID, i.Name SELECT i.ItemID, i.ItemName
FROM Items i FROM Items i
WHERE i.BusinessID = :bizId WHERE i.ItemBusinessID = :bizId
AND i.ParentItemID = 0 AND i.ItemParentItemID = 0
AND i.IsActive = 1 AND i.ItemIsActive = 1
AND i.IsCollapsible = 0 AND i.ItemIsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID) AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
ORDER BY i.SortOrder ORDER BY i.ItemSortOrder
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
categories = []; categories = [];
for (row in qCategories) { for (row in qCategories) {
arrayAppend(categories, { "ItemID": row.ID, "Name": row.Name }); arrayAppend(categories, { "ItemID": row.ItemID, "ItemName": row.ItemName });
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -21,8 +21,8 @@ actions = [];
// First, let's see what templates already exist and are active for burgers // First, let's see what templates already exist and are active for burgers
qExistingLinks = queryExecute(" qExistingLinks = queryExecute("
SELECT tl.ItemID as MenuItemID, tl.TemplateItemID, t.Name as TemplateName SELECT tl.ItemID as MenuItemID, tl.TemplateItemID, t.ItemName as TemplateName
FROM lt_ItemID_TemplateItemID tl FROM ItemTemplateLinks tl
JOIN Items t ON t.ItemID = tl.TemplateItemID JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE tl.ItemID IN (:burgerIds) WHERE tl.ItemID IN (:burgerIds)
", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" }); ", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" });
@ -33,8 +33,8 @@ for (row in qExistingLinks) {
// Reactivate template 11196 (Extras with Add Cheese) // Reactivate template 11196 (Extras with Add Cheese)
if (!dryRun) { if (!dryRun) {
queryExecute("UPDATE Items SET IsActive = 1 WHERE ItemID = 11196", {}, { datasource: "payfrit" }); queryExecute("UPDATE Items SET ItemIsActive = 1 WHERE ItemID = 11196", {}, { datasource: "payfrit" });
queryExecute("UPDATE Items SET IsActive = 1 WHERE ParentItemID = 11196", {}, { datasource: "payfrit" }); queryExecute("UPDATE Items SET ItemIsActive = 1 WHERE ItemParentItemID = 11196", {}, { datasource: "payfrit" });
} }
arrayAppend(actions, { action: dryRun ? "WOULD_REACTIVATE" : "REACTIVATED", itemID: 11196, name: "Extras (Add Cheese, Add Onions)" }); arrayAppend(actions, { action: dryRun ? "WOULD_REACTIVATE" : "REACTIVATED", itemID: 11196, name: "Extras (Add Cheese, Add Onions)" });
@ -42,13 +42,13 @@ arrayAppend(actions, { action: dryRun ? "WOULD_REACTIVATE" : "REACTIVATED", item
for (burgerId in burgerIds) { for (burgerId in burgerIds) {
// Check if link already exists // Check if link already exists
qCheck = queryExecute(" qCheck = queryExecute("
SELECT COUNT(*) as cnt FROM lt_ItemID_TemplateItemID WHERE ItemID = :burgerId AND TemplateItemID = 11196 SELECT COUNT(*) as cnt FROM ItemTemplateLinks WHERE ItemID = :burgerId AND TemplateItemID = 11196
", { burgerId: burgerId }, { datasource: "payfrit" }); ", { burgerId: burgerId }, { datasource: "payfrit" });
if (qCheck.cnt EQ 0) { if (qCheck.cnt EQ 0) {
if (!dryRun) { if (!dryRun) {
queryExecute(" queryExecute("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder) INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:burgerId, 11196, 2) VALUES (:burgerId, 11196, 2)
", { burgerId: burgerId }, { datasource: "payfrit" }); ", { burgerId: burgerId }, { datasource: "payfrit" });
} }
@ -59,17 +59,17 @@ for (burgerId in burgerIds) {
// Verify the result // Verify the result
if (!dryRun) { if (!dryRun) {
qVerify = queryExecute(" qVerify = queryExecute("
SELECT mi.ID, mi.Name, GROUP_CONCAT(t.Name ORDER BY tl.SortOrder) as Templates SELECT mi.ItemID, mi.ItemName, GROUP_CONCAT(t.ItemName ORDER BY tl.SortOrder) as Templates
FROM Items mi FROM Items mi
LEFT JOIN lt_ItemID_TemplateItemID tl ON tl.ItemID = mi.ID LEFT JOIN ItemTemplateLinks tl ON tl.ItemID = mi.ItemID
LEFT JOIN Items t ON t.ItemID = tl.TemplateItemID LEFT JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE mi.ID IN (:burgerIds) WHERE mi.ItemID IN (:burgerIds)
GROUP BY mi.ID, mi.Name GROUP BY mi.ItemID, mi.ItemName
", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" }); ", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" });
result = []; result = [];
for (row in qVerify) { for (row in qVerify) {
arrayAppend(result, { itemID: row.ID, name: row.Name, templates: row.Templates }); arrayAppend(result, { itemID: row.ItemID, name: row.ItemName, templates: row.Templates });
} }
} else { } else {
result = "Dry run - no changes made"; result = "Dry run - no changes made";

View file

@ -1,38 +0,0 @@
<cfsetting showdebugoutput="false">
<cfcontent type="application/json" reset="true">
<cfscript>
// One-time fix: remove # prefix from BrandColor
qBefore = queryExecute("
SELECT ID, BrandColor
FROM Businesses
WHERE BrandColor LIKE :pattern
", { pattern: { value: "##%", cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
if (qBefore.recordCount > 0) {
queryExecute("
UPDATE Businesses
SET BrandColor = SUBSTRING(BrandColor, 2)
WHERE BrandColor LIKE :pattern
", { pattern: { value: "##%", cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
}
qAfter = queryExecute("
SELECT ID, BrandColor
FROM Businesses
WHERE BrandColor IS NOT NULL AND LENGTH(BrandColor) > 0
", {}, { datasource: "payfrit" });
rows = [];
for (i = 1; i <= qAfter.recordCount; i++) {
arrayAppend(rows, {
"BusinessID": qAfter.BusinessID[i],
"BrandColor": qAfter.BrandColor[i]
});
}
writeOutput(serializeJSON({
"OK": true,
"FIXED": qBefore.recordCount,
"CURRENT": rows
}));
</cfscript>

View file

@ -1,37 +0,0 @@
<cfsetting showdebugoutput="false">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Fix orphaned tasks that had old category ID 4 (deleted Service Point)
// Update them to use new category ID 25 (new Service Point)
try {
// First check how many tasks are affected
qCount = queryExecute("
SELECT COUNT(*) as cnt FROM Tasks WHERE TaskCategoryID = 4
", [], { datasource: "payfrit" });
affectedCount = qCount.cnt;
if (affectedCount > 0) {
// Update them to the new category
queryExecute("
UPDATE Tasks SET TaskCategoryID = 25 WHERE TaskCategoryID = 4
", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Updated " & affectedCount & " tasks from category 4 to category 25"
}));
} else {
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "No tasks found with category ID 4"
}));
}
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -30,8 +30,8 @@
* 2. Re-parent the three flavors under the new group * 2. Re-parent the three flavors under the new group
* 3. Remove template flag from flavors * 3. Remove template flag from flavors
* 4. Mark "Choose Flavor" as the template * 4. Mark "Choose Flavor" as the template
* 5. Create lt_ItemID_TemplateItemID entry for Shake -> Choose Flavor * 5. Create ItemTemplateLinks entry for Shake -> Choose Flavor
* 6. Set RequiresChildSelection=1 on the shake item * 6. Set ItemRequiresChildSelection=1 on the shake item
*/ */
response = { "OK": false, "steps": [] }; response = { "OK": false, "steps": [] };
@ -46,19 +46,19 @@ try {
// Step 1: Create "Choose Flavor" modifier group under Shake // Step 1: Create "Choose Flavor" modifier group under Shake
queryExecute(" queryExecute("
INSERT INTO Items ( INSERT INTO Items (
CategoryID, ItemCategoryID,
Name, ItemName,
Description, ItemDescription,
ParentItemID, ItemParentItemID,
Price, ItemPrice,
IsActive, ItemIsActive,
IsCheckedByDefault, ItemIsCheckedByDefault,
RequiresChildSelection, ItemRequiresChildSelection,
MaxNumSelectionReq, ItemMaxNumSelectionReq,
IsCollapsible, ItemIsCollapsible,
SortOrder, ItemSortOrder,
IsModifierTemplate, ItemIsModifierTemplate,
AddedOn ItemAddedOn
) VALUES ( ) VALUES (
:categoryID, :categoryID,
'Choose Flavor', 'Choose Flavor',
@ -81,22 +81,22 @@ try {
// Get the new Choose Flavor ID // Get the new Choose Flavor ID
qNewGroup = queryExecute(" qNewGroup = queryExecute("
SELECT ID FROM Items SELECT ItemID FROM Items
WHERE Name = 'Choose Flavor' WHERE ItemName = 'Choose Flavor'
AND ParentItemID = :shakeItemID AND ItemParentItemID = :shakeItemID
ORDER BY ID DESC ORDER BY ItemID DESC
LIMIT 1 LIMIT 1
", { shakeItemID: shakeItemID }, { datasource: "payfrit" }); ", { shakeItemID: shakeItemID }, { datasource: "payfrit" });
chooseFlavorID = qNewGroup.ID; chooseFlavorID = qNewGroup.ItemID;
arrayAppend(response.steps, "Created 'Choose Flavor' group with ID: " & chooseFlavorID); arrayAppend(response.steps, "Created 'Choose Flavor' group with ID: " & chooseFlavorID);
// Step 2: Re-parent the three flavors under Choose Flavor // Step 2: Re-parent the three flavors under Choose Flavor
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET ParentItemID = :chooseFlavorID, SET ItemParentItemID = :chooseFlavorID,
IsModifierTemplate = 0, ItemIsModifierTemplate = 0,
IsCheckedByDefault = 0 ItemIsCheckedByDefault = 0
WHERE ItemID IN (:chocolateID, :strawberryID, :vanillaID) WHERE ItemID IN (:chocolateID, :strawberryID, :vanillaID)
", { ", {
chooseFlavorID: chooseFlavorID, chooseFlavorID: chooseFlavorID,
@ -109,14 +109,14 @@ try {
// Step 3: Set Vanilla as default (common choice) // Step 3: Set Vanilla as default (common choice)
queryExecute(" queryExecute("
UPDATE Items SET IsCheckedByDefault = 1 WHERE ItemID = :vanillaID UPDATE Items SET ItemIsCheckedByDefault = 1 WHERE ItemID = :vanillaID
", { vanillaID: vanillaID }, { datasource: "payfrit" }); ", { vanillaID: vanillaID }, { datasource: "payfrit" });
arrayAppend(response.steps, "Set Vanilla as default flavor"); arrayAppend(response.steps, "Set Vanilla as default flavor");
// Step 4: Remove old template links for the flavors // Step 4: Remove old template links for the flavors
queryExecute(" queryExecute("
DELETE FROM lt_ItemID_TemplateItemID DELETE FROM ItemTemplateLinks
WHERE TemplateItemID IN (:chocolateID, :strawberryID, :vanillaID) WHERE TemplateItemID IN (:chocolateID, :strawberryID, :vanillaID)
", { ", {
chocolateID: chocolateID, chocolateID: chocolateID,
@ -126,9 +126,9 @@ try {
arrayAppend(response.steps, "Removed old template links for flavor items"); arrayAppend(response.steps, "Removed old template links for flavor items");
// Step 5: Create lt_ItemID_TemplateItemID for Shake -> Choose Flavor // Step 5: Create ItemTemplateLinks for Shake -> Choose Flavor
queryExecute(" queryExecute("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder) INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:shakeItemID, :chooseFlavorID, 0) VALUES (:shakeItemID, :chooseFlavorID, 0)
ON DUPLICATE KEY UPDATE SortOrder = 0 ON DUPLICATE KEY UPDATE SortOrder = 0
", { ", {
@ -138,14 +138,14 @@ try {
arrayAppend(response.steps, "Created template link: Shake -> Choose Flavor"); arrayAppend(response.steps, "Created template link: Shake -> Choose Flavor");
// Step 6: Set RequiresChildSelection on shake // Step 6: Set ItemRequiresChildSelection on shake
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET RequiresChildSelection = 1 SET ItemRequiresChildSelection = 1
WHERE ItemID = :shakeItemID WHERE ItemID = :shakeItemID
", { shakeItemID: shakeItemID }, { datasource: "payfrit" }); ", { shakeItemID: shakeItemID }, { datasource: "payfrit" });
arrayAppend(response.steps, "Set RequiresChildSelection=1 on Shake item"); arrayAppend(response.steps, "Set ItemRequiresChildSelection=1 on Shake item");
response["OK"] = true; response["OK"] = true;
response["chooseFlavorID"] = chooseFlavorID; response["chooseFlavorID"] = chooseFlavorID;

View file

@ -75,17 +75,17 @@ function buildAddressString(line1, line2, city, zipCode) {
<cfif structKeyExists(url, "geocode") AND structKeyExists(url, "addressId")> <cfif structKeyExists(url, "geocode") AND structKeyExists(url, "addressId")>
<cfset addressId = val(url.addressId)> <cfset addressId = val(url.addressId)>
<cfquery name="addr" datasource="payfrit"> <cfquery name="addr" datasource="payfrit">
SELECT Line1, Line2, City, ZIPCode SELECT AddressLine1, AddressLine2, AddressCity, AddressZIPCode
FROM Addresses WHERE ID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer"> FROM Addresses WHERE AddressID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer">
</cfquery> </cfquery>
<cfif addr.recordCount GT 0> <cfif addr.recordCount GT 0>
<cfset fullAddress = buildAddressString(addr.Line1, addr.Line2, addr.City, addr.ZIPCode)> <cfset fullAddress = buildAddressString(addr.AddressLine1, addr.AddressLine2, addr.AddressCity, addr.AddressZIPCode)>
<cfset geo = geocodeAddress(fullAddress)> <cfset geo = geocodeAddress(fullAddress)>
<cfif geo.success> <cfif geo.success>
<cfquery datasource="payfrit"> <cfquery datasource="payfrit">
UPDATE Addresses SET Latitude = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">, UPDATE Addresses SET AddressLat = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">,
Longitude = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal"> AddressLng = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal">
WHERE ID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer"> WHERE AddressID = <cfqueryparam value="#addressId#" cfsqltype="cf_sql_integer">
</cfquery> </cfquery>
<cfoutput><div class="success">Geocoded Address ID #addressId#: #geo.lat#, #geo.lng#</div></cfoutput> <cfoutput><div class="success">Geocoded Address ID #addressId#: #geo.lat#, #geo.lng#</div></cfoutput>
<cfelse> <cfelse>
@ -96,21 +96,21 @@ function buildAddressString(line1, line2, city, zipCode) {
<cfif structKeyExists(url, "geocodeAll")> <cfif structKeyExists(url, "geocodeAll")>
<cfquery name="missing" datasource="payfrit"> <cfquery name="missing" datasource="payfrit">
SELECT ID, Line1, Line2, City, ZIPCode SELECT AddressID, AddressLine1, AddressLine2, AddressCity, AddressZIPCode
FROM Addresses FROM Addresses
WHERE (Latitude IS NULL OR Latitude = 0) WHERE (AddressLat IS NULL OR AddressLat = 0)
AND Line1 IS NOT NULL AND Line1 != '' AND AddressLine1 IS NOT NULL AND AddressLine1 != ''
</cfquery> </cfquery>
<cfset successCount = 0> <cfset successCount = 0>
<cfset failCount = 0> <cfset failCount = 0>
<cfloop query="missing"> <cfloop query="missing">
<cfset fullAddress = buildAddressString(missing.Line1, missing.Line2, missing.City, missing.ZIPCode)> <cfset fullAddress = buildAddressString(missing.AddressLine1, missing.AddressLine2, missing.AddressCity, missing.AddressZIPCode)>
<cfset geo = geocodeAddress(fullAddress)> <cfset geo = geocodeAddress(fullAddress)>
<cfif geo.success> <cfif geo.success>
<cfquery datasource="payfrit"> <cfquery datasource="payfrit">
UPDATE Addresses SET Latitude = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">, UPDATE Addresses SET AddressLat = <cfqueryparam value="#geo.lat#" cfsqltype="cf_sql_decimal">,
Longitude = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal"> AddressLng = <cfqueryparam value="#geo.lng#" cfsqltype="cf_sql_decimal">
WHERE ID = <cfqueryparam value="#missing.ID#" cfsqltype="cf_sql_integer"> WHERE AddressID = <cfqueryparam value="#missing.AddressID#" cfsqltype="cf_sql_integer">
</cfquery> </cfquery>
<cfset successCount = successCount + 1> <cfset successCount = successCount + 1>
<cfelse> <cfelse>
@ -123,23 +123,23 @@ function buildAddressString(line1, line2, city, zipCode) {
<cfquery name="addresses" datasource="payfrit"> <cfquery name="addresses" datasource="payfrit">
SELECT SELECT
b.ID, b.BusinessID,
b.Name, b.BusinessName,
a.ID, a.AddressID,
a.Line1, a.AddressLine1,
a.Line2, a.AddressLine2,
a.City, a.AddressCity,
a.ZIPCode, a.AddressZIPCode,
a.Latitude, a.AddressLat,
a.Longitude a.AddressLng
FROM Businesses b FROM Businesses b
LEFT JOIN Addresses a ON b.AddressID = a.ID LEFT JOIN Addresses a ON b.BusinessAddressID = a.AddressID
ORDER BY b.Name ORDER BY b.BusinessName
</cfquery> </cfquery>
<cfset missingCount = 0> <cfset missingCount = 0>
<cfloop query="addresses"> <cfloop query="addresses">
<cfif (NOT len(addresses.Latitude) OR val(addresses.Latitude) EQ 0) AND len(addresses.Line1)> <cfif (NOT len(addresses.AddressLat) OR val(addresses.AddressLat) EQ 0) AND len(addresses.AddressLine1)>
<cfset missingCount = missingCount + 1> <cfset missingCount = missingCount + 1>
</cfif> </cfif>
</cfloop> </cfloop>
@ -166,31 +166,31 @@ function buildAddressString(line1, line2, city, zipCode) {
<cfloop query="addresses"> <cfloop query="addresses">
<tr> <tr>
<td> <td>
#addresses.Name# #addresses.BusinessName#
<cfif len(addresses.Latitude) AND val(addresses.Latitude) NEQ 0> <cfif len(addresses.AddressLat) AND val(addresses.AddressLat) NEQ 0>
<span class="has-coords">#chr(10003)#</span> <span class="has-coords">#chr(10003)#</span>
<cfelseif len(addresses.Line1)> <cfelseif len(addresses.AddressLine1)>
<span class="no-coords">#chr(9679)#</span> <span class="no-coords">#chr(9679)#</span>
</cfif> </cfif>
</td> </td>
<td class="address"> <td class="address">
<cfif len(addresses.Line1)> <cfif len(addresses.AddressLine1)>
#addresses.Line1#<cfif len(addresses.Line2)>, #addresses.Line2#</cfif><br> #addresses.AddressLine1#<cfif len(addresses.AddressLine2)>, #addresses.AddressLine2#</cfif><br>
#addresses.City# #addresses.ZIPCode# #addresses.AddressCity# #addresses.AddressZIPCode#
<cfelse> <cfelse>
<em style="color:##666;">No address</em> <em style="color:##666;">No address</em>
</cfif> </cfif>
</td> </td>
<td class="coords"> <td class="coords">
<cfif len(addresses.Latitude) AND val(addresses.Latitude) NEQ 0> <cfif len(addresses.AddressLat) AND val(addresses.AddressLat) NEQ 0>
#numberFormat(addresses.Latitude, "_.______")#<br> #numberFormat(addresses.AddressLat, "_.______")#<br>
#numberFormat(addresses.Longitude, "_.______")# #numberFormat(addresses.AddressLng, "_.______")#
<cfelse> <cfelse>
- -
</cfif> </cfif>
</td> </td>
<td> <td>
<cfif len(addresses.Line1) AND len(addresses.AddressID)> <cfif len(addresses.AddressLine1) AND len(addresses.AddressID)>
<a href="?geocode=1&addressId=#addresses.AddressID#"><button class="btn-lookup">Lookup</button></a> <a href="?geocode=1&addressId=#addresses.AddressID#"><button class="btn-lookup">Lookup</button></a>
</cfif> </cfif>
</td> </td>

View file

@ -29,7 +29,7 @@ try {
response["ERROR"] = "ChildBusinessID required"; response["ERROR"] = "ChildBusinessID required";
} else { } else {
queryExecute(" queryExecute("
UPDATE Businesses SET ParentBusinessID = :parentId WHERE ID = :childId UPDATE Businesses SET BusinessParentBusinessID = :parentId WHERE BusinessID = :childId
", { ", {
parentId: { value = parentID > 0 ? parentID : javaCast("null", ""), cfsqltype = "cf_sql_integer", null = parentID == 0 }, parentId: { value = parentID > 0 ? parentID : javaCast("null", ""), cfsqltype = "cf_sql_integer", null = parentID == 0 },
childId: childID childId: childID

View file

@ -26,13 +26,13 @@ try {
// Step 1: Get all parent items (burgers, combos, etc.) // Step 1: Get all parent items (burgers, combos, etc.)
qParentItems = queryExecute(" qParentItems = queryExecute("
SELECT i.ID, i.Name SELECT i.ItemID, i.ItemName
FROM Items i FROM Items i
INNER JOIN Categories c ON c.ID = i.CategoryID INNER JOIN Categories c ON c.CategoryID = i.ItemCategoryID
WHERE c.BusinessID = :businessID WHERE c.CategoryBusinessID = :businessID
AND i.ParentItemID = 0 AND i.ItemParentItemID = 0
AND i.IsActive = 1 AND i.ItemIsActive = 1
ORDER BY i.Name ORDER BY i.ItemName
", { businessID: businessID }, { datasource: "payfrit" }); ", { businessID: businessID }, { datasource: "payfrit" });
arrayAppend(response.steps, "Found " & qParentItems.recordCount & " parent items"); arrayAppend(response.steps, "Found " & qParentItems.recordCount & " parent items");
@ -41,20 +41,20 @@ try {
qModifiers = queryExecute(" qModifiers = queryExecute("
SELECT SELECT
m.ItemID, m.ItemID,
m.Name, m.ItemName,
m.ParentItemID, m.ItemParentItemID,
m.Price, m.ItemPrice,
m.IsCheckedByDefault, m.ItemIsCheckedByDefault,
m.SortOrder, m.ItemSortOrder,
p.Name as ParentName p.ItemName as ParentName
FROM Items m FROM Items m
INNER JOIN Items p ON p.ItemID = m.ParentItemID INNER JOIN Items p ON p.ItemID = m.ItemParentItemID
INNER JOIN Categories c ON c.ID = p.CategoryID INNER JOIN Categories c ON c.CategoryID = p.ItemCategoryID
WHERE c.BusinessID = :businessID WHERE c.CategoryBusinessID = :businessID
AND m.ParentItemID > 0 AND m.ItemParentItemID > 0
AND m.IsActive = 1 AND m.ItemIsActive = 1
AND p.ParentItemID = 0 AND p.ItemParentItemID = 0
ORDER BY m.Name, m.ItemID ORDER BY m.ItemName, m.ItemID
", { businessID: businessID }, { datasource: "payfrit" }); ", { businessID: businessID }, { datasource: "payfrit" });
arrayAppend(response.steps, "Found " & qModifiers.recordCount & " level-1 modifiers"); arrayAppend(response.steps, "Found " & qModifiers.recordCount & " level-1 modifiers");
@ -62,17 +62,17 @@ try {
// Step 3: Group modifiers by name to find duplicates // Step 3: Group modifiers by name to find duplicates
modifiersByName = {}; modifiersByName = {};
for (mod in qModifiers) { for (mod in qModifiers) {
modName = mod.Name; modName = mod.ItemName;
if (!structKeyExists(modifiersByName, modName)) { if (!structKeyExists(modifiersByName, modName)) {
modifiersByName[modName] = []; modifiersByName[modName] = [];
} }
arrayAppend(modifiersByName[modName], { arrayAppend(modifiersByName[modName], {
"ItemID": mod.ItemID, "ItemID": mod.ItemID,
"ParentItemID": mod.ParentItemID, "ParentItemID": mod.ItemParentItemID,
"ParentName": mod.ParentName, "ParentName": mod.ParentName,
"Price": mod.Price, "Price": mod.ItemPrice,
"IsDefault": mod.IsCheckedByDefault, "IsDefault": mod.ItemIsCheckedByDefault,
"SortOrder": mod.SortOrder "SortOrder": mod.ItemSortOrder
}); });
} }
@ -91,7 +91,7 @@ try {
// Mark as template // Mark as template
queryExecute(" queryExecute("
UPDATE Items SET IsModifierTemplate = 1 WHERE ItemID = :itemID UPDATE Items SET ItemIsModifierTemplate = 1 WHERE ItemID = :itemID
", { itemID: templateItemID }, { datasource: "payfrit" }); ", { itemID: templateItemID }, { datasource: "payfrit" });
templateMap[modName] = templateItemID; templateMap[modName] = templateItemID;
@ -111,7 +111,7 @@ try {
// Insert link (ignore duplicates) // Insert link (ignore duplicates)
try { try {
queryExecute(" queryExecute("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder) INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:itemID, :templateID, :sortOrder) VALUES (:itemID, :templateID, :sortOrder)
ON DUPLICATE KEY UPDATE SortOrder = :sortOrder ON DUPLICATE KEY UPDATE SortOrder = :sortOrder
", { ", {
@ -134,7 +134,7 @@ try {
if (i > 1) { if (i > 1) {
arrayAppend(orphanItems, { arrayAppend(orphanItems, {
"ItemID": inst.ItemID, "ItemID": inst.ItemID,
"Name": modName, "ItemName": modName,
"WasUnder": inst.ParentName "WasUnder": inst.ParentName
}); });
} }
@ -143,12 +143,12 @@ try {
// Single instance - still mark as template for consistency // Single instance - still mark as template for consistency
singleItem = instances[1]; singleItem = instances[1];
queryExecute(" queryExecute("
UPDATE Items SET IsModifierTemplate = 1 WHERE ItemID = :itemID UPDATE Items SET ItemIsModifierTemplate = 1 WHERE ItemID = :itemID
", { itemID: singleItem.ItemID }, { datasource: "payfrit" }); ", { itemID: singleItem.ItemID }, { datasource: "payfrit" });
// Create link // Create link
queryExecute(" queryExecute("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder) INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:itemID, :templateID, :sortOrder) VALUES (:itemID, :templateID, :sortOrder)
ON DUPLICATE KEY UPDATE SortOrder = :sortOrder ON DUPLICATE KEY UPDATE SortOrder = :sortOrder
", { ", {

View file

@ -27,20 +27,20 @@ try {
// Find all businesses with items in unified schema // Find all businesses with items in unified schema
if (len(businessFilter)) { if (len(businessFilter)) {
qBusinesses = queryExecute(" qBusinesses = queryExecute("
SELECT DISTINCT BusinessID as BusinessID SELECT DISTINCT ItemBusinessID as BusinessID
FROM Items FROM Items
WHERE BusinessID = :bid AND BusinessID > 0 WHERE ItemBusinessID = :bid AND ItemBusinessID > 0
", { bid: businessFilter }, { datasource: "payfrit" }); ", { bid: businessFilter }, { datasource: "payfrit" });
} else { } else {
qBusinesses = queryExecute(" qBusinesses = queryExecute("
SELECT DISTINCT BusinessID as BusinessID SELECT DISTINCT ItemBusinessID as BusinessID
FROM Items FROM Items
WHERE BusinessID > 0 WHERE ItemBusinessID > 0
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
} }
for (biz in qBusinesses) { for (biz in qBusinesses) {
bizId = biz.ID; bizId = biz.BusinessID;
bizResult = { "BusinessID": bizId, "CategoriesCreated": 0, "ItemsUpdated": 0 }; bizResult = { "BusinessID": bizId, "CategoriesCreated": 0, "ItemsUpdated": 0 };
try { try {
@ -48,26 +48,26 @@ try {
qCategoryItems = queryExecute(" qCategoryItems = queryExecute("
SELECT DISTINCT SELECT DISTINCT
p.ItemID, p.ItemID,
p.Name, p.ItemName,
p.SortOrder p.ItemSortOrder
FROM Items p FROM Items p
INNER JOIN Items c ON c.ParentItemID = p.ItemID INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.BusinessID = :bizId WHERE p.ItemBusinessID = :bizId
AND p.ParentItemID = 0 AND p.ItemParentItemID = 0
AND (p.IsCollapsible = 0 OR p.IsCollapsible IS NULL) AND (p.ItemIsCollapsible = 0 OR p.ItemIsCollapsible IS NULL)
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = p.ItemID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = p.ItemID
) )
ORDER BY p.SortOrder, p.Name ORDER BY p.ItemSortOrder, p.ItemName
", { bizId: bizId }, { datasource: "payfrit" }); ", { bizId: bizId }, { datasource: "payfrit" });
sortOrder = 0; sortOrder = 0;
for (catItem in qCategoryItems) { for (catItem in qCategoryItems) {
// Check if category already exists for this business with same name // Check if category already exists for this business with same name
qExisting = queryExecute(" qExisting = queryExecute("
SELECT ID FROM Categories SELECT CategoryID FROM Categories
WHERE BusinessID = :bizId AND Name = :name WHERE CategoryBusinessID = :bizId AND CategoryName = :name
", { bizId: bizId, name: left(catItem.Name, 30) }, { datasource: "payfrit" }); ", { bizId: bizId, name: left(catItem.ItemName, 30) }, { datasource: "payfrit" });
if (qExisting.recordCount == 0) { if (qExisting.recordCount == 0) {
// Get next CategoryID // Get next CategoryID
@ -79,12 +79,12 @@ try {
// Create new category with explicit ID // Create new category with explicit ID
queryExecute(" queryExecute("
INSERT INTO Categories INSERT INTO Categories
(CategoryID, BusinessID, ParentCategoryID, Name, SortOrder, AddedOn) (CategoryID, CategoryBusinessID, CategoryParentCategoryID, CategoryName, CategorySortOrder, CategoryAddedOn)
VALUES (:catId, :bizId, 0, :name, :sortOrder, NOW()) VALUES (:catId, :bizId, 0, :name, :sortOrder, NOW())
", { ", {
catId: newCatId, catId: newCatId,
bizId: bizId, bizId: bizId,
name: left(catItem.Name, 30), name: left(catItem.ItemName, 30),
sortOrder: sortOrder sortOrder: sortOrder
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
@ -96,9 +96,9 @@ try {
// Update all children of this category Item to have the new CategoryID // Update all children of this category Item to have the new CategoryID
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET CategoryID = :catId SET ItemCategoryID = :catId
WHERE ParentItemID = :parentId WHERE ItemParentItemID = :parentId
AND BusinessID = :bizId AND ItemBusinessID = :bizId
", { ", {
catId: newCatId, catId: newCatId,
parentId: catItem.ItemID, parentId: catItem.ItemID,

View file

@ -1,299 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="false">
<cfscript>
// Localhost-only protection
remoteAddr = cgi.REMOTE_ADDR;
if (remoteAddr != "127.0.0.1" && remoteAddr != "::1" && remoteAddr != "0:0:0:0:0:0:0:1" && remoteAddr != "10.10.0.2") {
writeOutput("Forbidden"); abort;
}
</cfscript>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>API Performance Dashboard</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0f1117; color: #e1e4e8; padding: 20px; }
h1 { font-size: 22px; font-weight: 600; margin-bottom: 16px; color: #fff; }
.controls { display: flex; gap: 10px; margin-bottom: 20px; align-items: center; flex-wrap: wrap; }
.controls label { font-size: 13px; color: #8b949e; }
.controls select, .controls input { background: #1c1f26; border: 1px solid #30363d; color: #e1e4e8; padding: 6px 10px; border-radius: 6px; font-size: 13px; }
.controls button { background: #238636; color: #fff; border: none; padding: 6px 16px; border-radius: 6px; font-size: 13px; cursor: pointer; font-weight: 500; }
.controls button:hover { background: #2ea043; }
.controls button.secondary { background: #30363d; }
.controls button.secondary:hover { background: #3d444d; }
.summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }
.card { background: #1c1f26; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
.card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.card .value { font-size: 28px; font-weight: 700; color: #fff; }
.card .unit { font-size: 13px; color: #8b949e; font-weight: 400; }
.section { margin-bottom: 28px; }
.section h2 { font-size: 15px; font-weight: 600; margin-bottom: 10px; color: #c9d1d9; display: flex; align-items: center; gap: 8px; }
.section h2 .badge { background: #30363d; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; color: #8b949e; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th { text-align: left; padding: 8px 12px; border-bottom: 1px solid #30363d; color: #8b949e; font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
thead th.num { text-align: right; }
tbody td { padding: 8px 12px; border-bottom: 1px solid #1c1f26; }
tbody td.num { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr:hover { background: #1c1f26; }
tbody td.endpoint { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: #58a6ff; }
.bar-cell { position: relative; }
.bar-bg { position: absolute; left: 0; top: 2px; bottom: 2px; border-radius: 3px; opacity: 0.15; }
.bar-db { background: #f0883e; }
.bar-app { background: #58a6ff; }
.bar-wrap { display: flex; height: 18px; border-radius: 3px; overflow: hidden; min-width: 80px; }
.bar-seg-db { background: #f0883e; height: 100%; }
.bar-seg-app { background: #58a6ff; height: 100%; }
.legend { display: flex; gap: 16px; font-size: 11px; color: #8b949e; margin-bottom: 12px; }
.legend span { display: flex; align-items: center; gap: 4px; }
.legend .dot { width: 10px; height: 10px; border-radius: 2px; }
.legend .dot.db { background: #f0883e; }
.legend .dot.app { background: #58a6ff; }
.status { padding: 8px 12px; background: #1c1f26; border-radius: 6px; font-size: 12px; color: #8b949e; margin-bottom: 16px; }
.status.error { border: 1px solid #da3633; color: #f85149; }
.status.loading { border: 1px solid #30363d; }
.tag { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 11px; font-weight: 500; }
.tag.fast { background: #23863620; color: #3fb950; }
.tag.ok { background: #d29b0020; color: #d29922; }
.tag.slow { background: #da363320; color: #f85149; }
.auto-refresh { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.auto-refresh input[type=checkbox] { accent-color: #238636; }
.timestamp { font-size: 11px; color: #484f58; }
</style>
</head>
<body>
<h1>API Performance Dashboard</h1>
<div class="controls">
<label>Time range:
<select id="hours">
<option value="1">Last 1 hour</option>
<option value="6">Last 6 hours</option>
<option value="24" selected>Last 24 hours</option>
<option value="72">Last 3 days</option>
<option value="168">Last 7 days</option>
<option value="720">Last 30 days</option>
</select>
</label>
<label>Rows:
<input type="number" id="limit" value="20" min="5" max="100" style="width:60px">
</label>
<button onclick="loadAll()">Refresh</button>
<div class="auto-refresh">
<input type="checkbox" id="autoRefresh">
<label for="autoRefresh" style="font-size:12px;color:#8b949e;cursor:pointer">Auto-refresh 30s</label>
</div>
<span class="timestamp" id="lastUpdated"></span>
</div>
<div id="status" class="status loading">Loading...</div>
<div id="summarySection" class="section" style="display:none">
<div class="summary-cards" id="summaryCards"></div>
</div>
<div class="legend">
<span><span class="dot db"></span> DB time</span>
<span><span class="dot app"></span> App time</span>
</div>
<div id="countSection" class="section" style="display:none">
<h2>Top Endpoints by Volume <span class="badge" id="countBadge"></span></h2>
<div style="overflow-x:auto"><table id="countTable"><thead></thead><tbody></tbody></table></div>
</div>
<div id="latencySection" class="section" style="display:none">
<h2>Top Endpoints by Latency <span class="badge" id="latencyBadge"></span></h2>
<div style="overflow-x:auto"><table id="latencyTable"><thead></thead><tbody></tbody></table></div>
</div>
<div id="slowSection" class="section" style="display:none">
<h2>Slowest Individual Requests <span class="badge" id="slowBadge"></span></h2>
<div style="overflow-x:auto"><table id="slowTable"><thead></thead><tbody></tbody></table></div>
</div>
<script>
const API = 'perf.cfm';
let refreshTimer = null;
function qs(s) { return document.querySelector(s); }
function fmt(n) { return n == null ? '-' : Number(n).toLocaleString(); }
function fmtMs(n) {
if (n == null) return '-';
n = Number(n);
if (n < 100) return '<span class="tag fast">' + n + 'ms</span>';
if (n < 500) return '<span class="tag ok">' + n + 'ms</span>';
return '<span class="tag slow">' + n + 'ms</span>';
}
function fmtBytes(n) {
if (!n) return '-';
n = Number(n);
if (n < 1024) return n + ' B';
if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
return (n / 1048576).toFixed(1) + ' MB';
}
function pct(part, total) { return total > 0 ? Math.round(part / total * 100) : 0; }
function timeBar(dbMs, appMs) {
var total = (dbMs || 0) + (appMs || 0);
if (total === 0) return '';
var dbPct = pct(dbMs, total);
var appPct = 100 - dbPct;
return '<div class="bar-wrap" title="DB: ' + dbMs + 'ms / App: ' + appMs + 'ms">'
+ '<div class="bar-seg-db" style="width:' + dbPct + '%"></div>'
+ '<div class="bar-seg-app" style="width:' + appPct + '%"></div>'
+ '</div>';
}
function endpointName(ep) {
return ep.replace(/^\/api\//, '');
}
async function fetchView(view) {
var h = qs('#hours').value;
var l = qs('#limit').value;
var r = await fetch(API + '?view=' + view + '&hours=' + h + '&limit=' + l);
return r.json();
}
async function loadAll() {
qs('#status').className = 'status loading';
qs('#status').textContent = 'Loading...';
qs('#status').style.display = '';
try {
var [summary, count, latency, slow] = await Promise.all([
fetchView('summary'), fetchView('count'), fetchView('latency'), fetchView('slow')
]);
if (!summary.OK) throw new Error(summary.MESSAGE || summary.ERROR);
qs('#status').style.display = 'none';
// Summary cards
var s = summary.DATA;
qs('#summaryCards').innerHTML =
card('Total Requests', fmt(s.TotalRequests), '') +
card('Unique Endpoints', fmt(s.UniqueEndpoints), '') +
card('Avg Latency', s.OverallAvgMs || 0, 'ms') +
card('Max Latency', s.OverallMaxMs || 0, 'ms') +
card('Avg DB Time', s.OverallAvgDbMs || 0, 'ms') +
card('Avg App Time', s.OverallAvgAppMs || 0, 'ms') +
card('Avg Queries', s.OverallAvgQueries || 0, '/req') +
card('Data Since', s.FirstLog || 'none', '');
qs('#summarySection').style.display = '';
// Count table
renderCountTable(count.DATA || []);
// Latency table
renderLatencyTable(latency.DATA || []);
// Slow table
renderSlowTable(slow.DATA || []);
qs('#lastUpdated').textContent = 'Updated ' + new Date().toLocaleTimeString();
} catch (e) {
qs('#status').className = 'status error';
qs('#status').textContent = 'Error: ' + e.message;
qs('#status').style.display = '';
}
}
function card(label, value, unit) {
return '<div class="card"><div class="label">' + label + '</div><div class="value">' + value + ' <span class="unit">' + unit + '</span></div></div>';
}
function renderCountTable(data) {
if (!data.length) { qs('#countSection').style.display = 'none'; return; }
qs('#countBadge').textContent = data.length + ' endpoints';
var maxCalls = Math.max(...data.map(r => r.Calls));
var html = '<thead><tr><th>Endpoint</th><th class="num">Calls</th><th class="num">Avg</th><th class="num">DB</th><th class="num">App</th><th>DB / App Split</th><th class="num">Max</th><th class="num">Queries</th><th class="num">Avg Size</th></tr></thead><tbody>';
data.forEach(function(r) {
html += '<tr>'
+ '<td class="endpoint">' + endpointName(r.Endpoint) + '</td>'
+ '<td class="num">' + fmt(r.Calls) + '</td>'
+ '<td class="num">' + fmtMs(r.AvgMs) + '</td>'
+ '<td class="num">' + fmt(r.AvgDbMs) + 'ms</td>'
+ '<td class="num">' + fmt(r.AvgAppMs) + 'ms</td>'
+ '<td>' + timeBar(r.AvgDbMs, r.AvgAppMs) + '</td>'
+ '<td class="num">' + fmtMs(r.MaxMs) + '</td>'
+ '<td class="num">' + r.AvgQueries + '</td>'
+ '<td class="num">' + fmtBytes(r.AvgBytes) + '</td>'
+ '</tr>';
});
html += '</tbody>';
qs('#countTable').innerHTML = html;
qs('#countSection').style.display = '';
}
function renderLatencyTable(data) {
if (!data.length) { qs('#latencySection').style.display = 'none'; return; }
qs('#latencyBadge').textContent = data.length + ' endpoints';
var html = '<thead><tr><th>Endpoint</th><th class="num">Calls</th><th class="num">Avg</th><th class="num">DB</th><th class="num">App</th><th>DB / App Split</th><th class="num">Max</th><th class="num">Queries</th></tr></thead><tbody>';
data.forEach(function(r) {
html += '<tr>'
+ '<td class="endpoint">' + endpointName(r.Endpoint) + '</td>'
+ '<td class="num">' + fmt(r.Calls) + '</td>'
+ '<td class="num">' + fmtMs(r.AvgMs) + '</td>'
+ '<td class="num">' + fmt(r.AvgDbMs) + 'ms</td>'
+ '<td class="num">' + fmt(r.AvgAppMs) + 'ms</td>'
+ '<td>' + timeBar(r.AvgDbMs, r.AvgAppMs) + '</td>'
+ '<td class="num">' + fmtMs(r.MaxMs) + '</td>'
+ '<td class="num">' + r.AvgQueries + '</td>'
+ '</tr>';
});
html += '</tbody>';
qs('#latencyTable').innerHTML = html;
qs('#latencySection').style.display = '';
}
function renderSlowTable(data) {
if (!data.length) { qs('#slowSection').style.display = 'none'; return; }
qs('#slowBadge').textContent = data.length + ' requests';
var html = '<thead><tr><th>Endpoint</th><th class="num">Total</th><th class="num">DB</th><th class="num">App</th><th>DB / App Split</th><th class="num">Queries</th><th class="num">Size</th><th class="num">Biz</th><th>When</th></tr></thead><tbody>';
data.forEach(function(r) {
html += '<tr>'
+ '<td class="endpoint">' + endpointName(r.Endpoint) + '</td>'
+ '<td class="num">' + fmtMs(r.TotalMs) + '</td>'
+ '<td class="num">' + fmt(r.DbMs) + 'ms</td>'
+ '<td class="num">' + fmt(r.AppMs) + 'ms</td>'
+ '<td>' + timeBar(r.DbMs, r.AppMs) + '</td>'
+ '<td class="num">' + r.QueryCount + '</td>'
+ '<td class="num">' + fmtBytes(r.ResponseBytes) + '</td>'
+ '<td class="num">' + (r.BusinessID || '-') + '</td>'
+ '<td style="font-size:11px;color:#8b949e;white-space:nowrap">' + (r.LoggedAt || '') + '</td>'
+ '</tr>';
});
html += '</tbody>';
qs('#slowTable').innerHTML = html;
qs('#slowSection').style.display = '';
}
// Auto-refresh
qs('#autoRefresh').addEventListener('change', function() {
if (this.checked) {
refreshTimer = setInterval(loadAll, 30000);
} else {
clearInterval(refreshTimer);
refreshTimer = null;
}
});
// Load on page open
loadAll();
</script>
</body>
</html>

View file

@ -1,179 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
function apiAbort(payload) {
writeOutput(serializeJSON(payload));
abort;
}
// Localhost-only protection
remoteAddr = cgi.REMOTE_ADDR;
if (remoteAddr != "127.0.0.1" && remoteAddr != "::1" && remoteAddr != "0:0:0:0:0:0:0:1" && remoteAddr != "10.10.0.2") {
apiAbort({ "OK": false, "ERROR": "forbidden" });
}
try {
// Parse parameters
view = structKeyExists(url, "view") ? lcase(url.view) : "count";
hours = structKeyExists(url, "hours") ? val(url.hours) : 24;
if (hours <= 0 || hours > 720) hours = 24;
limitRows = structKeyExists(url, "limit") ? val(url.limit) : 20;
if (limitRows <= 0 || limitRows > 100) limitRows = 20;
// Flush any buffered metrics first
flushPerfBuffer();
response = { "OK": true, "VIEW": view, "HOURS": hours };
if (view == "count") {
// Top endpoints by call count
q = queryExecute("
SELECT
Endpoint,
COUNT(*) as Calls,
ROUND(AVG(TotalMs)) as AvgMs,
ROUND(AVG(DbMs)) as AvgDbMs,
ROUND(AVG(AppMs)) as AvgAppMs,
MAX(TotalMs) as MaxMs,
ROUND(AVG(QueryCount), 1) as AvgQueries,
ROUND(AVG(ResponseBytes)) as AvgBytes
FROM ApiPerfLogs
WHERE LoggedAt > DATE_SUB(NOW(), INTERVAL :hours HOUR)
GROUP BY Endpoint
ORDER BY Calls DESC
LIMIT :lim
", {
hours: { value: hours, cfsqltype: "cf_sql_integer" },
lim: { value: limitRows, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
rows = [];
for (row in q) {
arrayAppend(rows, {
"Endpoint": row.Endpoint,
"Calls": row.Calls,
"AvgMs": row.AvgMs,
"AvgDbMs": row.AvgDbMs,
"AvgAppMs": row.AvgAppMs,
"MaxMs": row.MaxMs,
"AvgQueries": row.AvgQueries,
"AvgBytes": row.AvgBytes
});
}
response["DATA"] = rows;
} else if (view == "latency") {
// Top endpoints by average latency
q = queryExecute("
SELECT
Endpoint,
COUNT(*) as Calls,
ROUND(AVG(TotalMs)) as AvgMs,
ROUND(AVG(DbMs)) as AvgDbMs,
ROUND(AVG(AppMs)) as AvgAppMs,
MAX(TotalMs) as MaxMs,
ROUND(AVG(QueryCount), 1) as AvgQueries
FROM ApiPerfLogs
WHERE LoggedAt > DATE_SUB(NOW(), INTERVAL :hours HOUR)
GROUP BY Endpoint
HAVING Calls >= 3
ORDER BY AvgMs DESC
LIMIT :lim
", {
hours: { value: hours, cfsqltype: "cf_sql_integer" },
lim: { value: limitRows, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
rows = [];
for (row in q) {
arrayAppend(rows, {
"Endpoint": row.Endpoint,
"Calls": row.Calls,
"AvgMs": row.AvgMs,
"AvgDbMs": row.AvgDbMs,
"AvgAppMs": row.AvgAppMs,
"MaxMs": row.MaxMs,
"AvgQueries": row.AvgQueries
});
}
response["DATA"] = rows;
} else if (view == "slow") {
// Slowest individual requests
q = queryExecute("
SELECT Endpoint, TotalMs, DbMs, AppMs, QueryCount, ResponseBytes,
BusinessID, UserID, LoggedAt
FROM ApiPerfLogs
WHERE LoggedAt > DATE_SUB(NOW(), INTERVAL :hours HOUR)
ORDER BY TotalMs DESC
LIMIT :lim
", {
hours: { value: hours, cfsqltype: "cf_sql_integer" },
lim: { value: limitRows, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
rows = [];
for (row in q) {
arrayAppend(rows, {
"Endpoint": row.Endpoint,
"TotalMs": row.TotalMs,
"DbMs": row.DbMs,
"AppMs": row.AppMs,
"QueryCount": row.QueryCount,
"ResponseBytes": row.ResponseBytes,
"BusinessID": row.BusinessID,
"UserID": row.UserID,
"LoggedAt": dateTimeFormat(row.LoggedAt, "yyyy-mm-dd HH:nn:ss")
});
}
response["DATA"] = rows;
} else if (view == "summary") {
// Overall summary stats
q = queryExecute("
SELECT
COUNT(*) as TotalRequests,
COUNT(DISTINCT Endpoint) as UniqueEndpoints,
ROUND(AVG(TotalMs)) as OverallAvgMs,
MAX(TotalMs) as OverallMaxMs,
ROUND(AVG(DbMs)) as OverallAvgDbMs,
ROUND(AVG(AppMs)) as OverallAvgAppMs,
ROUND(AVG(QueryCount), 1) as OverallAvgQueries,
MIN(LoggedAt) as FirstLog,
MAX(LoggedAt) as LastLog
FROM ApiPerfLogs
WHERE LoggedAt > DATE_SUB(NOW(), INTERVAL :hours HOUR)
", {
hours: { value: hours, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
response["DATA"] = {
"TotalRequests": q.TotalRequests,
"UniqueEndpoints": q.UniqueEndpoints,
"OverallAvgMs": q.OverallAvgMs,
"OverallMaxMs": q.OverallMaxMs,
"OverallAvgDbMs": q.OverallAvgDbMs,
"OverallAvgAppMs": q.OverallAvgAppMs,
"OverallAvgQueries": q.OverallAvgQueries,
"FirstLog": isDate(q.FirstLog) ? dateTimeFormat(q.FirstLog, "yyyy-mm-dd HH:nn:ss") : "",
"LastLog": isDate(q.LastLog) ? dateTimeFormat(q.LastLog, "yyyy-mm-dd HH:nn:ss") : ""
};
} else {
apiAbort({ "OK": false, "ERROR": "invalid_view", "MESSAGE": "Use ?view=count|latency|slow|summary" });
}
apiAbort(response);
} catch (any e) {
apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message,
"DETAIL": e.detail
});
}
</cfscript>

View file

@ -1,37 +0,0 @@
<cfsetting showdebugoutput="false">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Add TaskTypeID column to QuickTaskTemplates table if it doesn't exist
try {
// Check if column exists
qCheck = queryExecute("
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'QuickTaskTemplates'
AND COLUMN_NAME = 'TaskTypeID'
", [], { datasource: "payfrit" });
if (qCheck.recordCount == 0) {
queryExecute("
ALTER TABLE QuickTaskTemplates
ADD COLUMN TaskTypeID INT NULL AFTER TaskCategoryID
", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column TaskTypeID added to QuickTaskTemplates"
}));
} else {
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Column already exists"
}));
}
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -1,21 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// One-time cleanup: delete test tasks and reset
try {
// Delete tasks 30, 31, 32 (test tasks with bad data)
queryExecute("DELETE FROM Tasks WHERE ID IN (30, 31, 32)", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Cleanup complete - deleted test tasks"
}));
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -50,13 +50,14 @@ try {
// Get template details // Get template details
qTemplate = queryExecute(" qTemplate = queryExecute("
SELECT SELECT
Title as Title, QuickTaskTemplateTitle as Title,
Details as Details, QuickTaskTemplateDetails as Details,
TaskCategoryID as CategoryID QuickTaskTemplateCategoryID as CategoryID,
QuickTaskTemplateTypeID as TypeID
FROM QuickTaskTemplates FROM QuickTaskTemplates
WHERE ID = :id WHERE QuickTaskTemplateID = :id
AND BusinessID = :businessID AND QuickTaskTemplateBusinessID = :businessID
AND IsActive = 1 AND QuickTaskTemplateIsActive = 1
", { ", {
id: { value: templateID, cfsqltype: "cf_sql_integer" }, id: { value: templateID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -66,21 +67,24 @@ try {
apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Template not found" }); apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Template not found" });
} }
// Create the task (ClaimedByUserID=0 means unclaimed/pending) // Create the task
queryExecute(" queryExecute("
INSERT INTO Tasks ( INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID, TaskBusinessID, TaskCategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES ( ) VALUES (
:businessID, :categoryID, :typeID, :businessID, :categoryID, :typeID,
:title, :details, NOW(), 0 :title, :details, 0, NOW(),
'quicktask', :templateID
) )
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: qTemplate.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(qTemplate.CategoryID) }, categoryID: { value: qTemplate.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(qTemplate.CategoryID) },
typeID: { value: 0, cfsqltype: "cf_sql_integer" }, typeID: { value: qTemplate.TypeID, cfsqltype: "cf_sql_integer", null: isNull(qTemplate.TypeID) },
title: { value: qTemplate.Title, cfsqltype: "cf_sql_varchar" }, title: { value: qTemplate.Title, cfsqltype: "cf_sql_varchar" },
details: { value: qTemplate.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(qTemplate.Details) } details: { value: qTemplate.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(qTemplate.Details) },
templateID: { value: templateID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });

View file

@ -1,39 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
try {
q = queryExecute("
SELECT ID, Title, Details, TaskCategoryID, ClaimedByUserID, CompletedOn, CreatedOn
FROM Tasks
WHERE BusinessID = 47
ORDER BY ID DESC
LIMIT 20
", [], { datasource: "payfrit" });
tasks = [];
for (row in q) {
arrayAppend(tasks, {
"TaskID": row.ID,
"Title": row.Title,
"Details": isNull(row.Details) ? "" : row.Details,
"CategoryID": row.ID,
"ClaimedByUserID": row.ClaimedByUserID,
"CompletedOn": isNull(row.CompletedOn) ? "" : dateTimeFormat(row.CompletedOn, "yyyy-mm-dd HH:nn:ss"),
"AddedOn": isNull(row.CreatedOn) ? "" : dateTimeFormat(row.CreatedOn, "yyyy-mm-dd HH:nn:ss")
});
}
writeOutput(serializeJSON({
"OK": true,
"COUNT": arrayLen(tasks),
"TASKS": tasks
}));
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -50,7 +50,7 @@ try {
// Verify template exists and belongs to this business // Verify template exists and belongs to this business
qCheck = queryExecute(" qCheck = queryExecute("
SELECT QuickTaskTemplateID FROM QuickTaskTemplates SELECT QuickTaskTemplateID FROM QuickTaskTemplates
WHERE ID = :id AND BusinessID = :businessID WHERE QuickTaskTemplateID = :id AND QuickTaskTemplateBusinessID = :businessID
", { ", {
id: { value: templateID, cfsqltype: "cf_sql_integer" }, id: { value: templateID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -62,8 +62,8 @@ try {
// Soft delete by setting IsActive to 0 // Soft delete by setting IsActive to 0
queryExecute(" queryExecute("
UPDATE QuickTaskTemplates SET IsActive = 0 UPDATE QuickTaskTemplates SET QuickTaskTemplateIsActive = 0
WHERE ID = :id WHERE QuickTaskTemplateID = :id
", { ", {
id: { value: templateID, cfsqltype: "cf_sql_integer" } id: { value: templateID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });

View file

@ -45,22 +45,23 @@ try {
// Get quick task templates for this business // Get quick task templates for this business
q = queryExecute(" q = queryExecute("
SELECT SELECT
qt.ID, qt.QuickTaskTemplateID,
qt.Name as Name, qt.QuickTaskTemplateName as Name,
qt.TaskCategoryID as CategoryID, qt.QuickTaskTemplateCategoryID as CategoryID,
qt.Title as Title, qt.QuickTaskTemplateTypeID as TypeID,
qt.Details as Details, qt.QuickTaskTemplateTitle as Title,
qt.Icon as Icon, qt.QuickTaskTemplateDetails as Details,
qt.Color as Color, qt.QuickTaskTemplateIcon as Icon,
qt.SortOrder as SortOrder, qt.QuickTaskTemplateColor as Color,
qt.IsActive as IsActive, qt.QuickTaskTemplateSortOrder as SortOrder,
tc.Name as Name, qt.QuickTaskTemplateIsActive as IsActive,
tc.Color as CategoryColor tc.TaskCategoryName as CategoryName,
tc.TaskCategoryColor as CategoryColor
FROM QuickTaskTemplates qt FROM QuickTaskTemplates qt
LEFT JOIN TaskCategories tc ON qt.TaskCategoryID = tc.ID LEFT JOIN TaskCategories tc ON qt.QuickTaskTemplateCategoryID = tc.TaskCategoryID
WHERE qt.BusinessID = :businessID WHERE qt.QuickTaskTemplateBusinessID = :businessID
AND qt.IsActive = 1 AND qt.QuickTaskTemplateIsActive = 1
ORDER BY qt.SortOrder, qt.ID ORDER BY qt.QuickTaskTemplateSortOrder, qt.QuickTaskTemplateID
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
@ -68,16 +69,17 @@ try {
templates = []; templates = [];
for (row in q) { for (row in q) {
arrayAppend(templates, { arrayAppend(templates, {
"QuickTaskTemplateID": row.ID, "QuickTaskTemplateID": row.QuickTaskTemplateID,
"Name": row.Name, "Name": row.Name,
"CategoryID": isNull(row.CategoryID) ? "" : row.CategoryID, "CategoryID": isNull(row.CategoryID) ? "" : row.CategoryID,
"TypeID": isNull(row.TypeID) ? "" : row.TypeID,
"Title": row.Title, "Title": row.Title,
"Details": isNull(row.Details) ? "" : row.Details, "Details": isNull(row.Details) ? "" : row.Details,
"Icon": isNull(row.Icon) ? "add_box" : row.Icon, "Icon": isNull(row.Icon) ? "add_box" : row.Icon,
"Color": isNull(row.Color) ? "##6366f1" : row.Color, "Color": isNull(row.Color) ? "##6366f1" : row.Color,
"SortOrder": row.SortOrder, "SortOrder": row.SortOrder,
"IsActive": row.IsActive, "IsActive": row.IsActive,
"Name": isNull(row.Name) ? "" : row.Name, "CategoryName": isNull(row.CategoryName) ? "" : row.CategoryName,
"CategoryColor": isNull(row.CategoryColor) ? "" : row.CategoryColor "CategoryColor": isNull(row.CategoryColor) ? "" : row.CategoryColor
}); });
} }

View file

@ -1,20 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
try {
// Delete all Quick Task templates for business 1
queryExecute("DELETE FROM QuickTaskTemplates WHERE BusinessID = 1", [], { datasource: "payfrit" });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "All Quick Task templates purged"
}));
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": e.message
}));
}
</cfscript>

View file

@ -5,7 +5,7 @@
<cfscript> <cfscript>
// Create or update a quick task template // Create or update a quick task template
// Input: BusinessID (required), QuickTaskTemplateID (optional - for update), // Input: BusinessID (required), QuickTaskTemplateID (optional - for update),
// Name, Title, Details, CategoryID, Icon, Color // Name, Title, Details, CategoryID, TypeID, Icon, Color
// Output: { OK: true, TEMPLATE_ID: int } // Output: { OK: true, TEMPLATE_ID: int }
function apiAbort(required struct payload) { function apiAbort(required struct payload) {
@ -46,12 +46,8 @@ try {
templateName = structKeyExists(data, "Name") ? trim(toString(data.Name)) : ""; templateName = structKeyExists(data, "Name") ? trim(toString(data.Name)) : "";
templateTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : ""; templateTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : "";
templateDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : ""; templateDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : "";
hasCategory = false; categoryID = structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0 ? int(data.CategoryID) : javaCast("null", "");
catID = 0; typeID = structKeyExists(data, "TypeID") && isNumeric(data.TypeID) && data.TypeID > 0 ? int(data.TypeID) : javaCast("null", "");
if (structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0) {
catID = int(data.CategoryID);
hasCategory = true;
}
templateIcon = structKeyExists(data, "Icon") && len(trim(data.Icon)) ? trim(toString(data.Icon)) : "add_box"; templateIcon = structKeyExists(data, "Icon") && len(trim(data.Icon)) ? trim(toString(data.Icon)) : "add_box";
templateColor = structKeyExists(data, "Color") && len(trim(data.Color)) ? trim(toString(data.Color)) : "##6366f1"; templateColor = structKeyExists(data, "Color") && len(trim(data.Color)) ? trim(toString(data.Color)) : "##6366f1";
@ -62,15 +58,12 @@ try {
if (!len(templateTitle)) { if (!len(templateTitle)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Title is required" }); apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Title is required" });
} }
if (!hasCategory) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Please select a category" });
}
if (templateID > 0) { if (templateID > 0) {
// UPDATE existing template // UPDATE existing template
qCheck = queryExecute(" qCheck = queryExecute("
SELECT QuickTaskTemplateID FROM QuickTaskTemplates SELECT QuickTaskTemplateID FROM QuickTaskTemplates
WHERE ID = :id AND BusinessID = :businessID WHERE QuickTaskTemplateID = :id AND QuickTaskTemplateBusinessID = :businessID
", { ", {
id: { value: templateID, cfsqltype: "cf_sql_integer" }, id: { value: templateID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -82,18 +75,20 @@ try {
queryExecute(" queryExecute("
UPDATE QuickTaskTemplates SET UPDATE QuickTaskTemplates SET
Name = :name, QuickTaskTemplateName = :name,
Title = :title, QuickTaskTemplateTitle = :title,
Details = :details, QuickTaskTemplateDetails = :details,
TaskCategoryID = :categoryID, QuickTaskTemplateCategoryID = :categoryID,
Icon = :icon, QuickTaskTemplateTypeID = :typeID,
Color = :color QuickTaskTemplateIcon = :icon,
WHERE ID = :id QuickTaskTemplateColor = :color
WHERE QuickTaskTemplateID = :id
", { ", {
name: { value: templateName, cfsqltype: "cf_sql_varchar" }, name: { value: templateName, cfsqltype: "cf_sql_varchar" },
title: { value: templateTitle, cfsqltype: "cf_sql_varchar" }, title: { value: templateTitle, cfsqltype: "cf_sql_varchar" },
details: { value: templateDetails, cfsqltype: "cf_sql_longvarchar", null: !len(templateDetails) }, details: { value: templateDetails, cfsqltype: "cf_sql_longvarchar", null: !len(templateDetails) },
categoryID: { value: catID, cfsqltype: "cf_sql_integer", null: !hasCategory }, categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) },
typeID: { value: typeID, cfsqltype: "cf_sql_integer", null: isNull(typeID) },
icon: { value: templateIcon, cfsqltype: "cf_sql_varchar" }, icon: { value: templateIcon, cfsqltype: "cf_sql_varchar" },
color: { value: templateColor, cfsqltype: "cf_sql_varchar" }, color: { value: templateColor, cfsqltype: "cf_sql_varchar" },
id: { value: templateID, cfsqltype: "cf_sql_integer" } id: { value: templateID, cfsqltype: "cf_sql_integer" }
@ -109,8 +104,8 @@ try {
// INSERT new template // INSERT new template
// Get next sort order // Get next sort order
qSort = queryExecute(" qSort = queryExecute("
SELECT COALESCE(MAX(SortOrder), 0) + 1 as nextSort SELECT COALESCE(MAX(QuickTaskTemplateSortOrder), 0) + 1 as nextSort
FROM QuickTaskTemplates WHERE BusinessID = :businessID FROM QuickTaskTemplates WHERE QuickTaskTemplateBusinessID = :businessID
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
@ -119,18 +114,19 @@ try {
queryExecute(" queryExecute("
INSERT INTO QuickTaskTemplates ( INSERT INTO QuickTaskTemplates (
BusinessID, Name, Title, QuickTaskTemplateBusinessID, QuickTaskTemplateName, QuickTaskTemplateTitle,
Details, TaskCategoryID, QuickTaskTemplateDetails, QuickTaskTemplateCategoryID, QuickTaskTemplateTypeID,
Icon, Color, SortOrder QuickTaskTemplateIcon, QuickTaskTemplateColor, QuickTaskTemplateSortOrder
) VALUES ( ) VALUES (
:businessID, :name, :title, :details, :categoryID, :icon, :color, :sortOrder :businessID, :name, :title, :details, :categoryID, :typeID, :icon, :color, :sortOrder
) )
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
name: { value: templateName, cfsqltype: "cf_sql_varchar" }, name: { value: templateName, cfsqltype: "cf_sql_varchar" },
title: { value: templateTitle, cfsqltype: "cf_sql_varchar" }, title: { value: templateTitle, cfsqltype: "cf_sql_varchar" },
details: { value: templateDetails, cfsqltype: "cf_sql_longvarchar", null: !len(templateDetails) }, details: { value: templateDetails, cfsqltype: "cf_sql_longvarchar", null: !len(templateDetails) },
categoryID: { value: catID, cfsqltype: "cf_sql_integer", null: !hasCategory }, categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) },
typeID: { value: typeID, cfsqltype: "cf_sql_integer", null: isNull(typeID) },
icon: { value: templateIcon, cfsqltype: "cf_sql_varchar" }, icon: { value: templateIcon, cfsqltype: "cf_sql_varchar" },
color: { value: templateColor, cfsqltype: "cf_sql_varchar" }, color: { value: templateColor, cfsqltype: "cf_sql_varchar" },
sortOrder: { value: nextSort, cfsqltype: "cf_sql_integer" } sortOrder: { value: nextSort, cfsqltype: "cf_sql_integer" }
@ -149,10 +145,7 @@ try {
apiAbort({ apiAbort({
"OK": false, "OK": false,
"ERROR": "server_error", "ERROR": "server_error",
"MESSAGE": e.message, "MESSAGE": e.message
"DETAIL": structKeyExists(e, "detail") ? e.detail : "none",
"TYPE": structKeyExists(e, "type") ? e.type : "none",
"TAG": (structKeyExists(e, "tagContext") && isArray(e.tagContext) && arrayLen(e.tagContext) > 0) ? serializeJSON(e.tagContext[1]) : "none"
}); });
} }
</cfscript> </cfscript>

View file

@ -15,20 +15,20 @@ try {
// Create QuickTaskTemplates table // Create QuickTaskTemplates table
queryExecute(" queryExecute("
CREATE TABLE IF NOT EXISTS QuickTaskTemplates ( CREATE TABLE IF NOT EXISTS QuickTaskTemplates (
ID INT AUTO_INCREMENT PRIMARY KEY, QuickTaskTemplateID INT AUTO_INCREMENT PRIMARY KEY,
BusinessID INT NOT NULL, QuickTaskTemplateBusinessID INT NOT NULL,
Name VARCHAR(100) NOT NULL, QuickTaskTemplateName VARCHAR(100) NOT NULL,
TaskCategoryID INT NULL, QuickTaskTemplateCategoryID INT NULL,
TaskTypeID INT NULL, QuickTaskTemplateTypeID INT NULL,
Title VARCHAR(255) NOT NULL, QuickTaskTemplateTitle VARCHAR(255) NOT NULL,
Details TEXT NULL, QuickTaskTemplateDetails TEXT NULL,
Icon VARCHAR(30) DEFAULT 'add_box', QuickTaskTemplateIcon VARCHAR(30) DEFAULT 'add_box',
Color VARCHAR(20) DEFAULT '##6366f1', QuickTaskTemplateColor VARCHAR(20) DEFAULT '##6366f1',
SortOrder INT DEFAULT 0, QuickTaskTemplateSortOrder INT DEFAULT 0,
IsActive BIT(1) DEFAULT b'1', QuickTaskTemplateIsActive BIT(1) DEFAULT b'1',
CreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP, QuickTaskTemplateCreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_business_active (BusinessID, IsActive), INDEX idx_business_active (QuickTaskTemplateBusinessID, QuickTaskTemplateIsActive),
INDEX idx_sort (BusinessID, SortOrder) INDEX idx_sort (QuickTaskTemplateBusinessID, QuickTaskTemplateSortOrder)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });

View file

@ -5,42 +5,42 @@
<cfscript> <cfscript>
businessId = 27; // Big Dean's businessId = 27; // Big Dean's
// Categories are items with ParentItemID=0 AND IsCollapsible=0 // Categories are items with ItemParentItemID=0 AND ItemIsCollapsible=0
// Modifier templates are items with ParentItemID=0 AND IsCollapsible=1 // Modifier templates are items with ItemParentItemID=0 AND ItemIsCollapsible=1
// Menu items are children of categories // Menu items are children of categories
// Modifiers are children of menu items or modifier templates // Modifiers are children of menu items or modifier templates
// Get category IDs (NOT modifier templates) // Get category IDs (NOT modifier templates)
categoryIds = queryExecute(" categoryIds = queryExecute("
SELECT ID SELECT ItemID
FROM Items FROM Items
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND ParentItemID = 0 AND ItemParentItemID = 0
AND IsCollapsible = 0 AND ItemIsCollapsible = 0
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId }, { datasource: "payfrit" });
catIdList = ""; catIdList = "";
for (cat in categoryIds) { for (cat in categoryIds) {
catIdList = listAppend(catIdList, cat.ID); catIdList = listAppend(catIdList, cat.ItemID);
} }
// Now get actual menu items (direct children of categories) // Now get actual menu items (direct children of categories)
// Exclude items that are template options (their parent is a collapsible modifier group) // Exclude items that are template options (their parent is a collapsible modifier group)
items = queryExecute(" items = queryExecute("
SELECT i.ID, i.Name SELECT i.ItemID, i.ItemName
FROM Items i FROM Items i
WHERE i.BusinessID = :bizId WHERE i.ItemBusinessID = :bizId
AND i.ParentItemID IN (#catIdList#) AND i.ItemParentItemID IN (#catIdList#)
AND i.IsCollapsible = 0 AND i.ItemIsCollapsible = 0
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID
) )
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId }, { datasource: "payfrit" });
updated = []; updated = [];
for (item in items) { for (item in items) {
itemName = lcase(item.Name); itemName = lcase(item.ItemName);
newPrice = 0; newPrice = 0;
// Drinks - $3-6 // Drinks - $3-6
@ -99,13 +99,13 @@ for (item in items) {
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET Price = :price SET ItemPrice = :price
WHERE ItemID = :itemId WHERE ItemID = :itemId
", { price: newPrice, itemId: item.ID }, { datasource: "payfrit" }); ", { price: newPrice, itemId: item.ItemID }, { datasource: "payfrit" });
arrayAppend(updated, { arrayAppend(updated, {
"ItemID": item.ID, "ItemID": item.ItemID,
"Name": item.Name, "ItemName": item.ItemName,
"NewPrice": newPrice "NewPrice": newPrice
}); });
} }
@ -113,15 +113,15 @@ for (item in items) {
// Update modifier prices (children of menu items, NOT direct children of categories) // Update modifier prices (children of menu items, NOT direct children of categories)
// Modifiers are items whose parent is NOT a category (i.e., parent is a menu item or modifier group) // Modifiers are items whose parent is NOT a category (i.e., parent is a menu item or modifier group)
modifiers = queryExecute(" modifiers = queryExecute("
SELECT ID, Name SELECT ItemID, ItemName
FROM Items FROM Items
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND ParentItemID > 0 AND ItemParentItemID > 0
AND ParentItemID NOT IN (#catIdList#) AND ItemParentItemID NOT IN (#catIdList#)
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId }, { datasource: "payfrit" });
for (mod in modifiers) { for (mod in modifiers) {
modName = lcase(mod.Name); modName = lcase(mod.ItemName);
modPrice = 0; modPrice = 0;
// Proteins are expensive add-ons // Proteins are expensive add-ons
@ -150,7 +150,7 @@ for (mod in modifiers) {
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET Price = :price SET ItemPrice = :price
WHERE ItemID = :itemId WHERE ItemID = :itemId
", { price: modPrice, itemId: mod.ItemID }, { datasource: "payfrit" }); ", { price: modPrice, itemId: mod.ItemID }, { datasource: "payfrit" });
} }
@ -158,17 +158,17 @@ for (mod in modifiers) {
// Reset category prices to $0 (shouldn't have prices for reporting) // Reset category prices to $0 (shouldn't have prices for reporting)
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET Price = 0 SET ItemPrice = 0
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND ParentItemID = 0 AND ItemParentItemID = 0
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId }, { datasource: "payfrit" });
// Reset modifier group prices to $0 (only options have prices) // Reset modifier group prices to $0 (only options have prices)
queryExecute(" queryExecute("
UPDATE Items UPDATE Items
SET Price = 0 SET ItemPrice = 0
WHERE BusinessID = :bizId WHERE ItemBusinessID = :bizId
AND IsCollapsible = 1 AND ItemIsCollapsible = 1
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId }, { datasource: "payfrit" });
writeOutput(serializeJSON({ writeOutput(serializeJSON({

View file

@ -50,7 +50,7 @@ try {
// Verify exists and belongs to business // Verify exists and belongs to business
qCheck = queryExecute(" qCheck = queryExecute("
SELECT ScheduledTaskID FROM ScheduledTaskDefinitions SELECT ScheduledTaskID FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", { ", {
id: { value: taskID, cfsqltype: "cf_sql_integer" }, id: { value: taskID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -62,7 +62,7 @@ try {
// Hard delete the definition // Hard delete the definition
queryExecute(" queryExecute("
DELETE FROM ScheduledTaskDefinitions WHERE ID = :id DELETE FROM ScheduledTaskDefinitions WHERE ScheduledTaskID = :id
", { ", {
id: { value: taskID, cfsqltype: "cf_sql_integer" } id: { value: taskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });

View file

@ -45,24 +45,23 @@ try {
// Get scheduled task definitions for this business // Get scheduled task definitions for this business
q = queryExecute(" q = queryExecute("
SELECT SELECT
st.ID, st.ScheduledTaskID,
st.Name as Name, st.ScheduledTaskName as Name,
st.TaskCategoryID as CategoryID, st.ScheduledTaskCategoryID as CategoryID,
st.Title as Title, st.ScheduledTaskTypeID as TypeID,
st.Details as Details, st.ScheduledTaskTitle as Title,
st.CronExpression as CronExpression, st.ScheduledTaskDetails as Details,
COALESCE(st.ScheduleType, 'cron') as ScheduleType, st.ScheduledTaskCronExpression as CronExpression,
st.IntervalMinutes as IntervalMinutes, st.ScheduledTaskIsActive as IsActive,
st.IsActive as IsActive, st.ScheduledTaskLastRunOn as LastRunOn,
st.LastRunOn as LastRunOn, st.ScheduledTaskNextRunOn as NextRunOn,
st.NextRunOn as NextRunOn, st.ScheduledTaskCreatedOn as CreatedOn,
st.CreatedOn as CreatedOn, tc.TaskCategoryName as CategoryName,
tc.Name as Name, tc.TaskCategoryColor as CategoryColor
tc.Color as CategoryColor
FROM ScheduledTaskDefinitions st FROM ScheduledTaskDefinitions st
LEFT JOIN TaskCategories tc ON st.TaskCategoryID = tc.ID LEFT JOIN TaskCategories tc ON st.ScheduledTaskCategoryID = tc.TaskCategoryID
WHERE st.BusinessID = :businessID WHERE st.ScheduledTaskBusinessID = :businessID
ORDER BY st.IsActive DESC, st.Name ORDER BY st.ScheduledTaskIsActive DESC, st.ScheduledTaskName
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
@ -70,19 +69,18 @@ try {
scheduledTasks = []; scheduledTasks = [];
for (row in q) { for (row in q) {
arrayAppend(scheduledTasks, { arrayAppend(scheduledTasks, {
"ScheduledTaskID": row.ID, "ScheduledTaskID": row.ScheduledTaskID,
"Name": row.Name, "Name": row.Name,
"CategoryID": isNull(row.CategoryID) ? "" : row.CategoryID, "CategoryID": isNull(row.CategoryID) ? "" : row.CategoryID,
"TypeID": isNull(row.TypeID) ? "" : row.TypeID,
"Title": row.Title, "Title": row.Title,
"Details": isNull(row.Details) ? "" : row.Details, "Details": isNull(row.Details) ? "" : row.Details,
"CronExpression": row.CronExpression, "CronExpression": row.CronExpression,
"ScheduleType": row.ScheduleType,
"IntervalMinutes": isNull(row.IntervalMinutes) ? "" : row.IntervalMinutes,
"IsActive": row.IsActive ? true : false, "IsActive": row.IsActive ? true : false,
"LastRunOn": isNull(row.LastRunOn) ? "" : dateTimeFormat(row.LastRunOn, "yyyy-mm-dd HH:nn:ss"), "LastRunOn": isNull(row.LastRunOn) ? "" : dateTimeFormat(row.LastRunOn, "yyyy-mm-dd HH:nn:ss"),
"NextRunOn": isNull(row.NextRunOn) ? "" : dateTimeFormat(row.NextRunOn, "yyyy-mm-dd HH:nn:ss"), "NextRunOn": isNull(row.NextRunOn) ? "" : dateTimeFormat(row.NextRunOn, "yyyy-mm-dd HH:nn:ss"),
"CreatedOn": dateTimeFormat(row.CreatedOn, "yyyy-mm-dd HH:nn:ss"), "CreatedOn": dateTimeFormat(row.CreatedOn, "yyyy-mm-dd HH:nn:ss"),
"Name": isNull(row.Name) ? "" : row.Name, "CategoryName": isNull(row.CategoryName) ? "" : row.CategoryName,
"CategoryColor": isNull(row.CategoryColor) ? "" : row.CategoryColor "CategoryColor": isNull(row.CategoryColor) ? "" : row.CategoryColor
}); });
} }

View file

@ -51,11 +51,12 @@ try {
// Get scheduled task definition // Get scheduled task definition
qDef = queryExecute(" qDef = queryExecute("
SELECT SELECT
Title as Title, ScheduledTaskTitle as Title,
Details as Details, ScheduledTaskDetails as Details,
TaskCategoryID as CategoryID ScheduledTaskCategoryID as CategoryID,
ScheduledTaskTypeID as TypeID
FROM ScheduledTaskDefinitions FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", { ", {
id: { value: scheduledTaskID, cfsqltype: "cf_sql_integer" }, id: { value: scheduledTaskID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -65,21 +66,24 @@ try {
apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Scheduled task not found" }); apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Scheduled task not found" });
} }
// Create the task (ClaimedByUserID=0 means unclaimed/pending) // Create the task
queryExecute(" queryExecute("
INSERT INTO Tasks ( INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID, TaskBusinessID, TaskCategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES ( ) VALUES (
:businessID, :categoryID, :typeID, :businessID, :categoryID, :typeID,
:title, :details, NOW(), 0 :title, :details, 0, NOW(),
'scheduled_manual', :scheduledTaskID
) )
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: qDef.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(qDef.CategoryID) }, categoryID: { value: qDef.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(qDef.CategoryID) },
typeID: { value: 0, cfsqltype: "cf_sql_integer" }, typeID: { value: qDef.TypeID, cfsqltype: "cf_sql_integer", null: isNull(qDef.TypeID) },
title: { value: qDef.Title, cfsqltype: "cf_sql_varchar" }, title: { value: qDef.Title, cfsqltype: "cf_sql_varchar" },
details: { value: qDef.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(qDef.Details) } details: { value: qDef.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(qDef.Details) },
scheduledTaskID: { value: scheduledTaskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });

View file

@ -68,65 +68,57 @@ try {
dueTasks = queryExecute(" dueTasks = queryExecute("
SELECT SELECT
ScheduledTaskID, ScheduledTaskID,
BusinessID as BusinessID, ScheduledTaskBusinessID as BusinessID,
TaskCategoryID as CategoryID, ScheduledTaskCategoryID as CategoryID,
Title as Title, ScheduledTaskTypeID as TypeID,
Details as Details, ScheduledTaskTitle as Title,
CronExpression as CronExpression, ScheduledTaskDetails as Details,
COALESCE(ScheduleType, 'cron') as ScheduleType, ScheduledTaskCronExpression as CronExpression
IntervalMinutes as IntervalMinutes
FROM ScheduledTaskDefinitions FROM ScheduledTaskDefinitions
WHERE IsActive = 1 WHERE ScheduledTaskIsActive = 1
AND NextRunOn <= NOW() AND ScheduledTaskNextRunOn <= NOW()
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
createdTasks = []; createdTasks = [];
for (task in dueTasks) { for (task in dueTasks) {
// Create the actual task (ClaimedByUserID=0 means unclaimed/pending) // Create the actual task
queryExecute(" queryExecute("
INSERT INTO Tasks ( INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID, TaskBusinessID, TaskCategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES ( ) VALUES (
:businessID, :categoryID, :typeID, :businessID, :categoryID, :typeID,
:title, :details, NOW(), 0 :title, :details, 0, NOW(),
'scheduled', :scheduledTaskID
) )
", { ", {
businessID: { value: task.BusinessID, cfsqltype: "cf_sql_integer" }, businessID: { value: task.BusinessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: task.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(task.CategoryID) }, categoryID: { value: task.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(task.CategoryID) },
typeID: { value: 0, cfsqltype: "cf_sql_integer" }, typeID: { value: task.TypeID, cfsqltype: "cf_sql_integer", null: isNull(task.TypeID) },
title: { value: task.Title, cfsqltype: "cf_sql_varchar" }, title: { value: task.Title, cfsqltype: "cf_sql_varchar" },
details: { value: task.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(task.Details) } details: { value: task.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(task.Details) },
scheduledTaskID: { value: task.ScheduledTaskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
// Calculate next run based on schedule type // Calculate next run and update the scheduled task
if (task.ScheduleType == "interval_after_completion" && !isNull(task.IntervalMinutes) && task.IntervalMinutes > 0) { nextRun = calculateNextRun(task.CronExpression);
// After-completion interval: don't schedule next run until task is completed
// Set to far future (effectively paused until task completion triggers recalculation)
nextRun = javaCast("null", "");
} else if (task.ScheduleType == "interval" && !isNull(task.IntervalMinutes) && task.IntervalMinutes > 0) {
// Fixed interval: next run = NOW + interval minutes
nextRun = dateAdd("n", task.IntervalMinutes, now());
} else {
// Cron-based: use cron parser
nextRun = calculateNextRun(task.CronExpression);
}
queryExecute(" queryExecute("
UPDATE ScheduledTaskDefinitions SET UPDATE ScheduledTaskDefinitions SET
LastRunOn = NOW(), ScheduledTaskLastRunOn = NOW(),
NextRunOn = :nextRun ScheduledTaskNextRunOn = :nextRun
WHERE ID = :id WHERE ScheduledTaskID = :id
", { ", {
nextRun: { value: nextRun, cfsqltype: "cf_sql_timestamp", null: isNull(nextRun) }, nextRun: { value: nextRun, cfsqltype: "cf_sql_timestamp" },
id: { value: task.ID, cfsqltype: "cf_sql_integer" } id: { value: task.ScheduledTaskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
arrayAppend(createdTasks, { arrayAppend(createdTasks, {
"ScheduledTaskID": task.ID, "ScheduledTaskID": task.ScheduledTaskID,
"TaskID": qNew.newID, "TaskID": qNew.newID,
"BusinessID": task.BusinessID, "BusinessID": task.BusinessID,
"Title": task.Title "Title": task.Title

View file

@ -5,8 +5,7 @@
<cfscript> <cfscript>
// Create or update a scheduled task definition // Create or update a scheduled task definition
// Input: BusinessID (required), ScheduledTaskID (optional - for update), // Input: BusinessID (required), ScheduledTaskID (optional - for update),
// Name, Title, Details, CategoryID, TypeID, CronExpression, IsActive, // Name, Title, Details, CategoryID, TypeID, CronExpression, IsActive
// ScheduleType ('cron' or 'interval'), IntervalMinutes (for interval type)
// Output: { OK: true, SCHEDULED_TASK_ID: int } // Output: { OK: true, SCHEDULED_TASK_ID: int }
function apiAbort(required struct payload) { function apiAbort(required struct payload) {
@ -109,13 +108,10 @@ try {
taskTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : ""; taskTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : "";
taskDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : ""; taskDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : "";
categoryID = structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0 ? int(data.CategoryID) : javaCast("null", ""); categoryID = structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0 ? int(data.CategoryID) : javaCast("null", "");
typeID = structKeyExists(data, "TypeID") && isNumeric(data.TypeID) && data.TypeID > 0 ? int(data.TypeID) : javaCast("null", "");
cronExpression = structKeyExists(data, "CronExpression") ? trim(toString(data.CronExpression)) : ""; cronExpression = structKeyExists(data, "CronExpression") ? trim(toString(data.CronExpression)) : "";
isActive = structKeyExists(data, "IsActive") ? (data.IsActive ? 1 : 0) : 1; isActive = structKeyExists(data, "IsActive") ? (data.IsActive ? 1 : 0) : 1;
// New interval scheduling fields
scheduleType = structKeyExists(data, "ScheduleType") ? trim(toString(data.ScheduleType)) : "cron";
intervalMinutes = structKeyExists(data, "IntervalMinutes") && isNumeric(data.IntervalMinutes) ? int(data.IntervalMinutes) : javaCast("null", "");
// Validate required fields // Validate required fields
if (!len(taskName)) { if (!len(taskName)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Name is required" }); apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Name is required" });
@ -123,42 +119,24 @@ try {
if (!len(taskTitle)) { if (!len(taskTitle)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Title is required" }); apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Title is required" });
} }
if (!len(cronExpression)) {
// Validate based on schedule type apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "CronExpression is required" });
if (scheduleType == "interval" || scheduleType == "interval_after_completion") {
// Interval-based scheduling
if (isNull(intervalMinutes) || intervalMinutes < 1) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "IntervalMinutes is required for interval scheduling (minimum 1)" });
}
// Set a placeholder cron expression for interval type
if (!len(cronExpression)) {
cronExpression = "* * * * *";
}
// For NEW tasks: run immediately. For UPDATES: next run = NOW + interval
if (taskID == 0) {
nextRunOn = now(); // Run immediately on first creation
} else {
nextRunOn = dateAdd("n", intervalMinutes, now());
}
} else {
// Cron-based scheduling
if (!len(cronExpression)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "CronExpression is required" });
}
// Validate cron format (5 parts)
cronParts = listToArray(cronExpression, " ");
if (arrayLen(cronParts) != 5) {
apiAbort({ "OK": false, "ERROR": "invalid_cron", "MESSAGE": "Cron expression must have 5 parts: minute hour day month weekday" });
}
// Calculate next run time from cron
nextRunOn = calculateNextRun(cronExpression);
} }
// Validate cron format (5 parts)
cronParts = listToArray(cronExpression, " ");
if (arrayLen(cronParts) != 5) {
apiAbort({ "OK": false, "ERROR": "invalid_cron", "MESSAGE": "Cron expression must have 5 parts: minute hour day month weekday" });
}
// Calculate next run time
nextRunOn = calculateNextRun(cronExpression);
if (taskID > 0) { if (taskID > 0) {
// UPDATE existing // UPDATE existing
qCheck = queryExecute(" qCheck = queryExecute("
SELECT ScheduledTaskID FROM ScheduledTaskDefinitions SELECT ScheduledTaskID FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", { ", {
id: { value: taskID, cfsqltype: "cf_sql_integer" }, id: { value: taskID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -170,24 +148,22 @@ try {
queryExecute(" queryExecute("
UPDATE ScheduledTaskDefinitions SET UPDATE ScheduledTaskDefinitions SET
Name = :name, ScheduledTaskName = :name,
Title = :title, ScheduledTaskTitle = :title,
Details = :details, ScheduledTaskDetails = :details,
TaskCategoryID = :categoryID, ScheduledTaskCategoryID = :categoryID,
CronExpression = :cron, ScheduledTaskTypeID = :typeID,
ScheduleType = :scheduleType, ScheduledTaskCronExpression = :cron,
IntervalMinutes = :intervalMinutes, ScheduledTaskIsActive = :isActive,
IsActive = :isActive, ScheduledTaskNextRunOn = :nextRun
NextRunOn = :nextRun WHERE ScheduledTaskID = :id
WHERE ID = :id
", { ", {
name: { value: taskName, cfsqltype: "cf_sql_varchar" }, name: { value: taskName, cfsqltype: "cf_sql_varchar" },
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" }, title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) }, details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) },
categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) }, categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) },
typeID: { value: typeID, cfsqltype: "cf_sql_integer", null: isNull(typeID) },
cron: { value: cronExpression, cfsqltype: "cf_sql_varchar" }, cron: { value: cronExpression, cfsqltype: "cf_sql_varchar" },
scheduleType: { value: scheduleType, cfsqltype: "cf_sql_varchar" },
intervalMinutes: { value: intervalMinutes, cfsqltype: "cf_sql_integer", null: isNull(intervalMinutes) },
isActive: { value: isActive, cfsqltype: "cf_sql_bit" }, isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" }, nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" },
id: { value: taskID, cfsqltype: "cf_sql_integer" } id: { value: taskID, cfsqltype: "cf_sql_integer" }
@ -204,12 +180,11 @@ try {
// INSERT new // INSERT new
queryExecute(" queryExecute("
INSERT INTO ScheduledTaskDefinitions ( INSERT INTO ScheduledTaskDefinitions (
BusinessID, Name, Title, ScheduledTaskBusinessID, ScheduledTaskName, ScheduledTaskTitle,
Details, TaskCategoryID, ScheduledTaskDetails, ScheduledTaskCategoryID, ScheduledTaskTypeID,
CronExpression, ScheduleType, IntervalMinutes, ScheduledTaskCronExpression, ScheduledTaskIsActive, ScheduledTaskNextRunOn
IsActive, NextRunOn
) VALUES ( ) VALUES (
:businessID, :name, :title, :details, :categoryID, :cron, :scheduleType, :intervalMinutes, :isActive, :nextRun :businessID, :name, :title, :details, :categoryID, :typeID, :cron, :isActive, :nextRun
) )
", { ", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
@ -217,65 +192,19 @@ try {
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" }, title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) }, details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) },
categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) }, categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) },
typeID: { value: typeID, cfsqltype: "cf_sql_integer", null: isNull(typeID) },
cron: { value: cronExpression, cfsqltype: "cf_sql_varchar" }, cron: { value: cronExpression, cfsqltype: "cf_sql_varchar" },
scheduleType: { value: scheduleType, cfsqltype: "cf_sql_varchar" },
intervalMinutes: { value: intervalMinutes, cfsqltype: "cf_sql_integer", null: isNull(intervalMinutes) },
isActive: { value: isActive, cfsqltype: "cf_sql_bit" }, isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" } nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" }
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" }); qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
newScheduledTaskID = qNew.newID;
// Create the first task immediately
queryExecute("
INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID
) VALUES (
:businessID, :categoryID, :typeID,
:title, :details, NOW(), 0
)
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: categoryID, cfsqltype: "cf_sql_integer", null: isNull(categoryID) },
typeID: { value: 0, cfsqltype: "cf_sql_integer" },
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) }
}, { datasource: "payfrit" });
qTask = queryExecute("SELECT LAST_INSERT_ID() as taskID", [], { datasource: "payfrit" });
// Now set the NEXT run time (not the immediate one we just created)
if (scheduleType == "interval" || scheduleType == "interval_after_completion") {
if (scheduleType == "interval_after_completion") {
// After-completion: don't schedule next until task is completed
actualNextRun = javaCast("null", "");
} else {
// Fixed interval: next run = NOW + interval
actualNextRun = dateAdd("n", intervalMinutes, now());
}
} else {
// Cron-based
actualNextRun = calculateNextRun(cronExpression);
}
queryExecute("
UPDATE ScheduledTaskDefinitions
SET LastRunOn = NOW(),
NextRunOn = :nextRun
WHERE ID = :id
", {
nextRun: { value: actualNextRun, cfsqltype: "cf_sql_timestamp", null: isNull(actualNextRun) },
id: { value: newScheduledTaskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
apiAbort({ apiAbort({
"OK": true, "OK": true,
"SCHEDULED_TASK_ID": newScheduledTaskID, "SCHEDULED_TASK_ID": qNew.newID,
"TASK_ID": qTask.taskID, "NEXT_RUN": dateTimeFormat(nextRunOn, "yyyy-mm-dd HH:nn:ss"),
"NEXT_RUN": isNull(actualNextRun) ? "" : dateTimeFormat(actualNextRun, "yyyy-mm-dd HH:nn:ss"), "MESSAGE": "Scheduled task created"
"MESSAGE": "Scheduled task created and first task added"
}); });
} }

View file

@ -15,48 +15,27 @@ try {
// Create ScheduledTaskDefinitions table // Create ScheduledTaskDefinitions table
queryExecute(" queryExecute("
CREATE TABLE IF NOT EXISTS ScheduledTaskDefinitions ( CREATE TABLE IF NOT EXISTS ScheduledTaskDefinitions (
ID INT AUTO_INCREMENT PRIMARY KEY, ScheduledTaskID INT AUTO_INCREMENT PRIMARY KEY,
BusinessID INT NOT NULL, ScheduledTaskBusinessID INT NOT NULL,
Name VARCHAR(100) NOT NULL, ScheduledTaskName VARCHAR(100) NOT NULL,
TaskCategoryID INT NULL, ScheduledTaskCategoryID INT NULL,
TaskTypeID INT NULL, ScheduledTaskTypeID INT NULL,
Title VARCHAR(255) NOT NULL, ScheduledTaskTitle VARCHAR(255) NOT NULL,
Details TEXT NULL, ScheduledTaskDetails TEXT NULL,
CronExpression VARCHAR(100) NOT NULL, ScheduledTaskCronExpression VARCHAR(100) NOT NULL,
ScheduleType VARCHAR(20) DEFAULT 'cron', ScheduledTaskIsActive BIT(1) DEFAULT b'1',
IntervalMinutes INT NULL, ScheduledTaskLastRunOn DATETIME NULL,
IsActive BIT(1) DEFAULT b'1', ScheduledTaskNextRunOn DATETIME NULL,
LastRunOn DATETIME NULL, ScheduledTaskCreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP,
NextRunOn DATETIME NULL, ScheduledTaskCreatedByUserID INT NULL,
CreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_business (ScheduledTaskBusinessID),
CreatedByUserID INT NULL, INDEX idx_active_next (ScheduledTaskIsActive, ScheduledTaskNextRunOn)
INDEX idx_business (BusinessID),
INDEX idx_active_next (IsActive, NextRunOn)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
// Add new columns if they don't exist (for existing tables)
try {
queryExecute("
ALTER TABLE ScheduledTaskDefinitions
ADD COLUMN ScheduleType VARCHAR(20) DEFAULT 'cron' AFTER CronExpression
", [], { datasource: "payfrit" });
} catch (any e) {
// Column likely already exists
}
try {
queryExecute("
ALTER TABLE ScheduledTaskDefinitions
ADD COLUMN IntervalMinutes INT NULL AFTER ScheduleType
", [], { datasource: "payfrit" });
} catch (any e) {
// Column likely already exists
}
apiAbort({ apiAbort({
"OK": true, "OK": true,
"MESSAGE": "ScheduledTaskDefinitions table created/verified with interval support" "MESSAGE": "ScheduledTaskDefinitions table created/verified"
}); });
} catch (any e) { } catch (any e) {

View file

@ -97,13 +97,11 @@ try {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "ScheduledTaskID is required" }); apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "ScheduledTaskID is required" });
} }
// Verify exists and get cron expression and schedule type // Verify exists and get cron expression
qCheck = queryExecute(" qCheck = queryExecute("
SELECT ScheduledTaskID, CronExpression as CronExpression, SELECT ScheduledTaskID, ScheduledTaskCronExpression as CronExpression
COALESCE(ScheduleType, 'cron') as ScheduleType,
IntervalMinutes as IntervalMinutes
FROM ScheduledTaskDefinitions FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", { ", {
id: { value: taskID, cfsqltype: "cf_sql_integer" }, id: { value: taskID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" } businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -113,26 +111,20 @@ try {
apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Scheduled task not found" }); apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Scheduled task not found" });
} }
// If enabling, recalculate next run time based on schedule type // If enabling, recalculate next run time
nextRunUpdate = ""; nextRunUpdate = "";
if (isActive) { if (isActive) {
if ((qCheck.ScheduleType == "interval" || qCheck.ScheduleType == "interval_after_completion") && !isNull(qCheck.IntervalMinutes) && qCheck.IntervalMinutes > 0) { nextRunOn = calculateNextRun(qCheck.CronExpression);
// Interval-based: next run = NOW + interval minutes nextRunUpdate = ", ScheduledTaskNextRunOn = :nextRun";
nextRunOn = dateAdd("n", qCheck.IntervalMinutes, now());
} else {
// Cron-based: use cron parser
nextRunOn = calculateNextRun(qCheck.CronExpression);
}
nextRunUpdate = ", NextRunOn = :nextRun";
} }
// Update status // Update status
if (isActive) { if (isActive) {
queryExecute(" queryExecute("
UPDATE ScheduledTaskDefinitions SET UPDATE ScheduledTaskDefinitions SET
IsActive = :isActive, ScheduledTaskIsActive = :isActive,
NextRunOn = :nextRun ScheduledTaskNextRunOn = :nextRun
WHERE ID = :id WHERE ScheduledTaskID = :id
", { ", {
isActive: { value: isActive, cfsqltype: "cf_sql_bit" }, isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" }, nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" },
@ -140,8 +132,8 @@ try {
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
} else { } else {
queryExecute(" queryExecute("
UPDATE ScheduledTaskDefinitions SET IsActive = :isActive UPDATE ScheduledTaskDefinitions SET ScheduledTaskIsActive = :isActive
WHERE ID = :id WHERE ScheduledTaskID = :id
", { ", {
isActive: { value: isActive, cfsqltype: "cf_sql_bit" }, isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
id: { value: taskID, cfsqltype: "cf_sql_integer" } id: { value: taskID, cfsqltype: "cf_sql_integer" }

View file

@ -21,8 +21,8 @@ if (businessId <= 0 || userId <= 0) {
try { try {
// Update employee record // Update employee record
queryExecute(" queryExecute("
UPDATE Employees UPDATE lt_Users_Businesses_Employees
SET IsActive = ? SET EmployeeIsActive = ?
WHERE BusinessID = ? AND UserID = ? WHERE BusinessID = ? AND UserID = ?
", [ ", [
{ value: isActive, cfsqltype: "cf_sql_bit" }, { value: isActive, cfsqltype: "cf_sql_bit" },
@ -32,12 +32,12 @@ try {
// Get updated record // Get updated record
q = queryExecute(" q = queryExecute("
SELECT e.ID, e.BusinessID, e.UserID, e.StatusID, SELECT e.EmployeeID, e.BusinessID, e.UserID, e.EmployeeStatusID,
CAST(e.IsActive AS UNSIGNED) AS IsActive, CAST(e.EmployeeIsActive AS UNSIGNED) AS EmployeeIsActive,
b.Name, u.FirstName, u.LastName b.BusinessName, u.UserFirstName, u.UserLastName
FROM Employees e FROM lt_Users_Businesses_Employees e
JOIN Businesses b ON e.BusinessID = b.ID JOIN Businesses b ON e.BusinessID = b.BusinessID
JOIN Users u ON e.UserID = u.ID JOIN Users u ON e.UserID = u.UserID
WHERE e.BusinessID = ? AND e.UserID = ? WHERE e.BusinessID = ? AND e.UserID = ?
", [ ", [
{ value: businessId, cfsqltype: "cf_sql_integer" }, { value: businessId, cfsqltype: "cf_sql_integer" },
@ -49,13 +49,13 @@ try {
"OK": true, "OK": true,
"MESSAGE": "Employee updated", "MESSAGE": "Employee updated",
"EMPLOYEE": { "EMPLOYEE": {
"EmployeeID": q.ID, "EmployeeID": q.EmployeeID,
"BusinessID": q.BusinessID, "BusinessID": q.BusinessID,
"Name": q.Name, "BusinessName": q.BusinessName,
"UserID": q.UserID, "UserID": q.UserID,
"UserName": trim(q.FirstName & " " & q.LastName), "UserName": trim(q.UserFirstName & " " & q.UserLastName),
"StatusID": q.StatusID, "StatusID": q.EmployeeStatusID,
"IsActive": q.IsActive "IsActive": q.EmployeeIsActive
} }
})); }));
} else { } else {

View file

@ -7,11 +7,11 @@ response = { "OK": false };
try { try {
queryExecute(" queryExecute("
UPDATE Businesses SET HeaderImageExtension = 'jpg' WHERE ID = 37 UPDATE Businesses SET BusinessHeaderImageExtension = 'jpg' WHERE BusinessID = 37
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
response.OK = true; response.OK = true;
response.message = "Set HeaderImageExtension to 'jpg' for business 37"; response.message = "Set BusinessHeaderImageExtension to 'jpg' for business 37";
} catch (any e) { } catch (any e) {
response.error = e.message; response.error = e.message;
} }

View file

@ -5,7 +5,7 @@
<cfscript> <cfscript>
/** /**
* Setup Lazy Daisy Beacons * Setup Lazy Daisy Beacons
* Creates a beacon for each service point and assigns them * Creates a beacon for each service point and links them
*/ */
response = { "OK": false, "steps": [] }; response = { "OK": false, "steps": [] };
@ -14,10 +14,10 @@ try {
// Get all service points for Lazy Daisy // Get all service points for Lazy Daisy
qServicePoints = queryExecute(" qServicePoints = queryExecute("
SELECT ID, Name SELECT ServicePointID, ServicePointName
FROM ServicePoints FROM ServicePoints
WHERE BusinessID = :bizID AND IsActive = 1 WHERE ServicePointBusinessID = :bizID AND ServicePointIsActive = 1
ORDER BY SortOrder, ID ORDER BY ServicePointID
", { bizID: lazyDaisyID }, { datasource: "payfrit" }); ", { bizID: lazyDaisyID }, { datasource: "payfrit" });
response.steps.append("Found " & qServicePoints.recordCount & " service points for Lazy Daisy"); response.steps.append("Found " & qServicePoints.recordCount & " service points for Lazy Daisy");
@ -25,20 +25,20 @@ try {
// Create a beacon for each service point // Create a beacon for each service point
beaconsCreated = 0; beaconsCreated = 0;
for (sp in qServicePoints) { for (sp in qServicePoints) {
beaconName = "Beacon - " & sp.Name; beaconName = "Beacon - " & sp.ServicePointName;
// Check if beacon already exists for this business with this name // Check if beacon already exists for this business with this name
qExisting = queryExecute(" qExisting = queryExecute("
SELECT ID FROM Beacons SELECT BeaconID FROM Beacons
WHERE BusinessID = :bizId AND Name = :name WHERE BeaconBusinessID = :bizId AND BeaconName = :name
", { bizId: lazyDaisyID, name: beaconName }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID, name: beaconName }, { datasource: "payfrit" });
if (qExisting.recordCount == 0) { if (qExisting.recordCount == 0) {
// Generate a unique UUID for this beacon (32 hex chars, no dashes) // Generate a unique UUID for this beacon (32 hex chars, no dashes)
beaconUUID = "PAYFRIT00037" & numberFormat(sp.ID, "0000000000000000000"); beaconUUID = "PAYFRIT00037" & numberFormat(sp.ServicePointID, "0000000000000000000");
queryExecute(" queryExecute("
INSERT INTO Beacons (BusinessID, Name, UUID, IsActive) INSERT INTO Beacons (BeaconBusinessID, BeaconName, BeaconUUID, BeaconIsActive)
VALUES (:bizId, :name, :uuid, 1) VALUES (:bizId, :name, :uuid, 1)
", { ", {
bizId: lazyDaisyID, bizId: lazyDaisyID,
@ -49,18 +49,18 @@ try {
qNewBeacon = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); qNewBeacon = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
newBeaconId = qNewBeacon.id; newBeaconId = qNewBeacon.id;
// Assign beacon directly to service point // Create assignment to service point
queryExecute(" queryExecute("
UPDATE ServicePoints INSERT INTO lt_Beacon_Businesses_ServicePoints
SET BeaconID = :beaconId, AssignedByUserID = 1 (BeaconID, BusinessID, ServicePointID, lt_Beacon_Businesses_ServicePointAssignedByUserID)
WHERE ID = :spId AND BusinessID = :bizId VALUES (:beaconId, :bizId, :spId, 1)
", { ", {
beaconId: newBeaconId, beaconId: newBeaconId,
bizId: lazyDaisyID, bizId: lazyDaisyID,
spId: sp.ID spId: sp.ServicePointID
}, { datasource: "payfrit" }); }, { datasource: "payfrit" });
response.steps.append("Created beacon '" & beaconName & "' (ID: " & newBeaconId & ") -> " & sp.Name); response.steps.append("Created beacon '" & beaconName & "' (ID: " & newBeaconId & ") -> " & sp.ServicePointName);
beaconsCreated++; beaconsCreated++;
} else { } else {
response.steps.append("Beacon '" & beaconName & "' already exists, skipping"); response.steps.append("Beacon '" & beaconName & "' already exists, skipping");
@ -69,14 +69,13 @@ try {
// Get final status // Get final status
qFinal = queryExecute(" qFinal = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.BeaconID, b.BeaconUUID, b.BeaconName, lt.BusinessID, biz.BusinessName, lt.ServicePointID, sp.ServicePointName
b.Name AS BeaconName, b.UUID, sp.Name AS ServicePointName, FROM lt_Beacon_Businesses_ServicePoints lt
biz.Name AS BusinessName JOIN Beacons b ON b.BeaconID = lt.BeaconID
FROM ServicePoints sp JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
JOIN Beacons b ON b.ID = sp.BeaconID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
JOIN Businesses biz ON biz.ID = sp.BusinessID WHERE lt.BusinessID = :bizId
WHERE sp.BusinessID = :bizId AND sp.BeaconID IS NOT NULL ORDER BY sp.ServicePointName
ORDER BY sp.Name
", { bizId: lazyDaisyID }, { datasource: "payfrit" }); ", { bizId: lazyDaisyID }, { datasource: "payfrit" });
beacons = []; beacons = [];
@ -84,9 +83,8 @@ try {
arrayAppend(beacons, { arrayAppend(beacons, {
"BeaconID": qFinal.BeaconID[i], "BeaconID": qFinal.BeaconID[i],
"BeaconName": qFinal.BeaconName[i], "BeaconName": qFinal.BeaconName[i],
"UUID": qFinal.UUID[i], "UUID": qFinal.BeaconUUID[i],
"BusinessName": qFinal.BusinessName[i], "BusinessName": qFinal.BusinessName[i],
"ServicePointID": qFinal.ServicePointID[i],
"ServicePointName": qFinal.ServicePointName[i] "ServicePointName": qFinal.ServicePointName[i]
}); });
} }

View file

@ -13,19 +13,19 @@ try {
// Hours: Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm // Hours: Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm
// Get California StateID // Get California StateID
qState = queryExecute("SELECT tt_StateID FROM tt_States WHERE Abbreviation = 'CA' LIMIT 1"); qState = queryExecute("SELECT tt_StateID FROM tt_States WHERE tt_StateAbbreviation = 'CA' LIMIT 1");
stateId = qState.recordCount > 0 ? qState.tt_StateID : 5; // Default to 5 if not found stateId = qState.recordCount > 0 ? qState.tt_StateID : 5; // Default to 5 if not found
// Check if Big Dean's already has an address // Check if Big Dean's already has an address
existingAddr = queryExecute(" existingAddr = queryExecute("
SELECT ID FROM Addresses SELECT AddressID FROM Addresses
WHERE BusinessID = :bizId AND UserID = 0 WHERE AddressBusinessID = :bizId AND AddressUserID = 0
", { bizId: businessId }); ", { bizId: businessId });
if (existingAddr.recordCount == 0) { if (existingAddr.recordCount == 0) {
// Insert new address // Insert new address
queryExecute(" queryExecute("
INSERT INTO Addresses (UserID, BusinessID, AddressTypeID, Line1, City, StateID, ZIPCode, IsDeleted, AddedOn) INSERT INTO Addresses (AddressUserID, AddressBusinessID, AddressTypeID, AddressLine1, AddressCity, AddressStateID, AddressZIPCode, AddressIsDeleted, AddressAddedOn)
VALUES (0, :bizId, '2', :line1, :city, :stateId, :zip, 0, NOW()) VALUES (0, :bizId, '2', :line1, :city, :stateId, :zip, 0, NOW())
", { ", {
bizId: businessId, bizId: businessId,
@ -39,8 +39,8 @@ try {
// Update existing address // Update existing address
queryExecute(" queryExecute("
UPDATE Addresses UPDATE Addresses
SET Line1 = :line1, City = :city, StateID = :stateId, ZIPCode = :zip SET AddressLine1 = :line1, AddressCity = :city, AddressStateID = :stateId, AddressZIPCode = :zip
WHERE BusinessID = :bizId AND UserID = 0 WHERE AddressBusinessID = :bizId AND AddressUserID = 0
", { ", {
bizId: businessId, bizId: businessId,
line1: "1615 Ocean Front Walk", line1: "1615 Ocean Front Walk",
@ -53,7 +53,7 @@ try {
// Check existing hours for this business // Check existing hours for this business
existingHours = queryExecute(" existingHours = queryExecute("
SELECT COUNT(*) as cnt FROM Hours WHERE BusinessID = :bizId SELECT COUNT(*) as cnt FROM Hours WHERE HoursBusinessID = :bizId
", { bizId: businessId }); ", { bizId: businessId });
if (existingHours.cnt == 0) { if (existingHours.cnt == 0) {
@ -64,39 +64,39 @@ try {
// Sun: 11am-10pm (day 1) // Sun: 11am-10pm (day 1)
// Sunday (1): 11am-10pm // Sunday (1): 11am-10pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 1, '11:00:00', '22:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 1, '11:00:00', '22:00:00')", { bizId: businessId });
// Monday (2): 11am-10pm // Monday (2): 11am-10pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 2, '11:00:00', '22:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 2, '11:00:00', '22:00:00')", { bizId: businessId });
// Tuesday (3): 11am-10pm // Tuesday (3): 11am-10pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 3, '11:00:00', '22:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 3, '11:00:00', '22:00:00')", { bizId: businessId });
// Wednesday (4): 11am-10pm // Wednesday (4): 11am-10pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 4, '11:00:00', '22:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 4, '11:00:00', '22:00:00')", { bizId: businessId });
// Thursday (5): 11am-10pm // Thursday (5): 11am-10pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 5, '11:00:00', '22:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 5, '11:00:00', '22:00:00')", { bizId: businessId });
// Friday (6): 11am-11pm // Friday (6): 11am-11pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 6, '11:00:00', '23:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 6, '11:00:00', '23:00:00')", { bizId: businessId });
// Saturday (7): 11am-11pm // Saturday (7): 11am-11pm
queryExecute("INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) VALUES (:bizId, 7, '11:00:00', '23:00:00')", { bizId: businessId }); queryExecute("INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizId, 7, '11:00:00', '23:00:00')", { bizId: businessId });
response["HOURS_ACTION"] = "inserted 7 days"; response["HOURS_ACTION"] = "inserted 7 days";
} else { } else {
// Update existing hours // Update existing hours
// Mon-Thu: 11am-10pm // Mon-Thu: 11am-10pm
queryExecute("UPDATE Hours SET OpenTime = '11:00:00', ClosingTime = '22:00:00' WHERE BusinessID = :bizId AND DayID IN (1, 2, 3, 4, 5)", { bizId: businessId }); queryExecute("UPDATE Hours SET HoursOpenTime = '11:00:00', HoursClosingTime = '22:00:00' WHERE HoursBusinessID = :bizId AND HoursDayID IN (1, 2, 3, 4, 5)", { bizId: businessId });
// Fri-Sat: 11am-11pm // Fri-Sat: 11am-11pm
queryExecute("UPDATE Hours SET OpenTime = '11:00:00', ClosingTime = '23:00:00' WHERE BusinessID = :bizId AND DayID IN (6, 7)", { bizId: businessId }); queryExecute("UPDATE Hours SET HoursOpenTime = '11:00:00', HoursClosingTime = '23:00:00' WHERE HoursBusinessID = :bizId AND HoursDayID IN (6, 7)", { bizId: businessId });
response["HOURS_ACTION"] = "updated"; response["HOURS_ACTION"] = "updated";
} }
// Update phone on Businesses table (if column exists) // Update phone on Businesses table (if column exists)
try { try {
queryExecute("UPDATE Businesses SET Phone = :phone WHERE ID = :bizId", { queryExecute("UPDATE Businesses SET BusinessPhone = :phone WHERE BusinessID = :bizId", {
phone: "(310) 393-2666", phone: "(310) 393-2666",
bizId: businessId bizId: businessId
}); });
@ -107,35 +107,35 @@ try {
// Verify the data // Verify the data
address = queryExecute(" address = queryExecute("
SELECT a.*, s.Abbreviation SELECT a.*, s.tt_StateAbbreviation
FROM Addresses a FROM Addresses a
LEFT JOIN tt_States s ON s.ID = a.StateID LEFT JOIN tt_States s ON s.tt_StateID = a.AddressStateID
WHERE a.BusinessID = :bizId AND a.UserID = 0 WHERE a.AddressBusinessID = :bizId AND a.AddressUserID = 0
", { bizId: businessId }); ", { bizId: businessId });
hours = queryExecute(" hours = queryExecute("
SELECT h.*, d.tt_DayName SELECT h.*, d.tt_DayName
FROM Hours h FROM Hours h
JOIN tt_Days d ON d.ID = h.DayID JOIN tt_Days d ON d.tt_DayID = h.HoursDayID
WHERE h.BusinessID = :bizId WHERE h.HoursBusinessID = :bizId
ORDER BY h.DayID ORDER BY h.HoursDayID
", { bizId: businessId }); ", { bizId: businessId });
response["OK"] = true; response["OK"] = true;
response["BUSINESS_ID"] = businessId; response["BUSINESS_ID"] = businessId;
response["ADDRESS"] = address.recordCount > 0 ? { response["ADDRESS"] = address.recordCount > 0 ? {
"line1": address.Line1, "line1": address.AddressLine1,
"city": address.City, "city": address.AddressCity,
"state": address.Abbreviation, "state": address.tt_StateAbbreviation,
"zip": address.ZIPCode "zip": address.AddressZIPCode
} : "not found"; } : "not found";
hoursArr = []; hoursArr = [];
for (h in hours) { for (h in hours) {
arrayAppend(hoursArr, { arrayAppend(hoursArr, {
"day": h.tt_DayName, "day": h.tt_DayName,
"open": timeFormat(h.OpenTime, "h:mm tt"), "open": timeFormat(h.HoursOpenTime, "h:mm tt"),
"close": timeFormat(h.ClosingTime, "h:mm tt") "close": timeFormat(h.HoursClosingTime, "h:mm tt")
}); });
} }
response["HOURS"] = hoursArr; response["HOURS"] = hoursArr;

View file

@ -9,19 +9,19 @@ response = { "OK": false };
try { try {
// Check if Big Dean's already has stations // Check if Big Dean's already has stations
existing = queryExecute(" existing = queryExecute("
SELECT COUNT(*) as cnt FROM Stations WHERE BusinessID = :bizId SELECT COUNT(*) as cnt FROM Stations WHERE StationBusinessID = :bizId
", { bizId: businessId }); ", { bizId: businessId });
if (existing.cnt == 0) { if (existing.cnt == 0) {
// Insert Kitchen station // Insert Kitchen station
queryExecute(" queryExecute("
INSERT INTO Stations (BusinessID, Name, Color, SortOrder, IsActive) INSERT INTO Stations (StationBusinessID, StationName, StationColor, StationSortOrder, StationIsActive)
VALUES (:bizId, 'Kitchen', :color1, 1, 1) VALUES (:bizId, 'Kitchen', :color1, 1, 1)
", { bizId: businessId, color1: "##FF5722" }); ", { bizId: businessId, color1: "##FF5722" });
// Insert Bar station // Insert Bar station
queryExecute(" queryExecute("
INSERT INTO Stations (BusinessID, Name, Color, SortOrder, IsActive) INSERT INTO Stations (StationBusinessID, StationName, StationColor, StationSortOrder, StationIsActive)
VALUES (:bizId, 'Bar', :color2, 2, 1) VALUES (:bizId, 'Bar', :color2, 2, 1)
", { bizId: businessId, color2: "##2196F3" }); ", { bizId: businessId, color2: "##2196F3" });
@ -32,18 +32,18 @@ try {
// Get current stations // Get current stations
stations = queryExecute(" stations = queryExecute("
SELECT ID, Name, Color, SortOrder SELECT StationID, StationName, StationColor, StationSortOrder
FROM Stations FROM Stations
WHERE BusinessID = :bizId AND IsActive = 1 WHERE StationBusinessID = :bizId AND StationIsActive = 1
ORDER BY SortOrder ORDER BY StationSortOrder
", { bizId: businessId }); ", { bizId: businessId });
stationArr = []; stationArr = [];
for (s in stations) { for (s in stations) {
arrayAppend(stationArr, { arrayAppend(stationArr, {
"StationID": s.ID, "StationID": s.StationID,
"Name": s.Name, "StationName": s.StationName,
"Color": s.Color "StationColor": s.StationColor
}); });
} }

View file

@ -9,62 +9,67 @@ try {
lazyDaisyID = 37; lazyDaisyID = 37;
// Get all beacons // Get all beacons
qBeacons = queryExecute("SELECT ID, UUID FROM Beacons", {}, { datasource: "payfrit" }); qBeacons = queryExecute("SELECT BeaconID, BeaconUUID FROM Beacons", {}, { datasource: "payfrit" });
response.steps.append("Found " & qBeacons.recordCount & " beacons"); response.steps.append("Found " & qBeacons.recordCount & " beacons");
// Create service point for Table 1 if it doesn't exist // Create service point for Table 1 if it doesn't exist
qSP = queryExecute(" qSP = queryExecute("
SELECT ID FROM ServicePoints SELECT ServicePointID FROM ServicePoints
WHERE BusinessID = :bizID AND Name = 'Table 1' WHERE ServicePointBusinessID = :bizID AND ServicePointName = 'Table 1'
", { bizID: lazyDaisyID }, { datasource: "payfrit" }); ", { bizID: lazyDaisyID }, { datasource: "payfrit" });
if (qSP.recordCount == 0) { if (qSP.recordCount == 0) {
queryExecute(" queryExecute("
INSERT INTO ServicePoints (BusinessID, Name) INSERT INTO ServicePoints (ServicePointBusinessID, ServicePointName, ServicePointTypeID)
VALUES (:bizID, 'Table 1') VALUES (:bizID, 'Table 1', 1)
", { bizID: lazyDaisyID }, { datasource: "payfrit" }); ", { bizID: lazyDaisyID }, { datasource: "payfrit" });
qSP = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); qSP = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
servicePointID = qSP.id; servicePointID = qSP.id;
response.steps.append("Created service point 'Table 1' (ID: " & servicePointID & ")"); response.steps.append("Created service point 'Table 1' (ID: " & servicePointID & ")");
} else { } else {
servicePointID = qSP.ID; servicePointID = qSP.ServicePointID;
response.steps.append("Found existing service point 'Table 1' (ID: " & servicePointID & ")"); response.steps.append("Found existing service point 'Table 1' (ID: " & servicePointID & ")");
} }
// Assign all beacons to the Table 1 service point // Map all beacons to Lazy Daisy with Table 1
for (i = 1; i <= qBeacons.recordCount; i++) { for (i = 1; i <= qBeacons.recordCount; i++) {
beaconID = qBeacons.ID[i]; beaconID = qBeacons.BeaconID[i];
// Unassign this beacon from any other service point first // Check if mapping exists
queryExecute(" qMap = queryExecute("
UPDATE ServicePoints SET BeaconID = NULL, AssignedByUserID = NULL SELECT * FROM lt_Beacon_Businesses_ServicePoints WHERE BeaconID = :beaconID
WHERE BeaconID = :beaconID
", { beaconID: beaconID }, { datasource: "payfrit" }); ", { beaconID: beaconID }, { datasource: "payfrit" });
// Assign beacon to Table 1 service point if (qMap.recordCount == 0) {
queryExecute(" queryExecute("
UPDATE ServicePoints SET BeaconID = :beaconID, AssignedByUserID = 1 INSERT INTO lt_Beacon_Businesses_ServicePoints (BeaconID, BusinessID, ServicePointID)
WHERE ID = :spID AND BusinessID = :bizID VALUES (:beaconID, :bizID, :spID)
", { beaconID: beaconID, bizID: lazyDaisyID, spID: servicePointID }, { datasource: "payfrit" }); ", { beaconID: beaconID, bizID: lazyDaisyID, spID: servicePointID }, { datasource: "payfrit" });
response.steps.append("Assigned beacon " & beaconID & " to Table 1"); response.steps.append("Created mapping for beacon " & beaconID);
} else {
queryExecute("
UPDATE lt_Beacon_Businesses_ServicePoints
SET BusinessID = :bizID, ServicePointID = :spID
WHERE BeaconID = :beaconID
", { beaconID: beaconID, bizID: lazyDaisyID, spID: servicePointID }, { datasource: "payfrit" });
response.steps.append("Updated mapping for beacon " & beaconID);
}
} }
// Get final status // Get final status
qFinal = queryExecute(" qFinal = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.BeaconID, b.BeaconUUID, lt.BusinessID, biz.BusinessName, lt.ServicePointID, sp.ServicePointName
b.Name AS BeaconName, b.UUID, sp.Name AS ServicePointName, FROM lt_Beacon_Businesses_ServicePoints lt
biz.Name AS BusinessName JOIN Beacons b ON b.BeaconID = lt.BeaconID
FROM ServicePoints sp JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
JOIN Beacons b ON b.ID = sp.BeaconID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
JOIN Businesses biz ON biz.ID = sp.BusinessID
WHERE sp.BeaconID IS NOT NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
beacons = []; beacons = [];
for (i = 1; i <= qFinal.recordCount; i++) { for (i = 1; i <= qFinal.recordCount; i++) {
arrayAppend(beacons, { arrayAppend(beacons, {
"BeaconID": qFinal.BeaconID[i], "BeaconID": qFinal.BeaconID[i],
"UUID": qFinal.UUID[i], "UUID": qFinal.BeaconUUID[i],
"BusinessID": qFinal.BusinessID[i], "BusinessID": qFinal.BusinessID[i],
"BusinessName": qFinal.BusinessName[i], "BusinessName": qFinal.BusinessName[i],
"ServicePointID": qFinal.ServicePointID[i], "ServicePointID": qFinal.ServicePointID[i],

View file

@ -11,32 +11,32 @@
<cfscript> <cfscript>
/** /**
* Setup Modifier Templates system * Setup Modifier Templates system
* 1. Add IsModifierTemplate column to Items * 1. Add ItemIsModifierTemplate column to Items
* 2. Create lt_ItemID_TemplateItemID table * 2. Create ItemTemplateLinks table
*/ */
response = { "OK": false, "steps": [] }; response = { "OK": false, "steps": [] };
try { try {
// Step 1: Add IsModifierTemplate column if it doesn't exist // Step 1: Add ItemIsModifierTemplate column if it doesn't exist
try { try {
queryExecute(" queryExecute("
ALTER TABLE Items ADD COLUMN IsModifierTemplate TINYINT(1) DEFAULT 0 ALTER TABLE Items ADD COLUMN ItemIsModifierTemplate TINYINT(1) DEFAULT 0
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Added IsModifierTemplate column"); arrayAppend(response.steps, "Added ItemIsModifierTemplate column");
} catch (any e) { } catch (any e) {
if (findNoCase("Duplicate column", e.message)) { if (findNoCase("Duplicate column", e.message)) {
arrayAppend(response.steps, "IsModifierTemplate column already exists"); arrayAppend(response.steps, "ItemIsModifierTemplate column already exists");
} else { } else {
arrayAppend(response.steps, "Error adding column: " & e.message); arrayAppend(response.steps, "Error adding column: " & e.message);
} }
} }
// Step 2: Create lt_ItemID_TemplateItemID table if it doesn't exist // Step 2: Create ItemTemplateLinks table if it doesn't exist
try { try {
queryExecute(" queryExecute("
CREATE TABLE IF NOT EXISTS lt_ItemID_TemplateItemID ( CREATE TABLE IF NOT EXISTS ItemTemplateLinks (
ID INT AUTO_INCREMENT PRIMARY KEY, LinkID INT AUTO_INCREMENT PRIMARY KEY,
ItemID INT NOT NULL, ItemID INT NOT NULL,
TemplateItemID INT NOT NULL, TemplateItemID INT NOT NULL,
SortOrder INT DEFAULT 0, SortOrder INT DEFAULT 0,
@ -45,9 +45,9 @@ try {
INDEX idx_template (TemplateItemID) INDEX idx_template (TemplateItemID)
) )
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Created lt_ItemID_TemplateItemID table"); arrayAppend(response.steps, "Created ItemTemplateLinks table");
} catch (any e) { } catch (any e) {
arrayAppend(response.steps, "lt_ItemID_TemplateItemID: " & e.message); arrayAppend(response.steps, "ItemTemplateLinks: " & e.message);
} }
response["OK"] = true; response["OK"] = true;

View file

@ -13,20 +13,20 @@
<cfset queryExecute(" <cfset queryExecute("
CREATE TABLE IF NOT EXISTS Stations ( CREATE TABLE IF NOT EXISTS Stations (
StationID INT AUTO_INCREMENT PRIMARY KEY, StationID INT AUTO_INCREMENT PRIMARY KEY,
BusinessID INT NOT NULL, StationBusinessID INT NOT NULL,
Name VARCHAR(100) NOT NULL, StationName VARCHAR(100) NOT NULL,
Color VARCHAR(7) DEFAULT '##666666', StationColor VARCHAR(7) DEFAULT '##666666',
SortOrder INT DEFAULT 0, StationSortOrder INT DEFAULT 0,
IsActive TINYINT(1) DEFAULT 1, StationIsActive TINYINT(1) DEFAULT 1,
AddedOn DATETIME DEFAULT NOW(), StationAddedOn DATETIME DEFAULT NOW(),
FOREIGN KEY (BusinessID) REFERENCES Businesses(BusinessID) FOREIGN KEY (StationBusinessID) REFERENCES Businesses(BusinessID)
) )
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<!--- Add StationID column to Items table if it doesn't exist ---> <!--- Add ItemStationID column to Items table if it doesn't exist --->
<cftry> <cftry>
<cfset queryExecute(" <cfset queryExecute("
ALTER TABLE Items ADD COLUMN StationID INT DEFAULT NULL ALTER TABLE Items ADD COLUMN ItemStationID INT DEFAULT NULL
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfset stationColumnAdded = true> <cfset stationColumnAdded = true>
<cfcatch> <cfcatch>
@ -39,7 +39,7 @@
<cfif stationColumnAdded> <cfif stationColumnAdded>
<cftry> <cftry>
<cfset queryExecute(" <cfset queryExecute("
ALTER TABLE Items ADD FOREIGN KEY (StationID) REFERENCES Stations(StationID) ALTER TABLE Items ADD FOREIGN KEY (ItemStationID) REFERENCES Stations(StationID)
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfcatch></cfcatch> <cfcatch></cfcatch>
</cftry> </cftry>
@ -47,12 +47,12 @@
<!--- Create some default stations for business 1 (In and Out Burger) if none exist ---> <!--- Create some default stations for business 1 (In and Out Burger) if none exist --->
<cfset qCheck = queryExecute(" <cfset qCheck = queryExecute("
SELECT COUNT(*) AS cnt FROM Stations WHERE BusinessID = 1 SELECT COUNT(*) AS cnt FROM Stations WHERE StationBusinessID = 1
", [], { datasource = "payfrit" })> ", [], { datasource = "payfrit" })>
<cfif qCheck.cnt EQ 0> <cfif qCheck.cnt EQ 0>
<cfset queryExecute(" <cfset queryExecute("
INSERT INTO Stations (BusinessID, Name, Color, SortOrder) VALUES INSERT INTO Stations (StationBusinessID, StationName, StationColor, StationSortOrder) VALUES
(1, 'Grill', '##FF5722', 1), (1, 'Grill', '##FF5722', 1),
(1, 'Fry', '##FFC107', 2), (1, 'Fry', '##FFC107', 2),
(1, 'Drinks', '##2196F3', 3), (1, 'Drinks', '##2196F3', 3),

View file

@ -3,57 +3,36 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript> <cfscript>
// Switch beacon mapping from one business to another via join table. // Switch all beacons from one business to another
// Beacons.BusinessID (owner) is NOT touched.
fromBiz = 17; // In-N-Out fromBiz = 17; // In-N-Out
toBiz = 27; // Big Dean's toBiz = 27; // Big Dean's
// Remove mapping for source business
queryExecute(" queryExecute("
DELETE FROM lt_BeaconsID_BusinessesID UPDATE lt_Beacon_Businesses_ServicePoints
WHERE BusinessID = :fromBiz SET BusinessID = :toBiz
", { fromBiz: fromBiz }, { datasource: "payfrit" }); WHERE BusinessID = :fromBiz
// Add mapping for target business (for beacons owned by source)
queryExecute("
INSERT INTO lt_BeaconsID_BusinessesID (BeaconID, BusinessID)
SELECT ID, :toBiz FROM Beacons WHERE BusinessID = :fromBiz
ON DUPLICATE KEY UPDATE ID = ID
", { toBiz: toBiz, fromBiz: fromBiz }, { datasource: "payfrit" }); ", { toBiz: toBiz, fromBiz: fromBiz }, { datasource: "payfrit" });
// Clear ServicePoints.BeaconID for source business (no longer valid)
queryExecute("
UPDATE ServicePoints
SET BeaconID = NULL, AssignedByUserID = NULL
WHERE BusinessID = :fromBiz AND BeaconID IS NOT NULL
", { fromBiz: fromBiz }, { datasource: "payfrit" });
// Get current state // Get current state
q = queryExecute(" q = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID, SELECT lt.*, b.BusinessName
b.Name AS BeaconName, biz.Name AS BusinessName, FROM lt_Beacon_Businesses_ServicePoints lt
sp.Name AS ServicePointName JOIN Businesses b ON b.BusinessID = lt.BusinessID
FROM ServicePoints sp
JOIN Beacons b ON b.ID = sp.BeaconID
JOIN Businesses biz ON biz.ID = sp.BusinessID
WHERE sp.BeaconID IS NOT NULL
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
rows = []; rows = [];
for (row in q) { for (row in q) {
arrayAppend(rows, { arrayAppend(rows, {
"BeaconID": row.BeaconID, "BeaconID": row.BeaconID,
"BeaconName": row.BeaconName, "BusinessID": row.BusinessID,
"BusinessID": row.BusinessID, "BusinessName": row.BusinessName,
"BusinessName": row.BusinessName, "ServicePointID": row.ServicePointID
"ServicePointID": row.ServicePointID, });
"ServicePointName": row.ServicePointName
});
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"MESSAGE": "Switched beacons from BusinessID #fromBiz# to #toBiz#", "MESSAGE": "Switched beacons from BusinessID #fromBiz# to #toBiz#",
"MAPPINGS": rows "MAPPINGS": rows
})); }));
</cfscript> </cfscript>

View file

@ -7,10 +7,10 @@
<cfset queryExecute( <cfset queryExecute(
" "
INSERT INTO Tasks ( INSERT INTO Tasks (
BusinessID, TaskBusinessID,
OrderID, TaskOrderID,
ClaimedByUserID, TaskClaimedByUserID,
CreatedOn TaskAddedOn
) VALUES ( ) VALUES (
1, 1,
999, 999,

View file

@ -2,16 +2,16 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript> <cfscript>
qTask = queryExecute(" qTask = queryExecute("
SELECT ID, TaskTypeID, ClaimedByUserID, CompletedOn SELECT TaskID, TaskTypeID, TaskClaimedByUserID, TaskCompletedOn
FROM Tasks FROM Tasks
WHERE ID = 57 WHERE TaskID = 57
", [], { datasource: "payfrit" }); ", [], { datasource: "payfrit" });
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"TaskID": qTask.ID, "TaskID": qTask.TaskID,
"TaskTypeID": qTask.TaskTypeID, "TaskTypeID": qTask.TaskTypeID,
"ClaimedByUserID": qTask.ClaimedByUserID, "TaskClaimedByUserID": qTask.TaskClaimedByUserID,
"CompletedOn": qTask.CompletedOn "TaskCompletedOn": qTask.TaskCompletedOn
})); }));
</cfscript> </cfscript>

View file

@ -3,65 +3,50 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript> <cfscript>
// Update beacon mapping via join table. Owner (Beacons.BusinessID) is NOT changed. // Update Beacon 2 to point to In-N-Out (BusinessID 17)
beaconId = 2; beaconId = 2;
oldBusinessId = 37; // previous mapping
newBusinessId = 17; newBusinessId = 17;
// Remove old mapping
queryExecute(" queryExecute("
DELETE FROM lt_BeaconsID_BusinessesID UPDATE lt_Beacon_Businesses_ServicePoints
WHERE BeaconID = :beaconId AND BusinessID = :oldBizId SET BusinessID = :newBizId
", { beaconId: beaconId, oldBizId: oldBusinessId }, { datasource: "payfrit" }); WHERE BeaconID = :beaconId
", { newBizId: newBusinessId, beaconId: beaconId }, { datasource: "payfrit" });
// Add new mapping
queryExecute("
INSERT INTO lt_BeaconsID_BusinessesID (BeaconID, BusinessID)
VALUES (:beaconId, :newBizId)
ON DUPLICATE KEY UPDATE ID = ID
", { beaconId: beaconId, newBizId: newBusinessId }, { datasource: "payfrit" });
// Clear ServicePoints.BeaconID for old business where this beacon was assigned
queryExecute("
UPDATE ServicePoints
SET BeaconID = NULL, AssignedByUserID = NULL
WHERE BeaconID = :beaconId AND BusinessID = :oldBizId
", { beaconId: beaconId, oldBizId: oldBusinessId }, { datasource: "payfrit" });
// Get current state // Get current state
q = queryExecute(" q = queryExecute("
SELECT SELECT
b.ID AS BeaconID, b.BeaconID,
b.UUID, b.BeaconUUID,
b.Name AS BeaconName, b.BeaconName,
b.BusinessID AS BeaconBusinessID, lt.BusinessID,
sp.ID AS ServicePointID, lt.ServicePointID,
sp.Name AS ServicePointName, biz.BusinessName,
sp.BusinessID AS ServicePointBusinessID, sp.ServicePointName
biz.Name AS BusinessName FROM Beacons b
FROM Beacons b LEFT JOIN lt_Beacon_Businesses_ServicePoints lt ON lt.BeaconID = b.BeaconID
LEFT JOIN ServicePoints sp ON sp.BeaconID = b.ID LEFT JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
LEFT JOIN Businesses biz ON biz.ID = b.BusinessID LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
WHERE b.IsActive = 1 WHERE b.BeaconIsActive = 1
ORDER BY b.ID ORDER BY b.BeaconID
", {}, { datasource: "payfrit" }); ", {}, { datasource: "payfrit" });
rows = []; rows = [];
for (row in q) { for (row in q) {
arrayAppend(rows, { arrayAppend(rows, {
"BeaconID": row.BeaconID, "BeaconID": row.BeaconID,
"UUID": row.UUID, "BeaconUUID": row.BeaconUUID,
"BeaconName": row.BeaconName, "BeaconName": row.BeaconName ?: "",
"BeaconBusinessID": row.BeaconBusinessID, "BusinessID": row.BusinessID ?: 0,
"BusinessName": row.BusinessName, "BusinessName": row.BusinessName ?: "",
"ServicePointID": row.ServicePointID ?: 0, "ServicePointID": row.ServicePointID ?: 0,
"ServicePointName": row.ServicePointName ?: "" "ServicePointName": row.ServicePointName ?: ""
}); });
} }
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"MESSAGE": "Updated beacon #beaconId# to BusinessID #newBusinessId#", "MESSAGE": "Updated beacon #beaconId# to BusinessID #newBusinessId#",
"BEACONS": rows "BEACONS": rows
})); }));
</cfscript> </cfscript>

View file

@ -6,68 +6,72 @@
// Update Big Dean's business info // Update Big Dean's business info
businessId = 27; businessId = 27;
// Big Dean's actual info // Big Dean's actual address and hours
address = "1615 Ocean Front Walk, Santa Monica, CA 90401";
phone = "(310) 393-2666"; phone = "(310) 393-2666";
hours = "Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm"; hours = "Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm";
try { try {
// Update phone and hours on Businesses table // First get column names from INFORMATION_SCHEMA
cols = queryExecute("
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit' AND TABLE_NAME = 'Businesses'
ORDER BY ORDINAL_POSITION
");
colNames = [];
for (c in cols) {
arrayAppend(colNames, c.COLUMN_NAME);
}
// Check if we have the columns we need
hasAddress = arrayFindNoCase(colNames, "BusinessAddress") > 0;
hasPhone = arrayFindNoCase(colNames, "BusinessPhone") > 0;
hasHours = arrayFindNoCase(colNames, "BusinessHours") > 0;
// Add columns if missing
if (!hasAddress) {
queryExecute("ALTER TABLE Businesses ADD COLUMN BusinessAddress VARCHAR(255)");
}
if (!hasPhone) {
queryExecute("ALTER TABLE Businesses ADD COLUMN BusinessPhone VARCHAR(50)");
}
if (!hasHours) {
queryExecute("ALTER TABLE Businesses ADD COLUMN BusinessHours VARCHAR(255)");
}
// Update with new info
queryExecute(" queryExecute("
UPDATE Businesses UPDATE Businesses
SET Phone = :phone, SET BusinessAddress = :address,
Hours = :hours BusinessPhone = :phone,
WHERE ID = :bizId BusinessHours = :hours
WHERE BusinessID = :bizId
", { ", {
address: address,
phone: phone, phone: phone,
hours: hours, hours: hours,
bizId: businessId bizId: businessId
}, { datasource: "payfrit" }); });
// Update or insert address in Addresses table
qAddr = queryExecute("
SELECT ID FROM Addresses
WHERE BusinessID = :bizId AND IsDeleted = 0
LIMIT 1
", { bizId: businessId }, { datasource: "payfrit" });
if (qAddr.recordCount > 0) {
queryExecute("
UPDATE Addresses
SET Line1 = :line1, City = :city, ZIPCode = :zip
WHERE ID = :addrId
", {
line1: "1615 Ocean Front Walk",
city: "Santa Monica",
zip: "90401",
addrId: qAddr.ID
}, { datasource: "payfrit" });
} else {
queryExecute("
INSERT INTO Addresses (BusinessID, UserID, AddressTypeID, Line1, City, ZIPCode, AddedOn)
VALUES (:bizId, 0, 'business', :line1, :city, :zip, NOW())
", {
bizId: businessId,
line1: "1615 Ocean Front Walk",
city: "Santa Monica",
zip: "90401"
}, { datasource: "payfrit" });
}
// Get updated record // Get updated record
updated = queryExecute(" updated = queryExecute("
SELECT ID, Name, Phone, Hours SELECT BusinessID, BusinessName, BusinessAddress, BusinessPhone, BusinessHours
FROM Businesses FROM Businesses
WHERE ID = :bizId WHERE BusinessID = :bizId
", { bizId: businessId }, { datasource: "payfrit" }); ", { bizId: businessId });
writeOutput(serializeJSON({ writeOutput(serializeJSON({
"OK": true, "OK": true,
"MESSAGE": "Updated Big Dean's info", "MESSAGE": "Updated Big Dean's info",
"COLUMNS_EXISTED": { "address": hasAddress, "phone": hasPhone, "hours": hasHours },
"BUSINESS": { "BUSINESS": {
"BusinessID": updated.ID, "BusinessID": updated.BusinessID,
"Name": updated.Name, "BusinessName": updated.BusinessName,
"Phone": updated.Phone, "BusinessAddress": updated.BusinessAddress,
"Hours": updated.Hours "BusinessPhone": updated.BusinessPhone,
"BusinessHours": updated.BusinessHours
} }
})); }));

View file

@ -1,67 +0,0 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8">
<!---
About Screen Content API
Returns content for the mobile app's About screen.
Edit this file to update the app's about information without releasing a new app version.
--->
<cfscript>
try {
// Features displayed on the About screen
// icon: Flutter icon name (see AboutFeature._iconMap in about_info.dart)
features = [
{
"ICON": "qr_code_scanner",
"TITLE": "Scan & Order",
"DESCRIPTION": "Scan the table beacon to browse the menu and order directly from your phone"
},
{
"ICON": "group",
"TITLE": "Group Orders",
"DESCRIPTION": "Invite friends to join your order and split the bill easily"
},
{
"ICON": "delivery_dining",
"TITLE": "Delivery & Takeaway",
"DESCRIPTION": "Order for delivery or pick up when dining in isn't an option"
},
{
"ICON": "payment",
"TITLE": "Easy Payment",
"DESCRIPTION": "Pay your share securely with just a few taps"
}
];
// Contact links displayed on the About screen
// icon: Flutter icon name (see AboutContact._iconMap in about_info.dart)
contacts = [
{
"ICON": "help_outline",
"LABEL": "help.payfrit.com",
"URL": "https://help.payfrit.com"
},
{
"ICON": "language",
"LABEL": "www.payfrit.com",
"URL": "https://www.payfrit.com"
}
];
writeOutput(serializeJSON({
"OK": true,
"DESCRIPTION": "Payfrit makes dining out easier. Order from your table, split the bill with friends, and pay without waiting.",
"FEATURES": features,
"CONTACTS": contacts,
"COPYRIGHT": "© #year(now())# Payfrit. All rights reserved."
}));
} catch (any e) {
writeOutput(serializeJSON({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message
}));
}
</cfscript>

View file

@ -34,20 +34,28 @@ if (!structKeyExists(request,"BusinessID") || !isNumeric(request.BusinessID) ||
/* ---------- INPUT ---------- */ /* ---------- INPUT ---------- */
data = readJsonBody(); data = readJsonBody();
if (!structKeyExists(data,"ServicePointID") || !isNumeric(data.ServicePointID) || int(data.ServicePointID) LTE 0){ if (
apiAbort({OK=false,ERROR="missing_ServicePointID"}); !structKeyExists(data,"lt_Beacon_Businesses_ServicePointID")
|| !isNumeric(data.lt_Beacon_Businesses_ServicePointID)
|| int(data.lt_Beacon_Businesses_ServicePointID) LTE 0
){
apiAbort({OK=false,ERROR="missing_lt_Beacon_Businesses_ServicePointID"});
} }
ServicePointID = int(data.ServicePointID); RelID = int(data.lt_Beacon_Businesses_ServicePointID);
</cfscript> </cfscript>
<!--- Confirm the service point exists for this business and has a beacon assigned ---> <!--- Confirm the row exists for this BusinessID (and capture what it was) --->
<cfquery name="qFind" datasource="payfrit"> <cfquery name="qFind" datasource="payfrit">
SELECT ID, BeaconID SELECT
FROM ServicePoints lt_Beacon_Businesses_ServicePointID,
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#ServicePointID#"> BeaconID,
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#"> ServicePointID
AND BeaconID IS NOT NULL FROM lt_Beacon_Businesses_ServicePoints
WHERE lt_Beacon_Businesses_ServicePointID =
<cfqueryparam cfsqltype="cf_sql_integer" value="#RelID#">
AND BusinessID =
<cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
LIMIT 1 LIMIT 1
</cfquery> </cfquery>
@ -55,28 +63,28 @@ ServicePointID = int(data.ServicePointID);
<cfoutput>#serializeJSON({ <cfoutput>#serializeJSON({
"OK"=false, "OK"=false,
"ERROR"="not_found", "ERROR"="not_found",
"ServicePointID"=ServicePointID, "lt_Beacon_Businesses_ServicePointID"=RelID,
"BusinessID"=(request.BusinessID & "") "BusinessID"=(request.BusinessID & "")
})#</cfoutput> })#</cfoutput>
<cfabort> <cfabort>
</cfif> </cfif>
<cfset removedBeaconID = qFind.BeaconID> <!--- Delete it --->
<!--- Unassign beacon from service point --->
<cfquery datasource="payfrit"> <cfquery datasource="payfrit">
UPDATE ServicePoints DELETE FROM lt_Beacon_Businesses_ServicePoints
SET BeaconID = NULL, WHERE lt_Beacon_Businesses_ServicePointID =
AssignedByUserID = NULL <cfqueryparam cfsqltype="cf_sql_integer" value="#RelID#">
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#ServicePointID#"> AND BusinessID =
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#"> <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
LIMIT 1
</cfquery> </cfquery>
<cfoutput>#serializeJSON({ <cfoutput>#serializeJSON({
"OK"=true, "OK"=true,
"ERROR"="", "ERROR"="",
"ACTION"="unassigned", "ACTION"="deleted",
"ServicePointID"=ServicePointID, "lt_Beacon_Businesses_ServicePointID"=RelID,
"BeaconID"=removedBeaconID, "BeaconID"=qFind.BeaconID,
"ServicePointID"=qFind.ServicePointID,
"BusinessID"=(request.BusinessID & "") "BusinessID"=(request.BusinessID & "")
})#</cfoutput> })#</cfoutput>

View file

@ -17,29 +17,32 @@ if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) ||
<cfquery name="q" datasource="payfrit"> <cfquery name="q" datasource="payfrit">
SELECT SELECT
sp.ID AS ServicePointID, lt.lt_Beacon_Businesses_ServicePointID,
sp.BeaconID, lt.BeaconID,
sp.BusinessID, lt.BusinessID,
sp.AssignedByUserID, lt.ServicePointID,
b.Name AS BeaconName, lt.lt_Beacon_Businesses_ServicePointNotes,
b.UUID, b.BeaconName,
sp.Name AS ServicePointName b.BeaconUUID,
FROM ServicePoints sp sp.ServicePointName
JOIN Beacons b ON b.ID = sp.BeaconID FROM lt_Beacon_Businesses_ServicePoints lt
WHERE sp.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#"> JOIN Beacons b ON b.BeaconID = lt.BeaconID
AND sp.BeaconID IS NOT NULL LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
ORDER BY b.Name, sp.Name WHERE lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
ORDER BY b.BeaconName, sp.ServicePointName
</cfquery> </cfquery>
<cfset assignments = []> <cfset assignments = []>
<cfloop query="q"> <cfloop query="q">
<cfset arrayAppend(assignments, { <cfset arrayAppend(assignments, {
"ServicePointID" = q.ServicePointID, "lt_Beacon_Businesses_ServicePointID" = q.lt_Beacon_Businesses_ServicePointID,
"BeaconID" = q.BeaconID, "BeaconID" = q.BeaconID,
"BusinessID" = q.BusinessID, "BusinessID" = q.BusinessID,
"ServicePointID" = q.ServicePointID,
"BeaconName" = q.BeaconName, "BeaconName" = q.BeaconName,
"UUID" = q.UUID, "BeaconUUID" = q.BeaconUUID,
"ServicePointName"= q.ServicePointName "ServicePointName"= q.ServicePointName,
"lt_Beacon_Businesses_ServicePointNotes" = q.lt_Beacon_Businesses_ServicePointNotes
})> })>
</cfloop> </cfloop>

Some files were not shown because too many files have changed in this diff Show more