This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/admin/geocode.cfm
John Mizerek 1210249f54 Normalize database column and table names across entire codebase
Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).

Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
  Addresses, Hours, Menus, Categories, Items, Stations, Orders,
  OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
  ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
  VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
  tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
  dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
  Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
  UserIsActive→IsActive, UserBalance→Balance, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:39:12 -08:00

204 lines
6.9 KiB
Text

<cfsetting showdebugoutput="false">
<!DOCTYPE html>
<html>
<head>
<title>Address Geocoding</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 1000px; margin: 40px auto; padding: 20px; background: ##1a1a1a; color: ##fff; }
h1 { color: ##4CAF50; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid ##333; }
th { background: ##2a2a2a; }
tr:hover { background: ##2a2a2a; }
.address { color: ##aaa; font-size: 13px; }
.success { color: ##4CAF50; padding: 10px; background: ##1b3d1b; border-radius: 4px; margin: 10px 0; }
.error { color: ##f44336; padding: 10px; background: ##3d1b1b; border-radius: 4px; margin: 10px 0; }
.has-coords { color: ##4CAF50; }
.no-coords { color: ##ff9800; }
button { padding: 6px 12px; background: ##4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: ##45a049; }
.btn-lookup { background: ##2196F3; }
.btn-lookup:hover { background: ##1976D2; }
.coords { font-family: monospace; font-size: 12px; color: ##888; }
</style>
</head>
<body>
<h1>Address Geocoding</h1>
<p>Auto-geocode addresses using OpenStreetMap Nominatim (free, no API key required)</p>
<cfscript>
function geocodeAddress(addressString) {
var result = { "success": false, "error": "" };
if (len(trim(addressString)) EQ 0) {
result.error = "Empty address";
return result;
}
try {
var httpService = new http();
httpService.setMethod("GET");
httpService.setUrl("https://nominatim.openstreetmap.org/search?q=" & urlEncodedFormat(addressString) & "&format=json&limit=1");
httpService.addParam(type="header", name="User-Agent", value="Payfrit/1.0");
httpService.setTimeout(10);
var httpResult = httpService.send().getPrefix();
if (httpResult.statusCode CONTAINS "200") {
var data = deserializeJSON(httpResult.fileContent);
if (arrayLen(data) GT 0) {
result.success = true;
result.lat = data[1].lat;
result.lng = data[1].lon;
return result;
}
result.error = "No results found";
return result;
}
result.error = "HTTP " & httpResult.statusCode;
return result;
} catch (any e) {
result.error = e.message;
return result;
}
}
function buildAddressString(line1, line2, city, zipCode) {
var parts = [];
if (len(trim(line1))) arrayAppend(parts, trim(line1));
if (len(trim(line2))) arrayAppend(parts, trim(line2));
if (len(trim(city))) arrayAppend(parts, trim(city));
if (len(trim(zipCode))) arrayAppend(parts, trim(zipCode));
return arrayToList(parts, ", ");
}
</cfscript>
<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">
</cfquery>
<cfif addr.recordCount GT 0>
<cfset fullAddress = buildAddressString(addr.Line1, addr.Line2, addr.City, addr.ZIPCode)>
<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">
</cfquery>
<cfoutput><div class="success">Geocoded Address ID #addressId#: #geo.lat#, #geo.lng#</div></cfoutput>
<cfelse>
<cfoutput><div class="error">Failed: #geo.error#</div></cfoutput>
</cfif>
</cfif>
</cfif>
<cfif structKeyExists(url, "geocodeAll")>
<cfquery name="missing" datasource="payfrit">
SELECT ID, Line1, Line2, City, ZIPCode
FROM Addresses
WHERE (Latitude IS NULL OR Latitude = 0)
AND Line1 IS NOT NULL AND Line1 != ''
</cfquery>
<cfset successCount = 0>
<cfset failCount = 0>
<cfloop query="missing">
<cfset fullAddress = buildAddressString(missing.Line1, missing.Line2, missing.City, missing.ZIPCode)>
<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">
</cfquery>
<cfset successCount = successCount + 1>
<cfelse>
<cfset failCount = failCount + 1>
</cfif>
<cfset sleep(1100)>
</cfloop>
<cfoutput><div class="success">Geocoded #successCount# addresses. #failCount# failed.</div></cfoutput>
</cfif>
<cfquery name="addresses" datasource="payfrit">
SELECT
b.ID,
b.Name,
a.ID,
a.Line1,
a.Line2,
a.City,
a.ZIPCode,
a.Latitude,
a.Longitude
FROM Businesses b
LEFT JOIN Addresses a ON b.AddressID = a.ID
ORDER BY b.Name
</cfquery>
<cfset missingCount = 0>
<cfloop query="addresses">
<cfif (NOT len(addresses.Latitude) OR val(addresses.Latitude) EQ 0) AND len(addresses.Line1)>
<cfset missingCount = missingCount + 1>
</cfif>
</cfloop>
<cfoutput>
<p>
<strong>#missingCount#</strong> addresses missing coordinates.
<cfif missingCount GT 0>
<a href="?geocodeAll=1"><button class="btn-lookup">Geocode All Missing (#missingCount#)</button></a>
<small style="color:##888;">(~#missingCount# seconds due to rate limiting)</small>
</cfif>
</p>
<table>
<thead>
<tr>
<th>Business</th>
<th>Address</th>
<th>Coordinates</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<cfloop query="addresses">
<tr>
<td>
#addresses.Name#
<cfif len(addresses.Latitude) AND val(addresses.Latitude) NEQ 0>
<span class="has-coords">#chr(10003)#</span>
<cfelseif len(addresses.Line1)>
<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#
<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, "_.______")#
<cfelse>
-
</cfif>
</td>
<td>
<cfif len(addresses.Line1) AND len(addresses.AddressID)>
<a href="?geocode=1&addressId=#addresses.AddressID#"><button class="btn-lookup">Lookup</button></a>
</cfif>
</td>
</tr>
</cfloop>
</tbody>
</table>
</cfoutput>
</body>
</html>