lbyte-budget 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.simplecov +25 -0
  3. data/API_DOCUMENTATION.md +526 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CLEANUP.md +85 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/INTEGRATION_GUIDE.md +452 -0
  8. data/LICENSE.txt +21 -0
  9. data/RAILS_USAGE.md +510 -0
  10. data/README.md +367 -0
  11. data/Rakefile +19 -0
  12. data/TESTING.md +243 -0
  13. data/TEST_IMPLEMENTATION_SUMMARY.md +246 -0
  14. data/TEST_SUITE.md +253 -0
  15. data/app/controllers/budget/application_controller.rb +10 -0
  16. data/app/controllers/budget/line_items_controller.rb +112 -0
  17. data/app/controllers/budget/payments_controller.rb +108 -0
  18. data/app/controllers/budget/quotes_controller.rb +180 -0
  19. data/app/models/budget/line_item.rb +59 -0
  20. data/app/models/budget/payment.rb +58 -0
  21. data/app/models/budget/quote.rb +158 -0
  22. data/app/views/budget/line_items/index.json.jbuilder +7 -0
  23. data/app/views/budget/line_items/show.json.jbuilder +6 -0
  24. data/app/views/budget/payments/index.json.jbuilder +6 -0
  25. data/app/views/budget/payments/show.json.jbuilder +5 -0
  26. data/app/views/budget/quotes/index.json.jbuilder +15 -0
  27. data/app/views/budget/quotes/show.json.jbuilder +23 -0
  28. data/config/routes.rb +12 -0
  29. data/db/migrate/20251129000001_create_budget_quotes.rb +17 -0
  30. data/db/migrate/20251129000002_create_budget_line_items.rb +17 -0
  31. data/db/migrate/20251129000003_create_budget_payments.rb +18 -0
  32. data/examples/basic_usage.rb +130 -0
  33. data/examples/test_examples.rb +113 -0
  34. data/lib/budget/engine.rb +25 -0
  35. data/lib/budget/line_item.rb +66 -0
  36. data/lib/budget/payment.rb +56 -0
  37. data/lib/budget/quote.rb +163 -0
  38. data/lib/budget/version.rb +5 -0
  39. data/lib/budget.rb +10 -0
  40. data/lib/generators/budget/INSTALL +46 -0
  41. data/lib/generators/budget/install_generator.rb +33 -0
  42. data/sig/budget.rbs +4 -0
  43. metadata +115 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4a6790bd37c0ab98951fbe00bee4ebcb3ceb150621e5f9dea2f55084faf134e
