Skip to content

Migration: Base44 → Billettsalg

This document combines the original feature gap analysis between the conductor's Base44 app and our Billettsalg app, along with the detailed post-v0.9.0 migration tracking.

Part 1: Feature Gap Analysis

Originally from gap analysis comparing all 13 pages of billettsalgsskjema.base44.app with our implementation.

Executive Summary

After analyzing all 13 pages from the conductor's existing Base44 app, our app has strong fundamentals (multi-organization, events, sales registration). All critical and medium-priority gaps have been closed. The only remaining gap is internal messaging, which was deprioritized in favor of external communication tools.

Feature Comparison Matrix

FeatureConductor's AppOur AppGap Level
Member Features
Register Sale✅ Full form with concert, ticket types, payment method✅ RegisterSaleView✅ Parity
Order Tickets (request from manager)✅ /OrderTickets route✅ OrderTicketsView✅ Parity
My Sales Overview✅ Summary cards + tabbed view✅ HistoryView✅ Parity
Ticket Manager Features
Manage Orders (Bestillinger)✅ EditorOrders - approve/reject/deliver orders✅ OrdersView (admin)✅ Parity
Order History (Bestillingshistorikk)✅ Full history with archive function✅ OrdersView (history tab)✅ Parity
Ticket Allocations (Tildelinger)✅ Per-member allocation per concert✅ AllocationsView✅ Parity
Internal Messages✅ Admin ↔ Ticket managers messaging❌ No messaging🟢 Low
Treasurer Features
Reports (Rapporter)✅ Multi-tab: Per member/organization/concert/type/Ticketmaster/All✅ ReportsView (6 tabs)✅ Parity
CSV Export✅ Export button✅ Excel export exists✅ Parity
Admin Features
Organization Management (Kor)✅ CRUD with member count, status badge✅ OrganizationsView✅ Parity
Concert Management (Konserter)✅ Rich cards with pricing, capacity, progress bars, fees✅ EventsView/EventFormView✅ Parity
User Management (Brukere)✅ Full user admin✅ UsersView✅ Parity
Ticketmaster Integration✅ Dedicated /AdminTicketmaster page✅ External sales per showtime✅ Parity
Ticket Types
Adult/Child✅ Parity
Student✅ Preset button✅ Parity
Senior✅ Preset button✅ Parity
Special Price (Avtalt spesialpris)✅ Negotiated prices✅ Preset button✅ Parity
Per-ticket fee (Billettavgift)✅ Per type, per concert✅ Fee field on TicketType✅ Parity
Order Workflow
Order Statuses✅ Pending → Godkjent → Levert / Avbrutt✅ Pending/Approved/Delivered✅ Parity
Archive Orders by Concert✅ Bulk archive feature✅ Event-scoped bulk archive (#25)✅ Parity
Multi-Organization
Multiple organizations✅ Filter by organization across pages✅ OrganizationSelector✅ Parity
Organization badges on concerts✅ Shows which organization per concert✅ Parity

Implemented Features

Order/Request Tickets Workflow ✅

The conductor's app has a ticket request system where members don't just register sales, but can order tickets from a ticket manager:

  1. Member submits order via /OrderTickets (concert, quantities per type)
  2. Order enters "Pending" status
  3. Ticket manager sees it in /EditorOrders, can Approve/Reject
  4. Once approved → "Godkjent" (Approved)
  5. Manager delivers tickets → "Levert" (Delivered)
  6. Can be cancelled → "Avbrutt" (Cancelled)

Implementation completed:

  • New entity: TicketOrder with status workflow (api/src/shared/types.ts)
  • New member view: OrderTicketsView.vue - order tickets with showtime selection
  • New admin view: admin/OrdersView.vue - manage orders with approve/deliver/cancel
  • API endpoints: /api/orders, /api/orders/:id/approve, /api/orders/:id/deliver, /api/orders/:id/cancel
  • Database container: ticketOrders (partitioned by organizationId)
  • Routes: /order-tickets for members, /admin/orders for admins
  • i18n: English and Norwegian translations

Per-Ticket Fees (Billettavgift) ✅

Each concert can have different fees per ticket type:

  • Example: "Voksen: 30 kr" fee, "Barn: 25 kr" fee
  • Fees are displayed separately from ticket price
  • Fees are tracked in reports ("Total billettavgift: 500 kr")

Implementation completed:

  • Fee field added to TicketType (api/src/shared/types.ts)
  • Tracked in registrations, orders, external sales, and reports
  • Displayed in concert cards (EventsView) and event form (EventFormView)
  • Fee totals included in Excel/CSV exports

Order History with Archive ✅

  • ✅ History tab in admin/OrdersView.vue showing delivered/cancelled orders
  • ✅ Table with: Date, Member, Event, Tickets breakdown, Total, Status
  • ✅ Summary cards with total orders, total tickets, and total revenue
  • ✅ Archive flag support via the archived field on TicketOrder and PATCH /api/orders/:id/archive, available since v0.9.0
  • ✅ Event-scoped bulk archive parity delivered by #25 through a preview endpoint and bounded bulk archive endpoint

The original archive endpoint supported one order at a time. The archive flag alone did not provide parity with the Conductor app's archive-by-event workflow. Issue #25 adds that separate capability while retaining the individual endpoint. Issue #315 tightens eligibility: cancelled orders remain eligible, while settled orders require a valid sold-only invoice basis and an issued invoice. Bulk archive never overrides this rule.

Invoice and settlement lifecycle ✅

Issue #315 separates three concepts that older migration notes treated as one:

  • Settled means a manager approved reconciliation and froze the sold-only invoice basis.
  • Invoiced means an invoice was issued or sent to the member.
  • Payment collection remains outside the invoice flag.

The member-and-event invoice action covers active registrations and eligible settled printed orders. Printed revenue comes from validated sold quantities at order-time prices, never the original order total. Existing settled orders do not receive synthetic invoice timestamps. Existing archived orders remain grandfathered until they are unarchived.

Enhanced Ticket Types ✅

  • Preset buttons for Student, Senior, and Special added to EventFormView.vue
  • Presets include default prices and fees (e.g., Student: 150 kr / 10 kr fee)
  • Duplicate-name validation prevents adding the same preset twice
  • System still supports fully flexible custom types

Ticket Allocations ✅

  • Allocation entity in api/src/shared/types.ts
  • AllocationsView.vue for managing allocations
  • Full API: GET/POST /api/allocations, DELETE /api/allocations/:id, GET /api/my-allocations
  • Members endpoint for allocation status: GET /api/allocations/members
  • Allocation summary card for ticket managers on dashboard

Enhanced Reports ✅

ReportsView provides all views from the conductor's app:

  • ✅ Per member (By Member tab)
  • ✅ Per organization (By Organization tab)
  • ✅ Per concert (By Event tab)
  • ✅ Per ticket type (By Type tab)
  • ✅ Ticketmaster (external sales tab)
  • ✅ All sales (complete list tab)

Implementation: ReportsView.vue with 6-tab system, date range filtering, member filter dropdown, and CSV/Excel export.

Concert Card Enhancements ✅

  • ✅ Progress bar: 27 / 200 sold with visual bar (color-coded: green ≥90%, blue ≥70%, yellow ≥50%, gray <50%)
  • ✅ "Igjen" (Remaining) count
  • ✅ Ticket fees section (purple-colored fee display per type)

Deferred Features

Internal Messaging 🟢 Low Priority

Simple messaging between admins and ticket managers. Recommendation: Defer this. Email notifications may suffice.


Data Model Changes

New Entities

typescript
// Ticket Order/Request System
interface TicketOrder {
  id: string;
  organizationId: string;
  eventId: string;
  showtimeId?: string;
  showtimeDateTime?: string;
  showtimeLabel?: string;
  requestedBy: string;
  requestedByName: string;
  requestedByEmail: string;
  tickets: {
    ticketTypeId: string;
    ticketTypeName: string;
    quantity: number;
    unitPrice: number;
    fee: number;
  }[];
  totalQuantity: number;
  totalAmount: number;
  totalFees: number;
  status: "pending" | "approved" | "delivered" | "awaitingReconciliation" | "settled" | "cancelled";
  soldQuantity?: number;
  soldTypeQuantities?: SoldTypeQuantity[];
  returnedQuantity?: number;
  invoiceBasis?: InvoiceBasis;
  invoicedAt?: string;
  invoicedBy?: string;
  invoicedByName?: string;
  archiveOverride?: OrderArchiveOverrideEvidence;
  approvedBy?: string;
  approvedByName?: string;
  approvedAt?: string;
  deliveredBy?: string;
  deliveredByName?: string;
  deliveredAt?: string;
  cancelledBy?: string;
  cancelledByName?: string;
  cancelledAt?: string;
  cancelReason?: string;
  createdAt: string;
  updatedAt: string;
}

// Ticket Allocation
interface TicketAllocation {
  id: string;
  organizationId: string;
  eventId: string;
  userId: string;
  userName: string;
  userEmail: string;
  allocations: {
    ticketTypeId: string;
    ticketTypeName: string;
    quantity: number;
  }[];
  totalAllocated: number;
  createdBy: string;
  createdByName: string;
  createdAt: string;
  updatedAt: string;
}

Modified Entities

typescript
// Add fee to TicketType
interface TicketType {
  // ... existing fields
  fee?: number; // Per-ticket fee
}

Role Structure Comparison

RoleConductor's AppOur App
Medlem (Member)Registrer salg, Bestill billetter, Mine salg✅ Matching
Billettansvarlig (Ticket Manager)Bestillinger, Bestillingshistorikk, Tildelinger, Meldinger✅ Matching (except messaging)
Kasserer (Treasurer)Rapporter✅ Admin can access
AdminKor, Konserter, Brukere, Ticketmaster, etc.✅ Matching

Part 2: Detailed Migration Tracking (Post-v0.9.0)

Generated: January 31, 2026 Last Updated: March 2026 (post-v0.9.0 implementation review)

Executive Summary

CategoryBase44 FeaturesOur ImplementationGap Level
User ManagementMulti-role per user (billettansvarlig, kasserer)roles[] on OrganizationMembership✅ Done
Events/ConcertsComplex pricing (price + fee per type)✅ Fee per ticket type (Implemented in v0.9.0)✅ Done
Order WorkflowFull workflow (pending→approved→delivered)✅ Implemented✅ Done
AllocationsAuto-create on delivery✅ AllocationsView (Implemented in v0.9.0)✅ Done
External SalesDedicated Ticketmaster page✅ Dedicated TicketmasterView✅ Done
MessagingEmail notifications (via Messages page)Not implemented🔵 Low
Reports6-tab report system with fee tracking✅ 6-tab ReportsView with fees (Implemented in v0.9.0)✅ Done
User ApprovalPending/approved status✅ Via membership✅ Done
Navigation4 role-based sections✅ 4 sections (Member/Ticket Mgr/Treasurer/Admin)✅ Done

Quick Comparison

AspectBase44OursWinner
Ticket pricingPrice + Fee separated✅ Price + Fee separatedTie (parity)
Multiple showtimes❌ None✅ Full supportOurs
Flexible ticket types❌ Hardcoded adult/child✅ Dynamic typesOurs
Order audit trail❌ Minimal✅ Full historyOurs
Correction requests❌ None✅ Full systemOurs
Invitation links❌ None✅ Shareable linksOurs
Email messaging✅ Has (via Messages)❌ NoneBase44 (low prio)
Report filters✅ Date range, member✅ Event, member, date rangeTie (parity)

1. User Management

Base44 User Model

typescript
{
  email: string
  full_name: string
  first_name: string
  last_name: string
  phone_number: string
  organization_id: string                    // Single primary organization
  roles: string[]                     // ["billettansvarlig", "kasserer"]
  extended_role: "editor" | null      // Admin-level access
  approval_status: "pending" | "approved"
  is_verified: boolean
  _app_role: "user" | "admin"         // App-level role
}

Our User Model

typescript
// User (global identity)
{
  id: string
  email: string
  name: string
  isSuperAdmin?: boolean
}

// OrganizationMembership (per-organization)
{
  organizationId: string
  userId: string
  role: "member" | "ticketManager" | "treasurer" | "admin"
  roles?: string[]                   // ["billettansvarlig", "kasserer"]
  approved: boolean
}

Gaps & Recommendations

FeatureBase44OursRecommendation
Phone Number✅ Has❌ MissingAdd phone?: string to User
First/Last Name✅ Separate❌ Single nameKeep single name (simpler)
Multiple Roles✅ Array ["billettansvarlig", "kasserer"]roles?: string[] (Implemented in v0.9.0)✅ Done
Extended Role"editor" for partial adminticketManager / treasurer role hierarchy✅ Equivalent (Implemented in v0.9.0)
Approval Statuspending/approvedapproved: boolean✅ Equivalent

Priority: ✅ Done - Only phone number remains as a minor gap


2. Events/Concerts (Pricing Model)

Base44 Concert Model

typescript
{
  name: string;
  date: string; // "2026-03-09"
  location: string;
  description: string;
  organization_id: string;

  // Member pricing
  adult_price: 350;
  adult_ticket_fee: 30; // Fee ADDED to price
  child_price: 175;
  child_ticket_fee: 25;

  // Special prices with fees
  special_prices: [
    { name: "Student", price: 150, ticket_fee: 10 },
    { name: "Senior", price: 200, ticket_fee: 25 },
    { name: "Avtalt spesialpris", price: 85, ticket_fee: 15 },
  ];

  // Ticketmaster pricing (external sales)
  ticketmaster_price: 350;
  ticketmaster_adult_fee: 30;
  ticketmaster_child_price: 200;
  ticketmaster_child_fee: 15;

  max_tickets: 200;
  is_active: boolean;
}

Our Event + TicketType Model

typescript
// Event
{
  id: string
  organizationId: string
  name: string
  date: string
  location?: string
  description?: string
  venueCapacity: number              // Per showtime
  active: boolean
  salesLocked?: boolean
  showtimes?: Showtime[]
}

// TicketType (separate entity)
{
  id: string
  eventId: string
  organizationId: string
  name: string                       // "Adult", "Child", "Student"
  price: number
  fee: number                        // Ticket fee (Implemented in v0.9.0)
  ticketmasterPrice?: number         // External pricing override
  ticketmasterFee?: number           // External fee override
  sortOrder: number
}

Gaps & Recommendations

FeatureBase44OursRecommendation
Ticket Fees✅ Separate fee per typefee field on TicketType (Implemented in v0.9.0)✅ Done
Adult/Child Built-in✅ Fixed fields❌ Flexible types⚠️ Our way is MORE flexible
Special Prices✅ Dynamic array✅ Via TicketType✅ Better - our types are entities
Ticketmaster Pricing✅ Separate fieldsticketmasterPrice/ticketmasterFee (Implemented in v0.9.0)✅ Done
Max Tickets✅ Per concert✅ Per showtime✅ Better - per showtime is more flexible
Multiple Showtimes❌ None✅ Showtimes array✅ We're better

Priority: ✅ Done - Fee fields and Ticketmaster pricing implemented


3. Order Workflow

Base44 TicketOrder Model

typescript
{
  concert_id: string;
  organization_id: string;
  member_email: string;
  member_name: string;
  adult_tickets: number; // Fixed field
  child_tickets: number; // Fixed field
  special_orders: [{ name: "Student", quantity: 5 }];
  status: "pending" | "approved" | "delivered" | "cancelled";
  notes: string; // Member notes
  editor_notes: string; // Admin notes
  is_archived: boolean;
}

Our TicketOrder Model

typescript
{
  id: string
  organizationId: string
  eventId: string
  eventName: string
  showtimeId?: string

  requestedBy: string
  requestedByName: string
  requestedByEmail: string

  // Flexible ticket items
  tickets: [
    { ticketTypeId, ticketTypeName, quantity, unitPrice }
  ]
  totalQuantity: number
  totalAmount: number

  status: "pending" | "approved" | "delivered" |
    "awaitingReconciliation" | "settled" | "cancelled"
  soldQuantity?: number
  soldTypeQuantities?: SoldTypeQuantity[]
  returnedQuantity?: number
  invoiceBasis?: InvoiceBasis
  invoicedAt?: string
  invoicedBy?: string
  invoicedByName?: string

  // Full audit trail
  approvedBy?: string
  approvedByName?: string
  approvedAt?: string
  deliveredBy?, deliveredByName?, deliveredAt?
  cancelledBy?, cancelledByName?, cancelledAt?
  cancelReason?: string

  note?: string
  adminNote?: string               // Admin/editor notes (Implemented in v0.9.0)
  adminNoteBy?: string
  adminNoteAt?: string
  archived?: boolean               // Archive old orders (Implemented in v0.9.0)
  archiveOverride?: OrderArchiveOverrideEvidence
}

Comparison

FeatureBase44OursWinner
Status Workflow✅ Same✅ SameTie
Fixed Adult/Child❌ Less flexible✅ Dynamic typesOurs
Editor Notes✅ HasadminNote (Implemented in v0.9.0)Tie
Archived Flag✅ Hasarchived (Implemented in v0.9.0)Tie
Sold-only invoice basis❌ No✅ Frozen at settlementOurs
Invoice-issued audit❌ Minimal✅ Member/event state with stable actor auditOurs
Archive prerequisite evidence❌ No✅ Persistent admin override evidenceOurs
Audit Trail❌ Less detail✅ Full trailOurs
Showtime Support❌ None✅ HasOurs

Priority: ✅ Done - Our model is now feature-complete with full parity


4. Ticket Allocations

When an order is marked "delivered", Base44 creates an Allocation record. We have a dedicated AllocationsView (/admin/allocations) that provides pre-assignment of tickets to members by ticket type, with per-member allocation management. (Implemented in v0.9.0)

Priority: ✅ Done


5. Ticket Sales (Direct Member Sales)

FeatureBase44OursRecommendation
Member SalesVia TicketSaleVia Registration✅ Equivalent
External SalesVia TicketSaleVia ExternalSale✅ Cleaner separation
Fee Tracking✅ Per sale✅ Via TicketType fee (Implemented in v0.9.0)✅ Done
Showtime Support❌ None✅ HasOurs better

Priority: ✅ Done - Fee calculations derive from TicketType fee field


6. Messaging (Email Notifications) 🔵 LOW PRIORITY

This is NOT in-app messaging - it appears to be an email sending system:

  • Admin composes message with subject/content
  • Sends email to individual member or entire organization
  • Tracks if email was read (via read receipts or link tracking)

Our Implementation: None — We rely on external email for now.

Recommendation: DEFER - Very low priority.


7. Reports ✅ IMPLEMENTED

We have a dedicated ReportsView (/admin/reports) with 6 tabs (Implemented in v0.9.0):

  • Per medlem (Members) - Sales grouped by member with invoice tracking
  • Per kor (Organizations) - Multi-organization breakdown
  • Per konsert (Events) - Event-level sales
  • Per type (Types) - Ticket type breakdown
  • Ticketmaster - External sales via Ticketmaster
  • Alle salg (All) - Complete transaction list

Priority: ✅ Done - Full 6-tab report system with fee tracking.


8. UI/Navigation Comparison

Page-by-Page Comparison

Base44 PageOur EquivalentStatusNotes
/HomeRegisterSaleView✅ EquivalentSame functionality
/OrderTicketsOrderTicketsView✅ ImplementedOrder workflow
/MySalesHistoryView✅ EquivalentMember's sales
/EditorOrdersadmin/OrdersView✅ ImplementedPending/Approved tabs
/OrderHistoryadmin/OrdersView✅ CombinedHistory tab in OrdersView
/AdminAllocationsadmin/AllocationsView✅ ImplementedDedicated page (Implemented in v0.9.0)
/Messages❌ None🔴 MISSINGNeed to implement
/AdminReportsadmin/ReportsView✅ Implemented6-tab reports (Implemented in v0.9.0)
/AdminOrganizationssuper-admin/OrganizationsView✅ Equivalent
/AdminConcertsadmin/EventsView✅ EquivalentWe call them "Events"
/AdminTicketmasteradmin/TicketmasterView✅ ImplementedDedicated page (Implemented in v0.9.0)
/AdminUsersadmin/MembersView✅ Equivalent+ super-admin/UsersView

Features We Have That They Don't

  1. Correction Request System - Members can request changes to sales
  2. Multiple Showtimes per event
  3. Invitation Links - Shareable join links with expiry
  4. Super Admin - Cross-organization global admin role
  5. Separate External Sales entity - Cleaner data model

9. Implementation Priority (Updated)

Phase 1: Critical (Before Migration) — All Done ✅

#TaskEffortDescription
1Order WorkflowDonePending→Approved→Delivered
2Ticket FeesDonefee on TicketType (Implemented in v0.9.0)
3Fee in ReportsDonetotalFee shown in all report tabs (Implemented in v0.9.0)
4Admin NotesDoneadminNote field on TicketOrder (Implemented in v0.9.0)

Phase 2: Important (For Feature Parity) — Mostly Done

#TaskEffortDescription
5🟡 Phone Number~30mAdd phone to User entity
6Multiple RolesDoneroles[] on OrganizationMembership (Implemented in v0.9.0)
7Archive FlagDonearchived boolean on orders (Implemented in v0.9.0)
8Date Range FilterDoneAdd date filters to Reports (Implemented in v0.9.0)
9All Sales ListDone"Alle salg" tab in ReportsView (Implemented in v0.9.0)

Phase 3: Nice to Have

#TaskEffortDescription
10Email Messaging~4hEmail composer (VERY low priority, defer)
11Enhanced ReportsDone6-tab report system (Implemented in v0.9.0)
12Role-Based NavDone4 sections: Member/Ticket Mgr/Treasurer/Admin (Implemented in v0.9.0)

Features We Do BETTER (Keep As-Is)

  • Multiple Showtimes per event (Base44 has none)
  • Flexible Ticket Types (not hardcoded adult/child)
  • Full Audit Trail on orders (who/when for each status change)
  • Separate ExternalSale entity (cleaner than their TicketSale)
  • Correction Request System (Base44 doesn't have this!)
  • Invitation Links for joining organizations with expiry
  • Super Admin role for cross-organization management
  • i18n Support (English + Norwegian)

10. Migration Checklist

Data Migration Required

  • [ ] Organizations → Organizations (1:1)
  • [ ] Users → Users + OrganizationMembership
  • [ ] Concerts → Events + TicketTypes (split pricing)
  • [ ] TicketOrders → TicketOrders (transform structure)
  • [ ] TicketAllocations → Skip (use delivered orders)
  • [ ] TicketSales → Registrations + ExternalSales (split by type)
  • [ ] Messages → Skip or migrate later

Schema Changes (✅ All Completed in v0.9.0)

typescript
// 1. ✅ Fee on TicketType (Done)
interface TicketType {
  // ... existing
  fee: number;                  // ✅ IMPLEMENTED
  ticketmasterPrice?: number;   // ✅ IMPLEMENTED
  ticketmasterFee?: number;     // ✅ IMPLEMENTED
}

// 2. Phone on User (still pending)
interface User {
  // ... existing
  phone?: string; // 🟡 NOT YET IMPLEMENTED
}

// 3. ✅ Roles on OrganizationMembership (Done)
interface OrganizationMembership {
  // ... existing
  role: "member" | "ticketManager" | "treasurer" | "admin"; // ✅ IMPLEMENTED (role hierarchy)
  roles?: string[];             // ✅ IMPLEMENTED
}

// 4. ✅ AdminNote on TicketOrder (Done)
interface TicketOrder {
  // ... existing
  adminNote?: string;           // ✅ IMPLEMENTED
  adminNoteBy?: string;         // ✅ IMPLEMENTED
  adminNoteAt?: string;         // ✅ IMPLEMENTED
  archived?: boolean;           // ✅ IMPLEMENTED
}

11. Suggested Next Steps

Most originally-identified gaps have been closed as of v0.9.0. Remaining items:

  1. Add phone number — Only remaining schema gap (🟡 Minor)
  2. Create migration script — Transform Base44 data to our schema
  3. Internal messaging — If needed (currently deferred)

Summary of Remaining Gaps

GapPriorityEffort
Phone number on User🟡 Minor~30m
Email messaging⚪ Defer~4h
Data migration script🟡 Medium~8h

Built with VitePress