Skip to content

Commit 94fe01c

Browse files
committed
feat: Add bundle configuration API and management
- Implemented new API routes for bundle configurations, including creation, retrieval, updating, and deletion. - Created BundleConfigurationController to handle bundle configuration logic. - Added validation requests for bundle configuration and adding to cart. - Introduced Bundle model with necessary relationships and methods for SKU and share token generation. - Created migration for bundle_configurations table and added necessary fields. - Updated existing Bundle model to include new fields for kit type and weight compatibility. - Seeded initial data for the Weekender Solar Kit and sample configurations.
1 parent d835ca3 commit 94fe01c

File tree

10 files changed

+1425
-0
lines changed

10 files changed

+1425
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Models\BundleConfiguration;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Http\JsonResponse;
9+
use Illuminate\Support\Facades\Auth;
10+
use Illuminate\Validation\ValidationException;
11+
12+
class BundleConfigurationController extends Controller
13+
{
14+
/**
15+
* Get user's bundle configurations
16+
*
17+
* @param Request $request
18+
* @return JsonResponse
19+
*/
20+
public function index(Request $request): JsonResponse
21+
{
22+
$query = BundleConfiguration::with('bundle')
23+
->where(function ($q) {
24+
$q->where('user_id', Auth::id())
25+
->orWhereNull('user_id');
26+
})
27+
->where('is_active', true);
28+
29+
// Filter by bundle if specified
30+
if ($request->has('bundle_id')) {
31+
$query->where('bundle_id', $request->bundle_id);
32+
}
33+
34+
// Search by name
35+
if ($request->has('search') && $request->search) {
36+
$query->where('name', 'like', '%' . $request->search . '%');
37+
}
38+
39+
$configurations = $query->orderBy('updated_at', 'desc')
40+
->paginate(20);
41+
42+
return response()->json([
43+
'success' => true,
44+
'data' => $configurations->map(function ($configuration) {
45+
return [
46+
'id' => $configuration->id,
47+
'name' => $configuration->name,
48+
'bundle' => [
49+
'id' => $configuration->bundle->id,
50+
'name' => $configuration->bundle->name,
51+
'slug' => $configuration->bundle->slug,
52+
],
53+
'sku' => $configuration->sku,
54+
'total_price' => $configuration->total_price,
55+
'formatted_price' => $configuration->getFormattedPrice(),
56+
'total_weight_g' => $configuration->total_weight_g,
57+
'formatted_weight' => $configuration->getFormattedWeight(),
58+
'weight_compatibility' => $configuration->weight_compatibility,
59+
'compatibility_description' => $configuration->getWeightCompatibilityDescription(),
60+
'share_token' => $configuration->share_token,
61+
'created_at' => $configuration->created_at,
62+
'updated_at' => $configuration->updated_at,
63+
];
64+
}),
65+
'pagination' => [
66+
'current_page' => $configurations->currentPage(),
67+
'per_page' => $configurations->perPage(),
68+
'total' => $configurations->total(),
69+
'last_page' => $configurations->lastPage(),
70+
'from' => $configurations->firstItem(),
71+
'to' => $configurations->lastItem(),
72+
]
73+
]);
74+
}
75+
76+
/**
77+
* Update bundle configuration
78+
*
79+
* @param Request $request
80+
* @param int $id
81+
* @return JsonResponse
82+
* @throws ValidationException
83+
*/
84+
public function update(Request $request, int $id): JsonResponse
85+
{
86+
$configuration = BundleConfiguration::where('id', $id)
87+
->where(function ($q) {
88+
$q->where('user_id', Auth::id())
89+
->orWhereNull('user_id');
90+
})
91+
->where('is_active', true)
92+
->firstOrFail();
93+
94+
$validated = $request->validate([
95+
'configuration' => 'required|array',
96+
'configuration.espresso_module' => 'boolean',
97+
'configuration.filter_attachment' => 'boolean',
98+
'configuration.fan_accessory' => 'boolean',
99+
'configuration.solar_panel_size' => 'in:10W,15W,20W',
100+
'name' => 'nullable|string|max:255',
101+
]);
102+
103+
try {
104+
$configuration->update([
105+
'configuration_data' => array_merge(
106+
$configuration->configuration_data,
107+
$validated['configuration']
108+
),
109+
'name' => $validated['name'] ?? $configuration->name,
110+
]);
111+
112+
return response()->json([
113+
'success' => true,
114+
'message' => 'Bundle configuration updated successfully',
115+
'data' => [
116+
'id' => $configuration->id,
117+
'name' => $configuration->name,
118+
'sku' => $configuration->sku,
119+
'total_price' => $configuration->total_price,
120+
'formatted_price' => $configuration->getFormattedPrice(),
121+
'total_weight_g' => $configuration->total_weight_g,
122+
'formatted_weight' => $configuration->getFormattedWeight(),
123+
'weight_compatibility' => $configuration->weight_compatibility,
124+
'compatibility_description' => $configuration->getWeightCompatibilityDescription(),
125+
'configuration_summary' => $configuration->getConfigurationSummary(),
126+
]
127+
]);
128+
129+
} catch (\Exception $e) {
130+
return response()->json([
131+
'success' => false,
132+
'message' => 'Failed to update bundle configuration',
133+
'error' => $e->getMessage()
134+
], 500);
135+
}
136+
}
137+
138+
/**
139+
* Delete bundle configuration
140+
*
141+
* @param int $id
142+
* @return JsonResponse
143+
*/
144+
public function destroy(int $id): JsonResponse
145+
{
146+
$configuration = BundleConfiguration::where('id', $id)
147+
->where(function ($q) {
148+
$q->where('user_id', Auth::id())
149+
->orWhereNull('user_id');
150+
})
151+
->where('is_active', true)
152+
->firstOrFail();
153+
154+
try {
155+
$configuration->update(['is_active' => false]);
156+
157+
return response()->json([
158+
'success' => true,
159+
'message' => 'Bundle configuration deleted successfully'
160+
]);
161+
162+
} catch (\Exception $e) {
163+
return response()->json([
164+
'success' => false,
165+
'message' => 'Failed to delete bundle configuration',
166+
'error' => $e->getMessage()
167+
], 500);
168+
}
169+
}
170+
171+
/**
172+
* Duplicate bundle configuration
173+
*
174+
* @param Request $request
175+
* @param int $id
176+
* @return JsonResponse
177+
*/
178+
public function duplicate(Request $request, int $id): JsonResponse
179+
{
180+
$originalConfiguration = BundleConfiguration::where('id', $id)
181+
->where(function ($q) {
182+
$q->where('user_id', Auth::id())
183+
->orWhereNull('user_id');
184+
})
185+
->where('is_active', true)
186+
->firstOrFail();
187+
188+
$validated = $request->validate([
189+
'name' => 'nullable|string|max:255',
190+
]);
191+
192+
try {
193+
$newConfiguration = $originalConfiguration->bundle->createConfiguration(
194+
$originalConfiguration->configuration_data,
195+
Auth::id(),
196+
$validated['name'] ?? 'Copy of ' . ($originalConfiguration->name ?? 'Configuration')
197+
);
198+
199+
return response()->json([
200+
'success' => true,
201+
'message' => 'Bundle configuration duplicated successfully',
202+
'data' => [
203+
'id' => $newConfiguration->id,
204+
'name' => $newConfiguration->name,
205+
'sku' => $newConfiguration->sku,
206+
'total_price' => $newConfiguration->total_price,
207+
'formatted_price' => $newConfiguration->getFormattedPrice(),
208+
'total_weight_g' => $newConfiguration->total_weight_g,
209+
'formatted_weight' => $newConfiguration->getFormattedWeight(),
210+
'weight_compatibility' => $newConfiguration->weight_compatibility,
211+
'compatibility_description' => $newConfiguration->getWeightCompatibilityDescription(),
212+
]
213+
], 201);
214+
215+
} catch (\Exception $e) {
216+
return response()->json([
217+
'success' => false,
218+
'message' => 'Failed to duplicate bundle configuration',
219+
'error' => $e->getMessage()
220+
], 500);
221+
}
222+
}
223+
224+
/**
225+
* Get configuration statistics for user
226+
*
227+
* @return JsonResponse
228+
*/
229+
public function stats(): JsonResponse
230+
{
231+
$stats = BundleConfiguration::where(function ($q) {
232+
$q->where('user_id', Auth::id())
233+
->orWhereNull('user_id');
234+
})
235+
->where('is_active', true)
236+
->selectRaw('
237+
COUNT(*) as total_configurations,
238+
AVG(total_price) as avg_price,
239+
AVG(total_weight_g) as avg_weight,
240+
SUM(CASE WHEN total_weight_g < 5000 THEN 1 ELSE 0 END) as day_pack_compatible,
241+
SUM(CASE WHEN total_weight_g BETWEEN 5000 AND 10000 THEN 1 ELSE 0 END) as overnight_pack_compatible,
242+
SUM(CASE WHEN total_weight_g > 10000 THEN 1 ELSE 0 END) as base_camp_setups
243+
')
244+
->first();
245+
246+
return response()->json([
247+
'success' => true,
248+
'data' => [
249+
'total_configurations' => (int) $stats->total_configurations,
250+
'average_price' => round((float) $stats->avg_price, 2),
251+
'average_weight' => round((float) $stats->avg_weight / 1000, 2),
252+
'compatibility_breakdown' => [
253+
'day_pack_compatible' => (int) $stats->day_pack_compatible,
254+
'overnight_pack_compatible' => (int) $stats->overnight_pack_compatible,
255+
'base_camp_setups' => (int) $stats->base_camp_setups,
256+
]
257+
]
258+
]);
259+
}
260+
}

0 commit comments

Comments
 (0)