4
+ data.tar.gz: f95b5d8a82477d7ec9d2585019e212c0de249875ff48353d98da26b63ae29b07
5
+ SHA512:
6
+ metadata.gz: 34a9ef72b6310b1dfacf16cc49e41877ebd9c7899ed2f4bdcc37bcf4cb6eacb4abfdbb398848deafb8e1c2a81c55d2b8e0d3146e5b41bb658b73d50d98dea514
7
+ data.tar.gz: 02c5d50f54bb79615644041b4788ac3e7427a1d5d38e116b36749881a9b6708dea580b6625ef7384aea7459b8d6ced3263392998ed68e8f8e629b9eb7373a884
data/.simplecov ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ add_filter '/spec/'
5
+ add_filter '/examples/'
6
+ add_filter '/db/'
7
+ add_filter '/config/'
8
+
9
+ add_group 'ActiveRecord Models', 'app/models'
10
+ add_group 'Controllers', 'app/controllers'
11
+ add_group 'Views', 'app/views'
12
+ add_group 'Engine', 'lib/budget/engine.rb'
13
+ add_group 'Main', 'lib/budget.rb'
14
+
15
+ # Coverage thresholds
16
+ minimum_coverage 99
17
+ minimum_coverage_by_file 90
18
+
19
+ # Configure formatters
20
+ if ENV['CI']
21
+ formatter SimpleCov::Formatter::SimpleFormatter
22
+ else
23
+ formatter SimpleCov::Formatter::HTMLFormatter
24
+ end
25
+ end
@@ -0,0 +1,526 @@
1
+ # Budget Engine - JSON API Documentation
2
+
3
+ ## Overview
4
+
5
+ The Budget Engine provides a complete REST JSON API for managing quotes, line items, and payments. All endpoints return JSON responses using JBuilder.
6
+
7
+ ## Base URL
8
+
9
+ When mounted in your Rails app at `/budget`:
10
+ ```
11
+ https://your-app.com/budget
12
+ ```
13
+
14
+ ## Authentication
15
+
16
+ The engine doesn't include authentication. Implement authentication in your main Rails app using `before_action` callbacks.
17
+
18
+ ## API Endpoints
19
+
20
+ ### Quotes
21
+
22
+ #### List All Quotes
23
+ ```
24
+ GET /budget/quotes
25
+ ```
26
+
27
+ **Parameters:**
28
+ - `page` (optional) - Page number for pagination
29
+ - `per_page` (optional) - Items per page (default: 20)
30
+
31
+ **Response:**
32
+ ```json
33
+ [
34
+ {
35
+ "id": 1,
36
+ "customer_name": "María González",
37
+ "customer_contact": "555-1234",
38
+ "quote_date": "2025-11-29T10:30:00.000Z",
39
+ "created_at": "2025-11-29T10:30:00.000Z",
40
+ "line_items_count": 3,
41
+ "payments_count": 1,
42
+ "totals": {
43
+ "total": 265.00,
44
+ "total_paid": 132.50,
45
+ "remaining_balance": 132.50,
46
+ "fully_paid": false
47
+ }
48
+ }
49
+ ]
50
+ ```
51
+
52
+ #### Get Quote Details
53
+ ```
54
+ GET /budget/quotes/:id
55
+ ```
56
+
57
+ **Response:**
58
+ ```json
59
+ {
60
+ "id": 1,
61
+ "customer_name": "María González",
62
+ "customer_contact": "555-1234",
63
+ "notes": "Prefiere montura liviana",
64
+ "quote_date": "2025-11-29T10:30:00.000Z",
65
+ "created_at": "2025-11-29T10:30:00.000Z",
66
+ "updated_at": "2025-11-29T10:30:00.000Z",
67
+ "line_items": [
68
+ {
69
+ "id": 1,
70
+ "description": "Lentes progresivos alta gama",
71
+ "price": 150.00,
72
+ "category": "lente",
73
+ "quantity": 1,
74
+ "category_name": "Lente",
75
+ "subtotal": 150.00,
76
+ "created_at": "2025-11-29T10:31:00.000Z"
77
+ },
78
+ {
79
+ "id": 2,
80
+ "description": "Montura de titanio",
81
+ "price": 80.00,
82
+ "category": "montura",
83
+ "quantity": 1,
84
+ "category_name": "Montura",
85
+ "subtotal": 80.00,
86
+ "created_at": "2025-11-29T10:32:00.000Z"
87
+ }
88
+ ],
89
+ "payments": [
90
+ {
91
+ "id": 1,
92
+ "amount": 132.50,
93
+ "payment_date": "2025-11-29T10:35:00.000Z",
94
+ "payment_method": "efectivo",
95
+ "payment_method_name": "Efectivo",
96
+ "notes": "Adelanto 50%",
97
+ "created_at": "2025-11-29T10:35:00.000Z"
98
+ }
99
+ ],
100
+ "totals": {
101
+ "total": 265.00,
102
+ "total_paid": 132.50,
103
+ "remaining_balance": 132.50,
104
+ "fully_paid": false
105
+ },
106
+ "category_breakdown": {
107
+ "lente": 150.00,
108
+ "montura": 80.00,
109
+ "tratamiento": 35.00
110
+ }
111
+ }
112
+ ```
113
+
114
+ #### Create Quote
115
+ ```
116
+ POST /budget/quotes
117
+ ```
118
+
119
+ **Request Body:**
120
+ ```json
121
+ {
122
+ "quote": {
123
+ "customer_name": "María González",
124
+ "customer_contact": "555-1234",
125
+ "notes": "Cliente preferencial"
126
+ }
127
+ }
128
+ ```
129
+
130
+ **Response:** Same as GET /budget/quotes/:id (201 Created)
131
+
132
+ #### Update Quote
133
+ ```
134
+ PATCH /budget/quotes/:id
135
+ ```
136
+
137
+ **Request Body:**
138
+ ```json
139
+ {
140
+ "quote": {
141
+ "customer_contact": "555-5678",
142
+ "notes": "Updated notes"
143
+ }
144
+ }
145
+ ```
146
+
147
+ **Response:** Same as GET /budget/quotes/:id (200 OK)
148
+
149
+ #### Delete Quote
150
+ ```
151
+ DELETE /budget/quotes/:id
152
+ ```
153
+
154
+ **Response:** 204 No Content
155
+
156
+ #### Get Quote Summary
157
+ ```
158
+ GET /budget/quotes/:id/summary
159
+ ```
160
+
161
+ **Response:**
162
+ ```json
163
+ {
164
+ "id": 1,
165
+ "customer_name": "María González",
166
+ "customer_contact": "555-1234",
167
+ "date": "2025-11-29T10:30:00.000Z",
168
+ "line_items_count": 3,
169
+ "total": 265.00,
170
+ "total_paid": 132.50,
171
+ "remaining_balance": 132.50,
172
+ "fully_paid": false,
173
+ "category_breakdown": {
174
+ "lente": 150.00,
175
+ "montura": 80.00,
176
+ "tratamiento": 35.00
177
+ },
178
+ "payments_count": 1
179
+ }
180
+ ```
181
+
182
+ ### Line Items
183
+
184
+ #### List Quote Line Items
185
+ ```
186
+ GET /budget/quotes/:quote_id/line_items
187
+ ```
188
+
189
+ **Response:**
190
+ ```json
191
+ [
192
+ {
193
+ "id": 1,
194
+ "description": "Lentes progresivos",
195
+ "price": 150.00,
196
+ "category": "lente",
197
+ "quantity": 1,
198
+ "category_name": "Lente",
199
+ "subtotal": 150.00,
200
+ "created_at": "2025-11-29T10:31:00.000Z"
201
+ }
202
+ ]
203
+ ```
204
+
205
+ #### Get Line Item Details
206
+ ```
207
+ GET /budget/quotes/:quote_id/line_items/:id
208
+ ```
209
+
210
+ **Response:**
211
+ ```json
212
+ {
213
+ "id": 1,
214
+ "description": "Lentes progresivos alta gama",
215
+ "price": 150.00,
216
+ "category": "lente",
217
+ "quantity": 1,
218
+ "category_name": "Lente",
219
+ "subtotal": 150.00,
220
+ "quote_id": 1,
221
+ "created_at": "2025-11-29T10:31:00.000Z",
222
+ "updated_at": "2025-11-29T10:31:00.000Z"
223
+ }
224
+ ```
225
+
226
+ #### Create Line Item
227
+ ```
228
+ POST /budget/quotes/:quote_id/line_items
229
+ ```
230
+
231
+ **Request Body:**
232
+ ```json
233
+ {
234
+ "line_item": {
235
+ "description": "Lentes progresivos",
236
+ "price": 150.00,
237
+ "category": "lente",
238
+ "quantity": 1
239
+ }
240
+ }
241
+ ```
242
+
243
+ **Categories:** `lente`, `montura`, `tratamiento`, `other`
244
+
245
+ **Response:** Same as GET line_item (201 Created)
246
+
247
+ #### Update Line Item
248
+ ```
249
+ PATCH /budget/quotes/:quote_id/line_items/:id
250
+ ```
251
+
252
+ **Request Body:**
253
+ ```json
254
+ {
255
+ "line_item": {
256
+ "price": 160.00,
257
+ "quantity": 2
258
+ }
259
+ }
260
+ ```
261
+
262
+ **Response:** Same as GET line_item (200 OK)
263
+
264
+ #### Delete Line Item
265
+ ```
266
+ DELETE /budget/quotes/:quote_id/line_items/:id
267
+ ```
268
+
269
+ **Response:** 204 No Content
270
+
271
+ ### Payments
272
+
273
+ #### List Quote Payments
274
+ ```
275
+ GET /budget/quotes/:quote_id/payments
276
+ ```
277
+
278
+ **Response:**
279
+ ```json
280
+ [
281
+ {
282
+ "id": 1,
283
+ "amount": 132.50,
284
+ "payment_date": "2025-11-29T10:35:00.000Z",
285
+ "payment_method": "efectivo",
286
+ "payment_method_name": "Efectivo",
287
+ "notes": "Adelanto 50%",
288
+ "created_at": "2025-11-29T10:35:00.000Z"
289
+ }
290
+ ]
291
+ ```
292
+
293
+ #### Get Payment Details
294
+ ```
295
+ GET /budget/quotes/:quote_id/payments/:id
296
+ ```
297
+
298
+ **Response:**
299
+ ```json
300
+ {
301
+ "id": 1,
302
+ "amount": 132.50,
303
+ "payment_date": "2025-11-29T10:35:00.000Z",
304
+ "payment_method": "efectivo",
305
+ "payment_method_name": "Efectivo",
306
+ "notes": "Adelanto 50%",
307
+ "quote_id": 1,
308
+ "created_at": "2025-11-29T10:35:00.000Z",
309
+ "updated_at": "2025-11-29T10:35:00.000Z"
310
+ }
311
+ ```
312
+
313
+ #### Create Payment
314
+ ```
315
+ POST /budget/quotes/:quote_id/payments
316
+ ```
317
+
318
+ **Request Body:**
319
+ ```json
320
+ {
321
+ "payment": {
322
+ "amount": 132.50,
323
+ "payment_method": "efectivo",
324
+ "notes": "Adelanto 50%"
325
+ }
326
+ }
327
+ ```
328
+
329
+ **Payment Methods:** `efectivo`, `tarjeta`, `transferencia`, `cheque`, `other`
330
+
331
+ **Response:** Same as GET payment (201 Created)
332
+
333
+ #### Update Payment
334
+ ```
335
+ PATCH /budget/quotes/:quote_id/payments/:id
336
+ ```
337
+
338
+ **Request Body:**
339
+ ```json
340
+ {
341
+ "payment": {
342
+ "amount": 150.00,
343
+ "notes": "Updated amount"
344
+ }
345
+ }
346
+ ```
347
+
348
+ **Response:** Same as GET payment (200 OK)
349
+
350
+ #### Delete Payment
351
+ ```
352
+ DELETE /budget/quotes/:quote_id/payments/:id
353
+ ```
354
+
355
+ **Response:** 204 No Content
356
+
357
+ ## Error Responses
358
+
359
+ ### Validation Errors (422 Unprocessable Entity)
360
+ ```json
361
+ {
362
+ "errors": [
363
+ "Customer name can't be blank",
364
+ "Price must be greater than or equal to 0"
365
+ ]
366
+ }
367
+ ```
368
+
369
+ ### Not Found (404)
370
+ ```json
371
+ {
372
+ "error": "Record not found"
373
+ }
374
+ ```
375
+
376
+ ## Example Usage
377
+
378
+ ### Complete Workflow
379
+
380
+ ```javascript
381
+ // 1. Create a quote
382
+ const quote = await fetch('/budget/quotes', {
383
+ method: 'POST',
384
+ headers: { 'Content-Type': 'application/json' },
385
+ body: JSON.stringify({
386
+ quote: {
387
+ customer_name: 'María González',
388
+ customer_contact: 'maria@example.com'
389
+ }
390
+ })
391
+ }).then(r => r.json());
392
+
393
+ // 2. Add line items
394
+ await fetch(`/budget/quotes/${quote.id}/line_items`, {
395
+ method: 'POST',
396
+ headers: { 'Content-Type': 'application/json' },
397
+ body: JSON.stringify({
398
+ line_item: {
399
+ description: 'Lentes progresivos',
400
+ price: 150.00,
401
+ category: 'lente'
402
+ }
403
+ })
404
+ });
405
+
406
+ await fetch(`/budget/quotes/${quote.id}/line_items`, {
407
+ method: 'POST',
408
+ headers: { 'Content-Type': 'application/json' },
409
+ body: JSON.stringify({
410
+ line_item: {
411
+ description: 'Montura titanio',
412
+ price: 80.00,
413
+ category: 'montura'
414
+ }
415
+ })
416
+ });
417
+
418
+ // 3. Get updated quote with totals
419
+ const updatedQuote = await fetch(`/budget/quotes/${quote.id}`)
420
+ .then(r => r.json());
421
+
422
+ console.log('Total:', updatedQuote.totals.total); // 230.00
423
+
424
+ // 4. Add payment
425
+ await fetch(`/budget/quotes/${quote.id}/payments`, {
426
+ method: 'POST',
427
+ headers: { 'Content-Type': 'application/json' },
428
+ body: JSON.stringify({
429
+ payment: {
430
+ amount: 115.00,
431
+ payment_method: 'efectivo',
432
+ notes: 'Adelanto 50%'
433
+ }
434
+ })
435
+ });
436
+
437
+ // 5. Check remaining balance
438
+ const finalQuote = await fetch(`/budget/quotes/${quote.id}`)
439
+ .then(r => r.json());
440
+
441
+ console.log('Remaining:', finalQuote.totals.remaining_balance); // 115.00
442
+ console.log('Fully paid?', finalQuote.totals.fully_paid); // false
443
+ ```
444
+
445
+ ## Mounting in Your Rails App
446
+
447
+ In your main app's `config/routes.rb`:
448
+
449
+ ```ruby
450
+ mount Budget::Engine => '/budget'
451
+ ```
452
+
453
+ This makes all endpoints available at `/budget/*`.
454
+
455
+ ## Adding Authentication
456
+
457
+ In your main Rails app:
458
+
459
+ ```ruby
460
+ # config/initializers/budget.rb
461
+ Budget::QuotesController.class_eval do
462
+ before_action :authenticate_user!
463
+ end
464
+
465
+ Budget::LineItemsController.class_eval do
466
+ before_action :authenticate_user!
467
+ end
468
+
469
+ Budget::PaymentsController.class_eval do
470
+ before_action :authenticate_user!
471
+ end
472
+ ```
473
+
474
+ Or use concerns:
475
+
476
+ ```ruby
477
+ # app/controllers/concerns/budget_authentication.rb
478
+ module BudgetAuthentication
479
+ extend ActiveSupport::Concern
480
+
481
+ included do
482
+ before_action :authenticate_user!
483
+ end
484
+ end
485
+
486
+ # config/initializers/budget.rb
487
+ [
488
+ Budget::QuotesController,
489
+ Budget::LineItemsController,
490
+ Budget::PaymentsController
491
+ ].each do |controller|
492
+ controller.include BudgetAuthentication
493
+ end
494
+ ```
495
+
496
+ ## Pagination
497
+
498
+ The quotes index endpoint supports pagination via kaminari (if available in your app):
499
+
500
+ ```
501
+ GET /budget/quotes?page=2&per_page=50
502
+ ```
503
+
504
+ Install kaminari in your main app:
505
+ ```ruby
506
+ gem 'kaminari'
507
+ ```
508
+
509
+ ## CORS Configuration
510
+
511
+ For API access from different domains, configure CORS in your main Rails app:
512
+
513
+ ```ruby
514
+ # Gemfile
515
+ gem 'rack-cors'
516
+
517
+ # config/initializers/cors.rb
518
+ Rails.application.config.middleware.insert_before 0, Rack::Cors do
519
+ allow do
520
+ origins '*' # or specific domains
521
+ resource '/budget/*',
522
+ headers: :any,
523
+ methods: [:get, :post, :patch, :delete, :options]
524
+ end
525
+ end
526
+ ```
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-11-29
4
+
5
+ - Initial release
data/CLEANUP.md ADDED
@@ -0,0 +1,85 @@
1
+ # Cleanup Guide
2
+
3
+ ## Project Structure (Rails Engine Only)
4
+
5
+ This gem is now a **Rails Engine only** - PORO classes have been removed for simplicity.
6
+
7
+ ## Essential Documentation
8
+
9
+ - ✅ **README.md** - Main documentation and installation guide
10
+ - ✅ **API_DOCUMENTATION.md** - Complete REST API reference
11
+ - ✅ **RAILS_USAGE.md** - Rails Engine usage guide
12
+ - ✅ **TESTING.md** - Test suite documentation
13
+ - ✅ **CHANGELOG.md** - Version history
14
+ - ✅ **CODE_OF_CONDUCT.md** - Community guidelines
15
+ - ✅ **LICENSE.txt** - MIT License
16
+
17
+ ## Core Files
18
+
19
+ ### Library Structure
20
+ ```
21
+ lib/
22
+ ├── budget.rb # Main module - loads Rails Engine
23
+ └── budget/
24
+ ├── version.rb # Version constant
25
+ └── engine.rb # Rails Engine configuration
26
+ ```
27
+
28
+ ### Rails Engine Structure
29
+ ```
30
+ app/
31
+ ├── models/budget/ # ActiveRecord models
32
+ │ ├── quote.rb
33
+ │ ├── line_item.rb
34
+ │ └── payment.rb
35
+ ├── controllers/budget/ # REST API controllers
36
+ │ ├── quotes_controller.rb
37
+ │ ├── line_items_controller.rb
38
+ │ └── payments_controller.rb
39
+ └── views/budget/ # JBuilder JSON views
40
+ ├── quotes/
41
+ ├── line_items/
42
+ └── payments/
43
+
44
+ db/migrate/ # Database migrations
45
+ ├── 20251129000001_create_budget_quotes.rb
46
+ ├── 20251129000002_create_budget_line_items.rb
47
+ └── 20251129000003_create_budget_payments.rb
48
+ ```
49
+
50
+ ### Test Files
51
+ ```
52
+ spec/
53
+ ├── rails_helper.rb # Rails test configuration
54
+ ├── budget_spec.rb # Module tests
55
+ ├── models/ # ActiveRecord model tests (145 examples)
56
+ ├── controllers/ # API controller tests (80 examples)
57
+ └── lib/ # Engine tests (8 examples)
58
+ ```
59
+
60
+ ### Examples
61
+ ```
62
+ examples/
63
+ ├── basic_usage.rb # Basic usage example (requires Rails)
64
+ └── test_examples.rb # Test scenarios (requires Rails)
65
+ ```
66
+
67
+ ## Files Removed
68
+
69
+ The following PORO implementation files have been removed:
70
+ - `lib/budget/quote.rb` (PORO class)
71
+ - `lib/budget/line_item.rb` (PORO class)
72
+ - `lib/budget/payment.rb` (PORO class)
73
+ - `spec/budget/*.rb` (PORO tests)
74
+ - `spec/spec_helper.rb` (PORO test config - now minimal)
75
+
76
+ ## Architecture Decision
77
+
78
+ **Rails-only approach** provides:
79
+ - ✅ Simpler codebase
80
+ - ✅ No class constant conflicts
81
+ - ✅ Better Rails integration
82
+ - ✅ Easier maintenance
83
+ - ✅ Clear focus on Rails Engine functionality
84
+
85
+ Users can still use ActiveRecord models in memory without persistence if needed.