- Refactor New Quote page to enterprise data-entry layout (2-column, dense) - Add Payment Terms dropdown (COD, NET 15, NET 30, NET 60) - Fix sidebar menu active states and route names - Fix brand filter badge visibility on brands page - Remove company_name references (use business instead) - Polish Promotions page layout - Fix double-click issue on sidebar menu collapse - Make all searches case-insensitive (like -> ilike for PostgreSQL)
208 lines
6.9 KiB
PHP
208 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api\Accounting;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Accounting\ApVendor;
|
|
use App\Models\Business;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ApVendorController extends Controller
|
|
{
|
|
/**
|
|
* List vendors for a business.
|
|
*
|
|
* GET /api/{business}/ap/vendors
|
|
*/
|
|
public function index(Request $request, Business $business): JsonResponse
|
|
{
|
|
$query = ApVendor::where('business_id', $business->id);
|
|
|
|
// Search
|
|
if ($request->filled('search')) {
|
|
$search = $request->search;
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'ilike', "%{$search}%")
|
|
->orWhere('code', 'ilike', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// Active filter
|
|
if ($request->has('active')) {
|
|
$query->where('is_active', $request->boolean('active'));
|
|
}
|
|
|
|
$vendors = $query->orderBy('name')->paginate($request->get('per_page', 50));
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $vendors->items(),
|
|
'meta' => [
|
|
'current_page' => $vendors->currentPage(),
|
|
'last_page' => $vendors->lastPage(),
|
|
'per_page' => $vendors->perPage(),
|
|
'total' => $vendors->total(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get a single vendor.
|
|
*
|
|
* GET /api/{business}/ap/vendors/{vendor}
|
|
*/
|
|
public function show(Business $business, ApVendor $vendor): JsonResponse
|
|
{
|
|
if ($vendor->business_id !== $business->id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Vendor does not belong to this business.',
|
|
], 403);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $vendor,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create a new vendor.
|
|
*
|
|
* POST /api/{business}/ap/vendors
|
|
*/
|
|
public function store(Request $request, Business $business): JsonResponse
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'code' => 'nullable|string|max:50',
|
|
'name' => 'required|string|max:255',
|
|
'legal_name' => 'nullable|string|max:255',
|
|
'tax_id' => 'nullable|string|max:50',
|
|
'default_payment_terms' => 'nullable|integer|min:0',
|
|
'default_gl_account_id' => 'nullable|integer|exists:gl_accounts,id',
|
|
'contact_name' => 'nullable|string|max:255',
|
|
'contact_email' => 'nullable|email|max:255',
|
|
'contact_phone' => 'nullable|string|max:50',
|
|
'address_line1' => 'nullable|string|max:255',
|
|
'address_line2' => 'nullable|string|max:255',
|
|
'city' => 'nullable|string|max:100',
|
|
'state' => 'nullable|string|max:100',
|
|
'postal_code' => 'nullable|string|max:20',
|
|
'country' => 'nullable|string|max:100',
|
|
'is_1099' => 'boolean',
|
|
'notes' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
// Generate code if not provided
|
|
if (empty($validated['code'])) {
|
|
$validated['code'] = $this->generateVendorCode($business->id, $validated['name']);
|
|
}
|
|
|
|
$vendor = ApVendor::create([
|
|
'business_id' => $business->id,
|
|
...$validated,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Vendor {$vendor->name} created.",
|
|
'data' => $vendor,
|
|
], 201);
|
|
} catch (\Exception $e) {
|
|
Log::error('Vendor creation failed', [
|
|
'business_id' => $business->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to create vendor: '.$e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update a vendor.
|
|
*
|
|
* PUT /api/{business}/ap/vendors/{vendor}
|
|
*/
|
|
public function update(Request $request, Business $business, ApVendor $vendor): JsonResponse
|
|
{
|
|
if ($vendor->business_id !== $business->id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Vendor does not belong to this business.',
|
|
], 403);
|
|
}
|
|
|
|
try {
|
|
$validated = $request->validate([
|
|
'code' => 'nullable|string|max:50',
|
|
'name' => 'required|string|max:255',
|
|
'legal_name' => 'nullable|string|max:255',
|
|
'tax_id' => 'nullable|string|max:50',
|
|
'default_payment_terms' => 'nullable|integer|min:0',
|
|
'default_gl_account_id' => 'nullable|integer|exists:gl_accounts,id',
|
|
'contact_name' => 'nullable|string|max:255',
|
|
'contact_email' => 'nullable|email|max:255',
|
|
'contact_phone' => 'nullable|string|max:50',
|
|
'address_line1' => 'nullable|string|max:255',
|
|
'address_line2' => 'nullable|string|max:255',
|
|
'city' => 'nullable|string|max:100',
|
|
'state' => 'nullable|string|max:100',
|
|
'postal_code' => 'nullable|string|max:20',
|
|
'country' => 'nullable|string|max:100',
|
|
'is_1099' => 'boolean',
|
|
'is_active' => 'boolean',
|
|
'notes' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
$vendor->update($validated);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Vendor {$vendor->name} updated.",
|
|
'data' => $vendor->fresh(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
Log::error('Vendor update failed', [
|
|
'vendor_id' => $vendor->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to update vendor: '.$e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate vendor code from name.
|
|
*/
|
|
protected function generateVendorCode(int $businessId, string $name): string
|
|
{
|
|
$words = preg_split('/\s+/', strtoupper($name));
|
|
$prefix = '';
|
|
foreach ($words as $word) {
|
|
$prefix .= substr(preg_replace('/[^A-Z0-9]/', '', $word), 0, 3);
|
|
if (strlen($prefix) >= 6) {
|
|
break;
|
|
}
|
|
}
|
|
$prefix = substr($prefix, 0, 6);
|
|
|
|
$count = ApVendor::where('business_id', $businessId)
|
|
->where('code', 'ilike', "{$prefix}%")
|
|
->count();
|
|
|
|
return $count > 0 ? "{$prefix}-{$count}" : $prefix;
|
|
}
|
|
}
|