From 6e8ce04e9e62f023311e1168beace17daa126fa0 Mon Sep 17 00:00:00 2001 From: meichthys Date: Sat, 7 Mar 2026 03:44:15 +0000 Subject: [PATCH 1/4] Remove notable relationships in favor of family member relationships. --- .../family_members/family_members.json | 10 +- .../church_people/doctype/person/person.json | 774 +++++++++--------- church/church_people/doctype/person/person.py | 4 +- 3 files changed, 397 insertions(+), 391 deletions(-) diff --git a/church/church_people/doctype/family_members/family_members.json b/church/church_people/doctype/family_members/family_members.json index bbb35b7..5673137 100644 --- a/church/church_people/doctype/family_members/family_members.json +++ b/church/church_people/doctype/family_members/family_members.json @@ -8,7 +8,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "member" + "member", + "relationship_to_head" ], "fields": [ { @@ -19,6 +20,13 @@ "label": "Family Member", "options": "Person", "reqd": 1 + }, + { + "fieldname": "relationship_to_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Relationship to Head", + "options": "Person Relation Type" } ], "grid_page_length": 50, diff --git a/church/church_people/doctype/person/person.json b/church/church_people/doctype/person/person.json index d6d603b..00efced 100644 --- a/church/church_people/doctype/person/person.json +++ b/church/church_people/doctype/person/person.json @@ -1,388 +1,388 @@ { - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "format:Person {#}", - "creation": "2025-08-21 01:19:39.714186", - "description": "A person that has interacted with the church in some way (i.e. attendee, member, visitor, etc).", - "doctype": "DocType", - "documentation": "/app/manual%3A-people", - "engine": "InnoDB", - "field_order": [ - "personal_information_section", - "first_name", - "last_name", - "full_name", - "gender", - "column_break_agva", - "photo", - "birthday", - "alergies", - "portal_user", - "church_records_section", - "is_member", - "membership_date", - "membership_status", - "column_break_lvwv", - "is_baptized", - "baptism_date", - "section_break_osjz", - "positions", - "contact_information_tab", - "contact_information_section", - "home_address", - "different_mailing_address", - "mailing_address", - "column_break_vzrz", - "primary_phone", - "email", - "section_break_eqre", - "letters", - "family_tab", - "column_break_gwhd", - "family", - "is_head_of_household", - "is_married", - "anniversary", - "spouse", - "column_break_fkwj", - "relationships", - "notes_tab", - "notes" - ], - "fields": [ - { - "allow_in_quick_entry": 1, - "fieldname": "first_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "First Name", - "reqd": 1 - }, - { - "allow_in_quick_entry": 1, - "fieldname": "last_name", - "fieldtype": "Data", - "in_standard_filter": 1, - "label": "Last Name" - }, - { - "fieldname": "full_name", - "fieldtype": "Data", - "in_preview": 1, - "label": "Full Name", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_gwhd", - "fieldtype": "Column Break" - }, - { - "allow_in_quick_entry": 1, - "default": "0", - "description": "Is this person a church member?", - "fieldname": "is_member", - "fieldtype": "Check", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Is Member" - }, - { - "allow_in_quick_entry": 1, - "default": "Today", - "depends_on": "eval:doc.is_member", - "description": "When did the person become a member?", - "fieldname": "membership_date", - "fieldtype": "Date", - "label": "Membership Date", - "mandatory_depends_on": "eval:doc.is_member" - }, - { - "fieldname": "birthday", - "fieldtype": "Date", - "label": "Birthday" - }, - { - "fieldname": "personal_information_section", - "fieldtype": "Section Break", - "label": "Personal Information" - }, - { - "fieldname": "contact_information_section", - "fieldtype": "Section Break" - }, - { - "fieldname": "home_address", - "fieldtype": "Link", - "label": "Home Address", - "options": "Address" - }, - { - "depends_on": "eval: doc.different_mailing_address", - "fieldname": "mailing_address", - "fieldtype": "Link", - "label": "Mailing Address", - "options": "Address" - }, - { - "fieldname": "column_break_vzrz", - "fieldtype": "Column Break" - }, - { - "fieldname": "primary_phone", - "fieldtype": "Phone", - "in_preview": 1, - "label": "Primary Phone" - }, - { - "fieldname": "email", - "fieldtype": "Data", - "in_preview": 1, - "label": "Email" - }, - { - "allow_in_quick_entry": 1, - "fieldname": "family", - "fieldtype": "Link", - "in_list_view": 1, - "in_preview": 1, - "label": "Family", - "options": "Family", - "print_hide": 1, - "search_index": 1 - }, - { - "allow_in_quick_entry": 1, - "default": "0", - "depends_on": "eval:doc.family;", - "description": "Is this person 'Head of Household\" for their Family?", - "fieldname": "is_head_of_household", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Head of Household", - "print_hide": 1 - }, - { - "fieldname": "photo", - "fieldtype": "Attach Image", - "label": "Photo" - }, - { - "fieldname": "contact_information_tab", - "fieldtype": "Tab Break", - "label": "Contact" - }, - { - "fieldname": "alergies", - "fieldtype": "Data", - "label": "Alergies", - "print_hide": 1 - }, - { - "fieldname": "church_records_section", - "fieldtype": "Section Break", - "label": "Church Records" - }, - { - "fieldname": "column_break_lvwv", - "fieldtype": "Column Break" - }, - { - "allow_in_quick_entry": 1, - "depends_on": "eval:doc.is_member", - "fieldname": "membership_status", - "fieldtype": "Link", - "in_preview": 1, - "label": "Membership Status", - "mandatory_depends_on": "eval:doc.is_member", - "options": "Member Status", - "search_index": 1 - }, - { - "default": "0", - "fieldname": "is_baptized", - "fieldtype": "Check", - "label": "Is Baptized" - }, - { - "depends_on": "eval:doc.is_baptized", - "fieldname": "baptism_date", - "fieldtype": "Date", - "label": "Baptism Date" - }, - { - "fieldname": "positions", - "fieldtype": "Table", - "label": "Positions", - "options": "Position" - }, - { - "fieldname": "section_break_osjz", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_agva", - "fieldtype": "Column Break" - }, - { - "fieldname": "family_tab", - "fieldtype": "Tab Break", - "label": "Family" - }, - { - "default": "0", - "fieldname": "is_married", - "fieldtype": "Check", - "label": "Is Married", - "print_hide": 1 - }, - { - "depends_on": "eval:doc.is_married;", - "fieldname": "anniversary", - "fieldtype": "Date", - "label": "Anniversary" - }, - { - "depends_on": "eval:doc.is_married;", - "fieldname": "spouse", - "fieldtype": "Link", - "in_preview": 1, - "label": "Spouse", - "link_filters": "[[\"Person\",\"name\",\"!=\",\"{{doc.id}}\"]]", - "options": "Person" - }, - { - "fieldname": "column_break_fkwj", - "fieldtype": "Column Break" - }, - { - "fieldname": "notes_tab", - "fieldtype": "Tab Break", - "in_preview": 1, - "label": "Notes" - }, - { - "fieldname": "notes", - "fieldtype": "Text Editor", - "label": "Notes", - "print_hide": 1 - }, - { - "allow_in_quick_entry": 1, - "description": "Note: Relations are not automatically reciprocal. (i.e. If you are editing 'Mark' and add 'Jane' as 'Sister', Jane's record will not automatically be updated to show 'Mark' as 'Brother'. ", - "fieldname": "relationships", - "fieldtype": "Table", - "label": "Notable Relationships", - "options": "Person Relation", - "print_hide": 1 - }, - { - "allow_in_quick_entry": 1, - "default": "Unknown", - "fieldname": "gender", - "fieldtype": "Select", - "label": "Gender", - "options": "Female\nMale\nUnknown", - "print_hide": 1 - }, - { - "fieldname": "section_break_eqre", - "fieldtype": "Section Break" - }, - { - "fieldname": "letters", - "fieldtype": "Table", - "label": "Letters to the Church", - "options": "Letter", - "print_hide": 1 - }, - { - "fieldname": "portal_user", - "fieldtype": "Link", - "label": "Portal User", - "options": "User" - }, - { - "default": "0", - "fieldname": "different_mailing_address", - "fieldtype": "Check", - "label": "Different Mailing Address" - } - ], - "grid_page_length": 50, - "image_field": "photo", - "index_web_pages_for_search": 1, - "links": [ - { - "link_doctype": "Prayer Request", - "link_fieldname": "requestor" - }, - { - "link_doctype": "Prayer", - "link_fieldname": "person" - }, - { - "link_doctype": "Collection", - "link_fieldname": "person" - }, - { - "link_doctype": "Function", - "link_fieldname": "person" - }, - { - "link_doctype": "Alms Request", - "link_fieldname": "recipient" - } - ], - "modified": "2026-02-22 23:11:46.660564", - "modified_by": "Administrator", - "module": "Church People", - "name": "Person", - "naming_rule": "Expression", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Church Manager", - "select": 1, - "share": 1, - "write": 1 - }, - { - "read": 1, - "role": "Church User", - "write": 1 - } - ], - "quick_entry": 1, - "route": "church-personz", - "row_format": "Dynamic", - "search_fields": "last_name, first_name", - "show_preview_popup": 1, - "show_title_field_in_link": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "title_field": "full_name", - "track_changes": 1 -} \ No newline at end of file + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "format:Person {#}", + "creation": "2025-08-21 01:19:39.714186", + "description": "A person that has interacted with the church in some way (i.e. attendee, member, visitor, etc).", + "doctype": "DocType", + "documentation": "/app/manual%3A-people", + "engine": "InnoDB", + "field_order": [ + "personal_information_section", + "church", + "first_name", + "last_name", + "full_name", + "gender", + "column_break_agva", + "photo", + "birthday", + "alergies", + "portal_user", + "church_records_section", + "is_member", + "membership_date", + "membership_status", + "column_break_lvwv", + "is_baptized", + "baptism_date", + "section_break_osjz", + "positions", + "contact_information_tab", + "contact_information_section", + "home_address", + "different_mailing_address", + "mailing_address", + "column_break_vzrz", + "primary_phone", + "email", + "section_break_eqre", + "letters", + "family_tab", + "column_break_gwhd", + "family", + "is_head_of_household", + "is_married", + "anniversary", + "spouse", + "column_break_fkwj", + "notes_tab", + "notes" + ], + "fields": [ + { + "fieldname": "church", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Church", + "options": "Church", + "search_index": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "first_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "First Name", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "last_name", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Last Name" + }, + { + "fieldname": "full_name", + "fieldtype": "Data", + "in_preview": 1, + "label": "Full Name", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_gwhd", + "fieldtype": "Column Break" + }, + { + "allow_in_quick_entry": 1, + "default": "0", + "description": "Is this person a church member?", + "fieldname": "is_member", + "fieldtype": "Check", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Is Member" + }, + { + "allow_in_quick_entry": 1, + "default": "Today", + "depends_on": "eval:doc.is_member", + "description": "When did the person become a member?", + "fieldname": "membership_date", + "fieldtype": "Date", + "label": "Membership Date", + "mandatory_depends_on": "eval:doc.is_member" + }, + { + "fieldname": "birthday", + "fieldtype": "Date", + "label": "Birthday" + }, + { + "fieldname": "personal_information_section", + "fieldtype": "Section Break", + "label": "Personal Information" + }, + { + "fieldname": "contact_information_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "home_address", + "fieldtype": "Link", + "label": "Home Address", + "options": "Address" + }, + { + "depends_on": "eval: doc.different_mailing_address", + "fieldname": "mailing_address", + "fieldtype": "Link", + "label": "Mailing Address", + "options": "Address" + }, + { + "fieldname": "column_break_vzrz", + "fieldtype": "Column Break" + }, + { + "fieldname": "primary_phone", + "fieldtype": "Phone", + "in_preview": 1, + "label": "Primary Phone" + }, + { + "fieldname": "email", + "fieldtype": "Data", + "in_preview": 1, + "label": "Email" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "family", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "label": "Family", + "options": "Family", + "print_hide": 1, + "search_index": 1 + }, + { + "allow_in_quick_entry": 1, + "default": "0", + "depends_on": "eval:doc.family;", + "description": "Is this person 'Head of Household\" for their Family?", + "fieldname": "is_head_of_household", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Head of Household", + "print_hide": 1 + }, + { + "fieldname": "photo", + "fieldtype": "Attach Image", + "label": "Photo" + }, + { + "fieldname": "contact_information_tab", + "fieldtype": "Tab Break", + "label": "Contact" + }, + { + "fieldname": "alergies", + "fieldtype": "Data", + "label": "Alergies", + "print_hide": 1 + }, + { + "fieldname": "church_records_section", + "fieldtype": "Section Break", + "label": "Church Records" + }, + { + "fieldname": "column_break_lvwv", + "fieldtype": "Column Break" + }, + { + "allow_in_quick_entry": 1, + "depends_on": "eval:doc.is_member", + "fieldname": "membership_status", + "fieldtype": "Link", + "in_preview": 1, + "label": "Membership Status", + "mandatory_depends_on": "eval:doc.is_member", + "options": "Member Status", + "search_index": 1 + }, + { + "default": "0", + "fieldname": "is_baptized", + "fieldtype": "Check", + "label": "Is Baptized" + }, + { + "depends_on": "eval:doc.is_baptized", + "fieldname": "baptism_date", + "fieldtype": "Date", + "label": "Baptism Date" + }, + { + "fieldname": "positions", + "fieldtype": "Table", + "label": "Positions", + "options": "Position" + }, + { + "fieldname": "section_break_osjz", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_agva", + "fieldtype": "Column Break" + }, + { + "fieldname": "family_tab", + "fieldtype": "Tab Break", + "label": "Family" + }, + { + "default": "0", + "fieldname": "is_married", + "fieldtype": "Check", + "label": "Is Married", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_married;", + "fieldname": "anniversary", + "fieldtype": "Date", + "label": "Anniversary" + }, + { + "depends_on": "eval:doc.is_married;", + "fieldname": "spouse", + "fieldtype": "Link", + "in_preview": 1, + "label": "Spouse", + "link_filters": "[[\"Person\",\"name\",\"!=\",\"{{doc.id}}\"]]", + "options": "Person" + }, + { + "fieldname": "column_break_fkwj", + "fieldtype": "Column Break" + }, + { + "fieldname": "notes_tab", + "fieldtype": "Tab Break", + "in_preview": 1, + "label": "Notes" + }, + { + "fieldname": "notes", + "fieldtype": "Text Editor", + "label": "Notes", + "print_hide": 1 + }, + { + "allow_in_quick_entry": 1, + "default": "Unknown", + "fieldname": "gender", + "fieldtype": "Select", + "label": "Gender", + "options": "Female\nMale\nUnknown", + "print_hide": 1 + }, + { + "fieldname": "section_break_eqre", + "fieldtype": "Section Break" + }, + { + "fieldname": "letters", + "fieldtype": "Table", + "label": "Letters to the Church", + "options": "Letter", + "print_hide": 1 + }, + { + "fieldname": "portal_user", + "fieldtype": "Link", + "label": "Portal User", + "options": "User" + }, + { + "default": "0", + "fieldname": "different_mailing_address", + "fieldtype": "Check", + "label": "Different Mailing Address" + } + ], + "grid_page_length": 50, + "image_field": "photo", + "index_web_pages_for_search": 1, + "links": [ + { + "link_doctype": "Prayer Request", + "link_fieldname": "requestor" + }, + { + "link_doctype": "Prayer", + "link_fieldname": "person" + }, + { + "link_doctype": "Collection", + "link_fieldname": "person" + }, + { + "link_doctype": "Function", + "link_fieldname": "person" + }, + { + "link_doctype": "Alms Request", + "link_fieldname": "recipient" + } + ], + "modified": "2026-02-22 23:11:46.660564", + "modified_by": "Administrator", + "module": "Church People", + "name": "Person", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Church Manager", + "select": 1, + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Church User", + "write": 1 + } + ], + "quick_entry": 1, + "route": "church-personz", + "row_format": "Dynamic", + "search_fields": "last_name, first_name", + "show_preview_popup": 1, + "show_title_field_in_link": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "full_name", + "track_changes": 1 +} diff --git a/church/church_people/doctype/person/person.py b/church/church_people/doctype/person/person.py index 0fba087..fceda71 100644 --- a/church/church_people/doctype/person/person.py +++ b/church/church_people/doctype/person/person.py @@ -109,9 +109,7 @@ class Person(Document): @frappe.whitelist() def new_family_from_person(self): # Check if a family with this person's name already exists - existing_family = frappe.db.exists( - "Family", {"family_name": f"{self.last_name} - {self.first_name}"} - ) + existing_family = frappe.db.exists("Family", {"family_name": f"{self.last_name} - {self.first_name}"}) if existing_family: # Set this person's family to the existing one From 6dbb102dadc2fd2af5025f130f15e5277063b8ea Mon Sep 17 00:00:00 2001 From: meichthys Date: Sat, 7 Mar 2026 04:03:22 +0000 Subject: [PATCH 2/4] remove unused "Church Location" doctype --- .../doctype/church_location/__init__.py | 0 .../church_location/church_location.js | 8 - .../church_location/church_location.json | 139 ------------------ .../church_location/church_location.py | 9 -- .../church_location/test_church_location.py | 9 -- .../workspace/operations/operations.json | 12 +- 6 files changed, 1 insertion(+), 176 deletions(-) delete mode 100644 church/church_operations/doctype/church_location/__init__.py delete mode 100644 church/church_operations/doctype/church_location/church_location.js delete mode 100644 church/church_operations/doctype/church_location/church_location.json delete mode 100644 church/church_operations/doctype/church_location/church_location.py delete mode 100644 church/church_operations/doctype/church_location/test_church_location.py diff --git a/church/church_operations/doctype/church_location/__init__.py b/church/church_operations/doctype/church_location/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/church/church_operations/doctype/church_location/church_location.js b/church/church_operations/doctype/church_location/church_location.js deleted file mode 100644 index c7589a4..0000000 --- a/church/church_operations/doctype/church_location/church_location.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026, meichthys and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("Church Location", { -// refresh(frm) { - -// }, -// }); diff --git a/church/church_operations/doctype/church_location/church_location.json b/church/church_operations/doctype/church_location/church_location.json deleted file mode 100644 index eb6f50d..0000000 --- a/church/church_operations/doctype/church_location/church_location.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "format:{title}", - "creation": "2026-01-05 23:28:09.404374", - "default_view": "Tree", - "description": "A physical location associated with the Church. (i.e. Office, Library, etc)", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "title", - "notes", - "column_break_djot", - "parent_church_location", - "is_group", - "photo", - "hidden_fields_section", - "lft", - "rgt", - "old_parent" - ], - "fields": [ - { - "allow_in_quick_entry": 1, - "fieldname": "title", - "fieldtype": "Data", - "in_list_view": 1, - "in_preview": 1, - "in_standard_filter": 1, - "label": "Title", - "reqd": 1 - }, - { - "allow_in_quick_entry": 1, - "fieldname": "notes", - "fieldtype": "Text Editor", - "in_list_view": 1, - "in_preview": 1, - "label": "Notes" - }, - { - "fieldname": "lft", - "fieldtype": "Int", - "hidden": 1, - "label": "Left", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "rgt", - "fieldtype": "Int", - "hidden": 1, - "label": "Right", - "no_copy": 1, - "read_only": 1 - }, - { - "default": "0", - "description": "Check this if other locations are located within this location.", - "fieldname": "is_group", - "fieldtype": "Check", - "label": "Is Group" - }, - { - "fieldname": "old_parent", - "fieldtype": "Link", - "label": "Old Parent", - "options": "Church Location" - }, - { - "description": "If this location is located within another location, choose the other location here.", - "fieldname": "parent_church_location", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Parent Location", - "options": "Church Location" - }, - { - "fieldname": "column_break_djot", - "fieldtype": "Column Break" - }, - { - "fieldname": "hidden_fields_section", - "fieldtype": "Section Break", - "hidden": 1, - "label": "Hidden Fields" - }, - { - "fieldname": "photo", - "fieldtype": "Attach Image", - "label": "Photo" - } - ], - "grid_page_length": 50, - "image_field": "photo", - "index_web_pages_for_search": 1, - "is_tree": 1, - "links": [], - "modified": "2026-01-05 23:51:38.123146", - "modified_by": "Administrator", - "module": "Church Operations", - "name": "Church Location", - "naming_rule": "Expression", - "nsm_parent_field": "parent_church_location", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Church Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "row_format": "Dynamic", - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/church/church_operations/doctype/church_location/church_location.py b/church/church_operations/doctype/church_location/church_location.py deleted file mode 100644 index 75a8f52..0000000 --- a/church/church_operations/doctype/church_location/church_location.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, meichthys and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class ChurchLocation(Document): - pass diff --git a/church/church_operations/doctype/church_location/test_church_location.py b/church/church_operations/doctype/church_location/test_church_location.py deleted file mode 100644 index 36754d4..0000000 --- a/church/church_operations/doctype/church_location/test_church_location.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, meichthys and Contributors -# See license.txt - -# import frappe -from frappe.tests.utils import FrappeTestCase - - -class TestChurchLocation(FrappeTestCase): - pass diff --git a/church/church_operations/workspace/operations/operations.json b/church/church_operations/workspace/operations/operations.json index 44c7740..840f237 100644 --- a/church/church_operations/workspace/operations/operations.json +++ b/church/church_operations/workspace/operations/operations.json @@ -17,7 +17,7 @@ "hidden": 0, "is_query_report": 0, "label": "Operations Documents", - "link_count": 3, + "link_count": 2, "link_type": "DocType", "onboard": 0, "type": "Card Break" @@ -32,16 +32,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Church Locations", - "link_count": 0, - "link_to": "Church Location", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, From 9af58a227f3e20e422b598e213e5a66dc46757a2 Mon Sep 17 00:00:00 2001 From: meichthys Date: Sun, 8 Mar 2026 03:59:21 +0000 Subject: [PATCH 3/4] Add Church Directory --- .../doctype/church/church.json | 165 ++++++++++++++++++ .../church_members/church_members.json | 4 +- .../workspace/people/people.json | 33 +++- 3 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 church/church_foundations/doctype/church/church.json diff --git a/church/church_foundations/doctype/church/church.json b/church/church_foundations/doctype/church/church.json new file mode 100644 index 0000000..1507cef --- /dev/null +++ b/church/church_foundations/doctype/church/church.json @@ -0,0 +1,165 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:church_name", + "creation": "2025-09-17 21:10:06.782386", + "default_view": "List", + "description": "A church or church branch.", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "church_name", + "legal_name", + "is_group", + "parent_church", + "column_break_rmsm", + "founding_date", + "address", + "default_bible_translation", + "mission_statement", + "section_break_nxne", + "about", + "hidden_fields_section", + "lft", + "rgt", + "old_parent" + ], + "fields": [ + { + "fieldname": "church_name", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "legal_name", + "fieldtype": "Data", + "label": "Legal Name" + }, + { + "fieldname": "founding_date", + "fieldtype": "Date", + "label": "Founding Date" + }, + { + "fieldname": "column_break_rmsm", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Check this if other churches are grouped under this one.", + "fieldname": "is_group", + "fieldtype": "Check", + "label": "Is Group" + }, + { + "description": "If this church is a branch of another, select the parent here.", + "fieldname": "parent_church", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Parent Church", + "options": "Church" + }, + { + "fieldname": "address", + "fieldtype": "Link", + "label": "Address", + "options": "Address" + }, + { + "fieldname": "default_bible_translation", + "fieldtype": "Link", + "label": "Default Bible Translation", + "options": "Bible Translation" + }, + { + "fieldname": "mission_statement", + "fieldtype": "Small Text", + "label": "Mission Statement" + }, + { + "fieldname": "section_break_nxne", + "fieldtype": "Section Break" + }, + { + "description": "A short description of the church. By default, this is used on the 'About Us' webpage.", + "fieldname": "about", + "fieldtype": "Text Editor", + "label": "About" + }, + { + "fieldname": "hidden_fields_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Hidden Fields" + }, + { + "fieldname": "lft", + "fieldtype": "Int", + "hidden": 1, + "label": "Left", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "rgt", + "fieldtype": "Int", + "hidden": 1, + "label": "Right", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "old_parent", + "fieldtype": "Link", + "label": "Old Parent", + "options": "Church" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "is_tree": 1, + "links": [], + "modified": "2026-03-07 17:02:03.356563", + "modified_by": "Administrator", + "module": "Church Foundations", + "name": "Church", + "naming_rule": "By fieldname", + "nsm_parent_field": "parent_church", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Church Manager", + "select": 1, + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "row_format": "Dynamic", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "church_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/church/church_people/number_card/church_members/church_members.json b/church/church_people/number_card/church_members/church_members.json index 3218648..521a772 100644 --- a/church/church_people/number_card/church_members/church_members.json +++ b/church/church_people/number_card/church_members/church_members.json @@ -11,8 +11,8 @@ "idx": 0, "is_public": 0, "is_standard": 1, - "label": "Church Member Count", - "modified": "2025-09-21 22:43:41.991451", + "label": "Member Count", + "modified": "2026-03-07 15:50:10.752238", "modified_by": "Administrator", "module": "Church People", "name": "Church Members", diff --git a/church/church_people/workspace/people/people.json b/church/church_people/workspace/people/people.json index 0ec983f..f5b0a41 100644 --- a/church/church_people/workspace/people/people.json +++ b/church/church_people/workspace/people/people.json @@ -1,11 +1,11 @@ { "charts": [ { - "chart_name": "Persons Count", + "chart_name": "People", "label": "Persons" }, { - "chart_name": "Member Count (New by Month)", + "chart_name": "Members", "label": "New Members" } ], @@ -82,12 +82,23 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Church Directory", + "link_count": 0, + "link_to": "Church Directory Report", + "link_type": "Report", + "onboard": 0, + "report_ref_doctype": "Family", + "type": "Link" + }, { "description": "These are some pre-prepared reports. You can always create your own reports by going to `[Document] Report`, filtering the report, then choosing `save as` in the `...` menu.", "hidden": 0, "is_query_report": 0, "label": "Person Reports", - "link_count": 5, + "link_count": 6, "link_type": "DocType", "onboard": 0, "type": "Card Break" @@ -144,9 +155,19 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Church Directory", + "link_count": 0, + "link_to": "Church Directory Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2026-03-04 23:13:06.657575", + "modified": "2026-03-07 16:11:26.024550", "modified_by": "Administrator", "module": "Church People", "name": "People", @@ -157,11 +178,11 @@ }, { "label": "Members", - "number_card_name": "Church Members" + "number_card_name": "Members" }, { "label": "Families", - "number_card_name": "Church Families" + "number_card_name": "Families" } ], "owner": "Administrator", From 96c49420a1458d1b7f218f408a67585f31721cc7 Mon Sep 17 00:00:00 2001 From: meichthys Date: Sun, 8 Mar 2026 03:59:31 +0000 Subject: [PATCH 4/4] finish church directory --- .../doctype/church/church.js | 8 + .../dashboard_chart/members/members.json | 34 ++ .../dashboard_chart/people/people.json | 33 ++ .../church_directory_report/__init__.py | 0 .../church_directory.html | 525 ++++++++++++++++++ .../church_directory_report.js | 115 ++++ .../church_directory_report.json | 35 ++ .../church_directory_report.py | 395 +++++++++++++ 8 files changed, 1145 insertions(+) create mode 100644 church/church_foundations/doctype/church/church.js create mode 100644 church/church_people/dashboard_chart/members/members.json create mode 100644 church/church_people/dashboard_chart/people/people.json create mode 100644 church/church_people/report/church_directory_report/__init__.py create mode 100644 church/church_people/report/church_directory_report/church_directory.html create mode 100644 church/church_people/report/church_directory_report/church_directory_report.js create mode 100644 church/church_people/report/church_directory_report/church_directory_report.json create mode 100644 church/church_people/report/church_directory_report/church_directory_report.py diff --git a/church/church_foundations/doctype/church/church.js b/church/church_foundations/doctype/church/church.js new file mode 100644 index 0000000..dd0d822 --- /dev/null +++ b/church/church_foundations/doctype/church/church.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, meichthys and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Church", { +// refresh(frm) { + +// }, +// }); diff --git a/church/church_people/dashboard_chart/members/members.json b/church/church_people/dashboard_chart/members/members.json new file mode 100644 index 0000000..ebcdab0 --- /dev/null +++ b/church/church_people/dashboard_chart/members/members.json @@ -0,0 +1,34 @@ +{ + "based_on": "membership_date", + "chart_name": "Members", + "chart_type": "Sum", + "creation": "2026-03-07 15:58:56.308560", + "currency": "USD", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Person", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Person\",\"is_member\",\"=\",1,false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2026-03-07 16:00:38.065515", + "modified": "2026-03-07 16:01:28.327322", + "modified_by": "Administrator", + "module": "Church People", + "name": "Members", + "number_of_groups": 0, + "owner": "Administrator", + "parent_document_type": "", + "roles": [], + "show_values_over_chart": 1, + "source": "", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "", + "y_axis": [] +} \ No newline at end of file diff --git a/church/church_people/dashboard_chart/people/people.json b/church/church_people/dashboard_chart/people/people.json new file mode 100644 index 0000000..04b21d8 --- /dev/null +++ b/church/church_people/dashboard_chart/people/people.json @@ -0,0 +1,33 @@ +{ + "based_on": "creation", + "chart_name": "People", + "chart_type": "Sum", + "creation": "2026-03-07 15:56:57.922304", + "currency": "USD", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Person", + "dynamic_filters_json": "[]", + "filters_json": "[]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "modified": "2026-03-07 15:56:57.922304", + "modified_by": "Administrator", + "module": "Church People", + "name": "People", + "number_of_groups": 0, + "owner": "Administrator", + "parent_document_type": "", + "roles": [], + "show_values_over_chart": 1, + "source": "", + "time_interval": "Weekly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "", + "y_axis": [] +} \ No newline at end of file diff --git a/church/church_people/report/church_directory_report/__init__.py b/church/church_people/report/church_directory_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/church/church_people/report/church_directory_report/church_directory.html b/church/church_people/report/church_directory_report/church_directory.html new file mode 100644 index 0000000..744c0b7 --- /dev/null +++ b/church/church_people/report/church_directory_report/church_directory.html @@ -0,0 +1,525 @@ + + + + +{{ church.church_name }} – Church Directory + + + + + +
+
{{ church.church_name }}
+ + {% if church.legal_name and church.legal_name != church.church_name %} + + {% endif %} + + {% if church_address %} +
+ {{ church_address.address_line1 }}{% if church_address.address_line2 %}
{{ church_address.address_line2 }}{% endif %}
+ {{ church_address.city }}{% if church_address.state %}, {{ church_address.state }}{% endif %}{% if church_address.pincode %} {{ church_address.pincode }}{% endif %} +
+ {% endif %} + +
Church Directory
+
{{ generated_date }}
+ + {% if church.mission_statement %} +
{{ church.mission_statement }}
+ {% endif %} +
+ + +{% set ns = namespace(current_letter='') %} +{% for entry in all_entries %} + {% set first_letter = (entry.sort_name[0] if entry.sort_name else '#').upper() %} + {% if first_letter != ns.current_letter %} + {% set ns.current_letter = first_letter %} +
{{ first_letter }}
+ {% endif %} + +
+
+ +
+
{{ entry.display_name }}
+ {% if show_church_label %}
{{ entry.church_name }}
{% endif %} +
+ + +
+ {% if entry.address_line1 or entry.city %} +
+ {% if entry.address_line1 %}{{ entry.address_line1 }}{% if entry.address_line2 %}, {{ entry.address_line2 }}{% endif %}
{% endif %} + {% if entry.city %}{{ entry.city }}{% if entry.state %}, {{ entry.state }}{% endif %}{% if entry.pincode %} {{ entry.pincode }}{% endif %}{% endif %} +
+ {% endif %} + {% if show_photos and entry.family_photo %} + {{ entry.display_name }} + {% endif %} +
+
+ + + + + {% if show_photos %}{% endif %} + + {% if show_membership %}{% endif %} + + + + + + {% for member in entry.members %} + + {% if show_photos %} + + {% endif %} + + {% if show_membership %} + + {% endif %} + + + + {% endfor %} + +
NameMembershipPhone
+ {% if member.photo %} + + {% endif %} + + {{ member.full_name }} + {% if show_hoh and member.is_head_of_household %}{% endif %} + {% if show_roles and member.positions %} +
{{ member.positions | join(', ') }}
+ {% endif %} +
+ {% if member.membership_status %} + {{ member.membership_status }} + {% endif %} + {{ member.primary_phone or '' }}
+
+{% endfor %} + + +{% if show_birthdays and birthdays %} +
+
Birthdays
+ {% set ns2 = namespace(current_month='') %} + {% for person in birthdays %} + {% if person.month_name != ns2.current_month %} + {% set ns2.current_month = person.month_name %} +
{{ person.month_name }}
+ {% endif %} +
+ {{ person.full_name }} + {{ person.month_day }} +
+ {% endfor %} +
+{% endif %} + + +{% if show_anniversaries and anniversaries %} +
+
Anniversaries
+ {% set ns3 = namespace(current_month='') %} + {% for couple in anniversaries %} + {% if couple.month_name != ns3.current_month %} + {% set ns3.current_month = couple.month_name %} +
{{ couple.month_name }}
+ {% endif %} +
+ {{ couple.display_name }} + {{ couple.month_day }} +
+ {% endfor %} +
+{% endif %} + + +{% if show_missionaries and missionaries %} +
+
Missionaries
+ {% for m in missionaries %} +
+
+ {% if show_photos and m.photo %} + {{ m.title }} + {% endif %} +
+
{{ m.title }}
+ {% if m.agency %}
{{ m.agency }}
{% endif %} + {% if m.country and not m.sensitive %}
{{ m.country }}
{% endif %} + {% if m.sensitive %}Sensitive{% endif %} + {% if m.mission_statement %}
{{ m.mission_statement }}
{% endif %} +
+
+ {% if not m.sensitive and (m.email or m.website) %} +
+ {% if m.email %}Email: {{ m.email }}{% endif %} + {% if m.email and m.website %}
{% endif %} + {% if m.website %}Website: {{ m.website }}{% endif %} +
+ {% endif %} +
+ {% endfor %} +
+{% endif %} + + + diff --git a/church/church_people/report/church_directory_report/church_directory_report.js b/church/church_people/report/church_directory_report/church_directory_report.js new file mode 100644 index 0000000..71e5894 --- /dev/null +++ b/church/church_people/report/church_directory_report/church_directory_report.js @@ -0,0 +1,115 @@ +frappe.query_reports["Church Directory Report"] = { + filters: [ + { + fieldname: "church", + label: __("Church"), + fieldtype: "Link", + options: "Church", + reqd: 1, + }, + { + fieldname: "include_sub_churches", + label: __("Include Sub-Churches"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "members_only", + label: __("Members Only"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "show_photos", + label: __("Show Photos"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "show_roles", + label: __("Show Positions"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "show_membership", + label: __("Show Membership Status"), + fieldtype: "Check", + default: 1, + }, + { + fieldname: "show_hoh", + label: __("Show Head of Household"), + fieldtype: "Check", + default: 1, + }, + { + fieldname: "show_birthdays", + label: __("Include Birthday List"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "show_anniversaries", + label: __("Include Anniversary List"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "show_missionaries", + label: __("Include Missionaries"), + fieldtype: "Check", + default: 0, + }, + ], + + onload: function (report) { + report.page.add_inner_button(__('Print Directory'), function () { + const church = report.get_filter_value('church'); + const include_sub_churches = report.get_filter_value('include_sub_churches') ? 1 : 0; + const members_only = report.get_filter_value('members_only') ? 1 : 0; + const show_photos = report.get_filter_value('show_photos') ? 1 : 0; + const show_roles = report.get_filter_value('show_roles') ? 1 : 0; + const show_membership = report.get_filter_value('show_membership') ? 1 : 0; + const show_hoh = report.get_filter_value('show_hoh') ? 1 : 0; + const show_birthdays = report.get_filter_value('show_birthdays') ? 1 : 0; + const show_anniversaries = report.get_filter_value('show_anniversaries') ? 1 : 0; + const show_missionaries = report.get_filter_value('show_missionaries') ? 1 : 0; + + if (!church) { + frappe.msgprint(__('Please select a Church first.')); + return; + } + + frappe.call({ + method: 'church.church_people.report.church_directory_report.church_directory_report.get_directory_html', + args: { + church, + include_sub_churches, + members_only, + show_photos, + show_roles, + show_membership, + show_hoh, + show_birthdays, + show_anniversaries, + show_missionaries, + }, + freeze: true, + freeze_message: __('Generating Church Directory\u2026'), + callback: function (r) { + if (!r.message) return; + const win = window.open('', '_blank'); + win.document.open(); + win.document.write(r.message); + win.document.close(); + // Allow images/fonts to load before triggering print + win.addEventListener('load', function () { + win.focus(); + win.print(); + }); + }, + }); + }); + }, +}; diff --git a/church/church_people/report/church_directory_report/church_directory_report.json b/church/church_people/report/church_directory_report/church_directory_report.json new file mode 100644 index 0000000..f08fa68 --- /dev/null +++ b/church/church_people/report/church_directory_report/church_directory_report.json @@ -0,0 +1,35 @@ +{ + "add_total_row": 0, + "add_translate_data": 0, + "columns": [], + "creation": "2026-03-08 00:00:00.000000", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letterhead": null, + "modified": "2026-03-08 00:00:00.000000", + "modified_by": "Administrator", + "module": "Church People", + "name": "Church Directory Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Family", + "report_name": "Church Directory Report", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Church Manager" + }, + { + "role": "Church User" + } + ], + "timeout": 0 +} diff --git a/church/church_people/report/church_directory_report/church_directory_report.py b/church/church_people/report/church_directory_report/church_directory_report.py new file mode 100644 index 0000000..6200962 --- /dev/null +++ b/church/church_people/report/church_directory_report/church_directory_report.py @@ -0,0 +1,395 @@ +import calendar +import os + +import frappe +from frappe.utils import today as frappe_today + + +def execute(filters=None): + return get_columns(), get_data(filters) + + +def get_columns(): + return [ + {"fieldname": "family", "fieldtype": "Link", "label": "Family", "options": "Family", "width": 200}, + { + "fieldname": "head_of_household", + "fieldtype": "Link", + "label": "Head of Household", + "options": "Person", + "width": 180, + }, + {"fieldname": "church", "fieldtype": "Link", "label": "Church", "options": "Church", "width": 160}, + {"fieldname": "city", "fieldtype": "Data", "label": "City", "width": 140}, + {"fieldname": "state", "fieldtype": "Data", "label": "State", "width": 100}, + {"fieldname": "member_count", "fieldtype": "Int", "label": "Members", "width": 80}, + ] + + +def get_data(filters): + if not filters or not filters.get("church"): + return [] + + church = filters.get("church") + members_only = frappe.utils.cint(filters.get("members_only", 0)) + include_sub_churches = frappe.utils.cint(filters.get("include_sub_churches", 0)) + + churches = get_church_scope(church, include_sub_churches) + church_in = build_in_clause(churches) + + families = frappe.db.sql( + f""" + SELECT + f.name AS family_id, + f.family_name, + f.church, + COALESCE(a.city, '') AS city, + COALESCE(a.state, '') AS state + FROM `tabFamily` f + LEFT JOIN `tabAddress` a ON a.name = f.home_address + WHERE f.church IN {church_in} + ORDER BY f.family_name ASC + """, + as_dict=True, + ) + + member_filter = "AND p.is_member = 1" if members_only else "" + + all_members = frappe.db.sql( + f""" + SELECT p.name, p.full_name, p.family, p.is_head_of_household + FROM `tabPerson` p + WHERE p.church IN {church_in} + AND p.family IS NOT NULL AND p.family != '' + {member_filter} + """, + as_dict=True, + ) + + members_by_family = {} + for m in all_members: + members_by_family.setdefault(m.family, []).append(m) + + result = [] + for family in families: + members = members_by_family.get(family.family_id, []) + head = next((m.name for m in members if m.is_head_of_household), None) + if not head and members: + head = members[0].name + result.append( + { + "family": family.family_id, + "head_of_household": head or "", + "church": family.church, + "city": family.city, + "state": family.state, + "member_count": len(members), + } + ) + + return result + + +@frappe.whitelist() +def get_directory_html( + church, + members_only=0, + include_sub_churches=0, + show_photos=0, + show_roles=0, + show_membership=1, + show_hoh=1, + show_birthdays=0, + show_anniversaries=0, + show_missionaries=0, +): + """Generate the full HTML for the church directory, ready to print.""" + members_only = frappe.utils.cint(members_only) + include_sub_churches = frappe.utils.cint(include_sub_churches) + show_photos = frappe.utils.cint(show_photos) + show_roles = frappe.utils.cint(show_roles) + show_membership = frappe.utils.cint(show_membership) + show_hoh = frappe.utils.cint(show_hoh) + show_birthdays = frappe.utils.cint(show_birthdays) + show_anniversaries = frappe.utils.cint(show_anniversaries) + show_missionaries = frappe.utils.cint(show_missionaries) + + church_doc = frappe.get_doc("Church", church) + church_address = None + if church_doc.address: + church_address = frappe.get_doc("Address", church_doc.address) + + churches = get_church_scope(church, include_sub_churches) + church_in = build_in_clause(churches) + + families = frappe.db.sql( + f""" + SELECT + f.name AS family_id, + f.family_name, + f.church AS church_name, + f.photo AS family_photo, + COALESCE(a.address_line1, '') AS address_line1, + COALESCE(a.address_line2, '') AS address_line2, + COALESCE(a.city, '') AS city, + COALESCE(a.state, '') AS state, + COALESCE(a.pincode, '') AS pincode + FROM `tabFamily` f + LEFT JOIN `tabAddress` a ON a.name = f.home_address + WHERE f.church IN {church_in} + ORDER BY f.family_name ASC + """, + as_dict=True, + ) + + member_filter = "AND p.is_member = 1" if members_only else "" + + all_members = frappe.db.sql( + f""" + SELECT + p.name AS person_name, + p.full_name, + p.primary_phone, + p.email, + p.membership_status, + p.is_head_of_household, + p.photo, + p.family + FROM `tabPerson` p + WHERE p.church IN {church_in} + AND p.family IS NOT NULL AND p.family != '' + {member_filter} + ORDER BY p.family, p.is_head_of_household DESC, p.last_name, p.first_name + """, + as_dict=True, + ) + + # Fetch active positions if requested + roles_by_person = {} + if show_roles: + today = frappe_today() + active_positions = frappe.db.sql( + f""" + SELECT pos.parent AS person_name, pos.position + FROM `tabPosition` pos + INNER JOIN `tabPerson` p ON p.name = pos.parent + WHERE pos.parenttype = 'Person' + AND pos.position IS NOT NULL + AND pos.start_date <= %(today)s + AND (pos.end_date IS NULL OR pos.end_date >= %(today)s) + AND p.church IN {church_in} + ORDER BY pos.parent, pos.start_date + """, + {"today": today}, + as_dict=True, + ) + for row in active_positions: + roles_by_person.setdefault(row.person_name, []).append(row.position) + + for m in all_members: + m["positions"] = roles_by_person.get(m.person_name, []) + + members_by_family = {} + for m in all_members: + members_by_family.setdefault(m.family, []).append(m) + + individuals_raw = frappe.db.sql( + f""" + SELECT + p.name AS person_name, + p.full_name, + p.last_name, + p.primary_phone, + p.email, + p.membership_status, + p.photo, + p.church AS church_name + FROM `tabPerson` p + WHERE p.church IN {church_in} + AND (p.family IS NULL OR p.family = '') + {member_filter} + ORDER BY p.last_name, p.first_name + """, + as_dict=True, + ) + + for p in individuals_raw: + p["positions"] = roles_by_person.get(p.person_name, []) + p["is_head_of_household"] = 0 + + # Build merged sorted entry list + all_entries = [] + + for family in families: + members = members_by_family.get(family.family_id, []) + if members: + all_entries.append( + { + "sort_name": family.family_name, + "display_name": family.family_name + " Family", + "is_individual": False, + "church_name": family.church_name, + "family_photo": family.family_photo, + "address_line1": family.address_line1, + "address_line2": family.address_line2, + "city": family.city, + "state": family.state, + "pincode": family.pincode, + "members": members, + } + ) + + for person in individuals_raw: + sort_key = (person.get("last_name") or person.get("full_name") or "").strip() + all_entries.append( + { + "sort_name": sort_key, + "display_name": person.full_name, + "is_individual": True, + "church_name": person.church_name, + "family_photo": None, + "address_line1": "", + "address_line2": "", + "city": "", + "state": "", + "pincode": "", + "members": [person], + } + ) + + all_entries.sort(key=lambda e: (e["sort_name"] or "").upper()) + + # ── Birthdays ──────────────────────────────────────────────── + birthdays = [] + if show_birthdays: + raw_birthdays = frappe.db.sql( + f""" + SELECT + p.full_name, + p.birthday, + MONTH(p.birthday) AS birth_month, + DAY(p.birthday) AS birth_day + FROM `tabPerson` p + WHERE p.church IN {church_in} + AND p.birthday IS NOT NULL + {member_filter} + ORDER BY MONTH(p.birthday), DAY(p.birthday), p.last_name, p.first_name + """, + as_dict=True, + ) + for row in raw_birthdays: + row["month_name"] = calendar.month_name[int(row.birth_month)] + row["month_day"] = f"{calendar.month_name[int(row.birth_month)]} {int(row.birth_day)}" + birthdays.append(row) + + # ── Anniversaries ──────────────────────────────────────────── + anniversaries = [] + if show_anniversaries: + raw_anniversaries = frappe.db.sql( + f""" + SELECT + p.name AS person_name, + p.spouse AS spouse_name, + p.first_name AS person_first, + p.full_name AS person_full, + s.first_name AS spouse_first, + COALESCE(f.family_name, '') AS family_name, + p.anniversary, + MONTH(p.anniversary) AS ann_month, + DAY(p.anniversary) AS ann_day + FROM `tabPerson` p + LEFT JOIN `tabPerson` s ON s.name = p.spouse + LEFT JOIN `tabFamily` f ON f.name = p.family + WHERE p.church IN {church_in} + AND p.anniversary IS NOT NULL + AND p.is_married = 1 + {member_filter} + ORDER BY MONTH(p.anniversary), DAY(p.anniversary), p.last_name, p.first_name + """, + as_dict=True, + ) + seen_persons = set() + for row in raw_anniversaries: + if row.person_name in seen_persons: + continue + seen_persons.add(row.person_name) + if row.spouse_name: + seen_persons.add(row.spouse_name) + row["month_name"] = calendar.month_name[int(row.ann_month)] + row["month_day"] = f"{calendar.month_name[int(row.ann_month)]} {int(row.ann_day)}" + if row.spouse_first and row.family_name: + row["display_name"] = f"{row.person_first} & {row.spouse_first} {row.family_name}" + elif row.spouse_first: + row["display_name"] = f"{row.person_full} & {row.spouse_first}" + else: + row["display_name"] = row.person_full + anniversaries.append(row) + + # ── Missionaries ───────────────────────────────────────────── + missionaries = [] + if show_missionaries: + missionaries = frappe.db.sql( + f""" + SELECT + m.title, + m.agency, + m.country, + m.email, + m.website, + m.photo, + m.sensitive, + m.mission_statement + FROM `tabMissionary` m + WHERE m.church IN {church_in} + ORDER BY m.title + """, + as_dict=True, + ) + + template_path = os.path.join(os.path.dirname(__file__), "church_directory.html") + with open(template_path) as f: + template = f.read() + + context = { + "church": church_doc, + "church_address": church_address, + "all_entries": all_entries, + "show_church_label": bool(include_sub_churches), + "show_photos": show_photos, + "show_roles": show_roles, + "show_membership": show_membership, + "show_hoh": show_hoh, + "birthdays": birthdays, + "anniversaries": anniversaries, + "missionaries": missionaries, + "show_birthdays": show_birthdays, + "show_anniversaries": show_anniversaries, + "show_missionaries": show_missionaries, + "generated_date": frappe.utils.formatdate(frappe.utils.nowdate(), "MMMM yyyy"), + } + + return frappe.render_template(template, context) + + +def get_church_scope(church, include_sub_churches): + """Return the church itself, or the full subtree if include_sub_churches is set.""" + if not include_sub_churches: + return [church] + + return frappe.db.sql_list( + """ + SELECT child.name + FROM `tabChurch` child + INNER JOIN `tabChurch` parent + ON child.lft >= parent.lft AND child.rgt <= parent.rgt + WHERE parent.name = %s + ORDER BY child.lft + """, + church, + ) + + +def build_in_clause(values): + """Return a safely escaped SQL IN clause string, e.g. ('A', 'B').""" + escaped = [frappe.db.escape(v) for v in values] + return "(" + ", ".join(escaped) + ")"