Compare commits
14 Commits
qa
...
33571a5fd7
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
33571a5fd7 | ||
|
|
94e211f87e | ||
|
|
8b6d3d5fad | ||
|
|
c89e5bdf7d | ||
|
|
10af713fa1 | ||
|
|
ebb263cd36 | ||
|
|
82d9c10130 | ||
|
|
cb24cf575b | ||
|
|
e4c07cb838 | ||
|
|
f38a5afdd7 | ||
|
|
9423c79c80 | ||
|
|
8a958b9c48 | ||
|
|
82882e859e | ||
|
|
2d28e7c1d5 |
@@ -1,6 +1,6 @@
|
|||||||
APP_NAME=Laravel
|
APP_NAME=Laravel
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ namespace App\Events;
|
|||||||
|
|
||||||
use App\Models\ChatMessage;
|
use App\Models\ChatMessage;
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class NewChatMessage implements ShouldBroadcastNow
|
class NewChatMessage implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
|
|
||||||
@@ -17,23 +17,30 @@ class NewChatMessage implements ShouldBroadcastNow
|
|||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(ChatMessage $message)
|
public function __construct(ChatMessage $message)
|
||||||
{
|
{
|
||||||
// Also load sender polymorphic relationship
|
// Safe data only (no heavy relationships in queue)
|
||||||
$message->load('sender');
|
$this->message = [
|
||||||
|
'id' => $message->id,
|
||||||
|
'ticket_id' => $message->ticket_id,
|
||||||
|
'sender_id' => $message->sender_id,
|
||||||
|
'sender_type' => $message->sender_type,
|
||||||
|
'message' => $message->message,
|
||||||
|
'file_path' => $message->file_path,
|
||||||
|
'file_type' => $message->file_type,
|
||||||
|
'created_at' => $message->created_at->toDateTimeString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Load sender separately for broadcastWith()
|
||||||
|
$this->sender = $message->sender;
|
||||||
|
}
|
||||||
|
|
||||||
$this->message = $message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The channel the event should broadcast on.
|
* The channel the event should broadcast on.
|
||||||
*/
|
*/
|
||||||
public function broadcastOn()
|
public function broadcastOn()
|
||||||
{
|
{
|
||||||
return [
|
return new PrivateChannel('ticket.' . $this->message->ticket_id);
|
||||||
new PrivateChannel('ticket.' . $this->message->ticket_id),
|
|
||||||
new PrivateChannel('admin.chat') // 👈 ADD THIS
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,40 +48,25 @@ class NewChatMessage implements ShouldBroadcastNow
|
|||||||
*/
|
*/
|
||||||
public function broadcastWith()
|
public function broadcastWith()
|
||||||
{
|
{
|
||||||
\Log::info('APP_URL USED IN EVENT', [
|
|
||||||
'url' => config('app.url'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
\Log::info("DEBUG: NewChatMessage broadcasting on channel ticket.".$this->message->ticket_id);
|
\Log::info("DEBUG: NewChatMessage broadcasting on channel ticket.".$this->message->ticket_id);
|
||||||
|
|
||||||
\Log::info("EVENT BROADCAST FIRED", [
|
|
||||||
'channel' => 'ticket.'.$this->message->ticket_id,
|
|
||||||
'sender_type' => $this->message->sender_type,
|
|
||||||
'sender_id' => $this->message->sender_id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $this->message->id,
|
'id' => $this->message->id,
|
||||||
'ticket_id' => $this->message->ticket_id,
|
'ticket_id' => $this->message->ticket_id,
|
||||||
'sender_id' => $this->message->sender_id,
|
'sender_id' => $this->message->sender_id,
|
||||||
'sender_type' => $this->message->sender_type,
|
'sender_type' => $this->message->sender_type,
|
||||||
'message' => $this->message->message,
|
'message' => $this->message->message,
|
||||||
'client_id' => $this->message->client_id,
|
'file_url' => $this->message->file_path
|
||||||
|
? asset('storage/' . $this->message->file_path)
|
||||||
// ✅ relative path only
|
: null,
|
||||||
'file_path' => $this->message->file_path ?? null,
|
'file_type' => $this->message->file_type,
|
||||||
'file_type' => $this->message->file_type ?? null,
|
|
||||||
|
|
||||||
'sender' => [
|
'sender' => [
|
||||||
'id' => $this->message->sender->id,
|
'id' => $this->message->sender->id,
|
||||||
'name' => $this->getSenderName(),
|
'name' => $this->getSenderName(),
|
||||||
'is_admin' => $this->message->sender_type === \App\Models\Admin::class,
|
'is_admin' => $this->message->sender_type === \App\Models\Admin::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'created_at' => $this->message->created_at->toDateTimeString(),
|
'created_at' => $this->message->created_at->toDateTimeString(),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,10 +84,4 @@ class NewChatMessage implements ShouldBroadcastNow
|
|||||||
// Admin model has ->name
|
// Admin model has ->name
|
||||||
return $sender->name ?? "Admin";
|
return $sender->name ?? "Admin";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastAs()
|
|
||||||
{
|
|
||||||
return 'NewChatMessage';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exports;
|
|
||||||
|
|
||||||
use Illuminate\Contracts\View\View;
|
|
||||||
use Maatwebsite\Excel\Concerns\FromView;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class InvoicesExport implements FromView
|
|
||||||
{
|
|
||||||
protected $request;
|
|
||||||
|
|
||||||
public function __construct(Request $request)
|
|
||||||
{
|
|
||||||
$this->request = $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function view(): View
|
|
||||||
{
|
|
||||||
$request = $this->request;
|
|
||||||
|
|
||||||
$invoices = DB::table('invoices')
|
|
||||||
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
|
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
|
|
||||||
->select(
|
|
||||||
'invoices.invoice_number',
|
|
||||||
'invoices.invoice_date',
|
|
||||||
'invoices.mark_no',
|
|
||||||
'containers.container_number',
|
|
||||||
'containers.container_date',
|
|
||||||
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
|
|
||||||
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
|
|
||||||
'invoices.final_amount',
|
|
||||||
'invoices.final_amount_with_gst',
|
|
||||||
'invoices.status as invoice_status'
|
|
||||||
)
|
|
||||||
->when($request->filled('search'), function ($q) use ($request) {
|
|
||||||
$search = trim($request->search);
|
|
||||||
$q->where(function ($qq) use ($search) {
|
|
||||||
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('invoices.mark_no', 'like', "%{$search}%")
|
|
||||||
->orWhere('containers.container_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.company_name', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
->when($request->filled('status'), function ($q) use ($request) {
|
|
||||||
$q->where('invoices.status', $request->status);
|
|
||||||
})
|
|
||||||
->when($request->filled('from_date'), function ($q) use ($request) {
|
|
||||||
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
|
|
||||||
})
|
|
||||||
->when($request->filled('to_date'), function ($q) use ($request) {
|
|
||||||
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
|
|
||||||
})
|
|
||||||
->orderByDesc('containers.container_date')
|
|
||||||
->orderByDesc('invoices.id')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return view('admin.pdf.invoices_excel', compact('invoices'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,35 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
use App\Models\SupportTicket;
|
||||||
|
use App\Models\ChatMessage;
|
||||||
|
use App\Events\NewChatMessage;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
=======
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\SupportTicket;
|
use App\Models\SupportTicket;
|
||||||
use App\Models\ChatMessage;
|
use App\Models\ChatMessage;
|
||||||
use App\Events\NewChatMessage;
|
use App\Events\NewChatMessage;
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
class AdminChatController extends Controller
|
class AdminChatController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
<<<<<<< HEAD
|
||||||
|
* Page 1: List all customer chat tickets
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$tickets = SupportTicket::with(['user', 'messages' => function($query) {
|
||||||
|
$query->latest()->limit(1);
|
||||||
|
}])
|
||||||
|
->orderBy('updated_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.chat_support', compact('tickets'));
|
||||||
|
}
|
||||||
|
=======
|
||||||
* Page 1: List all active user chats
|
* Page 1: List all active user chats
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
@@ -28,11 +49,26 @@ class AdminChatController extends Controller
|
|||||||
return view('admin.chat_support', compact('tickets'));
|
return view('admin.chat_support', compact('tickets'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page 2: Open chat window for a specific user
|
* Page 2: Open chat window for a specific user
|
||||||
*/
|
*/
|
||||||
public function openChat($ticketId)
|
public function openChat($ticketId)
|
||||||
|
<<<<<<< HEAD
|
||||||
|
{
|
||||||
|
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
||||||
|
$messages = ChatMessage::where('ticket_id', $ticketId)
|
||||||
|
->orderBy('created_at', 'asc')
|
||||||
|
->with('sender')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.chat_window', compact('ticket', 'messages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin sends a message to the user (FIXED - LIVE CHAT)
|
||||||
|
=======
|
||||||
{
|
{
|
||||||
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
||||||
|
|
||||||
@@ -53,6 +89,7 @@ class AdminChatController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin sends a message to the user
|
* Admin sends a message to the user
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
*/
|
*/
|
||||||
public function sendMessage(Request $request, $ticketId)
|
public function sendMessage(Request $request, $ticketId)
|
||||||
{
|
{
|
||||||
@@ -69,9 +106,12 @@ class AdminChatController extends Controller
|
|||||||
'sender_id' => $admin->id,
|
'sender_id' => $admin->id,
|
||||||
'sender_type' => \App\Models\Admin::class,
|
'sender_type' => \App\Models\Admin::class,
|
||||||
'message' => $request->message,
|
'message' => $request->message,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
'read_by_admin' => true,
|
'read_by_admin' => true,
|
||||||
'read_by_user' => false,
|
'read_by_user' => false,
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
];
|
];
|
||||||
|
|
||||||
// File Upload
|
// File Upload
|
||||||
@@ -85,11 +125,20 @@ class AdminChatController extends Controller
|
|||||||
$message = ChatMessage::create($data);
|
$message = ChatMessage::create($data);
|
||||||
$message->load('sender');
|
$message->load('sender');
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
\Log::info("DEBUG: ChatController sendMessage called", [
|
||||||
|
=======
|
||||||
\Log::info("DEBUG: ChatController sendMessage called", [
|
\Log::info("DEBUG: ChatController sendMessage called", [
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
'ticket_id' => $ticketId,
|
'ticket_id' => $ticketId,
|
||||||
'payload' => $request->all()
|
'payload' => $request->all()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 🔥 LIVE CHAT - Queue bypass (100% working)
|
||||||
|
broadcast(new NewChatMessage($message))->toOthers();
|
||||||
|
|
||||||
|
=======
|
||||||
// Broadcast real-time
|
// Broadcast real-time
|
||||||
broadcast(new NewChatMessage($message));
|
broadcast(new NewChatMessage($message));
|
||||||
|
|
||||||
@@ -97,6 +146,7 @@ class AdminChatController extends Controller
|
|||||||
'ticket_id' => $ticketId,
|
'ticket_id' => $ticketId,
|
||||||
'payload' => $request->all()
|
'payload' => $request->all()
|
||||||
]);
|
]);
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => $message
|
'message' => $message
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use App\Models\InvoiceInstallment;
|
use App\Models\InvoiceInstallment;
|
||||||
use App\Models\InvoiceChargeGroup;
|
|
||||||
use App\Models\InvoiceChargeGroupItem;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
@@ -17,106 +15,49 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// INVOICE LIST PAGE
|
// INVOICE LIST PAGE
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function index(Request $request)
|
public function index()
|
||||||
{
|
{
|
||||||
$query = Invoice::with(['items', 'customer', 'container']);
|
$invoices = Invoice::with(['items', 'customer', 'container'])
|
||||||
|
->latest()
|
||||||
if ($request->filled('search')) {
|
->get();
|
||||||
$search = $request->search;
|
|
||||||
$query->where(function ($q) use ($search) {
|
|
||||||
$q->where('invoice_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('customer_name', 'like', "%{$search}%");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('status') && $request->status !== 'all') {
|
|
||||||
$query->where('status', $request->status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('start_date')) {
|
|
||||||
$query->whereDate('invoice_date', '>=', $request->start_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('end_date')) {
|
|
||||||
$query->whereDate('invoice_date', '<=', $request->end_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoices = $query->latest()->get();
|
|
||||||
|
|
||||||
return view('admin.invoice', compact('invoices'));
|
return view('admin.invoice', compact('invoices'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// POPUP VIEW
|
// POPUP VIEW (AJAX)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function popup($id)
|
public function popup($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with([
|
$invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
|
||||||
'items',
|
$shipment = null;
|
||||||
'chargeGroups.items',
|
|
||||||
])->findOrFail($id);
|
|
||||||
|
|
||||||
$shipment = null;
|
return view('admin.popup_invoice', compact('invoice', 'shipment'));
|
||||||
|
}
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
|
||||||
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
|
||||||
->unique()
|
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// EDIT INVOICE PAGE
|
// EDIT INVOICE PAGE
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with([
|
$invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
|
||||||
'items',
|
|
||||||
'customer',
|
|
||||||
'container',
|
|
||||||
'chargeGroups.items',
|
|
||||||
'installments',
|
|
||||||
])->findOrFail($id);
|
|
||||||
|
|
||||||
// ✅ Customer details sync — जर test data आला असेल तर fix होईल
|
|
||||||
if ($invoice->customer) {
|
|
||||||
$needsUpdate = [];
|
|
||||||
|
|
||||||
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
|
|
||||||
$needsUpdate['customer_email'] = $invoice->customer->email;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
|
|
||||||
$needsUpdate['customer_address'] = $invoice->customer->address;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
|
|
||||||
$needsUpdate['pincode'] = $invoice->customer->pincode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($needsUpdate)) {
|
|
||||||
$invoice->update($needsUpdate);
|
|
||||||
$invoice->refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
// ADD THIS SECTION: Calculate customer's total due across all invoices
|
||||||
->flatMap(function ($group) {
|
$customerTotalDue = Invoice::where('customer_id', $invoice->customer_id)
|
||||||
return $group->items->pluck('invoice_item_id');
|
->where('status', '!=', 'cancelled')
|
||||||
})
|
->where('status', '!=', 'void')
|
||||||
->unique()
|
->sum('final_amount_with_gst');
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
|
// Pass the new variable to the view
|
||||||
|
return view('admin.invoice_edit', compact('invoice', 'shipment', 'customerTotalDue'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE (HEADER ONLY)
|
// UPDATE INVOICE (HEADER LEVEL)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// UPDATE INVOICE (HEADER LEVEL)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
@@ -127,34 +68,85 @@ class AdminInvoiceController extends Controller
|
|||||||
|
|
||||||
$invoice = Invoice::findOrFail($id);
|
$invoice = Invoice::findOrFail($id);
|
||||||
|
|
||||||
|
// 1) VALIDATION
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'invoice_date' => 'required|date',
|
'invoice_date' => 'required|date',
|
||||||
'due_date' => 'required|date|after_or_equal:invoice_date',
|
'due_date' => 'required|date|after_or_equal:invoice_date',
|
||||||
'status' => 'required|in:pending,paying,paid,overdue',
|
'final_amount' => 'required|numeric|min:0',
|
||||||
|
'tax_type' => 'required|in:gst,igst',
|
||||||
|
'tax_percent' => 'required|numeric|min:0|max:28',
|
||||||
|
'status' => 'required|in:pending,paid,overdue',
|
||||||
'notes' => 'nullable|string',
|
'notes' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Log::info('✅ Validated Invoice Header Update Data', $data);
|
Log::info('✅ Validated Invoice Update Data', $data);
|
||||||
|
|
||||||
$invoice->update($data);
|
// 2) CALCULATE GST / TOTALS
|
||||||
$invoice->refresh();
|
$finalAmount = (float) $data['final_amount'];
|
||||||
|
$taxPercent = (float) $data['tax_percent'];
|
||||||
|
|
||||||
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
|
if ($data['tax_type'] === 'gst') {
|
||||||
'invoice_id' => $invoice->id,
|
Log::info('🟢 GST Selected', compact('taxPercent'));
|
||||||
'charge_groups_total' => $invoice->charge_groups_total,
|
|
||||||
'gst_amount' => $invoice->gst_amount,
|
$data['cgst_percent'] = $taxPercent / 2;
|
||||||
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
$data['sgst_percent'] = $taxPercent / 2;
|
||||||
|
$data['igst_percent'] = 0;
|
||||||
|
} else {
|
||||||
|
Log::info('🔵 IGST Selected', compact('taxPercent'));
|
||||||
|
|
||||||
|
$data['cgst_percent'] = 0;
|
||||||
|
$data['sgst_percent'] = 0;
|
||||||
|
$data['igst_percent'] = $taxPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gstAmount = ($finalAmount * $taxPercent) / 100;
|
||||||
|
$data['gst_amount'] = $gstAmount;
|
||||||
|
$data['final_amount_with_gst'] = $finalAmount + $gstAmount;
|
||||||
|
$data['gst_percent'] = $taxPercent;
|
||||||
|
|
||||||
|
Log::info('📌 Final Calculated Invoice Values', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'final_amount' => $finalAmount,
|
||||||
|
'gst_amount' => $data['gst_amount'],
|
||||||
|
'final_amount_with_gst' => $data['final_amount_with_gst'],
|
||||||
|
'tax_type' => $data['tax_type'],
|
||||||
|
'cgst_percent' => $data['cgst_percent'],
|
||||||
|
'sgst_percent' => $data['sgst_percent'],
|
||||||
|
'igst_percent' => $data['igst_percent'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 3) UPDATE DB
|
||||||
|
$invoice->update($data);
|
||||||
|
|
||||||
|
Log::info('✅ Invoice Updated Successfully', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 4) LOG ACTUAL DB VALUES
|
||||||
|
$invoice->refresh();
|
||||||
|
Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'final_amount' => $invoice->final_amount,
|
||||||
|
'gst_percent' => $invoice->gst_percent,
|
||||||
|
'gst_amount' => $invoice->gst_amount,
|
||||||
|
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
||||||
|
'tax_type' => $invoice->tax_type,
|
||||||
|
'cgst_percent' => $invoice->cgst_percent,
|
||||||
|
'sgst_percent' => $invoice->sgst_percent,
|
||||||
|
'igst_percent' => $invoice->igst_percent,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 5) REGENERATE PDF
|
||||||
$this->generateInvoicePDF($invoice);
|
$this->generateInvoicePDF($invoice);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->route('admin.invoices.edit', $invoice->id)
|
->route('admin.invoices.index')
|
||||||
->with('success', 'Invoice updated & PDF generated successfully.');
|
->with('success', 'Invoice updated & PDF generated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE ITEMS (फक्त items save)
|
// 🔹 UPDATE INVOICE ITEMS (price + ttl_amount)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function updateItems(Request $request, Invoice $invoice)
|
public function updateItems(Request $request, Invoice $invoice)
|
||||||
{
|
{
|
||||||
@@ -169,7 +161,9 @@ class AdminInvoiceController extends Controller
|
|||||||
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($data['items'] as $itemId => $itemData) {
|
$itemsInput = $data['items'];
|
||||||
|
|
||||||
|
foreach ($itemsInput as $itemId => $itemData) {
|
||||||
$item = InvoiceItem::where('id', $itemId)
|
$item = InvoiceItem::where('id', $itemId)
|
||||||
->where('invoice_id', $invoice->id)
|
->where('invoice_id', $invoice->id)
|
||||||
->first();
|
->first();
|
||||||
@@ -187,15 +181,46 @@ class AdminInvoiceController extends Controller
|
|||||||
$item->save();
|
$item->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info('✅ Invoice items updated (no totals recalculation)', [
|
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
|
||||||
'invoice_id' => $invoice->id,
|
->sum('ttl_amount');
|
||||||
|
|
||||||
|
$taxType = $invoice->tax_type;
|
||||||
|
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
|
||||||
|
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
|
||||||
|
$igstPercent = (float) ($invoice->igst_percent ?? 0);
|
||||||
|
|
||||||
|
$gstPercent = 0;
|
||||||
|
if ($taxType === 'gst') {
|
||||||
|
$gstPercent = $cgstPercent + $sgstPercent;
|
||||||
|
} elseif ($taxType === 'igst') {
|
||||||
|
$gstPercent = $igstPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gstAmount = $newBaseAmount * $gstPercent / 100;
|
||||||
|
$finalWithGst = $newBaseAmount + $gstAmount;
|
||||||
|
|
||||||
|
$invoice->final_amount = $newBaseAmount;
|
||||||
|
$invoice->gst_amount = $gstAmount;
|
||||||
|
$invoice->final_amount_with_gst = $finalWithGst;
|
||||||
|
$invoice->gst_percent = $gstPercent;
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
Log::info('✅ Invoice items updated & totals recalculated', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'final_amount' => $invoice->final_amount,
|
||||||
|
'gst_amount' => $invoice->gst_amount,
|
||||||
|
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
||||||
|
'tax_type' => $invoice->tax_type,
|
||||||
|
'cgst_percent' => $invoice->cgst_percent,
|
||||||
|
'sgst_percent' => $invoice->sgst_percent,
|
||||||
|
'igst_percent' => $invoice->igst_percent,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return back()->with('success', 'Invoice items updated successfully.');
|
return back()->with('success', 'Invoice items updated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// PDF GENERATION
|
// PDF GENERATION USING mPDF
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function generateInvoicePDF($invoice)
|
public function generateInvoicePDF($invoice)
|
||||||
{
|
{
|
||||||
@@ -232,6 +257,17 @@ class AdminInvoiceController extends Controller
|
|||||||
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
|
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function downloadInvoice($id)
|
||||||
|
{
|
||||||
|
$invoice = Invoice::findOrFail($id);
|
||||||
|
|
||||||
|
// ALWAYS regenerate to reflect latest HTML/CSS
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
|
$invoice->refresh();
|
||||||
|
|
||||||
|
return response()->download(public_path($invoice->pdf_path));
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// INSTALLMENTS (ADD)
|
// INSTALLMENTS (ADD)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -244,12 +280,9 @@ class AdminInvoiceController extends Controller
|
|||||||
'amount' => 'required|numeric|min:1',
|
'amount' => 'required|numeric|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invoice = Invoice::findOrFail($invoice_id);
|
$invoice = Invoice::findOrFail($invoice_id);
|
||||||
|
|
||||||
$grandTotal = $invoice->grand_total_with_charges ?? 0;
|
|
||||||
|
|
||||||
$paidTotal = $invoice->installments()->sum('amount');
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
$remaining = $grandTotal - $paidTotal;
|
$remaining = $invoice->final_amount_with_gst - $paidTotal;
|
||||||
|
|
||||||
if ($request->amount > $remaining) {
|
if ($request->amount > $remaining) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -266,24 +299,24 @@ class AdminInvoiceController extends Controller
|
|||||||
'amount' => $request->amount,
|
'amount' => $request->amount,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newPaid = $paidTotal + $request->amount;
|
$newPaid = $paidTotal + $request->amount;
|
||||||
$remaining = max(0, $grandTotal - $newPaid);
|
|
||||||
|
|
||||||
if ($newPaid >= $grandTotal && $grandTotal > 0) {
|
if ($newPaid >= $invoice->final_amount_with_gst) {
|
||||||
$invoice->update(['status' => 'paid']);
|
$invoice->update(['status' => 'paid']);
|
||||||
|
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Installment added successfully.',
|
'message' => 'Installment added successfully.',
|
||||||
'installment' => $installment,
|
'installment' => $installment,
|
||||||
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
'totalPaid' => $newPaid,
|
||||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
'gstAmount' => $invoice->gst_amount,
|
||||||
'grandTotal' => $grandTotal,
|
'finalAmountWithGst' => $invoice->final_amount_with_gst,
|
||||||
'totalPaid' => $newPaid,
|
'baseAmount' => $invoice->final_amount,
|
||||||
'remaining' => $remaining,
|
'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
|
||||||
'isCompleted' => $remaining <= 0,
|
'isCompleted' => $newPaid >= $invoice->final_amount_with_gst,
|
||||||
'isZero' => $newPaid == 0,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,158 +329,25 @@ class AdminInvoiceController extends Controller
|
|||||||
$invoice = $installment->invoice;
|
$invoice = $installment->invoice;
|
||||||
|
|
||||||
$installment->delete();
|
$installment->delete();
|
||||||
$invoice->refresh();
|
|
||||||
|
|
||||||
$grandTotal = $invoice->grand_total_with_charges ?? 0;
|
|
||||||
|
|
||||||
$paidTotal = $invoice->installments()->sum('amount');
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
$remaining = max(0, $grandTotal - $paidTotal);
|
$remaining = $invoice->final_amount_with_gst - $paidTotal;
|
||||||
|
|
||||||
if ($paidTotal <= 0 && $grandTotal > 0) {
|
if ($remaining > 0 && $invoice->status === 'paid') {
|
||||||
$invoice->update(['status' => 'pending']);
|
|
||||||
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
|
|
||||||
$invoice->update(['status' => 'pending']);
|
$invoice->update(['status' => 'pending']);
|
||||||
|
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Installment deleted.',
|
'message' => 'Installment deleted.',
|
||||||
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
'totalPaid' => $paidTotal,
|
||||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
'gstAmount' => $invoice->gst_amount,
|
||||||
'grandTotal' => $grandTotal,
|
'finalAmountWithGst' => $invoice->final_amount_with_gst,
|
||||||
'totalPaid' => $paidTotal,
|
'baseAmount' => $invoice->final_amount,
|
||||||
'remaining' => $remaining,
|
'remaining' => $remaining,
|
||||||
'isZero' => $paidTotal == 0,
|
'isZero' => $paidTotal == 0,
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
// CHARGE GROUP SAVE
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
public function storeChargeGroup(Request $request, $invoiceId)
|
|
||||||
{
|
|
||||||
Log::info('🟡 storeChargeGroup HIT', [
|
|
||||||
'invoice_id' => $invoiceId,
|
|
||||||
'payload' => $request->all(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
|
|
||||||
|
|
||||||
$data = $request->validate([
|
|
||||||
'groupname' => 'required|string|max:255',
|
|
||||||
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
|
|
||||||
'basisvalue' => 'required|numeric',
|
|
||||||
'rate' => 'required|numeric|min:0.0001',
|
|
||||||
'autototal' => 'required|numeric|min:0.01',
|
|
||||||
'itemids' => 'required|array',
|
|
||||||
'itemids.*' => 'integer|exists:invoice_items,id',
|
|
||||||
|
|
||||||
'tax_type' => 'nullable|in:none,gst,igst',
|
|
||||||
'gst_percent' => 'nullable|numeric|min:0|max:28',
|
|
||||||
'total_with_gst' => 'nullable|numeric|min:0',
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info('✅ storeChargeGroup VALIDATED', $data);
|
|
||||||
|
|
||||||
// duplicate name check
|
|
||||||
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
|
|
||||||
->where('group_name', $data['groupname'])
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
if ($exists) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
$taxType = $data['tax_type'] ?? 'gst';
|
|
||||||
$gstPercent = $data['gst_percent'] ?? 0;
|
|
||||||
$baseTotal = $data['autototal'];
|
|
||||||
|
|
||||||
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
|
|
||||||
if ($totalWithGst == 0 && $gstPercent > 0) {
|
|
||||||
$gstAmount = ($baseTotal * $gstPercent) / 100;
|
|
||||||
$totalWithGst = $baseTotal + $gstAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1) Group create
|
|
||||||
$group = InvoiceChargeGroup::create([
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'group_name' => $data['groupname'],
|
|
||||||
'basis_type' => $data['basistype'],
|
|
||||||
'basis_value' => $data['basisvalue'],
|
|
||||||
'rate' => $data['rate'],
|
|
||||||
'total_charge' => $baseTotal,
|
|
||||||
'tax_type' => $taxType,
|
|
||||||
'gst_percent' => $gstPercent,
|
|
||||||
'total_with_gst' => $totalWithGst,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 2) Items link
|
|
||||||
foreach ($data['itemids'] as $itemId) {
|
|
||||||
InvoiceChargeGroupItem::create([
|
|
||||||
'group_id' => $group->id,
|
|
||||||
'invoice_item_id' => $itemId,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) सर्व groups वरून invoice level totals
|
|
||||||
$invoice->load('chargeGroups');
|
|
||||||
|
|
||||||
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base
|
|
||||||
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst
|
|
||||||
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only
|
|
||||||
|
|
||||||
$invoiceGstPercent = $group->gst_percent ?? 0;
|
|
||||||
$invoiceTaxType = $group->tax_type ?? 'gst';
|
|
||||||
|
|
||||||
$cgstPercent = 0;
|
|
||||||
$sgstPercent = 0;
|
|
||||||
$igstPercent = 0;
|
|
||||||
|
|
||||||
if ($invoiceTaxType === 'gst') {
|
|
||||||
$cgstPercent = $invoiceGstPercent / 2;
|
|
||||||
$sgstPercent = $invoiceGstPercent / 2;
|
|
||||||
} elseif ($invoiceTaxType === 'igst') {
|
|
||||||
$igstPercent = $invoiceGstPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔴 इथे main fix:
|
|
||||||
// final_amount = base (total_charge sum)
|
|
||||||
// final_amount_with_gst = base + gst (total_with_gst sum)
|
|
||||||
// grand_total_with_charges = final_amount_with_gst (same)
|
|
||||||
$invoice->update([
|
|
||||||
'charge_groups_total' => $chargeGroupsBase,
|
|
||||||
'gst_amount' => $chargeGroupsGst,
|
|
||||||
'gst_percent' => $invoiceGstPercent,
|
|
||||||
'tax_type' => $invoiceTaxType,
|
|
||||||
'cgst_percent' => $cgstPercent,
|
|
||||||
'sgst_percent' => $sgstPercent,
|
|
||||||
'igst_percent' => $igstPercent,
|
|
||||||
|
|
||||||
'final_amount' => $chargeGroupsBase,
|
|
||||||
'final_amount_with_gst' => $chargeGroupsWithG,
|
|
||||||
'grand_total_with_charges' => $chargeGroupsWithG,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'charge_groups_total' => $chargeGroupsBase,
|
|
||||||
'gst_amount' => $chargeGroupsGst,
|
|
||||||
'gst_percent' => $invoiceGstPercent,
|
|
||||||
'tax_type' => $invoiceTaxType,
|
|
||||||
'cgst_percent' => $cgstPercent,
|
|
||||||
'sgst_percent' => $sgstPercent,
|
|
||||||
'igst_percent' => $igstPercent,
|
|
||||||
'final_amount' => $invoice->final_amount,
|
|
||||||
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
|
||||||
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Charge group saved successfully.',
|
|
||||||
'group_id' => $group->id,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,91 +10,26 @@ use App\Models\MarkList;
|
|||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Container;
|
|
||||||
use App\Models\Admin;
|
|
||||||
use App\Models\Shipment;
|
|
||||||
use PDF;
|
use PDF;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use App\Exports\OrdersExport;
|
use App\Exports\OrdersExport;
|
||||||
use App\Imports\OrderItemsPreviewImport;
|
use App\Imports\OrderItemsPreviewImport;
|
||||||
|
|
||||||
|
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use App\Exports\InvoicesExport;
|
|
||||||
|
|
||||||
class AdminOrderController extends Controller
|
class AdminOrderController extends Controller
|
||||||
{
|
{
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
* DASHBOARD (old UI: stats + recent orders)
|
* LIST / DASHBOARD
|
||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function dashboard()
|
public function index()
|
||||||
{
|
{
|
||||||
$totalOrders = Order::count();
|
|
||||||
$pendingOrders = Order::where('status', 'pending')->count();
|
|
||||||
$totalShipments = Shipment::count();
|
|
||||||
$totalItems = OrderItem::count();
|
|
||||||
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
|
||||||
$activeCustomers = User::where('status', 'active')->count();
|
|
||||||
$inactiveCustomers = User::where('status', 'inactive')->count();
|
|
||||||
$totalStaff = Admin::where('type', 'staff')->count();
|
|
||||||
|
|
||||||
$markList = MarkList::where('status', 'active')->get();
|
|
||||||
$orders = Order::latest()->get();
|
$orders = Order::latest()->get();
|
||||||
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
|
|
||||||
return view('admin.dashboard', compact(
|
return view('admin.dashboard', compact('orders', 'markList'));
|
||||||
'totalOrders',
|
|
||||||
'pendingOrders',
|
|
||||||
'totalShipments',
|
|
||||||
'totalItems',
|
|
||||||
'totalRevenue',
|
|
||||||
'activeCustomers',
|
|
||||||
'inactiveCustomers',
|
|
||||||
'totalStaff',
|
|
||||||
'orders',
|
|
||||||
'markList'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------
|
|
||||||
* LIST (new: Invoices Management for Orders page)
|
|
||||||
* ---------------------------*/
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
|
||||||
$invoices = DB::table('invoices')
|
|
||||||
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
|
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
|
|
||||||
->select(
|
|
||||||
'invoices.id',
|
|
||||||
'invoices.invoice_number',
|
|
||||||
'invoices.invoice_date',
|
|
||||||
'invoices.final_amount',
|
|
||||||
'invoices.final_amount_with_gst',
|
|
||||||
'invoices.status as invoice_status',
|
|
||||||
'invoices.mark_no',
|
|
||||||
'invoices.container_id', // <<< हे नक्की घाल
|
|
||||||
'containers.container_number',
|
|
||||||
'containers.container_date',
|
|
||||||
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
|
|
||||||
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
|
|
||||||
)
|
|
||||||
|
|
||||||
->when($request->filled('search'), function ($q) use ($request) {
|
|
||||||
$search = trim($request->search);
|
|
||||||
$q->where(function ($qq) use ($search) {
|
|
||||||
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('containers.container_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('invoices.mark_no', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.company_name', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
->when($request->filled('status'), function ($q) use ($request) {
|
|
||||||
$q->where('invoices.status', $request->status);
|
|
||||||
})
|
|
||||||
->orderByDesc('invoices.invoice_date') // इथे बदल
|
|
||||||
->orderByDesc('invoices.id') // same-date साठी tie-breaker
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return view('admin.orders', compact('invoices'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
@@ -106,9 +41,52 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.orders_create', compact('markList'));
|
return view('admin.orders_create', compact('markList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/**
|
||||||
* SHOW / POPUP
|
* Store a new order and optionally create initial invoice
|
||||||
* ---------------------------*/
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate([
|
||||||
|
'mark_no' => 'required|string',
|
||||||
|
'origin' => 'nullable|string',
|
||||||
|
'destination' => 'nullable|string',
|
||||||
|
// totals optional when creating without items
|
||||||
|
'ctn' => 'nullable|numeric',
|
||||||
|
'qty' => 'nullable|numeric',
|
||||||
|
'ttl_qty' => 'nullable|numeric',
|
||||||
|
'ttl_amount' => 'nullable|numeric',
|
||||||
|
'cbm' => 'nullable|numeric',
|
||||||
|
'ttl_cbm' => 'nullable|numeric',
|
||||||
|
'kg' => 'nullable|numeric',
|
||||||
|
'ttl_kg' => 'nullable|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$order = Order::create([
|
||||||
|
'order_id' => $this->generateOrderId(),
|
||||||
|
'mark_no' => $data['mark_no'],
|
||||||
|
'origin' => $data['origin'] ?? null,
|
||||||
|
'destination' => $data['destination'] ?? null,
|
||||||
|
'ctn' => $data['ctn'] ?? 0,
|
||||||
|
'qty' => $data['qty'] ?? 0,
|
||||||
|
'ttl_qty' => $data['ttl_qty'] ?? 0,
|
||||||
|
'ttl_amount' => $data['ttl_amount'] ?? 0,
|
||||||
|
'cbm' => $data['cbm'] ?? 0,
|
||||||
|
'ttl_cbm' => $data['ttl_cbm'] ?? 0,
|
||||||
|
'kg' => $data['kg'] ?? 0,
|
||||||
|
'ttl_kg' => $data['ttl_kg'] ?? 0,
|
||||||
|
'status' => 'pending',
|
||||||
|
]);
|
||||||
|
|
||||||
|
//If you want to auto-create an invoice at order creation, uncomment:
|
||||||
|
// $this->createInvoice($order);
|
||||||
|
|
||||||
|
return redirect()->route('admin.orders.show', $order->id)
|
||||||
|
->with('success', 'Order created successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// SHOW / POPUP
|
||||||
|
// ---------------------------
|
||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
$order = Order::with('items', 'markList')->findOrFail($id);
|
$order = Order::with('items', 'markList')->findOrFail($id);
|
||||||
@@ -126,46 +104,57 @@ class AdminOrderController extends Controller
|
|||||||
$user = User::where('customer_id', $order->markList->customer_id)->first();
|
$user = User::where('customer_id', $order->markList->customer_id)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.popup', compact('order', 'user'));
|
$data['order_id'] = $order->id;
|
||||||
|
|
||||||
|
OrderItem::create($data);
|
||||||
|
|
||||||
|
// recalc totals and save to order
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
// $this->updateInvoiceFromOrder($order); // <-- NEW
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
* ORDER ITEM MANAGEMENT (existing orders)
|
* ORDER ITEM MANAGEMENT (existing orders)
|
||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function addItem(Request $request, $orderId)
|
public function addItem(Request $request, $orderId)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($orderId);
|
$order = Order::findOrFail($orderId);
|
||||||
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'description' => 'required|string',
|
'description' => 'required|string',
|
||||||
'ctn' => 'nullable|numeric',
|
'ctn' => 'nullable|numeric',
|
||||||
'qty' => 'nullable|numeric',
|
'qty' => 'nullable|numeric',
|
||||||
'unit' => 'nullable|string',
|
'unit' => 'nullable|string',
|
||||||
'price' => 'nullable|numeric',
|
'price' => 'nullable|numeric',
|
||||||
'cbm' => 'nullable|numeric',
|
'cbm' => 'nullable|numeric',
|
||||||
'kg' => 'nullable|numeric',
|
'kg' => 'nullable|numeric',
|
||||||
'shop_no' => 'nullable|string',
|
'shop_no' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ctn = (float) ($data['ctn'] ?? 0);
|
// ✅ BACKEND CALCULATION
|
||||||
$qty = (float) ($data['qty'] ?? 0);
|
$ctn = (float) ($data['ctn'] ?? 0);
|
||||||
$price = (float) ($data['price'] ?? 0);
|
$qty = (float) ($data['qty'] ?? 0);
|
||||||
$cbm = (float) ($data['cbm'] ?? 0);
|
$price = (float) ($data['price'] ?? 0);
|
||||||
$kg = (float) ($data['kg'] ?? 0);
|
$cbm = (float) ($data['cbm'] ?? 0);
|
||||||
|
$kg = (float) ($data['kg'] ?? 0);
|
||||||
|
|
||||||
$data['ttl_qty'] = $ctn * $qty;
|
$data['ttl_qty'] = $ctn * $qty;
|
||||||
$data['ttl_amount'] = $data['ttl_qty'] * $price;
|
$data['ttl_amount'] = $data['ttl_qty'] * $price;
|
||||||
$data['ttl_cbm'] = $cbm * $ctn;
|
$data['ttl_cbm'] = $cbm * $ctn;
|
||||||
$data['ttl_kg'] = $ctn * $kg;
|
$data['ttl_kg'] = $ctn * $kg;
|
||||||
|
|
||||||
$data['order_id'] = $order->id;
|
$data['order_id'] = $order->id;
|
||||||
|
|
||||||
OrderItem::create($data);
|
OrderItem::create($data);
|
||||||
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteItem($id)
|
public function deleteItem($id)
|
||||||
{
|
{
|
||||||
@@ -175,6 +164,7 @@ class AdminOrderController extends Controller
|
|||||||
$item->delete();
|
$item->delete();
|
||||||
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
||||||
}
|
}
|
||||||
@@ -187,6 +177,7 @@ class AdminOrderController extends Controller
|
|||||||
$item->restore();
|
$item->restore();
|
||||||
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||||
}
|
}
|
||||||
@@ -235,14 +226,14 @@ class AdminOrderController extends Controller
|
|||||||
$items = $order->items()->get();
|
$items = $order->items()->get();
|
||||||
|
|
||||||
$order->update([
|
$order->update([
|
||||||
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
||||||
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
||||||
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
||||||
'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
||||||
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
||||||
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
||||||
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
||||||
'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
|
'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,6 +336,7 @@ class AdminOrderController extends Controller
|
|||||||
$order = Order::with([
|
$order = Order::with([
|
||||||
'markList',
|
'markList',
|
||||||
'items',
|
'items',
|
||||||
|
'invoice.items',
|
||||||
'shipments' => function ($q) use ($id) {
|
'shipments' => function ($q) use ($id) {
|
||||||
$q->whereHas('orders', function ($oq) use ($id) {
|
$q->whereHas('orders', function ($oq) use ($id) {
|
||||||
$oq->where('orders.id', $id)
|
$oq->where('orders.id', $id)
|
||||||
@@ -363,14 +355,14 @@ class AdminOrderController extends Controller
|
|||||||
'order_id' => $order->order_id,
|
'order_id' => $order->order_id,
|
||||||
'status' => $order->status,
|
'status' => $order->status,
|
||||||
'totals' => [
|
'totals' => [
|
||||||
'ctn' => $order->ctn,
|
'ctn' => $order->ctn,
|
||||||
'qty' => $order->qty,
|
'qty' => $order->qty,
|
||||||
'ttl_qty' => $order->ttl_qty,
|
'ttl_qty' => $order->ttl_qty,
|
||||||
'cbm' => $order->cbm,
|
'cbm' => $order->cbm,
|
||||||
'ttl_cbm' => $order->ttl_cbm,
|
'ttl_cbm' => $order->ttl_cbm,
|
||||||
'kg' => $order->kg,
|
'kg' => $order->kg,
|
||||||
'ttl_kg' => $order->ttl_kg,
|
'ttl_kg' => $order->ttl_kg,
|
||||||
'amount' => $order->ttl_amount,
|
'amount' => $order->ttl_amount,
|
||||||
],
|
],
|
||||||
'items' => $order->items,
|
'items' => $order->items,
|
||||||
];
|
];
|
||||||
@@ -424,6 +416,29 @@ class AdminOrderController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$invoiceData = null;
|
$invoiceData = null;
|
||||||
|
if ($order->invoice) {
|
||||||
|
$invoice = $order->invoice;
|
||||||
|
$invoiceData = [
|
||||||
|
'invoice_no' => $invoice->invoice_number,
|
||||||
|
'status' => $invoice->status,
|
||||||
|
'invoice_date' => $invoice->invoice_date,
|
||||||
|
'due_date' => $invoice->due_date,
|
||||||
|
'customer' => [
|
||||||
|
'name' => $invoice->customer_name,
|
||||||
|
'mobile' => $invoice->customer_mobile,
|
||||||
|
'email' => $invoice->customer_email,
|
||||||
|
'address' => $invoice->customer_address,
|
||||||
|
'pincode' => $invoice->pincode,
|
||||||
|
],
|
||||||
|
'items' => $invoice->items,
|
||||||
|
'summary' => [
|
||||||
|
'amount' => $invoice->final_amount,
|
||||||
|
'cgst' => 0,
|
||||||
|
'sgst' => 0,
|
||||||
|
'total' => $invoice->final_amount_with_gst,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return view('admin.see_order', compact(
|
return view('admin.see_order', compact(
|
||||||
'order',
|
'order',
|
||||||
@@ -434,13 +449,14 @@ class AdminOrderController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
* FILTERED LIST + EXPORTS (old orders listing)
|
* FILTERED LIST + EXPORTS
|
||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function orderShow()
|
public function orderShow()
|
||||||
{
|
{
|
||||||
$orders = Order::with([
|
$orders = Order::with([
|
||||||
'markList',
|
'markList',
|
||||||
'shipments',
|
'shipments',
|
||||||
|
'invoice'
|
||||||
])->latest('id')->get();
|
])->latest('id')->get();
|
||||||
|
|
||||||
return view('admin.orders', compact('orders'));
|
return view('admin.orders', compact('orders'));
|
||||||
@@ -449,7 +465,7 @@ class AdminOrderController extends Controller
|
|||||||
private function buildOrdersQueryFromRequest(Request $request)
|
private function buildOrdersQueryFromRequest(Request $request)
|
||||||
{
|
{
|
||||||
$query = Order::query()
|
$query = Order::query()
|
||||||
->with(['markList', 'shipments']);
|
->with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$search = trim($request->search);
|
$search = trim($request->search);
|
||||||
@@ -462,12 +478,23 @@ class AdminOrderController extends Controller
|
|||||||
->orWhere('origin', 'like', "%{$search}%")
|
->orWhere('origin', 'like', "%{$search}%")
|
||||||
->orWhere('destination', 'like', "%{$search}%");
|
->orWhere('destination', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
|
->orWhereHas('invoice', function ($q3) use ($search) {
|
||||||
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
->orWhereHas('shipments', function ($q4) use ($search) {
|
->orWhereHas('shipments', function ($q4) use ($search) {
|
||||||
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$query->where(function ($q) use ($request) {
|
||||||
|
$q->whereHas('invoice', function ($q2) use ($request) {
|
||||||
|
$q2->where('status', $request->status);
|
||||||
|
})->orWhereDoesntHave('invoice');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('shipment')) {
|
if ($request->filled('shipment')) {
|
||||||
$query->where(function ($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->whereHas('shipments', function ($q2) use ($request) {
|
$q->whereHas('shipments', function ($q2) use ($request) {
|
||||||
@@ -489,83 +516,62 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
public function downloadPdf(Request $request)
|
public function downloadPdf(Request $request)
|
||||||
{
|
{
|
||||||
$invoices = DB::table('invoices')
|
$orders = $this->buildOrdersQueryFromRequest($request)->get();
|
||||||
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
|
$filters = [
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
|
'search' => $request->search,
|
||||||
->select(
|
'status' => $request->status,
|
||||||
'invoices.invoice_number',
|
'shipment' => $request->shipment,
|
||||||
'invoices.invoice_date',
|
'from' => $request->from_date,
|
||||||
'invoices.mark_no',
|
'to' => $request->to_date,
|
||||||
'containers.container_number',
|
];
|
||||||
'containers.container_date',
|
|
||||||
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
|
|
||||||
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
|
|
||||||
'invoices.final_amount',
|
|
||||||
'invoices.final_amount_with_gst',
|
|
||||||
'invoices.status as invoice_status'
|
|
||||||
)
|
|
||||||
->when($request->filled('search'), function ($q) use ($request) {
|
|
||||||
$search = trim($request->search);
|
|
||||||
$q->where(function ($qq) use ($search) {
|
|
||||||
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('invoices.mark_no', 'like', "%{$search}%")
|
|
||||||
->orWhere('containers.container_number', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.company_name', 'like', "%{$search}%")
|
|
||||||
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
->when($request->filled('status'), function ($q) use ($request) {
|
|
||||||
$q->where('invoices.status', $request->status);
|
|
||||||
})
|
|
||||||
->when($request->filled('from_date'), function ($q) use ($request) {
|
|
||||||
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
|
|
||||||
})
|
|
||||||
->when($request->filled('to_date'), function ($q) use ($request) {
|
|
||||||
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
|
|
||||||
})
|
|
||||||
->orderByDesc('containers.container_date')
|
|
||||||
->orderByDesc('invoices.id')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$pdf = PDF::loadView('admin.pdf.invoices_report', compact('invoices'))
|
$pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
|
||||||
->setPaper('a4', 'landscape');
|
->setPaper('a4', 'landscape');
|
||||||
|
|
||||||
return $pdf->download(
|
return $pdf->download(
|
||||||
'invoices-report-' . now()->format('Y-m-d') . '.pdf'
|
'orders-report-' . now()->format('Y-m-d') . '.pdf'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadExcel(Request $request)
|
public function downloadExcel(Request $request)
|
||||||
{
|
{
|
||||||
return Excel::download(
|
return Excel::download(
|
||||||
new InvoicesExport($request),
|
new OrdersExport($request),
|
||||||
'invoices-report-' . now()->format('Y-m-d') . '.xlsx'
|
'orders-report-' . now()->format('Y-m-d') . '.xlsx'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------
|
/* --------------------------------------------------
|
||||||
* NEW: Create Order + Invoice directly from popup
|
* NEW: Create Order + Invoice directly from popup
|
||||||
|
* route: admin.orders.temp.add (Create New Order form)
|
||||||
* --------------------------------------------------*/
|
* --------------------------------------------------*/
|
||||||
public function addTempItem(Request $request)
|
public function addTempItem(Request $request)
|
||||||
{
|
{
|
||||||
|
// 1) order-level fields
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'mark_no' => 'required',
|
'mark_no' => 'required',
|
||||||
'origin' => 'nullable',
|
'origin' => 'required',
|
||||||
'destination' => 'nullable',
|
'destination' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 2) multi-row items
|
||||||
$items = $request->validate([
|
$items = $request->validate([
|
||||||
'items' => 'required|array',
|
'items' => 'required|array',
|
||||||
'items.*.description' => 'required|string',
|
'items.*.description' => 'required|string',
|
||||||
'items.*.ctn' => 'nullable|numeric',
|
'items.*.ctn' => 'nullable|numeric',
|
||||||
'items.*.qty' => 'nullable|numeric',
|
'items.*.qty' => 'nullable|numeric',
|
||||||
|
|
||||||
'items.*.unit' => 'nullable|string',
|
'items.*.unit' => 'nullable|string',
|
||||||
'items.*.price' => 'nullable|numeric',
|
'items.*.price' => 'nullable|numeric',
|
||||||
|
|
||||||
'items.*.cbm' => 'nullable|numeric',
|
'items.*.cbm' => 'nullable|numeric',
|
||||||
|
|
||||||
'items.*.kg' => 'nullable|numeric',
|
'items.*.kg' => 'nullable|numeric',
|
||||||
|
|
||||||
'items.*.shop_no' => 'nullable|string',
|
'items.*.shop_no' => 'nullable|string',
|
||||||
])['items'];
|
])['items'];
|
||||||
|
|
||||||
|
// रिकामे rows काढा
|
||||||
$items = array_filter($items, function ($row) {
|
$items = array_filter($items, function ($row) {
|
||||||
return trim($row['description'] ?? '') !== '';
|
return trim($row['description'] ?? '') !== '';
|
||||||
});
|
});
|
||||||
@@ -574,31 +580,38 @@ class AdminOrderController extends Controller
|
|||||||
return back()->with('error', 'Add at least one item.');
|
return back()->with('error', 'Add at least one item.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
|
||||||
foreach ($items as &$item) {
|
foreach ($items as &$item) {
|
||||||
|
|
||||||
$ctn = (float) ($item['ctn'] ?? 0);
|
$ctn = (float) ($item['ctn'] ?? 0);
|
||||||
$qty = (float) ($item['qty'] ?? 0);
|
$qty = (float) ($item['qty'] ?? 0);
|
||||||
$price = (float) ($item['price'] ?? 0);
|
$price = (float) ($item['price'] ?? 0);
|
||||||
$cbm = (float) ($item['cbm'] ?? 0);
|
$cbm = (float) ($item['cbm'] ?? 0);
|
||||||
$kg = (float) ($item['kg'] ?? 0);
|
$kg = (float) ($item['kg'] ?? 0);
|
||||||
|
|
||||||
|
// Calculated fields
|
||||||
$item['ttl_qty'] = $ctn * $qty;
|
$item['ttl_qty'] = $ctn * $qty;
|
||||||
$item['ttl_amount'] = $item['ttl_qty'] * $price;
|
$item['ttl_amount'] = $item['ttl_qty'] * $price;
|
||||||
$item['ttl_cbm'] = $cbm * $ctn;
|
$item['ttl_cbm'] = $cbm * $ctn;
|
||||||
$item['ttl_kg'] = $ctn * $kg;
|
$item['ttl_kg'] = $ctn * $kg;
|
||||||
}
|
}
|
||||||
unset($item);
|
unset($item); // VERY IMPORTANT
|
||||||
|
|
||||||
$total_ctn = array_sum(array_column($items, 'ctn'));
|
|
||||||
$total_qty = array_sum(array_column($items, 'qty'));
|
|
||||||
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
|
||||||
$total_amount = array_sum(array_column($items, 'ttl_amount'));
|
|
||||||
$total_cbm = array_sum(array_column($items, 'cbm'));
|
|
||||||
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
|
|
||||||
$total_kg = array_sum(array_column($items, 'kg'));
|
|
||||||
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
|
||||||
|
|
||||||
|
// 3) totals
|
||||||
|
$total_ctn = array_sum(array_column($items, 'ctn'));
|
||||||
|
$total_qty = array_sum(array_column($items, 'qty'));
|
||||||
|
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
||||||
|
$total_amount = array_sum(array_column($items, 'ttl_amount'));
|
||||||
|
$total_cbm = array_sum(array_column($items, 'cbm'));
|
||||||
|
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
|
||||||
|
$total_kg = array_sum(array_column($items, 'kg'));
|
||||||
|
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
||||||
|
|
||||||
|
// 4) order id generate
|
||||||
$orderId = $this->generateOrderId();
|
$orderId = $this->generateOrderId();
|
||||||
|
|
||||||
|
// 5) order create
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'order_id' => $orderId,
|
'order_id' => $orderId,
|
||||||
'mark_no' => $request->mark_no,
|
'mark_no' => $request->mark_no,
|
||||||
@@ -615,6 +628,7 @@ class AdminOrderController extends Controller
|
|||||||
'status' => 'order_placed',
|
'status' => 'order_placed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 6) order items
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
OrderItem::create([
|
OrderItem::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
@@ -633,37 +647,149 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7) invoice number
|
||||||
$invoiceNumber = $this->generateInvoiceNumber();
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
|
|
||||||
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
// 1. Auto-generate invoice number
|
||||||
$customer = null;
|
// $lastInvoice = \App\Models\Invoice::latest()->first();
|
||||||
if ($markList && $markList->customer_id) {
|
// $nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
|
||||||
$customer = User::where('customer_id', $markList->customer_id)->first();
|
// $invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
// 2. Fetch customer (using mark list → customer_id)
|
||||||
|
// $markList = MarkList::where('mark_no', $order->mark_no)->first();
|
||||||
|
// $customer = null;
|
||||||
|
|
||||||
|
// if ($markList && $markList->customer_id) {
|
||||||
|
// $customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 3. Create Invoice Record
|
||||||
|
// $invoice = \App\Models\Invoice::create([
|
||||||
|
// 'order_id' => $order->id,
|
||||||
|
// 'customer_id' => $customer->id ?? null,
|
||||||
|
// 'mark_no' => $order->mark_no,
|
||||||
|
|
||||||
|
// 'invoice_number' => $invoiceNumber,
|
||||||
|
// 'invoice_date' => now(),
|
||||||
|
// 'due_date' => now()->addDays(10),
|
||||||
|
|
||||||
|
// 'payment_method' => null,
|
||||||
|
// 'reference_no' => null,
|
||||||
|
// 'status' => 'pending',
|
||||||
|
|
||||||
|
// 'final_amount' => $total_amount,
|
||||||
|
// 'gst_percent' => 0,
|
||||||
|
// 'gst_amount' => 0,
|
||||||
|
// 'final_amount_with_gst' => $total_amount,
|
||||||
|
|
||||||
|
// // snapshot customer fields
|
||||||
|
// 'customer_name' => $customer->customer_name ?? null,
|
||||||
|
// 'company_name' => $customer->company_name ?? null,
|
||||||
|
// 'customer_email' => $customer->email ?? null,
|
||||||
|
// 'customer_mobile' => $customer->mobile_no ?? null,
|
||||||
|
// 'customer_address' => $customer->address ?? null,
|
||||||
|
// 'pincode' => $customer->pincode ?? null,
|
||||||
|
|
||||||
|
// 'notes' => null,
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// 4. Clone order items into invoice_items
|
||||||
|
// foreach ($order->items as $item) {
|
||||||
|
// \App\Models\InvoiceItem::create([
|
||||||
|
// 'invoice_id' => $invoice->id,
|
||||||
|
// 'description' => $item->description,
|
||||||
|
// 'ctn' => $item->ctn,
|
||||||
|
// 'qty' => $item->qty,
|
||||||
|
// 'ttl_qty' => $item->ttl_qty,
|
||||||
|
// 'unit' => $item->unit,
|
||||||
|
// 'price' => $item->price,
|
||||||
|
// 'ttl_amount' => $item->ttl_amount,
|
||||||
|
// 'cbm' => $item->cbm,
|
||||||
|
// 'ttl_cbm' => $item->ttl_cbm,
|
||||||
|
// 'kg' => $item->kg,
|
||||||
|
// 'ttl_kg' => $item->ttl_kg,
|
||||||
|
// 'shop_no' => $item->shop_no,
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 5. TODO: PDF generation (I will add this later)
|
||||||
|
// $invoice->pdf_path = null; // placeholder for now
|
||||||
|
// $invoice->save();
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// END INVOICE CREATION
|
||||||
|
// =======================
|
||||||
|
|
||||||
|
// CLEAR TEMP DATA
|
||||||
|
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
||||||
|
|
||||||
|
return redirect()->route('admin.orders.index')
|
||||||
|
->with('success', 'Order + Invoice created successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------
|
||||||
|
* UPDATE ORDER ITEM (existing orders)
|
||||||
|
* ---------------------------*/
|
||||||
|
public function updateItem(Request $request, $id)
|
||||||
|
{
|
||||||
|
$item = OrderItem::findOrFail($id);
|
||||||
|
$order = $item->order;
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'description' => 'required|string',
|
||||||
|
'ctn' => 'nullable|numeric',
|
||||||
|
'qty' => 'nullable|numeric',
|
||||||
|
'unit' => 'nullable|string',
|
||||||
|
'price' => 'nullable|numeric',
|
||||||
|
'cbm' => 'nullable|numeric',
|
||||||
|
'kg' => 'nullable|numeric',
|
||||||
|
'shop_no' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ✅ BACKEND CALCULATION
|
||||||
|
$ctn = (float) ($request->ctn ?? 0);
|
||||||
|
$qty = (float) ($request->qty ?? 0);
|
||||||
|
$price = (float) ($request->price ?? 0);
|
||||||
|
$cbm = (float) ($request->cbm ?? 0);
|
||||||
|
$kg = (float) ($request->kg ?? 0);
|
||||||
|
|
||||||
|
$item->update([
|
||||||
|
'description' => $request->description,
|
||||||
|
'ctn' => $ctn,
|
||||||
|
'qty' => $qty,
|
||||||
|
'ttl_qty' => $ctn * $qty,
|
||||||
|
'unit' => $request->unit,
|
||||||
|
'price' => $price,
|
||||||
|
'ttl_amount' => ($ctn * $qty) * $price,
|
||||||
|
'cbm' => $cbm,
|
||||||
|
'ttl_cbm' => $cbm * $ctn,
|
||||||
|
'kg' => $kg,
|
||||||
|
'ttl_kg' => $ctn * $kg,
|
||||||
|
'shop_no' => $request->shop_no,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
return back()->with('success', 'Item updated successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function updateInvoiceFromOrder(Order $order)
|
||||||
|
{
|
||||||
|
$invoice = Invoice::where('order_id', $order->id)->first();
|
||||||
|
|
||||||
|
if (!$invoice) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoice = Invoice::create([
|
$invoice->final_amount = $order->ttl_amount;
|
||||||
'order_id' => $order->id,
|
$invoice->gst_percent = 0;
|
||||||
'customer_id' => $customer->id ?? null,
|
$invoice->gst_amount = 0;
|
||||||
'mark_no' => $order->mark_no,
|
$invoice->final_amount_with_gst = $order->ttl_amount;
|
||||||
'invoice_number' => $invoiceNumber,
|
$invoice->save();
|
||||||
'invoice_date' => now(),
|
|
||||||
'due_date' => now()->addDays(10),
|
InvoiceItem::where('invoice_id', $invoice->id)->delete();
|
||||||
'payment_method' => null,
|
|
||||||
'reference_no' => null,
|
|
||||||
'status' => 'pending',
|
|
||||||
'final_amount' => $total_amount,
|
|
||||||
'gst_percent' => 0,
|
|
||||||
'gst_amount' => 0,
|
|
||||||
'final_amount_with_gst' => $total_amount,
|
|
||||||
'customer_name' => $customer->customer_name ?? null,
|
|
||||||
'company_name' => $customer->company_name ?? null,
|
|
||||||
'customer_email' => $customer->email ?? null,
|
|
||||||
'customer_mobile' => $customer->mobile_no ?? null,
|
|
||||||
'customer_address' => $customer->address ?? null,
|
|
||||||
'pincode' => $customer->pincode ?? null,
|
|
||||||
'notes' => null,
|
|
||||||
'pdf_path' => null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
@@ -682,81 +808,37 @@ class AdminOrderController extends Controller
|
|||||||
'shop_no' => $item->shop_no,
|
'shop_no' => $item->shop_no,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('admin.orders.index')
|
|
||||||
->with('success', 'Order + Invoice created successfully.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
|
||||||
* UPDATE ORDER ITEM (existing orders)
|
|
||||||
* ---------------------------*/
|
|
||||||
public function updateItem(Request $request, $id)
|
|
||||||
{
|
|
||||||
$item = OrderItem::findOrFail($id);
|
|
||||||
$order = $item->order;
|
|
||||||
|
|
||||||
|
|
||||||
|
public function uploadExcelPreview(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'description' => 'required|string',
|
'excel' => 'required|file|mimes:xlsx,xls'
|
||||||
'ctn' => 'nullable|numeric',
|
|
||||||
'qty' => 'nullable|numeric',
|
|
||||||
'unit' => 'nullable|string',
|
|
||||||
'price' => 'nullable|numeric',
|
|
||||||
'cbm' => 'nullable|numeric',
|
|
||||||
'kg' => 'nullable|numeric',
|
|
||||||
'shop_no' => 'nullable|string',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ctn = (float) ($request->ctn ?? 0);
|
$import = new OrderItemsPreviewImport();
|
||||||
$qty = (float) ($request->qty ?? 0);
|
Excel::import($import, $request->file('excel'));
|
||||||
$price = (float) ($request->price ?? 0);
|
|
||||||
$cbm = (float) ($request->cbm ?? 0);
|
|
||||||
$kg = (float) ($request->kg ?? 0);
|
|
||||||
|
|
||||||
$item->update([
|
return response()->json([
|
||||||
'description' => $request->description,
|
'success' => true,
|
||||||
'ctn' => $ctn,
|
'items' => $import->rows
|
||||||
'qty' => $qty,
|
|
||||||
'ttl_qty' => $ctn * $qty,
|
|
||||||
'unit' => $request->unit,
|
|
||||||
'price' => $price,
|
|
||||||
'ttl_amount' => ($ctn * $qty) * $price,
|
|
||||||
'cbm' => $cbm,
|
|
||||||
'ttl_cbm' => $cbm * $ctn,
|
|
||||||
'kg' => $kg,
|
|
||||||
'ttl_kg' => $ctn * $kg,
|
|
||||||
'shop_no' => $request->shop_no,
|
|
||||||
]);
|
]);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
$this->recalcTotals($order);
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
return back()->with('success', 'Item updated successfully');
|
'message' => 'Invalid Excel file format'
|
||||||
}
|
], 422);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
public function uploadExcelPreview(Request $request)
|
\Log::error($e);
|
||||||
{
|
return response()->json([
|
||||||
try {
|
'success' => false,
|
||||||
$request->validate([
|
'message' => 'Server error'
|
||||||
'excel' => 'required|file|mimes:xlsx,xls'
|
], 500);
|
||||||
]);
|
|
||||||
|
|
||||||
$import = new OrderItemsPreviewImport();
|
|
||||||
Excel::import($import, $request->file('excel'));
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'items' => $import->rows
|
|
||||||
]);
|
|
||||||
} catch (ValidationException $e) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Invalid Excel file format'
|
|
||||||
], 422);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Server error'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Order;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
@@ -11,98 +12,45 @@ class AdminReportController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the reports page with joined data
|
* Display the reports page with joined data
|
||||||
*/
|
*/
|
||||||
// public function index(Request $request)
|
public function index(Request $request)
|
||||||
// {
|
{
|
||||||
/*********************************************************
|
// -------------------------------
|
||||||
* OLD FLOW (Order + Shipment + Invoice)
|
// FETCH REPORT DATA
|
||||||
* फक्त reference साठी ठेवलेला, वापरत नाही.
|
// ONLY orders that have BOTH:
|
||||||
*********************************************************/
|
// 1. Invoice
|
||||||
|
// 2. Shipment
|
||||||
/*
|
// -------------------------------
|
||||||
$reports = DB::table('orders')
|
$reports = DB::table('orders')
|
||||||
->join('shipment_items', 'shipment_items.order_id', '=', 'orders.id')
|
->join('shipment_items', 'shipment_items.order_id', '=', 'orders.id')
|
||||||
->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id')
|
->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id')
|
||||||
->join('invoices', 'invoices.order_id', '=', 'orders.id')
|
->join('invoices', 'invoices.order_id', '=', 'orders.id')
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no')
|
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no')
|
||||||
->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id')
|
->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id')
|
||||||
->select(...)
|
|
||||||
->orderBy('shipments.shipment_date', 'desc')
|
|
||||||
->get();
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*********************************************************
|
|
||||||
* NEW FLOW (Container + Invoice + MarkList)
|
|
||||||
*********************************************************/
|
|
||||||
|
|
||||||
// $reports = DB::table('invoices')
|
|
||||||
// ->join('containers', 'containers.id', '=', 'invoices.containerid')
|
|
||||||
// ->leftJoin('mark_list', 'mark_list.markno', '=', 'invoices.markno')
|
|
||||||
// ->select(
|
|
||||||
// 'invoices.id as invoicepk',
|
|
||||||
// 'invoices.invoicenumber',
|
|
||||||
// 'invoices.invoicedate',
|
|
||||||
// 'invoices.finalamount',
|
|
||||||
// 'invoices.finalamountwithgst',
|
|
||||||
// 'invoices.gstpercent',
|
|
||||||
// 'invoices.gstamount',
|
|
||||||
// 'invoices.status as invoicestatus',
|
|
||||||
// 'invoices.markno',
|
|
||||||
|
|
||||||
// 'containers.id as containerpk',
|
|
||||||
// 'containers.containernumber',
|
|
||||||
// 'containers.containerdate',
|
|
||||||
// 'containers.containername',
|
|
||||||
|
|
||||||
// 'mark_list.companyname',
|
|
||||||
// 'mark_list.customername'
|
|
||||||
// )
|
|
||||||
// ->orderBy('containers.containerdate', 'desc')
|
|
||||||
// ->get();
|
|
||||||
|
|
||||||
// return view('admin.reports', compact('reports'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
|
||||||
$reports = DB::table('invoices')
|
|
||||||
->join('containers', 'containers.id', '=', 'invoices.container_id')
|
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
|
|
||||||
->select(
|
->select(
|
||||||
// INVOICE
|
'orders.id as order_pk',
|
||||||
'invoices.id as invoicepk',
|
'orders.order_id',
|
||||||
|
'orders.mark_no',
|
||||||
|
'orders.origin',
|
||||||
|
'orders.destination',
|
||||||
|
|
||||||
|
'shipments.id as shipment_pk',
|
||||||
|
'shipments.shipment_id',
|
||||||
|
'shipments.status as shipment_status',
|
||||||
|
'shipments.shipment_date',
|
||||||
|
|
||||||
'invoices.invoice_number',
|
'invoices.invoice_number',
|
||||||
'invoices.invoice_date',
|
'invoices.invoice_date',
|
||||||
'invoices.final_amount',
|
'invoices.final_amount',
|
||||||
'invoices.final_amount_with_gst',
|
'invoices.status as invoice_status',
|
||||||
'invoices.gst_percent',
|
|
||||||
'invoices.gst_amount',
|
|
||||||
'invoices.status as invoicestatus',
|
|
||||||
'invoices.mark_no',
|
|
||||||
|
|
||||||
// CONTAINER
|
'mark_list.company_name',
|
||||||
'containers.id as containerpk',
|
'mark_list.customer_name'
|
||||||
'containers.container_number',
|
|
||||||
'containers.container_date',
|
|
||||||
'containers.container_name',
|
|
||||||
|
|
||||||
// RAW FIELDS (for reference/debug if needed)
|
|
||||||
'invoices.company_name as inv_company_name',
|
|
||||||
'invoices.customer_name as inv_customer_name',
|
|
||||||
'mark_list.company_name as ml_company_name',
|
|
||||||
'mark_list.customer_name as ml_customer_name',
|
|
||||||
|
|
||||||
// FINAL FIELDS (automatically pick invoice first, else mark_list)
|
|
||||||
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
|
|
||||||
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
|
|
||||||
)
|
)
|
||||||
->orderBy('invoices.invoice_date', 'desc')
|
|
||||||
->orderBy('invoices.id', 'desc')
|
|
||||||
|
|
||||||
|
->orderBy('shipments.shipment_date', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('admin.reports', compact('reports'));
|
return view('admin.reports', compact('reports'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ public function approveProfileUpdate($id)
|
|||||||
$req = \App\Models\UpdateRequest::findOrFail($id);
|
$req = \App\Models\UpdateRequest::findOrFail($id);
|
||||||
$user = \App\Models\User::findOrFail($req->user_id);
|
$user = \App\Models\User::findOrFail($req->user_id);
|
||||||
|
|
||||||
|
// FIX: Ensure data is array
|
||||||
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
||||||
|
|
||||||
foreach ($newData as $key => $value) {
|
foreach ($newData as $key => $value) {
|
||||||
@@ -95,18 +96,8 @@ public function approveProfileUpdate($id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user table
|
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
// Update mark_list table
|
|
||||||
\App\Models\MarkList::where('customer_id', $user->customer_id)
|
|
||||||
->update([
|
|
||||||
'customer_name' => $user->customer_name,
|
|
||||||
'company_name' => $user->company_name,
|
|
||||||
'mobile_no' => $user->mobile_no
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update request status
|
|
||||||
$req->status = 'approved';
|
$req->status = 'approved';
|
||||||
$req->admin_note = 'Approved by admin on ' . now();
|
$req->admin_note = 'Approved by admin on ' . now();
|
||||||
$req->save();
|
$req->save();
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ use App\Models\Invoice;
|
|||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use Carbon\Carbon;
|
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class ContainerController extends Controller
|
class ContainerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -255,7 +252,7 @@ class ContainerController extends Controller
|
|||||||
$rowText = strtoupper(implode(' ', $trimmedRow));
|
$rowText = strtoupper(implode(' ', $trimmedRow));
|
||||||
if (
|
if (
|
||||||
stripos($rowText, 'TOTAL') !== false ||
|
stripos($rowText, 'TOTAL') !== false ||
|
||||||
stripos($rowText, 'TTL') !== false ||
|
stripos($rowText, 'TTL') !== false ||
|
||||||
stripos($rowText, 'GRAND') !== false
|
stripos($rowText, 'GRAND') !== false
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
@@ -276,96 +273,7 @@ class ContainerController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($cleanedRows)) {
|
// MARK CHECK: strict - collect ALL marks + unmatched rows
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'No valid item rows found in Excel.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FORMULA CHECK
|
|
||||||
$cleanNumber = function ($value) {
|
|
||||||
if (is_string($value)) {
|
|
||||||
$value = str_replace(',', '', trim($value));
|
|
||||||
}
|
|
||||||
return is_numeric($value) ? (float)$value : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
$formulaErrors = [];
|
|
||||||
|
|
||||||
foreach ($cleanedRows as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$offset = $item['offset'];
|
|
||||||
|
|
||||||
$ctn = $essentialColumns['ctn_col'] !== null ? $cleanNumber($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
|
||||||
$qty = $essentialColumns['qty_col'] !== null ? $cleanNumber($row[$essentialColumns['qty_col']] ?? 0) : 0;
|
|
||||||
$ttlQ = $essentialColumns['totalqty_col'] !== null ? $cleanNumber($row[$essentialColumns['totalqty_col']] ?? 0) : 0;
|
|
||||||
$cbm = $essentialColumns['cbm_col'] !== null ? $cleanNumber($row[$essentialColumns['cbm_col']] ?? 0) : 0;
|
|
||||||
$ttlC = $essentialColumns['totalcbm_col'] !== null ? $cleanNumber($row[$essentialColumns['totalcbm_col']] ?? 0) : 0;
|
|
||||||
$kg = $essentialColumns['kg_col'] !== null ? $cleanNumber($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
|
||||||
$ttlK = $essentialColumns['totalkg_col'] !== null ? $cleanNumber($row[$essentialColumns['totalkg_col']] ?? 0) : 0;
|
|
||||||
|
|
||||||
$price = $essentialColumns['price_col'] !== null ? $cleanNumber($row[$essentialColumns['price_col']] ?? 0) : 0;
|
|
||||||
$ttlAmount = $essentialColumns['amount_col'] !== null ? $cleanNumber($row[$essentialColumns['amount_col']] ?? 0) : 0;
|
|
||||||
|
|
||||||
$desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : '';
|
|
||||||
$mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : '';
|
|
||||||
|
|
||||||
$expTtlQty = $qty * $ctn;
|
|
||||||
$expTtlCbm = $cbm * $ctn;
|
|
||||||
$expTtlKg = $kg * $ctn;
|
|
||||||
$expTtlAmount = ($qty * $ctn) * $price;
|
|
||||||
|
|
||||||
$rowErrors = [];
|
|
||||||
|
|
||||||
if (abs($ttlQ - $expTtlQty) > 0.01) {
|
|
||||||
$rowErrors['TOTAL QTY'] = [
|
|
||||||
'actual' => $ttlQ,
|
|
||||||
'expected' => $expTtlQty,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs($ttlC - $expTtlCbm) > 0.0005) {
|
|
||||||
$rowErrors['TOTAL CBM'] = [
|
|
||||||
'actual' => $ttlC,
|
|
||||||
'expected' => $expTtlCbm,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs($ttlK - $expTtlKg) > 0.01) {
|
|
||||||
$rowErrors['TOTAL KG'] = [
|
|
||||||
'actual' => $ttlK,
|
|
||||||
'expected' => $expTtlKg,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($essentialColumns['amount_col'] !== null && $essentialColumns['price_col'] !== null) {
|
|
||||||
if (abs($ttlAmount - $expTtlAmount) > 0.01) {
|
|
||||||
$rowErrors['TOTAL AMOUNT'] = [
|
|
||||||
'actual' => $ttlAmount,
|
|
||||||
'expected' => $expTtlAmount,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($rowErrors)) {
|
|
||||||
$rowData = [];
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
|
||||||
$value = $row[$colIndex] ?? null;
|
|
||||||
if (is_string($value)) $value = trim($value);
|
|
||||||
$rowData[$headingText] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$formulaErrors[] = [
|
|
||||||
'excel_row' => $headerRowIndex + 1 + $offset,
|
|
||||||
'mark_no' => $mark,
|
|
||||||
'description' => $desc,
|
|
||||||
'errors' => $rowErrors,
|
|
||||||
'data' => $rowData,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK CHECK
|
|
||||||
$marksFromExcel = [];
|
$marksFromExcel = [];
|
||||||
foreach ($cleanedRows as $item) {
|
foreach ($cleanedRows as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
@@ -391,8 +299,6 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
$unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
|
$unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
|
||||||
|
|
||||||
$markErrors = [];
|
|
||||||
|
|
||||||
if (!empty($unmatchedMarks)) {
|
if (!empty($unmatchedMarks)) {
|
||||||
foreach ($cleanedRows as $item) {
|
foreach ($cleanedRows as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
@@ -410,24 +316,21 @@ class ContainerController extends Controller
|
|||||||
$rowData[$headingText] = $value;
|
$rowData[$headingText] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$markErrors[] = [
|
$unmatchedRowsData[] = [
|
||||||
'excel_row' => $headerRowIndex + 1 + $offset,
|
'excel_row' => $headerRowIndex + 1 + $offset,
|
||||||
'mark_no' => $rowMark,
|
'mark_no' => $rowMark,
|
||||||
'data' => $rowData,
|
'data' => $rowData,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($formulaErrors) || !empty($markErrors)) {
|
|
||||||
return back()
|
return back()
|
||||||
|
->withErrors(['excel_file' => 'Some mark numbers are not found in Mark List. Container not created.'])
|
||||||
->withInput()
|
->withInput()
|
||||||
->with([
|
->with('unmatched_rows', $unmatchedRowsData);
|
||||||
'formula_errors' => $formulaErrors,
|
|
||||||
'mark_errors' => $markErrors,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP 1: Marks → customers mapping + grouping
|
// STEP 1: Marks → customers mapping + grouping
|
||||||
|
|
||||||
$markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
|
$markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
|
||||||
->where('status', 'active')
|
->where('status', 'active')
|
||||||
->get();
|
->get();
|
||||||
@@ -475,6 +378,7 @@ class ContainerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// STEP 2: Container + ContainerRows save
|
// STEP 2: Container + ContainerRows save
|
||||||
|
|
||||||
$container = Container::create([
|
$container = Container::create([
|
||||||
'container_name' => $request->container_name,
|
'container_name' => $request->container_name,
|
||||||
'container_number' => $request->container_number,
|
'container_number' => $request->container_number,
|
||||||
@@ -508,6 +412,7 @@ class ContainerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// STEP 3: per-customer invoices + invoice items
|
// STEP 3: per-customer invoices + invoice items
|
||||||
|
|
||||||
$invoiceCount = 0;
|
$invoiceCount = 0;
|
||||||
|
|
||||||
foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
|
foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
|
||||||
@@ -518,46 +423,32 @@ class ContainerController extends Controller
|
|||||||
$firstMark = $rowsForCustomer[0]['mark'];
|
$firstMark = $rowsForCustomer[0]['mark'];
|
||||||
$snap = $markToSnapshot[$firstMark] ?? null;
|
$snap = $markToSnapshot[$firstMark] ?? null;
|
||||||
|
|
||||||
// ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
|
|
||||||
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
|
||||||
|
|
||||||
$invoice = new Invoice();
|
$invoice = new Invoice();
|
||||||
$invoice->container_id = $container->id;
|
$invoice->container_id = $container->id;
|
||||||
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
|
// $invoice->customer_id = $customerId;
|
||||||
$invoice->mark_no = $firstMark;
|
|
||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
$invoice->invoice_number = $this->generateInvoiceNumber();
|
||||||
|
$invoice->invoice_date = now()->toDateString();
|
||||||
|
$invoice->due_date = null;
|
||||||
|
|
||||||
// invoice_date = container_date
|
|
||||||
$invoice->invoice_date = $container->container_date;
|
|
||||||
|
|
||||||
// due_date = container_date + 10 days
|
|
||||||
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
|
||||||
->addDays(10)
|
|
||||||
->format('Y-m-d');
|
|
||||||
|
|
||||||
// ✅ Snapshot data from MarkList (backward compatibility)
|
|
||||||
if ($snap) {
|
if ($snap) {
|
||||||
$invoice->customer_name = $snap['customer_name'] ?? null;
|
$invoice->customer_name = $snap['customer_name'] ?? null;
|
||||||
$invoice->company_name = $snap['company_name'] ?? null;
|
$invoice->company_name = $snap['company_name'] ?? null;
|
||||||
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ User model वरून email, address, pincode घ्या
|
|
||||||
if ($customerUser) {
|
|
||||||
$invoice->customer_email = $customerUser->email ?? null;
|
|
||||||
$invoice->customer_address = $customerUser->address ?? null;
|
|
||||||
$invoice->pincode = $customerUser->pincode ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoice->final_amount = 0;
|
$invoice->final_amount = 0;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = 0;
|
$invoice->final_amount_with_gst = 0;
|
||||||
|
|
||||||
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
$invoice->customer_email = null;
|
||||||
|
$invoice->customer_address = null;
|
||||||
|
$invoice->pincode = null;
|
||||||
|
|
||||||
|
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
||||||
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
||||||
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
||||||
|
|
||||||
$invoice->pdf_path = null;
|
$invoice->pdf_path = null;
|
||||||
$invoice->status = 'pending';
|
$invoice->status = 'pending';
|
||||||
|
|
||||||
@@ -567,47 +458,42 @@ class ContainerController extends Controller
|
|||||||
$totalAmount = 0;
|
$totalAmount = 0;
|
||||||
|
|
||||||
foreach ($rowsForCustomer as $item) {
|
foreach ($rowsForCustomer as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
$offset = $item['offset'];
|
|
||||||
|
|
||||||
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
||||||
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
$ctn = $essentialColumns['ctn_col'] !== null ? (int)($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
||||||
$qty = $essentialColumns['qty_col'] !== null ? (int) ($row[$essentialColumns['qty_col']] ?? 0) : 0;
|
$qty = $essentialColumns['qty_col'] !== null ? (int)($row[$essentialColumns['qty_col']] ?? 0) : 0;
|
||||||
$ttlQty = $essentialColumns['totalqty_col'] !== null ? (int) ($row[$essentialColumns['totalqty_col']] ?? 0) : $qty;
|
$ttlQty = $essentialColumns['totalqty_col'] !== null ? (int)($row[$essentialColumns['totalqty_col']] ?? 0) : $qty;
|
||||||
$unit = $essentialColumns['unit_col'] !== null ? ($row[$essentialColumns['unit_col']] ?? null) : null;
|
$unit = $essentialColumns['unit_col'] !== null ? ($row[$essentialColumns['unit_col']] ?? null) : null;
|
||||||
$price = $essentialColumns['price_col'] !== null ? (float) ($row[$essentialColumns['price_col']] ?? 0) : 0;
|
$price = $essentialColumns['price_col'] !== null ? (float)($row[$essentialColumns['price_col']] ?? 0) : 0;
|
||||||
$ttlAmount = $essentialColumns['amount_col'] !== null ? (float) ($row[$essentialColumns['amount_col']] ?? 0) : 0;
|
$ttlAmount = $essentialColumns['amount_col'] !== null ? (float)($row[$essentialColumns['amount_col']] ?? 0) : 0;
|
||||||
$cbm = $essentialColumns['cbm_col'] !== null ? (float) ($row[$essentialColumns['cbm_col']] ?? 0) : 0;
|
$cbm = $essentialColumns['cbm_col'] !== null ? (float)($row[$essentialColumns['cbm_col']] ?? 0) : 0;
|
||||||
$ttlCbm = $essentialColumns['totalcbm_col'] !== null ? (float) ($row[$essentialColumns['totalcbm_col']] ?? $cbm) : $cbm;
|
$ttlCbm = $essentialColumns['totalcbm_col'] !== null ? (float)($row[$essentialColumns['totalcbm_col']] ?? $cbm) : $cbm;
|
||||||
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
$kg = $essentialColumns['kg_col'] !== null ? (float)($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
||||||
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float)($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
||||||
|
|
||||||
$rowIndex = $headerRowIndex + 1 + $offset;
|
|
||||||
|
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'container_id' => $container->id,
|
'description'=> $description,
|
||||||
'container_row_index' => $rowIndex,
|
'ctn' => $ctn,
|
||||||
'description' => $description,
|
'qty' => $qty,
|
||||||
'ctn' => $ctn,
|
'ttl_qty' => $ttlQty,
|
||||||
'qty' => $qty,
|
'unit' => $unit,
|
||||||
'ttl_qty' => $ttlQty,
|
'price' => $price,
|
||||||
'unit' => $unit,
|
'ttl_amount' => $ttlAmount,
|
||||||
'price' => $price,
|
'cbm' => $cbm,
|
||||||
'ttl_amount' => $ttlAmount,
|
'ttl_cbm' => $ttlCbm,
|
||||||
'cbm' => $cbm,
|
'kg' => $kg,
|
||||||
'ttl_cbm' => $ttlCbm,
|
'ttl_kg' => $ttlKg,
|
||||||
'kg' => $kg,
|
'shop_no' => null,
|
||||||
'ttl_kg' => $ttlKg,
|
|
||||||
'shop_no' => null,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$totalAmount += $ttlAmount;
|
$totalAmount += $ttlAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoice->final_amount = $totalAmount;
|
$invoice->final_amount = $totalAmount;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = $totalAmount;
|
$invoice->final_amount_with_gst = $totalAmount;
|
||||||
|
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
@@ -620,18 +506,7 @@ class ContainerController extends Controller
|
|||||||
public function show(Container $container)
|
public function show(Container $container)
|
||||||
{
|
{
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
|
return view('admin.container_show', compact('container'));
|
||||||
// paid / paying invoices च्या row indexes collect करा
|
|
||||||
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
|
|
||||||
->where('invoices.container_id', $container->id)
|
|
||||||
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
|
|
||||||
->pluck('invoice_items.container_row_index')
|
|
||||||
->filter()
|
|
||||||
->unique()
|
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateRows(Request $request, Container $container)
|
public function updateRows(Request $request, Container $container)
|
||||||
@@ -643,155 +518,17 @@ class ContainerController extends Controller
|
|||||||
->where('id', $rowId)
|
->where('id', $rowId)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$row) {
|
if (!$row) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1) update container_rows.data
|
|
||||||
$data = $row->data ?? [];
|
$data = $row->data ?? [];
|
||||||
|
|
||||||
foreach ($cols as $colHeader => $value) {
|
foreach ($cols as $colHeader => $value) {
|
||||||
$data[$colHeader] = $value;
|
$data[$colHeader] = $value;
|
||||||
}
|
}
|
||||||
$row->update(['data' => $data]);
|
|
||||||
|
|
||||||
// 2) normalize keys
|
$row->update([
|
||||||
$normalizedMap = [];
|
'data' => $data,
|
||||||
foreach ($data as $key => $value) {
|
]);
|
||||||
if ($key === null || $key === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$normKey = strtoupper((string)$key);
|
|
||||||
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
|
|
||||||
$normalizedMap[$normKey] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper: get first numeric value from given keys
|
|
||||||
$getFirstNumeric = function (array $map, array $possibleKeys) {
|
|
||||||
foreach ($possibleKeys as $search) {
|
|
||||||
$normSearch = strtoupper($search);
|
|
||||||
$normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
|
|
||||||
|
|
||||||
foreach ($map as $nKey => $value) {
|
|
||||||
if (strpos($nKey, $normSearch) !== false) {
|
|
||||||
if (is_numeric($value)) {
|
|
||||||
return (float)$value;
|
|
||||||
}
|
|
||||||
if (is_string($value) && is_numeric(trim($value))) {
|
|
||||||
return (float)trim($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3) read values – QTY vs TTLQTY separately
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
|
||||||
$qtyKeys = ['QTY', 'PCS', 'PIECES']; // per-carton qty
|
|
||||||
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY']; // total qty
|
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
|
||||||
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
|
|
||||||
|
|
||||||
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
|
|
||||||
|
|
||||||
// per carton qty
|
|
||||||
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
|
|
||||||
|
|
||||||
// total qty direct from TOTALQTY/TTLQTY/ITLQTY
|
|
||||||
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
|
|
||||||
|
|
||||||
// if total column is 0 then compute ctn * qty
|
|
||||||
if ($ttlQ == 0 && $ctn && $qty) {
|
|
||||||
$ttlQ = $ctn * $qty;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cbm = $getFirstNumeric($normalizedMap, ['CBM']);
|
|
||||||
$ttlC = $getFirstNumeric($normalizedMap, ['TOTALCBM', 'TTLCBM', 'ITLCBM']);
|
|
||||||
if ($ttlC == 0 && $cbm && $ctn) {
|
|
||||||
$ttlC = $cbm * $ctn;
|
|
||||||
}
|
|
||||||
|
|
||||||
$kg = $getFirstNumeric($normalizedMap, ['KG', 'WEIGHT']);
|
|
||||||
$ttlK = $getFirstNumeric($normalizedMap, ['TOTALKG', 'TTKG']);
|
|
||||||
if ($ttlK == 0 && $kg && $ctn) {
|
|
||||||
$ttlK = $kg * $ctn;
|
|
||||||
}
|
|
||||||
|
|
||||||
$price = $getFirstNumeric($normalizedMap, ['PRICE', 'RATE']);
|
|
||||||
$amount = $getFirstNumeric($normalizedMap, $amountKeys);
|
|
||||||
if ($amount == 0 && $price && $ttlQ) {
|
|
||||||
$amount = $price * $ttlQ;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) get description
|
|
||||||
$desc = null;
|
|
||||||
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
|
|
||||||
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
|
|
||||||
foreach ($normalizedMap as $nKey => $v) {
|
|
||||||
if (strpos($nKey, $normD) !== false) {
|
|
||||||
$desc = is_string($v) ? trim($v) : $v;
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rowIndex = $row->row_index;
|
|
||||||
|
|
||||||
// 5) find linked invoice_items
|
|
||||||
$items = InvoiceItem::where('container_id', $container->id)
|
|
||||||
->where('container_row_index', $rowIndex)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if ($items->isEmpty() && $desc) {
|
|
||||||
$items = InvoiceItem::where('container_id', $container->id)
|
|
||||||
->whereNull('container_row_index')
|
|
||||||
->where('description', $desc)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6) update invoice_items + recalc invoice totals
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$item->description = $desc;
|
|
||||||
$item->ctn = $ctn;
|
|
||||||
$item->qty = $qty; // per carton
|
|
||||||
$item->ttl_qty = $ttlQ; // total
|
|
||||||
$item->price = $price;
|
|
||||||
$item->ttl_amount = $amount;
|
|
||||||
$item->cbm = $cbm;
|
|
||||||
$item->ttl_cbm = $ttlC;
|
|
||||||
$item->kg = $kg;
|
|
||||||
$item->ttl_kg = $ttlK;
|
|
||||||
$item->save();
|
|
||||||
|
|
||||||
$invoice = $item->invoice;
|
|
||||||
if ($invoice) {
|
|
||||||
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
|
|
||||||
->sum('ttl_amount');
|
|
||||||
|
|
||||||
$taxType = $invoice->tax_type;
|
|
||||||
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
|
|
||||||
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
|
|
||||||
$igstPercent = (float) ($invoice->igst_percent ?? 0);
|
|
||||||
|
|
||||||
$gstPercent = 0;
|
|
||||||
if ($taxType === 'gst') {
|
|
||||||
$gstPercent = $cgstPercent + $sgstPercent;
|
|
||||||
} elseif ($taxType === 'igst') {
|
|
||||||
$gstPercent = $igstPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
$gstAmount = $newBaseAmount * $gstPercent / 100;
|
|
||||||
$finalWithGst = $newBaseAmount + $gstAmount;
|
|
||||||
|
|
||||||
$invoice->final_amount = $newBaseAmount;
|
|
||||||
$invoice->gst_amount = $gstAmount;
|
|
||||||
$invoice->final_amount_with_gst = $finalWithGst;
|
|
||||||
$invoice->gst_percent = $gstPercent;
|
|
||||||
$invoice->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
@@ -801,37 +538,17 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
public function updateStatus(Request $request, Container $container)
|
public function updateStatus(Request $request, Container $container)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate(['status' => 'required|in:pending,in-progress,completed,cancelled']);
|
||||||
'status' => 'required|in:container-ready,export-custom,international-transit,arrived-at-india,import-custom,warehouse,domestic-distribution,out-for-delivery,delivered',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$container->status = $request->status;
|
$container->update(['status' => $request->status]);
|
||||||
$container->save();
|
|
||||||
|
|
||||||
if ($request->wantsJson() || $request->ajax()) {
|
return redirect()->route('containers.index')->with('success', 'Status updated.');
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'status' => $container->status,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return back()->with('success', 'Container status updated.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy(Container $container)
|
public function destroy(Container $container)
|
||||||
{
|
{
|
||||||
$container->delete();
|
$container->delete();
|
||||||
|
return redirect()->route('containers.index')->with('success', 'Container deleted.');
|
||||||
if (request()->wantsJson() || request()->ajax()) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Container deleted',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()
|
|
||||||
->route('containers.index')
|
|
||||||
->with('success', 'Container deleted.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateInvoiceNumber(): string
|
private function generateInvoiceNumber(): string
|
||||||
@@ -857,103 +574,4 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT);
|
return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadPdf(Container $container)
|
|
||||||
{
|
|
||||||
$container->load('rows');
|
|
||||||
|
|
||||||
$pdf = Pdf::loadView('admin.container_pdf', [
|
|
||||||
'container' => $container,
|
|
||||||
])->setPaper('a4', 'landscape');
|
|
||||||
|
|
||||||
$fileName = 'container-'.$container->container_number.'.pdf';
|
|
||||||
|
|
||||||
return $pdf->download($fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function downloadExcel(Container $container)
|
|
||||||
{
|
|
||||||
if (!$container->excel_file) {
|
|
||||||
abort(404, 'Excel file not found on record.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stored path like "containers/abc.xlsx"
|
|
||||||
$path = $container->excel_file;
|
|
||||||
|
|
||||||
if (!Storage::exists($path)) {
|
|
||||||
abort(404, 'Excel file missing on server.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileName = 'container-'.$container->container_number.'.xlsx';
|
|
||||||
|
|
||||||
return Storage::download($path, $fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function popupPopup(Container $container)
|
|
||||||
{
|
|
||||||
// existing show सारखाच data वापरू
|
|
||||||
$container->load('rows');
|
|
||||||
|
|
||||||
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
|
||||||
$rows = $container->rows ?? collect();
|
|
||||||
|
|
||||||
$totalCtn = 0;
|
|
||||||
$totalQty = 0;
|
|
||||||
$totalCbm = 0;
|
|
||||||
$totalKg = 0;
|
|
||||||
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
|
||||||
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
|
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
|
||||||
|
|
||||||
$getFirstNumeric = function (array $data, array $possibleKeys) {
|
|
||||||
$normalizedMap = [];
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
if ($key === null) continue;
|
|
||||||
$normKey = strtoupper((string)$key);
|
|
||||||
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
|
|
||||||
$normalizedMap[$normKey] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($possibleKeys as $search) {
|
|
||||||
$normSearch = strtoupper($search);
|
|
||||||
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
|
|
||||||
|
|
||||||
foreach ($normalizedMap as $nKey => $value) {
|
|
||||||
if (strpos($nKey, $normSearch) !== false) {
|
|
||||||
if (is_numeric($value)) {
|
|
||||||
return (float)$value;
|
|
||||||
}
|
|
||||||
if (is_string($value) && is_numeric(trim($value))) {
|
|
||||||
return (float)trim($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$data = $row->data ?? [];
|
|
||||||
if (!is_array($data)) continue;
|
|
||||||
|
|
||||||
$totalCtn += $getFirstNumeric($data, $ctnKeys);
|
|
||||||
$totalQty += $getFirstNumeric($data, $qtyKeys);
|
|
||||||
$totalCbm += $getFirstNumeric($data, $cbmKeys);
|
|
||||||
$totalKg += $getFirstNumeric($data, $kgKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
$summary = [
|
|
||||||
'total_ctn' => round($totalCtn, 2),
|
|
||||||
'total_qty' => round($totalQty, 2),
|
|
||||||
'total_cbm' => round($totalCbm, 3),
|
|
||||||
'total_kg' => round($totalKg, 2),
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('admin.partials.container_popup_readonly', [
|
|
||||||
'container' => $container,
|
|
||||||
'summary' => $summary,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,9 +67,12 @@ class ChatController extends Controller
|
|||||||
'sender_id' => auth()->id(),
|
'sender_id' => auth()->id(),
|
||||||
'sender_type' => \App\Models\User::class,
|
'sender_type' => \App\Models\User::class,
|
||||||
'message' => $request->message,
|
'message' => $request->message,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
'client_id' => $request->client_id, // ✅ ADD
|
'client_id' => $request->client_id, // ✅ ADD
|
||||||
'read_by_admin' => false,
|
'read_by_admin' => false,
|
||||||
'read_by_user' => true,
|
'read_by_user' => true,
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handle file upload
|
// Handle file upload
|
||||||
@@ -86,7 +89,11 @@ class ChatController extends Controller
|
|||||||
$message->load('sender');
|
$message->load('sender');
|
||||||
|
|
||||||
// Fire real-time event
|
// Fire real-time event
|
||||||
|
<<<<<<< HEAD
|
||||||
|
broadcast(new NewChatMessage($message))->toOthers();
|
||||||
|
=======
|
||||||
broadcast(new NewChatMessage($message));
|
broadcast(new NewChatMessage($message));
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
|||||||
@@ -21,33 +21,23 @@ class UserOrderController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Get customer invoices with containers
|
// Get all orders
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$invoices = $user->invoices()->with('container')->get();
|
$orders = $user->orders()->with('invoice')->get();
|
||||||
|
|
||||||
// Unique containers for this customer
|
|
||||||
$containers = $invoices->pluck('container')->filter()->unique('id');
|
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Counts based on container status
|
// Counts
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$totalOrders = $containers->count();
|
$totalOrders = $orders->count();
|
||||||
|
$delivered = $orders->where('status', 'delivered')->count();
|
||||||
$delivered = $containers->where('status', 'delivered')->count();
|
$inTransit = $orders->where('status', '!=', 'delivered')->count();
|
||||||
|
$active = $totalOrders;
|
||||||
$inTransit = $containers->whereNotIn('status', [
|
|
||||||
'delivered',
|
|
||||||
'warehouse',
|
|
||||||
'domestic-distribution'
|
|
||||||
])->count();
|
|
||||||
|
|
||||||
$active = $totalOrders;
|
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Total Amount = sum of invoice totals
|
// Total Amount = Invoice.total_with_gst
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$totalAmount = $invoices->sum(function ($invoice) {
|
$totalAmount = $orders->sum(function ($o) {
|
||||||
return $invoice->final_amount_with_gst ?? 0;
|
return $o->invoice->final_amount_with_gst ?? 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Format total amount in K, L, Cr
|
// Format total amount in K, L, Cr
|
||||||
@@ -55,12 +45,13 @@ class UserOrderController extends Controller
|
|||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => true,
|
'status' => true,
|
||||||
|
|
||||||
'summary' => [
|
'summary' => [
|
||||||
'active_orders' => $active,
|
'active_orders' => $active,
|
||||||
'in_transit_orders' => $inTransit,
|
'in_transit_orders' => $inTransit,
|
||||||
'delivered_orders' => $delivered,
|
'delivered_orders' => $delivered,
|
||||||
'total_value' => $formattedAmount,
|
'total_value' => $formattedAmount, // formatted value
|
||||||
'total_raw' => $totalAmount
|
'total_raw' => $totalAmount // original value
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -99,28 +90,20 @@ class UserOrderController extends Controller
|
|||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get invoices with containers for this customer
|
// Fetch orders for this user
|
||||||
$invoices = $user->invoices()
|
$orders = $user->orders()
|
||||||
->with('container')
|
->with(['invoice', 'shipments'])
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->get();
|
->get()
|
||||||
|
->map(function ($o) {
|
||||||
// Extract unique containers
|
return [
|
||||||
$containers = $invoices->pluck('container')
|
'order_id' => $o->order_id,
|
||||||
->filter()
|
'status' => $o->status,
|
||||||
->unique('id')
|
'amount' => $o->ttl_amount,
|
||||||
->values();
|
'description'=> "Order from {$o->origin} to {$o->destination}",
|
||||||
|
'created_at' => $o->created_at,
|
||||||
$orders = $containers->map(function ($container) {
|
];
|
||||||
|
});
|
||||||
return [
|
|
||||||
'order_id' => $container->id,
|
|
||||||
'container_number' => $container->container_number,
|
|
||||||
'status' => $container->status,
|
|
||||||
'container_date' => $container->container_date,
|
|
||||||
'created_at' => $container->created_at,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
@@ -132,73 +115,45 @@ public function orderDetails($order_id)
|
|||||||
{
|
{
|
||||||
$user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
if (!$user) {
|
$order = $user->orders()
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Unauthorized'
|
|
||||||
], 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find container first
|
|
||||||
$container = \App\Models\Container::find($order_id);
|
|
||||||
|
|
||||||
if (!$container) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Container not found'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find invoice belonging to this user for this container
|
|
||||||
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
|
|
||||||
->where('container_id', $container->id)
|
|
||||||
->with(['items'])
|
->with(['items'])
|
||||||
|
->where('order_id', $order_id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$invoice) {
|
if (!$order) {
|
||||||
return response()->json([
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
'success' => false,
|
|
||||||
'message' => 'Order not found for this user'
|
|
||||||
], 404);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'order' => [
|
'order' => $order
|
||||||
'container_id' => $container->id,
|
|
||||||
'container_number' => $container->container_number,
|
|
||||||
'container_date' => $container->container_date,
|
|
||||||
'status' => $container->status,
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'items' => $invoice->items
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// public function orderShipment($order_id)
|
public function orderShipment($order_id)
|
||||||
// {
|
{
|
||||||
// $user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
// // Get order
|
// Get order
|
||||||
// $order = $user->orders()->where('order_id', $order_id)->first();
|
$order = $user->orders()->where('order_id', $order_id)->first();
|
||||||
|
|
||||||
// if (!$order) {
|
if (!$order) {
|
||||||
// return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Find shipment only for this order
|
// Find shipment only for this order
|
||||||
// $shipment = $order->shipments()
|
$shipment = $order->shipments()
|
||||||
// ->with(['items' => function ($q) use ($order) {
|
->with(['items' => function ($q) use ($order) {
|
||||||
// $q->where('order_id', $order->id);
|
$q->where('order_id', $order->id);
|
||||||
// }])
|
}])
|
||||||
// ->first();
|
->first();
|
||||||
|
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'success' => true,
|
'success' => true,
|
||||||
// 'shipment' => $shipment
|
'shipment' => $shipment
|
||||||
// ]);
|
]);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
public function orderInvoice($order_id)
|
public function orderInvoice($order_id)
|
||||||
@@ -224,35 +179,23 @@ public function trackOrder($order_id)
|
|||||||
{
|
{
|
||||||
$user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
if (!$user) {
|
$order = $user->orders()
|
||||||
return response()->json([
|
->with('shipments')
|
||||||
'success' => false,
|
->where('order_id', $order_id)
|
||||||
'message' => 'Unauthorized'
|
|
||||||
], 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the container belongs to this customer via invoice
|
|
||||||
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
|
|
||||||
->where('container_id', $order_id)
|
|
||||||
->with('container')
|
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$invoice || !$invoice->container) {
|
if (!$order) {
|
||||||
return response()->json([
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
'success' => false,
|
|
||||||
'message' => 'Order not found'
|
|
||||||
], 404);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$container = $invoice->container;
|
$shipment = $order->shipments()->first();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'track' => [
|
'track' => [
|
||||||
'order_id' => $container->id,
|
'order_id' => $order->order_id,
|
||||||
'container_number' => $container->container_number,
|
'shipment_status' => $shipment->status ?? 'pending',
|
||||||
'status' => $container->status,
|
'shipment_date' => $shipment->shipment_date ?? null,
|
||||||
'container_date' => $container->container_date,
|
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -346,44 +289,44 @@ public function invoiceDetails($invoice_id)
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function confirmOrder($order_id)
|
public function confirmOrder($order_id)
|
||||||
// {
|
{
|
||||||
// $user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
// if (! $user) {
|
if (! $user) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'success' => false,
|
'success' => false,
|
||||||
// 'message' => 'Unauthorized'
|
'message' => 'Unauthorized'
|
||||||
// ], 401);
|
], 401);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// $order = $user->orders()
|
$order = $user->orders()
|
||||||
// ->where('order_id', $order_id)
|
->where('order_id', $order_id)
|
||||||
// ->first();
|
->first();
|
||||||
|
|
||||||
// if (! $order) {
|
if (! $order) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'success' => false,
|
'success' => false,
|
||||||
// 'message' => 'Order not found'
|
'message' => 'Order not found'
|
||||||
// ], 404);
|
], 404);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // 🚫 Only allow confirm from order_placed
|
// 🚫 Only allow confirm from order_placed
|
||||||
// if ($order->status !== 'order_placed') {
|
if ($order->status !== 'order_placed') {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'success' => false,
|
'success' => false,
|
||||||
// 'message' => 'Order cannot be confirmed'
|
'message' => 'Order cannot be confirmed'
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// $order->status = 'order_confirmed';
|
$order->status = 'order_confirmed';
|
||||||
// $order->save();
|
$order->save();
|
||||||
|
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'success' => true,
|
'success' => true,
|
||||||
// 'message' => 'Order confirmed successfully'
|
'message' => 'Order confirmed successfully'
|
||||||
// ]);
|
]);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ class ChatMessage extends Model
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
<<<<<<< HEAD
|
||||||
|
'ticket_id',
|
||||||
|
'sender_id',
|
||||||
|
'sender_type', // user OR admin
|
||||||
|
'message',
|
||||||
|
'file_path',
|
||||||
|
'file_type',
|
||||||
|
];
|
||||||
|
=======
|
||||||
'ticket_id',
|
'ticket_id',
|
||||||
'sender_id',
|
'sender_id',
|
||||||
'sender_type',
|
'sender_type',
|
||||||
@@ -20,6 +29,9 @@ class ChatMessage extends Model
|
|||||||
'read_by_user',
|
'read_by_user',
|
||||||
'client_id',
|
'client_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ticket this message belongs to.
|
* The ticket this message belongs to.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Invoice extends Model
|
|||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'container_id',
|
'container_id',
|
||||||
'customer_id',
|
'customer_id',
|
||||||
@@ -31,13 +32,6 @@ class Invoice extends Model
|
|||||||
'pincode',
|
'pincode',
|
||||||
'pdf_path',
|
'pdf_path',
|
||||||
'notes',
|
'notes',
|
||||||
// totals from charge groups
|
|
||||||
'charge_groups_total',
|
|
||||||
'grand_total_with_charges',
|
|
||||||
'tax_type',
|
|
||||||
'cgst_percent',
|
|
||||||
'sgst_percent',
|
|
||||||
'igst_percent',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
@@ -49,9 +43,16 @@ class Invoice extends Model
|
|||||||
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
|
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function container()
|
// NEW: invoice आता container वर depend
|
||||||
|
public function container()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Container::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OLD: order() relation काढले आहे
|
||||||
|
// public function order()
|
||||||
// {
|
// {
|
||||||
// return $this->belongsTo(Container::class);
|
// return $this->belongsTo(Order::class);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public function customer()
|
public function customer()
|
||||||
@@ -64,16 +65,11 @@ class Invoice extends Model
|
|||||||
return $this->hasMany(InvoiceInstallment::class);
|
return $this->hasMany(InvoiceInstallment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function chargeGroups()
|
|
||||||
{
|
|
||||||
return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
* Helper Functions
|
* Helper Functions
|
||||||
****************************/
|
****************************/
|
||||||
|
|
||||||
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस)
|
// Auto calculate GST fields (you can call this in controller before saving)
|
||||||
public function calculateTotals()
|
public function calculateTotals()
|
||||||
{
|
{
|
||||||
$gst = ($this->final_amount * $this->gst_percent) / 100;
|
$gst = ($this->final_amount * $this->gst_percent) / 100;
|
||||||
@@ -81,53 +77,16 @@ class Invoice extends Model
|
|||||||
$this->final_amount_with_gst = $this->final_amount + $gst;
|
$this->final_amount_with_gst = $this->final_amount + $gst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check overdue status condition
|
||||||
public function isOverdue()
|
public function isOverdue()
|
||||||
{
|
{
|
||||||
return $this->status === 'pending' && now()->gt($this->due_date);
|
return $this->status === 'pending' && now()->gt($this->due_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// जर पुढे container → shipment relation असेल तर हा helper नंतर adjust करू
|
||||||
public function getShipment()
|
public function getShipment()
|
||||||
{
|
{
|
||||||
|
// आधी order वरून shipment घेत होत; container flow मध्ये नंतर गरज पडल्यास बदलू
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Charge groups base total (WITHOUT GST)
|
|
||||||
public function getChargeGroupsTotalAttribute()
|
|
||||||
{
|
|
||||||
// base = total_charge sum
|
|
||||||
return (float) $this->chargeGroups->sum('total_charge');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Grand total: Charge groups base + GST (items ignore)
|
|
||||||
public function getGrandTotalWithChargesAttribute()
|
|
||||||
{
|
|
||||||
$base = (float) ($this->charge_groups_total ?? 0);
|
|
||||||
$gst = (float) ($this->gst_amount ?? 0);
|
|
||||||
|
|
||||||
return $base + $gst;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function totalPaid(): float
|
|
||||||
{
|
|
||||||
return (float) $this->installments()->sum('amount');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remainingAmount(): float
|
|
||||||
{
|
|
||||||
$grand = (float) $this->grand_total_with_charges;
|
|
||||||
$paid = (float) $this->totalPaid();
|
|
||||||
|
|
||||||
return max(0, $grand - $paid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isLockedForEdit(): bool
|
|
||||||
{
|
|
||||||
return $this->status === 'paid';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function container()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(\App\Models\Container::class, 'container_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class InvoiceChargeGroup extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'invoice_id',
|
|
||||||
'group_name',
|
|
||||||
'basis_type',
|
|
||||||
'basis_value',
|
|
||||||
'rate',
|
|
||||||
'total_charge',
|
|
||||||
|
|
||||||
'tax_type',
|
|
||||||
'gst_percent',
|
|
||||||
'total_with_gst',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function invoice()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Invoice::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function items()
|
|
||||||
{
|
|
||||||
return $this->hasMany(InvoiceChargeGroupItem::class, 'group_id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class InvoiceChargeGroupItem extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'group_id',
|
|
||||||
'invoice_item_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function group()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(InvoiceChargeGroup::class, 'group_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function item()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(InvoiceItem::class, 'invoice_item_id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,6 @@ class InvoiceItem extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'invoice_id',
|
'invoice_id',
|
||||||
'container_id', // Container mapping
|
|
||||||
'container_row_index', // Container row index
|
|
||||||
|
|
||||||
'description',
|
'description',
|
||||||
'ctn',
|
'ctn',
|
||||||
@@ -39,79 +37,4 @@ class InvoiceItem extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Invoice::class);
|
return $this->belongsTo(Invoice::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function chargeGroupItems()
|
|
||||||
{
|
|
||||||
return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
// हे helper: पहिला group fetch करून त्यावरून rate/total काढणे
|
|
||||||
public function getChargeRateAttribute()
|
|
||||||
{
|
|
||||||
$pivot = $this->chargeGroupItems->first();
|
|
||||||
if (!$pivot || !$pivot->group) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$group = $pivot->group;
|
|
||||||
|
|
||||||
// basis नुसार या item चा basis value
|
|
||||||
$basis = 0;
|
|
||||||
switch ($group->basis_type) {
|
|
||||||
case 'ttl_qty':
|
|
||||||
$basis = $this->ttl_qty;
|
|
||||||
break;
|
|
||||||
case 'amount':
|
|
||||||
$basis = $this->ttl_amount;
|
|
||||||
break;
|
|
||||||
case 'ttl_cbm':
|
|
||||||
$basis = $this->ttl_cbm;
|
|
||||||
break;
|
|
||||||
case 'ttl_kg':
|
|
||||||
$basis = $this->ttl_kg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// group चा rate field आधीच आहे, ते direct वापरू
|
|
||||||
return (float) $group->rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChargeTotalAttribute()
|
|
||||||
{
|
|
||||||
$pivot = $this->chargeGroupItems->first();
|
|
||||||
if (!$pivot || !$pivot->group) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$group = $pivot->group;
|
|
||||||
|
|
||||||
$basis = 0;
|
|
||||||
switch ($group->basis_type) {
|
|
||||||
case 'ttl_qty':
|
|
||||||
$basis = $this->ttl_qty;
|
|
||||||
break;
|
|
||||||
case 'amount':
|
|
||||||
$basis = $this->ttl_amount;
|
|
||||||
break;
|
|
||||||
case 'ttl_cbm':
|
|
||||||
$basis = $this->ttl_cbm;
|
|
||||||
break;
|
|
||||||
case 'ttl_kg':
|
|
||||||
$basis = $this->ttl_kg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// per unit rate
|
|
||||||
$rate = (float) $group->rate;
|
|
||||||
// item total = basis * rate
|
|
||||||
return $basis * $rate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
app/Models/LoadingListItem.php
Normal file
28
app/Models/LoadingListItem.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LoadingListItem extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'container_id',
|
||||||
|
'mark',
|
||||||
|
'description',
|
||||||
|
'ctn',
|
||||||
|
'qty',
|
||||||
|
'total_qty',
|
||||||
|
'unit',
|
||||||
|
'price',
|
||||||
|
'cbm',
|
||||||
|
'total_cbm',
|
||||||
|
'kg',
|
||||||
|
'total_kg',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function container()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Container::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,10 +58,10 @@ class Order extends Model
|
|||||||
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
|
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function invoice()
|
public function invoice()
|
||||||
// {
|
{
|
||||||
// return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
|
return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
const STATUS_LABELS = [
|
const STATUS_LABELS = [
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ class User extends Authenticatable implements JWTSubject
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
public function invoices()
|
||||||
|
{
|
||||||
|
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
// App\Models\User.php
|
// App\Models\User.php
|
||||||
|
|
||||||
@@ -105,10 +107,6 @@ public function invoiceInstallments()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invoices()
|
|
||||||
{
|
|
||||||
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@
|
|||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true,
|
"pestphp/pest-plugin": true,
|
||||||
"php-http/discovery": true
|
"php-http/discovery": true
|
||||||
}
|
},
|
||||||
|
"platform-check": false
|
||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"prefer-stable": true
|
||||||
|
|||||||
65
composer.lock
generated
65
composer.lock
generated
@@ -2880,6 +2880,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maennchen/zipstream-php",
|
"name": "maennchen/zipstream-php",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"version": "3.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||||
|
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
||||||
|
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
||||||
|
=======
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -2890,21 +2902,34 @@
|
|||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"ext-zlib": "*",
|
"ext-zlib": "*",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"php-64bit": "^8.3"
|
||||||
|
=======
|
||||||
"php-64bit": "^8.2"
|
"php-64bit": "^8.2"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"brianium/paratest": "^7.7",
|
"brianium/paratest": "^7.7",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.86",
|
||||||
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
|
"mikey179/vfsstream": "^1.6",
|
||||||
|
"php-coveralls/php-coveralls": "^2.5",
|
||||||
|
"phpunit/phpunit": "^12.0",
|
||||||
|
=======
|
||||||
"friendsofphp/php-cs-fixer": "^3.16",
|
"friendsofphp/php-cs-fixer": "^3.16",
|
||||||
"guzzlehttp/guzzle": "^7.5",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"mikey179/vfsstream": "^1.6",
|
"mikey179/vfsstream": "^1.6",
|
||||||
"php-coveralls/php-coveralls": "^2.5",
|
"php-coveralls/php-coveralls": "^2.5",
|
||||||
"phpunit/phpunit": "^11.0",
|
"phpunit/phpunit": "^11.0",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"vimeo/psalm": "^6.0"
|
"vimeo/psalm": "^6.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@@ -2946,7 +2971,11 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1"
|
||||||
|
=======
|
||||||
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
|
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2954,7 +2983,11 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"time": "2025-12-10T09:58:31+00:00"
|
||||||
|
=======
|
||||||
"time": "2025-01-27T12:07:53+00:00"
|
"time": "2025-01-27T12:07:53+00:00"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "markbaker/complex",
|
"name": "markbaker/complex",
|
||||||
@@ -4181,6 +4214,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoffice/phpspreadsheet",
|
"name": "phpoffice/phpspreadsheet",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"version": "1.30.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||||
|
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
||||||
|
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
||||||
|
=======
|
||||||
"version": "1.30.1",
|
"version": "1.30.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -4191,6 +4236,7 @@
|
|||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
|
||||||
"reference": "fa8257a579ec623473eabfe49731de5967306c4c",
|
"reference": "fa8257a579ec623473eabfe49731de5967306c4c",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4213,12 +4259,19 @@
|
|||||||
"markbaker/complex": "^3.0",
|
"markbaker/complex": "^3.0",
|
||||||
"markbaker/matrix": "^3.0",
|
"markbaker/matrix": "^3.0",
|
||||||
"php": ">=7.4.0 <8.5.0",
|
"php": ">=7.4.0 <8.5.0",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
"psr/http-client": "^1.0",
|
"psr/http-client": "^1.0",
|
||||||
"psr/http-factory": "^1.0",
|
"psr/http-factory": "^1.0",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"doctrine/instantiator": "^1.5",
|
||||||
|
=======
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
||||||
"friendsofphp/php-cs-fixer": "^3.2",
|
"friendsofphp/php-cs-fixer": "^3.2",
|
||||||
"mitoteam/jpgraph": "^10.3",
|
"mitoteam/jpgraph": "^10.3",
|
||||||
@@ -4265,6 +4318,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Adrien Crivelli"
|
"name": "Adrien Crivelli"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Owen Leibman"
|
||||||
|
=======
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||||
@@ -4281,9 +4340,15 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2"
|
||||||
|
},
|
||||||
|
"time": "2026-01-11T05:58:24+00:00"
|
||||||
|
=======
|
||||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
|
||||||
},
|
},
|
||||||
"time": "2025-10-26T16:01:04+00:00"
|
"time": "2025-10-26T16:01:04+00:00"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Name
|
| Application Name
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| This value is the name of your application, which will be used when the
|
|
||||||
| framework needs to place the application's name in a notification or
|
|
||||||
| other UI elements where an application name needs to be displayed.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
@@ -19,11 +14,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Environment
|
| Application Environment
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| This value determines the "environment" your application is currently
|
|
||||||
| running in. This may determine how you prefer to configure various
|
|
||||||
| services the application utilizes. Set this in your ".env" file.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'env' => env('APP_ENV', 'production'),
|
'env' => env('APP_ENV', 'production'),
|
||||||
@@ -32,11 +22,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Debug Mode
|
| Application Debug Mode
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| When your application is in debug mode, detailed error messages with
|
|
||||||
| stack traces will be shown on every error that occurs within your
|
|
||||||
| application. If disabled, a simple generic error page is shown.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'debug' => (bool) env('APP_DEBUG', false),
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
@@ -45,11 +30,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application URL
|
| Application URL
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| This URL is used by the console to properly generate URLs when using
|
|
||||||
| the Artisan command line tool. You should set this to the root of
|
|
||||||
| the application so that it's available within Artisan commands.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'url' => env('APP_URL', 'http://localhost'),
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
@@ -58,11 +38,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Timezone
|
| Application Timezone
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| Here you may specify the default timezone for your application, which
|
|
||||||
| will be used by the PHP date and date-time functions. The timezone
|
|
||||||
| is set to "UTC" by default as it is suitable for most use cases.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'UTC',
|
||||||
@@ -71,11 +46,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Locale Configuration
|
| Application Locale Configuration
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| The application locale determines the default locale that will be used
|
|
||||||
| by Laravel's translation / localization methods. This option can be
|
|
||||||
| set to any locale for which you plan to have translation strings.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'locale' => env('APP_LOCALE', 'en'),
|
'locale' => env('APP_LOCALE', 'en'),
|
||||||
@@ -88,11 +58,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Encryption Key
|
| Encryption Key
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| This key is utilized by Laravel's encryption services and should be set
|
|
||||||
| to a random, 32 character string to ensure that all encrypted values
|
|
||||||
| are secure. You should do this prior to deploying the application.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'cipher' => 'AES-256-CBC',
|
'cipher' => 'AES-256-CBC',
|
||||||
@@ -109,13 +74,6 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Maintenance Mode Driver
|
| Maintenance Mode Driver
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| These configuration options determine the driver used to determine and
|
|
||||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
|
||||||
| allow maintenance mode to be controlled across multiple machines.
|
|
||||||
|
|
|
||||||
| Supported drivers: "file", "cache"
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'maintenance' => [
|
'maintenance' => [
|
||||||
@@ -123,4 +81,53 @@ return [
|
|||||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Class Aliases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
'aliases' => [
|
||||||
|
|
||||||
|
'App' => Illuminate\Support\Facades\App::class,
|
||||||
|
'Arr' => Illuminate\Support\Arr::class,
|
||||||
|
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||||
|
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||||
|
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||||
|
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
|
||||||
|
'Bus' => Illuminate\Support\Facades\Bus::class,
|
||||||
|
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||||
|
'Config' => Illuminate\Support\Facades\Config::class,
|
||||||
|
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||||
|
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||||
|
'DB' => Illuminate\Support\Facades\DB::class,
|
||||||
|
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||||
|
'Event' => Illuminate\Support\Facades\Event::class,
|
||||||
|
'File' => Illuminate\Support\Facades\File::class,
|
||||||
|
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||||
|
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||||
|
'Http' => Illuminate\Support\Facades\Http::class,
|
||||||
|
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||||
|
'Log' => Illuminate\Support\Facades\Log::class,
|
||||||
|
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||||
|
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||||
|
'Password' => Illuminate\Support\Facades\Password::class,
|
||||||
|
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||||
|
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
|
||||||
|
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||||
|
'Request' => Illuminate\Support\Facades\Request::class,
|
||||||
|
'Response' => Illuminate\Support\Facades\Response::class,
|
||||||
|
'Route' => Illuminate\Support\Facades\Route::class,
|
||||||
|
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||||
|
'Session' => Illuminate\Support\Facades\Session::class,
|
||||||
|
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||||
|
'Str' => Illuminate\Support\Str::class,
|
||||||
|
'URL' => Illuminate\Support\Facades\URL::class,
|
||||||
|
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||||
|
'View' => Illuminate\Support\Facades\View::class,
|
||||||
|
|
||||||
|
// ✅ Laravel‑Excel facade
|
||||||
|
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
380
config/excel.php
Normal file
380
config/excel.php
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Excel;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'exports' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Chunk size
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using FromQuery, the query is automatically chunked.
|
||||||
|
| Here you can specify how big the chunk should be.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'chunk_size' => 1000,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pre-calculate formulas during export
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
'pre_calculate_formulas' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable strict null comparison
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When enabling strict null comparison empty cells ('') will
|
||||||
|
| be added to the sheet.
|
||||||
|
*/
|
||||||
|
'strict_null_comparison' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| CSV Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'csv' => [
|
||||||
|
'delimiter' => ',',
|
||||||
|
'enclosure' => '"',
|
||||||
|
'line_ending' => PHP_EOL,
|
||||||
|
'use_bom' => false,
|
||||||
|
'include_separator_line' => false,
|
||||||
|
'excel_compatibility' => false,
|
||||||
|
'output_encoding' => '',
|
||||||
|
'test_auto_detect' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Worksheet properties
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. default title, creator, subject,...
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'properties' => [
|
||||||
|
'creator' => '',
|
||||||
|
'lastModifiedBy' => '',
|
||||||
|
'title' => '',
|
||||||
|
'description' => '',
|
||||||
|
'subject' => '',
|
||||||
|
'keywords' => '',
|
||||||
|
'category' => '',
|
||||||
|
'manager' => '',
|
||||||
|
'company' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'imports' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Read Only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with imports, you might only be interested in the
|
||||||
|
| data that the sheet exists. By default we ignore all styles,
|
||||||
|
| however if you want to do some logic based on style data
|
||||||
|
| you can enable it by setting read_only to false.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'read_only' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Ignore Empty
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with imports, you might be interested in ignoring
|
||||||
|
| rows that have null values or empty strings. By default rows
|
||||||
|
| containing empty strings or empty values are not ignored but can be
|
||||||
|
| ignored by enabling the setting ignore_empty to true.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'ignore_empty' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Heading Row Formatter
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the heading row formatter.
|
||||||
|
| Available options: none|slug|custom
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'heading_row' => [
|
||||||
|
'formatter' => 'slug',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| CSV Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'csv' => [
|
||||||
|
'delimiter' => null,
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_character' => '\\',
|
||||||
|
'contiguous' => false,
|
||||||
|
'input_encoding' => Csv::GUESS_ENCODING,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Worksheet properties
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. default title, creator, subject,...
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'properties' => [
|
||||||
|
'creator' => '',
|
||||||
|
'lastModifiedBy' => '',
|
||||||
|
'title' => '',
|
||||||
|
'description' => '',
|
||||||
|
'subject' => '',
|
||||||
|
'keywords' => '',
|
||||||
|
'category' => '',
|
||||||
|
'manager' => '',
|
||||||
|
'company' => '',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cell Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure middleware that is executed on getting a cell value
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'cells' => [
|
||||||
|
'middleware' => [
|
||||||
|
//\Maatwebsite\Excel\Middleware\TrimCellValue::class,
|
||||||
|
//\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Extension detector
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure here which writer/reader type should be used when the package
|
||||||
|
| needs to guess the correct type based on the extension alone.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'extension_detector' => [
|
||||||
|
'xlsx' => Excel::XLSX,
|
||||||
|
'xlsm' => Excel::XLSX,
|
||||||
|
'xltx' => Excel::XLSX,
|
||||||
|
'xltm' => Excel::XLSX,
|
||||||
|
'xls' => Excel::XLS,
|
||||||
|
'xlt' => Excel::XLS,
|
||||||
|
'ods' => Excel::ODS,
|
||||||
|
'ots' => Excel::ODS,
|
||||||
|
'slk' => Excel::SLK,
|
||||||
|
'xml' => Excel::XML,
|
||||||
|
'gnumeric' => Excel::GNUMERIC,
|
||||||
|
'htm' => Excel::HTML,
|
||||||
|
'html' => Excel::HTML,
|
||||||
|
'csv' => Excel::CSV,
|
||||||
|
'tsv' => Excel::TSV,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PDF Extension
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure here which Pdf driver should be used by default.
|
||||||
|
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'pdf' => Excel::DOMPDF,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Value Binder
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| PhpSpreadsheet offers a way to hook into the process of a value being
|
||||||
|
| written to a cell. In there some assumptions are made on how the
|
||||||
|
| value should be formatted. If you want to change those defaults,
|
||||||
|
| you can implement your own default value binder.
|
||||||
|
|
|
||||||
|
| Possible value binders:
|
||||||
|
|
|
||||||
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
|
||||||
|
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
|
||||||
|
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'value_binder' => [
|
||||||
|
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default cell caching driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
|
||||||
|
| dealing with large files, this might result into memory issues. If you
|
||||||
|
| want to mitigate that, you can configure a cell caching driver here.
|
||||||
|
| When using the illuminate driver, it will store each value in the
|
||||||
|
| cache store. This can slow down the process, because it needs to
|
||||||
|
| store each value. You can use the "batch" store if you want to
|
||||||
|
| only persist to the store when the memory limit is reached.
|
||||||
|
|
|
||||||
|
| Drivers: memory|illuminate|batch
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'driver' => 'memory',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Batch memory caching
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with the "batch" caching driver, it will only
|
||||||
|
| persist to the store when the memory limit is reached.
|
||||||
|
| Here you can tweak the memory limit to your liking.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'batch' => [
|
||||||
|
'memory_limit' => 60000,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Illuminate cache
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the "illuminate" caching driver, it will automatically use
|
||||||
|
| your default cache store. However if you prefer to have the cell
|
||||||
|
| cache on a separate store, you can configure the store name here.
|
||||||
|
| You can use any store defined in your cache config. When leaving
|
||||||
|
| at "null" it will use the default store.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'illuminate' => [
|
||||||
|
'store' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Time-to-live (TTL)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The TTL of items written to cache. If you want to keep the items cached
|
||||||
|
| indefinitely, set this to null. Otherwise, set a number of seconds,
|
||||||
|
| a \DateInterval, or a callable.
|
||||||
|
|
|
||||||
|
| Allowable types: callable|\DateInterval|int|null
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'default_ttl' => 10800,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Transaction Handler
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default the import is wrapped in a transaction. This is useful
|
||||||
|
| for when an import may fail and you want to retry it. With the
|
||||||
|
| transactions, the previous import gets rolled-back.
|
||||||
|
|
|
||||||
|
| You can disable the transaction handler by setting this to null.
|
||||||
|
| Or you can choose a custom made transaction handler here.
|
||||||
|
|
|
||||||
|
| Supported handlers: null|db
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'transactions' => [
|
||||||
|
'handler' => 'db',
|
||||||
|
'db' => [
|
||||||
|
'connection' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'temporary_files' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local Temporary Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When exporting and importing files, we use a temporary file, before
|
||||||
|
| storing reading or downloading. Here you can customize that path.
|
||||||
|
| permissions is an array with the permission flags for the directory (dir)
|
||||||
|
| and the create file (file).
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'local_path' => storage_path('framework/cache/laravel-excel'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local Temporary Path Permissions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Permissions is an array with the permission flags for the directory (dir)
|
||||||
|
| and the create file (file).
|
||||||
|
| If omitted the default permissions of the filesystem will be used.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'local_permissions' => [
|
||||||
|
// 'dir' => 0755,
|
||||||
|
// 'file' => 0644,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Remote Temporary Disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with a multi server setup with queues in which you
|
||||||
|
| cannot rely on having a shared local temporary path, you might
|
||||||
|
| want to store the temporary file on a shared disk. During the
|
||||||
|
| queue executing, we'll retrieve the temporary file from that
|
||||||
|
| location instead. When left to null, it will always use
|
||||||
|
| the local path. This setting only has effect when using
|
||||||
|
| in conjunction with queued imports and exports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'remote_disk' => null,
|
||||||
|
'remote_prefix' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Force Resync
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with a multi server setup as above, it's possible
|
||||||
|
| for the clean up that occurs after entire queue has been run to only
|
||||||
|
| cleanup the server that the last AfterImportJob runs on. The rest of the server
|
||||||
|
| would still have the local temporary file stored on it. In this case your
|
||||||
|
| local storage limits can be exceeded and future imports won't be processed.
|
||||||
|
| To mitigate this you can set this config value to be true, so that after every
|
||||||
|
| queued chunk is processed the local temporary file is deleted on the server that
|
||||||
|
| processed it.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'force_resync_remote' => null,
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -34,9 +34,6 @@ class CreateInvoiceItemsTable extends Migration
|
|||||||
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
|
|
||||||
$table->integer('container_row_index')->nullable()->after('container_id');
|
|
||||||
|
|
||||||
// FK
|
// FK
|
||||||
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||||
});
|
});
|
||||||
@@ -52,6 +49,4 @@ class CreateInvoiceItemsTable extends Migration
|
|||||||
});
|
});
|
||||||
Schema::dropIfExists('invoice_items');
|
Schema::dropIfExists('invoice_items');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('containers', function (Blueprint $table) {
|
Schema::table('containers', function (Blueprint $table) {
|
||||||
$table->string('status', 21)
|
$table->string('status', 20)
|
||||||
->default('pending')
|
->default('pending')
|
||||||
->after('container_date');
|
->after('container_date');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('invoice_charge_groups', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->unsignedBigInteger('invoice_id');
|
|
||||||
$table->string('group_name')->nullable(); // उदा. "FREIGHT", "HANDLING"
|
|
||||||
$table->enum('basis_type', ['ttl_qty', 'amount', 'ttl_cbm', 'ttl_kg']);
|
|
||||||
$table->decimal('basis_value', 15, 3)->default(0); // auto calculate केलेला total basis
|
|
||||||
$table->decimal('rate', 15, 3)->default(0); // per basis rate (helper)
|
|
||||||
$table->decimal('total_charge', 15, 2); // admin नी manually टाकलेला total
|
|
||||||
$table->timestamps();
|
|
||||||
|
|
||||||
$table->foreign('invoice_id')
|
|
||||||
->references('id')->on('invoices')
|
|
||||||
->onDelete('cascade');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('invoice_charge_groups');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('invoice_charge_group_items', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->unsignedBigInteger('group_id');
|
|
||||||
$table->unsignedBigInteger('invoice_item_id');
|
|
||||||
$table->timestamps();
|
|
||||||
|
|
||||||
$table->foreign('group_id')
|
|
||||||
->references('id')->on('invoice_charge_groups')
|
|
||||||
->onDelete('cascade');
|
|
||||||
|
|
||||||
$table->foreign('invoice_item_id')
|
|
||||||
->references('id')->on('invoice_items')
|
|
||||||
->onDelete('cascade');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('invoice_charge_group_items');
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
Schema::table('invoice_items', function (Blueprint $table) {
|
|
||||||
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
|
|
||||||
$table->integer('container_row_index')->nullable()->after('container_id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::table('invoice_items', function (Blueprint $table) {
|
|
||||||
$table->dropColumn(['container_id', 'container_row_index']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('invoices', function (Blueprint $table) {
|
|
||||||
// column आधीच आहे का हे check करून, नसेल तरच add करायचा
|
|
||||||
if (!Schema::hasColumn('invoices', 'due_date')) {
|
|
||||||
$table->date('due_date')
|
|
||||||
->nullable()
|
|
||||||
->after('invoice_date');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('invoices', function (Blueprint $table) {
|
|
||||||
if (Schema::hasColumn('invoices', 'due_date')) {
|
|
||||||
$table->dropColumn('due_date');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('containers', function (Blueprint $table) {
|
|
||||||
$table->string('status', 50)->change();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('containers', function (Blueprint $table) {
|
|
||||||
$table->string('status', 20)->change();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('invoice_charge_groups', function (Blueprint $table) {
|
|
||||||
$table->string('tax_type')->nullable()->after('total_charge');
|
|
||||||
$table->decimal('gst_percent', 5, 2)->default(0)->after('tax_type');
|
|
||||||
$table->decimal('total_with_gst', 15, 2)->default(0)->after('gst_percent');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('invoice_charge_groups', function (Blueprint $table) {
|
|
||||||
$table->dropColumn(['tax_type', 'gst_percent', 'total_with_gst']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
class AddChargeColumnsToInvoicesTable extends Migration
|
|
||||||
{
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
Schema::table('invoices', function (Blueprint $table) {
|
|
||||||
if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
|
|
||||||
$table->decimal('charge_groups_total', 15, 2)
|
|
||||||
->nullable()
|
|
||||||
->after('final_amount_with_gst');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) {
|
|
||||||
$table->decimal('grand_total_with_charges', 15, 2)
|
|
||||||
->nullable()
|
|
||||||
->after('charge_groups_total');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::table('invoices', function (Blueprint $table) {
|
|
||||||
if (Schema::hasColumn('invoices', 'charge_groups_total')) {
|
|
||||||
$table->dropColumn('charge_groups_total');
|
|
||||||
}
|
|
||||||
if (Schema::hasColumn('invoices', 'grand_total_with_charges')) {
|
|
||||||
$table->dropColumn('grand_total_with_charges');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
DB::statement("
|
|
||||||
ALTER TABLE `invoices`
|
|
||||||
MODIFY `status` ENUM('pending','paying','paid','overdue')
|
|
||||||
NOT NULL DEFAULT 'pending'
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
DB::statement("
|
|
||||||
ALTER TABLE `invoices`
|
|
||||||
MODIFY `status` ENUM('pending','paid','overdue')
|
|
||||||
NOT NULL DEFAULT 'pending'
|
|
||||||
");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -25,12 +25,6 @@ class PermissionSeeder extends Seeder
|
|||||||
// EXTRA (ORDERS)
|
// EXTRA (ORDERS)
|
||||||
'orders.view', // you added this separately
|
'orders.view', // you added this separately
|
||||||
|
|
||||||
// CONTAINER
|
|
||||||
'container.view',
|
|
||||||
'container.create',
|
|
||||||
'container.update',
|
|
||||||
'container.delete',
|
|
||||||
|
|
||||||
// SHIPMENT
|
// SHIPMENT
|
||||||
'shipment.view',
|
'shipment.view',
|
||||||
'shipment.create',
|
'shipment.create',
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,30 +1,31 @@
|
|||||||
import Echo from "laravel-echo";
|
import Echo from 'laravel-echo';
|
||||||
import Pusher from "pusher-js";
|
import Pusher from 'pusher-js';
|
||||||
|
|
||||||
window.Pusher = Pusher;
|
window.Pusher = Pusher;
|
||||||
|
|
||||||
console.log("[ECHO] Initializing Reverb...");
|
// Get CSRF token from meta tag
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||||
window.Echo = new Echo({
|
window.Echo = new Echo({
|
||||||
broadcaster: "reverb",
|
broadcaster: 'reverb',
|
||||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||||
|
|
||||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||||
wsPort: Number(import.meta.env.VITE_REVERB_PORT),
|
wsPort: import.meta.env.VITE_REVERB_PORT ?? 8080,
|
||||||
|
wssPort: import.meta.env.VITE_REVERB_PORT ?? 8080,
|
||||||
|
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'http') === 'https',
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
|
||||||
forceTLS: false,
|
authEndpoint: '/admin/broadcasting/auth',
|
||||||
disableStats: true,
|
|
||||||
|
|
||||||
authEndpoint: "/broadcasting/auth",
|
|
||||||
|
// ⭐ MOST IMPORTANT ⭐
|
||||||
|
withCredentials: true,
|
||||||
|
|
||||||
auth: {
|
auth: {
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-TOKEN": document
|
'X-CSRF-TOKEN': csrfToken,
|
||||||
.querySelector('meta[name="csrf-token"]')
|
'Accept': 'application/json',
|
||||||
?.getAttribute("content"),
|
}
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("[ECHO] Loaded Successfully!", window.Echo);
|
console.log('%c[ECHO] Initialized!', 'color: green; font-weight: bold;', window.Echo);
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
<div class="container-fluid py-2">
|
|
||||||
|
|
||||||
{{-- Top info cards (container / date / status) --}}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Container Name</small>
|
|
||||||
<div class="fw-semibold">{{ $container->container_name ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Container No</small>
|
|
||||||
<div class="fw-semibold">{{ $container->container_number ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Container Date</small>
|
|
||||||
<div class="fw-semibold">
|
|
||||||
{{ $container->container_date ? \Carbon\Carbon::parse($container->container_date)->format('d-m-Y') : '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Status</small>
|
|
||||||
<div class="fw-semibold text-capitalize">{{ $container->status ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Totals (CTN / Qty / CBM / KG) --}}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Total CTN</small>
|
|
||||||
<div class="fw-semibold">{{ $summary['total_ctn'] ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Total Qty</small>
|
|
||||||
<div class="fw-semibold">{{ $summary['total_qty'] ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Total CBM</small>
|
|
||||||
<div class="fw-semibold">{{ $summary['total_cbm'] ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="card p-2">
|
|
||||||
<small class="text-muted">Total KG</small>
|
|
||||||
<div class="fw-semibold">{{ $summary['total_kg'] ?? '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Excel rows – same headings as container_show --}}
|
|
||||||
@php
|
|
||||||
$allHeadings = [];
|
|
||||||
foreach ($container->rows as $row) {
|
|
||||||
if (is_array($row->data)) {
|
|
||||||
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="table-responsive" style="max-height: 500px; border-radius: 8px;">
|
|
||||||
<table class="table table-sm table-bordered align-middle">
|
|
||||||
<thead class="table-warning">
|
|
||||||
<tr>
|
|
||||||
<th style="width: 40px;">#</th>
|
|
||||||
@foreach($allHeadings as $heading)
|
|
||||||
<th>{{ $heading }}</th>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@forelse($container->rows as $index => $row)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $index + 1 }}</td>
|
|
||||||
@foreach($allHeadings as $heading)
|
|
||||||
@php
|
|
||||||
$val = is_array($row->data) ? ($row->data[$heading] ?? '') : '';
|
|
||||||
@endphp
|
|
||||||
<td>{{ $val }}</td>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<tr>
|
|
||||||
<td colspan="{{ count($allHeadings) + 1 }}" class="text-center text-muted py-3">
|
|
||||||
No Excel rows for this container.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -2603,12 +2603,11 @@ function renderPaymentTable(list){
|
|||||||
<td>${escapeHtml(entry.entry_date)}</td>
|
<td>${escapeHtml(entry.entry_date)}</td>
|
||||||
<td>${escapeHtml(entry.description)}</td>
|
<td>${escapeHtml(entry.description)}</td>
|
||||||
|
|
||||||
<!-- Order Quantity - Clickable number without box -->
|
|
||||||
<td>
|
<td>
|
||||||
<span onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"
|
<button type="button" class="entry-link"
|
||||||
style="cursor: pointer; color: #276dea; font-weight: 600; text-decoration: underline; text-decoration-color: #ccc;">
|
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')">
|
||||||
${entry.order_quantity ?? '-'}
|
${entry.order_quantity ?? '-'}
|
||||||
</span>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>${escapeHtml(entry.region)}</td>
|
<td>${escapeHtml(entry.region)}</td>
|
||||||
@@ -2681,6 +2680,7 @@ function renderPaymentTable(list){
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cycleToggle(btn) {
|
function cycleToggle(btn) {
|
||||||
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
|
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
|
||||||
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
|
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
|
||||||
|
|||||||
@@ -1,548 +1,96 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Chat Support Dashboard')
|
@section('page-title', 'Chat Support')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
--success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
||||||
--danger-gradient: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
|
|
||||||
--warning-gradient: linear-gradient(135deg, #f7971e 0%, #ffd200 100%);
|
|
||||||
--info-gradient: linear-gradient(135deg, #56ccf2 0%, #2f80ed 100%);
|
|
||||||
--card-shadow: 0 3px 10px rgba(0,0,0,0.05);
|
|
||||||
--card-shadow-hover: 0 6px 16px rgba(0,0,0,0.10);
|
|
||||||
--border-radius: 8px;
|
|
||||||
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
<div class="container py-4">
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-dashboard {
|
<h2 class="mb-4 fw-bold">Customer Support Chat</h2>
|
||||||
min-height: 100vh;
|
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-header {
|
<div class="card shadow-sm">
|
||||||
text-align: center;
|
<div class="card-body p-0">
|
||||||
margin-bottom: 1rem;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-title {
|
@if($tickets->count() === 0)
|
||||||
font-size: clamp(1.4rem, 2.5vw, 2rem);
|
<div class="p-4 text-center text-muted">
|
||||||
font-weight: 800;
|
<h5>No customer chats yet.</h5>
|
||||||
background: var(--primary-gradient);
|
</div>
|
||||||
-webkit-background-clip: text;
|
@else
|
||||||
background-clip: text;
|
<ul class="list-group list-group-flush">
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
margin: 0 0 0.4rem 0;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-title::before {
|
@foreach($tickets as $ticket)
|
||||||
content: '💬';
|
@php
|
||||||
position: absolute;
|
// Get last message
|
||||||
left: -1.6rem;
|
$lastMsg = $ticket->messages()->latest()->first();
|
||||||
top: 50%;
|
@endphp
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 1.3rem;
|
|
||||||
animation: bounce 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
<li class="list-group-item py-3">
|
||||||
0%, 20%, 50%, 80%, 100% { transform: translateY(-50%) translateY(0); }
|
|
||||||
40% { transform: translateY(-50%) translateY(-5px); }
|
|
||||||
60% { transform: translateY(-50%) translateY(-2px); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-subtitle {
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
max-width: 380px;
|
|
||||||
margin: 0 auto;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🔔 GLOBAL NEW MESSAGE COUNTER */
|
<!-- Left side: User info + last message -->
|
||||||
.global-notify {
|
<div class="d-flex align-items-center gap-3">
|
||||||
margin: 0 auto 0.75rem auto;
|
|
||||||
max-width: 320px;
|
|
||||||
padding: 0.35rem 0.7rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(15,23,42,0.03);
|
|
||||||
border: 1px dashed #cbd5f5;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
color: #1e293b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-notify-badge {
|
<!-- Profile Circle -->
|
||||||
background: #ef4444;
|
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center"
|
||||||
color: #fff;
|
style="width: 45px; height: 45px; font-size: 18px;">
|
||||||
min-width: 18px;
|
{{ strtoupper(substr($ticket->user->customer_name ?? $ticket->user->name, 0, 1)) }}
|
||||||
padding: 0 0.35rem;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 999px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 700;
|
|
||||||
box-shadow: 0 0 0 2px rgba(254, 226, 226, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-notify.d-none {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 0.75rem 0.9rem;
|
|
||||||
box-shadow: var(--card-shadow);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
/* max-height / overflow काढले, जेणेकरून बाहेरचा page scroll वापरला जाईल */
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.6rem;
|
|
||||||
padding-bottom: 0.45rem;
|
|
||||||
border-bottom: 1px solid #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-title {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1e293b;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-count {
|
|
||||||
background: var(--primary-gradient);
|
|
||||||
color: white;
|
|
||||||
padding: 0.1rem 0.45rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-item {
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
margin-bottom: 0.3rem;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
transition: var(--transition);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-item::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 2px;
|
|
||||||
background: var(--primary-gradient);
|
|
||||||
transform: scaleY(0);
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-item:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: var(--card-shadow-hover);
|
|
||||||
border-color: rgba(102, 126, 234, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-item:hover::before {
|
|
||||||
transform: scaleY(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.4rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-avatar {
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: var(--info-gradient);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: white;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-shadow: 0 3px 8px rgba(86, 204, 242, 0.25);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-avatar::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
|
||||||
transition: left 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-item:hover .ticket-avatar::after {
|
|
||||||
left: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-name {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1e293b;
|
|
||||||
margin: 0 0 0.08rem 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unread-count {
|
|
||||||
background: #ef4444;
|
|
||||||
color: white;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 999px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
font-weight: 700;
|
|
||||||
box-shadow: 0 2px 6px rgba(239, 68, 68, 0.4);
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { transform: scale(1); }
|
|
||||||
50% { transform: scale(1.05); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-preview {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
line-height: 1.25;
|
|
||||||
margin-bottom: 0.1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
max-height: 1.8em;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-time {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
color: #94a3b8;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.18rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-time svg {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-top: 0.3rem;
|
|
||||||
border-top: 1px dashed #e5e7eb;
|
|
||||||
margin-top: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
padding: 0.14rem 0.45rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-open { background: var(--success-gradient); color: white; }
|
|
||||||
.status-closed{ background: var(--danger-gradient); color: white; }
|
|
||||||
|
|
||||||
.chat-btn {
|
|
||||||
background: var(--primary-gradient);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: var(--transition);
|
|
||||||
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.25);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.35);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-btn::after {
|
|
||||||
content: '→';
|
|
||||||
transition: margin-left 0.25s ease;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-btn:hover::after {
|
|
||||||
margin-left: 0.18rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1.6rem 1rem;
|
|
||||||
background: rgba(255, 255, 255, 0.6);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
border: 2px dashed #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 2.8rem;
|
|
||||||
margin-bottom: 0.9rem;
|
|
||||||
display: block;
|
|
||||||
animation: float 3s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes float {
|
|
||||||
0%, 100% { transform: translateY(0px); }
|
|
||||||
50% { transform: translateY(-8px); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-title {
|
|
||||||
font-size: 1.05rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1e293b;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-subtitle {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-id {
|
|
||||||
background: rgba(102, 126, 234, 0.08);
|
|
||||||
color: #667eea;
|
|
||||||
padding: 0.12rem 0.5rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.65rem;
|
|
||||||
font-weight: 600;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.18rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-message-dot {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.45rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
width: 7px;
|
|
||||||
height: 7px;
|
|
||||||
background: #ef4444;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid white;
|
|
||||||
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.3);
|
|
||||||
animation: blink 1.5s infinite;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 100% { opacity: 1; transform: scale(1); }
|
|
||||||
50% { opacity: 0.5; transform: scale(1.1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* इथे आता inner scroll नाही */
|
|
||||||
.tickets-list {
|
|
||||||
/* flex: 1; काढला, overflow काढला, parent + body scroll वापरेल */
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-list::-webkit-scrollbar {
|
|
||||||
width: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-list::-webkit-scrollbar-track {
|
|
||||||
background: #f1f5f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-list::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--primary-gradient);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.chat-dashboard {
|
|
||||||
padding: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tickets-container {
|
|
||||||
/* max-height काढलेले, mobile वरही outer scroll */
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-footer {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.35rem;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-btn {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="chat-dashboard">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="dashboard-header">
|
|
||||||
<h1 class="dashboard-title">Live Chat Dashboard</h1>
|
|
||||||
<p class="dashboard-subtitle">
|
|
||||||
Monitor customer conversations with real-time updates
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🔔 GLOBAL NEW MESSAGES NOTIFICATION -->
|
|
||||||
<div id="globalNewMessageBox" class="global-notify d-none">
|
|
||||||
<span>New messages:</span>
|
|
||||||
<span id="globalNewMessageCount" class="global-notify-badge">0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tickets Container -->
|
|
||||||
<div class="tickets-container">
|
|
||||||
<div class="tickets-header">
|
|
||||||
<div>
|
|
||||||
<h2 class="tickets-title">
|
|
||||||
📋 Active Conversations
|
|
||||||
<span class="tickets-count">{{ $tickets->count() }}</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($tickets->count() === 0)
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div class="empty-state">
|
|
||||||
<div class="empty-icon">💬</div>
|
|
||||||
<h3 class="empty-title">No Active Conversations</h3>
|
|
||||||
<p class="empty-subtitle">
|
|
||||||
Customer conversations will appear here with real-time notifications.
|
|
||||||
</p>
|
|
||||||
<div class="ticket-id">Ready for support requests</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<!-- Tickets List -->
|
|
||||||
<div class="tickets-list">
|
|
||||||
@foreach($tickets as $ticket)
|
|
||||||
<div class="ticket-item" data-ticket-id="{{ $ticket->id }}">
|
|
||||||
@if($ticket->unread_count > 0)
|
|
||||||
<div class="new-message-dot"></div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="ticket-header">
|
|
||||||
<div class="ticket-avatar">
|
|
||||||
{{ strtoupper(substr($ticket->user->customer_name ?? $ticket->user->name, 0, 1)) }}
|
|
||||||
</div>
|
|
||||||
<div class="ticket-content">
|
|
||||||
<div class="ticket-name">
|
|
||||||
{{ $ticket->user->customer_name ?? $ticket->user->name }}
|
|
||||||
@if($ticket->unread_count > 0)
|
|
||||||
<span id="badge-{{ $ticket->id }}" class="unread-count">
|
|
||||||
{{ $ticket->unread_count }}
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@php
|
|
||||||
$lastMsg = $ticket->messages()->latest()->first();
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="ticket-preview">
|
|
||||||
@if($lastMsg)
|
|
||||||
@if($lastMsg->message)
|
|
||||||
{{ Str::limit($lastMsg->message, 45) }}
|
|
||||||
@elseif(Str::startsWith($lastMsg->file_type, 'image'))
|
|
||||||
📷 Photo shared
|
|
||||||
@else
|
|
||||||
📎 File attached
|
|
||||||
@endif
|
|
||||||
@else
|
|
||||||
<em>Conversation started</em>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($lastMsg)
|
|
||||||
<div class="ticket-time">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>
|
|
||||||
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>
|
|
||||||
</svg>
|
|
||||||
{{ $lastMsg->created_at->diffForHumans() }}
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ticket-footer">
|
<div>
|
||||||
<span class="status-badge status-{{ $ticket->status }}">
|
<!-- Customer Name -->
|
||||||
{{ ucfirst($ticket->status) }}
|
<h6 class="mb-1 fw-semibold">
|
||||||
</span>
|
{{ $ticket->user->customer_name ?? $ticket->user->name }}
|
||||||
<a href="{{ route('admin.chat.open', $ticket->id) }}" class="chat-btn">
|
</h6>
|
||||||
Open Chat
|
|
||||||
</a>
|
<!-- Last message preview -->
|
||||||
</div>
|
<small class="text-muted">
|
||||||
</div>
|
@if($lastMsg)
|
||||||
@endforeach
|
@if($lastMsg->message)
|
||||||
</div>
|
{{ Str::limit($lastMsg->message, 35) }}
|
||||||
@endif
|
@elseif($lastMsg->file_type === 'image')
|
||||||
|
📷 Image
|
||||||
|
@elseif($lastMsg->file_type === 'video')
|
||||||
|
🎥 Video
|
||||||
|
@else
|
||||||
|
📎 Attachment
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<i>No messages yet</i>
|
||||||
|
@endif
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Side: Status + Button -->
|
||||||
|
<div class="text-end">
|
||||||
|
|
||||||
|
<!-- Ticket Status -->
|
||||||
|
<span class="badge
|
||||||
|
{{ $ticket->status === 'open' ? 'bg-success' : 'bg-danger' }}">
|
||||||
|
{{ ucfirst($ticket->status) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- Open Chat Button -->
|
||||||
|
<a href="{{ route('admin.chat.open', $ticket->id) }}"
|
||||||
|
class="btn btn-sm btn-primary ms-2">
|
||||||
|
Open Chat →
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -98,7 +98,9 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-title i { color: var(--primary-color); }
|
.filter-title i {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.filter-grid {
|
.filter-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -106,7 +108,9 @@
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group { position: relative; }
|
.filter-group {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-group label {
|
.filter-group label {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -135,7 +139,9 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(76, 111, 255, 0.1);
|
box-shadow: 0 0 0 3px rgba(76, 111, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-input::placeholder { color: #94a3b8; }
|
.filter-input::placeholder {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-actions {
|
.filter-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -206,7 +212,9 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header h2 i { color: white; }
|
.card-header h2 i {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-badge {
|
.stats-badge {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
@@ -231,7 +239,9 @@
|
|||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-item:last-child { border-bottom: none; }
|
.container-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
.container-header {
|
.container-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -287,92 +297,40 @@
|
|||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STATUS DROPDOWN (badge look) */
|
.status-badge {
|
||||||
.status-dropdown {
|
padding: 6px 16px;
|
||||||
position: relative;
|
border-radius: 20px;
|
||||||
min-width: 190px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dropdown-toggle {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--dark-text);
|
letter-spacing: 0.3px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dropdown-toggle span { white-space: nowrap; }
|
.status-badge i {
|
||||||
|
font-size: 10px;
|
||||||
.status-dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: -230%;
|
|
||||||
right: 0;
|
|
||||||
z-index: 30;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 8px 0;
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
width: 220px;
|
|
||||||
max-height: 340px;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dropdown-menu.open { display: block; }
|
.status-pending {
|
||||||
|
background: #fef3c7;
|
||||||
.status-option {
|
color: #d97706;
|
||||||
padding: 6px 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--dark-text);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: background 0.15s;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-option:hover { background: #eef2ff; }
|
.status-in-progress {
|
||||||
|
background: #dbeafe;
|
||||||
.status-option .dot {
|
color: #1d4ed8;
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: #9ca3af;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-option.active .dot {
|
.status-completed {
|
||||||
background: #22c55e;
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* COLOR MAPPING per status – dropdown tint + main toggle text color */
|
.status-cancelled {
|
||||||
.status-option.status-container-ready { background: #eff6ff; color: #1d4ed8; }
|
background: #fee2e2;
|
||||||
.status-option.status-export-custom { background: #fff7ed; color: #b45309; }
|
color: #991b1b;
|
||||||
.status-option.status-international-transit { background: #f5f3ff; color: #4c1d95; }
|
}
|
||||||
.status-option.status-arrived-at-india { background: #ecfdf5; color: #15803d; }
|
|
||||||
.status-option.status-import-custom { background: #fffbeb; color: #92400e; }
|
|
||||||
.status-option.status-warehouse { background: #f4f4f5; color: #374151; }
|
|
||||||
.status-option.status-domestic-distribution { background: #faf5ff; color: #6d28d9; }
|
|
||||||
.status-option.status-out-for-delivery { background: #eff6ff; color: #1d4ed8; }
|
|
||||||
.status-option.status-delivered { background: #ecfdf5; color: #15803d; }
|
|
||||||
|
|
||||||
.status-dropdown-toggle.status-container-ready { background: #eff6ff; color: #1d4ed8; }
|
|
||||||
.status-dropdown-toggle.status-export-custom { background: #fff7ed; color: #b45309; }
|
|
||||||
.status-dropdown-toggle.status-international-transit { background: #f5f3ff; color: #4c1d95; }
|
|
||||||
.status-dropdown-toggle.status-arrived-at-india { background: #ecfdf5; color: #15803d; }
|
|
||||||
.status-dropdown-toggle.status-import-custom { background: #fffbeb; color: #92400e; }
|
|
||||||
.status-dropdown-toggle.status-warehouse { background: #f4f4f5; color: #374151; }
|
|
||||||
.status-dropdown-toggle.status-domestic-distribution { background: #faf5ff; color: #6d28d9; }
|
|
||||||
.status-dropdown-toggle.status-out-for-delivery { background: #eff6ff; color: #1d4ed8; }
|
|
||||||
.status-dropdown-toggle.status-delivered { background: #ecfdf5; color: #15803d; }
|
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -415,7 +373,39 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-form { position: relative; }
|
.update-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--dark-text);
|
||||||
|
background: white;
|
||||||
|
min-width: 140px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-btn {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-btn:hover {
|
||||||
|
background: #3b5de6;
|
||||||
|
}
|
||||||
|
|
||||||
.no-results {
|
.no-results {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -454,13 +444,16 @@
|
|||||||
animation: slideIn 0.3s ease;
|
animation: slideIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success-message i { font-size: 20px; }
|
.success-message i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from { opacity: 0; transform: translateY(-10px); }
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 🔥 Totals section */
|
||||||
.totals-section {
|
.totals-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
@@ -511,7 +504,9 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.filter-grid { grid-template-columns: 1fr; }
|
.filter-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
.container-header {
|
.container-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -524,8 +519,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
.update-form { width: 100%; }
|
.update-form {
|
||||||
.status-dropdown { width: 100%; }
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.status-select, .update-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -537,12 +537,10 @@
|
|||||||
Manage all containers, track status, and view entries in real-time
|
Manage all containers, track status, and view entries in real-time
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@can('container.create')
|
|
||||||
<a href="{{ route('containers.create') }}" class="add-container-btn">
|
<a href="{{ route('containers.create') }}" class="add-container-btn">
|
||||||
<i class="fas fa-plus-circle"></i>
|
<i class="fas fa-plus-circle"></i>
|
||||||
Add New Container
|
Add New Container
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
@@ -569,15 +567,10 @@
|
|||||||
<label><i class="fas fa-tag"></i> Status</label>
|
<label><i class="fas fa-tag"></i> Status</label>
|
||||||
<select name="status" class="filter-select">
|
<select name="status" class="filter-select">
|
||||||
<option value="">All Status</option>
|
<option value="">All Status</option>
|
||||||
<option value="container-ready" {{ request('status') == 'container-ready' ? 'selected' : '' }}>Container Ready</option>
|
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
<option value="export-custom" {{ request('status') == 'export-custom' ? 'selected' : '' }}>Export Custom</option>
|
<option value="in-progress" {{ request('status') == 'in-progress' ? 'selected' : '' }}>In Progress</option>
|
||||||
<option value="international-transit" {{ request('status') == 'international-transit' ? 'selected' : '' }}>International Transit</option>
|
<option value="completed" {{ request('status') == 'completed' ? 'selected' : '' }}>Completed</option>
|
||||||
<option value="arrived-at-india" {{ request('status') == 'arrived-at-india' ? 'selected' : '' }}>Arrived at India</option>
|
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
||||||
<option value="import-custom" {{ request('status') == 'import-custom' ? 'selected' : '' }}>Import Custom</option>
|
|
||||||
<option value="warehouse" {{ request('status') == 'warehouse' ? 'selected' : '' }}>Warehouse</option>
|
|
||||||
<option value="domestic-distribution" {{ request('status') == 'domestic-distribution' ? 'selected' : '' }}>Domestic Distribution</option>
|
|
||||||
<option value="out-for-delivery" {{ request('status') == 'out-for-delivery' ? 'selected' : '' }}>Out for Delivery</option>
|
|
||||||
<option value="delivered" {{ request('status') == 'delivered' ? 'selected' : '' }}>Delivered</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -615,31 +608,22 @@
|
|||||||
<p>Get started by creating your first container</p>
|
<p>Get started by creating your first container</p>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@php
|
|
||||||
$labels = [
|
|
||||||
'container-ready' => 'Container Ready',
|
|
||||||
'export-custom' => 'Export Custom',
|
|
||||||
'international-transit' => 'International Transit',
|
|
||||||
'arrived-at-india' => 'Arrived at India',
|
|
||||||
'import-custom' => 'Import Custom',
|
|
||||||
'warehouse' => 'Warehouse',
|
|
||||||
'domestic-distribution' => 'Domestic Distribution',
|
|
||||||
'out-for-delivery' => 'Out for Delivery',
|
|
||||||
'delivered' => 'Delivered',
|
|
||||||
];
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
@foreach($containers as $container)
|
@foreach($containers as $container)
|
||||||
@php
|
@php
|
||||||
$status = $container->status ?? 'container-ready';
|
$status = $container->status;
|
||||||
$statusLabel = $labels[$status] ?? ucfirst(str_replace('-', ' ', $status));
|
$statusClass = match ($status) {
|
||||||
|
'completed' => 'status-completed',
|
||||||
|
'in-progress' => 'status-in-progress',
|
||||||
|
'cancelled' => 'status-cancelled',
|
||||||
|
default => 'status-pending',
|
||||||
|
};
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="container-item">
|
<div class="container-item">
|
||||||
<div class="container-header">
|
<div class="container-header">
|
||||||
<div class="container-info">
|
<div class="container-info">
|
||||||
<div class="container-avatar">
|
<div class="container-avatar">
|
||||||
{{ strtoupper(substr($container->container_name, 0, 2)) }}
|
{{ substr($container->container_name, 0, 2) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="container-details">
|
<div class="container-details">
|
||||||
<h3>{{ $container->container_name }}</h3>
|
<h3>{{ $container->container_name }}</h3>
|
||||||
@@ -661,56 +645,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@can('containers.update_status')
|
<span class="status-badge {{ $statusClass }}">
|
||||||
<form action="{{ route('containers.update-status', $container->id) }}"
|
<i class="fas fa-circle"></i>
|
||||||
method="POST"
|
{{ ucfirst(str_replace('-', ' ', $status)) }}
|
||||||
class="update-form ajax-status-form"
|
</span>
|
||||||
data-container-id="{{ $container->id }}">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
@php $statusClass = 'status-' . $status; @endphp
|
<a href="{{ route('containers.show', $container->id) }}" class="action-btn view-btn">
|
||||||
|
<i class="fas fa-eye"></i> View
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="status-dropdown">
|
<form action="{{ route('containers.update-status', $container->id) }}"
|
||||||
<div class="status-dropdown-toggle {{ $statusClass }}">
|
method="POST" class="update-form">
|
||||||
<span class="status-dropdown-label">
|
@csrf
|
||||||
{{ $statusLabel }}
|
<select name="status" class="status-select">
|
||||||
</span>
|
<option value="pending" {{ $status === 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
<i class="fas fa-chevron-down" style="font-size:11px;color:#4b5563;"></i>
|
<option value="in-progress" {{ $status === 'in-progress' ? 'selected' : '' }}>In Progress</option>
|
||||||
</div>
|
<option value="completed" {{ $status === 'completed' ? 'selected' : '' }}>Completed</option>
|
||||||
<div class="status-dropdown-menu">
|
<option value="cancelled" {{ $status === 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
||||||
@foreach($labels as $value => $label)
|
</select>
|
||||||
@php $optClass = 'status-' . $value; @endphp
|
<button type="submit" class="update-btn">
|
||||||
<div class="status-option {{ $optClass }} {{ $status === $value ? 'active' : '' }}"
|
<i class="fas fa-sync-alt"></i> Update
|
||||||
data-status="{{ $value }}">
|
</button>
|
||||||
<span class="dot"></span>
|
</form>
|
||||||
<span>{{ $label }}</span>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@endcan
|
|
||||||
|
|
||||||
@can('container.update')
|
<form action="{{ route('containers.destroy', $container->id) }}" method="POST"
|
||||||
<a href="{{ route('containers.show', $container->id) }}" class="action-btn view-btn">
|
onsubmit="return confirm('Are you sure you want to delete this container and all its entries?');">
|
||||||
<i class="fas fa-eye"></i> View
|
@csrf
|
||||||
</a>
|
@method('DELETE')
|
||||||
@endcan
|
<button type="submit" class="action-btn delete-btn">
|
||||||
|
<i class="fas fa-trash"></i> Delete
|
||||||
@can('container.delete')
|
</button>
|
||||||
<form action="{{ route('containers.destroy', $container->id) }}" method="POST"
|
</form>
|
||||||
class="delete-form"
|
|
||||||
data-container-id="{{ $container->id }}">
|
|
||||||
@csrf
|
|
||||||
@method('DELETE')
|
|
||||||
<button type="submit" class="action-btn delete-btn">
|
|
||||||
<i class="fas fa-trash"></i> Delete
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
@endcan
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔥 Totals instead of first row preview -->
|
||||||
<div class="totals-section">
|
<div class="totals-section">
|
||||||
<div class="total-card">
|
<div class="total-card">
|
||||||
<div class="total-value">{{ number_format($container->summary['total_ctn'], 1) }}</div>
|
<div class="total-value">{{ number_format($container->summary['total_ctn'], 1) }}</div>
|
||||||
@@ -735,121 +704,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
// STATUS DROPDOWN
|
|
||||||
document.querySelectorAll('.status-dropdown').forEach(function (wrapper) {
|
|
||||||
const toggle = wrapper.querySelector('.status-dropdown-toggle');
|
|
||||||
const menu = wrapper.querySelector('.status-dropdown-menu');
|
|
||||||
|
|
||||||
toggle.addEventListener('click', function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
document.querySelectorAll('.status-dropdown-menu.open').forEach(m => {
|
|
||||||
if (m !== menu) m.classList.remove('open');
|
|
||||||
});
|
|
||||||
menu.classList.toggle('open');
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.querySelectorAll('.status-option').forEach(function (opt) {
|
|
||||||
opt.addEventListener('click', function () {
|
|
||||||
const status = this.dataset.status;
|
|
||||||
const form = wrapper.closest('form');
|
|
||||||
const labelEl = wrapper.querySelector('.status-dropdown-label');
|
|
||||||
const toggleEl= wrapper.querySelector('.status-dropdown-toggle');
|
|
||||||
|
|
||||||
// UI: dropdown label + active item
|
|
||||||
menu.querySelectorAll('.status-option').forEach(o => o.classList.remove('active'));
|
|
||||||
this.classList.add('active');
|
|
||||||
labelEl.textContent = this.querySelector('span:nth-child(2)').textContent;
|
|
||||||
menu.classList.remove('open');
|
|
||||||
|
|
||||||
// toggle रंग class reset करून नवा status-* दे
|
|
||||||
toggleEl.className = 'status-dropdown-toggle';
|
|
||||||
toggleEl.classList.add('status-' + status);
|
|
||||||
|
|
||||||
const url = form.getAttribute('action');
|
|
||||||
const token = form.querySelector('input[name="_token"]').value;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('_token', token);
|
|
||||||
formData.append('status', status);
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(async res => {
|
|
||||||
let data = null;
|
|
||||||
try { data = await res.json(); } catch (e) {}
|
|
||||||
if (!res.ok || !data || !data.success) {
|
|
||||||
alert('Status update failed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
alert('Network error while updating status');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function () {
|
|
||||||
document.querySelectorAll('.status-dropdown-menu.open')
|
|
||||||
.forEach(m => m.classList.remove('open'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// DELETE VIA AJAX
|
|
||||||
document.querySelectorAll('.delete-form').forEach(function (form) {
|
|
||||||
form.addEventListener('submit', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!confirm('Are you sure you want to delete this container and all its entries?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = form.getAttribute('action');
|
|
||||||
const token = form.querySelector('input[name="_token"]').value;
|
|
||||||
const item = form.closest('.container-item');
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('_token', token);
|
|
||||||
formData.append('_method', 'DELETE');
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(async res => {
|
|
||||||
let data = null;
|
|
||||||
try { data = await res.json(); } catch (e) {}
|
|
||||||
|
|
||||||
if (!res.ok || !data || !data.success) {
|
|
||||||
alert('Delete failed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item.style.opacity = '0';
|
|
||||||
item.style.transform = 'translateX(-10px)';
|
|
||||||
setTimeout(() => item.remove(), 200);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
alert('Network error while deleting container');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
.cm-add-wrapper { padding: 10px 0 20px 0; }
|
.cm-add-wrapper {
|
||||||
|
padding: 10px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-add-header-card {
|
.cm-add-header-card {
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -13,6 +16,7 @@
|
|||||||
box-shadow: 0 6px 18px rgba(15,35,52,0.18);
|
box-shadow: 0 6px 18px rgba(15,35,52,0.18);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-add-header-card .card-body {
|
.cm-add-header-card .card-body {
|
||||||
padding: 14px 18px;
|
padding: 14px 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -20,73 +24,82 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.cm-add-title { margin: 0; font-size: 20px; font-weight: 600; }
|
|
||||||
.cm-add-sub { font-size: 12px; opacity: 0.9; }
|
.cm-add-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-add-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-add-main-card {
|
.cm-add-main-card {
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 6px 18px rgba(15,35,52,0.12);
|
box-shadow: 0 6px 18px rgba(15,35,52,0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-add-main-card .card-header {
|
.cm-add-main-card .card-header {
|
||||||
background:#ffffff;
|
background:#ffffff;
|
||||||
border-bottom: 1px solid #edf0f5;
|
border-bottom: 1px solid #edf0f5;
|
||||||
padding: 10px 18px;
|
padding: 10px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-add-main-card .card-header h5 {
|
.cm-add-main-card .card-header h5 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-form-label { font-size: 13px; font-weight: 500; color:#495057; margin-bottom: 4px; }
|
.cm-form-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color:#495057;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-form-control {
|
.cm-form-control {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border:1px solid #d0d7e2;
|
border:1px solid #d0d7e2;
|
||||||
padding: 8px 11px;
|
padding: 8px 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-form-control:focus {
|
.cm-form-control:focus {
|
||||||
border-color:#4c6fff;
|
border-color:#4c6fff;
|
||||||
box-shadow:0 0 0 0.15rem rgba(76,111,255,.25);
|
box-shadow:0 0 0 0.15rem rgba(76,111,255,.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-help-text { font-size: 11px; color:#868e96; margin-top: 2px; }
|
.cm-help-text {
|
||||||
.cm-btn-primary { border-radius: 20px; padding: 6px 22px; font-size: 13px; font-weight: 500; }
|
font-size: 11px;
|
||||||
.cm-btn-secondary { border-radius: 20px; padding: 6px 18px; font-size: 13px; }
|
color:#868e96;
|
||||||
|
margin-top: 2px;
|
||||||
.error-card {
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid #f5c2c7;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-row-box {
|
.cm-btn-primary {
|
||||||
border: 1px solid #e9ecef;
|
border-radius: 20px;
|
||||||
border-radius: 8px;
|
padding: 6px 22px;
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-item {
|
|
||||||
font-size: 12px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-item span {
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-btn-secondary {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 6px 18px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-error-list {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid cm-add-wrapper">
|
<div class="container-fluid cm-add-wrapper">
|
||||||
|
|
||||||
|
{{-- TOP GRADIENT HEADER --}}
|
||||||
<div class="card cm-add-header-card">
|
<div class="card cm-add-header-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div>
|
<div>
|
||||||
@@ -101,23 +114,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- MAIN CARD --}}
|
||||||
<div class="card cm-add-main-card">
|
<div class="card cm-add-main-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5>Add Container</h5>
|
<h5>Add Container</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
{{-- SUCCESS --}}
|
{{-- SUCCESS MESSAGE --}}
|
||||||
@if (session('success'))
|
@if (session('success'))
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
{{ session('success') }}
|
{{ session('success') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- VALIDATION --}}
|
{{-- VALIDATION ERRORS --}}
|
||||||
@if ($errors->any())
|
@if ($errors->any())
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<ul class="mb-0">
|
<ul class="mb-0 cm-error-list">
|
||||||
@foreach ($errors->all() as $error)
|
@foreach ($errors->all() as $error)
|
||||||
<li>{{ $error }}</li>
|
<li>{{ $error }}</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
@@ -125,183 +139,93 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- COMBINED ERROR PANEL (summary text) --}}
|
{{-- UNMATCHED ROWS TABLE --}}
|
||||||
@if(session('formula_errors') || session('mark_errors'))
|
@if (session('unmatched_rows'))
|
||||||
|
<div class="alert alert-warning mt-3">
|
||||||
|
<strong>Mark number not matched:</strong>
|
||||||
|
|
||||||
<div class="card error-card mb-3">
|
|
||||||
<div class="card-header bg-danger text-white small">
|
|
||||||
⚠ Excel Validation Issues Found
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="small mb-1">
|
|
||||||
Some rows in your Excel file have formula or mark issues.
|
|
||||||
See the table below, and detailed messages after the table.
|
|
||||||
</div>
|
|
||||||
<ul class="small mb-0 ps-3">
|
|
||||||
<li>Red highlighted rows indicate formula mismatches in the uploaded Excel data.</li>
|
|
||||||
<li>Yellow highlighted rows indicate marks from the Excel file that do not match any record in the system.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- 1) Excel-style table at top --}}
|
@php
|
||||||
@php
|
$unmatchedRows = session('unmatched_rows');
|
||||||
$formulaErrors = session('formula_errors') ?? [];
|
$headings = [];
|
||||||
$markErrors = session('mark_errors') ?? [];
|
if (!empty($unmatchedRows)) {
|
||||||
|
$headings = array_keys($unmatchedRows[0]['data'] ?? []);
|
||||||
$allRowsData = [];
|
// इथे Excel मधला 'MARK' कॉलम hide करतो, कारण आधीच Mark No वेगळा column आहे
|
||||||
|
$headings = array_filter($headings, function ($h) {
|
||||||
foreach ($formulaErrors as $fe) {
|
return strtoupper(trim($h)) !== 'MARK';
|
||||||
if (!empty($fe['data'] ?? null)) {
|
});
|
||||||
$allRowsData[] = $fe['data'];
|
|
||||||
}
|
}
|
||||||
}
|
@endphp
|
||||||
|
|
||||||
foreach ($markErrors as $me) {
|
@if(!empty($unmatchedRows))
|
||||||
if (!empty($me['data'] ?? null)) {
|
<div class="table-responsive" style="max-height:260px; overflow:auto; border:1px solid #e3e6ef;">
|
||||||
$allRowsData[] = $me['data'];
|
<table class="table table-sm table-bordered mb-0" style="font-size:11.5px; min-width:800px;">
|
||||||
}
|
<thead class="table-light">
|
||||||
}
|
<tr>
|
||||||
|
<th>Excel Row</th>
|
||||||
$headings = [];
|
<th>Mark No</th>
|
||||||
if (!empty($allRowsData)) {
|
@foreach($headings as $head)
|
||||||
$headings = array_keys($allRowsData[0]);
|
<th>{{ $head }}</th>
|
||||||
}
|
@endforeach
|
||||||
@endphp
|
</tr>
|
||||||
|
</thead>
|
||||||
@if(!empty($headings))
|
<tbody>
|
||||||
<div class="card mb-3">
|
@foreach($unmatchedRows as $row)
|
||||||
<div class="card-header small">
|
|
||||||
Error rows in Excel view
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-2">
|
|
||||||
<div class="table-responsive" style="max-height:260px; overflow:auto;">
|
|
||||||
<table class="table table-sm table-bordered mb-0" style="font-size:11.5px;">
|
|
||||||
<thead class="table-light">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Excel Row</th>
|
<td>{{ $row['excel_row'] }}</td>
|
||||||
<th>Mark No</th>
|
<td>{{ $row['mark_no'] }}</td>
|
||||||
@foreach($headings as $head)
|
@foreach($headings as $head)
|
||||||
<th>{{ $head }}</th>
|
<td>{{ $row['data'][$head] ?? '' }}</td>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{-- Formula error rows (red – formula mismatch, critical) --}}
|
|
||||||
@foreach($formulaErrors as $fe)
|
|
||||||
@php $rowData = $fe['data'] ?? []; @endphp
|
|
||||||
@if(!empty($rowData))
|
|
||||||
<tr class="table-danger">
|
|
||||||
<td>{{ $fe['excel_row'] }}</td>
|
|
||||||
<td>{{ $fe['mark_no'] }}</td>
|
|
||||||
@foreach($headings as $head)
|
|
||||||
<td>{{ $rowData[$head] ?? '' }}</td>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
{{-- Mark error rows (yellow – mark not found, warning) --}}
|
|
||||||
@foreach($markErrors as $me)
|
|
||||||
@php $rowData = $me['data'] ?? []; @endphp
|
|
||||||
@if(!empty($rowData))
|
|
||||||
<tr class="table-warning">
|
|
||||||
<td>{{ $me['excel_row'] }}</td>
|
|
||||||
<td>{{ $me['mark_no'] }}</td>
|
|
||||||
@foreach($headings as $head)
|
|
||||||
<td>{{ $rowData[$head] ?? '' }}</td>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- 2) Detailed per-row error boxes BELOW the table --}}
|
|
||||||
<div class="card error-card mb-3">
|
|
||||||
<div class="card-header bg-light small">
|
|
||||||
Detailed error messages
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
{{-- Formula Errors (detailed) --}}
|
|
||||||
@if(session('formula_errors'))
|
|
||||||
@foreach(session('formula_errors') as $fe)
|
|
||||||
<div class="error-row-box">
|
|
||||||
<div class="error-title">
|
|
||||||
Row {{ $fe['excel_row'] }}
|
|
||||||
@if($fe['mark_no']) | Mark: {{ $fe['mark_no'] }} @endif
|
|
||||||
@if($fe['description']) | {{ $fe['description'] }} @endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@foreach($fe['errors'] as $field => $detail)
|
|
||||||
<div class="error-item text-danger">
|
|
||||||
❌ <span>{{ $field }}</span> →
|
|
||||||
Expected: {{ number_format($detail['expected'],4) }}
|
|
||||||
| Got: {{ number_format($detail['actual'],4) }}
|
|
||||||
</div>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</tbody>
|
||||||
@endforeach
|
</table>
|
||||||
@endif
|
</div>
|
||||||
|
@endif
|
||||||
{{-- Mark Errors (detailed) --}}
|
|
||||||
@if(session('mark_errors'))
|
|
||||||
@foreach(session('mark_errors') as $me)
|
|
||||||
<div class="error-row-box bg-warning bg-opacity-10 border-warning">
|
|
||||||
<div class="error-title">
|
|
||||||
Row {{ $me['excel_row'] }}
|
|
||||||
</div>
|
|
||||||
<div class="error-item text-warning">
|
|
||||||
❌ Mark <strong>{{ $me['mark_no'] }}</strong> not found in database
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- FORM --}}
|
{{-- FORM: unmatched_rows असल्यावर form लपवायचा असेल तर खालील condition ठेवा --}}
|
||||||
@if (!session('formula_errors') && !session('mark_errors'))
|
@if (!session('unmatched_rows'))
|
||||||
<form action="{{ route('containers.store') }}" method="POST" enctype="multipart/form-data" class="mt-3">
|
<form action="{{ route('containers.store') }}" method="POST" enctype="multipart/form-data" class="mt-3">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
{{-- Container Name --}}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="cm-form-label">Container Name</label>
|
<label class="cm-form-label">Container Name</label>
|
||||||
<input type="text" name="container_name"
|
<input type="text"
|
||||||
|
name="container_name"
|
||||||
class="form-control cm-form-control"
|
class="form-control cm-form-control"
|
||||||
value="{{ old('container_name') }}"
|
value="{{ old('container_name') }}"
|
||||||
placeholder="Enter container name">
|
placeholder="Enter container name">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Container Number --}}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="cm-form-label">Container Number</label>
|
<label class="cm-form-label">Container Number</label>
|
||||||
<input type="text" name="container_number"
|
<input type="text"
|
||||||
|
name="container_number"
|
||||||
class="form-control cm-form-control"
|
class="form-control cm-form-control"
|
||||||
value="{{ old('container_number') }}"
|
value="{{ old('container_number') }}"
|
||||||
placeholder="Enter container number">
|
placeholder="Enter container number">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Container Date --}}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="cm-form-label">Container Date</label>
|
<label class="cm-form-label">Container Date</label>
|
||||||
<input type="date"
|
<input type="date"
|
||||||
name="container_date" {{-- name fix --}}
|
name="container_date"
|
||||||
id="containerdate" {{-- JS साठी जुना id ठेवला --}}
|
|
||||||
class="form-control cm-form-control"
|
class="form-control cm-form-control"
|
||||||
value="{{ old('container_date') }}">
|
value="{{ old('container_date') }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Excel File --}}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="cm-form-label">Loading List Excel</label>
|
<label class="cm-form-label">Loading List Excel</label>
|
||||||
<input type="file" name="excel_file"
|
<input type="file"
|
||||||
|
name="excel_file"
|
||||||
class="form-control cm-form-control"
|
class="form-control cm-form-control"
|
||||||
accept=".xls,.xlsx">
|
accept=".xls,.xlsx">
|
||||||
<div class="cm-help-text">
|
<div class="cm-help-text">
|
||||||
@@ -324,26 +248,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const dateInput = document.getElementById('containerdate');
|
|
||||||
if (!dateInput) return;
|
|
||||||
|
|
||||||
const today = new Date();
|
|
||||||
const year = today.getFullYear();
|
|
||||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(today.getDate()).padStart(2, '0');
|
|
||||||
const todayStr = `${year}-${month}-${day}`;
|
|
||||||
|
|
||||||
// Todays date
|
|
||||||
dateInput.min = todayStr;
|
|
||||||
|
|
||||||
// old date remove
|
|
||||||
if (dateInput.value && dateInput.value < todayStr) {
|
|
||||||
dateInput.value = todayStr;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Container {{ $container->container_number }} Summary</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: DejaVu Sans, sans-serif;
|
|
||||||
font-size: 10px;
|
|
||||||
margin: 10px;
|
|
||||||
background: #e5e7ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LOGO HEADER */
|
|
||||||
.logo-header-wrap {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.logo-header-inner {
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
.logo-header-logo-cell {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.logo-header-text-cell {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
.logo-header-logo {
|
|
||||||
height: 32px; /* banner type, कमी उंची */
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
.logo-header-title-top {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #7b1111; /* dark maroon */
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
.logo-header-title-bottom {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #7b1111;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* COMMON CARD GRID – 4 equal columns, 2 rows */
|
|
||||||
.card-grid {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 8px 6px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.card-row {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
display: table-cell;
|
|
||||||
width: 25%;
|
|
||||||
padding: 7px 10px;
|
|
||||||
border-radius: 14px;
|
|
||||||
box-shadow: 0 4px 12px rgba(15,35,52,0.18);
|
|
||||||
color: #0f172a;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* INFO CARDS (FIRST ROW) */
|
|
||||||
.info-id { background: #e0f2ff; border-left: 4px solid #2563eb; }
|
|
||||||
.info-no { background: #dcfce7; border-left: 4px solid #22c55e; }
|
|
||||||
.info-date { background: #fee2e2; border-left: 4px solid #ef4444; }
|
|
||||||
.info-name { background: #fef9c3; border-left: 4px solid #f59e0b; }
|
|
||||||
|
|
||||||
/* TOTAL CARDS (SECOND ROW) */
|
|
||||||
.total-ctn { background: #dbeafe; border-left: 4px solid #1d4ed8; }
|
|
||||||
.total-qty { background: #bbf7d0; border-left: 4px solid #16a34a; }
|
|
||||||
.total-cbm { background: #fef3c7; border-left: 4px solid #d97706; }
|
|
||||||
.total-kg { background: #fecaca; border-left: 4px solid #dc2626; }
|
|
||||||
|
|
||||||
.label-text {
|
|
||||||
font-size: 9px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.4px;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
.value-text-small {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 800;
|
|
||||||
margin-top: 2px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.value-text-big {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 800;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TABLE – solid yellow header */
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: 6px;
|
|
||||||
table-layout: fixed;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
padding: 4px 3px;
|
|
||||||
text-align: center;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background: #fbd85d;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0c0909;
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
font-size: 9px;
|
|
||||||
color: #111827;
|
|
||||||
}
|
|
||||||
tr:nth-child(even) td {
|
|
||||||
background: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead { display: table-header-group; }
|
|
||||||
tr { page-break-inside: avoid; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
@php
|
|
||||||
$totalCtn = 0;
|
|
||||||
$totalQty = 0;
|
|
||||||
$totalCbm = 0.0;
|
|
||||||
$totalKg = 0.0;
|
|
||||||
|
|
||||||
foreach ($container->rows as $row) {
|
|
||||||
if (!is_array($row->data)) continue;
|
|
||||||
foreach ($row->data as $h => $v) {
|
|
||||||
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $h));
|
|
||||||
$val = is_numeric(str_replace([','], '', $v)) ? floatval(str_replace([','], '', $v)) : 0;
|
|
||||||
|
|
||||||
if (str_contains($norm, 'TOTALCTN') || $norm === 'CTN' || str_contains($norm,'TOTALCNTR') || str_contains($norm,'TOTALCARTON')) {
|
|
||||||
$totalCtn += $val;
|
|
||||||
}
|
|
||||||
if (str_contains($norm,'TOTALQTY') || str_contains($norm,'ITLQTY') || str_contains($norm,'TTLQTY')) {
|
|
||||||
$totalQty += $val;
|
|
||||||
}
|
|
||||||
if (str_contains($norm,'TOTALCBM') || str_contains($norm,'TTLCBM') || str_contains($norm,'ITLCBM')) {
|
|
||||||
$totalCbm += $val;
|
|
||||||
}
|
|
||||||
if (str_contains($norm,'TOTALKG') || str_contains($norm,'TTKG')) {
|
|
||||||
$totalKg += $val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$allHeadings = [];
|
|
||||||
foreach ($container->rows as $row) {
|
|
||||||
if (is_array($row->data)) {
|
|
||||||
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
{{-- LOGO + TEXT – full left, text खाली (reference image) --}}
|
|
||||||
<div class="logo-header-wrap">
|
|
||||||
<div class="logo-header-inner">
|
|
||||||
<div class="logo-header-logo-cell">
|
|
||||||
<img src="{{ public_path('images/kentlogo1.png') }}"
|
|
||||||
class="logo-header-logo"
|
|
||||||
alt="Kent Logo">
|
|
||||||
</div>
|
|
||||||
<div class="logo-header-text-cell">
|
|
||||||
<div class="logo-header-title-top">KENT</div>
|
|
||||||
<div class="logo-header-title-bottom">International Pvt. Ltd.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- TWO ROW GRID – FIRST: INFO / SECOND: TOTALS --}}
|
|
||||||
<div class="card-grid">
|
|
||||||
<div class="card-row">
|
|
||||||
<div class="card info-id">
|
|
||||||
<div class="label-text">Container ID</div>
|
|
||||||
<div class="value-text-small">{{ $container->id }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card info-no">
|
|
||||||
<div class="label-text">Container Number</div>
|
|
||||||
<div class="value-text-small">{{ $container->container_number }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card info-date">
|
|
||||||
<div class="label-text">Container Date</div>
|
|
||||||
<div class="value-text-small">
|
|
||||||
{{ $container->container_date ? $container->container_date->format('d-m-Y') : '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card info-name">
|
|
||||||
<div class="label-text">Container Name</div>
|
|
||||||
<div class="value-text-small">
|
|
||||||
{{ $container->container_name ?? '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-row">
|
|
||||||
<div class="card total-ctn">
|
|
||||||
<div class="label-text">Total CTN</div>
|
|
||||||
<div class="value-text-big">{{ number_format($totalCtn, 0) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card total-qty">
|
|
||||||
<div class="label-text">Total QTY</div>
|
|
||||||
<div class="value-text-big">{{ number_format($totalQty, 0) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card total-cbm">
|
|
||||||
<div class="label-text">Total CBM</div>
|
|
||||||
<div class="value-text-big">{{ number_format($totalCbm, 3) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card total-kg">
|
|
||||||
<div class="label-text">Total KG</div>
|
|
||||||
<div class="value-text-big">{{ number_format($totalKg, 2) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- FULL TABLE --}}
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width:18px;">#</th>
|
|
||||||
@foreach($allHeadings as $heading)
|
|
||||||
<th>{{ $heading }}</th>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($container->rows as $index => $row)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $index + 1 }}</td>
|
|
||||||
@foreach($allHeadings as $heading)
|
|
||||||
<td>{{ $row->data[$heading] ?? '' }}</td>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user