Changelog — PropertyPro
All notable changes to this app are recorded here. Newest entries on top.
[2026-04-30]
- Removed the duplicate "Charge Type — One-time / Monthly" picker from step 3 of the New Charge wizard. It was a leftover from the dry-cleaning ancestor (it was actually a Pickup/Delivery toggle rebranded with labels) and added nothing — the Cycle dropdown in the Billing Period card already captures whether a charge is one-time, weekly, monthly, quarterly, or yearly, and that's the value surfaced on the charge list, charge detail, public invoice, and tenant portal. Step 3 is now noticeably shorter and cleaner.
- Fixed "Tenant not found." error in the Messages & Notes section on Femi Adeyemi's charge — the demo seed was placing Femi in Palm Court Villas while his charge sat in Sunrise Apartments, so messaging him from his own charge returned a 404. Femi now correctly belongs to Sunrise Apartments (matching his Park Avenue address). New installs pick this up automatically; existing installs need a reseed.
Internal
- Dropped
delivery_type, delivery_fee, delivery_notes from orders (schema, demo + fresh seeds, all order-related queries in api/orders.php, api/portal.php, api/customers.php, api/dashboard.php, api/payments.php, api/reports.php, helpers.php, and the views/order-detail.php / views/public/invoice.php / views/public/portal.php consumers). Pre-launch — no back-compat preserved.
- Removed
pickup_or_delivery, pickup, delivery, delivery_fee label overrides from app.json and seeds/*.sql since nothing reads them anymore.
[2026-04-29]
- Fixed "Invalid status." error when changing a charge's status — the status filter and the Change Status form now accept all configured statuses (Sent, Due, Overdue, Paid, etc.), not just Cancelled.
- Fixed the tenant detail screen — opening a tenant was failing with a server error because the customer query referenced columns that were removed during the identity migration. The screen now loads tenant info, recent rent charges, projects, tickets, and notifications again.
Internal
- Restored
customers.portal_pin (with a guarded ALTER) since the public tenant portal still authenticates against it; the migration dropped the column without rewiring portal auth.
- Replaced two stale
c.user_id references with c.pancho_user_id in api/customers.php (case get, case delete).
[2026-04-21]
Internal
- Removed
ensure_platform_admin_in_app_db() from helpers.php — it was querying an app-local users table that was retired during the identity migration, so it threw (silently caught) on every page load. The modern tenant_members table + _per_user_app_bootstrap_owner() hook replaces its purpose entirely.
[2026-04-20]
- Fixed broken share-invoice URL — the share-receipt button was silently losing the tenant id (Pancho UUID was cast to an integer, producing
/p/0/…). Share links now resolve correctly.
- Demo install now loads sample data cleanly — the seed file was still referencing the old
users table that was retired during the identity migration, so Try Demo was erroring out on install.
Internal
- Activity Log API now joins
audit_log.user_id to tenant_members.pancho_user_id (the old users table was dropped during the identity migration). No user-visible change; the page just stops 500ing when opened.
settings-sections.php portal URL now url-encodes the UUID segment.
seeds/demo.sql — staff/owner/tenant rows moved from users to tenant_members; customers.user_id/portal_pin columns replaced with customers.pancho_user_id.
[2026-04-17]
- Removed the floating action button (+) from the ticket detail screen — it was overlapping the message compose area
- Fixed the tenant portal Pay button disappearing when Stripe is the active provider — the check now only requires keys to be saved (the separate Enable toggle is no longer a gate, so admins can't forget to flip it). Same cleanup for PayStack
- Stripe card modal now shows a "Loading secure payment form…" indicator while Stripe Elements initialise (they can take several seconds on slower networks), and the Pay button stays disabled until the form is ready. Real Stripe load errors now surface instead of silently spinning
- Tenant portal now accepts phone numbers in any common format — US with or without "+1" / "1", Nigerian with or without "+234" / "234" / leading "0". Tenants no longer need the exact format to log in
- Tenant detail page now shows the tenant's maintenance tickets (subject, status, date) after the Projects section — tapping one opens the ticket
- Fixed the PayStack / Stripe settings section in dark mode — the "Get your API keys" info box, the processing-fee panel, and the "Default: 1.5% + ₦100..." note now use theme-aware colors instead of hard-coded light-mode pastels
- Tenant detail page now shows a "Recent Activity" section with the last 10 audit entries for that tenant (charge created, status change, payment, ticket update, etc.)
- Fixed ticket message attachments (photos/PDFs) not showing up in the chat on both admin and tenant portal — image URL was being resolved against the app route and 404'ing; now loads from the web root
- Fixed tenant search not filtering when typing in the New Ticket sheet
- Fixed submitting a ticket (admin or tenant) crashing with "call to undefined function generate_ticket_number"
- Fixed ticket detail crashing with "no such column: tm.attachment_path" — ticket messages can now include file attachments
- Fixed tenant portal: entering a phone number not in the system no longer leaves the button stuck on "Checking..."
- Fixed tenant portal: demo now includes ready-to-use tenant accounts — any demo tenant can log in with PIN 1234
- Updated subscription price to $47/month (USD) and ₦25,000/month (NGN); setup fee is now $497 (USD) and ₦450,000 (NGN)
- Fixed Print Receipt showing a 500 error on the live server — removed stale bootstrap includes that the router already handles, which were crashing when the referenced config files didn't exist on the server
- Fixed Print Receipt still 404-ing — the public-route dispatcher was building the wrong file path ("views/public/public/invoice.php") and failing to open the actual receipt page
- Fixed Delete Charge failing with an integrity-constraint error — the delete now also cleans up attached files and detaches customer notes, so no more foreign-key complaints
- Rewrote the marketplace description, benefits, and features to be clearer, more outcome-focused, and work for landlords or property managers anywhere (not tied to a specific region)
- Rewrote the PropertyPro client proposal (marketing/proposal.md) to cover more use cases — landlords, property managers, caretakers, hostel operators, real estate companies — with a clearer problem-and-solution narrative and real benefits instead of only features
- Restored the "Promote" tab in the app marketplace on mobile and desktop — every signed-in account now gets an affiliate link automatically, so the tab always shows up when the affiliates extension is on
- The client proposal now shows up in the Promote tab alongside Email, WhatsApp, Facebook, etc. with a Copy button — affiliates can grab the ready-to-send pitch in one tap (works for every app's
marketing/proposal.md, not just PropertyPro)
- Fixed Owners page showing "Access Denied" for the account owner — the list, add, edit, activate, PIN reset and delete actions all work again
- Fixed Projects and Tickets admin actions (create/edit/delete) that were also 403-ing for the account owner
- Fixed narrow message / note / status composer boxes across the app — they now fill the sheet width on mobile
- Fixed the discount "Why?" field overflowing the edit charge sheet on small screens
- Fixed uploaded photos/documents not showing a preview in the charge detail page
- Fixed "Print Receipt" on a charge opening a 404 page — it now opens the public receipt cleanly
- Fixed home page crash ("no such table: orders") caused by a bad migration step — home, charges and dashboard now load on fresh and existing installs
- Fixed tenant detail page crash ("no such column: c.user_id") — tenant profile, recent charges and outstanding balance now load correctly
- Fixed Activity Log link on the home page — tapping it now opens a filterable, paginated audit trail instead of a 404
- Fixed demo re-install crashing with "FOREIGN KEY / UNIQUE constraint failed" when a previous install was still in grace period
- Added Activity Log screen: searches by details, filters by action type, newest-first, admin-only
Internal
- Swapped
require_admin() for require_permission($area, $action) across api/owners.php, api/projects.php, api/tickets.php so the platform-user short-circuit (role='admin') applies to in-app admin actions
- Added
owners entry to the app.json permissions matrix
- Changed
.form-input in core/assets/css/core.css to width: 100%; max-width: 100%; box-sizing: border-box — covers every sheet composer that had narrow HTML-default widths
- Fixed attachment preview URL construction in
views/order-detail.php (dropped stale basePath prefix — uploads live at web root)
- Rewrote
orders.share-token URL to the public-route pattern /p/{userId}/{appId}/invoice?token=…
- Fixed
require_once depth in views/public/invoice.php and views/public/portal.php — corrected from 3 to 4 levels up so the bootstrap file resolves on the live server
- Added
user_id (REFERENCES users) and portal_pin columns to the customers table in schema.sql; the framework's lazy migrator adds them on existing installs
- Added
/audit route, views/audit.php, and api/audit.php (admin-only)
- Rewrote
seeds/demo.sql and seeds/fresh.sql to use INSERT OR IGNORE for fixed-ID rows so re-seeding an existing DB is safe (grace-period case)
[2026-04-16]
- Added Stripe as a second payment provider alongside PayStack — admin picks PayStack (default) or Stripe from Settings
- Tenants see no provider branding — same "Pay" button regardless of provider
- Added Stripe keys and fee configuration in Settings (mirrors existing PayStack section)
- Added "Special Projects" toggle in Settings to enable/disable the Projects feature at runtime
- Added "Shareable Links" section in Settings with copy-to-clipboard buttons for tenant portal and invoice URLs
- Project contributions now support both PayStack and Stripe payment flows
- Simplified payment provider setting — removed confusing Auto mode; just PayStack or Stripe
- Fixed Charges page crash (missing billing columns in database)
- Fixed Owners page not loading (missing active/must_change_pin columns)
- Fixed Projects nav item still showing when the feature is disabled
- Fixed search bar styling on Charges, Tenants, and Tickets pages
- Fixed button styling on Tenants and Tickets pages to match platform standard
- Fixed all bottom sheets (New Project, New Ticket, Add Owner, Record Payment, etc.) — now slide up correctly with overlay and drag-to-close
- Fixed recording project payments (and other admin actions) failing with foreign key error
Internal
- Platform admin is now auto-synced into the app's users table on first access — prevents FK violations on existing databases without requiring a DB wipe
- Created
lib/payments.php — centralised provider resolution and payment verification for both providers
- Added
stripe_payment_intent_id column to payments and project_payments tables
- Added
billing_year and billing_cycle columns to orders table
- Added
active and must_change_pin columns to users table
- Nav rendering now respects the
feature key on nav items (generic fix for all apps)
- Fixed dangling
require_once for non-existent config/paystack.php
- Replaced hardcoded
'NGN' currency with dynamic currency from settings
is_feature_enabled() now respects per-app features_* overrides in the settings table
[Baseline] — 2026-04-16
- Existing feature set captured as the baseline. Subsequent changes will be prepended above this entry.