update perbaikan pos, return, dan report profit nlost
All checks were successful
All checks were successful
This commit is contained in:
@@ -18,15 +18,18 @@ public function index(Request $request)
|
||||
$branchId = $user->branch_id ?? $user->active_branch_id;
|
||||
|
||||
$baseQuery = SaleDetails::query()
|
||||
->whereHas('product', fn ($q) => $q->where('business_id', $user->business_id))
|
||||
->whereHas('product', fn ($q) =>
|
||||
$q->where('business_id', $user->business_id)
|
||||
)
|
||||
->when(moduleCheck('MultiBranchAddon') && $branchId, function ($q) use ($branchId) {
|
||||
$q->whereHas('sale', fn ($q) => $q->where('branch_id', $branchId));
|
||||
$q->whereHas('sale', fn ($q) =>
|
||||
$q->where('branch_id', $branchId)
|
||||
);
|
||||
})
|
||||
->when($request->filled('search'), function ($q) use ($request) {
|
||||
$q->whereHas('product', function ($q) use ($request) {
|
||||
$q->where('productName', 'like', "%{$request->search}%")
|
||||
->orWhere('productCode', 'like', "%{$request->search}%")
|
||||
->orWhere('hsn_code', 'like', "%{$request->search}%");
|
||||
->orWhere('productCode', 'like', "%{$request->search}%");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,11 +42,11 @@ public function index(Request $request)
|
||||
->sum('lossProfit');
|
||||
|
||||
$product_lossProfits = $baseQuery
|
||||
->with('product:id,productName,productCode,hsn_code')
|
||||
->with('product:id,productName,productCode')
|
||||
->select(
|
||||
'product_id',
|
||||
DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) AS profit'),
|
||||
DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) AS loss')
|
||||
DB::raw('SUM(CASE WHEN lossProfit > 0 THEN lossProfit ELSE 0 END) AS profit'),
|
||||
DB::raw('SUM(CASE WHEN lossProfit < 0 THEN lossProfit ELSE 0 END) AS loss')
|
||||
)
|
||||
->groupBy('product_id')
|
||||
->paginate($request->per_page ?? 20)
|
||||
@@ -51,13 +54,17 @@ public function index(Request $request)
|
||||
|
||||
if ($request->ajax()) {
|
||||
return response()->json([
|
||||
'data' => view('business::reports.product-loss-profit.datas', compact('product_lossProfits'))->render(),
|
||||
'loss' => currency_format($loss, currency: business_currency()),
|
||||
'profit' => currency_format($profit, currency: business_currency())
|
||||
'data' => view(
|
||||
'business::reports.product-loss-profit.datas',
|
||||
compact('product_lossProfits')
|
||||
)->render()
|
||||
]);
|
||||
}
|
||||
|
||||
return view('business::reports.product-loss-profit.index', compact('product_lossProfits', 'profit', 'loss'));
|
||||
return view(
|
||||
'business::reports.product-loss-profit.index',
|
||||
compact('product_lossProfits', 'profit', 'loss')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,36 +78,26 @@ public function exportCsv()
|
||||
return Excel::download(new ExportProductLossProfit, 'product-lossProfit.csv');
|
||||
}
|
||||
|
||||
public function exportPdf(Request $request)
|
||||
public function exportPdf()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$branchId = $user->branch_id ?? $user->active_branch_id;
|
||||
$branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null;
|
||||
|
||||
$baseQuery = SaleDetails::query()
|
||||
->whereHas('product', fn ($q) =>
|
||||
$q->where('business_id', $user->business_id)
|
||||
)
|
||||
->when(moduleCheck('MultiBranchAddon') && $branchId, function ($q) use ($branchId) {
|
||||
$q->whereHas('sale', fn ($q) => $q->where('branch_id', $branchId));
|
||||
})
|
||||
->when($request->filled('search'), function ($q) use ($request) {
|
||||
$q->whereHas('product', function ($q) use ($request) {
|
||||
$q->where('productName', 'like', "%{$request->search}%")
|
||||
->orWhere('productCode', 'like', "%{$request->search}%")
|
||||
->orWhere('hsn_code', 'like', "%{$request->search}%");
|
||||
});
|
||||
});
|
||||
|
||||
$product_lossProfits = $baseQuery
|
||||
->with('product:id,productName,productCode,hsn_code')
|
||||
->select(
|
||||
'product_id',
|
||||
DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) AS profit'),
|
||||
DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) AS loss')
|
||||
)
|
||||
->groupBy('product_id')
|
||||
->limit($request->per_page ?? 20)
|
||||
->get();
|
||||
$product_lossProfits = SaleDetails::with('product:id,productName,productCode')
|
||||
->whereHas('product', function ($q) {
|
||||
$q->where('business_id', auth()->user()->business_id);
|
||||
})
|
||||
->when($branchId, function ($q) use ($branchId) {
|
||||
$q->whereHas('sale', function ($sale) use ($branchId) {
|
||||
$sale->where('branch_id', $branchId);
|
||||
});
|
||||
})
|
||||
->select(
|
||||
'product_id',
|
||||
DB::raw('SUM(CASE WHEN lossProfit > 0 THEN lossProfit ELSE 0 END) as profit'),
|
||||
DB::raw('SUM(CASE WHEN lossProfit < 0 THEN lossProfit ELSE 0 END) as loss')
|
||||
)
|
||||
->groupBy('product_id')
|
||||
->get();
|
||||
|
||||
return PdfService::render('business::reports.product-loss-profit.pdf', compact('product_lossProfits'), 'product-loss-profit-report.pdf');
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -40,212 +40,65 @@ public function store(Request $request)
|
||||
'warehouse_id' => 'nullable|exists:warehouses,id',
|
||||
'type' => 'nullable|string|in:sale,purchase',
|
||||
'id' => 'required|integer',
|
||||
'name' => 'nullable|string',
|
||||
'name' => 'required|string',
|
||||
'quantity' => 'required|numeric',
|
||||
'price' => 'required|numeric',
|
||||
'product_code' => 'nullable|string',
|
||||
'product_unit_id' => 'nullable|integer',
|
||||
'product_unit_name' => 'nullable|string',
|
||||
'product_image' => 'nullable|string',
|
||||
'profit_percent' => 'nullable|numeric',
|
||||
'sales_price' => 'nullable|numeric',
|
||||
'whole_sale_price' => 'nullable|numeric',
|
||||
'dealer_price' => 'nullable|numeric',
|
||||
'expire_date' => 'nullable|date',
|
||||
'product_type' => 'nullable|in:single,variant,combo',
|
||||
'variant_name' => 'nullable|string',
|
||||
'serial_numbers' => 'nullable|array',
|
||||
'serial_numbers.*' => 'string',
|
||||
'has_serial' => 'nullable|boolean',
|
||||
'price_without_tax' => 'required_without:serial_numbers|numeric|nullable',
|
||||
'customer_type' => 'nullable|string'
|
||||
'vat_percent' => 'nullable|numeric',
|
||||
]);
|
||||
|
||||
$incomingSerials = $request->serial_numbers ?? [];
|
||||
|
||||
// Serial Mode
|
||||
if (!empty($incomingSerials)) {
|
||||
// Sale
|
||||
if ($request->type === 'sale') {
|
||||
|
||||
$stocks = Stock::where('business_id', auth()->user()->business_id)
|
||||
->where('product_id', $request->id)
|
||||
->where(function ($query) use ($incomingSerials) {
|
||||
foreach ($incomingSerials as $serial) {
|
||||
$query->orWhereJsonContains('serial_numbers', $serial);
|
||||
}
|
||||
})->get();
|
||||
|
||||
// Map serial → stock
|
||||
$serialStockMap = [];
|
||||
|
||||
foreach ($stocks as $stock) {
|
||||
foreach ($incomingSerials as $serial) {
|
||||
if (in_array($serial, $stock->serial_numbers ?? [])) {
|
||||
$serialStockMap[$stock->id][] = $serial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add or merge cart per stock
|
||||
foreach ($serialStockMap as $stockId => $serialsForStock) {
|
||||
|
||||
$resolvedStock = $stocks->firstWhere('id', $stockId);
|
||||
$qty = count($serialsForStock);
|
||||
|
||||
$customerType = $request->customer_type ?? 'Retailer';
|
||||
|
||||
// Determine price based on customer type
|
||||
$price = round($resolvedStock->productSalePrice ?? 0, 2);
|
||||
|
||||
if ($customerType === 'Dealer') {
|
||||
$price = round($resolvedStock->productDealerPrice ?? 0, 2);
|
||||
} elseif ($customerType === 'Wholesaler') {
|
||||
$price = round($resolvedStock->productWholeSalePrice ?? 0, 2);
|
||||
}
|
||||
|
||||
$existingCartItem = Cart::search(function ($item) use ($request, $stockId) {
|
||||
return $item->id == $request->id &&
|
||||
$item->options->type === 'sale' &&
|
||||
$item->options->stock_id == $stockId;
|
||||
})->first();
|
||||
|
||||
if ($existingCartItem) {
|
||||
|
||||
$existingSerials = $existingCartItem->options->serial_numbers ?? [];
|
||||
|
||||
// Remove already existing serials from incoming
|
||||
$newSerials = array_diff($serialsForStock, $existingSerials);
|
||||
|
||||
$duplicates = array_intersect($existingSerials, $newSerials);
|
||||
|
||||
if (!empty($duplicates)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Serial already exists in cart',
|
||||
'duplicates' => array_values($duplicates),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$mergedSerials = array_values(array_unique(
|
||||
array_merge($existingSerials, $serialsForStock)
|
||||
));
|
||||
|
||||
Cart::update($existingCartItem->rowId, [
|
||||
'qty' => count($mergedSerials),
|
||||
'options' => $existingCartItem->options->merge([
|
||||
'serial_numbers' => $mergedSerials,
|
||||
]),
|
||||
]);
|
||||
|
||||
} else {
|
||||
|
||||
Cart::add([
|
||||
'id' => $request->id,
|
||||
'name' => $request->name,
|
||||
'qty' => $qty,
|
||||
'price' => $price,
|
||||
'options' => [
|
||||
'type' => 'sale',
|
||||
'product_code' => $request->product_code,
|
||||
'product_unit_id' => $request->product_unit_id,
|
||||
'product_unit_name' => $request->product_unit_name,
|
||||
'product_image' => $request->product_image,
|
||||
'product_type' => $request->product_type,
|
||||
'variant_name' => $request->variant_name,
|
||||
'stock_id' => $resolvedStock->id,
|
||||
'warehouse_id' => $resolvedStock->warehouse_id,
|
||||
'sales_price' => $resolvedStock->productSalePrice,
|
||||
'whole_sale_price' => $resolvedStock->productWholeSalePrice,
|
||||
'dealer_price' => $resolvedStock->productDealerPrice,
|
||||
'serial_numbers' => $serialsForStock,
|
||||
'has_serial' => $request->has_serial ?? 1,
|
||||
'price_without_tax' => $resolvedStock->price_without_tax,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
// Check for existing item in cart by type
|
||||
$existingCartItem = Cart::search(
|
||||
fn($item) => $item->id == $request->id &&
|
||||
$item->options->type == $request->type &&
|
||||
match ($request->type) {
|
||||
'purchase' => $item->options->batch_no == $request->batch_no,
|
||||
'sale' => $item->options->stock_id == $request->stock_id,
|
||||
default => false,
|
||||
}
|
||||
// Purchase
|
||||
else {
|
||||
Cart::add([
|
||||
'id' => $request->id,
|
||||
'name' => $request->name,
|
||||
'qty' => count($incomingSerials),
|
||||
'price' => round($request->price, 2),
|
||||
'options' => [
|
||||
'type' => 'purchase',
|
||||
'product_code' => $request->product_code,
|
||||
'product_unit_id' => $request->product_unit_id,
|
||||
'product_unit_name' => $request->product_unit_name,
|
||||
'product_image' => $request->product_image,
|
||||
'product_type' => $request->product_type,
|
||||
'variant_name' => $request->variant_name,
|
||||
'stock_id' => null,
|
||||
'warehouse_id' => $request->warehouse_id,
|
||||
'serial_numbers' => $incomingSerials,
|
||||
'batch_no' => $request->batch_no,
|
||||
'expire_date' => $request->expire_date,
|
||||
'purchase_price' => $request->purchase_price,
|
||||
'profit_percent' => $request->profit_percent,
|
||||
'sales_price' => $request->sales_price,
|
||||
'whole_sale_price' => $request->whole_sale_price,
|
||||
'dealer_price' => $request->dealer_price,
|
||||
'has_serial' => $request->has_serial ?? 1,
|
||||
'price_without_tax' => $request->price_without_tax,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Serial product added successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
// Normal product ( without serial)
|
||||
$existingCartItem = Cart::search(function ($item) use ($request) {
|
||||
return $item->id == $request->id &&
|
||||
$item->options->type == $request->type;
|
||||
})->first();
|
||||
)->first();
|
||||
|
||||
if ($existingCartItem) {
|
||||
|
||||
Cart::update($existingCartItem->rowId, [
|
||||
'qty' => $existingCartItem->qty + $request->quantity
|
||||
]);
|
||||
|
||||
$newQty = $existingCartItem->qty + $request->quantity;
|
||||
Cart::update($existingCartItem->rowId, ['qty' => $newQty]);
|
||||
} else {
|
||||
|
||||
Cart::add([
|
||||
// Add new item to cart
|
||||
$mainItemData = [
|
||||
'id' => $request->id,
|
||||
'name' => $request->name,
|
||||
'qty' => $request->quantity,
|
||||
'price' => round($request->price, 2),
|
||||
'price' => $request->price,
|
||||
'options' => [
|
||||
'type' => $request->type,
|
||||
'product_code' => $request->product_code,
|
||||
'product_unit_id' => $request->product_unit_id,
|
||||
'product_unit_name' => $request->product_unit_name,
|
||||
'product_image' => $request->product_image,
|
||||
'product_type' => $request->product_type,
|
||||
'variant_name' => $request->variant_name,
|
||||
'stock_id' => $request->stock_id,
|
||||
'batch_no' => $request->batch_no,
|
||||
'product_image' => $request->product_image,
|
||||
'expire_date' => $request->expire_date,
|
||||
'purchase_price' => $request->purchase_price,
|
||||
'profit_percent' => $request->profit_percent,
|
||||
'sales_price' => $request->sales_price,
|
||||
'whole_sale_price' => $request->whole_sale_price,
|
||||
'dealer_price' => $request->dealer_price,
|
||||
'product_type' => $request->product_type,
|
||||
'warehouse_id' => $request->warehouse_id,
|
||||
'has_serial' => $request->has_serial ?? 0,
|
||||
'price_without_tax' => $request->price_without_tax,
|
||||
'variant_name' => $request->variant_name,
|
||||
'vat_percent' => $request->vat_percent,
|
||||
]
|
||||
]);
|
||||
];
|
||||
Cart::add($mainItemData);
|
||||
}
|
||||
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Item added to cart successfully.'
|
||||
@@ -255,45 +108,20 @@ public function store(Request $request)
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$cart = Cart::get($id);
|
||||
|
||||
if (!$cart) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('Item not found in cart')
|
||||
]);
|
||||
return response()->json(['success' => false, 'message' => __('Item not found in cart')]);
|
||||
}
|
||||
|
||||
$qty = $request->qty ?? $cart->qty;
|
||||
|
||||
if ($qty < 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('Enter a valid quantity')
|
||||
]);
|
||||
}
|
||||
|
||||
// incoming serial
|
||||
$incomingSerials = $request->serial_numbers ?? null;
|
||||
|
||||
if (is_array($incomingSerials)) {
|
||||
$existingSerials = $incomingSerials;
|
||||
} else {
|
||||
$existingSerials = $cart->options->serial_numbers ?? [];
|
||||
}
|
||||
|
||||
// normalize to array
|
||||
if (!is_array($existingSerials)) {
|
||||
$existingSerials = !empty($existingSerials) ? [$existingSerials] : [];
|
||||
}
|
||||
|
||||
$hasSerial = $cart->options->has_serial ?? 0;
|
||||
|
||||
if ($hasSerial) {
|
||||
$qty = count($existingSerials);
|
||||
return response()->json(['success' => false, 'message' => __('Enter a valid quantity')]);
|
||||
}
|
||||
|
||||
Cart::update($id, [
|
||||
'qty' => $qty,
|
||||
'price' => round($request->price ?? $cart->price, 2),
|
||||
'price' => $request->price ?? $cart->price,
|
||||
'options' => [
|
||||
'type' => $cart->options->type,
|
||||
'expire_date' => $request->expire_date ?? $cart->options->expire_date,
|
||||
@@ -303,7 +131,6 @@ public function update(Request $request, $id)
|
||||
'product_unit_id' => $cart->options->product_unit_id,
|
||||
'product_unit_name' => $cart->options->product_unit_name,
|
||||
'product_image' => $cart->options->product_image,
|
||||
'profit_percent' => $cart->options->profit_percent,
|
||||
'sales_price' => $cart->options->sales_price,
|
||||
'discount' => $request->discount ?? $cart->options->discount,
|
||||
'whole_sale_price' => $cart->options->whole_sale_price,
|
||||
@@ -312,9 +139,7 @@ public function update(Request $request, $id)
|
||||
'product_type' => $cart->options->product_type,
|
||||
'warehouse_id' => $cart->options->warehouse_id,
|
||||
'variant_name' => $cart->options->variant_name,
|
||||
'price_without_tax' => $cart->options->price_without_tax,
|
||||
'has_serial' => $hasSerial,
|
||||
'serial_numbers' => $existingSerials,
|
||||
'vat_percent' => $cart->options->vat_percent,
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
namespace Modules\Business\App\Http\Controllers;
|
||||
|
||||
use App\Events\MultiPaymentProcessed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Branch;
|
||||
use App\Models\Party;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\Sale;
|
||||
use App\Models\SaleReturn;
|
||||
use App\Models\SaleReturnDetails;
|
||||
use App\Models\Party;
|
||||
use App\Models\Stock;
|
||||
use App\Services\VatTransactionService;
|
||||
use App\Models\Branch;
|
||||
use App\Models\SaleReturn;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\SaleReturnDetails;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class SaleReturnController extends Controller
|
||||
{
|
||||
@@ -85,67 +84,47 @@ public function create(Request $request)
|
||||
return view('business::sale-returns.create', compact('sale', 'discount_per_unit_factor', 'avg_rounding_amount', 'payment_types'));
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
// split-array structure from front-end request
|
||||
$products = [];
|
||||
for ($index = 0; $index < count($request->products); $index += 3) {
|
||||
$products[] = [
|
||||
'detail_id' => $request->products[$index]['detail_id'] ?? null,
|
||||
'return_qty' => $request->products[$index + 1]['return_qty'] ?? 0,
|
||||
'serial_numbers' => $request->products[$index + 2]['serial_numbers'] ?? '[]',
|
||||
];
|
||||
}
|
||||
$request->merge(['products' => $products]);
|
||||
|
||||
$request->validate([
|
||||
'sale_id' => 'required|exists:sales,id',
|
||||
'products' => 'required|array|min:1',
|
||||
'products.*.detail_id' => 'required|exists:sale_details,id',
|
||||
'products.*.return_qty' => 'required|integer',
|
||||
'products.*.serial_numbers' => 'nullable|string',
|
||||
'return_qty' => 'required|array',
|
||||
]);
|
||||
|
||||
$business_id = auth()->user()->business_id;
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Load sale with details and product info
|
||||
$sale = Sale::with(
|
||||
'details:id,sale_id,product_id,price,discount,lossProfit,quantities,productPurchasePrice,stock_id,expire_date,serial_numbers',
|
||||
'details.product:id,product_type,has_serial',
|
||||
'details.product.combo_products'
|
||||
)
|
||||
->where('business_id', $business_id)
|
||||
->findOrFail($request->sale_id);
|
||||
$sale = Sale::with('details:id,sale_id,product_id,price,discount,lossProfit,quantities,productPurchasePrice,stock_id,expire_date', 'details.product:id,product_type', 'details.product.combo_products')
|
||||
->where('business_id', $business_id)
|
||||
->findOrFail($request->sale_id);
|
||||
|
||||
$old_sale_due = $sale->dueAmount;
|
||||
|
||||
// Calculate discount factors
|
||||
// Calculate total discount factor with itemwise discount
|
||||
$total_discount = $sale->discountAmount;
|
||||
$total_sale_amount = $sale->details->sum(fn($detail) => $detail->price * $detail->quantities);
|
||||
$discount_per_unit_factor = $total_sale_amount > 0 ? $total_discount / $total_sale_amount : 0;
|
||||
$rounding_amount_per_unit = $sale->details->sum('quantities') > 0 ? $sale->rounding_amount / $sale->details->sum('quantities') : 0;
|
||||
|
||||
// Create sale return record
|
||||
$sale_return = SaleReturn::create([
|
||||
'return_date' => now(),
|
||||
'business_id' => $business_id,
|
||||
'sale_id' => $request->sale_id,
|
||||
'invoice_no' => $sale->invoiceNumber,
|
||||
'return_date' => now(),
|
||||
]);
|
||||
|
||||
$sale_return_detail_data = [];
|
||||
$total_return_amount = 0;
|
||||
$total_return_discount = 0;
|
||||
$total_return_vat = 0;
|
||||
$total_loss_profit_adjustment = 0;
|
||||
|
||||
foreach ($request->products as $indextem) {
|
||||
foreach ($sale->details as $key => $detail) {
|
||||
$requested_qty = $request->return_qty[$key];
|
||||
|
||||
$detail = $sale->details->where('id', $indextem['detail_id'])->first();
|
||||
if (!$detail) continue; // skip if detail not found
|
||||
|
||||
$requested_qty = (int) ($indextem['return_qty'] ?? 0);
|
||||
if ($requested_qty <= 0) continue;
|
||||
if ($requested_qty <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($requested_qty > $detail->quantities) {
|
||||
return response()->json([
|
||||
@@ -153,78 +132,63 @@ public function store(Request $request)
|
||||
], 400);
|
||||
}
|
||||
|
||||
$return_serials = !empty($indextem['serial_numbers']) ? json_decode($indextem['serial_numbers'], true) : [];
|
||||
$product = $detail->product;
|
||||
|
||||
// Include SaleDetails discount in return calculation
|
||||
$unit_discount = $detail->price * $discount_per_unit_factor;
|
||||
$indextem_cart_discount = $detail->discount ?? 0;
|
||||
$total_discount_per_unit = $unit_discount + $indextem_cart_discount;
|
||||
$item_cart_discount = $detail->discount ?? 0;
|
||||
$total_discount_per_unit = $unit_discount + $item_cart_discount;
|
||||
|
||||
// Calculate VAT for the item
|
||||
$item_vat_percent = $detail->product->vat->rate ?? 0;
|
||||
$item_vat_amount = ($detail->price * $item_vat_percent) / 100;
|
||||
|
||||
$return_discount = $total_discount_per_unit * $requested_qty;
|
||||
$return_amount = ($detail->price - $total_discount_per_unit + $rounding_amount_per_unit) * $requested_qty;
|
||||
$return_amount = ($detail->price - $total_discount_per_unit + $rounding_amount_per_unit + $item_vat_amount) * $requested_qty;
|
||||
|
||||
$total_return_amount += $return_amount;
|
||||
$total_return_discount += $return_discount;
|
||||
$total_return_vat += ($item_vat_amount * $requested_qty);
|
||||
|
||||
$product = $detail->product;
|
||||
|
||||
// Combo products
|
||||
if ($product && $product->product_type === 'combo') {
|
||||
|
||||
$combo_total_purchase = 0;
|
||||
$combo_total_sale = $detail->price * $requested_qty;
|
||||
|
||||
foreach ($product->combo_products as $comboItem) {
|
||||
$stock = Stock::find($comboItem->stock_id);
|
||||
|
||||
if (!$stock) {
|
||||
return response()->json([
|
||||
'message' => __("Stock not found for combo item '{$comboItem->product->productName}'"),
|
||||
], 400);
|
||||
}
|
||||
|
||||
// increase stock by combo component quantity * returned qty
|
||||
$restore_qty = $comboItem->quantity * $requested_qty;
|
||||
$stock->increment('productStock', $restore_qty);
|
||||
|
||||
$combo_total_purchase += $comboItem->purchase_price * $restore_qty;
|
||||
}
|
||||
|
||||
// loss/profit adjustment for combo
|
||||
$loss_profit_adjustment = ($combo_total_sale - $combo_total_purchase) - $return_discount;
|
||||
$total_loss_profit_adjustment += $loss_profit_adjustment;
|
||||
}
|
||||
// Single / variant products
|
||||
|
||||
else {
|
||||
$stock = Stock::where('id', $detail->stock_id)->first();
|
||||
if (!$stock) return response()->json(['error' => 'Stock not found.'], 404);
|
||||
|
||||
if ($product->has_serial) {
|
||||
|
||||
// Validate serial count
|
||||
if (count($return_serials) !== $requested_qty) {
|
||||
return response()->json([
|
||||
'message' => 'Serial count must match return quantity'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$sold_serials = is_array($detail->serial_numbers) ? $detail->serial_numbers : json_decode($detail->serial_numbers ?? '[]', true);
|
||||
$indexnvalid = array_diff($return_serials, $sold_serials);
|
||||
if (!empty($indexnvalid)) {
|
||||
return response()->json(['message' => 'Invalid serial detected'], 400);
|
||||
}
|
||||
|
||||
// Restore stock
|
||||
$stock->increment('productStock', $requested_qty);
|
||||
|
||||
// Restore serials to stock
|
||||
$stock_serials = $stock->serial_numbers ?? [];
|
||||
$stock->update([
|
||||
'serial_numbers' => array_values(array_unique(array_merge($stock_serials, $return_serials)))
|
||||
]);
|
||||
|
||||
// Remove serials from sale detail
|
||||
$detail->update([
|
||||
'serial_numbers' => array_values(array_diff($sold_serials, $return_serials))
|
||||
]);
|
||||
} else {
|
||||
$stock->increment('productStock', $requested_qty);
|
||||
if (!$stock) {
|
||||
return response()->json(['error' => 'Stock not found.'], 404);
|
||||
}
|
||||
|
||||
$stock->increment('productStock', $requested_qty);
|
||||
|
||||
$loss_profit_adjustment = (($detail->price - $stock->productPurchasePrice) * $requested_qty) - $return_discount;
|
||||
$total_loss_profit_adjustment += $loss_profit_adjustment;
|
||||
}
|
||||
|
||||
$loss_profit_adjustment = $detail->lossProfit * ($requested_qty / $detail->quantities);
|
||||
$total_loss_profit_adjustment += $loss_profit_adjustment;
|
||||
|
||||
// Update sale detail
|
||||
// Update sale details
|
||||
$detail->quantities -= $requested_qty;
|
||||
$detail->lossProfit -= $loss_profit_adjustment;
|
||||
$detail->timestamps = false;
|
||||
@@ -236,21 +200,22 @@ public function store(Request $request)
|
||||
'sale_return_id' => $sale_return->id,
|
||||
'return_qty' => $requested_qty,
|
||||
'return_amount' => $return_amount,
|
||||
'serial_numbers' => $return_serials ?? null,
|
||||
]);
|
||||
|
||||
$previous_returned_qty = SaleReturnDetails::where('sale_detail_id', $detail->id)->sum('return_qty');
|
||||
$totalQty = $detail->quantities + $previous_returned_qty;
|
||||
|
||||
VatTransactionService::productReturnVatCalculation($detail, $requested_qty, $returnDetail, $totalQty);
|
||||
// Record VAT transaction for the return
|
||||
if (class_exists('App\Services\VatTransactionService')) {
|
||||
$totalQty = $detail->quantities + $requested_qty; // Original quantity before this return decrement
|
||||
\App\Services\VatTransactionService::productReturnVatCalculation($detail, $requested_qty, $returnDetail, $totalQty);
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_return_amount <= 0) {
|
||||
return response()->json("You cannot return an empty product.", 400);
|
||||
}
|
||||
|
||||
// Remaining refund / party logic
|
||||
$remaining_refund = $total_return_amount;
|
||||
|
||||
// Adjust Due
|
||||
$new_due = $sale->dueAmount;
|
||||
if ($remaining_refund > 0) {
|
||||
if ($remaining_refund >= $sale->dueAmount) {
|
||||
@@ -262,6 +227,7 @@ public function store(Request $request)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust Paid (refund part)
|
||||
$new_paid = $sale->paidAmount;
|
||||
if ($remaining_refund > 0) {
|
||||
if ($remaining_refund >= $sale->paidAmount) {
|
||||
@@ -273,49 +239,53 @@ public function store(Request $request)
|
||||
}
|
||||
}
|
||||
|
||||
// total sale amount
|
||||
$new_total_amount = max(0, $sale->totalAmount - $total_return_amount);
|
||||
|
||||
$sale->update([
|
||||
'change_amount' => 0,
|
||||
'dueAmount' => $new_due,
|
||||
'paidAmount' => $new_paid,
|
||||
'totalAmount' => $new_total_amount,
|
||||
'actual_total_amount' => $new_total_amount,
|
||||
'vat_amount' => max(0, $sale->vat_amount - $total_return_vat),
|
||||
'discountAmount' => max(0, $sale->discountAmount - $total_return_discount),
|
||||
'lossProfit' => $sale->lossProfit - $total_loss_profit_adjustment,
|
||||
]);
|
||||
|
||||
// Party refund logic
|
||||
$due_reduction = $old_sale_due - $new_due;
|
||||
// Party Refund Logic
|
||||
$party = Party::find($sale->party_id);
|
||||
|
||||
if ($due_reduction > 0) {
|
||||
Party::where('business_id', $business_id)
|
||||
->where('id', $sale->party_id)
|
||||
->decrement('due', $due_reduction);
|
||||
}
|
||||
if ($party) {
|
||||
|
||||
$party = Party::where('business_id', $business_id)->find($sale->party_id);
|
||||
|
||||
if ($party && $remaining_refund > 0) {
|
||||
// use leftover refund after due/paid adjustments
|
||||
$refund_amount = $remaining_refund;
|
||||
|
||||
// Reduce party due
|
||||
if ($party->due > 0) {
|
||||
|
||||
if ($party->due >= $remaining_refund) {
|
||||
$party->decrement('due', $remaining_refund);
|
||||
$remaining_refund = 0;
|
||||
if ($party->due >= $refund_amount) {
|
||||
$party->decrement('due', $refund_amount);
|
||||
$refund_amount = 0;
|
||||
} else {
|
||||
$remaining_refund -= $party->due;
|
||||
$refund_amount -= $party->due;
|
||||
$party->update(['due' => 0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($remaining_refund > 0) {
|
||||
$party->increment('wallet', $remaining_refund);
|
||||
// Add leftover refund to wallet
|
||||
if ($refund_amount > 0) {
|
||||
$party->increment('wallet', $refund_amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Payment event
|
||||
$payments = $request->payments ?? [];
|
||||
$payments = $request->payments;
|
||||
|
||||
if (isset($payments['main'])) {
|
||||
$mainPayment = $payments['main'];
|
||||
$mainPayment['amount'] = $request->receive_amount ?? 0;
|
||||
$payments = [$mainPayment];
|
||||
}
|
||||
|
||||
$payments = collect($payments)->map(function ($payment) use ($total_return_amount) {
|
||||
$payment['amount'] = $total_return_amount;
|
||||
return $payment;
|
||||
@@ -325,22 +295,20 @@ public function store(Request $request)
|
||||
$payments,
|
||||
$sale_return->id,
|
||||
'sale_return',
|
||||
$total_return_amount,
|
||||
$total_return_amount ?? 0,
|
||||
));
|
||||
|
||||
$tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($sale->business_id) == 'standard_a4';
|
||||
$invoice_route = $tax_invoice ? route('business.sales.tax.invoice', $sale->id) : route('business.sales.invoice', $sale->id);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'message' => __('Sale returned successfully.'),
|
||||
'redirect' => route('business.sale-returns.index'),
|
||||
'secondary_redirect_url' => $invoice_route,
|
||||
'secondary_redirect_url' => route('business.sales.invoice', $sale->id),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return response()->json(['message' => __('Something went wrong!') . ' ' . $e->getMessage()], 500);
|
||||
return response()->json(['message' => __('Something went wrong!')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user