Commit 1661936
authored
feat: subscription management (#67)
* Add subscription management feature spec
- Requirements: 7 user stories with 31 acceptance criteria
- Design: Database schema, composables, components, 23 correctness properties
- Tasks: 16 top-level tasks with comprehensive property-based testing
- Features: Multi-currency support, auto-renewal, lifecycle tracking
- API integration: exchangerate-api.com with localStorage caching
* feat: add database schema and types for subscription management
- Add subscriptions table with RLS policies and constraints
- Add user_preferences table for storing user settings
- Add TypeScript interfaces for Subscription, Currency, ExchangeRate, UserPreference
- Add display models and component prop/emit interfaces
- Add indexes and triggers for both tables
Implements task 1: Set up database schema and types
Requirements: 1.1, 1.2, 1.3, 1.5, 2.1, 2.2, 2.3, 4.1, 4.2
* feat: implement useCurrency composable with exchange rate API integration
- Add useCurrency composable with full currency conversion functionality
- Implement fetchExchangeRates to call exchangerate-api.com
- Add localStorage caching with 24-hour validity
- Implement convert and getExchangeRate functions with inverse fallback
- Add loadUserCurrencyPreference and setMainCurrency for database integration
- Define supported currencies: CNY, USD, EUR, GBP, JPY, HKD
- Add comprehensive unit tests (20 tests passing)
- Validates Requirements 4.1, 4.2, 4.3, 4.4, 4.5
Task: 2.1 Create useCurrency composable with exchange rate API integration
* feat: add property-based test for currency conversion accuracy
- Install fast-check for property-based testing
- Implement Property 10: Currency conversion accuracy test
- Test validates that convert(amount, from, to) = amount × exchangeRate
- Runs 100 iterations across all supported currency pairs
- Validates Requirements 4.1, 4.3, 4.4
* feat: add Property 12 test for same currency display
- Implement property-based test for same currency display
- Validates that when original currency matches main currency, no conversion is applied
- Tests that convert() returns original amount and getExchangeRate() returns 1
- Runs 100 iterations across all supported currencies
- Validates Requirements 4.5
* feat: complete currency management composable with property tests
- Implement useCurrency composable with exchange rate API integration
- Add localStorage caching with 24-hour validity for exchange rates
- Implement currency conversion functions (convert, getExchangeRate)
- Add user preference management (loadUserCurrencyPreference, setMainCurrency)
- Support 6 currencies: CNY, USD, EUR, GBP, JPY, HKD
- Add Property 10: Currency conversion accuracy test (100 iterations)
- Add Property 12: Same currency display test (100 iterations)
- All 22 tests passing
Validates Requirements: 4.1, 4.2, 4.3, 4.4, 4.5
Completes task 2 and subtasks 2.1, 2.2, 2.3
* feat: implement useSubscriptions composable with CRUD operations
- Add loadSubscriptions function to fetch from database
- Add createSubscription with comprehensive validation
- Add updateSubscription with validation
- Add deleteSubscription function
- Add calculateNextBillingDate for monthly/yearly frequencies
- Add snake_case to camelCase conversion utilities
- Validate required fields, billing frequency, and auto-renew/end date relationship
- Include proper error handling with Chinese error messages
Task: 3.1 Create useSubscriptions composable with CRUD operations
Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 2.1, 2.2, 2.3, 2.4, 2.5, 3.1, 3.3
* feat: add property test for subscription creation (Property 1)
- Implement property-based test validating subscription creation with valid data
- Test runs 100 iterations with randomly generated valid inputs
- Validates Requirements 1.1 and 1.4
- Fix bug: convertKeysToCamelCase now properly converts null to undefined for endDate field
- All 96 tests passing
* feat: add property test for required field validation (Property 2)
- Implements Property 2: Required field validation
- Validates Requirements 1.2
- Tests that subscription creation rejects data with missing required fields
- Verifies appropriate error messages are thrown for validation failures
- Runs 100 iterations with various combinations of missing fields
- All tests passing
* feat: add property test for billing frequency validation (Property 3)
- Implements Property 3: Billing frequency validation
- Validates Requirements 1.3
- Tests that only 'monthly' or 'yearly' are accepted as valid billing frequencies
- Generates 100 test cases with invalid billing frequency values
- Verifies proper error handling and Chinese error messages
- All tests passing
* feat: add property test for end date and auto-renew relationship
- Implement Property 4 test validating Requirements 1.5
- Test verifies invalid combinations are rejected (auto-renew with end date, non-auto-renew without end date)
- Test verifies valid combinations are accepted
- Runs 100 iterations with randomly generated subscription data
- All tests passing
* feat: add property test for subscription update persistence (Property 5)
* feat: add property test for auto-renew to fixed end date transition
- Implement Property 6 test validating Requirements 2.2
- Test ensures end date is required when changing from auto-renew to fixed end date
- Validates both rejection (no end date) and acceptance (with end date) cases
- Runs 100 iterations with randomly generated subscription data
- All tests passing
* feat: add Property 7 test for fixed end date to auto-renew transition
- Implements property-based test validating Requirements 2.3
- Tests that changing subscription from fixed end date to auto-renew clears the end date
- Verifies end date is set to undefined when transitioning to auto-renew
- Runs 100 iterations with random subscription data
- All tests passing
* feat: add property test for currency change recalculation (Property 8)
- Implements Property 8: Currency change recalculation test
- Validates Requirement 2.5: currency modification and display amount recalculation
- Tests that subscription currency updates are properly persisted
- Runs 100 iterations across different currency combinations
- All tests passing
* feat: add property test for subscription deletion (Property 9)
- Implement Property 9: Subscription deletion test
- Validates Requirements 3.1, 3.3
- Tests that deleted subscriptions are removed from both database and list
- Verifies other subscriptions remain unaffected
- Runs 100 iterations with random subscription data
- All tests passing
* Complete task 3: subscription management composable with all property tests passing
* feat: implement useSubscriptionCalculations composable
- Add calculateMonthlyEquivalent and calculateYearlyEquivalent functions
- Add isExpired, isEndingSoon, and getDaysUntilEnd lifecycle checks
- Add calculateTotalCosts for aggregate subscription calculations
- Excludes expired subscriptions from totals
- Supports 30-day threshold for ending soon detection
Implements task 4.1 from subscription-management spec
Requirements: 6.3, 6.4, 6.5, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6
* feat: add property tests for subscription calculations composable
- Implement Property 19: Expiration detection tests (3 variants)
- Test expired subscriptions (past end dates)
- Test auto-renew subscriptions never expire
- Test future end dates are not expired
- Implement Property 18: Ending soon detection tests (4 variants)
- Test subscriptions ending within 30 days
- Test auto-renew subscriptions never ending soon
- Test far future end dates not ending soon
- Test expired subscriptions not ending soon
- Implement Property 21: Monthly total calculation test
- Validates sum of monthly + yearly/12 subscriptions
- Implement Property 22: Yearly total calculation test
- Validates sum of yearly + monthly*12 subscriptions
- Implement Property 23: Expired subscription exclusion test
- Validates expired subscriptions excluded from totals
- Validates active subscriptions included in totals
All tests run 100 iterations and validate requirements 6.3, 6.4, 6.5, 7.1-7.6
* feat: implement SubscriptionForm component
- Create SubscriptionForm.vue with all required fields
- Add form validation for required fields and positive amounts
- Implement conditional end date field based on renewal type
- Add currency selector with supported currencies
- Add billing frequency and renewal type radio buttons
- Handle both create and edit modes
- Emit save and update events
- Validate future dates for end date field
- Calculate next billing date automatically
Implements task 5.1 from subscription-management spec
Validates requirements: 1.1, 1.2, 1.3, 1.5, 2.1, 2.2, 2.3, 2.5
* feat: implement SubscriptionCard component
- Add SubscriptionCard.vue component with full display features
- Display subscription name, amount, and billing frequency
- Show original currency when different from main currency
- Display auto-renew indicator or end date
- Show warning for subscriptions ending soon
- Display expired badge for past end dates
- Show next billing date for active subscriptions
- Add edit and delete action buttons with proper emits
- Follow existing Vuetify styling patterns
- Include Chinese labels consistent with app
- Add ARIA labels for accessibility
Implements task 6.1 from subscription-management spec
Validates requirements: 4.2, 4.5, 5.3, 6.1, 6.2, 6.3, 6.4, 6.5
* feat: add property-based tests for SubscriptionCard display
- Implement Property 11: Dual currency display validation
- Implement Property 14: Subscription display information validation
- Implement Property 16: Auto-renew indicator validation
- Implement Property 17: End date display validation
- Implement Property 20: Next billing date calculation validation
All tests use fast-check with 100 iterations and verify correctness
properties for subscription display data structures.
Validates Requirements: 4.2, 5.3, 6.1, 6.2, 6.5
* feat: implement SubscriptionList component
- Add SubscriptionList.vue component with active/expired grouping
- Display loading state with spinner and Chinese text
- Show empty state when no subscriptions exist
- Group subscriptions by status (active/expired) with counts
- Forward edit and delete events from SubscriptionCard
- Follow existing codebase patterns and Chinese localization
- Mark task 7.1 as completed
Validates Requirements 5.1, 5.3
* feat: add property test for subscription list completeness
- Implement Property 13: Subscription list completeness
- Validates Requirements 5.1
- Tests that all subscriptions are displayed when navigating to subscriptions tab
- Verifies grouping logic maintains completeness (no lost/duplicated subscriptions)
- Runs 100 iterations with random subscription data
- Test passes successfully
* feat: implement SubscriptionSummary component
- Add SubscriptionSummary.vue component with card layout
- Display total monthly and yearly costs
- Show active subscription count
- Display currency indicator with localized names
- Include currency formatting utilities
- Responsive grid layout for mobile and desktop
- Mark task 8.1 as completed in tasks.md
Requirements: 7.1, 7.2
* feat: implement CurrencySettings component for subscription management
- Add CurrencySettings.vue component with currency selector
- Display current main currency with symbol, code, and name
- Provide dropdown with all supported currencies (CNY, USD, EUR, GBP, JPY, HKD)
- Emit update event when currency changes
- Include help text explaining currency conversion
- Follow existing Vuetify card-based design pattern
- Implement proper TypeScript types and v-model pattern
- Complete task 9.1 from subscription management spec
Requirements: 4.1, 4.3
* Add SubscriptionsView component and update tasks
* feat: implement SubscriptionsView with state persistence
- Implement main SubscriptionsView component with full CRUD operations
- Add currency settings, subscription summary, and list display
- Implement delete confirmation dialog
- Add comprehensive error handling and loading states
- Fix useSubscriptions composable to use module-level refs for state persistence
- Add Property 15 test validating state persistence across navigation
- All tests passing (10/10)
Validates Requirements: 1.1, 1.4, 2.1, 2.4, 3.1, 3.3, 4.1, 4.3, 5.1, 5.5
* feat: add subscriptions tab to bottom navigation
- Add subscriptions button with mdi-sync icon
- Add Chinese label '订阅' (subscriptions)
- Update route mapping to handle Subscriptions route
- Implements task 11.1 (Requirements 5.2, 5.4)
* feat: add subscriptions route to router
- Add /subscriptions route with requiresAuth meta flag
- Import SubscriptionsView component in router
- Complete task 11: Add subscriptions tab to navigation
- Validates Requirements 5.1, 5.2
* Mark task 12 (database migration scripts) as complete - schema already in db/schema.sql
* feat: add confirmation dialog for subscription deletion
- Implement Vuetify confirmation dialog in SubscriptionsView
- Display subscription name in confirmation message
- Add cancel and confirm actions with loading state
- Include proper error handling and user feedback
- Validates Requirements 3.2
Task 14.1 complete
* feat: add comprehensive error handling for subscriptions
* fix: make schema idempotent with IF NOT EXISTS and DROP IF EXISTS
* docs: document PGRST116 error as expected behavior in loadUserCurrencyPreference
* feat: move subscriptions tab to middle position in bottom navigation
* feat: improve subscriptions UI - move currency settings to user menu, center FAB, optimize layout
* feat: improve currency settings UX - add save button, fullscreen dialog, loading state, and fix HKD symbol consistency
* feat: standardize dialog UI - consistent button styles and fullscreen support
* feat: add validation to password change button - disable when fields empty or passwords don't match
* style: reduce subscription summary text size from h5 to h6 for consistency
* fix: move FAB to SubscriptionsView to fix add button functionality
* feat: add start_date to subscriptions and improve UI layout
- Add start_date field to subscriptions table and TypeScript types
- Update SubscriptionForm to include start date input
- Calculate next billing date based on start date
- Improve SubscriptionCard layout:
- Display badges (billing frequency, auto-renew, expiring soon) inline
- Hide next billing date when end date is before next billing
- Show '下次续订' for auto-renew vs '下次扣费' for fixed end
- Reduce spacing between edit/delete icons
- Update ExpenseList to match icon spacing
- Update empty state text to reflect centered FAB position
* fix: use timezone-aware date handling in subscription form
- Import and use getTodayDate() and formatDateToLocal() from dateUtils
- Ensures dates are handled in local timezone, preventing day shifts
- Matches the date handling approach used in expense form
* feat: add detailed breakdown dialogs for subscription costs
- Add info icons next to monthly and yearly total costs
- Show breakdown of each active subscription's contribution
- Display calculation method (e.g., '年费 ÷ 12' or '月费 × 12')
- Include billing frequency badges for each subscription
- Show total at bottom of breakdown1 parent c9c36ad commit 1661936
27 files changed
Lines changed: 5406 additions & 27 deletions
File tree
- db
- src
- components
- composables
- router
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
| 48 | + | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
| 72 | + | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
| 75 | + | |
76 | 76 | | |
77 | 77 | | |
78 | | - | |
| 78 | + | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| 97 | + | |
96 | 98 | | |
97 | 99 | | |
98 | 100 | | |
| 101 | + | |
99 | 102 | | |
100 | 103 | | |
101 | 104 | | |
| 105 | + | |
102 | 106 | | |
103 | 107 | | |
104 | 108 | | |
| 109 | + | |
105 | 110 | | |
106 | 111 | | |
107 | 112 | | |
108 | 113 | | |
| 114 | + | |
109 | 115 | | |
110 | 116 | | |
111 | 117 | | |
| 118 | + | |
112 | 119 | | |
113 | 120 | | |
114 | 121 | | |
| 122 | + | |
115 | 123 | | |
116 | 124 | | |
117 | 125 | | |
| 126 | + | |
118 | 127 | | |
119 | 128 | | |
120 | 129 | | |
| |||
198 | 207 | | |
199 | 208 | | |
200 | 209 | | |
| 210 | + | |
201 | 211 | | |
202 | 212 | | |
203 | 213 | | |
204 | 214 | | |
205 | 215 | | |
| 216 | + | |
206 | 217 | | |
207 | 218 | | |
208 | 219 | | |
209 | 220 | | |
210 | 221 | | |
| 222 | + | |
211 | 223 | | |
212 | 224 | | |
213 | 225 | | |
214 | 226 | | |
215 | 227 | | |
| 228 | + | |
216 | 229 | | |
217 | 230 | | |
218 | 231 | | |
219 | | - | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
0 commit comments