AI Review Center
Commit Review Workspace ?
Select a commit on the left to inspect quality, security, business value, findings, and suggested code changes.
Project AI Score
?
59%
Quality Avg
?
64%
Security Avg
?
45%
Reviews
?
57
Review Result ?
jattin01/avriti-backend · 7e44d1c6
The commit adds imports for product and service data with improvements to performance by preloading existing slugs, SKUs, and names to avoid repeated DB queries, and handles duplicates carefully. Cache control headers added for sample file downloads. The logic for processing CSV rows is clear and accounts for dry runs and error reporting. However, some improvements are possible in handling edge cases and consistent comment usage.
Quality
?
85%
Security
?
60%
Business Value
?
75%
Maintainability
?
78%
Issues & Suggested Fixes
?
1AI detects the issue
2Shows file, line, or evidence
3Suggests the fix to apply
Use Product::pluck('Slug') >Flip() May Cau...
Where
app/Http/Controllers/AdminProductController.php:170
Issue / Evidence
Use Product::pluck('slug')->flip() may cause issues if slugs are not unique or if the keys flip incorrectly
Suggested Fix
consider validating uniqueness or using a Set type for clarity.
Transform Sku Keys To Lowercase Only But N...
Where
app/Http/Controllers/AdminProductController.php:176
Issue / Evidence
Transform SKU keys to lowercase only but no normalization for Unicode or whitespace
Suggested Fix
consider normalization to avoid false negatives in uniqueness check.
Missing Validation
Where
app/Http/Controllers/AdminProductController.php:301
Issue / Evidence
Variants count uses max of multiple lists, but no validation for inconsistent lengths which might lead to out-of-bound errors or improper data pairing
Suggested Fix
add validation for consistent list lengths or handle missing data more robustly.
Duplicate Logic
Where
app/Http/Controllers/AdminProductController.php:342
Issue / Evidence
Duplicate SKU detection ends processing that row but does not add detailed logging or error severity
Suggested Fix
consider error logging with more details or returning user-friendly messages.
Consultation Fee Processing Strips Non Num...
Where
app/Http/Controllers/AdminServiceDetailController.php:305
Issue / Evidence
consultation_fee processing strips non-numeric but keeps original if no digits
Suggested Fix
consider stricter validation or normalization as currency fields to avoid downstream calculation errors.
Skip Existing Names But Does Not Handle Ca...
Where
app/Http/Controllers/AdminServiceDetailController.php:324
Issue / Evidence
Skip existing names but does not handle case where multiple imports run simultaneously causing potential race condition
Suggested Fix
consider adding locking or transaction safeguards.
Code Change Preview · .gitignore
?
Removed / Before Commit
- /Product Excel test only.xlsx - /Avriti_Ratna_Updated.xlsx - /Service Detail test.xlsx - /BACKEND_AI_PROMPT.md - \ No newline at end of file - \ No newline at end of file
Added / After Commit
+ /Product Excel test only.xlsx + /Avriti_Ratna_Updated.xlsx + /Service Detail test.xlsx + \ No newline at end of file + /BACKEND_AI_PROMPT.md + /Avriti_Services_sample_1.xlsx + /Product_Sample_1.xlsx + /Avriti_Services_sample.xlsx + \ No newline at end of file
Removed / Before Commit
- /** Download the sample Excel template for product import. */ - public function sampleExcel(): \Symfony\Component\HttpFoundation\BinaryFileResponse - { - $path = storage_path('app/samples/product-sample.xlsx'); - abort_unless(file_exists($path), 404); - - return response()->download($path, 'Product Sample.xlsx'); - } - - public function import(Request $request): \Illuminate\Http\JsonResponse - $types = ProductType::pluck('id', 'name'); - $variants = Variant::pluck('id', 'name'); - - foreach ($rows as $row) { - $rowNum = (int) $row['r']; - if ($rowNum === 1) continue; // skip header - continue; - }
Added / After Commit
+ /** Download the sample Excel template for product import. */ + public function sampleExcel(): \Symfony\Component\HttpFoundation\BinaryFileResponse + { + $path = storage_path('app/samples/Product_Sample_1.xlsx'); + abort_unless(file_exists($path), 404); + + // no-cache headers so a browser/proxy never serves an old cached copy. + return response()->download($path, 'Product_Sample_1.xlsx', [ + 'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0', + 'Pragma' => 'no-cache', + 'Expires' => '0', + ]); + } + + public function import(Request $request): \Illuminate\Http\JsonResponse + $types = ProductType::pluck('id', 'name'); + $variants = Variant::pluck('id', 'name'); +
Removed / Before Commit
- /** Download the sample Excel template for service-detail import. */ - public function sampleExcel(): \Symfony\Component\HttpFoundation\BinaryFileResponse - { - $path = storage_path('app/samples/service-detail-sample.xlsx'); - abort_unless(File::exists($path), 404); - - return response()->download($path, 'Service Detail Sample.xlsx'); - } - - /** - $skipped = 0; - $errors = []; - - foreach ($rows as $row) { - $rowNum = (int) $row['r']; - if ($rowNum === 1) continue; - $cells[$col] = $t === 's' ? ($strings[(int) $v] ?? '') : $v; - }
Added / After Commit
+ /** Download the sample Excel template for service-detail import. */ + public function sampleExcel(): \Symfony\Component\HttpFoundation\BinaryFileResponse + { + $path = storage_path('app/samples/Avriti_Services_sample_1.xlsx'); + abort_unless(File::exists($path), 404); + + return response()->download($path, 'Avriti_Services_sample_1.xlsx'); + } + + /** + $skipped = 0; + $errors = []; + + // --- Pass 1: group rows into services ------------------------------- + // A row with a non-empty `name` starts a new service (its full content + // is used). Tiers may be given two ways (both supported): + // 1. Comma-separated in a single row: + // tier_name = "Up to 2 BHK,Up to 4 BHK,Full Commercial"
Removed / Before Commit
- } - return [ - ...$item, - 'title' => $sd?->name ?? ($item['title'] ?? 'Service'), - 'thumbnail' => $image, - 'product_image' => $image, - 'is_service' => true,
Added / After Commit
+ } + return [ + ...$item, + // Prefer the checkout title — it carries the tier name + // (e.g. "Avriti Vastu — Up to 4 BHK"); fall back to the + // plain service name only if that's missing. + 'title' => ! empty($item['title']) ? $item['title'] : ($sd?->name ?? 'Service'), + 'thumbnail' => $image, + 'product_image' => $image, + 'is_service' => true,
Removed / Before Commit
- - return [ - ...$item, - 'title' => $serviceDetail?->name ?? ($item['title'] ?? 'Service'), - 'thumbnail' => $image, - 'product_image' => $image, - 'is_service' => true,
Added / After Commit
+ + return [ + ...$item, + // Prefer the checkout title — it carries the tier name + // (e.g. "Avriti Vastu — Up to 4 BHK"); fall back to the + // plain service name only if that's missing. + 'title' => ! empty($item['title']) ? $item['title'] : ($serviceDetail?->name ?? 'Service'), + 'thumbnail' => $image, + 'product_image' => $image, + 'is_service' => true,
Removed / Before Commit
- {{-- Items --}} - @php - $hasOnlyComboItems = $order->items->isNotEmpty() && $order->items->every(fn ($i) => $i->combo_product_id); - $itemsColumnCount = $hasOnlyComboItems ? 5 : 6; - @endphp - <div class="rounded-2xl border border-gray-200 overflow-hidden"> - <h3 class="px-4 py-3 text-sm font-semibold text-[#0b3dba] border-b border-gray-200">Items</h3> - <thead class="bg-gray-50 text-xs uppercase tracking-[0.1em] text-gray-500"> - <tr> - <th class="px-4 py-3">Product</th> - @unless ($hasOnlyComboItems) - <th class="px-4 py-3">Variant</th> - @endunless - <th class="px-4 py-3">SKU</th> - <th class="px-4 py-3">Price</th> - <th class="px-4 py-3">Qty</th> - <span class="ml-2 inline-flex rounded-full bg-[#eef4ff] px-2 py-0.5 text-[10px] font-semibold text-[#0b3dba]">Combo</span> - @endif
Added / After Commit
+ {{-- Items --}} + @php + $hasOnlyComboItems = $order->items->isNotEmpty() && $order->items->every(fn ($i) => $i->combo_product_id); + // Show the Variant column only if at least one item actually has a variant. + // Service-only orders (and combos) have none → hide the whole column. + $hasAnyVariant = $order->items->contains( + fn ($i) => ! $i->combo_product_id && (filled($i->attribute_value) || filled($i->variant_name)) + ); + $showVariantColumn = ! $hasOnlyComboItems && $hasAnyVariant; + $itemsColumnCount = $showVariantColumn ? 6 : 5; + @endphp + <div class="rounded-2xl border border-gray-200 overflow-hidden"> + <h3 class="px-4 py-3 text-sm font-semibold text-[#0b3dba] border-b border-gray-200">Items</h3> + <thead class="bg-gray-50 text-xs uppercase tracking-[0.1em] text-gray-500"> + <tr> + <th class="px-4 py-3">Product</th> + @if ($showVariantColumn) + <th class="px-4 py-3">Variant</th>
Removed / Before Commit
- form.append('_token', '{{ csrf_token() }}'); - if (dryRun) form.append('dry_run', '1'); - - fetch('{{ route("products.import") }}', { method: 'POST', body: form }) - .then(res => res.json()) - .then(data => { - renderResult(data); - - if (dryRun) { - doImport.classList.add('flex'); - doImport.disabled = false; - doImport.innerHTML = '<i class="fa-solid fa-upload"></i> Import Now'; - } - } else if (data.success && data.imported > 0) { - setTimeout(() => { window.location.reload(); }, 2000); - } - }) - .catch(() => {
Added / After Commit
+ form.append('_token', '{{ csrf_token() }}'); + if (dryRun) form.append('dry_run', '1'); + + fetch('{{ route("products.import") }}', { + method: 'POST', + body: form, + headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, + }) + .then(async res => { + // Read as text first — the server may return an HTML error page + // (413/419/500/504) instead of JSON, which would crash res.json(). + const raw = await res.text(); + let data; + try { + data = JSON.parse(raw); + } catch (e) { + const hint = res.status === 413 ? 'File too large — increase upload limit (post_max_size / client_max_body_size).' + : res.status === 419 ? 'Session expired — refresh the page and try again.'
Removed / Before Commit
- ]; - - // ── Init all Quill editors (common pattern) ── - function initQuill(editorEl) { - const textarea = editorEl.nextElementSibling; - if (!textarea || !textarea.matches('[data-quill-input]')) return; - const wordCountEl = editorEl.closest('div')?.querySelector('[data-quill-word-count]'); - const maxWords = parseInt(editorEl.dataset.maxWords, 10) || null; - - const q = new Quill(editorEl, { theme: 'snow', modules: { toolbar: toolbarOptions } }); - if (textarea.value.trim()) q.root.innerHTML = textarea.value; - - function updateWordCount() { - const text = q.getText().trim(); - wordCountEl.textContent = count; - wordCountEl.closest('.word-count')?.classList.toggle('warn', maxWords && count > maxWords); - } - const html = q.root.innerHTML;
Added / After Commit
+ ]; + + // ── Init all Quill editors (common pattern) ── + // If the user typed raw tags (Quill escaped them to <p> etc.), + // turn those back into real HTML so both toolbar-formatting AND + // hand-typed HTML render on the frontend. + function unescapeTypedHtml(html) { + // does the content contain escaped tags like <p> / <h1> ? + if (!/<\s*\/?\s*(p|h[1-6]|ul|ol|li|strong|b|em|i|u|br|span|div|a|blockquote)\b/i.test(html)) { + return html; + } + // unwrap Quill's auto <p> around the escaped markup, then unescape + const txt = document.createElement('div'); + txt.innerHTML = html; // real DOM: escaped entities become text + let raw = txt.textContent || ''; // e.g. "<p>Online Vastu...</p>" + return raw; + } +
Removed / Before Commit
- form.append('_token', '{{ csrf_token() }}'); - if (dryRun) form.append('dry_run', '1'); - - fetch('{{ route("service-details.import") }}', { method: 'POST', body: form }) - .then(res => res.json()) - .then(data => { - renderResult(data); - if (dryRun) { - validateBtn.classList.add('hidden'); - doImport.classList.add('flex'); - doImport.disabled = false; - doImport.innerHTML = '<i class="fa-solid fa-upload"></i> Import Now'; - } - } else if (data.success && data.imported > 0) { - setTimeout(() => { window.location.reload(); }, 2000); - } - }) - .catch(() => {
Added / After Commit
+ form.append('_token', '{{ csrf_token() }}'); + if (dryRun) form.append('dry_run', '1'); + + fetch('{{ route("service-details.import") }}', { + method: 'POST', + body: form, + headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, + }) + .then(async res => { + // Read as text first — the server may return an HTML error page + // (413/419/500/504) instead of JSON, which would crash res.json(). + const raw = await res.text(); + let data; + try { + data = JSON.parse(raw); + } catch (e) { + // Not JSON → surface the real HTTP status so server issues are visible. + const hint = res.status === 413 ? 'File too large — increase upload limit (post_max_size / client_max_body_size).'
Removed / Before Commit
- <div style="font-size:11px; color:#666; line-height:1.8;"> - {{ $shipping_addr['email'] ?? $orderData['shipping_email'] ?? '-' }}<br> - {{ $shipping_addr['phone'] ?? $billing['phone'] ?? '-' }}<br> - {{ $shipping_addr['address_line1'] ?? $billing['address_line1'] ?? '-' }}@if(!empty($shipping_addr['address_line2'] ?? $billing['address_line2'])), {{ $shipping_addr['address_line2'] ?? $billing['address_line2'] }}@endif<br> - {{ $shipping_addr['city'] ?? $billing['city'] ?? '-' }}, {{ $shipping_addr['state'] ?? $billing['state'] ?? '-' }}<br> - {{ $shipping_addr['pincode'] ?? $billing['pincode'] ?? '-' }} - </div> - <div style="font-size:11px; color:#666; line-height:1.8;"> - {{ $billing['email'] ?? $orderData['billing_email'] ?? '-' }}<br> - {{ $billing['phone'] ?? '-' }}<br> - {{ $billing['address_line1'] ?? '-' }}@if(!empty($billing['address_line2'])), {{ $billing['address_line2'] }}@endif<br> - {{ $billing['city'] ?? '-' }}, {{ $billing['state'] ?? '-' }}<br> - {{ $billing['pincode'] ?? '-' }} - </div>
Added / After Commit
+ <div style="font-size:11px; color:#666; line-height:1.8;"> + {{ $shipping_addr['email'] ?? $orderData['shipping_email'] ?? '-' }}<br> + {{ $shipping_addr['phone'] ?? $billing['phone'] ?? '-' }}<br> + {{ $shipping_addr['address_line1'] ?? $billing['address_line1'] ?? '-' }}@php($line2 = ($shipping_addr['address_line2'] ?? null) ?: ($billing['address_line2'] ?? null))@if(!empty($line2)), {{ $line2 }}@endif<br> + {{ $shipping_addr['city'] ?? $billing['city'] ?? '-' }}, {{ $shipping_addr['state'] ?? $billing['state'] ?? '-' }}<br> + {{ $shipping_addr['pincode'] ?? $billing['pincode'] ?? '-' }} + </div> + <div style="font-size:11px; color:#666; line-height:1.8;"> + {{ $billing['email'] ?? $orderData['billing_email'] ?? '-' }}<br> + {{ $billing['phone'] ?? '-' }}<br> + {{ $billing['address_line1'] ?? '-' }}@if(!empty($billing['address_line2'] ?? null)), {{ $billing['address_line2'] }}@endif<br> + {{ $billing['city'] ?? '-' }}, {{ $billing['state'] ?? '-' }}<br> + {{ $billing['pincode'] ?? '-' }} + </div>