diff --git a/models/tenant.go b/models/tenant.go index aba47a8..2088f17 100644 --- a/models/tenant.go +++ b/models/tenant.go @@ -57,6 +57,13 @@ type Tenantinfo struct { Approved int `json:"approved"` Moduleid int `json:"moduleid"` Subcategoryname string `json:"subcategoryname"` + // The app category this store trades under, by name. + // + // `GetTenantByID` has always joined `app_category b` and selected + // `b.categoryname`, and the struct had nowhere to put it — so the value was + // computed on every read and discarded, leaving the console to print + // "Category 2" at a merchant who trades under Daily Needs. + Categoryname string `json:"categoryname"` Firstname string `json:"firstname"` Lastname string `json:"lastname"` Accountname string `json:"Accountname"` diff --git a/services/tenantProfile.go b/services/tenantProfile.go index ec5c786..0da832d 100644 --- a/services/tenantProfile.go +++ b/services/tenantProfile.go @@ -37,6 +37,15 @@ var editableTenantFields = map[string]bool{ "primaryemail": true, "primarycontact": true, "companyname": true, + // The person who administers the shop — the Store admin. + // + // `tenants.firstname` — there is no lastname column, so this is the whole + // name. It was collected nowhere and writable nowhere, which is why every + // merchant read so far comes back with it empty and the store profile shows + // "—" for Store admin. It describes the business's own contact person, in + // the same way `companyname` does, and belongs to the merchant rather than + // to the platform. + "firstname": true, // Where it is. "address": true, diff --git a/services/tenantProfile_test.go b/services/tenantProfile_test.go index 31074a8..2d4d05a 100644 --- a/services/tenantProfile_test.go +++ b/services/tenantProfile_test.go @@ -130,3 +130,39 @@ func TestAllowingTheShopTypeDidNotAllowItsStanding(t *testing.T) { } } } + +// The person who runs the shop. +// +// `tenants.firstname` was collected by no form and permitted by no allowlist, +// so every merchant read came back with it empty and the store profile printed +// "—" for Store admin. It is the business's own contact person, not its +// standing on the platform. +func TestTenantProfileUpdateAllowsStoreAdmin(t *testing.T) { + clean, err := TenantProfileUpdate(map[string]any{ + "tenantid": 1147, + "firstname": "Ravi Kumar", + }) + if err != nil { + t.Fatalf("TenantProfileUpdate: %v", err) + } + if clean["firstname"] != "Ravi Kumar" { + t.Fatalf("firstname should survive the allowlist, got %v", clean["firstname"]) + } + if _, ok := clean["tenantid"]; ok { + t.Fatal("tenantid names the row and is never a value to write") + } +} + +// The guard that makes the allowlist worth having: a merchant must not be able +// to approve themselves or move under another partner by sending the field. +func TestTenantProfileUpdateStillRefusesPlatformFields(t *testing.T) { + _, err := TenantProfileUpdate(map[string]any{ + "tenantid": 1147, + "approved": 1, + "partnerid": 9, + "status": "Active", + }) + if err == nil { + t.Fatal("nothing in that request is editable, so it must not report success") + } +}