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
data/RAILS_USAGE.md ADDED
@@ -0,0 +1,510 @@
1
+ # Rails Engine Installation & Usage Guide
2
+
3
+ ## What is Budget?
4
+
5
+ Budget is a **Rails Engine** that provides complete quote/budget management functionality for businesses with advance payments and installment tracking.
6
+
7
+ **Features:**
8
+ - ✅ ActiveRecord models with associations
9
+ - ✅ Database migrations
10
+ - ✅ REST JSON API with JBuilder
11
+ - ✅ Business logic (calculations, validations)
12
+ - ✅ Complete Rails Engine integration
13
+
14
+ ## Installation Steps
15
+
16
+ ### 1. Add to Gemfile
17
+
18
+ ```ruby
19
+ gem 'budget-quotes'
20
+ ```
21
+
22
+ ### 2. Install the Gem
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ ### 3. Generate Migrations
29
+
30
+ ```bash
31
+ rails generate budget:install
32
+ ```
33
+
34
+ This creates three migration files in `db/migrate/`:
35
+ - `create_budget_quotes.rb`
36
+ - `create_budget_line_items.rb`
37
+ - `create_budget_payments.rb`
38
+
39
+ ### 4. Run Migrations
40
+
41
+ ```bash
42
+ rails db:migrate
43
+ ```
44
+
45
+ This creates three tables:
46
+ - `budget_quotes`
47
+ - `budget_line_items`
48
+ - `budget_payments`
49
+
50
+ ## Database Tables Created
51
+
52
+ ### budget_quotes
53
+ Stores customer quote information.
54
+
55
+ | Column | Type | Description |
56
+ |--------|------|-------------|
57
+ | id | integer | Primary key |
58
+ | customer_name | string | Customer name (required) |
59
+ | customer_contact | string | Phone/email |
60
+ | notes | text | Additional notes |
61
+ | quote_date | datetime | Quote creation date |
62
+ | created_at | datetime | Timestamp |
63
+ | updated_at | datetime | Timestamp |
64
+
65
+ ### budget_line_items
66
+ Stores individual items in each quote.
67
+
68
+ | Column | Type | Description |
69
+ |--------|------|-------------|
70
+ | id | integer | Primary key |
71
+ | budget_quote_id | integer | Foreign key to quotes |
72
+ | description | string | Item description (required) |
73
+ | price | decimal(10,2) | Unit price (required) |
74
+ | category | string | lente, montura, tratamiento, other |
75
+ | quantity | integer | Quantity (default: 1) |
76
+ | created_at | datetime | Timestamp |
77
+ | updated_at | datetime | Timestamp |
78
+
79
+ ### budget_payments
80
+ Stores payments made towards quotes.
81
+
82
+ | Column | Type | Description |
83
+ |--------|------|-------------|
84
+ | id | integer | Primary key |
85
+ | budget_quote_id | integer | Foreign key to quotes |
86
+ | amount | decimal(10,2) | Payment amount (required) |
87
+ | payment_date | datetime | When payment was made |
88
+ | payment_method | string | efectivo, tarjeta, transferencia, cheque, other |
89
+ | notes | text | Payment notes |
90
+ | created_at | datetime | Timestamp |
91
+ | updated_at | datetime | Timestamp |
92
+
93
+ ## Usage Examples
94
+
95
+ ### Creating a Quote
96
+
97
+ ```ruby
98
+ # In a controller or service
99
+ quote = Budget::Quote.create!(
100
+ customer_name: "María González",
101
+ customer_contact: "maria@example.com"
102
+ )
103
+ ```
104
+
105
+ ### Adding Line Items
106
+
107
+ ```ruby
108
+ # Add lenses
109
+ quote.add_line_item(
110
+ description: "Lentes progresivos alta gama",
111
+ price: 150.00,
112
+ category: "lente"
113
+ )
114
+
115
+ # Add frame
116
+ quote.add_line_item(
117
+ description: "Montura titanio ligera",
118
+ price: 80.00,
119
+ category: "montura"
120
+ )
121
+
122
+ # Add treatment (multiple quantity)
123
+ quote.line_items.create!(
124
+ description: "Protección UV",
125
+ price: 25.00,
126
+ category: "tratamiento",
127
+ quantity: 2
128
+ )
129
+ ```
130
+
131
+ ### Adding Payments
132
+
133
+ ```ruby
134
+ # Initial payment (adelanto)
135
+ quote.add_payment(
136
+ amount: 150.00,
137
+ payment_method: "efectivo",
138
+ notes: "Adelanto 50%"
139
+ )
140
+
141
+ # Later payment
142
+ quote.add_payment(
143
+ amount: 100.00,
144
+ payment_method: "tarjeta",
145
+ notes: "Pago parcial"
146
+ )
147
+ ```
148
+
149
+ ### Querying Quotes
150
+
151
+ ```ruby
152
+ # Find quote by ID
153
+ quote = Budget::Quote.find(1)
154
+
155
+ # Recent quotes
156
+ recent = Budget::Quote.recent.limit(10)
157
+
158
+ # Find by customer name
159
+ quotes = Budget::Quote.by_customer("María")
160
+
161
+ # Pending quotes (not fully paid)
162
+ pending = Budget::Quote.pending
163
+
164
+ # With associations
165
+ quote = Budget::Quote.includes(:line_items, :payments).find(1)
166
+ ```
167
+
168
+ ### Calculations
169
+
170
+ ```ruby
171
+ quote = Budget::Quote.find(1)
172
+
173
+ # Total price
174
+ quote.total # => 300.0
175
+
176
+ # Total paid
177
+ quote.total_paid # => 150.0
178
+
179
+ # Remaining balance
180
+ quote.remaining_balance # => 150.0
181
+
182
+ # Check if fully paid
183
+ quote.fully_paid? # => false
184
+
185
+ # Category breakdown
186
+ quote.category_breakdown
187
+ # => { lente: 150.0, montura: 80.0, tratamiento: 50.0, other: 20.0 }
188
+
189
+ # Initial payment
190
+ quote.initial_payment # => first Payment record
191
+
192
+ # Summary hash
193
+ quote.summary
194
+ # => { id: 1, customer_name: "María", total: 300.0, ... }
195
+ ```
196
+
197
+ ### Formatting for Display
198
+
199
+ ```ruby
200
+ # Print formatted quote
201
+ puts quote
202
+
203
+ # Output:
204
+ # ============================================================
205
+ # PRESUPUESTO #1
206
+ # ============================================================
207
+ # Cliente: María González
208
+ # Contacto: maria@example.com
209
+ # Fecha: 29/11/2025
210
+ #
211
+ # DETALLE:
212
+ # ------------------------------------------------------------
213
+ # 1. Lente - Lentes progresivos: $150.00
214
+ # 2. Montura - Montura titanio: $80.00
215
+ # ...
216
+ ```
217
+
218
+ ## Controller Example
219
+
220
+ ```ruby
221
+ class QuotesController < ApplicationController
222
+ def create
223
+ @quote = Budget::Quote.create!(quote_params)
224
+
225
+ # Add items from form
226
+ params[:line_items].each do |item|
227
+ @quote.add_line_item(
228
+ description: item[:description],
229
+ price: item[:price],
230
+ category: item[:category]
231
+ )
232
+ end
233
+
234
+ redirect_to quote_path(@quote), notice: "Quote created successfully"
235
+ end
236
+
237
+ def add_payment
238
+ @quote = Budget::Quote.find(params[:id])
239
+ @quote.add_payment(payment_params)
240
+
241
+ redirect_to quote_path(@quote), notice: "Payment added"
242
+ end
243
+
244
+ private
245
+
246
+ def quote_params
247
+ params.require(:quote).permit(:customer_name, :customer_contact, :notes)
248
+ end
249
+
250
+ def payment_params
251
+ params.require(:payment).permit(:amount, :payment_method, :notes)
252
+ end
253
+ end
254
+ ```
255
+
256
+ ## View Example (ERB)
257
+
258
+ ```erb
259
+ <h1>Quote #<%= @quote.id %></h1>
260
+
261
+ <div class="customer-info">
262
+ <p><strong>Customer:</strong> <%= @quote.customer_name %></p>
263
+ <p><strong>Contact:</strong> <%= @quote.customer_contact %></p>
264
+ <p><strong>Date:</strong> <%= @quote.quote_date.strftime('%d/%m/%Y') %></p>
265
+ </div>
266
+
267
+ <h2>Line Items</h2>
268
+ <table>
269
+ <% @quote.line_items.each do |item| %>
270
+ <tr>
271
+ <td><%= item.category_name %></td>
272
+ <td><%= item.description %></td>
273
+ <td><%= number_to_currency(item.price) %></td>
274
+ <td><%= item.quantity %></td>
275
+ <td><%= number_to_currency(item.subtotal) %></td>
276
+ </tr>
277
+ <% end %>
278
+ </table>
279
+
280
+ <div class="totals">
281
+ <p><strong>Total:</strong> <%= number_to_currency(@quote.total) %></p>
282
+ <p><strong>Paid:</strong> <%= number_to_currency(@quote.total_paid) %></p>
283
+ <p><strong>Balance:</strong> <%= number_to_currency(@quote.remaining_balance) %></p>
284
+ <p><strong>Status:</strong>
285
+ <span class="<%= @quote.fully_paid? ? 'paid' : 'pending' %>">
286
+ <%= @quote.fully_paid? ? 'PAID' : 'PENDING' %>
287
+ </span>
288
+ </p>
289
+ </div>
290
+
291
+ <h2>Payments</h2>
292
+ <ul>
293
+ <% @quote.payments.ordered.each do |payment| %>
294
+ <li><%= payment %></li>
295
+ <% end %>
296
+ </ul>
297
+ ```
298
+
299
+ ## API (JSON) Example
300
+
301
+ ```ruby
302
+ # app/controllers/api/quotes_controller.rb
303
+ module Api
304
+ class QuotesController < ApplicationController
305
+ def show
306
+ @quote = Budget::Quote.includes(:line_items, :payments).find(params[:id])
307
+
308
+ render json: {
309
+ id: @quote.id,
310
+ customer_name: @quote.customer_name,
311
+ customer_contact: @quote.customer_contact,
312
+ quote_date: @quote.quote_date,
313
+ total: @quote.total,
314
+ total_paid: @quote.total_paid,
315
+ remaining_balance: @quote.remaining_balance,
316
+ fully_paid: @quote.fully_paid?,
317
+ line_items: @quote.line_items.map do |item|
318
+ {
319
+ description: item.description,
320
+ price: item.price,
321
+ category: item.category,
322
+ quantity: item.quantity,
323
+ subtotal: item.subtotal
324
+ }
325
+ end,
326
+ payments: @quote.payments.ordered.map do |payment|
327
+ {
328
+ amount: payment.amount,
329
+ payment_date: payment.payment_date,
330
+ payment_method: payment.payment_method_name,
331
+ notes: payment.notes
332
+ }
333
+ end
334
+ }
335
+ end
336
+ end
337
+ end
338
+ ```
339
+
340
+ ## Routes Example
341
+
342
+ ```ruby
343
+ # config/routes.rb
344
+ Rails.application.routes.draw do
345
+ resources :quotes do
346
+ member do
347
+ post :add_payment
348
+ post :add_line_item
349
+ end
350
+ end
351
+
352
+ namespace :api do
353
+ resources :quotes, only: [:index, :show, :create]
354
+ end
355
+ end
356
+ ```
357
+
358
+ ## Validations
359
+
360
+ The models include built-in validations:
361
+
362
+ ### Budget::Quote
363
+ - `customer_name` - required
364
+ - `quote_date` - automatically set if not provided
365
+
366
+ ### Budget::LineItem
367
+ - `description` - required
368
+ - `price` - required, must be >= 0
369
+ - `quantity` - required, must be > 0
370
+ - `category` - must be in: lente, montura, tratamiento, other
371
+
372
+ ### Budget::Payment
373
+ - `amount` - required, must be > 0
374
+ - `payment_date` - automatically set if not provided
375
+ - `payment_method` - must be in: efectivo, tarjeta, transferencia, cheque, other
376
+
377
+ ## Scopes
378
+
379
+ ### Budget::Quote
380
+ - `recent` - Orders by created_at DESC
381
+ - `by_customer(name)` - Search by customer name
382
+ - `pending` - Only quotes not fully paid
383
+
384
+ ### Budget::Payment
385
+ - `ordered` - Orders by payment_date ASC
386
+ - `by_method(method)` - Filter by payment method
387
+
388
+ ## Testing in Your Rails App
389
+
390
+ ```ruby
391
+ # spec/models/budget/quote_spec.rb
392
+ require 'rails_helper'
393
+
394
+ RSpec.describe Budget::Quote, type: :model do
395
+ it "calculates total correctly" do
396
+ quote = create(:budget_quote)
397
+ quote.line_items.create!(description: "Test", price: 100, category: "other")
398
+
399
+ expect(quote.total).to eq(100)
400
+ end
401
+
402
+ it "tracks payments correctly" do
403
+ quote = create(:budget_quote)
404
+ quote.line_items.create!(description: "Test", price: 100, category: "other")
405
+ quote.payments.create!(amount: 50, payment_method: "efectivo")
406
+
407
+ expect(quote.remaining_balance).to eq(50)
408
+ expect(quote.fully_paid?).to be false
409
+ end
410
+ end
411
+ ```
412
+
413
+ ## Troubleshooting
414
+
415
+ ### Migrations not found
416
+ If migrations aren't generated, ensure you've run:
417
+ ```bash
418
+ rails generate budget:install
419
+ ```
420
+
421
+ ### Models not loading
422
+ Ensure Rails is properly loading the engine. Check that `config/application.rb` doesn't exclude engines.
423
+
424
+ ### Foreign key errors
425
+ Ensure migrations were run in the correct order:
426
+ 1. budget_quotes
427
+ 2. budget_line_items
428
+ 3. budget_payments
429
+
430
+ ## JSON API Usage
431
+
432
+ The Budget Engine includes a complete REST JSON API. See **API_DOCUMENTATION.md** for full details.
433
+
434
+ ### Mounting the Engine
435
+
436
+ In your Rails app's `config/routes.rb`:
437
+
438
+ ```ruby
439
+ mount Budget::Engine => '/budget'
440
+ ```
441
+
442
+ This makes all API endpoints available at `/budget/*`.
443
+
444
+ ### Quick API Examples
445
+
446
+ #### Create a quote via API
447
+ ```bash
448
+ curl -X POST http://localhost:3000/budget/quotes \
449
+ -H "Content-Type: application/json" \
450
+ -d '{"quote":{"customer_name":"María González","customer_contact":"555-1234"}}'
451
+ ```
452
+
453
+ #### Add line item
454
+ ```bash
455
+ curl -X POST http://localhost:3000/budget/quotes/1/line_items \
456
+ -H "Content-Type: application/json" \
457
+ -d '{"line_item":{"description":"Lentes","price":150.00,"category":"lente"}}'
458
+ ```
459
+
460
+ #### Add payment
461
+ ```bash
462
+ curl -X POST http://localhost:3000/budget/quotes/1/payments \
463
+ -H "Content-Type: application/json" \
464
+ -d '{"payment":{"amount":75.00,"payment_method":"efectivo"}}'
465
+ ```
466
+
467
+ #### Get quote with calculations
468
+ ```bash
469
+ curl http://localhost:3000/budget/quotes/1
470
+ ```
471
+
472
+ Response:
473
+ ```json
474
+ {
475
+ "id": 1,
476
+ "customer_name": "María González",
477
+ "line_items": [...],
478
+ "payments": [...],
479
+ "totals": {
480
+ "total": 150.00,
481
+ "total_paid": 75.00,
482
+ "remaining_balance": 75.00,
483
+ "fully_paid": false
484
+ }
485
+ }
486
+ ```
487
+
488
+ ### Available Endpoints
489
+
490
+ - `GET /budget/quotes` - List all quotes
491
+ - `GET /budget/quotes/:id` - Get quote details
492
+ - `POST /budget/quotes` - Create quote
493
+ - `PATCH /budget/quotes/:id` - Update quote
494
+ - `DELETE /budget/quotes/:id` - Delete quote
495
+ - `GET /budget/quotes/:id/summary` - Get quote summary
496
+ - `GET /budget/quotes/:quote_id/line_items` - List line items
497
+ - `POST /budget/quotes/:quote_id/line_items` - Add line item
498
+ - `PATCH /budget/quotes/:quote_id/line_items/:id` - Update line item
499
+ - `DELETE /budget/quotes/:quote_id/line_items/:id` - Delete line item
500
+ - `GET /budget/quotes/:quote_id/payments` - List payments
501
+ - `POST /budget/quotes/:quote_id/payments` - Add payment
502
+ - `PATCH /budget/quotes/:quote_id/payments/:id` - Update payment
503
+ - `DELETE /budget/quotes/:quote_id/payments/:id` - Delete payment
504
+
505
+ See **API_DOCUMENTATION.md** for complete API reference with request/response examples.
506
+
507
+ ## Support
508
+
509
+ For issues or questions, visit:
510
+ https://github.com/rubenpazch/lbyte-budget