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>
<cfquery name="check_user" datasource="#application.datasource#">
SELECT FirstName, Balance, ImageExtension
SELECT UserFirstName, UserBalance, UserImageExtension
FROM Users
WHERE ID = #request.UserID#
WHERE UserID = #request.UserID#
</cfquery>
</cfif>
@ -320,8 +320,8 @@
<cfif find("logout.cfm", request.cgiPath) EQ 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>
Hi, <cfif check_user.FirstName gt "">#check_user.FirstName#<br>#dollarformat(check_user.Balance)#<cfelse>Payfrit User</cfif><br>
<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.UserFirstName gt "">#check_user.UserFirstName#<br>#dollarformat(check_user.UserBalance)#<cfelse>Payfrit User</cfif><br>
<cfelse>
<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>
<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
WHERE A.UserID = D.UserID
AND
A.ItemID = C.ItemID
AND
B.ID = C.BusinessID
B.BusinessID = C.BusinessID
AND
C.BusinessID = #form.bizid#
AND
@ -170,11 +170,11 @@
</CFQUERY>
<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
WHERE O.BusinessID = M.BusinessID
AND
M.UserID = U.ID
M.UserID = U.UserID
ORDER BY O.AddedOn DESC
</CFQUERY>
@ -185,7 +185,7 @@
)
VALUES
(
#get_last_inserted.ID#,
#get_last_inserted.OrderID#,
#get_queued_food.CartID#
)
</CFQUERY>
@ -268,7 +268,7 @@
<CFQUERY name="get_user_104_balance" datasource="#application.datasource#" dbtype="ODBC">
SELECT balance
FROM Users
WHERE ID = 104
WHERE UserID = 104
</CFQUERY>
<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">
SELECT balance
FROM Users
WHERE ID = 104
WHERE UserID = 104
</CFQUERY>
<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");
tr.innerHTML = `
<td>${a.lt_Beacon_Businesses_ServicePointID}</td>
<td>${escapeHtml((a.Name || "") + " (ID " + a.BeaconID + ")")}</td>
<td>${escapeHtml((a.Name || "") + " (ID " + a.ServicePointID + ")")}</td>
<td>${escapeHtml((a.BeaconName || "") + " (ID " + a.BeaconID + ")")}</td>
<td>${escapeHtml((a.ServicePointName || "") + " (ID " + a.ServicePointID + ")")}</td>
<td>${escapeHtml(a.lt_Beacon_Businesses_ServicePointNotes || "")}</td>
<td>${escapeHtml(a.CreatedAt || "")}</td>
`;
@ -183,12 +183,12 @@ async function refreshBeacons(assignedBeaconIDs, keepSelectedBeaconID){
setSelectPlaceholder(sel, "-- Select Beacon --");
(out.BEACONS || []).forEach(b => {
const isAssigned = assignedBeaconIDs.has(String(b.ID));
const isAssigned = assignedBeaconIDs.has(String(b.BeaconID));
if (HIDE_ASSIGNED_BEACONS && isAssigned) return;
const opt = document.createElement("option");
opt.value = b.ID;
opt.textContent = String(b.ID) + " - " + (b.Name || "");
opt.value = b.BeaconID;
opt.textContent = String(b.BeaconID) + " - " + (b.BeaconName || "");
sel.appendChild(opt);
});
@ -203,12 +203,12 @@ async function refreshServicePoints(assignedServicePointIDs, keepSelectedService
setSelectPlaceholder(sel, "-- Select ServicePoint --");
(out.SERVICEPOINTS || []).forEach(sp => {
const isAssigned = assignedServicePointIDs.has(String(sp.ID));
const isAssigned = assignedServicePointIDs.has(String(sp.ServicePointID));
if (HIDE_ASSIGNED_SERVICEPOINTS && isAssigned) return;
const opt = document.createElement("option");
opt.value = sp.ID;
opt.textContent = String(sp.ID) + " - " + (sp.Name || "");
opt.value = sp.ServicePointID;
opt.textContent = String(sp.ServicePointID) + " - " + (sp.ServicePointName || "");
sel.appendChild(opt);
});

View file

@ -25,7 +25,7 @@
</head>
<body>
<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="row">
@ -38,8 +38,8 @@
</div>
<div>
<label>Name (required)</label><br>
<input id="Name" placeholder="Front Door" required>
<label>BeaconName (required)</label><br>
<input id="BeaconName" placeholder="Front Door" required>
</div>
<div>
@ -160,13 +160,13 @@ function escapeHtml(s){
}
function loadIntoForm(b){
document.getElementById("BeaconID").value = b.ID || "";
document.getElementById("Name").value = b.Name || "";
document.getElementById("BeaconID").value = b.BeaconID || "";
document.getElementById("BeaconName").value = b.BeaconName || "";
document.getElementById("UUID").value = b.UUID || "";
document.getElementById("NamespaceId").value = b.NamespaceId || "";
document.getElementById("InstanceId").value = b.InstanceId || "";
document.getElementById("IsActive").value = ("" + (b.IsActive ?? 1));
document.getElementById("DelBeaconID").value = b.ID || "";
document.getElementById("DelBeaconID").value = b.BeaconID || "";
}
async function refresh() {
@ -180,8 +180,8 @@ async function refresh() {
for (const b of items) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${b.ID}</td>
<td>${escapeHtml(b.Name||"")}</td>
<td>${b.BeaconID}</td>
<td>${escapeHtml(b.BeaconName||"")}</td>
<td>${escapeHtml(b.UUID||"")}</td>
<td>${escapeHtml(b.NamespaceId||"")}</td>
<td>${escapeHtml(b.InstanceId||"")}</td>
@ -194,15 +194,15 @@ async function refresh() {
}
async function saveBeacon() {
const name = (document.getElementById("Name").value || "").trim();
const name = (document.getElementById("BeaconName").value || "").trim();
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;
}
const body = {
BeaconID: valIntOrNull("BeaconID"),
Name: name,
BeaconName: name,
UUID: (document.getElementById("UUID").value || "").trim(),
NamespaceId: (document.getElementById("NamespaceId").value || "").trim(),
InstanceId: (document.getElementById("InstanceId").value || "").trim(),

View file

@ -7,16 +7,16 @@
<cfparam name="users_to_email" default="">
<cfquery name="select_users_to_email" datasource="#application.datasource#">
SELECT U.EmailAddress
SELECT U.UserEmailAddress
FROM Users U
WHERE UserID in (0,1,2)
</cfquery>
<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>
@ -95,18 +95,18 @@
<cfloop index="the_email_address" list="#users_to_email#">
<cfquery name="get_user_email" datasource="#application.datasource#">
SELECT UUID
SELECT UserUUID
FROM Users
WHERE EmailAddress = '#the_email_address#'
WHERE UserEmailAddress = '#the_email_address#'
AND
UserIsEmailverified = 1
AND
IsContactVerified > 0
UserIsContactVerified > 0
</cfquery>
<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">
#HTMLCodeFormat(form.this_email_body)#</cfmail>

View file

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

View file

@ -25,7 +25,7 @@
</head>
<body>
<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="row">
@ -38,18 +38,18 @@
</div>
<div>
<label>Name (required)</label><br>
<input id="Name" placeholder="Front Counter" required>
<label>ServicePointName (required)</label><br>
<input id="ServicePointName" placeholder="Front Counter" required>
</div>
<div>
<label>TypeID</label><br>
<input id="TypeID" placeholder="0">
<label>ServicePointTypeID</label><br>
<input id="ServicePointTypeID" placeholder="0">
</div>
<div>
<label>Code</label><br>
<input id="Code" placeholder="COUNTER">
<label>ServicePointCode</label><br>
<input id="ServicePointCode" placeholder="COUNTER">
</div>
<div>
@ -160,14 +160,14 @@ function escapeHtml(s){
}
function loadIntoForm(sp){
document.getElementById("ServicePointID").value = sp.ID || "";
document.getElementById("Name").value = sp.Name || "";
document.getElementById("TypeID").value = (sp.TypeID ?? 0);
document.getElementById("Code").value = sp.Code || "";
document.getElementById("ServicePointID").value = sp.ServicePointID || "";
document.getElementById("ServicePointName").value = sp.ServicePointName || "";
document.getElementById("ServicePointTypeID").value = (sp.ServicePointTypeID ?? 0);
document.getElementById("ServicePointCode").value = sp.ServicePointCode || "";
document.getElementById("Description").value = sp.Description || "";
document.getElementById("SortOrder").value = (sp.SortOrder ?? 0);
document.getElementById("IsActive").value = ("" + (sp.IsActive ?? 1));
document.getElementById("DelServicePointID").value = sp.ID || "";
document.getElementById("DelServicePointID").value = sp.ServicePointID || "";
}
async function refresh() {
@ -181,10 +181,10 @@ async function refresh() {
for (const sp of items) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${sp.ID}</td>
<td>${escapeHtml(sp.Name||"")}</td>
<td>${sp.TypeID}</td>
<td>${escapeHtml(sp.Code||"")}</td>
<td>${sp.ServicePointID}</td>
<td>${escapeHtml(sp.ServicePointName||"")}</td>
<td>${sp.ServicePointTypeID}</td>
<td>${escapeHtml(sp.ServicePointCode||"")}</td>
<td>${sp.SortOrder}</td>
<td>${sp.IsActive}</td>
`;
@ -195,17 +195,17 @@ async function refresh() {
}
async function saveSP() {
const name = (document.getElementById("Name").value || "").trim();
const name = (document.getElementById("ServicePointName").value || "").trim();
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;
}
const body = {
ServicePointID: valIntOrNull("ServicePointID"),
Name: name,
TypeID: valIntOrZero("TypeID"),
Code: (document.getElementById("Code").value || "").trim(),
ServicePointName: name,
ServicePointTypeID: valIntOrZero("ServicePointTypeID"),
ServicePointCode: (document.getElementById("ServicePointCode").value || "").trim(),
Description: (document.getElementById("Description").value || "").trim(),
SortOrder: valIntOrZero("SortOrder"),
IsActive: parseInt(document.getElementById("IsActive").value, 10)

View file

@ -173,11 +173,6 @@ if (len(request._api_path)) {
// Worker app endpoints
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
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/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/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/listCategories.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/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
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
try {
// Check if columns already exist
checkCols = queryExecute(
"SHOW COLUMNS FROM Addresses LIKE 'Latitude'",
"SHOW COLUMNS FROM Addresses LIKE 'AddressLat'",
[],
{ datasource = "payfrit" }
);
@ -15,8 +15,8 @@ try {
// Add the columns
queryExecute(
"ALTER TABLE Addresses
ADD COLUMN Latitude DECIMAL(10,7) NULL,
ADD COLUMN Longitude DECIMAL(10,7) NULL",
ADD COLUMN AddressLat DECIMAL(10,7) NULL,
ADD COLUMN AddressLng DECIMAL(10,7) NULL",
[],
{ 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">
<cfscript>
// Add SourceType and SourceID columns to Tasks table
// Add TaskSourceType and TaskSourceID columns to Tasks table
// These are needed for chat persistence feature
result = { "OK": true, "STEPS": [] };
@ -15,30 +15,30 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Tasks'
AND COLUMN_NAME IN ('SourceType', 'SourceID')
AND COLUMN_NAME IN ('TaskSourceType', 'TaskSourceID')
", [], { datasource: "payfrit" });
existingCols = valueList(cols.COLUMN_NAME);
arrayAppend(result.STEPS, "Existing columns: #existingCols#");
// Add SourceType if missing
if (!listFindNoCase(existingCols, "SourceType")) {
// Add TaskSourceType if missing
if (!listFindNoCase(existingCols, "TaskSourceType")) {
queryExecute("
ALTER TABLE Tasks ADD COLUMN SourceType VARCHAR(50) NULL
ALTER TABLE Tasks ADD COLUMN TaskSourceType VARCHAR(50) NULL
", [], { datasource: "payfrit" });
arrayAppend(result.STEPS, "Added SourceType column");
arrayAppend(result.STEPS, "Added TaskSourceType column");
} else {
arrayAppend(result.STEPS, "SourceType already exists");
arrayAppend(result.STEPS, "TaskSourceType already exists");
}
// Add SourceID if missing
if (!listFindNoCase(existingCols, "SourceID")) {
// Add TaskSourceID if missing
if (!listFindNoCase(existingCols, "TaskSourceID")) {
queryExecute("
ALTER TABLE Tasks ADD COLUMN SourceID INT NULL
ALTER TABLE Tasks ADD COLUMN TaskSourceID INT NULL
", [], { datasource: "payfrit" });
arrayAppend(result.STEPS, "Added SourceID column");
arrayAppend(result.STEPS, "Added TaskSourceID column");
} else {
arrayAppend(result.STEPS, "SourceID already exists");
arrayAppend(result.STEPS, "TaskSourceID already exists");
}
// Verify columns now exist
@ -47,7 +47,7 @@ try {
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'payfrit'
AND TABLE_NAME = 'Tasks'
AND COLUMN_NAME IN ('SourceType', 'SourceID')
AND COLUMN_NAME IN ('TaskSourceType', 'TaskSourceID')
", [], { datasource: "payfrit" });
result.COLUMNS = [];

View file

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

View file

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

View file

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

View file

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

View file

@ -13,10 +13,10 @@
* Cleanup Categories - Final step after migration verification
*
* 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)
* 3. Drops CategoryID column
* 4. Drops IsModifierTemplate column (derived from lt_ItemID_TemplateItemID now)
* 3. Drops ItemCategoryID column
* 4. Drops ItemIsModifierTemplate column (derived from ItemTemplateLinks now)
* 5. Drops Categories table
*
* Query param: ?confirm=YES to actually execute (otherwise shows verification only)
@ -30,7 +30,7 @@ try {
// Verification Step 1: Check for items without BusinessID
qNoBusinessID = queryExecute("
SELECT COUNT(*) as cnt FROM Items
WHERE BusinessID IS NULL OR BusinessID = 0
WHERE ItemBusinessID IS NULL OR ItemBusinessID = 0
", {}, { datasource: "payfrit" });
response.verification["itemsWithoutBusinessID"] = qNoBusinessID.cnt;
@ -46,38 +46,38 @@ try {
qCategoryItems = queryExecute("
SELECT COUNT(DISTINCT p.ItemID) as cnt
FROM Items p
INNER JOIN Items c ON c.ParentItemID = p.ItemID
WHERE p.ParentItemID = 0
AND p.BusinessID > 0
INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.ItemParentItemID = 0
AND p.ItemBusinessID > 0
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" });
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("
SELECT COUNT(DISTINCT tl.TemplateItemID) as cnt
FROM lt_ItemID_TemplateItemID tl
FROM ItemTemplateLinks tl
INNER JOIN Items t ON t.ItemID = tl.TemplateItemID
", {}, { datasource: "payfrit" });
response.verification["templatesInLinks"] = qTemplates.cnt;
// 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("
SELECT i.ID, i.Name, i.BusinessID
SELECT i.ItemID, i.ItemName, i.ItemBusinessID
FROM Items i
WHERE i.ParentItemID = 0
WHERE i.ItemParentItemID = 0
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 (
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" });
response.verification["orphanCount"] = qOrphans.recordCount;
@ -85,8 +85,8 @@ try {
for (orphan in qOrphans) {
arrayAppend(response.orphans, {
"ItemID": orphan.ItemID,
"Name": orphan.Name,
"BusinessID": orphan.BusinessID
"ItemName": orphan.ItemName,
"BusinessID": orphan.ItemBusinessID
});
}
@ -96,7 +96,7 @@ try {
if (!safeToCleanup) {
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;
writeOutput(serializeJSON(response));
abort;
@ -119,31 +119,31 @@ try {
// Execute cleanup
arrayAppend(response.steps, "Executing cleanup...");
// Step 1: Drop CategoryID column
// Step 1: Drop ItemCategoryID column
try {
queryExecute("
ALTER TABLE Items DROP COLUMN CategoryID
ALTER TABLE Items DROP COLUMN ItemCategoryID
", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Dropped CategoryID column from Items");
arrayAppend(response.steps, "Dropped ItemCategoryID column from Items");
} catch (any e) {
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 {
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 {
queryExecute("
ALTER TABLE Items DROP COLUMN IsModifierTemplate
ALTER TABLE Items DROP COLUMN ItemIsModifierTemplate
", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Dropped IsModifierTemplate column from Items");
arrayAppend(response.steps, "Dropped ItemIsModifierTemplate column from Items");
} catch (any e) {
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 {
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 {
// Find duplicate UserIDs for this business (keep the one with highest status or oldest)
qDupes = queryExecute("
SELECT ID, COUNT(*) as cnt, MIN(ID) as keepId
FROM Employees
SELECT UserID, COUNT(*) as cnt, MIN(EmployeeID) as keepId
FROM lt_Users_Businesses_Employees
WHERE BusinessID = ?
GROUP BY UserID
HAVING COUNT(*) > 1
@ -45,8 +45,8 @@ try {
for (row in qDupes) {
// Delete all but the one we want to keep (the one with lowest EmployeeID)
qDel = queryExecute("
DELETE FROM Employees
WHERE BusinessID = ? AND UserID = ? AND ID != ?
DELETE FROM lt_Users_Businesses_Employees
WHERE BusinessID = ? AND UserID = ? AND EmployeeID != ?
", [
{ value: businessId, cfsqltype: "cf_sql_integer" },
{ value: row.UserID, cfsqltype: "cf_sql_integer" },
@ -57,19 +57,19 @@ try {
// Get remaining employees
qRemaining = queryExecute("
SELECT e.ID, e.UserID, u.FirstName, u.LastName
FROM Employees e
JOIN Users u ON e.UserID = u.ID
SELECT e.EmployeeID, e.UserID, u.UserFirstName, u.UserLastName
FROM lt_Users_Businesses_Employees e
JOIN Users u ON e.UserID = u.UserID
WHERE e.BusinessID = ?
ORDER BY e.ID
ORDER BY e.EmployeeID
", [{ value: businessId, cfsqltype: "cf_sql_integer" }], { datasource: "payfrit" });
remaining = [];
for (r in qRemaining) {
arrayAppend(remaining, {
"EmployeeID": r.ID,
"EmployeeID": r.EmployeeID,
"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)
keepBusinessID = 37;
// Unassign all beacons from service points of other businesses
// First, reassign all beacons to Lazy Daisy
queryExecute("
UPDATE ServicePoints
SET BeaconID = NULL, AssignedByUserID = NULL
WHERE BusinessID != :keepID AND BeaconID IS NOT NULL
UPDATE lt_Beacon_Businesses_ServicePoints
SET BusinessID = :keepID
", { 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
qBiz = queryExecute("
SELECT ID, Name FROM Businesses WHERE ID != :keepID
SELECT BusinessID, BusinessName FROM Businesses WHERE BusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
deletedBusinesses = [];
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");
// Delete related data first (foreign key constraints)
// Delete lt_ItemID_TemplateItemID for items from other businesses
// Delete ItemTemplateLinks for items from other businesses
queryExecute("
DELETE itl FROM lt_ItemID_TemplateItemID itl
JOIN Items i ON i.ID = itl.ItemID
WHERE i.BusinessID != :keepID
DELETE itl FROM ItemTemplateLinks itl
JOIN Items i ON i.ItemID = itl.ItemID
WHERE i.ItemBusinessID != :keepID
", { 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
qItems = queryExecute("
SELECT COUNT(*) as cnt FROM Items WHERE BusinessID != :keepID
SELECT COUNT(*) as cnt FROM Items WHERE ItemBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
queryExecute("
DELETE FROM Items WHERE BusinessID != :keepID
DELETE FROM Items WHERE ItemBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted " & qItems.cnt & " items from other businesses");
// Delete Categories for other businesses
queryExecute("
DELETE FROM Categories WHERE BusinessID != :keepID
DELETE FROM Categories WHERE CategoryBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted categories from other businesses");
// Delete Hours for other businesses
queryExecute("
DELETE FROM Hours WHERE BusinessID != :keepID
DELETE FROM Hours WHERE HoursBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
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 {
queryExecute("
DELETE FROM Employees WHERE BusinessID != :keepID
DELETE FROM Employees WHERE EmployeeBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted employees from other businesses");
} catch (any e) {
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 {
queryExecute("
DELETE FROM ServicePoints WHERE BusinessID != :keepID
DELETE FROM ServicePoints WHERE ServicePointBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted service points from other businesses");
} catch (any e) {
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 {
queryExecute("
DELETE FROM Stations WHERE BusinessID != :keepID
DELETE FROM Stations WHERE StationBusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted stations from other businesses");
} catch (any e) {
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
queryExecute("
DELETE FROM Businesses WHERE ID != :keepID
DELETE FROM Businesses WHERE BusinessID != :keepID
", { keepID: keepBusinessID }, { datasource: "payfrit" });
response.steps.append("Deleted " & arrayLen(deletedBusinesses) & " businesses");
// Get beacon status
qBeacons = queryExecute("
SELECT sp.ID AS ServicePointID, sp.BeaconID, sp.BusinessID,
b.UUID, biz.Name AS BusinessName, sp.Name AS ServicePointName
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
SELECT lt.BeaconID, b.BeaconUUID, lt.BusinessID, biz.BusinessName, lt.ServicePointID
FROM lt_Beacon_Businesses_ServicePoints lt
JOIN Beacons b ON b.BeaconID = lt.BeaconID
JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
", {}, { datasource: "payfrit" });
beacons = [];
for (i = 1; i <= qBeacons.recordCount; i++) {
arrayAppend(beacons, {
"BeaconID": qBeacons.BeaconID[i],
"UUID": qBeacons.UUID[i],
"UUID": qBeacons.BeaconUUID[i],
"BusinessID": qBeacons.BusinessID[i],
"BusinessName": qBeacons.BusinessName[i],
"ServicePointID": qBeacons.ServicePointID[i],
"ServicePointName": qBeacons.ServicePointName[i]
"ServicePointID": qBeacons.ServicePointID[i]
});
}

View file

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

View file

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

View file

@ -10,27 +10,27 @@ try {
businessIDs = [38, 39, 40, 41, 42];
for (bizID in businessIDs) {
// Delete lt_ItemID_TemplateItemID for items belonging to this business
// Delete ItemTemplateLinks for items belonging to this business
queryExecute("
DELETE itl FROM lt_ItemID_TemplateItemID itl
INNER JOIN Items i ON i.ID = itl.ItemID
WHERE i.BusinessID = :bizID
DELETE itl FROM ItemTemplateLinks itl
INNER JOIN Items i ON i.ItemID = itl.ItemID
WHERE i.ItemBusinessID = :bizID
", { bizID: bizID }, { datasource: "payfrit" });
// 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
queryExecute("DELETE FROM Categories WHERE BusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
queryExecute("DELETE FROM Categories WHERE CategoryBusinessID = :bizID", { bizID: bizID }, { datasource: "payfrit" });
// 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
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
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");
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,67 +5,67 @@
<cfscript>
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";
qTemplates = queryExecute("
SELECT ID, Name, IsCollapsible, IsActive, ParentItemID, BusinessID
SELECT ItemID, ItemName, ItemIsCollapsible, ItemIsActive, ItemParentItemID, ItemBusinessID
FROM Items
WHERE ID IN (#templateIds#)
ORDER BY Name
WHERE ItemID IN (#templateIds#)
ORDER BY ItemName
", {}, { datasource: "payfrit" });
templates = [];
for (row in qTemplates) {
arrayAppend(templates, {
"ItemID": row.ID,
"Name": row.Name,
"IsCollapsible": row.IsCollapsible,
"IsActive": row.IsActive,
"ParentID": row.ParentItemID,
"BusinessID": row.BusinessID
"ItemID": row.ItemID,
"ItemName": row.ItemName,
"IsCollapsible": row.ItemIsCollapsible,
"IsActive": row.ItemIsActive,
"ParentID": row.ItemParentItemID,
"BusinessID": row.ItemBusinessID
});
}
// 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("
SELECT DISTINCT t.ItemID, t.Name, t.IsCollapsible, t.IsActive
SELECT DISTINCT t.ItemID, t.ItemName, t.ItemIsCollapsible, t.ItemIsActive
FROM Items t
WHERE t.BusinessID = :bizId
AND t.ParentItemID = 0
WHERE t.ItemBusinessID = :bizId
AND t.ItemParentItemID = 0
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)
ORDER BY t.Name
AND EXISTS (SELECT 1 FROM Items child WHERE child.ItemParentItemID = t.ItemID)
ORDER BY t.ItemName
", { bizId: bizId }, { datasource: "payfrit" });
potentialTemplates = [];
for (row in qMissingTemplates) {
arrayAppend(potentialTemplates, {
"ItemID": row.ID,
"Name": row.Name,
"IsCollapsible": row.IsCollapsible,
"IsActive": row.IsActive
"ItemID": row.ItemID,
"ItemName": row.ItemName,
"IsCollapsible": row.ItemIsCollapsible,
"IsActive": row.ItemIsActive
});
}
// What templates SHOULD burgers have? Let's see all templates used by ANY item
qAllTemplateUsage = queryExecute("
SELECT t.ItemID, t.Name, COUNT(tl.ItemID) as UsageCount
FROM lt_ItemID_TemplateItemID tl
SELECT t.ItemID, t.ItemName, COUNT(tl.ItemID) as UsageCount
FROM ItemTemplateLinks tl
JOIN Items t ON t.ItemID = tl.TemplateItemID
JOIN Items mi ON mi.ID = tl.ItemID AND mi.BusinessID = :bizId
GROUP BY t.ItemID, t.Name
ORDER BY t.Name
JOIN Items mi ON mi.ItemID = tl.ItemID AND mi.ItemBusinessID = :bizId
GROUP BY t.ItemID, t.ItemName
ORDER BY t.ItemName
", { bizId: bizId }, { datasource: "payfrit" });
allTemplates = [];
for (row in qAllTemplateUsage) {
arrayAppend(allTemplates, {
"TemplateID": row.ID,
"TemplateName": row.Name,
"TemplateID": row.ItemID,
"TemplateName": row.ItemName,
"UsageCount": row.UsageCount
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,39 +3,39 @@
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Check lt_ItemID_TemplateItemID for Big Dean's (BusinessID 27)
// Check ItemTemplateLinks for Big Dean's (BusinessID 27)
bizId = 27;
// 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
qTemplates = queryExecute("
SELECT DISTINCT tl.TemplateItemID, i.Name
FROM lt_ItemID_TemplateItemID tl
JOIN Items i ON i.ID = tl.TemplateItemID
WHERE i.BusinessID = :bizId
SELECT DISTINCT tl.TemplateItemID, i.ItemName
FROM ItemTemplateLinks tl
JOIN Items i ON i.ItemID = tl.TemplateItemID
WHERE i.ItemBusinessID = :bizId
", { bizId: bizId }, { datasource: "payfrit" });
templates = [];
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)
qCategories = queryExecute("
SELECT i.ID, i.Name, i.ParentItemID, i.IsCollapsible
SELECT i.ItemID, i.ItemName, i.ItemParentItemID, i.ItemIsCollapsible
FROM Items i
WHERE i.BusinessID = :bizId
AND i.ParentItemID = 0
AND i.IsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID)
ORDER BY i.SortOrder
WHERE i.ItemBusinessID = :bizId
AND i.ItemParentItemID = 0
AND i.ItemIsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
ORDER BY i.ItemSortOrder
", { bizId: bizId }, { datasource: "payfrit" });
categories = [];
for (row in qCategories) {
arrayAppend(categories, { "ItemID": row.ID, "Name": row.Name });
arrayAppend(categories, { "ItemID": row.ItemID, "ItemName": row.ItemName });
}
writeOutput(serializeJSON({

View file

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

View file

@ -28,19 +28,19 @@ try {
response["ERROR"] = "BusinessID required";
} else {
// 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) {
response["ERROR"] = "Business not found";
} else {
addrID = qBiz.AddressID;
addrID = qBiz.BusinessAddressID;
// 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
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;

View file

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

View file

@ -11,7 +11,7 @@
<cfscript>
/**
* 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": [] };
@ -19,24 +19,24 @@ response = { "OK": false, "deleted": 0, "orphans": [] };
try {
// Find orphans
qOrphans = queryExecute("
SELECT i.ID, i.Name, i.BusinessID
SELECT i.ItemID, i.ItemName, i.ItemBusinessID
FROM Items i
WHERE i.ParentItemID = 0
WHERE i.ItemParentItemID = 0
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 (
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" });
orphanIDs = [];
for (orphan in qOrphans) {
arrayAppend(response.orphans, {
"ItemID": orphan.ItemID,
"Name": orphan.Name,
"BusinessID": orphan.BusinessID
"ItemName": orphan.ItemName,
"BusinessID": orphan.ItemBusinessID
});
arrayAppend(orphanIDs, orphan.ItemID);
}
@ -44,7 +44,7 @@ try {
// Delete them by ID list
if (arrayLen(orphanIDs) > 0) {
queryExecute("
DELETE FROM Items WHERE ID IN (#arrayToList(orphanIDs)#)
DELETE FROM Items WHERE ItemID IN (#arrayToList(orphanIDs)#)
", {}, { datasource: "payfrit" });
}

View file

@ -14,16 +14,16 @@
* Eliminate Categories Table - Schema Migration
*
* 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)
* - 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)
*
* 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
* 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
*/
@ -34,30 +34,30 @@ try {
dryRun = structKeyExists(url, "dryRun") && url.dryRun == 1;
response["dryRun"] = dryRun;
// Step 1: Add BusinessID column if it doesn't exist
// Step 1: Add ItemBusinessID column if it doesn't exist
try {
if (!dryRun) {
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" });
}
arrayAppend(response.steps, "Added BusinessID column to Items table");
arrayAppend(response.steps, "Added ItemBusinessID column to Items table");
} catch (any e) {
if (findNoCase("Duplicate column", e.message)) {
arrayAppend(response.steps, "BusinessID column already exists");
arrayAppend(response.steps, "ItemBusinessID column already exists");
} else {
throw(e);
}
}
// Step 2: Add index on BusinessID
// Step 2: Add index on ItemBusinessID
try {
if (!dryRun) {
queryExecute("
CREATE INDEX idx_item_business ON Items (BusinessID)
CREATE INDEX idx_item_business ON Items (ItemBusinessID)
", {}, { datasource: "payfrit" });
}
arrayAppend(response.steps, "Added index on BusinessID");
arrayAppend(response.steps, "Added index on ItemBusinessID");
} catch (any e) {
if (findNoCase("Duplicate key name", e.message)) {
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 {
if (!dryRun) {
queryExecute("
@ -84,9 +84,9 @@ try {
// Step 4: Get all Categories
qCategories = queryExecute("
SELECT ID, BusinessID, Name
SELECT CategoryID, CategoryBusinessID, CategoryName
FROM Categories
ORDER BY BusinessID, Name
ORDER BY CategoryBusinessID, CategoryName
", {}, { datasource: "payfrit" });
arrayAppend(response.steps, "Found " & qCategories.recordCount & " categories to migrate");
@ -94,29 +94,29 @@ try {
// Step 4: Migrate each category
for (cat in qCategories) {
migration = {
"oldCategoryID": cat.ID,
"categoryName": cat.Name,
"businessID": cat.BusinessID,
"oldCategoryID": cat.CategoryID,
"categoryName": cat.CategoryName,
"businessID": cat.CategoryBusinessID,
"newItemID": 0,
"itemsUpdated": 0
};
if (!dryRun) {
// 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("
INSERT INTO Items (
BusinessID,
CategoryID,
Name,
Description,
ParentItemID,
Price,
IsActive,
IsCheckedByDefault,
RequiresChildSelection,
SortOrder,
AddedOn
ItemBusinessID,
ItemCategoryID,
ItemName,
ItemDescription,
ItemParentItemID,
ItemPrice,
ItemIsActive,
ItemIsCheckedByDefault,
ItemRequiresChildSelection,
ItemSortOrder,
ItemAddedOn
) VALUES (
:businessID,
0,
@ -131,56 +131,56 @@ try {
NOW()
)
", {
businessID: cat.BusinessID,
categoryName: cat.Name
businessID: cat.CategoryBusinessID,
categoryName: cat.CategoryName
}, { datasource: "payfrit" });
// Get the new Item ID
qNewItem = queryExecute("
SELECT ID FROM Items
WHERE BusinessID = :businessID
AND Name = :categoryName
AND ParentItemID = 0
ORDER BY ID DESC
SELECT ItemID FROM Items
WHERE ItemBusinessID = :businessID
AND ItemName = :categoryName
AND ItemParentItemID = 0
ORDER BY ItemID DESC
LIMIT 1
", {
businessID: cat.BusinessID,
categoryName: cat.Name
businessID: cat.CategoryBusinessID,
categoryName: cat.CategoryName
}, { datasource: "payfrit" });
newItemID = qNewItem.ID;
newItemID = qNewItem.ItemID;
migration["newItemID"] = newItemID;
// Update menu items in this category:
// - Set ParentItemID = newItemID (for top-level items only)
// - Set BusinessID = businessID (for all items)
// - Set ItemParentItemID = newItemID (for top-level items only)
// - Set ItemBusinessID = businessID (for all items)
queryExecute("
UPDATE Items
SET BusinessID = :businessID,
ParentItemID = :newItemID
WHERE CategoryID = :categoryID
AND ParentItemID = 0
SET ItemBusinessID = :businessID,
ItemParentItemID = :newItemID
WHERE ItemCategoryID = :categoryID
AND ItemParentItemID = 0
", {
businessID: cat.BusinessID,
businessID: cat.CategoryBusinessID,
newItemID: newItemID,
categoryID: cat.ID
categoryID: cat.CategoryID
}, { datasource: "payfrit" });
// Set BusinessID on ALL items in this category (including nested)
// Set ItemBusinessID on ALL items in this category (including nested)
queryExecute("
UPDATE Items
SET BusinessID = :businessID
WHERE CategoryID = :categoryID
AND (BusinessID IS NULL OR BusinessID = 0)
SET ItemBusinessID = :businessID
WHERE ItemCategoryID = :categoryID
AND (ItemBusinessID IS NULL OR ItemBusinessID = 0)
", {
businessID: cat.BusinessID,
categoryID: cat.ID
businessID: cat.CategoryBusinessID,
categoryID: cat.CategoryID
}, { datasource: "payfrit" });
// Count how many were updated
qCount = queryExecute("
SELECT COUNT(*) as cnt FROM Items
WHERE ParentItemID = :newItemID
WHERE ItemParentItemID = :newItemID
", { newItemID: newItemID }, { datasource: "payfrit" });
migration["itemsUpdated"] = qCount.cnt;
@ -188,9 +188,9 @@ try {
// Dry run - count what would be updated
qCount = queryExecute("
SELECT COUNT(*) as cnt FROM Items
WHERE CategoryID = :categoryID
AND ParentItemID = 0
", { categoryID: cat.ID }, { datasource: "payfrit" });
WHERE ItemCategoryID = :categoryID
AND ItemParentItemID = 0
", { categoryID: cat.CategoryID }, { datasource: "payfrit" });
migration["itemsToUpdate"] = qCount.cnt;
}
@ -198,37 +198,37 @@ try {
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
if (!dryRun) {
queryExecute("
UPDATE Items t
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID
INNER JOIN Items i ON i.ID = tl.ItemID
SET t.BusinessID = i.BusinessID
WHERE (t.BusinessID IS NULL OR t.BusinessID = 0)
AND i.BusinessID > 0
INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
INNER JOIN Items i ON i.ItemID = tl.ItemID
SET t.ItemBusinessID = i.ItemBusinessID
WHERE (t.ItemBusinessID IS NULL OR t.ItemBusinessID = 0)
AND i.ItemBusinessID > 0
", {}, { 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("
UPDATE Items c
INNER JOIN Items t ON t.ItemID = c.ParentItemID
SET c.BusinessID = t.BusinessID
WHERE t.BusinessID > 0
AND (c.BusinessID IS NULL OR c.BusinessID = 0)
INNER JOIN Items t ON t.ItemID = c.ItemParentItemID
SET c.ItemBusinessID = t.ItemBusinessID
WHERE t.ItemBusinessID > 0
AND (c.ItemBusinessID IS NULL OR c.ItemBusinessID = 0)
", {}, { 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)
queryExecute("
UPDATE Items t
INNER JOIN lt_ItemID_TemplateItemID tl ON tl.TemplateItemID = t.ItemID
SET t.ParentItemID = 0
WHERE t.ParentItemID != 0
INNER JOIN ItemTemplateLinks tl ON tl.TemplateItemID = t.ItemID
SET t.ItemParentItemID = 0
WHERE t.ItemParentItemID != 0
", {}, { datasource: "payfrit" });
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)
for (itemId in fakeCategories) {
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" });
}
// Also deactivate their children (modifier options that belong to these fake parents)
for (itemId in fakeCategories) {
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" });
}
// Now verify what categories remain
qCategories = queryExecute("
SELECT i.ID, i.Name
SELECT i.ItemID, i.ItemName
FROM Items i
WHERE i.BusinessID = :bizId
AND i.ParentItemID = 0
AND i.IsActive = 1
AND i.IsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM lt_ItemID_TemplateItemID tl WHERE tl.TemplateItemID = i.ID)
ORDER BY i.SortOrder
WHERE i.ItemBusinessID = :bizId
AND i.ItemParentItemID = 0
AND i.ItemIsActive = 1
AND i.ItemIsCollapsible = 0
AND NOT EXISTS (SELECT 1 FROM ItemTemplateLinks tl WHERE tl.TemplateItemID = i.ItemID)
ORDER BY i.ItemSortOrder
", { bizId: bizId }, { datasource: "payfrit" });
categories = [];
for (row in qCategories) {
arrayAppend(categories, { "ItemID": row.ID, "Name": row.Name });
arrayAppend(categories, { "ItemID": row.ItemID, "ItemName": row.ItemName });
}
writeOutput(serializeJSON({

View file

@ -21,8 +21,8 @@ actions = [];
// First, let's see what templates already exist and are active for burgers
qExistingLinks = queryExecute("
SELECT tl.ItemID as MenuItemID, tl.TemplateItemID, t.Name as TemplateName
FROM lt_ItemID_TemplateItemID tl
SELECT tl.ItemID as MenuItemID, tl.TemplateItemID, t.ItemName as TemplateName
FROM ItemTemplateLinks tl
JOIN Items t ON t.ItemID = tl.TemplateItemID
WHERE tl.ItemID IN (:burgerIds)
", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" });
@ -33,8 +33,8 @@ for (row in qExistingLinks) {
// Reactivate template 11196 (Extras with Add Cheese)
if (!dryRun) {
queryExecute("UPDATE Items SET IsActive = 1 WHERE ItemID = 11196", {}, { datasource: "payfrit" });
queryExecute("UPDATE Items SET IsActive = 1 WHERE ParentItemID = 11196", {}, { datasource: "payfrit" });
queryExecute("UPDATE Items SET ItemIsActive = 1 WHERE ItemID = 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)" });
@ -42,13 +42,13 @@ arrayAppend(actions, { action: dryRun ? "WOULD_REACTIVATE" : "REACTIVATED", item
for (burgerId in burgerIds) {
// Check if link already exists
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" });
if (qCheck.cnt EQ 0) {
if (!dryRun) {
queryExecute("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder)
INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:burgerId, 11196, 2)
", { burgerId: burgerId }, { datasource: "payfrit" });
}
@ -59,17 +59,17 @@ for (burgerId in burgerIds) {
// Verify the result
if (!dryRun) {
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
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
WHERE mi.ID IN (:burgerIds)
GROUP BY mi.ID, mi.Name
WHERE mi.ItemID IN (:burgerIds)
GROUP BY mi.ItemID, mi.ItemName
", { burgerIds: { value: arrayToList(burgerIds), list: true } }, { datasource: "payfrit" });
result = [];
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 {
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
* 3. Remove template flag from flavors
* 4. Mark "Choose Flavor" as the template
* 5. Create lt_ItemID_TemplateItemID entry for Shake -> Choose Flavor
* 6. Set RequiresChildSelection=1 on the shake item
* 5. Create ItemTemplateLinks entry for Shake -> Choose Flavor
* 6. Set ItemRequiresChildSelection=1 on the shake item
*/
response = { "OK": false, "steps": [] };
@ -46,19 +46,19 @@ try {
// Step 1: Create "Choose Flavor" modifier group under Shake
queryExecute("
INSERT INTO Items (
CategoryID,
Name,
Description,
ParentItemID,
Price,
IsActive,
IsCheckedByDefault,
RequiresChildSelection,
MaxNumSelectionReq,
IsCollapsible,
SortOrder,
IsModifierTemplate,
AddedOn
ItemCategoryID,
ItemName,
ItemDescription,
ItemParentItemID,
ItemPrice,
ItemIsActive,
ItemIsCheckedByDefault,
ItemRequiresChildSelection,
ItemMaxNumSelectionReq,
ItemIsCollapsible,
ItemSortOrder,
ItemIsModifierTemplate,
ItemAddedOn
) VALUES (
:categoryID,
'Choose Flavor',
@ -81,22 +81,22 @@ try {
// Get the new Choose Flavor ID
qNewGroup = queryExecute("
SELECT ID FROM Items
WHERE Name = 'Choose Flavor'
AND ParentItemID = :shakeItemID
ORDER BY ID DESC
SELECT ItemID FROM Items
WHERE ItemName = 'Choose Flavor'
AND ItemParentItemID = :shakeItemID
ORDER BY ItemID DESC
LIMIT 1
", { shakeItemID: shakeItemID }, { datasource: "payfrit" });
chooseFlavorID = qNewGroup.ID;
chooseFlavorID = qNewGroup.ItemID;
arrayAppend(response.steps, "Created 'Choose Flavor' group with ID: " & chooseFlavorID);
// Step 2: Re-parent the three flavors under Choose Flavor
queryExecute("
UPDATE Items
SET ParentItemID = :chooseFlavorID,
IsModifierTemplate = 0,
IsCheckedByDefault = 0
SET ItemParentItemID = :chooseFlavorID,
ItemIsModifierTemplate = 0,
ItemIsCheckedByDefault = 0
WHERE ItemID IN (:chocolateID, :strawberryID, :vanillaID)
", {
chooseFlavorID: chooseFlavorID,
@ -109,14 +109,14 @@ try {
// Step 3: Set Vanilla as default (common choice)
queryExecute("
UPDATE Items SET IsCheckedByDefault = 1 WHERE ItemID = :vanillaID
UPDATE Items SET ItemIsCheckedByDefault = 1 WHERE ItemID = :vanillaID
", { vanillaID: vanillaID }, { datasource: "payfrit" });
arrayAppend(response.steps, "Set Vanilla as default flavor");
// Step 4: Remove old template links for the flavors
queryExecute("
DELETE FROM lt_ItemID_TemplateItemID
DELETE FROM ItemTemplateLinks
WHERE TemplateItemID IN (:chocolateID, :strawberryID, :vanillaID)
", {
chocolateID: chocolateID,
@ -126,9 +126,9 @@ try {
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("
INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder)
INSERT INTO ItemTemplateLinks (ItemID, TemplateItemID, SortOrder)
VALUES (:shakeItemID, :chooseFlavorID, 0)
ON DUPLICATE KEY UPDATE SortOrder = 0
", {
@ -138,14 +138,14 @@ try {
arrayAppend(response.steps, "Created template link: Shake -> Choose Flavor");
// Step 6: Set RequiresChildSelection on shake
// Step 6: Set ItemRequiresChildSelection on shake
queryExecute("
UPDATE Items
SET RequiresChildSelection = 1
SET ItemRequiresChildSelection = 1
WHERE ItemID = :shakeItemID
", { 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["chooseFlavorID"] = chooseFlavorID;

View file

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

View file

@ -29,7 +29,7 @@ try {
response["ERROR"] = "ChildBusinessID required";
} else {
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 },
childId: childID

View file

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

View file

@ -27,20 +27,20 @@ try {
// Find all businesses with items in unified schema
if (len(businessFilter)) {
qBusinesses = queryExecute("
SELECT DISTINCT BusinessID as BusinessID
SELECT DISTINCT ItemBusinessID as BusinessID
FROM Items
WHERE BusinessID = :bid AND BusinessID > 0
WHERE ItemBusinessID = :bid AND ItemBusinessID > 0
", { bid: businessFilter }, { datasource: "payfrit" });
} else {
qBusinesses = queryExecute("
SELECT DISTINCT BusinessID as BusinessID
SELECT DISTINCT ItemBusinessID as BusinessID
FROM Items
WHERE BusinessID > 0
WHERE ItemBusinessID > 0
", {}, { datasource: "payfrit" });
}
for (biz in qBusinesses) {
bizId = biz.ID;
bizId = biz.BusinessID;
bizResult = { "BusinessID": bizId, "CategoriesCreated": 0, "ItemsUpdated": 0 };
try {
@ -48,26 +48,26 @@ try {
qCategoryItems = queryExecute("
SELECT DISTINCT
p.ItemID,
p.Name,
p.SortOrder
p.ItemName,
p.ItemSortOrder
FROM Items p
INNER JOIN Items c ON c.ParentItemID = p.ItemID
WHERE p.BusinessID = :bizId
AND p.ParentItemID = 0
AND (p.IsCollapsible = 0 OR p.IsCollapsible IS NULL)
INNER JOIN Items c ON c.ItemParentItemID = p.ItemID
WHERE p.ItemBusinessID = :bizId
AND p.ItemParentItemID = 0
AND (p.ItemIsCollapsible = 0 OR p.ItemIsCollapsible IS NULL)
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" });
sortOrder = 0;
for (catItem in qCategoryItems) {
// Check if category already exists for this business with same name
qExisting = queryExecute("
SELECT ID FROM Categories
WHERE BusinessID = :bizId AND Name = :name
", { bizId: bizId, name: left(catItem.Name, 30) }, { datasource: "payfrit" });
SELECT CategoryID FROM Categories
WHERE CategoryBusinessID = :bizId AND CategoryName = :name
", { bizId: bizId, name: left(catItem.ItemName, 30) }, { datasource: "payfrit" });
if (qExisting.recordCount == 0) {
// Get next CategoryID
@ -79,12 +79,12 @@ try {
// Create new category with explicit ID
queryExecute("
INSERT INTO Categories
(CategoryID, BusinessID, ParentCategoryID, Name, SortOrder, AddedOn)
(CategoryID, CategoryBusinessID, CategoryParentCategoryID, CategoryName, CategorySortOrder, CategoryAddedOn)
VALUES (:catId, :bizId, 0, :name, :sortOrder, NOW())
", {
catId: newCatId,
bizId: bizId,
name: left(catItem.Name, 30),
name: left(catItem.ItemName, 30),
sortOrder: sortOrder
}, { datasource: "payfrit" });
@ -96,9 +96,9 @@ try {
// Update all children of this category Item to have the new CategoryID
queryExecute("
UPDATE Items
SET CategoryID = :catId
WHERE ParentItemID = :parentId
AND BusinessID = :bizId
SET ItemCategoryID = :catId
WHERE ItemParentItemID = :parentId
AND ItemBusinessID = :bizId
", {
catId: newCatId,
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
qTemplate = queryExecute("
SELECT
Title as Title,
Details as Details,
TaskCategoryID as CategoryID
QuickTaskTemplateTitle as Title,
QuickTaskTemplateDetails as Details,
QuickTaskTemplateCategoryID as CategoryID,
QuickTaskTemplateTypeID as TypeID
FROM QuickTaskTemplates
WHERE ID = :id
AND BusinessID = :businessID
AND IsActive = 1
WHERE QuickTaskTemplateID = :id
AND QuickTaskTemplateBusinessID = :businessID
AND QuickTaskTemplateIsActive = 1
", {
id: { value: templateID, 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" });
}
// Create the task (ClaimedByUserID=0 means unclaimed/pending)
// Create the task
queryExecute("
INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID
TaskBusinessID, TaskCategoryID, TaskTypeID,
TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES (
:businessID, :categoryID, :typeID,
:title, :details, NOW(), 0
:title, :details, 0, NOW(),
'quicktask', :templateID
)
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
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" },
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" });
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
qCheck = queryExecute("
SELECT QuickTaskTemplateID FROM QuickTaskTemplates
WHERE ID = :id AND BusinessID = :businessID
WHERE QuickTaskTemplateID = :id AND QuickTaskTemplateBusinessID = :businessID
", {
id: { value: templateID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -62,8 +62,8 @@ try {
// Soft delete by setting IsActive to 0
queryExecute("
UPDATE QuickTaskTemplates SET IsActive = 0
WHERE ID = :id
UPDATE QuickTaskTemplates SET QuickTaskTemplateIsActive = 0
WHERE QuickTaskTemplateID = :id
", {
id: { value: templateID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });

View file

@ -45,22 +45,23 @@ try {
// Get quick task templates for this business
q = queryExecute("
SELECT
qt.ID,
qt.Name as Name,
qt.TaskCategoryID as CategoryID,
qt.Title as Title,
qt.Details as Details,
qt.Icon as Icon,
qt.Color as Color,
qt.SortOrder as SortOrder,
qt.IsActive as IsActive,
tc.Name as Name,
tc.Color as CategoryColor
qt.QuickTaskTemplateID,
qt.QuickTaskTemplateName as Name,
qt.QuickTaskTemplateCategoryID as CategoryID,
qt.QuickTaskTemplateTypeID as TypeID,
qt.QuickTaskTemplateTitle as Title,
qt.QuickTaskTemplateDetails as Details,
qt.QuickTaskTemplateIcon as Icon,
qt.QuickTaskTemplateColor as Color,
qt.QuickTaskTemplateSortOrder as SortOrder,
qt.QuickTaskTemplateIsActive as IsActive,
tc.TaskCategoryName as CategoryName,
tc.TaskCategoryColor as CategoryColor
FROM QuickTaskTemplates qt
LEFT JOIN TaskCategories tc ON qt.TaskCategoryID = tc.ID
WHERE qt.BusinessID = :businessID
AND qt.IsActive = 1
ORDER BY qt.SortOrder, qt.ID
LEFT JOIN TaskCategories tc ON qt.QuickTaskTemplateCategoryID = tc.TaskCategoryID
WHERE qt.QuickTaskTemplateBusinessID = :businessID
AND qt.QuickTaskTemplateIsActive = 1
ORDER BY qt.QuickTaskTemplateSortOrder, qt.QuickTaskTemplateID
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
@ -68,16 +69,17 @@ try {
templates = [];
for (row in q) {
arrayAppend(templates, {
"QuickTaskTemplateID": row.ID,
"QuickTaskTemplateID": row.QuickTaskTemplateID,
"Name": row.Name,
"CategoryID": isNull(row.CategoryID) ? "" : row.CategoryID,
"TypeID": isNull(row.TypeID) ? "" : row.TypeID,
"Title": row.Title,
"Details": isNull(row.Details) ? "" : row.Details,
"Icon": isNull(row.Icon) ? "add_box" : row.Icon,
"Color": isNull(row.Color) ? "##6366f1" : row.Color,
"SortOrder": row.SortOrder,
"IsActive": row.IsActive,
"Name": isNull(row.Name) ? "" : row.Name,
"CategoryName": isNull(row.CategoryName) ? "" : row.CategoryName,
"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>
// Create or update a quick task template
// 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 }
function apiAbort(required struct payload) {
@ -46,12 +46,8 @@ try {
templateName = structKeyExists(data, "Name") ? trim(toString(data.Name)) : "";
templateTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : "";
templateDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : "";
hasCategory = false;
catID = 0;
if (structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0) {
catID = int(data.CategoryID);
hasCategory = true;
}
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", "");
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";
@ -62,15 +58,12 @@ try {
if (!len(templateTitle)) {
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) {
// UPDATE existing template
qCheck = queryExecute("
SELECT QuickTaskTemplateID FROM QuickTaskTemplates
WHERE ID = :id AND BusinessID = :businessID
WHERE QuickTaskTemplateID = :id AND QuickTaskTemplateBusinessID = :businessID
", {
id: { value: templateID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -82,18 +75,20 @@ try {
queryExecute("
UPDATE QuickTaskTemplates SET
Name = :name,
Title = :title,
Details = :details,
TaskCategoryID = :categoryID,
Icon = :icon,
Color = :color
WHERE ID = :id
QuickTaskTemplateName = :name,
QuickTaskTemplateTitle = :title,
QuickTaskTemplateDetails = :details,
QuickTaskTemplateCategoryID = :categoryID,
QuickTaskTemplateTypeID = :typeID,
QuickTaskTemplateIcon = :icon,
QuickTaskTemplateColor = :color
WHERE QuickTaskTemplateID = :id
", {
name: { value: templateName, cfsqltype: "cf_sql_varchar" },
title: { value: templateTitle, cfsqltype: "cf_sql_varchar" },
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" },
color: { value: templateColor, cfsqltype: "cf_sql_varchar" },
id: { value: templateID, cfsqltype: "cf_sql_integer" }
@ -109,8 +104,8 @@ try {
// INSERT new template
// Get next sort order
qSort = queryExecute("
SELECT COALESCE(MAX(SortOrder), 0) + 1 as nextSort
FROM QuickTaskTemplates WHERE BusinessID = :businessID
SELECT COALESCE(MAX(QuickTaskTemplateSortOrder), 0) + 1 as nextSort
FROM QuickTaskTemplates WHERE QuickTaskTemplateBusinessID = :businessID
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
@ -119,18 +114,19 @@ try {
queryExecute("
INSERT INTO QuickTaskTemplates (
BusinessID, Name, Title,
Details, TaskCategoryID,
Icon, Color, SortOrder
QuickTaskTemplateBusinessID, QuickTaskTemplateName, QuickTaskTemplateTitle,
QuickTaskTemplateDetails, QuickTaskTemplateCategoryID, QuickTaskTemplateTypeID,
QuickTaskTemplateIcon, QuickTaskTemplateColor, QuickTaskTemplateSortOrder
) 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" },
name: { value: templateName, cfsqltype: "cf_sql_varchar" },
title: { value: templateTitle, cfsqltype: "cf_sql_varchar" },
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" },
color: { value: templateColor, cfsqltype: "cf_sql_varchar" },
sortOrder: { value: nextSort, cfsqltype: "cf_sql_integer" }
@ -149,10 +145,7 @@ try {
apiAbort({
"OK": false,
"ERROR": "server_error",
"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"
"MESSAGE": e.message
});
}
</cfscript>

View file

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

View file

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

View file

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

View file

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

View file

@ -51,11 +51,12 @@ try {
// Get scheduled task definition
qDef = queryExecute("
SELECT
Title as Title,
Details as Details,
TaskCategoryID as CategoryID
ScheduledTaskTitle as Title,
ScheduledTaskDetails as Details,
ScheduledTaskCategoryID as CategoryID,
ScheduledTaskTypeID as TypeID
FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID
WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", {
id: { value: scheduledTaskID, 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" });
}
// Create the task (ClaimedByUserID=0 means unclaimed/pending)
// Create the task
queryExecute("
INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID
TaskBusinessID, TaskCategoryID, TaskTypeID,
TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES (
:businessID, :categoryID, :typeID,
:title, :details, NOW(), 0
:title, :details, 0, NOW(),
'scheduled_manual', :scheduledTaskID
)
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
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" },
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" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });

View file

@ -68,65 +68,57 @@ try {
dueTasks = queryExecute("
SELECT
ScheduledTaskID,
BusinessID as BusinessID,
TaskCategoryID as CategoryID,
Title as Title,
Details as Details,
CronExpression as CronExpression,
COALESCE(ScheduleType, 'cron') as ScheduleType,
IntervalMinutes as IntervalMinutes
ScheduledTaskBusinessID as BusinessID,
ScheduledTaskCategoryID as CategoryID,
ScheduledTaskTypeID as TypeID,
ScheduledTaskTitle as Title,
ScheduledTaskDetails as Details,
ScheduledTaskCronExpression as CronExpression
FROM ScheduledTaskDefinitions
WHERE IsActive = 1
AND NextRunOn <= NOW()
WHERE ScheduledTaskIsActive = 1
AND ScheduledTaskNextRunOn <= NOW()
", {}, { datasource: "payfrit" });
createdTasks = [];
for (task in dueTasks) {
// Create the actual task (ClaimedByUserID=0 means unclaimed/pending)
// Create the actual task
queryExecute("
INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID
TaskBusinessID, TaskCategoryID, TaskTypeID,
TaskTitle, TaskDetails, TaskStatusID, TaskAddedOn,
TaskSourceType, TaskSourceID
) VALUES (
:businessID, :categoryID, :typeID,
:title, :details, NOW(), 0
:title, :details, 0, NOW(),
'scheduled', :scheduledTaskID
)
", {
businessID: { value: task.BusinessID, cfsqltype: "cf_sql_integer" },
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" },
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" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
// Calculate next run based on schedule type
if (task.ScheduleType == "interval_after_completion" && !isNull(task.IntervalMinutes) && task.IntervalMinutes > 0) {
// 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);
}
// Calculate next run and update the scheduled task
nextRun = calculateNextRun(task.CronExpression);
queryExecute("
UPDATE ScheduledTaskDefinitions SET
LastRunOn = NOW(),
NextRunOn = :nextRun
WHERE ID = :id
ScheduledTaskLastRunOn = NOW(),
ScheduledTaskNextRunOn = :nextRun
WHERE ScheduledTaskID = :id
", {
nextRun: { value: nextRun, cfsqltype: "cf_sql_timestamp", null: isNull(nextRun) },
id: { value: task.ID, cfsqltype: "cf_sql_integer" }
nextRun: { value: nextRun, cfsqltype: "cf_sql_timestamp" },
id: { value: task.ScheduledTaskID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
arrayAppend(createdTasks, {
"ScheduledTaskID": task.ID,
"ScheduledTaskID": task.ScheduledTaskID,
"TaskID": qNew.newID,
"BusinessID": task.BusinessID,
"Title": task.Title

View file

@ -5,8 +5,7 @@
<cfscript>
// Create or update a scheduled task definition
// Input: BusinessID (required), ScheduledTaskID (optional - for update),
// Name, Title, Details, CategoryID, TypeID, CronExpression, IsActive,
// ScheduleType ('cron' or 'interval'), IntervalMinutes (for interval type)
// Name, Title, Details, CategoryID, TypeID, CronExpression, IsActive
// Output: { OK: true, SCHEDULED_TASK_ID: int }
function apiAbort(required struct payload) {
@ -109,13 +108,10 @@ try {
taskTitle = structKeyExists(data, "Title") ? trim(toString(data.Title)) : "";
taskDetails = structKeyExists(data, "Details") ? trim(toString(data.Details)) : "";
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)) : "";
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
if (!len(taskName)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Name is required" });
@ -123,42 +119,24 @@ try {
if (!len(taskTitle)) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "Title is required" });
}
// Validate based on schedule type
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);
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
nextRunOn = calculateNextRun(cronExpression);
if (taskID > 0) {
// UPDATE existing
qCheck = queryExecute("
SELECT ScheduledTaskID FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID
WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", {
id: { value: taskID, cfsqltype: "cf_sql_integer" },
businessID: { value: businessID, cfsqltype: "cf_sql_integer" }
@ -170,24 +148,22 @@ try {
queryExecute("
UPDATE ScheduledTaskDefinitions SET
Name = :name,
Title = :title,
Details = :details,
TaskCategoryID = :categoryID,
CronExpression = :cron,
ScheduleType = :scheduleType,
IntervalMinutes = :intervalMinutes,
IsActive = :isActive,
NextRunOn = :nextRun
WHERE ID = :id
ScheduledTaskName = :name,
ScheduledTaskTitle = :title,
ScheduledTaskDetails = :details,
ScheduledTaskCategoryID = :categoryID,
ScheduledTaskTypeID = :typeID,
ScheduledTaskCronExpression = :cron,
ScheduledTaskIsActive = :isActive,
ScheduledTaskNextRunOn = :nextRun
WHERE ScheduledTaskID = :id
", {
name: { value: taskName, cfsqltype: "cf_sql_varchar" },
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) },
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" },
scheduleType: { value: scheduleType, cfsqltype: "cf_sql_varchar" },
intervalMinutes: { value: intervalMinutes, cfsqltype: "cf_sql_integer", null: isNull(intervalMinutes) },
isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" },
id: { value: taskID, cfsqltype: "cf_sql_integer" }
@ -204,12 +180,11 @@ try {
// INSERT new
queryExecute("
INSERT INTO ScheduledTaskDefinitions (
BusinessID, Name, Title,
Details, TaskCategoryID,
CronExpression, ScheduleType, IntervalMinutes,
IsActive, NextRunOn
ScheduledTaskBusinessID, ScheduledTaskName, ScheduledTaskTitle,
ScheduledTaskDetails, ScheduledTaskCategoryID, ScheduledTaskTypeID,
ScheduledTaskCronExpression, ScheduledTaskIsActive, ScheduledTaskNextRunOn
) 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" },
@ -217,65 +192,19 @@ try {
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_longvarchar", null: !len(taskDetails) },
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" },
scheduleType: { value: scheduleType, cfsqltype: "cf_sql_varchar" },
intervalMinutes: { value: intervalMinutes, cfsqltype: "cf_sql_integer", null: isNull(intervalMinutes) },
isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" }
}, { 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({
"OK": true,
"SCHEDULED_TASK_ID": newScheduledTaskID,
"TASK_ID": qTask.taskID,
"NEXT_RUN": isNull(actualNextRun) ? "" : dateTimeFormat(actualNextRun, "yyyy-mm-dd HH:nn:ss"),
"MESSAGE": "Scheduled task created and first task added"
"SCHEDULED_TASK_ID": qNew.newID,
"NEXT_RUN": dateTimeFormat(nextRunOn, "yyyy-mm-dd HH:nn:ss"),
"MESSAGE": "Scheduled task created"
});
}

View file

@ -15,48 +15,27 @@ try {
// Create ScheduledTaskDefinitions table
queryExecute("
CREATE TABLE IF NOT EXISTS ScheduledTaskDefinitions (
ID INT AUTO_INCREMENT PRIMARY KEY,
BusinessID INT NOT NULL,
Name VARCHAR(100) NOT NULL,
TaskCategoryID INT NULL,
TaskTypeID INT NULL,
Title VARCHAR(255) NOT NULL,
Details TEXT NULL,
CronExpression VARCHAR(100) NOT NULL,
ScheduleType VARCHAR(20) DEFAULT 'cron',
IntervalMinutes INT NULL,
IsActive BIT(1) DEFAULT b'1',
LastRunOn DATETIME NULL,
NextRunOn DATETIME NULL,
CreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP,
CreatedByUserID INT NULL,
INDEX idx_business (BusinessID),
INDEX idx_active_next (IsActive, NextRunOn)
ScheduledTaskID INT AUTO_INCREMENT PRIMARY KEY,
ScheduledTaskBusinessID INT NOT NULL,
ScheduledTaskName VARCHAR(100) NOT NULL,
ScheduledTaskCategoryID INT NULL,
ScheduledTaskTypeID INT NULL,
ScheduledTaskTitle VARCHAR(255) NOT NULL,
ScheduledTaskDetails TEXT NULL,
ScheduledTaskCronExpression VARCHAR(100) NOT NULL,
ScheduledTaskIsActive BIT(1) DEFAULT b'1',
ScheduledTaskLastRunOn DATETIME NULL,
ScheduledTaskNextRunOn DATETIME NULL,
ScheduledTaskCreatedOn DATETIME DEFAULT CURRENT_TIMESTAMP,
ScheduledTaskCreatedByUserID INT NULL,
INDEX idx_business (ScheduledTaskBusinessID),
INDEX idx_active_next (ScheduledTaskIsActive, ScheduledTaskNextRunOn)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
", [], { 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({
"OK": true,
"MESSAGE": "ScheduledTaskDefinitions table created/verified with interval support"
"MESSAGE": "ScheduledTaskDefinitions table created/verified"
});
} catch (any e) {

View file

@ -97,13 +97,11 @@ try {
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("
SELECT ScheduledTaskID, CronExpression as CronExpression,
COALESCE(ScheduleType, 'cron') as ScheduleType,
IntervalMinutes as IntervalMinutes
SELECT ScheduledTaskID, ScheduledTaskCronExpression as CronExpression
FROM ScheduledTaskDefinitions
WHERE ID = :id AND BusinessID = :businessID
WHERE ScheduledTaskID = :id AND ScheduledTaskBusinessID = :businessID
", {
id: { value: taskID, 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" });
}
// If enabling, recalculate next run time based on schedule type
// If enabling, recalculate next run time
nextRunUpdate = "";
if (isActive) {
if ((qCheck.ScheduleType == "interval" || qCheck.ScheduleType == "interval_after_completion") && !isNull(qCheck.IntervalMinutes) && qCheck.IntervalMinutes > 0) {
// Interval-based: next run = NOW + interval minutes
nextRunOn = dateAdd("n", qCheck.IntervalMinutes, now());
} else {
// Cron-based: use cron parser
nextRunOn = calculateNextRun(qCheck.CronExpression);
}
nextRunUpdate = ", NextRunOn = :nextRun";
nextRunOn = calculateNextRun(qCheck.CronExpression);
nextRunUpdate = ", ScheduledTaskNextRunOn = :nextRun";
}
// Update status
if (isActive) {
queryExecute("
UPDATE ScheduledTaskDefinitions SET
IsActive = :isActive,
NextRunOn = :nextRun
WHERE ID = :id
ScheduledTaskIsActive = :isActive,
ScheduledTaskNextRunOn = :nextRun
WHERE ScheduledTaskID = :id
", {
isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
nextRun: { value: nextRunOn, cfsqltype: "cf_sql_timestamp" },
@ -140,8 +132,8 @@ try {
}, { datasource: "payfrit" });
} else {
queryExecute("
UPDATE ScheduledTaskDefinitions SET IsActive = :isActive
WHERE ID = :id
UPDATE ScheduledTaskDefinitions SET ScheduledTaskIsActive = :isActive
WHERE ScheduledTaskID = :id
", {
isActive: { value: isActive, cfsqltype: "cf_sql_bit" },
id: { value: taskID, cfsqltype: "cf_sql_integer" }

View file

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

View file

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

View file

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

View file

@ -13,19 +13,19 @@ try {
// Hours: Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm
// 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
// Check if Big Dean's already has an address
existingAddr = queryExecute("
SELECT ID FROM Addresses
WHERE BusinessID = :bizId AND UserID = 0
SELECT AddressID FROM Addresses
WHERE AddressBusinessID = :bizId AND AddressUserID = 0
", { bizId: businessId });
if (existingAddr.recordCount == 0) {
// Insert new address
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())
", {
bizId: businessId,
@ -39,8 +39,8 @@ try {
// Update existing address
queryExecute("
UPDATE Addresses
SET Line1 = :line1, City = :city, StateID = :stateId, ZIPCode = :zip
WHERE BusinessID = :bizId AND UserID = 0
SET AddressLine1 = :line1, AddressCity = :city, AddressStateID = :stateId, AddressZIPCode = :zip
WHERE AddressBusinessID = :bizId AND AddressUserID = 0
", {
bizId: businessId,
line1: "1615 Ocean Front Walk",
@ -53,7 +53,7 @@ try {
// Check existing hours for this business
existingHours = queryExecute("
SELECT COUNT(*) as cnt FROM Hours WHERE BusinessID = :bizId
SELECT COUNT(*) as cnt FROM Hours WHERE HoursBusinessID = :bizId
", { bizId: businessId });
if (existingHours.cnt == 0) {
@ -64,39 +64,39 @@ try {
// Sun: 11am-10pm (day 1)
// 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
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
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
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
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
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
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";
} else {
// Update existing hours
// 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
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";
}
// Update phone on Businesses table (if column exists)
try {
queryExecute("UPDATE Businesses SET Phone = :phone WHERE ID = :bizId", {
queryExecute("UPDATE Businesses SET BusinessPhone = :phone WHERE BusinessID = :bizId", {
phone: "(310) 393-2666",
bizId: businessId
});
@ -107,35 +107,35 @@ try {
// Verify the data
address = queryExecute("
SELECT a.*, s.Abbreviation
SELECT a.*, s.tt_StateAbbreviation
FROM Addresses a
LEFT JOIN tt_States s ON s.ID = a.StateID
WHERE a.BusinessID = :bizId AND a.UserID = 0
LEFT JOIN tt_States s ON s.tt_StateID = a.AddressStateID
WHERE a.AddressBusinessID = :bizId AND a.AddressUserID = 0
", { bizId: businessId });
hours = queryExecute("
SELECT h.*, d.tt_DayName
FROM Hours h
JOIN tt_Days d ON d.ID = h.DayID
WHERE h.BusinessID = :bizId
ORDER BY h.DayID
JOIN tt_Days d ON d.tt_DayID = h.HoursDayID
WHERE h.HoursBusinessID = :bizId
ORDER BY h.HoursDayID
", { bizId: businessId });
response["OK"] = true;
response["BUSINESS_ID"] = businessId;
response["ADDRESS"] = address.recordCount > 0 ? {
"line1": address.Line1,
"city": address.City,
"state": address.Abbreviation,
"zip": address.ZIPCode
"line1": address.AddressLine1,
"city": address.AddressCity,
"state": address.tt_StateAbbreviation,
"zip": address.AddressZIPCode
} : "not found";
hoursArr = [];
for (h in hours) {
arrayAppend(hoursArr, {
"day": h.tt_DayName,
"open": timeFormat(h.OpenTime, "h:mm tt"),
"close": timeFormat(h.ClosingTime, "h:mm tt")
"open": timeFormat(h.HoursOpenTime, "h:mm tt"),
"close": timeFormat(h.HoursClosingTime, "h:mm tt")
});
}
response["HOURS"] = hoursArr;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,65 +3,50 @@
<cfcontent type="application/json; charset=utf-8" reset="true">
<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;
oldBusinessId = 37; // previous mapping
newBusinessId = 17;
// Remove old mapping
queryExecute("
DELETE FROM lt_BeaconsID_BusinessesID
WHERE BeaconID = :beaconId AND BusinessID = :oldBizId
", { beaconId: beaconId, oldBizId: oldBusinessId }, { 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" });
UPDATE lt_Beacon_Businesses_ServicePoints
SET BusinessID = :newBizId
WHERE BeaconID = :beaconId
", { newBizId: newBusinessId, beaconId: beaconId }, { datasource: "payfrit" });
// Get current state
q = queryExecute("
SELECT
b.ID AS BeaconID,
b.UUID,
b.Name AS BeaconName,
b.BusinessID AS BeaconBusinessID,
sp.ID AS ServicePointID,
sp.Name AS ServicePointName,
sp.BusinessID AS ServicePointBusinessID,
biz.Name AS BusinessName
FROM Beacons b
LEFT JOIN ServicePoints sp ON sp.BeaconID = b.ID
LEFT JOIN Businesses biz ON biz.ID = b.BusinessID
WHERE b.IsActive = 1
ORDER BY b.ID
SELECT
b.BeaconID,
b.BeaconUUID,
b.BeaconName,
lt.BusinessID,
lt.ServicePointID,
biz.BusinessName,
sp.ServicePointName
FROM Beacons b
LEFT JOIN lt_Beacon_Businesses_ServicePoints lt ON lt.BeaconID = b.BeaconID
LEFT JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
LEFT JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
WHERE b.BeaconIsActive = 1
ORDER BY b.BeaconID
", {}, { datasource: "payfrit" });
rows = [];
for (row in q) {
arrayAppend(rows, {
"BeaconID": row.BeaconID,
"UUID": row.UUID,
"BeaconName": row.BeaconName,
"BeaconBusinessID": row.BeaconBusinessID,
"BusinessName": row.BusinessName,
"ServicePointID": row.ServicePointID ?: 0,
"ServicePointName": row.ServicePointName ?: ""
});
arrayAppend(rows, {
"BeaconID": row.BeaconID,
"BeaconUUID": row.BeaconUUID,
"BeaconName": row.BeaconName ?: "",
"BusinessID": row.BusinessID ?: 0,
"BusinessName": row.BusinessName ?: "",
"ServicePointID": row.ServicePointID ?: 0,
"ServicePointName": row.ServicePointName ?: ""
});
}
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Updated beacon #beaconId# to BusinessID #newBusinessId#",
"BEACONS": rows
"OK": true,
"MESSAGE": "Updated beacon #beaconId# to BusinessID #newBusinessId#",
"BEACONS": rows
}));
</cfscript>

View file

@ -6,68 +6,72 @@
// Update Big Dean's business info
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";
hours = "Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm";
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("
UPDATE Businesses
SET Phone = :phone,
Hours = :hours
WHERE ID = :bizId
SET BusinessAddress = :address,
BusinessPhone = :phone,
BusinessHours = :hours
WHERE BusinessID = :bizId
", {
address: address,
phone: phone,
hours: hours,
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
updated = queryExecute("
SELECT ID, Name, Phone, Hours
SELECT BusinessID, BusinessName, BusinessAddress, BusinessPhone, BusinessHours
FROM Businesses
WHERE ID = :bizId
", { bizId: businessId }, { datasource: "payfrit" });
WHERE BusinessID = :bizId
", { bizId: businessId });
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Updated Big Dean's info",
"COLUMNS_EXISTED": { "address": hasAddress, "phone": hasPhone, "hours": hasHours },
"BUSINESS": {
"BusinessID": updated.ID,
"Name": updated.Name,
"Phone": updated.Phone,
"Hours": updated.Hours
"BusinessID": updated.BusinessID,
"BusinessName": updated.BusinessName,
"BusinessAddress": updated.BusinessAddress,
"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 ---------- */
data = readJsonBody();
if (!structKeyExists(data,"ServicePointID") || !isNumeric(data.ServicePointID) || int(data.ServicePointID) LTE 0){
apiAbort({OK=false,ERROR="missing_ServicePointID"});
if (
!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>
<!--- 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">
SELECT ID, BeaconID
FROM ServicePoints
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#ServicePointID#">
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
AND BeaconID IS NOT NULL
SELECT
lt_Beacon_Businesses_ServicePointID,
BeaconID,
ServicePointID
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
</cfquery>
@ -55,28 +63,28 @@ ServicePointID = int(data.ServicePointID);
<cfoutput>#serializeJSON({
"OK"=false,
"ERROR"="not_found",
"ServicePointID"=ServicePointID,
"lt_Beacon_Businesses_ServicePointID"=RelID,
"BusinessID"=(request.BusinessID & "")
})#</cfoutput>
<cfabort>
</cfif>
<cfset removedBeaconID = qFind.BeaconID>
<!--- Unassign beacon from service point --->
<!--- Delete it --->
<cfquery datasource="payfrit">
UPDATE ServicePoints
SET BeaconID = NULL,
AssignedByUserID = NULL
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#ServicePointID#">
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
DELETE 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
</cfquery>
<cfoutput>#serializeJSON({
"OK"=true,
"ERROR"="",
"ACTION"="unassigned",
"ServicePointID"=ServicePointID,
"BeaconID"=removedBeaconID,
"ACTION"="deleted",
"lt_Beacon_Businesses_ServicePointID"=RelID,
"BeaconID"=qFind.BeaconID,
"ServicePointID"=qFind.ServicePointID,
"BusinessID"=(request.BusinessID & "")
})#</cfoutput>

View file

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

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