8.6 KiB
Renewal Views
Scope
Define SQL views in fenedgec_members to support renewal-letter generation.
These views must read only from the existing local tables:
fenedgec_members.accountsfenedgec_members.contactsfenedgec_members.renewalsfenedgec_members.picklist_account_typefenedgec_members.picklist_sectorfenedgec_members.picklist_public_location
Per requirements/environment.md:
- Recipient/source reads are in
MEMBERS_REMOTE_MYSQL_DB(here:fenedgec_members). - No fallback behavior is defined in this requirement.
Source Filters (Current Schema)
Use only active rows:
- Accounts:
a.is_deleted = 0 - Contacts:
c.is_deleted = 0 - Renewals: no delete flag exists in
renewals; include all renewal rows unless other filter criteria apply.
Required Output Data
Account details:
accounts.idaccounts.nameaccounts.account_no_of_membersaccounts.account_type_idplus joinedpicklist_account_type.valueexposed asaccounts.typeaccounts.public_location_idplus joinedpicklist_public_location.valueexposed asaccounts.public_locationaccounts.sector_idplus joinedpicklist_sector.valueexposed asaccounts.account_sectoraccounts.facebookaccounts.twitteraccounts.other_social
Renewal details:
renewals.idrenewals.account_idrenewals.renewal_yearrenewals.statusrenewals.payment_methodrenewals.payment_amountrenewals.payment_daterenewals.selectedrenewals.created_atrenewals.updated_at
Contact 1 details (contacts.is_contact_1 = 1):
- Name
- Position/title
- Address
- Phone 1
- Phone 2
Contact 2 details (contacts.is_contact_2 = 1):
- Name
- Position/title
- Phone 1
- Phone 2
Fen Edge News contact details (contacts.is_fen_1 = 1):
- Name
Public contact details (contacts.is_public_contact = 1):
- Public Phone
- Public Email
View Hierarchy
Create the following views:
renewal_members(accounts whose account type picklist slug ismember)renewal_pending(renewals withstatus = 'pending')renewal_contact_1(contacts whereis_contact_1 = 1)renewal_contact_2(contacts whereis_contact_2 = 1)renewal_fen_contact(contacts whereis_fen_1 = 1)renewal_public_contact(contacts whereis_public_contact = 1)renewal_accounts_with_contacts(final denormalized export view joining the above plusrenewal_pending)
Column Rules
renewal_members
Required columns:
account_id->accounts.idaccount_name->accounts.nameaccount_no_of_members->accounts.account_no_of_membersaccount_type_id->accounts.account_type_idaccount_type->picklist_account_type.value(equivalent toaccounts.type)sector_id->accounts.sector_idaccount_sector->picklist_sector.value(equivalent toaccounts.account_sector)public_location_id->accounts.public_location_idpublic_location->picklist_public_location.value(equivalent toaccounts.public_location)account_facebook->accounts.facebookaccount_twitter->accounts.twitteraccount_other_social->accounts.other_social
Filter:
- Member-status check must use account type picklist
slug. LOWER(TRIM(COALESCE(picklist_account_type.slug, ''))) = 'member'
Contact views
Each contact view must expose all contact fields needed for renewals, prefixed by view role:
renewal_contact_1: prefixcontact_1_renewal_contact_2: prefixcontact_2_renewal_fen_contact: prefixfen_contact_renewal_public_contact: prefixpublic_contact_
Minimum required prefixed columns:
..._contact_id->contacts.id..._account_id->contacts.account_id..._name->TRIM(CONCAT_WS(' ', contacts.first_name, contacts.last_name))..._position->contacts.title..._address_1->contacts.contact_address_1..._address_2->contacts.contact_address_2..._town->contacts.contact_town..._county->contacts.contact_county..._postcode->contacts.contact_postcode..._email->contacts.contact_email_1..._phone_1->contacts.phone_1..._phone_2->contacts.phone_2
renewal_public_contact
Required columns:
public_contact_contact_id->contacts.idpublic_contact_account_id->contacts.account_idpublic_contact_public_phone->contacts.public_phonepublic_contact_public_email->contacts.public_email
Filter:
contacts.is_public_contact = 1
renewal_pending
Required columns:
renewal_id->renewals.idrenewal_account_id->renewals.account_idrenewal_year->renewals.renewal_yearrenewal_status->renewals.statusrenewal_selected->renewals.selectedrenewal_payment_method->renewals.payment_methodrenewal_payment_amount->renewals.payment_amountrenewal_payment_date->renewals.payment_daterenewal_created_at->renewals.created_atrenewal_updated_at->renewals.updated_at
Filter:
LOWER(TRIM(COALESCE(renewals.status, ''))) = 'pending'
renewal_accounts_with_contacts
Join key:
renewal_members.account_id = renewal_pending.renewal_account_idrenewal_members.account_id = renewal_contact_X.contact_X_account_idrenewal_members.account_id = renewal_public_contact.public_contact_account_id
Join type:
- Left join all contact views so an account still appears when some contact roles are missing.
Output:
- All
renewal_memberscolumns - All
renewal_pendingcolumns - Selected prefixed columns from each contact view
Multiple-Match Tie-Break Rules
If multiple contacts match the same role for one account:
- Pick the lowest
contacts.id. - This rule must be deterministic and documented in view SQL (for example via grouped subquery/min id).
Null and Empty Handling
- Preserve NULL values from source fields.
- Computed
..._namemay be empty string when all name parts are blank. - Do not convert NULL to placeholder text such as
N/A.
Legacy Name Review (Flagged)
The following names in prior drafts do not match current schema and are explicitly flagged:
members.Account->NON-EXISTENT(usefenedgec_members.accounts)members.Contact->NON-EXISTENT(usefenedgec_members.contacts)members.renewals->NON-EXISTENTin that schema name (usefenedgec_members.renewals)Account.Name->NON-EXISTENT(useaccounts.name)Account.Type->NON-EXISTENT(useaccounts.account_type_id+picklist_account_type.slugfor member-status checks, andpicklist_account_type.valuefor display)Account.AccountNoOfMembers->NON-EXISTENT(useaccounts.account_no_of_members)Contact.Accountid->NON-EXISTENT(usecontacts.account_id)First/Middle/Last->NON-EXISTENT(usefirst_name/last_name)JobTitle->NON-EXISTENT; mapped tocontacts.title(AMBIGUOUS: verify business meaning)Address1/Address2/Town/County/Postcode->NON-EXISTENT(usecontact_address_1/contact_address_2/contact_town/contact_county/contact_postcode)Email->NON-EXISTENT; mapped tocontacts.contact_email_1(AMBIGUOUS: confirm whethercontact_email_2should also be included)Home->NON-EXISTENT(usecontacts.phone_1)Mobile->NON-EXISTENT(usecontacts.phone_2)Contact1->NON-EXISTENT(usecontacts.is_contact_1)Contact2->NON-EXISTENT(usecontacts.is_contact_2)FENContact1->NON-EXISTENT(usecontacts.is_fen_1)deleted->NON-EXISTENTon accounts/contacts (useis_deleted)
Acceptance Criteria
- All views compile in
fenedgec_members. renewal_memberscontains only active member accounts.renewal_pendingcontains only rows where status ispending.renewal_contact_1only includes contacts flaggedis_contact_1 = 1.renewal_contact_2only includes contacts flaggedis_contact_2 = 1.renewal_fen_contactonly includes contacts flaggedis_fen_1 = 1.renewal_public_contactonly includes contacts flaggedis_public_contact = 1.renewal_accounts_with_contactsreturns one row per pending renewal row (no duplicatedrenewal_id).account_no_of_membersis present in final output.- Renewal fields listed above are present in final output.
- Account picklist-backed values are present in final output as
account_type/account_sector/public_location(or equivalentaccounts.type/accounts.account_sector/accounts.public_locationaliases). - Account social fields are present in final output as
account_facebook,account_twitter, andaccount_other_social. - Member-status check is based on
picklist_account_type.slug, not the label text. - No remote cross-system query is used by these views.