Grant-based system allowing businesses to share service points with other businesses. Includes grant CRUD API, time/eligibility/economics policies, enforcement at cart creation and order submit, Stripe payment routing for owner fees, and portal UI for managing grants. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
136 lines
5.3 KiB
Text
136 lines
5.3 KiB
Text
<cfscript>
|
|
/**
|
|
* Grant utility functions for SP-SM enforcement.
|
|
* Include this file where grant time/eligibility checks are needed.
|
|
*/
|
|
|
|
/**
|
|
* Check if a grant's time policy is currently active.
|
|
* @timePolicyType always | schedule | date_range | event
|
|
* @timePolicyData JSON string or struct with policy parameters
|
|
* @return boolean
|
|
*/
|
|
function isGrantTimeActive(required string timePolicyType, any timePolicyData = "") {
|
|
if (arguments.timePolicyType == "always") return true;
|
|
|
|
var policy = {};
|
|
if (isSimpleValue(arguments.timePolicyData) && len(trim(arguments.timePolicyData))) {
|
|
try { policy = deserializeJSON(arguments.timePolicyData); } catch (any e) { return false; }
|
|
} else if (isStruct(arguments.timePolicyData)) {
|
|
policy = arguments.timePolicyData;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
var nowDt = now();
|
|
|
|
switch (arguments.timePolicyType) {
|
|
case "schedule":
|
|
// policy: { days: [1,2,3,4,5], startTime: "09:00", endTime: "17:00" }
|
|
// days: 1=Sunday, 2=Monday, ... 7=Saturday (ColdFusion dayOfWeek)
|
|
if (!structKeyExists(policy, "days") || !isArray(policy.days)) return false;
|
|
var todayDow = dayOfWeek(nowDt);
|
|
if (!arrayFind(policy.days, todayDow)) return false;
|
|
if (structKeyExists(policy, "startTime") && structKeyExists(policy, "endTime")) {
|
|
var currentTime = timeFormat(nowDt, "HH:mm");
|
|
if (currentTime < policy.startTime || currentTime > policy.endTime) return false;
|
|
}
|
|
return true;
|
|
|
|
case "date_range":
|
|
// policy: { start: "2026-03-01", end: "2026-06-30" }
|
|
if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
|
|
var today = dateFormat(nowDt, "yyyy-mm-dd");
|
|
return (today >= policy.start && today <= policy.end);
|
|
|
|
case "event":
|
|
// policy: { name: "Summer Festival", start: "2026-07-04 10:00", end: "2026-07-04 22:00" }
|
|
if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
|
|
var nowStr = dateTimeFormat(nowDt, "yyyy-mm-dd HH:nn");
|
|
return (nowStr >= policy.start && nowStr <= policy.end);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a user meets the eligibility scope for a grant.
|
|
* @eligibilityScope public | employees | guests | internal
|
|
* @userID The ordering user's ID
|
|
* @ownerBusinessID The SP-owning business
|
|
* @guestBusinessID The granted business
|
|
* @return boolean
|
|
*/
|
|
function checkGrantEligibility(
|
|
required string eligibilityScope,
|
|
required numeric userID,
|
|
required numeric ownerBusinessID,
|
|
required numeric guestBusinessID
|
|
) {
|
|
if (arguments.eligibilityScope == "public") return true;
|
|
|
|
// Check if user is employee of a given business
|
|
var isGuestEmployee = queryExecute(
|
|
"SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
|
|
[
|
|
{ value = arguments.guestBusinessID, cfsqltype = "cf_sql_integer" },
|
|
{ value = arguments.userID, cfsqltype = "cf_sql_integer" }
|
|
],
|
|
{ datasource = "payfrit" }
|
|
).recordCount > 0;
|
|
|
|
var isOwnerEmployee = queryExecute(
|
|
"SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
|
|
[
|
|
{ value = arguments.ownerBusinessID, cfsqltype = "cf_sql_integer" },
|
|
{ value = arguments.userID, cfsqltype = "cf_sql_integer" }
|
|
],
|
|
{ datasource = "payfrit" }
|
|
).recordCount > 0;
|
|
|
|
switch (arguments.eligibilityScope) {
|
|
case "employees":
|
|
return isGuestEmployee;
|
|
case "guests":
|
|
return (!isGuestEmployee && !isOwnerEmployee);
|
|
case "internal":
|
|
return isOwnerEmployee;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record an entry in the immutable grant history table.
|
|
*/
|
|
function recordGrantHistory(
|
|
required numeric grantID,
|
|
required string action,
|
|
required numeric actorUserID,
|
|
required numeric actorBusinessID,
|
|
any previousData = "",
|
|
any newData = ""
|
|
) {
|
|
var prevJson = isStruct(arguments.previousData) ? serializeJSON(arguments.previousData) : (len(trim(arguments.previousData)) ? arguments.previousData : javaCast("null", ""));
|
|
var newJson = isStruct(arguments.newData) ? serializeJSON(arguments.newData) : (len(trim(arguments.newData)) ? arguments.newData : javaCast("null", ""));
|
|
|
|
var ip = "";
|
|
try { ip = cgi.REMOTE_ADDR; } catch (any e) {}
|
|
|
|
queryExecute(
|
|
"INSERT INTO ServicePointGrantHistory (GrantID, Action, ActorUserID, ActorBusinessID, PreviousData, NewData, IPAddress)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
[
|
|
{ value = arguments.grantID, cfsqltype = "cf_sql_integer" },
|
|
{ value = arguments.action, cfsqltype = "cf_sql_varchar" },
|
|
{ value = arguments.actorUserID, cfsqltype = "cf_sql_integer" },
|
|
{ value = arguments.actorBusinessID, cfsqltype = "cf_sql_integer" },
|
|
{ value = prevJson, cfsqltype = "cf_sql_varchar", null = isNull(prevJson) },
|
|
{ value = newJson, cfsqltype = "cf_sql_varchar", null = isNull(newJson) },
|
|
{ value = ip, cfsqltype = "cf_sql_varchar" }
|
|
],
|
|
{ datasource = "payfrit" }
|
|
);
|
|
}
|
|
</cfscript>
|