update marketing
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 5m14s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 17s
Build, Push and Deploy / deploy-staging (push) Successful in 41s
Build, Push and Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-14 11:55:22 +07:00
parent f80fcc4c1b
commit 05fd3230b8
448 changed files with 17545 additions and 5128 deletions

View File

@@ -24,7 +24,6 @@
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Support\Facades\Storage;
use Modules\Business\App\Exports\ExportProduct;
use Modules\Business\App\Exports\ExportExpiredProduct;
class AcnooProductController extends Controller
{
@@ -40,7 +39,7 @@ public function __construct()
public function index(Request $request)
{
$user = auth()->user();
$user = auth()->user();
$search = $request->input('search');
$products = Product::query()
@@ -66,13 +65,14 @@ public function index(Request $request)
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('productName', 'like', "%{$search}%")
->orWhere('productCode', 'like', "%{$search}%")
->orWhere('productPurchasePrice', 'like', "%{$search}%")
->orWhere('productSalePrice', 'like', "%{$search}%")
->orWhere('product_type', 'like', "%{$search}%")
->orWhereHas('category', fn($q) => $q->where('categoryName', 'like', "%{$search}%"))
->orWhereHas('brand', fn($q) => $q->where('brandName', 'like', "%{$search}%"))
->orWhereHas('unit', fn($q) => $q->where('unitName', 'like', "%{$search}%"));
->orWhere('productCode', 'like', "%{$search}%")
->orWhere('hsn_code', 'like', "%{$search}%")
->orWhere('productPurchasePrice', 'like', "%{$search}%")
->orWhere('productSalePrice', 'like', "%{$search}%")
->orWhere('product_type', 'like', "%{$search}%")
->orWhereHas('category', fn ($q) => $q->where('categoryName', 'like', "%{$search}%"))
->orWhereHas('brand', fn ($q) => $q->where('brandName', 'like', "%{$search}%"))
->orWhereHas('unit', fn ($q) => $q->where('unitName', 'like', "%{$search}%"));
});
})
->latest()
@@ -82,22 +82,22 @@ public function index(Request $request)
$products->getCollection()->transform(function ($product) {
if ($product->product_type === 'combo') {
$product->total_stock = $product->combo_products->sum(
fn($combo) => $combo->stock?->productStock ?? 0
fn ($combo) => $combo->stock?->productStock ?? 0
);
$product->total_cost = $product->combo_products->sum(
fn($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0)
fn ($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0)
);
$product->combo_items = $product->combo_products->map(function ($combo) {
return [
'name' => $combo->stock?->product?->productName ?? 'N/A',
'quantity' => $combo->quantity ?? 0,
'name' => $combo->stock?->product?->productName ?? 'N/A',
'quantity' => $combo->quantity ?? 0,
'purchase_price' => currency_format(
($combo->purchase_price ?? 0) * ($combo->quantity ?? 0),
currency: business_currency()
),
'stock' => $combo->stock?->productStock ?? 0,
'stock' => $combo->stock?->productStock ?? 0,
];
});
}
@@ -114,7 +114,6 @@ public function index(Request $request)
return view('business::products.index', compact('products'));
}
public function create()
{
$business_id = auth()->user()->business_id;
@@ -122,7 +121,7 @@ public function create()
$brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$product_id = (Product::where('business_id', $business_id)->count() ?? 0) + 1;
$vats = Vat::where('business_id', $business_id)->latest()->get();
$vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get();
$code = str_pad($product_id, 4, '0', STR_PAD_LEFT);
$product_models = ProductModel::where('business_id', $business_id)->latest()->get();
$warehouses = Warehouse::where('business_id', $business_id)->latest()->get();
@@ -130,9 +129,10 @@ public function create()
$racks = Rack::where('business_id', $business_id)->latest()->get();
$shelves = Shelf::where('business_id', $business_id)->latest()->get();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->where('value', 'LIKE', '%"business_id":' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? '';
->firstWhere('value.business_id', $business_id)
->value['product_profit_option'] ?? '';
return view('business::products.create', compact('categories', 'brands', 'units', 'code', 'vats', 'product_models', 'warehouses', 'racks', 'shelves', 'profit_option', 'variations'));
}
@@ -159,6 +159,7 @@ public function store(Request $request)
return $query->where('business_id', $business_id);
}),
],
'hsn_code' => 'nullable|string|max:255',
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
@@ -206,13 +207,21 @@ function ($attribute, $value, $fail) use ($request) {
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
$combo_tax_inclusive = $request->productSalePrice ?? 0;
$combo_tax_exclusive = $request->productSalePrice ?? 0;
if ($request->vat_id && $request->product_type === 'combo') {
$combo_tax_inclusive = number_format($combo_tax_exclusive * (1 + ($vat_rate / 100)), 3, '.', '');
}
// Create the product
$product = Product::create($request->only('productName', 'unit_id', 'brand_id', 'vat_id', 'vat_type', 'category_id', 'productCode', 'product_type', 'rack_id', 'shelf_id', 'model_id', 'variation_ids', 'warranty_guarantee_info') + [
$product = Product::create($request->only('productName', 'unit_id', 'brand_id', 'vat_id', 'vat_type', 'category_id', 'productCode', 'hsn_code', 'product_type', 'rack_id', 'shelf_id', 'model_id', 'variation_ids', 'warranty_guarantee_info') + [
'business_id' => $business_id,
'alert_qty' => $request->alert_qty ?? 0,
'is_displayed_in_pos' => $request->has('is_displayed_in_pos') ? 1 : 0,
'has_serial' => $request->product_type == 'combo' ? 0 : ($request->has_serial ?? 0),
'profit_percent' => $request->product_type == 'combo' ? $request->profit_percent ?? 0 : 0,
'productSalePrice' => $request->product_type == 'combo' ? $request->productSalePrice ?? 0 : 0,
'productSalePrice' => $request->product_type == 'combo' ? $combo_tax_inclusive : 0, // inclusive sales price
'price_without_tax' => $request->product_type == 'combo' ? $combo_tax_exclusive : 0,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture') : NULL,
]);
@@ -220,18 +229,15 @@ function ($attribute, $value, $fail) use ($request) {
if (in_array($request->product_type, ['single', 'variant']) && !empty($request->stocks)) {
$stockData = [];
foreach ($request->stocks as $stock) {
$base_price = $stock['exclusive_price'] ?? 0;
$purchasePrice = $request->vat_type === 'inclusive'
? $base_price + ($base_price * $vat_rate / 100)
: $base_price;
$stockData[] = [
'business_id' => $business_id,
'product_id' => $product->id,
'exclusive_price' => $stock['exclusive_price'] ?? 0,
'batch_no' => $stock['batch_no'] ?? null,
'warehouse_id' => $stock['warehouse_id'] ?? null,
'productStock' => $stock['productStock'] ?? 0,
'productPurchasePrice' => $purchasePrice,
'productPurchasePrice' => $stock['inclusive_price'] ?? 0, // inclusive purchase price / net cost price
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
@@ -281,16 +287,17 @@ public function edit($id)
$categories = Category::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$vats = Vat::where('business_id', $business_id)->latest()->get();
$vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get();
$product_models = ProductModel::where('business_id', $business_id)->latest()->get();
$warehouses = Warehouse::where('business_id', $business_id)->latest()->get();
$racks = Rack::where('business_id', $business_id)->latest()->get();
$shelves = Shelf::where('business_id', $business_id)->latest()->get();
$variations = Variation::where('business_id', auth()->user()->business_id)->where('status', 1)->get();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->where('value', 'LIKE', '%"business_id":' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? '';
->firstWhere('value.business_id', $business_id)
->value['product_profit_option'] ?? '';
$product = Product::with([
'stocks' => function ($query) {
@@ -334,6 +341,7 @@ public function update(Request $request, $id)
return $query->where('business_id', $business_id);
}),
],
'hsn_code' => 'nullable|string|max:255',
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
@@ -381,12 +389,21 @@ function ($attribute, $value, $fail) use ($request) {
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
$combo_tax_exclusive = $request->productSalePrice ?? 0;
$combo_tax_inclusive = $request->productSalePrice ?? 0;
if ($request->vat_id && $request->product_type === 'combo') {
$combo_tax_inclusive = number_format($combo_tax_exclusive * (1 + ($vat_rate / 100)), 3, '.', '');
}
// Update product
$product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent', 'is_displayed_in_pos']) + [
$product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent', 'productSalePrice', 'price_without_tax']) + [
'business_id' => $business_id,
'alert_qty' => $request->alert_qty ?? 0,
'is_displayed_in_pos' => $request->has('is_displayed_in_pos') ? 1 : 0,
'profit_percent' => $request->profit_percent ?? 0,
'has_serial' => $request->product_type == 'combo' ? 0 : ($request->has_serial ?? 0),
'profit_percent' => $request->product_type == 'combo' ? $request->profit_percent ?? 0 : 0,
'productSalePrice' => $request->product_type == 'combo' ? $combo_tax_inclusive : 0, // inclusive sales price
'price_without_tax' => $request->product_type == 'combo' ? $combo_tax_exclusive : 0,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture', $product->productPicture) : $product->productPicture,
]);
@@ -406,22 +423,16 @@ function ($attribute, $value, $fail) use ($request) {
// Insert or Update
foreach ($request->stocks as $stock) {
$stockId = $stock['stock_id'] ?? null;
// Recalculate price
$base_price = $stock['exclusive_price'] ?? 0;
$purchasePrice = $request->vat_type === 'inclusive'
? $base_price + ($base_price * $vat_rate / 100)
: $base_price;
$payload = [
'business_id' => $business_id,
'product_id' => $product->id,
'batch_no' => $stock['batch_no'] ?? null,
'warehouse_id' => $stock['warehouse_id'] ?? null,
'productStock' => $stock['productStock'] ?? 0,
'productPurchasePrice' => $purchasePrice,
'exclusive_price' => $stock['exclusive_price'] ?? 0,
'productPurchasePrice' =>$stock['inclusive_price'] ?? 0,
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
@@ -431,7 +442,7 @@ function ($attribute, $value, $fail) use ($request) {
'variation_data' => $stock['variation_data'] ?? null,
'variant_name' => $stock['variant_name'] ?? null,
'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id,
'serial_numbers' => $request->has_serial ? $stock['serial_numbers'] : null,
'serial_numbers' => $request->has_serial ? $stock['serial_numbers'] ?? null : null,
];
if ($stockId) {
@@ -463,13 +474,12 @@ function ($attribute, $value, $fail) use ($request) {
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => __('Something went wrong.'),
'error' => $e->getMessage(),
'message' => $e->getMessage(),
], 406);
}
}
public function destroy($id)
public function destroy(string $id)
{
$product = Product::findOrFail($id);
if (file_exists($product->productPicture)) {
@@ -494,33 +504,47 @@ public function deleteAll(Request $request)
}
Product::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected product deleted successfully'),
'redirect' => route('business.products.index')
'message' => __('Selected product deleted successfully'),
'redirect' => route('business.products.index')
]);
}
public function getAllProduct()
{
$isTransfer = request()->is_transfer == 1;
$products = Product::with([
'stocks' => function ($query) {
'stocks' => function ($query) use ($isTransfer) {
$query->where('productStock', '>', 0);
if (!$isTransfer) {
$query->where(function ($q) {
$q->whereNull('serial_numbers')
->orWhereJsonLength('serial_numbers', 0);
});
}
},
'category:id,categoryName',
'unit:id,unitName',
'stocks.warehouse:id,name',
'vat:id,rate'
'stocks.warehouse:id,name'
])
->where('business_id', auth()->user()->business_id)
->withSum('stocks as total_stock', 'productStock')
->withSum(['stocks as total_stock' => function ($query) use ($isTransfer) {
$query->where('productStock', '>', 0);
if (!$isTransfer) {
$query->where(function ($q) {
$q->whereNull('serial_numbers')
->orWhereJsonLength('serial_numbers', 0);
});
}
}], 'productStock')
->having('total_stock', '>', 0)
->latest()
->get()
->where('total_stock', '>', 0)
->values();
->get();
return response()->json($products);
}
public function getByCategory($category_id)
public function getByCategory(string $category_id)
{
$products = Product::where('business_id', auth()->user()->business_id)->where('category_id', $category_id)->get();
return response()->json($products);
@@ -536,15 +560,63 @@ public function exportCsv()
return Excel::download(new ExportProduct, 'product.csv');
}
public function exportPdf()
public function exportPdf(Request $request)
{
$products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName')
->where('business_id', auth()->user()->business_id)
$user = auth()->user();
$search = $request->input('search');
$products = Product::query()
->where('business_id', $user->business_id)
->with([
'stocks',
'unit:id,unitName',
'brand:id,brandName',
'category:id,categoryName',
'warehouse:id,name',
'rack:id,name',
'shelf:id,name',
'combo_products.stock.product:id,productName'
])
->withSum('stocks as total_stock', 'productStock')
->where(function ($query) {
$query->where('product_type', '!=', 'combo')
->orWhere(function ($q) {
$q->where('product_type', 'combo')
->whereHas('combo_products');
});
})
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('productName', 'like', "%{$search}%")
->orWhere('productCode', 'like', "%{$search}%")
->orWhere('hsn_code', 'like', "%{$search}%")
->orWhere('productPurchasePrice', 'like', "%{$search}%")
->orWhere('productSalePrice', 'like', "%{$search}%")
->orWhere('product_type', 'like', "%{$search}%")
->orWhereHas('category', fn ($q) => $q->where('categoryName', 'like', "%{$search}%"))
->orWhereHas('brand', fn ($q) => $q->where('brandName', 'like', "%{$search}%"))
->orWhereHas('unit', fn ($q) => $q->where('unitName', 'like', "%{$search}%"));
});
})
->latest()
->limit($request->per_page ?? 20)
->get();
return PdfService::render('business::products.pdf', compact('products'), 'product-list.pdf');
$products = $products->transform(function ($product) {
if ($product->product_type === 'combo') {
$product->total_stock = $product->combo_products->sum(
fn ($combo) => $combo->stock?->productStock ?? 0
);
$product->total_cost = $product->combo_products->sum(
fn ($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0)
);
}
return $product;
});
return PdfService::render('business::products.pdf', compact('products'),'product-list.pdf');
}
public function expiredProduct(Request $request)
@@ -561,6 +633,7 @@ public function expiredProduct(Request $request)
$q->where('type', 'like', '%' . $request->search . '%')
->orWhere('productName', 'like', '%' . $request->search . '%')
->orWhere('productCode', 'like', '%' . $request->search . '%')
->orWhere('hsn_code', 'like', '%' . $request->search . '%')
->orWhere('productSalePrice', 'like', '%' . $request->search . '%')
->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%')
->orWhereHas('unit', function ($q) use ($request) {
@@ -586,103 +659,23 @@ public function expiredProduct(Request $request)
return view('business::expired-products.index', compact('expired_products'));
}
public function exportExpireProductExcel()
{
return Excel::download(new ExportExpiredProduct, 'expired-product.xlsx');
}
public function exportExpireProductCsv()
{
return Excel::download(new ExportExpiredProduct, 'expired-product.csv');
}
public function show($id)
public function show(string $id)
{
$business_id = auth()->user()->business_id;
$product = Product::with('stocks')->where('business_id', $business_id)->findOrFail($id);
$categories = Category::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$vats = Vat::where('business_id', $business_id)->latest()->get();
$vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->where('value', 'LIKE', '%"business_id":' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? '';
->firstWhere('value.business_id', $business_id)
->value['product_profit_option'] ?? '';
return view('business::products.create-stock', compact('categories', 'brands', 'units', 'product', 'vats', 'profit_option'));
}
public function CreateStock(Request $request, string $id)
{
$product = Product::findOrFail($id);
$business_id = auth()->user()->business_id;
$request->validate([
'vat_id' => 'nullable|exists:vats,id',
'vat_type' => 'nullable|in:inclusive,exclusive',
'productDealerPrice' => 'nullable|numeric|min:0',
'exclusive_price' => 'required|numeric|min:0',
'inclusive_price' => 'required|numeric|min:0',
'profit_percent' => 'nullable|numeric',
'productSalePrice' => 'required|numeric|min:0',
'productWholeSalePrice' => 'nullable|numeric|min:0',
'productStock' => 'required|numeric|min:0',
'expire_date' => 'nullable|date',
'batch_no' => 'nullable|string',
'productCode' => [
'nullable',
'unique:products,productCode,' . $product->id . ',id,business_id,' . $business_id,
],
]);
DB::beginTransaction();
try {
// Calculate purchase price including VAT if applicable
$vat = Vat::find($request->vat_id);
$exclusive_price = $request->exclusive_price ?? 0;
$vat_amount = ($exclusive_price * ($vat->rate ?? 0)) / 100;
// Determine final purchase price based on VAT type
$purchase_price = $request->vat_type === 'exclusive' ? $exclusive_price : $exclusive_price + $vat_amount;
$batchNo = $request->batch_no ?? null;
$stock = Stock::where(['batch_no' => $batchNo, 'product_id' => $product->id])->first();
if ($stock) {
$stock->update($request->except('productStock', 'productPurchasePrice', 'productSalePrice', 'productDealerPrice', 'productWholeSalePrice') + [
'productStock' => $stock->productStock + $request->productStock,
'productPurchasePrice' => $purchase_price,
'productSalePrice' => $request->productSalePrice,
'productDealerPrice' => $request->productDealerPrice ?? 0,
'productWholeSalePrice' => $request->productWholeSalePrice ?? 0,
]);
} else {
Stock::create($request->except('productStock', 'productPurchasePrice', 'productSalePrice', 'productDealerPrice', 'productWholeSalePrice') + [
'product_id' => $product->id,
'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id,
'business_id' => $business_id,
'productStock' => $request->productStock ?? 0,
'productPurchasePrice' => $purchase_price,
'productSalePrice' => $request->productSalePrice,
'productDealerPrice' => $request->productDealerPrice ?? 0,
'productWholeSalePrice' => $request->productWholeSalePrice ?? 0,
]);
}
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'redirect' => route('business.products.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => __('Something went wrong.'),
], 406);
}
}
public function getShelf(Request $request)
{
$rack = Rack::with('shelves')->find($request->rack_id);
@@ -698,4 +691,53 @@ public function getProductVariants($product_id)
return response()->json($variant_stocks);
}
public function checkSerial(Request $request)
{
$request->validate([
'product_id' => 'required|exists:products,id',
'serial' => 'required|string'
]);
$exists = Stock::where('business_id', auth()->user()->business_id)
->where('product_id', $request->product_id)
->whereNotNull('serial_numbers')
->whereJsonContains('serial_numbers', $request->serial)
->exists();
return response()->json([
'exists' => $exists
]);
}
public function getProductSerials(Request $request)
{
$request->validate([
'product_id' => 'required|exists:products,id',
'warehouse_id' => 'nullable|exists:warehouses,id',
'batch_no' => 'nullable|string',
]);
$business_id = auth()->user()->business_id;
// Get all serial_numbers from stocks
$serials = Stock::where('business_id', $business_id)
->where('product_id', $request->product_id)
->when($request->filled('batch_no'), function ($q) use ($request) {
$q->where('batch_no', $request->batch_no);
})
->when($request->filled('warehouse_id'), function ($q) use ($request) {
$q->where('warehouse_id', $request->warehouse_id);
})
->whereNotNull('serial_numbers')
->pluck('serial_numbers')
->flatten()
->values();
return response()->json([
'success' => true,
'serials' => $serials
]);
}
}