Files
kulakpos_web/Modules/Business/App/Http/Controllers/AcnooPartyController.php

387 lines
15 KiB
PHP
Raw Normal View History

2026-03-15 17:08:23 +07:00
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
2026-05-14 11:55:22 +07:00
use App\Models\PaymentType;
2026-03-15 17:08:23 +07:00
use App\Helpers\HasUploader;
2026-05-14 11:55:22 +07:00
use App\Imports\PartyImport;
2026-03-15 17:08:23 +07:00
use Illuminate\Http\Request;
2026-05-14 11:55:22 +07:00
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
2026-03-15 17:08:23 +07:00
use App\Http\Controllers\Controller;
2026-05-14 11:55:22 +07:00
use Maatwebsite\Excel\Facades\Excel;
use App\Events\MultiPaymentProcessed;
// use App\Models\Country;
// use App\Models\State;
2026-03-15 17:08:23 +07:00
use Illuminate\Support\Facades\Storage;
class AcnooPartyController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:parties.read')->only(['index']);
$this->middleware('check.permission:parties.create')->only(['create', 'store']);
$this->middleware('check.permission:parties.update')->only(['edit', 'update']);
$this->middleware('check.permission:parties.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$user = auth()->user();
$business_id = $user->business_id;
$activeBranch = $user->active_branch;
$search = $request->input('search');
$party_type = $request->input('type');
$query = Party::where('business_id', $business_id)
2026-05-14 11:55:22 +07:00
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('name', 'like', '%'.$search.'%')
->orWhere('credit_limit', 'like', '%'.$search.'%')
->orWhere('phone', 'like', '%'.$search.'%')
->orWhere('type', 'like', '%'.$search.'%')
->orWhere('address', 'like', '%'.$search.'%')
->orWhere('due', 'like', '%'.$search.'%');
2026-03-15 17:08:23 +07:00
});
2026-05-14 11:55:22 +07:00
});
2026-03-15 17:08:23 +07:00
// Filter by party type
if ($party_type === 'Customer') {
$query->whereIn('type', ['Retailer', 'Dealer', 'Wholesaler']);
} elseif ($party_type === 'Supplier') {
$query->where('type', 'Supplier');
}
$parties = $query->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($activeBranch) {
$parties->setCollection(
$parties->getCollection()
->transform(function ($party) use ($activeBranch) {
2026-05-14 11:55:22 +07:00
$originalDue = $party->getRawOriginal('due') ?? 0;
$originalOpeningBalance = $party->opening_balance ?? 0;
2026-03-15 17:08:23 +07:00
$party_due = $party->type === 'Supplier'
2026-05-14 11:55:22 +07:00
? $party->purchases_dues->where('branch_id', $activeBranch->id)->sum('dueAmount')
: $party->sales_dues->where('branch_id', $activeBranch->id)->sum('dueAmount');
2026-03-15 17:08:23 +07:00
2026-05-14 11:55:22 +07:00
if ($party->branch_id === $activeBranch->id) {
$openingBalanceDue = $party->opening_balance_type === 'due' ? $originalOpeningBalance : 0;
$party->due = $openingBalanceDue + $party_due;
} else {
$party->due = $party_due;
}
2026-03-15 17:08:23 +07:00
return $party;
})
);
}
if ($request->ajax()) {
return response()->json([
2026-05-14 11:55:22 +07:00
'data' => view('business::parties.datas', compact('parties'))->render(),
2026-03-15 17:08:23 +07:00
]);
}
return view('business::parties.index', compact('parties', 'party_type'));
}
2026-05-14 11:55:22 +07:00
2026-03-15 17:08:23 +07:00
public function create()
{
2026-05-14 11:55:22 +07:00
$countries = [];
$states = [];
return view('business::parties.create', compact('countries', 'states'));
2026-03-15 17:08:23 +07:00
}
public function store(Request $request)
{
$request->validate([
'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', auth()->user()->business_id),
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'email' => 'nullable|email',
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => 'nullable|string|max:255',
2026-05-14 11:55:22 +07:00
'tax_no' => 'nullable|string|max:255',
2026-03-15 17:08:23 +07:00
'due' => 'nullable|numeric|min:0',
'billing_address' => 'nullable|array',
'billing_address.address' => 'nullable|string|max:255',
'billing_address.city' => 'nullable|string|max:255',
'billing_address.zip_code' => 'nullable|string|max:20',
'shipping_address' => 'nullable|array',
'shipping_address.address' => 'nullable|string|max:255',
'shipping_address.city' => 'nullable|string|max:255',
'shipping_address.state' => 'nullable|string|max:255',
'shipping_address.zip_code' => 'nullable|string|max:20',
'shipping_address.country' => 'nullable|string|max:255',
'credit_limit' => 'nullable|numeric|min:0|max:999999999999.99',
'opening_balance' => 'nullable|numeric|min:-999999999999.99|max:999999999999.99',
'opening_balance_type' => 'required|in:due,advance',
'meta' => 'nullable|array',
]);
Party::create($request->except('image', 'due', 'wallet', 'opening_balance', 'credit_limit','business_id') + [
'due' => ($request->opening_balance_type == 'due') ? ($request->opening_balance ?? 0) : 0,
'wallet' => ($request->opening_balance_type == 'advance') ? ($request->opening_balance ?? 0) : 0,
'opening_balance' => $request->opening_balance ?? 0,
'credit_limit' => $request->credit_limit ?? 0,
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'business_id' => auth()->user()->business_id
]);
$type = in_array($request->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($request->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => __(ucfirst($type) . ' created successfully'),
'redirect' => route('business.parties.index', ['type' => $type])
]);
}
public function edit($id)
{
$party = Party::where('business_id', auth()->user()->business_id)->findOrFail($id);
2026-05-14 11:55:22 +07:00
$countries = [];
$states = [];
return view('business::parties.edit', compact('party', 'countries', 'states'));
2026-03-15 17:08:23 +07:00
}
public function update(Request $request, $id)
{
$party = Party::findOrFail($id);
$request->validate([
'phone' => 'nullable|max:20|unique:parties,phone,' . $party->id . ',id,business_id,' . auth()->user()->business_id,
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'email' => 'nullable|email',
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => 'nullable|string|max:255',
2026-05-14 11:55:22 +07:00
'tax_no' => 'nullable|string|max:255',
2026-03-15 17:08:23 +07:00
'due' => 'nullable|numeric|min:0',
'billing_address' => 'nullable|array',
'billing_address.address' => 'nullable|string|max:255',
'billing_address.city' => 'nullable|string|max:255',
'billing_address.zip_code' => 'nullable|string|max:20',
'shipping_address' => 'nullable|array',
'shipping_address.address' => 'nullable|string|max:255',
'shipping_address.city' => 'nullable|string|max:255',
'shipping_address.state' => 'nullable|string|max:255',
'shipping_address.zip_code' => 'nullable|string|max:20',
'shipping_address.country' => 'nullable|string|max:255',
'credit_limit' => 'nullable|numeric|min:0|max:999999999999.99',
'opening_balance' => 'nullable|numeric|min:-999999999999.99|max:999999999999.99',
'opening_balance_type' => 'required|in:due,advance',
'meta' => 'nullable|array',
]);
$branch_logic = $party->branch_id == auth()->user()->active_branch?->id;
// Previous
$prevOpening = $party->opening_balance ?? 0;
$prevType = $party->opening_balance_type;
// Current
$currentOpening = $request->opening_balance ?? 0;
$currentType = $request->opening_balance_type;
// Start with existing balance
$due = $party->due;
$wallet = $party->wallet;
if ($prevType === $currentType) {
// Same type then adjust by difference
if ($currentType === 'due') {
$due += ($currentOpening - $prevOpening);
} else {
$wallet += ($currentOpening - $prevOpening);
}
} else {
// Type changed then shift balances
if ($prevType === 'due' && $currentType === 'advance') {
$due -= $prevOpening;
$wallet += $currentOpening;
} elseif ($prevType === 'advance' && $currentType === 'due') {
$wallet -= $prevOpening;
$due += $currentOpening;
}
}
$party->update($request->except('image', 'due', 'wallet', 'opening_balance', 'credit_limit', 'opening_balance_type','business_id') + [
'due' => $branch_logic ? $due : $party->due,
'wallet' => $branch_logic ? $wallet : $party->wallet,
'opening_balance' => $currentOpening,
'opening_balance_type' => $currentType,
'credit_limit' => $request->credit_limit ?? $party->credit_limit,
'image' => $request->image ? $this->upload($request, 'image', $party->image) : $party->image,
]
);
$type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => __(ucfirst($type) . ' updated successfully'),
'redirect' => route('business.parties.index', ['type' => $type])
]);
}
2026-05-14 11:55:22 +07:00
public function advancePayment(string $id)
{
$party = Party::where('business_id', auth()->user()->business_id)->with(['sales_dues', 'purchases_dues'])->findOrFail($id);
$payment_types = PaymentType::where('business_id', auth()->user()->business_id)->whereStatus(1)->latest()->get();
return view('business::parties.advance-payment', compact('party', 'payment_types'));
}
public function advancePaymentStore(Request $request)
{
$request->validate([
'party_id' => 'required|exists:parties,id',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'note' => 'nullable|string|max:255',
]);
DB::beginTransaction();
try {
$party = Party::findOrFail($request->party_id);
$payments = $request->payments ?? [];
if (isset($payments['main'])) {
$mainPayment = $payments['main'];
$mainPayment['amount'] = $request->amount ?? 0;
$payments = [$mainPayment];
}
// Only count actual received money (exclude cheque)
$payAmount = collect($payments)
->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
if ($payAmount <= 0) {
return response()->json([
'message' => __('No valid payment amount received (cheques are pending).')
], 400);
}
// Determine platform based on party type
$platform = $party->type === 'Supplier' ? 'advance_pay' : 'advance_collect';
$party->increment('wallet', $payAmount);
event(new MultiPaymentProcessed(
$payments,
$party->id,
$platform,
$payAmount,
$party->id,
$request->date ?? now(),
$request->note ?? null
));
DB::commit();
$type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : '');
$message = $type === 'Supplier' ? __('Advance amount paid successfully.') : __('Advance amount collected successfully.');
return response()->json([
'message' => $message,
'redirect' => route('business.parties.index', ['type' => $type])
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'error' => 'Transaction failed: ' . $e->getMessage()
], 500);
}
}
2026-03-15 17:08:23 +07:00
public function destroy($id)
{
$party = Party::findOrFail($id);
if (!$party->canBeDeleted()) {
return response()->json([
'message' => __('This party cannot be deleted.'),
], 400);
}
if (file_exists($party->image)) {
Storage::delete($party->image);
}
$party->delete();
$type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => ucfirst($party->type) . ' deleted successfully',
'redirect' => route('business.parties.index', ['type' => $type]),
]);
}
public function deleteAll(Request $request)
{
$parties = Party::whereIn('id', $request->ids)->get();
$partyType = null;
$undeletable = [];
foreach ($parties as $party) {
if ($partyType === null) {
$partyType = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : 'Customer');
}
if (!$party->canBeDeleted()) {
$undeletable[] = $party->name;
} else {
if (file_exists($party->image)) {
Storage::delete($party->image);
}
$party->delete();
}
}
$message = __('Selected parties deleted successfully');
if (!empty($undeletable)) {
$message .= ' (Some parties were skipped: ' . implode(', ', $undeletable) . ')';
}
return response()->json([
'message' => $message,
'redirect' => route('business.parties.index', ['type' => $partyType]),
]);
}
2026-05-14 11:55:22 +07:00
public function bulkStore(Request $request, $type)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv'
]);
$businessId = auth()->user()->business_id;
$import = new PartyImport($businessId);
try {
Excel::import($import, $request->file('file'));
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 422);
}
return response()->json([
'success' => true,
'message' => __('Bulk upload successfully.'),
'redirect' => route('business.parties.index', ['type' => $type])
]);
}
2026-03-15 17:08:23 +07:00
}