= Напредни извештаи од базата (SQL, складирани процедури и релациона алгебра) == 1. Месечни приходи и перформанси на плаќања По месец прикажува колку плаќања има, колку се успешни/неуспешни, колку е приходот од термини наспроти приходи од купување пакет, и месечна промена на приход. === SQL {{{ WITH bounds AS ( SELECT date_trunc('month', MIN(p."timestamp"))::date AS min_month, date_trunc('month', MAX(p."timestamp"))::date AS max_month FROM payment p ), months AS ( SELECT generate_series(min_month, max_month, interval '1 month')::date AS month_start FROM bounds ), pay AS ( SELECT date_trunc('month', p."timestamp")::date AS month_start, p.status, p.amount, p.appointment_id, p.package_purchase_id FROM payment p ), agg AS ( SELECT m.month_start, COUNT(pay.*) AS payment_cnt, COUNT(*) FILTER (WHERE pay.status = 'PAID') AS paid_cnt, COUNT(*) FILTER (WHERE pay.status = 'PENDING') AS pending_cnt, COUNT(*) FILTER (WHERE pay.status = 'FAILED') AS failed_cnt, COUNT(*) FILTER (WHERE pay.status = 'REFUNDED') AS refunded_cnt, COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total, COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'REFUNDED'), 0)::numeric(12,2) AS refunded_amount_total, COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID' AND pay.appointment_id IS NOT NULL), 0)::numeric(12,2) AS paid_appointment_amount, COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID' AND pay.appointment_id IS NULL AND pay.package_purchase_id IS NOT NULL), 0)::numeric(12,2) AS paid_package_amount FROM months m LEFT JOIN pay ON pay.month_start = m.month_start GROUP BY m.month_start ) SELECT a.month_start, a.payment_cnt, a.paid_cnt, a.pending_cnt, a.failed_cnt, a.refunded_cnt, a.paid_amount_total, a.refunded_amount_total, a.paid_appointment_amount, a.paid_package_amount, ROUND(100.0 * a.paid_cnt / NULLIF(a.payment_cnt, 0), 2) AS paid_rate_pct, ROUND(100.0 * a.failed_cnt / NULLIF(a.payment_cnt, 0), 2) AS failed_rate_pct, ROUND( a.paid_amount_total - COALESCE(LAG(a.paid_amount_total) OVER (ORDER BY a.month_start), 0), 2 ) AS mom_paid_amount_change FROM agg a ORDER BY a.month_start; }}} === Релациона алгебра {{{ B ← γ_{min_m:=min(month(ts)), max_m:=max(month(ts))}(Payment) M ← generate_series(B.min_m, B.max_m, 1 month) P ← π_{month:=month(ts), status, amount, appointment_id, package_purchase_id}(Payment) R ← γ_{month; cnt:=count(*), paid:=count_{status='PAID'}(*), pending:=count_{status='PENDING'}(*), failed:=count_{status='FAILED'}(*), refunded:=count_{status='REFUNDED'}(*), paid_sum:=sum_{status='PAID'}(amount), paid_appt:=sum_{status='PAID'∧appointment_id≠NULL}(amount), paid_pkg:=sum_{status='PAID'∧appointment_id=NULL∧package_purchase_id≠NULL}(amount)}(M ⟕_{M.month=P.month} P) }}} Овој извештај веднаш покажува дали растот доаѓа од класични термини или од продажба на пакети и дали има пад на success rate по месеци. == 2. Најпопуларни услуги според закажувања, приход и оценки За секоја услуга покажува број на закажувања по статус, алоциран платен приход, просечна оценка и број рецензии, со ранг на популарност. === SQL {{{ WITH appointment_base AS ( SELECT a.appointment_id, UPPER(s.name) AS status_name FROM appointment a JOIN status s ON s.status_id = a.status_id ), service_lines AS ( SELECT aps.appointment_id, aps.service_id, sv.name AS service_name, c.name AS category_name, sv.price AS service_list_price FROM appointmentservice aps JOIN service sv ON sv.service_id = aps.service_id LEFT JOIN category c ON c.category_id = sv.category_id ), appointment_list_totals AS ( SELECT sl.appointment_id, SUM(sl.service_list_price) AS list_total_price FROM service_lines sl GROUP BY sl.appointment_id ), paid_amount_per_appt AS ( SELECT p.appointment_id, MAX(p.amount) AS paid_amount FROM payment p WHERE p.status = 'PAID' AND p.appointment_id IS NOT NULL GROUP BY p.appointment_id ), revenue_alloc AS ( SELECT sl.appointment_id, sl.service_id, (pa.paid_amount * sl.service_list_price / NULLIF(alt.list_total_price, 0))::numeric(12,2) AS allocated_amount FROM service_lines sl JOIN appointment_list_totals alt ON alt.appointment_id = sl.appointment_id JOIN paid_amount_per_appt pa ON pa.appointment_id = sl.appointment_id ), reviews_by_service AS ( SELECT aps.service_id, ROUND(AVG(r.rating)::numeric, 2) AS avg_rating, COUNT(r.review_id) AS review_count FROM appointmentservice aps JOIN payment p ON p.appointment_id = aps.appointment_id AND p.status = 'PAID' JOIN review r ON r.payment_id = p.payment_id GROUP BY aps.service_id ), service_stats AS ( SELECT sl.service_id, sl.service_name, sl.category_name, COUNT(*) AS bookings_total, COUNT(*) FILTER (WHERE ab.status_name = 'COMPLETED') AS completed_bookings, COUNT(*) FILTER (WHERE ab.status_name = 'SCHEDULED') AS scheduled_bookings, COUNT(*) FILTER (WHERE ab.status_name = 'CANCELLED') AS cancelled_bookings, COUNT(*) FILTER (WHERE ab.status_name = 'NO_SHOW') AS no_show_bookings, COALESCE(SUM(ra.allocated_amount), 0)::numeric(12,2) AS allocated_paid_revenue FROM service_lines sl JOIN appointment_base ab ON ab.appointment_id = sl.appointment_id LEFT JOIN revenue_alloc ra ON ra.appointment_id = sl.appointment_id AND ra.service_id = sl.service_id GROUP BY sl.service_id, sl.service_name, sl.category_name ) SELECT ss.service_id, ss.service_name, ss.category_name, ss.bookings_total, ss.completed_bookings, ss.scheduled_bookings, ss.cancelled_bookings, ss.no_show_bookings, ss.allocated_paid_revenue, COALESCE(rv.avg_rating, 0)::numeric(4,2) AS avg_rating, COALESCE(rv.review_count, 0) AS review_count, DENSE_RANK() OVER ( ORDER BY ss.completed_bookings DESC, ss.allocated_paid_revenue DESC ) AS popularity_rank FROM service_stats ss LEFT JOIN reviews_by_service rv ON rv.service_id = ss.service_id ORDER BY popularity_rank, ss.service_id; }}} === Релациона алгебра {{{ SL ← AppointmentService ⨝ Service ⨝ Category AB ← Appointment ⨝ Status T ← γ_{appointment_id; list_total:=Σ(service_price)}(SL) PP ← σ_{status='PAID' ∧ appointment_id≠NULL}(Payment) RA ← γ_{appointment_id, service_id; alloc:=paid_amount*service_price/list_total}(SL ⨝ T ⨝ PP) RV ← γ_{service_id; avg_rating:=AVG(rating), review_count:=COUNT(*)}(AppointmentService ⨝ PP ⨝ Review) S ← γ_{service_id, service_name, category_name; bookings:=COUNT(*), completed:=COUNT_{status='COMPLETED'}(*), revenue:=Σ(alloc)}(SL ⨝ AB ⟕ RA) Result ← S ⟕ RV }}} Ова е најкорисен top services извештај за одлуки, кои услуги се чести, кои носат реален приход и како се оценети од клиенти. == 3. Лојалност и активност на клиенти По корисник дава извештај за активност со термини, платени/неуспешни плаќања, купени пакети, искористеност и моментални loyalty поени. === SQL {{{ WITH appt_agg AS ( SELECT a.user_id, COUNT(*) AS appointments_total, COUNT(*) FILTER (WHERE UPPER(s.name) = 'SCHEDULED') AS scheduled_cnt, COUNT(*) FILTER (WHERE UPPER(s.name) = 'COMPLETED') AS completed_cnt, COUNT(*) FILTER (WHERE UPPER(s.name) = 'CANCELLED') AS cancelled_cnt, COUNT(*) FILTER (WHERE UPPER(s.name) = 'NO_SHOW') AS no_show_cnt, MAX(a.appointment_time) AS last_appointment_at FROM appointment a JOIN status s ON s.status_id = a.status_id GROUP BY a.user_id ), pay_user_map AS ( SELECT p.payment_id, COALESCE(a.user_id, upp.user_id) AS user_id, p.status, p.amount, p.points_used, p.appointment_id, p.package_purchase_id, p."timestamp" FROM payment p LEFT JOIN appointment a ON a.appointment_id = p.appointment_id LEFT JOIN userpackagepurchase upp ON upp.purchase_id = p.package_purchase_id ), pay_agg AS ( SELECT pum.user_id, COUNT(*) AS payments_total, COUNT(*) FILTER (WHERE pum.status = 'PAID') AS paid_payments_cnt, COUNT(*) FILTER (WHERE pum.status = 'FAILED') AS failed_payments_cnt, COUNT(*) FILTER (WHERE pum.status = 'REFUNDED') AS refunded_payments_cnt, COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total, COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID' AND pum.appointment_id IS NOT NULL), 0)::numeric(12,2) AS paid_appointment_amount, COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID' AND pum.appointment_id IS NULL AND pum.package_purchase_id IS NOT NULL), 0)::numeric(12,2) AS paid_package_amount, COALESCE(SUM(pum.points_used) FILTER (WHERE pum.status = 'PAID'), 0) AS points_used_total, MAX(pum."timestamp") AS last_payment_at FROM pay_user_map pum WHERE pum.user_id IS NOT NULL GROUP BY pum.user_id ), pkg_agg AS ( SELECT upp.user_id, COUNT(*) AS package_purchases_total, COUNT(*) FILTER (WHERE upp.status = 'ACTIVE') AS active_packages_cnt, COALESCE(SUM(upp.total_uses), 0) AS total_package_uses_bought, COALESCE(SUM(upp.remaining_uses), 0) AS total_package_uses_remaining, COALESCE(SUM(upp.total_uses - upp.remaining_uses), 0) AS total_package_uses_used, MAX(upp.purchased_at) AS last_package_purchase_at FROM userpackagepurchase upp GROUP BY upp.user_id ) SELECT u.user_id, u.full_name, u.email, COALESCE(lc.points, 0) AS loyalty_points_balance, COALESCE(a.appointments_total, 0) AS appointments_total, COALESCE(a.completed_cnt, 0) AS completed_cnt, COALESCE(a.scheduled_cnt, 0) AS scheduled_cnt, COALESCE(a.cancelled_cnt, 0) AS cancelled_cnt, COALESCE(a.no_show_cnt, 0) AS no_show_cnt, ROUND(100.0 * COALESCE(a.completed_cnt, 0) / NULLIF(COALESCE(a.appointments_total, 0), 0), 2) AS completion_rate_pct, COALESCE(p.payments_total, 0) AS payments_total, COALESCE(p.paid_payments_cnt, 0) AS paid_payments_cnt, COALESCE(p.failed_payments_cnt, 0) AS failed_payments_cnt, COALESCE(p.refunded_payments_cnt, 0) AS refunded_payments_cnt, COALESCE(p.paid_amount_total, 0)::numeric(12,2) AS paid_amount_total, COALESCE(p.paid_appointment_amount, 0)::numeric(12,2) AS paid_appointment_amount, COALESCE(p.paid_package_amount, 0)::numeric(12,2) AS paid_package_amount, COALESCE(p.points_used_total, 0) AS points_used_total, COALESCE(pk.package_purchases_total, 0) AS package_purchases_total, COALESCE(pk.active_packages_cnt, 0) AS active_packages_cnt, COALESCE(pk.total_package_uses_bought, 0) AS total_package_uses_bought, COALESCE(pk.total_package_uses_used, 0) AS total_package_uses_used, COALESCE(pk.total_package_uses_remaining, 0) AS total_package_uses_remaining, ROW_NUMBER() OVER ( ORDER BY COALESCE(p.paid_amount_total, 0) DESC, COALESCE(lc.points, 0) DESC, u.user_id ) AS value_rank FROM "User" u LEFT JOIN loyaltycard lc ON lc.user_id = u.user_id LEFT JOIN appt_agg a ON a.user_id = u.user_id LEFT JOIN pay_agg p ON p.user_id = u.user_id LEFT JOIN pkg_agg pk ON pk.user_id = u.user_id ORDER BY value_rank; }}} === Релациона алгебра {{{ A ← γ_{user_id; appt_total:=COUNT(*), completed:=COUNT_{status='COMPLETED'}(*), ...}(Appointment ⨝ Status) P0 ← Payment ⟕ Appointment ⟕ UserPackagePurchase P ← γ_{user_id; paid_sum:=Σ_{status='PAID'}(amount), paid_cnt:=COUNT_{status='PAID'}(*), ...}(P0) K ← γ_{user_id; pkg_cnt:=COUNT(*), active_pkg:=COUNT_{status='ACTIVE'}(*), uses_bought:=Σ(total_uses), uses_rem:=Σ(remaining_uses)}(UserPackagePurchase) Result ← User ⟕ LoyaltyCard ⟕ A ⟕ P ⟕ K }}} Извештајот е добар за CRM сегментација: брзо се гледа кои клиенти носат најмногу приход, колку се активни и каков им е loyalty балансот. == 4. Продажба и искористеност на пакети За секој пакет покажува колку е продаден, колку е исплатен, колку користења се потрошени/останати и колку е „популарен“ во реална употреба. === SQL {{{ WITH package_base AS ( SELECT p.package_id, p.name AS package_name, p.max_usage, p.total_price, COUNT(ps.service_id) AS services_in_package, COALESCE(SUM(ps.discounted_price), 0)::numeric(12,2) AS computed_services_price_sum FROM package p LEFT JOIN packageservice ps ON ps.package_id = p.package_id GROUP BY p.package_id, p.name, p.max_usage, p.total_price ), purchase_stats AS ( SELECT upp.package_id, COUNT(*) AS purchases_cnt, COUNT(*) FILTER (WHERE upp.status = 'ACTIVE') AS active_purchases_cnt, COUNT(*) FILTER (WHERE upp.status = 'EXPIRED') AS expired_purchases_cnt, COUNT(*) FILTER (WHERE upp.status = 'CANCELLED') AS cancelled_purchases_cnt, COALESCE(SUM(upp.total_uses), 0) AS total_uses_sold, COALESCE(SUM(upp.remaining_uses), 0) AS total_uses_remaining, COALESCE(SUM(upp.total_uses - upp.remaining_uses), 0) AS total_uses_used FROM userpackagepurchase upp GROUP BY upp.package_id ), package_payment_stats AS ( SELECT upp.package_id, COUNT(*) FILTER (WHERE p.status = 'PAID') AS paid_payments_cnt, COUNT(*) FILTER (WHERE p.status = 'PENDING') AS pending_payments_cnt, COUNT(*) FILTER (WHERE p.status = 'FAILED') AS failed_payments_cnt, COUNT(*) FILTER (WHERE p.status = 'REFUNDED') AS refunded_payments_cnt, COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total FROM payment p JOIN userpackagepurchase upp ON upp.purchase_id = p.package_purchase_id WHERE p.appointment_id IS NULL GROUP BY upp.package_id ), usage_ops AS ( SELECT upp.package_id, COUNT(apu.*) AS linked_appointment_usages, COUNT(*) FILTER (WHERE apu.finalized_at IS NOT NULL) AS finalized_usages FROM userpackagepurchase upp LEFT JOIN appointmentpackageusage apu ON apu.purchase_id = upp.purchase_id GROUP BY upp.package_id ) SELECT pb.package_id, pb.package_name, pb.max_usage, pb.total_price, pb.services_in_package, pb.computed_services_price_sum, COALESCE(ps.purchases_cnt, 0) AS purchases_cnt, COALESCE(ps.active_purchases_cnt, 0) AS active_purchases_cnt, COALESCE(ps.expired_purchases_cnt, 0) AS expired_purchases_cnt, COALESCE(ps.cancelled_purchases_cnt, 0) AS cancelled_purchases_cnt, COALESCE(ps.total_uses_sold, 0) AS total_uses_sold, COALESCE(ps.total_uses_used, 0) AS total_uses_used, COALESCE(ps.total_uses_remaining, 0) AS total_uses_remaining, ROUND(100.0 * COALESCE(ps.total_uses_used, 0) / NULLIF(COALESCE(ps.total_uses_sold, 0), 0), 2) AS usage_rate_pct, COALESCE(pps.paid_payments_cnt, 0) AS paid_payments_cnt, COALESCE(pps.pending_payments_cnt, 0) AS pending_payments_cnt, COALESCE(pps.failed_payments_cnt, 0) AS failed_payments_cnt, COALESCE(pps.refunded_payments_cnt, 0) AS refunded_payments_cnt, COALESCE(pps.paid_amount_total, 0)::numeric(12,2) AS paid_amount_total, ROUND(COALESCE(pps.paid_amount_total, 0) / NULLIF(COALESCE(ps.purchases_cnt, 0), 0), 2) AS avg_paid_per_purchase, COALESCE(uo.linked_appointment_usages, 0) AS linked_appointment_usages, COALESCE(uo.finalized_usages, 0) AS finalized_usages, DENSE_RANK() OVER ( ORDER BY COALESCE(ps.purchases_cnt, 0) DESC, COALESCE(pps.paid_amount_total, 0) DESC ) AS package_popularity_rank FROM package_base pb LEFT JOIN purchase_stats ps ON ps.package_id = pb.package_id LEFT JOIN package_payment_stats pps ON pps.package_id = pb.package_id LEFT JOIN usage_ops uo ON uo.package_id = pb.package_id ORDER BY package_popularity_rank, pb.package_id; }}} === Релациона алгебра {{{ PB ← γ_{package_id, name; svc_cnt:=COUNT(service_id), svc_sum:=Σ(discounted_price)}(Package ⟕ PackageService) PS ← γ_{package_id; purchases:=COUNT(*), active:=COUNT_{status='ACTIVE'}(*), uses_sold:=Σ(total_uses), uses_used:=Σ(total_uses-remaining_uses)}(UserPackagePurchase) PP ← γ_{package_id; paid_cnt:=COUNT_{status='PAID'}(*), paid_sum:=Σ_{status='PAID'}(amount)}(σ_{appointment_id=NULL}(Payment ⨝ UserPackagePurchase)) UO ← γ_{package_id; linked:=COUNT(*), finalized:=COUNT_{finalized_at≠NULL}(*)}(UserPackagePurchase ⟕ AppointmentPackageUsage) Result ← PB ⟕ PS ⟕ PP ⟕ UO }}} Се гледа кои пакети навистина се продаваат и трошат, дали остануваат неискористени, и дали цената на пакет носи очекуван приход.