migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Banner;
class AcnooBannerController extends Controller
{
public function index()
{
$banners = Banner::where('status', 1)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $banners,
]);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Brand;
use Illuminate\Http\Request;
class AcnooBrandController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = Brand::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'brandName' => 'required|unique:brands,brandName,NULL,id,business_id,' . auth()->user()->business_id,
]);
$data = Brand::create($request->all() + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Brand $brand)
{
$request->validate([
'brandName' => [
'required',
'unique:brands,brandName,' . $brand->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$brand = $brand->update($request->all());
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $brand,
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Brand $brand)
{
$brand->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Http\Request;
class AcnooCategoryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = Category::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'categoryName' => 'required|unique:categories,categoryName,NULL,id,business_id,' . $business_id,
]);
$data = Category::create([
'categoryName' => $request->categoryName,
'variationCapacity' => $request->variationCapacity == 'true' ? 1 : 0,
'variationColor' => $request->variationColor == 'true' ? 1 : 0,
'variationSize' => $request->variationSize == 'true' ? 1 : 0,
'variationType' => $request->variationType == 'true' ? 1 : 0,
'variationWeight' => $request->variationWeight == 'true' ? 1 : 0,
'business_id' => $business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Category $category)
{
$request->validate([
'categoryName' => [
'required',
'unique:categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$category = $category->update([
'categoryName' => $request->categoryName,
'variationCapacity' => $request->variationCapacity == 'true' ? 1 : 0,
'variationColor' => $request->variationColor == 'true' ? 1 : 0,
'variationSize' => $request->variationSize == 'true' ? 1 : 0,
'variationType' => $request->variationType == 'true' ? 1 : 0,
'variationWeight' => $request->variationWeight == 'true' ? 1 : 0,
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $category,
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Category $category)
{
$category->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Currency;
use App\Models\UserCurrency;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class AcnooCurrencyController extends Controller
{
public function index()
{
$currencies = Currency::orderBy('is_default', 'desc')->orderBy('status', 'desc')->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $currencies
]);
}
public function show($id)
{
$currency = Currency::findOrFail($id);
$business_id = auth()->user()->business_id;
$user_currency = UserCurrency::where('business_id', $business_id)->first();
$user_currency->update([
'name' => $currency->name,
'code' => $currency->code,
'rate' => $currency->rate,
'symbol' => $currency->symbol,
'position' => $currency->position,
'country_name' => $currency->country_name,
]);
cache()->forget("business_currency_" . $business_id);
DB::commit();
return response()->json([
'message', __('Currency changed successfully'),
]);
}
}

View File

@@ -0,0 +1,249 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\DuePaymentReceived;
use App\Events\MultiPaymentProcessed;
use App\Models\Sale;
use App\Models\Party;
use App\Models\Business;
use App\Models\Purchase;
use App\Models\DueCollect;
use Illuminate\Http\Request;
use App\Traits\DateFilterTrait;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
class AcnooDueController extends Controller
{
Use DateFilterTrait;
public function index()
{
$query = DueCollect::with('user:id,name,role', 'party:id,name,email,phone,type,address', 'branch:id,name,phone,address','transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name')
->where('business_id', auth()->user()->business_id);
// Apply date filter
if(request('duration')){
$this->applyDateFilter($query, request('duration'), 'paymentDate', request('from_date'), request('to_date'));
}
$data = $query->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$party = Party::find($request->party_id);
$request->validate([
'paymentDate' => 'required|string',
'payDueAmount' => 'nullable|numeric',
'party_id' => 'required|exists:parties,id',
'invoiceNumber' => 'nullable|exists:' . ($party->type == 'Supplier' ? 'purchases' : 'sales') . ',invoiceNumber',
]);
$user = auth()->user();
$action_branch_id = $user->branch_id ?? $user->active_branch_id;
$payments = $request->payments ?? [];
$payDueAmount = collect($payments)
->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
if ($action_branch_id != $party->branch_id && !$request->invoiceNumber) {
return response()->json([
'message' => __('You must select an invoice when login any branch.')
], 400);
}
$branch_id = null;
if ($request->invoiceNumber) {
if ($party->type == 'Supplier') {
$invoice = Purchase::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first();
} else {
$invoice = Sale::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first();
}
if (!isset($invoice)) {
return response()->json([
'message' => 'Invoice Not Found.'
], 404);
}
if (!auth()->user()->active_branch) {
if (isset($invoice) && isset($invoice->branch_id)) {
$branch_id = $invoice->branch_id;
}
}
if ($invoice->dueAmount < $request->payDueAmount) {
return response()->json([
'message' => 'Invoice due is ' . $invoice->dueAmount . '. You can not pay more then the invoice due amount.'
], 400);
}
}
if (!$request->invoiceNumber) {
if ($party->type == 'Supplier') {
$all_invoice_due = Purchase::where('party_id', $request->party_id)->sum('dueAmount');
} else {
$all_invoice_due = Sale::where('party_id', $request->party_id)->sum('dueAmount');
}
if (($all_invoice_due + $request->payDueAmount) > $party->due) {
return response()->json([
'message' => __('You can pay only '. $party->due - $all_invoice_due .', without selecting an invoice.')
], 400);
}
if ($party->opening_balance_type == 'due') {
$party->update([
'opening_balance' => max(0, $party->opening_balance - $payDueAmount),
]);
}
}
$data = DueCollect::create($request->all() + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
'sale_id' => $party->type != 'Supplier' && isset($invoice) ? $invoice->id : NULL,
'purchase_id' => $party->type == 'Supplier' && isset($invoice) ? $invoice->id : NULL,
'totalDue' => isset($invoice) ? $invoice->dueAmount : $party->due,
'dueAmountAfterPay' => isset($invoice) ? ($invoice->dueAmount - $payDueAmount) : ($party->due - $payDueAmount),
]);
if (isset($invoice)) {
$invoice->update([
'dueAmount' => $invoice->dueAmount - $payDueAmount
]);
}
$party->type == 'Supplier' ? updateBalance($payDueAmount, 'decrement', $branch_id) : updateBalance($payDueAmount, 'increment', $branch_id);
$party->update([
'due' => $party->due - $payDueAmount
]);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$payments,
$data->id,
'due_collect',
$payDueAmount,
$party->id
));
// Send SMS
event(new DuePaymentReceived($data));
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data->load('user:id,name,role', 'party:id,name,email,phone,type,address', 'branch:id,name,phone,address','transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name'),
]);
}
public function invoiceWiseDue()
{
$data = Sale::select('id','dueAmount', 'paidAmount', 'totalAmount', 'invoiceNumber', 'saleDate', 'meta')
->where('business_id', auth()->user()->business_id)
->whereNull('party_id')
->where('dueAmount', '>', 0)
->latest()->paginate(10);
// Sum only for paginate data
$total_receivable = $data->getCollection()->sum('dueAmount');
return response()->json([
'message' => __('Data fetched successfully.'),
'total_receivable' => (float) $total_receivable,
'data' => $data,
]);
}
public function collectInvoiceDue(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'paymentDate' => 'required|string',
'payDueAmount' => 'nullable|numeric',
'invoiceNumber' => 'required|string|exists:sales,invoiceNumber',
]);
DB::beginTransaction();
try {
$invoice = Sale::where('business_id', $business_id)->where('invoiceNumber', $request->invoiceNumber)->whereNull('party_id')->first();
if (!$invoice) {
return response()->json([
'message' => 'Invoice Not Found.'
], 404);
}
$payments = $request->payments ?? [];
$payDueAmount = collect($payments)
->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
if ($invoice->dueAmount < $payDueAmount) {
return response()->json([
'message' => 'Invoice due is ' . $invoice->dueAmount . '. You cannot pay more than the invoice due amount.'
], 400);
}
$data = DueCollect::create([
'user_id' => auth()->id(),
'business_id' => $business_id,
'sale_id' => $invoice->id,
'invoiceNumber' => $request->invoiceNumber,
'totalDue' => $invoice->dueAmount,
'dueAmountAfterPay' => $invoice->dueAmount - $payDueAmount,
'payDueAmount' => $payDueAmount,
'payment_type_id' => $request->payment_type_id,
'paymentDate' => $request->paymentDate,
]);
$invoice->update([
'dueAmount' => $invoice->dueAmount - $payDueAmount
]);
$business = Business::findOrFail($business_id);
$business->update([
'remainingShopBalance' => $business->remainingShopBalance + $payDueAmount
]);
sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business_id);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$payments,
$data->id,
'due_collect',
$payDueAmount,
));
DB::commit();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data->load('user:id,name', 'party:id,name,email,phone,type,address','transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Something went wrong!',
'error' => $e->getMessage(),
], 500);
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use App\Models\Expense;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Events\MultiPaymentProcessed;
use App\Traits\DateFilterTrait;
class AcnooExpenseController extends Controller
{
use DateFilterTrait;
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$expenseQuery = Expense::with(['category:id,categoryName', 'payment_type:id,name', 'branch:id,name'])->where('business_id', $businessId);
$expenseQuery->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
});
// Apply date filter
if(request('duration')){
$this->applyDateFilter($expenseQuery, request('duration'), 'expenseDate', request('from_date'), request('to_date'));
}
// Search Filter
if ($request->filled('search')) {
$expenseQuery->where(function ($query) use ($request) {
$query->where('expanseFor', 'like', '%' . $request->search . '%')
->orWhere('paymentType', 'like', '%' . $request->search . '%')
->orWhere('referenceNo', 'like', '%' . $request->search . '%')
->orWhere('amount', 'like', '%' . $request->search . '%')
->orWhereHas('category', function ($q) use ($request) {
$q->where('categoryName', 'like', '%' . $request->search . '%');
})
->orWhereHas('payment_type', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
})
->orWhereHas('branch', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
});
});
}
$data = $expenseQuery->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'expense_category_id' => 'required|exists:expense_categories,id',
'expanseFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'note' => 'nullable|string',
'payments' => 'required|array|min:1',
'payments.*.type' => 'required|string',
'payments.*.amount' => 'nullable|numeric|min:0',
], [
'payments.required' => 'At least one payment method is required.',
'payments.*.type.required' => 'Each payment must have a type.',
'payments.*.amount.numeric' => 'Each payment amount must be numeric.',
]);
DB::beginTransaction();
try {
updateBalance($request->amount, 'decrement');
$data = Expense::create($request->except('status','paymentType') + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
]);
event(new MultiPaymentProcessed(
$request->payments ?? [],
$data->id,
'expense',
$request->amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\IncomeCategory;
use Illuminate\Http\Request;
class AcnooIncomeCategoryController extends Controller
{
public function index()
{
$data = IncomeCategory::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'categoryName' => 'required|unique:income_categories,categoryName,NULL,id,business_id,' . auth()->user()->business_id,
]);
$data = IncomeCategory::create($request->except('status') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status == 'true' ? 1 : 0,
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
$category = IncomeCategory::findOrFail($id);
$request->validate([
'categoryName' => [
'required',
'unique:income_categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$category->update($request->except('status') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status == 'true' ? 1 : 0,
]);
return response()->json([
'message' => __('Data updated successfully.'),
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
$category = IncomeCategory::findOrFail($id);
$category->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Income;
use App\Traits\DateFilterTrait;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Events\MultiPaymentProcessed;
class AcnooIncomeController extends Controller
{
Use DateFilterTrait;
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$incomeQuery = Income::with(['category:id,categoryName', 'payment_type:id,name', 'branch:id,name'])->where('business_id', $businessId);
// Branch filter
if ($request->branch_id) {
$incomeQuery->where('branch_id', $request->branch_id);
}
// Apply date filter
if(request('duration')){
$this->applyDateFilter($incomeQuery, request('duration'), 'incomeDate', request('from_date'), request('to_date'));
}
// Search filter
if ($request->filled('search')) {
$search = $request->search;
$incomeQuery->where(function ($query) use ($search) {
$query->where('incomeFor', 'like', '%' . $search . '%')
->orWhere('paymentType', 'like', '%' . $search . '%')
->orWhere('amount', 'like', '%' . $search . '%')
->orWhere('referenceNo', 'like', '%' . $search . '%')
->orWhereHas('category', fn($q) => $q->where('categoryName', 'like', '%' . $search . '%'))
->orWhereHas('payment_type', fn($q) => $q->where('name', 'like', '%' . $search . '%'))
->orWhereHas('branch', fn($q) => $q->where('name', 'like', '%' . $search . '%'));
});
}
$data = $incomeQuery->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'income_category_id' => 'required|exists:income_categories,id',
'incomeFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'note' => 'nullable|string',
'payments' => 'required|array|min:1',
'payments.*.type' => 'required|string',
'payments.*.amount' => 'nullable|numeric|min:0',
'payments.*.cheque_number' => 'nullable|string',
], [
'payments.required' => 'At least one payment method is required.',
'payments.*.type.required' => 'Each payment must have a type.',
'payments.*.amount.numeric' => 'Each payment amount must be numeric.',
]);
DB::beginTransaction();
try {
$total_amount = collect($request->payments)
->reject(fn($p) => strtolower($p['type'] ?? '') == 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
updateBalance($total_amount, 'decrement');
$data = Income::create($request->except('status', 'amount', 'paymentType') + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
'amount' => $total_amount,
]);
event(new MultiPaymentProcessed(
$request->payments ?? [],
$data->id,
'income',
$total_amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\DueCollect;
use App\Models\Party;
use App\Models\Purchase;
use App\Models\Sale;
use App\Models\SaleReturn;
use Illuminate\Http\Request;
class AcnooInvoiceController extends Controller
{
public function index(Request $request)
{
$request->validate([
'party_id' => 'required|exists:parties,id'
]);
$party = Party::select('id', 'due', 'name', 'type')->find(request('party_id'));
if ($party->type == 'Supplier')
{
$data = $party->load('purchases_dues:id,party_id,dueAmount,paidAmount,totalAmount,invoiceNumber');
} else {
$data = $party->load('sales_dues:id,party_id,dueAmount,paidAmount,totalAmount,invoiceNumber');
}
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function newInvoice(Request $request)
{
$request->validate([
'platform' => 'required|in:sales,purchases,due_collects,sales_return,purchases_return'
]);
if ($request->platform == 'sales') {
$prefix = 'S-';
$id = Sale::where('business_id', auth()->user()->business_id)->count();
} elseif ($request->platform == 'purchases') {
$prefix = 'P-';
$id = Purchase::where('business_id', auth()->user()->business_id)->count();
} elseif ($request->platform == 'sales_return') {
$prefix = 'SR-';
$id = SaleReturn::where('business_id', auth()->user()->business_id)->count();
} elseif ($request->platform == 'purchases_return') {
// $prefix = 'PR-';
// $id = Purchase::where('business_id', auth()->user()->business_id)->count();
}
else {
$prefix = 'D-';
$id = DueCollect::where('business_id', auth()->user()->business_id)->count();
}
$invoice = $prefix . str_pad($id + 1, 5, '0', STR_PAD_LEFT);
return response()->json($invoice);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AcnooLanguageController extends Controller
{
public function index()
{
$data = json_decode(file_get_contents(base_path('lang/langlist.json')), true);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'lang' => 'required|max:30|min:1|string'
]);
auth()->user()->update([
'lang' => $request->lang
]);
return response()->json([
'message' => __('Language updated successfully.')
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Option;
use App\Http\Controllers\Controller;
class AcnooPrivacyController extends Controller
{
public function index()
{
$policy = Option::where('key', 'policy')->first()->value;
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $policy ?? 'Privacy Policy',
]);
}
}

View File

@@ -0,0 +1,485 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\ComboProduct;
use App\Models\Stock;
use App\Models\Product;
use App\Helpers\HasUploader;
use App\Models\Vat;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooProductController extends Controller
{
use HasUploader;
/**
* Display a listing of the resource.
*/
public function index()
{
$user = auth()->user();
$products = Product::with([
'unit:id,unitName',
'vat:id,rate',
'brand:id,brandName',
'category:id,categoryName',
'product_model:id,name',
'stocks',
'combo_products.stock.product' => function ($query) {
$query->select('id', 'productName', 'productCode');
},
'rack:id,name',
'shelf:id,name'
])
->where(function ($query) {
$query->where(function ($q) {
$q->where('product_type', '!=', 'combo')
->whereHas('stocks');
})
->orWhere(function ($q) {
$q->where('product_type', 'combo')
->whereHas('combo_products');
});
})
->withSum('saleDetails', 'quantities')
->withSum('purchaseDetails', 'quantities')
->withSum('stocks', 'productStock')
->where('business_id', $user->business_id)
->latest()
->get();
$total_stock_value = $products->sum(function ($product) {
return $product->stocks->sum(function ($stock) {
return $stock->productPurchasePrice * $stock->productStock;
});
});
$products = $products->map(function ($product) {
$product->total_sale_amount = $product->saleDetails->sum(function ($sale) {
return ($sale->price - $sale->discount) * $sale->quantities;
});
$product->total_profit_loss = $product->saleDetails->sum(function ($sale) {
// If lossProfit column exists, use it
if (!is_null($sale->lossProfit)) {
return $sale->lossProfit;
}
// Otherwise calculate: (price - discount - purchase price) * quantity
return (($sale->price - $sale->discount) - $sale->productPurchasePrice) * $sale->quantities;
});
return $product;
});
return response()->json([
'message' => __('Data fetched successfully.'),
'total_stock_value' => $total_stock_value,
'data' => $products,
]);
}
public function store(Request $request)
{
if (is_string($request->stocks)) {
$request->merge(['stocks' => json_decode($request->stocks, true)]);
}
if (is_string($request->warranty_guarantee_info)) {
$request->merge(['warranty_guarantee_info' => json_decode($request->warranty_guarantee_info, true)]);
}
if (is_string($request->combo_products)) {
$request->merge(['combo_products' => json_decode($request->combo_products, true)]);
}
if (is_string($request->variation_ids)) {
$decoded = json_decode($request->variation_ids, true);
$request->merge(['variation_ids' => $decoded]);
}
$business_id = auth()->user()->business_id;
$request->validate([
'vat_id' => 'nullable|exists:vats,id',
'unit_id' => 'nullable|exists:units,id',
'brand_id' => 'nullable|exists:brands,id',
'category_id' => 'nullable|exists:categories,id',
'model_id' => 'nullable|exists:product_models,id',
'vat_type' => 'nullable|in:inclusive,exclusive',
'productName' => 'required|string|max:255',
'productPicture' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'productCode' => [
'nullable',
Rule::unique('products')->where(function ($query) use ($business_id) {
return $query->where('business_id', $business_id);
}),
],
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
'color' => 'nullable|string|max:255',
'weight' => 'nullable|string|max:255',
'capacity' => 'nullable|string|max:255',
'productManufacturer' => 'nullable|string|max:255',
'product_type' => 'required|in:single,variant,combo',
'variation_ids' => 'nullable|array',
'variation_ids.*' => 'exists:variations,id',
'stocks.*.warehouse_id' => 'nullable|exists:warehouses,id',
'stocks.*.productStock' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.exclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.inclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.profit_percent' => 'nullable|numeric|max:99999999.99',
'stocks.*.productSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productWholeSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productDealerPrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.mfg_date' => 'nullable|date',
'stocks.*.expire_date' => 'nullable|date|after_or_equal:stocks.*.mfg_date',
'stocks' => 'nullable|array',
'stocks.*.batch_no' => [
'nullable',
function ($attribute, $value, $fail) use ($request) {
$batchNos = collect($request->stocks)->pluck('batch_no')->filter()->toArray();
if (count($batchNos) !== count(array_unique($batchNos))) {
$fail('Duplicate batch number found in the request.');
}
},
],
// Combo validation
'combo_products' => 'nullable|array',
'combo_products.*.stock_id' => [
'required_if:product_type,combo',
Rule::exists('stocks', 'id')->where('business_id', $business_id),
],
'combo_products.*.quantity' => 'required_if:product_type,combo|numeric|min:1',
]);
DB::beginTransaction();
try {
// vat calculation
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
// 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') + [
'business_id' => $business_id,
'alert_qty' => $request->alert_qty ?? 0,
'profit_percent' => $request->product_type == 'combo' ? $request->profit_percent ?? 0 : 0,
'productSalePrice' => $request->product_type == 'combo' ? $request->productSalePrice ?? 0 : 0,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture') : NULL,
]);
// Single or Variant Product
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,
'batch_no' => $stock['batch_no'] ?? null,
'warehouse_id' => $stock['warehouse_id'] ?? null,
'productStock' => $stock['productStock'] ?? 0,
'productPurchasePrice' => $purchasePrice,
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
'productDealerPrice' => $stock['productDealerPrice'] ?? 0,
'mfg_date' => $stock['mfg_date'] ?? null,
'expire_date' => $stock['expire_date'] ?? null,
'variation_data' => isset($stock['variation_data']) ? json_encode($stock['variation_data']) : null,
'variant_name' => $stock['variant_name'] ?? null,
'serial_numbers' => $stock['serial_numbers'] ?? null,
'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id,
'created_at' => now(),
'updated_at' => now(),
];
}
Stock::insert($stockData);
}
// Combo Product
if ($request->product_type === 'combo' && !empty($request->combo_products)) {
foreach ($request->combo_products as $item) {
ComboProduct::create([
'product_id' => $product->id,
'stock_id' => $item['stock_id'],
'quantity' => $item['quantity'],
'purchase_price' => $item['purchase_price'],
]);
}
}
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $product
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage(),
], 406);
}
}
public function show(string $id)
{
$data = Product::with([
'unit:id,unitName',
'vat:id,rate',
'brand:id,brandName',
'category:id,categoryName',
'product_model:id,name',
'stocks',
'stocks.warehouse:id,name',
'combo_products.stock.product' => function ($query) {
$query->select('id', 'productName', 'productCode');
},
'rack:id,name',
'shelf:id,name'
])
->withSum('saleDetails', 'quantities')
->withSum('purchaseDetails', 'quantities')
->withSum('stocks', 'productStock')
->findOrFail($id);
$data->total_sale_amount = $data->saleDetails->sum(function ($sale) {
return ($sale->price - $sale->discount) * $sale->quantities;
});
$data->total_profit_loss = $data->saleDetails->sum(function ($sale) {
if (!is_null($sale->lossProfit)) {
return $sale->lossProfit;
}
return (($sale->price - $sale->discount) - $sale->productPurchasePrice) * $sale->quantities;
});
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Product $product)
{
$business_id = auth()->user()->business_id;
if ($product->product_type != $request->product_type) {
return response()->json([
'message' => __('Product type can not be changed.'),
], 406);
}
if (is_string($request->stocks)) {
$request->merge(['stocks' => json_decode($request->stocks, true)]);
}
if (is_string($request->warranty_guarantee_info)) {
$request->merge(['warranty_guarantee_info' => json_decode($request->warranty_guarantee_info, true)]);
}
if (is_string($request->combo_products)) {
$request->merge(['combo_products' => json_decode($request->combo_products, true)]);
}
if (is_string($request->variation_ids)) {
$request->merge(['variation_ids' => json_decode($request->variation_ids, true)]);
}
$request->validate([
'vat_id' => 'nullable|exists:vats,id',
'unit_id' => 'nullable|exists:units,id',
'brand_id' => 'nullable|exists:brands,id',
'category_id' => 'nullable|exists:categories,id',
'model_id' => 'nullable|exists:product_models,id',
'vat_type' => 'nullable|in:inclusive,exclusive',
'productName' => 'required|string|max:255',
'productPicture' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'productCode' => [
'nullable',
Rule::unique('products', 'productCode')->ignore($product->id)->where(function ($query) use ($business_id) {
return $query->where('business_id', $business_id);
}),
],
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
'color' => 'nullable|string|max:255',
'weight' => 'nullable|string|max:255',
'capacity' => 'nullable|string|max:255',
'productManufacturer' => 'nullable|string|max:255',
'product_type' => 'required|in:single,variant,combo',
'variation_ids' => 'nullable|array',
'variation_ids.*' => 'exists:variations,id',
'stocks.*.warehouse_id' => 'nullable|exists:warehouses,id',
'stocks.*.productStock' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.exclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.inclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.profit_percent' => 'nullable|numeric|max:99999999.99',
'stocks.*.productSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productWholeSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productDealerPrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.mfg_date' => 'nullable|date',
'stocks.*.expire_date' => 'nullable|date|after_or_equal:stocks.*.mfg_date',
'stocks' => 'nullable|array',
'stocks.*.batch_no' => [
'nullable',
function ($attribute, $value, $fail) use ($request) {
$batchNos = collect($request->stocks)->pluck('batch_no')->filter()->toArray();
if (count($batchNos) !== count(array_unique($batchNos))) {
$fail('Duplicate batch number found in the request.');
}
},
],
// Combo validation
'combo_products' => 'nullable|array',
'combo_products.*.stock_id' => [
'required_if:product_type,combo',
Rule::exists('stocks', 'id')->where('business_id', $business_id),
],
'combo_products.*.quantity' => 'required_if:product_type,combo|numeric|min:1',
]);
DB::beginTransaction();
try {
// VAT calculation
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
// Update product
$product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent']) + [
'business_id' => $business_id,
'alert_qty' => $request->alert_qty ?? 0,
'profit_percent' => $request->profit_percent ?? 0,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture', $product->productPicture) : $product->productPicture,
]);
// Delete previous stocks and combos
if ($product->product_type === 'combo') {
ComboProduct::where('product_id', $product->id)->delete();
}
// Handle Single/Variant Product Stocks
if (in_array($request->product_type, ['single', 'variant']) && !empty($request->stocks)) {
$existingStockIds = $product->stocks()->pluck('id')->toArray();
$incomingStockIds = collect($request->stocks)->pluck('stock_id')->filter()->toArray();
// Delete removed stocks
$stocksToDelete = array_diff($existingStockIds, $incomingStockIds);
Stock::whereIn('id', $stocksToDelete)->delete();
// Insert or Update
foreach ($request->stocks as $stock) {
// Decode variation_data if sent as string
if (isset($stock['variation_data']) && is_string($stock['variation_data'])) {
$stock['variation_data'] = json_decode($stock['variation_data'], true);
}
$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,
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
'productDealerPrice' => $stock['productDealerPrice'] ?? 0,
'mfg_date' => $stock['mfg_date'] ?? null,
'expire_date' => $stock['expire_date'] ?? null,
'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,
];
if ($stockId) {
Stock::where('id', $stockId)->update($payload);
} else {
Stock::create($payload);
}
}
}
// Handle Combo Product
if ($request->product_type === 'combo' && !empty($request->combo_products)) {
foreach ($request->combo_products as $item) {
ComboProduct::create([
'product_id' => $product->id,
'stock_id' => $item['stock_id'],
'quantity' => $item['quantity'],
'purchase_price' => $item['purchase_price'] ?? 0,
]);
}
}
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $product->load('stocks'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage()
], 406);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Product $product)
{
if (file_exists($product->productPicture)) {
Storage::delete($product->productPicture);
}
$product->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
public function generateCode()
{
$product_id = (Product::where('business_id', auth()->user()->business_id)->count() ?? 0) + 1;
$code = str_pad($product_id, 4, '0', STR_PAD_LEFT);
return response()->json([
'data' => $code,
]);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
class AcnooProfileController extends Controller
{
use HasUploader;
public function index()
{
$user = User::with('business')->findOrFail(auth()->id());
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $user
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|max:250',
'email' => ['required', 'email', Rule::unique('users')->ignore(auth()->id())],
'image' => 'nullable|image|mimes:jpeg,png,gif|dimensions:max_width=2000,max_height=2000|max:1048',
]);
$user = User::findOrFail(auth()->id());
$user->update($request->except('image') + [
'image' => $request->image ? $this->upload($request, 'image', $user->image) : $user->image,
]);
$user = User::findOrFail(auth()->id());
$data = [
'name' => $user->name,
'email' => $user->email,
'phone' => $user->phone,
'image' => $user->image,
];
return response()->json([
'message' => __('Profile updated successfully.'),
'data' => $data,
]);
}
public function changePassword(Request $request)
{
$request->validate([
'current_password' => 'required',
'password' => 'required|string|min:6',
]);
$user = auth()->user();
if ($request->current_password == $request->password){
return response()->json([
'message' => __('You have already used this password.')
], 422);
}
if (!Hash::check($request->current_password, $user->password)) {
return response()->json([
'message' => __('Current password does not match with old password.')
], 422);
}
$user->update([
'password' => Hash::make($request->password),
]);
return response()->json([
'message' => __('Password changed successfully.'),
]);
}
}

View File

@@ -0,0 +1,534 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Vat;
use App\Models\Sale;
use App\Models\Product;
use App\Models\Purchase;
use App\Models\PaymentType;
use App\Models\Transaction;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use App\Traits\DateFilterTrait;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class AcnooReportController extends Controller
{
use DateFilterTrait;
public function lossProfit(Request $request)
{
$user = auth()->user();
$businessId = $user->business_id;
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
$duration = $request->duration ?: 'today';
$salesQuery = DB::table('sales')
->select(
DB::raw('DATE(saleDate) as date'),
DB::raw('SUM(actual_total_amount) as total_sales'),
DB::raw('SUM(lossProfit) as total_sale_income')
)
->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->groupBy(DB::raw('DATE(saleDate)'));
$this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date);
$dailySales = $salesQuery->get();
$sale_datas = $dailySales->map(fn($sale) => (object)[
'type' => 'Sale',
'date' => $sale->date,
'total_sales' => $sale->total_sales,
'total_incomes' => $sale->total_sale_income,
]);
$incomeQuery = DB::table('incomes')
->select(
DB::raw('DATE(incomeDate) as date'),
DB::raw('SUM(amount) as total_incomes')
)
->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->groupBy(DB::raw('DATE(incomeDate)'));
$this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date);
$dailyIncomes = $incomeQuery->get();
$income_datas = $dailyIncomes->map(fn($income) => (object)[
'type' => 'Income',
'date' => $income->date,
'total_incomes' => $income->total_incomes,
]);
$mergedIncomeSaleData = collect();
$allDates = $dailySales->pluck('date')
->merge($dailyIncomes->pluck('date'))
->unique()
->sort();
foreach ($allDates as $date) {
if ($income = $income_datas->firstWhere('date', $date)) {
$mergedIncomeSaleData->push($income);
}
if ($sale = $sale_datas->firstWhere('date', $date)) {
$mergedIncomeSaleData->push($sale);
}
}
$dailyPayrolls = collect();
if (moduleCheck('HrmAddon')) {
$payrollQuery = DB::table('payrolls')
->select(
DB::raw('DATE(date) as date'),
DB::raw('SUM(amount) as total_payrolls')
)
->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->groupBy(DB::raw('DATE(date)'));
$this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date);
$dailyPayrolls = $payrollQuery->get();
}
$expenseQuery = DB::table('expenses')
->select(
DB::raw('DATE(expenseDate) as date'),
DB::raw('SUM(amount) as total_expenses_only')
)
->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->groupBy(DB::raw('DATE(expenseDate)'));
$this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date);
$dailyExpenses = $expenseQuery->get();
$mergedExpenseData = collect();
$allExpenseDates = $dailyExpenses->pluck('date')
->merge($dailyPayrolls->pluck('date'))
->unique()
->sort();
foreach ($allExpenseDates as $date) {
if ($expense = $dailyExpenses->firstWhere('date', $date)) {
$mergedExpenseData->push((object)[
'type' => 'Expense',
'date' => $date,
'total_expenses' => $expense->total_expenses_only,
]);
}
if ($payroll = $dailyPayrolls->firstWhere('date', $date)) {
$mergedExpenseData->push((object)[
'type' => 'Payroll',
'date' => $date,
'total_expenses' => $payroll->total_payrolls,
]);
}
}
$grossSaleProfit = $sale_datas->sum('total_sales');
$grossIncomeProfit = $income_datas->sum('total_incomes') + $sale_datas->sum('total_incomes');
$totalExpenses = $mergedExpenseData->sum('total_expenses');
$netProfit = $grossIncomeProfit - $totalExpenses;
$allTimeIncomes = DB::table('incomes')->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->sum('amount');
$allTimeSaleProfit = DB::table('sales')->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->sum('lossProfit');
$allTimePayrolls = moduleCheck('HrmAddon')
? DB::table('payrolls')->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->sum('amount')
: 0;
$allTimeExpensesOnly = DB::table('expenses')->where('business_id', $businessId)
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
->sum('amount');
$cardGrossProfit = $allTimeIncomes + $allTimeSaleProfit;
$totalCardExpenses = $allTimePayrolls + $allTimeExpensesOnly;
$cardNetProfit = $cardGrossProfit - $totalCardExpenses;
return response()->json([
'mergedIncomeSaleData' => $mergedIncomeSaleData->values(),
'mergedExpenseData' => $mergedExpenseData->values(),
'grossSaleProfit' => $grossSaleProfit,
'grossIncomeProfit' => $grossIncomeProfit,
'totalExpenses' => $totalExpenses,
'netProfit' => $netProfit,
'cardGrossProfit' => $cardGrossProfit,
'totalCardExpenses' => $totalCardExpenses,
'cardNetProfit' => $cardNetProfit,
]);
}
public function cashFlow(Request $request)
{
$query = Transaction::with([
'paymentType:id,name',
'sale:id,party_id',
'sale.party:id,name',
'saleReturn:id,sale_id',
'purchase:id,party_id',
'purchase.party:id,name',
'purchaseReturn:id,purchase_id',
'dueCollect:id,party_id',
'dueCollect.party:id,name',
])
->where('business_id', auth()->user()->business_id)
->whereIn('type', ['debit', 'credit']);
$total_cash_in = (clone $query)
->where('type', 'credit')
->sum('amount');
$total_cash_out = (clone $query)
->where('type', 'debit')
->sum('amount');
$total_running_cash = $total_cash_in - $total_cash_out;
// Apply date filter
$duration = $request->duration ?: 'today';
$this->applyDateFilter($query, $duration, 'date', $request->from_date, $request->to_date);
$cash_flows = $query->get();
$firstDate = $cash_flows->first()?->date;
if ($firstDate) {
$opening_balance = (clone $query)
->whereDate('date', '<', $firstDate)
->selectRaw("SUM(CASE WHEN type='credit' THEN amount ELSE 0 END) - SUM(CASE WHEN type='debit' THEN amount ELSE 0 END) as balance")
->value('balance') ?? 0;
} else {
$opening_balance = 0;
}
return response()->json([
'cash_in' => $total_cash_in,
'cash_out' => $total_cash_out,
'running_cash' => $total_running_cash,
'initial_running_cash' => $opening_balance,
'data' => $cash_flows,
]);
}
public function balanceSheetReport(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$productQuery = Product::select('id', 'business_id', 'productName', 'product_type', 'created_at')
->with(['stocks:id,business_id,product_id,productStock,productPurchasePrice', 'combo_products.stock'])
->where('business_id', $businessId);
$this->applyDateFilter($productQuery, $duration, 'created_at', $fromDate, $toDate);
$products = $productQuery->get()
->map(function ($item) {
$item->source = 'product';
return $item;
});
$bankQuery = PaymentType::where('business_id', $businessId);
$this->applyDateFilter($bankQuery, $duration, 'opening_date', $fromDate, $toDate);
$banks = $bankQuery->get()
->map(function ($item) {
$item->source = 'bank';
return $item;
});
$product_bank_datas = $products->merge($banks);
$total_stock_value = 0;
foreach ($products as $product) {
// SINGLE / VARIANT
if (in_array($product->product_type, ['single', 'variant'])) {
foreach ($product->stocks as $stock) {
$total_stock_value += $stock->productStock * $stock->productPurchasePrice;
}
}
// COMBO
if ($product->product_type === 'combo') {
foreach ($product->combo_products as $combo) {
$childStock = $combo->stock;
if ($childStock) {
$total_stock_value += ($childStock->productStock / $combo->quantity) * $combo->purchase_price;
}
}
}
}
$totalBankBalance = $banks->sum('balance');
$total_asset = $total_stock_value + $totalBankBalance;
return response()->json([
'asset_datas' => $product_bank_datas,
'total_asset' => $total_asset,
]);
}
public function subscriptionReport(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$subscriptionQuery = PlanSubscribe::with([
'plan:id,subscriptionName',
'business:id,companyName,business_category_id,pictureUrl',
'business.category:id,name',
'gateway:id,name'
])->where('business_id', $businessId);
$this->applyDateFilter($subscriptionQuery, $duration, 'created_at', $fromDate, $toDate);
$subscriptions = $subscriptionQuery->get();
return response()->json([
'data' => $subscriptions,
]);
}
public function taxReport(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$vats = Vat::where('business_id', $businessId)->whereStatus(1)->get();
//sales
$salesQuery = Sale::with('party:id,name,email,phone,type', 'vat:id,name')
->where('business_id', $businessId)
->where('vat_amount', '>', 0);
$this->applyDateFilter($salesQuery, $duration, 'created_at', $fromDate, $toDate);
$sales = $salesQuery->get()->map(function ($item) {
$item->source = 'sale'; // append a source field
return $item;
});
$salesTotalAmount = $sales->sum('totalAmount');
$salesTotalDiscount = $sales->sum('discountAmount');
$salesTotalVat = $sales->sum('vat_amount');
$salesVatTotals = [];
foreach ($vats as $vat) {
$salesVatTotals[$vat->id] = $sales->where('vat_id', $vat->id)->sum('vat_amount');
}
//purchase
$purchaseQuery = Purchase::with('party:id,name,email,phone,type', 'vat:id,name')
->where('business_id', $businessId)
->where('vat_amount', '>', 0);
$this->applyDateFilter($purchaseQuery, $duration, 'created_at', $fromDate, $toDate);
$purchases = $purchaseQuery->get()->map(function ($item) {
$item->source = 'purchase'; // append a source field
return $item;
});
$purchasesTotalAmount = $purchases->sum('totalAmount');
$purchasesTotalDiscount = $purchases->sum('discountAmount');
$purchasesTotalVat = $purchases->sum('vat_amount');
$purchasesVatTotals = [];
foreach ($vats as $vat) {
$purchasesVatTotals[$vat->id] = $purchases->where('vat_id', $vat->id)->sum('vat_amount');
}
return response()->json([
'sales' => $sales,
'sales_total_amount' => $salesTotalAmount,
'sales_total_discount' => $salesTotalDiscount,
'sales_total_vat' => $salesTotalVat,
'purchases' => $purchases,
'purchases_total_amount' => $purchasesTotalAmount,
'purchases_total_discount' => $purchasesTotalDiscount,
'purchases_total_vat' => $purchasesTotalVat,
]);
}
public function billWiseProfitReport(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$billQuery = Sale::select('id', 'business_id', 'party_id', 'invoiceNumber', 'saleDate', 'totalAmount', 'lossProfit')
->with('party:id,name', 'details:id,sale_id,product_id,price,quantities,productPurchasePrice,lossProfit', 'details.product:id,productName')->where('business_id', $businessId);
$this->applyDateFilter($billQuery, $duration, 'saleDate', $fromDate, $toDate);
$bills = $billQuery->get();
$total_amount = $bills->sum('totalAmount');
$total_bill_profit = $bills
->where('lossProfit', '>=', 0)
->sum('lossProfit');
$total_bill_loss = $bills
->where('lossProfit', '<', 0)
->sum('lossProfit');
return response()->json([
'data' => $bills,
'total_amount' => $total_amount,
'total_bill_profit' => $total_bill_profit,
'total_bill_loss' => $total_bill_loss,
]);
}
public function productSaleHistory(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$productQuery = Product::with(['saleDetails', 'purchaseDetails', 'saleDetails.sale', 'stocks', 'combo_products'])
->where('business_id', $businessId)
->whereHas('saleDetails.sale', function ($sale) use ($duration, $request) {
$this->applyDateFilter($sale, $duration, 'saleDate', $request->from_date, $request->to_date);
});
$products = $productQuery->get();
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
return $product->saleDetails->sum('quantities');
});
return response()->json([
'data' => $products,
'total_purchase_qty' => $total_purchase_qty,
'total_sale_qty' => $total_sale_qty,
]);
}
public function productSaleHistoryDetails(Request $request, $productId)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$product = Product::select('id', 'business_id', 'productName')
->with([
'saleDetails' => function ($q) use ($duration, $fromDate, $toDate) {
$q->whereHas('sale', function ($sale) use ($duration, $fromDate, $toDate) {
$this->applyDateFilter($sale, $duration, 'saleDate', $fromDate, $toDate);
});
$q->select('id', 'sale_id', 'product_id', 'quantities', 'price', 'productPurchasePrice')
->with([
'sale:id,invoiceNumber,saleDate'
]);
},
])->where('business_id', $businessId)->findOrFail($productId);
$totalQuantities = $product->saleDetails->sum('quantities');
$totalSalePrice = $product->saleDetails->sum('price');
$totalPurchasePrice = $product->saleDetails->sum('productPurchasePrice');
return response()->json([
'data' => $product,
'total_quantities' => $totalQuantities,
'total_sale_price' => $totalSalePrice,
'total_purchase_price' => $totalPurchasePrice,
]);
}
public function productPurchaseHistory(Request $request)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$productQuery = Product::with(['saleDetails', 'purchaseDetails', 'purchaseDetails.purchase', 'stocks', 'combo_products'])
->where('business_id', $businessId)
->whereHas('purchaseDetails.purchase', function ($purchase) use ($duration, $request) {
$this->applyDateFilter($purchase, $duration, 'purchaseDate', $request->from_date, $request->to_date);
});
$products = $productQuery->get();
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
return $product->saleDetails->sum('quantities');
});
return response()->json([
'data' => $products,
'total_purchase_qty' => $total_purchase_qty,
'total_sale_qty' => $total_sale_qty,
]);
}
public function productPurchaseHistoryDetails(Request $request, $productId)
{
$businessId = auth()->user()->business_id;
$duration = $request->duration ?: 'today';
$fromDate = $request->from_date;
$toDate = $request->to_date;
$product = Product::select('id', 'business_id', 'productName')
->with([
'purchaseDetails' => function ($q) use ($duration, $fromDate, $toDate) {
$q->whereHas('purchase', function ($purchase) use ($duration, $fromDate, $toDate) {
$this->applyDateFilter($purchase, $duration, 'purchaseDate', $fromDate, $toDate);
});
$q->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice')
->with([
'purchase:id,invoiceNumber,purchaseDate'
]);
},
])->where('business_id', $businessId)->findOrFail($productId);
$totalQuantities = $product->purchaseDetails->sum('quantities');
$totalPurchasePrice = $product->purchaseDetails->sum('productPurchasePrice');
return response()->json([
'data' => $product,
'total_quantities' => $totalQuantities,
'total_purchase_price' => $totalPurchasePrice,
]);
}
}

View File

@@ -0,0 +1,561 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\MultiPaymentProcessed;
use App\Models\PaymentType;
use App\Models\Product;
use App\Models\Sale;
use App\Models\Party;
use App\Models\Stock;
use App\Events\SaleSms;
use App\Models\SaleDetails;
use App\Helpers\HasUploader;
use App\Models\Transaction;
use App\Traits\DateFilterTrait;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooSaleController extends Controller
{
use HasUploader;
Use DateFilterTrait;
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
// Build the query with all eager loads
$query = Sale::with([
'user:id,name,role',
'party:id,name,email,phone,type,address',
'details',
'details.stock:id,batch_no,productStock',
'details.product:id,productName,category_id,productCode,productPurchasePrice,productStock,product_type',
'details.product.category:id,categoryName',
'saleReturns.details',
'vat:id,name,rate',
'branch:id,name,phone,address',
'transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta',
'transactions.paymentType:id,name,meta'
])->where('business_id', $business_id);
// Filter returned sales
$query->when(request('returned-sales') == "true", function ($query) {
$query->whereHas('saleReturns');
});
// Branch filter
if ($request->filled('branch_id')) {
$query->where('branch_id', $request->branch_id);
}
// Apply date filter
if(request('duration')){
$this->applyDateFilter($query, request('duration'), 'saleDate', request('from_date'), request('to_date'));
}
// Search filter
if ($request->filled('search')) {
$query->where(function ($q) use ($request) {
$q->where('invoiceNumber', 'like', "%{$request->search}%")
->orWhere('paymentType', 'like', "%{$request->search}%")
->orWhereHas('party', fn($q) => $q->where('name', 'like', "%{$request->search}%"))
->orWhereHas('payment_type', fn($q) => $q->where('name', 'like', "%{$request->search}%"))
->orWhereHas('branch', fn($q) => $q->where('name', 'like', "%{$request->search}%"));
});
}
$data = $query->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'total_amount' => $data->sum('totalAmount'),
'data' => $data,
]);
}
public function show(string $id)
{
$business_id = auth()->user()->business_id;
$sale = Sale::with(['user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.stock:id,batch_no,productStock', 'details.product:id,productName,category_id,productCode,productPurchasePrice,productStock,product_type', 'details.product.category:id,categoryName', 'saleReturns.details', 'vat:id,name,rate', 'branch:id,name,phone,address', 'transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name,meta'])
->where('business_id', $business_id)
->where('id', $id)
->first();
if (!$sale) {
return response()->json([
'message' => __('Sale not found.'),
], 404);
}
return response()->json([
'message' => __('Sale fetched successfully.'),
'data' => $sale,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'party_id' => 'nullable|exists:parties,id',
'vat_id' => 'nullable|exists:vats,id',
'products' => 'required',
'saleDate' => 'nullable|date',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
'rounding_option' => 'nullable|in:none,round_up,nearest_whole_number,nearest_0.05,nearest_0.1,nearest_0.5',
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
$request_products = json_decode($request->products, true);
$payments = json_decode($request->payments, true);
$paidAmount = collect($payments)
->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
if ($request->dueAmount > 0) {
$allowDueForGuest = product_setting()?->modules['allow_due_sale'] ?? false;
// No party
if (!$request->party_id) {
if (!$allowDueForGuest) {
return response()->json([
'message' => __('You have not allowed guest sales, so you cannot sell on due for a walking customer.')
], 400);
}
} else {
// Party exist
$party = Party::findOrFail($request->party_id);
$newDue = $party->due + $request->dueAmount;
// Check credit limit
if ($party->credit_limit > 0 && $newDue > $party->credit_limit) {
return response()->json([
'message' => __('Sale cannot be created. Party due will exceed credit limit!')
], 400);
}
$party->update(['due' => $newDue]);
}
}
updateBalance($paidAmount, 'increment');
$sale = Sale::create($request->except('image', 'isPaid', 'lossProfit', 'payment_type_id') + [
'user_id' => auth()->id(),
'business_id' => $business_id,
'isPaid' => filter_var($request->isPaid, FILTER_VALIDATE_BOOLEAN) ? 1 : 0,
'image' => $request->image ? $this->upload($request, 'image') : null,
'meta' => [
'note' => $request->note,
],
]);
$subtotal = 0;
$totalPurchaseAmount = 0;
foreach ($request_products as $productData) {
$productDiscount = $productData['discount'] ?? 0;
$product = Product::findOrFail($productData['product_id']);
$qty = $productData['quantities'] ?? 1;
$price = $productData['price'] ?? 0;
$lineSubtotal = ($price * $qty) - $productDiscount;
$subtotal += $lineSubtotal;
// Combo Products
if ($product->product_type == 'combo') {
$comboTotalPurchase = 0;
foreach ($product->combo_products as $comboItem) {
$stock = Stock::findOrFail($comboItem->stock_id);
$requiredQty = $comboItem->quantity * $qty;
if ($stock->productStock < $requiredQty) {
return response()->json([
'message' => "Combo item '{$productData['product_name']}' (Batch: {$stock->batch_no}) not available. Available stock: {$stock->productStock}"
], 400);
}
// Decrement stock
$stock->decrement('productStock', $requiredQty);
// Combo purchase total
$comboTotalPurchase += $stock->productPurchasePrice * $requiredQty;
}
$totalPurchaseAmount += $comboTotalPurchase;
// Store combo in SaleDetails
$saleDetails[] = [
'sale_id' => $sale->id,
'stock_id' => null,
'product_id' => $product->id,
'price' => $price,
'discount' => $productDiscount,
'lossProfit' => $lineSubtotal - $comboTotalPurchase,
'quantities' => $qty,
'productPurchasePrice' => $comboTotalPurchase ?? 0,
'expire_date' => null,
'warranty_guarantee_info' => $product->warranty_guarantee_info
? json_encode($product->warranty_guarantee_info)
: null,
];
} // Single / Variant Products
else {
$stock = Stock::findOrFail($productData['stock_id']);
if ($stock->productStock < $qty) {
return response()->json([
'message' => "Stock not available for product: {$productData['product_name']}. Available: {$stock->productStock}"
], 400);
}
// Decrement stock
$stock->decrement('productStock', $qty);
$purchasePrice = $stock->productPurchasePrice ?? 0;
// Calculate profit/loss
$totalPurchaseAmount += ($purchasePrice * $qty);
$saleDetails[] = [
'sale_id' => $sale->id,
'stock_id' => $stock->id,
'product_id' => $product->id,
'price' => $price,
'discount' => $productDiscount,
'lossProfit' => $lineSubtotal - ($purchasePrice * $qty),
'quantities' => $qty,
'productPurchasePrice' => $purchasePrice,
'expire_date' => $stock->expire_date ?? null,
'warranty_guarantee_info' => $product->warranty_guarantee_info
? json_encode($product->warranty_guarantee_info)
: null,
];
}
}
SaleDetails::insert($saleDetails);
$saleLossProfit = ($subtotal - $totalPurchaseAmount) - ($request->discountAmount ?? 0);
$sale->update([
'lossProfit' => $saleLossProfit,
]);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$payments,
$sale->id,
'sale',
$paidAmount,
$request->party_id ?? null
));
// Send SMS
event(new SaleSms($sale));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $sale->load('user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.stock:id,batch_no', 'details.product:id,productName,category_id,product_type', 'details.product.category:id,categoryName', 'saleReturns.details', 'vat:id,name,rate', 'payment_type:id,name', 'branch:id,name,phone,address'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
$request->validate([
'party_id' => 'nullable|exists:parties,id',
'vat_id' => 'nullable|exists:vats,id',
'products' => 'required',
'saleDate' => 'nullable|date',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
'rounding_option' => 'nullable|in:none,round_up,nearest_whole_number,nearest_0.05,nearest_0.1,nearest_0.5',
]);
$sale = Sale::findOrFail($id);
$business_id = auth()->user()->business_id;
DB::beginTransaction();
try {
if ($sale->load('saleReturns')->saleReturns->count() > 0) {
return response()->json([
'message' => __("You cannot update this sale because it has already been returned.")
], 400);
}
$request_products = json_decode($request->products, true);
$payments = json_decode($request->payments, true);
$prevDetails = SaleDetails::where('sale_id', $sale->id)->get();
// Rollback previous stock
foreach ($prevDetails as $prevItem) {
if ($prevItem->stock_id) {
$stock = Stock::findOrFail($prevItem->stock_id);
$stock->increment('productStock', $prevItem->quantities);
}
}
$prevDetails->each->delete();
$saleDetails = [];
$totalLossProfit = 0;
$totalProductWiseDiscount = 0;
foreach ($request_products as $productData) {
$product = Product::findOrFail($productData['product_id']);
$qty = $productData['quantities'] ?? 1;
$price = $productData['price'] ?? 0;
$productDiscount = $productData['discount'] ?? 0;
$totalProductWiseDiscount += $productDiscount;
$unitSalePrice = $price;
$unitDiscount = $productDiscount / max($qty, 1);
$finalSalePricePerUnit = $unitSalePrice - $unitDiscount;
// Combo product
if ($product->product_type == 'combo') {
foreach ($product->combo_products as $comboItem) {
$stock = Stock::findOrFail($comboItem->stock_id);
$requiredQty = $comboItem->quantity * $qty;
if ($stock->productStock < $requiredQty) {
return response()->json([
'message' => "Combo item '{$productData['product_name']}' (Batch: {$stock->batch_no}) not available. Available stock: {$stock->productStock}"
], 400);
}
// Decrement stock
$stock->decrement('productStock', $requiredQty);
// Total combo purchase
$comboPurchaseTotal += ($stock->productPurchasePrice * $requiredQty); // 🔥 changed
}
// purchase price per unit of combo
$purchaseUnit = $comboPurchaseTotal / max($qty, 1);
// correct lossProfit
$lossProfit = ($finalSalePricePerUnit - $purchaseUnit) * $qty;
$totalLossProfit += $lossProfit;
$saleDetails[] = [
'sale_id' => $sale->id,
'stock_id' => null,
'product_id' => $product->id,
'price' => $price,
'discount' => $productDiscount,
'lossProfit' => $lossProfit,
'quantities' => $qty,
'productPurchasePrice' => $purchaseUnit,
'expire_date' => null,
'warranty_guarantee_info' => $product->warranty_guarantee_info
? json_encode($product->warranty_guarantee_info)
: null,
];
} // Single / Variant product
else {
$stock = Stock::findOrFail($productData['stock_id']);
if ($stock->productStock < $qty) {
return response()->json([
'message' => "Stock not available for product: {$productData['product_name']}. Available: {$stock->productStock}"
], 400);
}
$stock->decrement('productStock', $qty);
$purchaseUnit = $stock->productPurchasePrice ?? 0;
$lossProfit = ($finalSalePricePerUnit - $purchaseUnit) * $qty;
$totalLossProfit += $lossProfit;
$saleDetails[] = [
'sale_id' => $sale->id,
'stock_id' => $stock->id,
'product_id' => $product->id,
'price' => $price,
'discount' => $productDiscount,
'lossProfit' => $lossProfit,
'quantities' => $qty,
'productPurchasePrice' => $purchaseUnit,
'expire_date' => $stock->expire_date ?? null,
'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null,
];
}
}
SaleDetails::insert($saleDetails);
// Check if any old transaction type = deposit
$hasDeposit = Transaction::where('business_id', $business_id)
->where('reference_id', $sale->id)
->where('platform', 'sale')
->where('type', 'deposit')
->exists();
$paidAmount = collect($payments)
->reject(function ($p) use ($hasDeposit) {
// exclude cheque only if not deposit
return !$hasDeposit && strtolower($p['type'] ?? '') === 'cheque';
})
->sum(fn($p) => $p['amount'] ?? 0);
$dueAmount = max($request->totalAmount - $paidAmount, 0);
// Handle party due
if ($sale->dueAmount || $dueAmount) {
$party = Party::findOrFail($request->party_id);
$newDue = $request->party_id == $sale->party_id
? (($party->due - $sale->dueAmount) + $dueAmount)
: ($party->due + $dueAmount);
if ($party->credit_limit > 0 && $newDue > $party->credit_limit) {
return response()->json([
'message' => __("Cannot update sale. Party due will exceed credit limit!")
], 400);
}
$party->update(['due' => $newDue]);
if ($request->party_id != $sale->party_id) {
$prev_party = Party::findOrFail($sale->party_id);
$prev_party->update([
'due' => $prev_party->due - $sale->dueAmount
]);
}
}
// Adjust business balance
$balanceDiff = $paidAmount - $sale->paidAmount;
updateBalance($balanceDiff, 'increment');
$sale->update($request->except('image', 'isPaid', 'paidAmount', 'paidAmount', 'dueAmount') + [
'user_id' => auth()->id(),
'isPaid' => filter_var($request->isPaid, FILTER_VALIDATE_BOOLEAN) ? 1 : 0,
'lossProfit' => $totalLossProfit - ($request->discountAmount ?? 0) - $totalProductWiseDiscount,
'image' => $request->image ? $this->upload($request, 'image', $sale->image) : $sale->image,
'paidAmount' => $paidAmount > $request->totalAmount ? $request->totalAmount : $paidAmount,
'dueAmount' => $dueAmount,
'meta' => [
'note' => $request->note,
],
]);
// Multiple Payment Process
$oldTransactions = Transaction::where('business_id', $business_id)
->where('reference_id', $sale->id)
->where('platform', 'sale')
->get();
// Revert old transactions
foreach ($oldTransactions as $old) {
switch ($old->transaction_type) {
case 'bank_payment':
$paymentType = PaymentType::find($old->payment_type_id);
if ($paymentType) {
$paymentType->decrement('balance', $old->amount);
}
break;
case 'wallet_payment':
if ($request->party_id) {
$party = Party::find($request->party_id);
if ($party) {
$party->increment('wallet', $old->amount);
}
}
break;
case 'cash_payment':
case 'cheque_payment':
// nothing to revert for cash/cheque
break;
}
}
// Delete old transactions
Transaction::where('business_id', $business_id)
->where('reference_id', $sale->id)
->where('platform', 'sale')
->delete();
// Process new payments
event(new MultiPaymentProcessed(
$payments ?? [],
$sale->id,
'sale',
$paidAmount,
$request->party_id ?? null,
));
DB::commit();
return response()->json([
'message' => __('Sale updated successfully.'),
'data' => $sale->load(
'details',
'details.product:id,productName,product_type',
'details.stock:id,batch_no',
'party:id,name,email,phone,address',
'vat:id,name,rate',
'branch:id,name,phone,address'
),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => 'Something went wrong.',
'error' => $e->getMessage()
], 500);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Sale $sale)
{
foreach ($sale->details as $item) {
Stock::findOrFail($item->stock_id)->increment('productStock', $item->quantities);
}
if ($sale->dueAmount) {
$party = Party::findOrFail($sale->party_id);
$party->update([
'due' => $party->due - $sale->dueAmount
]);
}
updateBalance($sale->paidAmount, 'decrement');
if (file_exists($sale->image)) {
Storage::delete($sale->image);
}
$sale->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\PlanSubscribe;
use App\Http\Controllers\Controller;
class AcnooSubscribesController extends Controller
{
public function index()
{
$subscribes = PlanSubscribe::where('user_id', auth()->id())->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $subscribes
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Plan;
use App\Http\Controllers\Controller;
class AcnooSubscriptionsController extends Controller
{
public function index()
{
$plans = Plan::whereStatus(1)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $plans,
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Option;
use App\Http\Controllers\Controller;
class AcnooTermsController extends Controller
{
public function index()
{
$term = Option::where('key', 'term')->first()->value;
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $term ?? 'Terms & Conditions.',
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class AcnooUserController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$user = auth()->user();
$data = User::with('branch:id,name')
->where('business_id', $user->business_id)
->when($user->role == 'staff', function ($q) use ($user) {
$q->where('branch_id', $user->branch_id);
})
->when($user->branch_id || $user->active_branch_id, function ($q) use ($user) {
$q->where('branch_id', $user->branch_id ?? $user->active_branch_id);
})
->where('id', '!=', auth()->id())
->where('role', 'staff')
->latest()
->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|max:30',
'password' => 'required|min:4|max:15',
'email' => 'required|email|unique:users,email',
'branch_id' => 'nullable|exists:branches,id',
]);
$data = User::create([
'role' => 'staff',
'name' => $request->name,
'email' => $request->email,
'visibility' => $request->visibility,
'password' => Hash::make($request->password),
'business_id' => auth()->user()->business_id,
'branch_id' => $request->branch_id ?? auth()->user()->branch_id ?? auth()->user()->active_branch_id,
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, User $user)
{
$request->validate([
'name' => 'required|max:30',
'password' => 'nullable|min:4|max:15',
'branch_id' => 'nullable|exists:branches,id',
'email' => 'required|email|unique:users,email,' . $user->id,
]);
$user->update([
'name' => $request->name,
'email' => $request->email,
'branch_id' => $request->branch_id,
'visibility' => $request->visibility,
'business_id' => auth()->user()->business_id,
'password' => $request->password ? Hash::make($request->password) : $user->password,
'branch_id' => $request->branch_id ?? auth()->user()->branch_id ?? auth()->user()->active_branch_id,
]);
return response()->json([
'message' => __('Data saved successfully.')
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(User $user)
{
$user->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Business;
use App\Models\Vat;
use Illuminate\Http\Request;
class AcnooVatController extends Controller
{
public function index()
{
$vats = Vat::where('business_id', auth()->user()->business_id)
->when(request('type') == 'single', function ($query) {
$query->whereNull('sub_vat');
})
->when(request('type') == 'group', function ($query) {
$query->whereNotNull('sub_vat');
})
->when(request('status'), function ($query) {
$query->where('status', request('status') == 'active' ? 1 : 0);
})
->latest()
->get();
return response()->json([
'message' => 'Data fetched successfully.',
'data' => $vats,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'vat_ids' => 'required_if:rate,null',
'rate' => 'required_if:rate,null|numeric',
]);
if ($request->rate && !$request->vat_ids) {
$vat = Vat::create($request->all() + [
'business_id' => auth()->user()->business_id,
]);
} elseif (!$request->rate && $request->vat_ids) {
$vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get();
$tax_rate = 0;
$sub_vats = [];
foreach ($vats as $vat) {
$sub_vats[] = [
'id' => $vat->id,
'name' => $vat->name,
'rate' => $vat->rate,
];
$tax_rate += $vat->rate;
}
$vat = Vat::create([
'rate' => $tax_rate,
'sub_vat' => $sub_vats,
'name' => $request->name,
'business_id' => auth()->user()->business_id,
]);
} else {
return response()->json([
'message' => 'Invalid data format.',
], 406);
}
return response()->json([
'message' => 'Data created successfully.',
'data' => $vat,
]);
}
public function update(Request $request, Vat $vat)
{
$request->validate([
'name' => 'required|string|max:255',
'vat_ids' => 'required_if:rate,null',
'rate' => 'required_if:rate,null|numeric',
]);
if ($request->rate && !$request->vat_ids) {
$vat->update($request->all());
$vatGroupExist = Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->get();
foreach ($vatGroupExist as $group) {
$subVats = collect($group->sub_vat)->map(function ($subVat) use ($vat) {
if ($subVat['id'] == $vat->id) {
$subVat['rate'] = $vat->rate;
$subVat['name'] = $vat->name;
}
return $subVat;
});
$group->update([
'rate' => $subVats->sum('rate'),
'sub_vat' => $subVats->toArray(),
]);
}
} elseif (!$request->rate && $request->vat_ids) {
$vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get();
$tax_rate = 0;
$sub_vats = [];
foreach ($vats as $single_tax) {
$sub_vats[] = [
'id' => $single_tax->id,
'name' => $single_tax->name,
'rate' => $single_tax->rate,
];
$tax_rate += $single_tax->rate;
}
$vat->update([
'rate' => $tax_rate,
'sub_vat' => $sub_vats,
'name' => $request->name,
'status' => $request->status ?? $vat->status,
]);
} else {
return response()->json([
'message' => 'Invalid data format.',
], 406);
}
return response()->json([
'message' => 'Data updated successfully.',
'data' => $vat,
]);
}
public function destroy(Vat $vat)
{
// When sub_vat is null, check if the VAT exists in any other VAT's sub_vat
if (is_null($vat->sub_vat) && Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->exists()) {
return response()->json([
'message' => 'Cannot delete. This VAT is part of a VAT group.',
], 404);
}
$vat->delete();
return response()->json([
'message' => 'Data deleted successfully',
]);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Models\User;
use App\Mail\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Mail;
class AcnooForgotPasswordController extends Controller
{
public function sendResetCode(Request $request) : JsonResponse
{
$request->validate([
'email' => 'required|email|exists:users,email'
]);
$expire = now()->addHour();
$code = random_int(100000,999999);
$user = User::where('email',$request->email)->first();
$user->update(['remember_token' => $code, 'email_verified_at' => $expire]);
$data = [
'code' => $code
];
try {
if (env('QUEUE_MAIL')) {
Mail::to($request->email)->queue(new PasswordReset($data));
} else {
Mail::to($request->email)->send(new PasswordReset($data));
}
return response()->json([
'message' => 'Password reset code has been sent to your email.',
]);
} catch (\Exception $exception){
return response()->json([
'message' => $exception->getMessage(),
], 422);
}
}
public function verifyResetCode(Request $request)
{
$request->validate([
'code' => 'required|integer',
'email' => 'required|exists:users,email',
]);
$user = User::where('email', $request->email)->first();
if ($user->remember_token == $request->code) {
if ($user->email_verified_at > now()) {
return response()->json([
'message' => __('The code has been verified.')
]);
} else {
return response()->json([
'error' => __('The verification code has expired.')
], 400);
}
} else {
return response()->json([
'error' => __('Invalid Code.')
], 404);
}
}
public function resetPassword(Request $request) : JsonResponse
{
$request->validate([
'email' => 'required|exists:users,email',
'password' => ['required', 'min:4'],
]);
$user = User::where('email', $request->email)->first();
$user->update([
'password' => bcrypt($request->password),
]);
return response()->json([
'message' => 'Your password has been changed!',
]);
}
}

View File

@@ -0,0 +1,323 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Models\User;
use App\Models\Branch;
use App\Models\Currency;
use App\Mail\WelcomeMail;
use App\Models\UserCurrency;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Laravel\Sanctum\NewAccessToken;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Config;
use App\Http\Controllers\Auth\RegisteredUserController;
class AuthController extends Controller
{
public function otpSettings()
{
$otp_settings = get_option('email-varification');
return response()->json([
'message' => 'Data fetched successfully.',
'data' => $otp_settings
]);
}
public function signUp(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'password' => 'required|min:6|max:100',
'email' => 'required|email',
]);
$code = random_int(100000, 999999);
$otpController = new RegisteredUserController();
$visibility_time = $otpController->getOtpTimeInSeconds();
$expire = now()->addSeconds($visibility_time);
$data = [
'code' => $code,
'name' => $request->name,
];
$user = User::where('email', $request->email)->first();
if ($user && $user->business_id) {
return response()->json([
'message' => 'This email is already exists.',
], 406);
}
$otp_settings = get_option('email-varification');
$verify_email = ($otp_settings['otp_status'] ?? false) && $otp_settings['otp_status'] == 'on' ? 1 : 0;
if ($verify_email) {
if (env('MAIL_USERNAME')) {
if (env('QUEUE_MAIL')) {
Mail::to($request->email)->queue(new WelcomeMail($data));
} else {
Mail::to($request->email)->send(new WelcomeMail($data));
}
} else {
return response()->json([
'message' => __('Mail service is not configured. Please contact your administrator.'),
], 406);
}
}
$user = User::updateOrCreate(['email' => $request->email], $request->except('password') + [
'is_verified' => $verify_email ? 0 : 1,
'remember_token' => $verify_email ? $code : NULL,
'email_verified_at' => $verify_email ? $expire : NULL,
'password' => Hash::make($request->password),
]);
return response()->json([
'message' => $verify_email ? 'An otp code has been sent to your email. Please check and confirm.' : 'Sign Up completed. Please setup your profile.',
'token' => $verify_email ? null : $user->createToken('createToken')->plainTextToken,
'data' => $user,
]);
}
public function submitOtp(Request $request)
{
$request->validate([
'email' => 'required|email',
'otp' => 'required|min:4|max:15',
]);
$user = User::where('email', $request->email)->first();
if (!$user) {
return response()->json([
'status' => 404,
'message' => __('User not found'),
], 404);
}
if ($user->remember_token == $request->otp) {
if ($user->email_verified_at > now()) {
Auth::login($user);
$is_setup = $user->business_id ? true : false;
$token = $user->createToken('createToken')->plainTextToken;
$accessToken = $user->createToken('createToken');
$this->setAccessTokenExpiration($accessToken);
$user->update([
'is_verified' => 1,
'remember_token' => NULL,
'email_verified_at' => now(),
]);
$currency = Currency::select('id', 'name', 'code', 'symbol', 'position')->where('is_default', 1)->first();
return response()->json([
'message' => 'Logged In successfully!',
'is_setup' => $is_setup,
'token' => $token,
'currency' => $currency,
]);
} else {
return response()->json([
'error' => __('The verification otp has been expired.')
], 400);
}
} else {
return response()->json([
'error' => __('Invalid otp.')
], 404);
}
}
public function login(Request $request)
{
$request->validate([
'password' => 'required',
'email' => 'required|email',
]);
if (auth()->attempt($request->only('email', 'password'))) {
$user = auth()->user();
$business = $user->business;
$branch = Branch::find($user->branch_id);
if (multibranch_active() && branch_count()) {
if ($branch && !$branch->status && $user->branch_id && !$branch->is_main) {
Auth::logout();
return response()->json([
'message' => 'This branch is not active, Please contact with manager.',
], 406);
}
} elseif (!multibranch_active()) {
if ($user->active_branch_id) {
$user->update([
'active_branch_id' => NULL
]);
} elseif ($user->branch_id && !$branch->is_main) {
Auth::logout();
return response()->json([
'message' => 'Multibranch is not allowed in your current package, please upgrade your subscription plan.',
], 406);
}
} elseif (!$branch && $user->branch_id) {
Auth::logout();
return response()->json([
'message' => 'Your current branch has been deleted, Please contact with manager.',
], 406);
} elseif ($business && !$business->status) {
Auth::logout();
return response()->json([
'message' => 'Your business is inactive. Please contact your administrator.',
], 406);
}
if ($user->role != 'staff' && $user->role != 'shop-owner') {
return response()->json([
'message' => 'You can not login as ' . $user->role . ' from the app!'
], 406);
}
if ($user->remember_token && !$user->business_id) { // If user didn't verify email
$code = random_int(100000, 999999);
$expire = now()->addMinutes(env('OTP_VISIBILITY_TIME') ?? 3);
$data = [
'code' => $code,
'name' => $request->name,
];
if (env('MAIL_USERNAME')) {
if (env('QUEUE_MAIL')) {
Mail::to($request->email)->queue(new WelcomeMail($data));
} else {
Mail::to($request->email)->send(new WelcomeMail($data));
}
} else {
return response()->json([
'message' => __('Mail service is not configured. Please contact your administrator.'),
], 406);
}
User::where('email', $request->email)->first()->update(['remember_token' => $code, 'email_verified_at' => $expire]);
return response()->json([
'message' => 'An otp code has been sent to your email. Please check and confirm.',
], 201);
}
$currency = UserCurrency::select('id', 'name', 'code', 'symbol', 'position')->where('business_id', $user->business_id)->first();
return response()->json([
'message' => 'User login successfully!',
'data' => [
'is_setup' => $user->business_id ? true : false,
'token' => $user->createToken('createToken')->plainTextToken,
'currency' => $currency ?? Currency::select('id', 'name', 'code', 'symbol', 'position')->where('is_default', 1)->first(),
],
]);
} else {
return response()->json([
'message' => 'Invalid email or password!'
], 404);
}
}
protected function setAccessTokenExpiration(NewAccessToken $accessToken)
{
$expiration = now()->addMinutes(Config::get('sanctum.expiration'));
DB::table('personal_access_tokens')
->where('id', $accessToken->accessToken->id)
->update(['expires_at' => $expiration]);
}
public function signOut(): JsonResponse
{
$currentToken = auth()->user()->currentAccessToken();
if ($currentToken) {
$currentToken->delete();
return response()->json([
'message' => __('Sign out successfully'),
]);
} else {
return response()->json([
'message' => __('Unauthorized'),
], 401);
}
}
public function refreshToken()
{
if (auth()->user()->tokens()) {
auth()->user()->currentAccessToken()->delete();
$data['token'] = auth()->user()->createToken('createToken')->plainTextToken;
return response()->json($data);
} else {
return response()->json([
'message' => __('Unauthorized'),
], 401);
}
}
public function resendOtp(Request $request)
{
$request->validate([
'email' => 'required|email|exists:users,email',
]);
$code = random_int(100000, 999999);
$otpController = new RegisteredUserController();
$visibility_time = $otpController->getOtpTimeInSeconds();
$expire = now()->addSeconds($visibility_time);
$data = [
'code' => $code,
'name' => $request->name,
];
if (env('MAIL_USERNAME')) {
if (env('QUEUE_MAIL')) {
Mail::to($request->email)->queue(new WelcomeMail($data));
} else {
Mail::to($request->email)->send(new WelcomeMail($data));
}
} else {
return response()->json([
'message' => __('Mail service is not configured. Please contact your administrator.'),
], 406);
}
User::where('email', $request->email)->first()->update(['remember_token' => $code, 'email_verified_at' => $expire]);
return response()->json([
'message' => 'An otp code has been sent to your email. Please check and confirm.',
]);
}
public function moduleCheck()
{
if (moduleCheck(request('module_name'))) {
return response()->json([
'status' => true
]);
}
return response()->json([
'status' => false
]);
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use Illuminate\Http\Request;
class BankController extends Controller
{
public function index()
{
$data = PaymentType::with('branch:id,name')->where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'branch_id' => 'nullable|exists:branches,id',
'opening_balance' => 'nullable|numeric|min:0',
'opening_date' => 'nullable|date',
'show_in_invoice' => 'boolean',
'meta' => 'nullable|array',
'meta.account_number' => 'nullable|string|max:255',
'meta.routing_number' => 'nullable|string|max:255',
'meta.upi_id' => 'nullable|string|max:255',
'meta.bank_name' => 'nullable|string|max:255',
'meta.account_holder' => 'nullable|string|max:255',
'meta.branch' => 'nullable|string|max:255',
]);
$forbidden = ['cash', 'cheque'];
if (in_array(strtolower($request->name), $forbidden)) {
return response()->json([
'message' => __('You cannot create a bank account with this name.'),
], 422);
}
$data = PaymentType::create($request->except('business_id', 'show_in_invoice', 'balance', 'opening_balance', 'meta') + [
'business_id' => auth()->user()->business_id,
'opening_balance' => $request->opening_balance ?? 0,
'balance' => $request->opening_balance ?? 0,
'show_in_invoice' => $request->show_in_invoice,
'meta' => [
'account_number' => $request->account_number,
'routing_number' => $request->routing_number,
'upi_id' => $request->upi_id,
'bank_name' => $request->bank_name,
'account_holder' => $request->account_holder,
'branch' => $request->branch,
]
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
public function show($id)
{
$data = PaymentType::with('branch:id,name')->where('business_id', auth()->user()->business_id)->find($id);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function update(Request $request, $id)
{
$request->validate([
'name' => 'required|string|max:255',
'branch_id' => 'nullable|exists:branches,id',
'opening_balance' => 'nullable|numeric|min:0',
'opening_date' => 'nullable|date',
'show_in_invoice' => 'boolean',
'meta' => 'nullable|array',
'meta.account_number' => 'nullable|string|max:255',
'meta.routing_number' => 'nullable|string|max:255',
'meta.upi_id' => 'nullable|string|max:255',
'meta.bank_name' => 'nullable|string|max:255',
'meta.account_holder' => 'nullable|string|max:255',
'meta.branch' => 'nullable|string|max:255',
]);
$payment_type = PaymentType::findOrFail($id);
$forbidden = ['cash', 'cheque'];
if (in_array(strtolower($request->name), $forbidden)) {
return response()->json([
'message' => __('You cannot create a bank account with this name.'),
], 422);
}
$hasTransactions = $payment_type->transactions()->exists();
if ($hasTransactions) {
return response()->json([
'message' => __("You can't Change opening balance because this bank already has transactions.")
], 422);
}
$updateData = $request->except('business_id', 'show_in_invoice', 'balance', 'opening_balance', 'meta');
// Add controlled fields
$updateData['business_id'] = auth()->user()->business_id;
$updateData['show_in_invoice'] = $request->show_in_invoice;
// Only update balances if no transactions exist
if (!$hasTransactions) {
$updateData['opening_balance'] = $request->opening_balance ?? 0;
$updateData['balance'] = $request->opening_balance ?? 0;
}
// Build meta explicitly
$updateData['meta'] = [
'account_number' => $request->account_number,
'routing_number' => $request->routing_number,
'upi_id' => $request->upi_id,
'bank_name' => $request->bank_name,
'account_holder' => $request->account_holder,
'branch' => $request->branch,
];
$payment_type->update($updateData);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $payment_type,
]);
}
public function destroy($id)
{
$paymentType = PaymentType::find($id);
if (!$paymentType) {
return response()->json([
'message' => __('Data not found.'),
'data' => null,
], 404);
}
// Check if this payment type is used in any transaction
$hasTransactions = $paymentType->transactions()->exists();
$balance = $paymentType->balance ?? 0;
if ($hasTransactions && $balance != 0) {
return response()->json([
'message' => __('Bank cant be deleted. It has transactions or balance.')
], 400);
}
$paymentType->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,357 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use App\Models\Transaction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class BankTransactionController extends Controller
{
use HasUploader;
public function index()
{
$business_id = auth()->user()->business_id;
$payment_type_id = request('bank_id');
$data = Transaction::with('user:id,name', 'branch:id,name')
->where('business_id', $business_id)
->where(function ($query) use ($payment_type_id) {
$query->where('payment_type_id', $payment_type_id)
->orWhere('from_bank', $payment_type_id)
->orWhere('to_bank', $payment_type_id);
})
->latest()
->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function show($id)
{
$business_id = auth()->user()->business_id;
$transaction = Transaction::with('user:id,name', 'branch:id,name')
->where('business_id', $business_id)
->where('id', $id)
->first();
if (!$transaction) {
return response()->json([
'message' => __('Transaction not found.'),
], 404);
}
return response()->json([
'message' => __('Transaction fetched successfully.'),
'data' => $transaction,
]);
}
public function store(Request $request)
{
$request->validate([
'from' => 'required|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:bank_to_bank,bank_to_cash,adjust_bank',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$business_id = auth()->user()->business_id;
$amount = $request->amount ?? 0;
$type = 'transfer';
DB::beginTransaction();
try {
$fromBank = PaymentType::find($request->from);
// Prevent transferring to the same bank
if ($request->transaction_type == 'bank_to_bank' && $request->from == $request->to) {
return response()->json([
'message' => 'Cannot transfer between the same bank account.'
], 400);
}
// Update balances based on transaction type
if ($request->transaction_type == 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in source bank account.'
], 400);
}
$fromBank->decrement('balance', $amount);
$toBank->increment('balance', $amount);
} elseif ($request->transaction_type == 'bank_to_cash') {
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in selected bank.'
], 400);
}
$fromBank->decrement('balance', $amount);
} elseif ($request->transaction_type == 'adjust_bank') {
$type = $request->type;
if ($type == 'credit' && $fromBank->balance < $amount) {
return response()->json([
'message' => 'Cannot decrease below zero balance.'
], 400);
}
if( $type == 'credit'){
$fromBank->increment('balance', $amount);
}else{
$fromBank->decrement('balance', $amount);
}
}
$data = Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => $type,
'platform' => 'bank',
'transaction_type' => $request->transaction_type,
'amount' => $amount,
'from_bank' => $request->from,
'to_bank' => ($request->transaction_type == 'bank_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage()
], 406);
}
}
public function update(Request $request, $id)
{
$request->validate([
'from' => 'required|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:bank_to_bank,bank_to_cash,adjust_bank',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$transaction = Transaction::findOrFail($id);
$fromBank = PaymentType::find($request->from);
$newAmount = $request->amount;
$newType = $request->transaction_type;
DB::beginTransaction();
try {
// Transaction type Same
if ($transaction->transaction_type === $request->transaction_type) {
if ($newType === 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
// Adjust balance difference
$diff = $newAmount - $transaction->amount;
if ($fromBank->balance < $diff && $diff > 0) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $diff);
$toBank->increment('balance', $diff);
} elseif ($newType === 'bank_to_cash') {
$diff = $newAmount - $transaction->amount;
if ($fromBank->balance < $diff && $diff > 0) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $diff);
} elseif ($newType === 'adjust_bank') {
$bankType = $request->type;
$oldType = $transaction->type;
$oldAmount = $transaction->amount;
if ($bankType === $oldType) {
// Same type: adjust by difference
$diff = $newAmount - $oldAmount;
if ($bankType == 'credit') {
$fromBank->increment('balance', $diff);
} else {
$fromBank->decrement('balance', $diff);
}
} else {
// Different type: reverse old and apply new
if ($oldType == 'credit') $fromBank->decrement('balance', $oldAmount);
else $fromBank->increment('balance', $oldAmount);
if ($bankType == 'credit') $fromBank->increment('balance', $newAmount);
else $fromBank->decrement('balance', $newAmount);
}
}
}
// Transaction type changed
else {
// Reverse old transaction completely
if ($transaction->transaction_type === 'bank_to_bank') {
$oldFrom = PaymentType::find($transaction->from_bank);
$oldTo = PaymentType::find($transaction->to_bank);
$oldFrom->increment('balance', $transaction->amount);
$oldTo->decrement('balance', $transaction->amount);
} elseif ($transaction->transaction_type === 'bank_to_cash') {
$oldFrom = PaymentType::find($transaction->from_bank);
$oldFrom->increment('balance', $transaction->amount);
} elseif ($transaction->transaction_type === 'adjust_bank') {
$oldFrom = PaymentType::find($transaction->from_bank);
$transaction->type === 'credit'
? $oldFrom->decrement('balance', $transaction->amount)
: $oldFrom->increment('balance', $transaction->amount);
}
// Apply new transaction
if ($newType === 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
if ($fromBank->balance < $newAmount) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $newAmount);
$toBank->increment('balance', $newAmount);
} elseif ($newType === 'bank_to_cash') {
if ($fromBank->balance < $newAmount) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $newAmount);
} elseif ($newType === 'adjust_bank') {
if ($request->type == 'credit') $fromBank->increment('balance', $newAmount);
else $fromBank->decrement('balance', $newAmount);
}
}
// Update transaction record
$transaction->update([
'type' => $newType === 'adjust_bank' ? $request->type : 'transfer',
'transaction_type' => $newType,
'amount' => $newAmount,
'from_bank' => $request->from,
'to_bank' => $newType === 'bank_to_bank' ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image', $transaction->image) : $transaction->image,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $transaction,
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => 'Error: ' . $e->getMessage()], 500);
}
}
public function destroy(string $id)
{
$transaction = Transaction::findOrFail($id);
DB::beginTransaction();
try {
$fromBank = PaymentType::find($transaction->from_bank);
$toBank = PaymentType::find($transaction->to_bank);
$amount = $transaction->amount;
// Allow only bank platform transactions to be deleted
if ($transaction->platform !== 'bank') {
return response()->json([
'message' => 'Cannot delete here, please delete from ' . ucfirst($transaction->platform) . ' section.',
], 400);
}
// Reverse balance changes based on transaction type
switch ($transaction->transaction_type) {
case 'bank_to_bank':
if ($toBank && $fromBank) {
// Ensure receiver bank has enough balance before reversing
if ($toBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in ' . $toBank->name . ' to reverse this transaction.',
], 400);
}
$fromBank->increment('balance', $amount);
$toBank->decrement('balance', $amount);
}
break;
case 'bank_to_cash':
if ($fromBank) {
$fromBank->increment('balance', $amount);
}
break;
case 'adjust_bank':
if ($fromBank) {
if ($transaction->type === 'credit') {
// Previously increased, so now decrease
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in ' . $fromBank->name . ' to reverse this transaction.',
], 400);
}
$fromBank->decrement('balance', $amount);
} else {
// Previously decreased, so now increase
$fromBank->increment('balance', $amount);
}
}
break;
}
if (file_exists($transaction->image)) {
Storage::delete($transaction->image);
}
$transaction->delete();
DB::commit();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => 'Error: ' . $e->getMessage()], 500);
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Imports\ProductImport;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
class BulkUploadControler extends Controller
{
public function store(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv'
]);
$businessId = auth()->user()->business_id;
Excel::import(new ProductImport($businessId), $request->file('file'));
return response()->json([
'message' => __('Bulk upload successfully.')
]);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\BusinessCategory;
class BusinessCategoryController extends Controller
{
public function index()
{
$data = BusinessCategory::whereStatus(1)->latest()->get();
return response()->json([
'data' => $data,
'message' => __('Data fetched successfully.'),
]);
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Option;
use App\Models\Plan;
use App\Models\User;
use App\Models\Business;
use App\Models\Currency;
use App\Helpers\HasUploader;
use App\Models\UserCurrency;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use Illuminate\Support\Facades\Hash;
class BusinessController extends Controller
{
use HasUploader;
public function index()
{
$business_id = auth()->user()->business_id;
// Ensure currency exists
$business_currency = UserCurrency::select('id', 'name', 'code', 'symbol', 'position')
->where('business_id', $business_id)
->first();
if (!$business_currency) {
$currency = Currency::where('is_default', 1)->first();
UserCurrency::create([
'name' => $currency->name,
'code' => $currency->code,
'rate' => $currency->rate,
'business_id' => $business_id,
'symbol' => $currency->symbol,
'currency_id' => $currency->id,
'position' => $currency->position,
'country_name' => $currency->country_name,
]);
}
// Fetch user and business info
$user = User::select('id', 'name', 'role', 'visibility', 'lang', 'email', 'branch_id', 'active_branch_id')->findOrFail(auth()->id());
$business = Business::with('category:id,name', 'enrolled_plan:id,plan_id,business_id,price,duration,allow_multibranch', 'enrolled_plan.plan:id,subscriptionName')->findOrFail($business_id);
//admin setting option
$generalValue = Option::where('key', 'general')->first()->value ?? [];
$develop_by_level = $generalValue['admin_footer_text'] ?? '';
$develop_by = $generalValue['admin_footer_link_text'] ?? '';
$develop_by_link = $generalValue['admin_footer_link'] ?? '';
// Get business settings option
$option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id);
$invoice_logo = $option->value['invoice_logo'] ?? null;
$a4_invoice_logo = $option->value['a4_invoice_logo'] ?? null;
$thermal_invoice_logo = $option->value['thermal_invoice_logo'] ?? null;
$invoice_scanner_logo = $option->value['invoice_scanner_logo'] ?? null;
$sale_rounding_option = $option->value['sale_rounding_option'] ?? 'none';
$note_label = $option->value['note_label'] ?? null;
$note = $option->value['note'] ?? null;
$gratitude_message = $option->value['gratitude_message'] ?? null;
$warranty_void_label = $option->value['warranty_void_label'] ?? null;
$warranty_void = $option->value['warranty_void'] ?? null;
$data = array_merge(
$business->toArray(),
['user' => $user->toArray() + ['active_branch' => $user->active_branch]],
['business_currency' => $business_currency],
['invoice_logo' => $invoice_logo],
['a4_invoice_logo' => $a4_invoice_logo],
['thermal_invoice_logo' => $thermal_invoice_logo],
['invoice_scanner_logo' => $invoice_scanner_logo],
['sale_rounding_option' => $sale_rounding_option],
['invoice_size' => !empty(invoice_setting()) && moduleCheck('ThermalPrinterAddon') ? invoice_setting() : null],
['invoice_language' => !empty(invoice_language()) ? invoice_language() : ''],
['note' => $note],
['note_label' => $note_label],
['gratitude_message' => $gratitude_message],
['warranty_void_label' => $warranty_void_label],
['warranty_void' => $warranty_void],
['show_note' => (int) ($option->value['show_note'] ?? 0)],
['show_gratitude_msg' => (int) ($option->value['show_gratitude_msg'] ?? 0)],
['show_invoice_scanner_logo' => (int) ($option->value['show_invoice_scanner_logo'] ?? 0)],
['show_a4_invoice_logo' => (int) ($option->value['show_a4_invoice_logo'] ?? 0)],
['show_thermal_invoice_logo' => (int) ($option->value['show_thermal_invoice_logo'] ?? 0)],
['show_warranty' => (int) ($option->value['show_warranty'] ?? 0)],
['develop_by_level' => $develop_by_level],
['develop_by' => $develop_by],
['develop_by_link' => $develop_by_link],
['branch_count' => branch_count()],
[
'addons' => [
'AffiliateAddon' => moduleCheck('AffiliateAddon'),
'MultiBranchAddon' => moduleCheck('MultiBranchAddon'),
'WarehouseAddon' => moduleCheck('WarehouseAddon'),
'ThermalPrinterAddon' => moduleCheck('ThermalPrinterAddon'),
'HrmAddon' => moduleCheck('HrmAddon'),
'CustomDomainAddon' => moduleCheck('CustomDomainAddon'),
'SerialCodeAddon' => moduleCheck('SerialCodeAddon')
]
],
);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data
]);
}
public function store(Request $request)
{
$request->validate([
'address' => 'nullable|max:250',
'companyName' => 'required|max:250',
'pictureUrl' => 'nullable|image|max:5120',
'shopOpeningBalance' => 'nullable|numeric',
'phoneNumber' => 'nullable',
'business_category_id' => 'required|exists:business_categories,id',
]);
DB::beginTransaction();
try {
$user = auth()->user();
$free_plan = Plan::where('subscriptionPrice', '<=', 0)->orWhere('offerPrice', '<=', 0)->first();
$business = Business::create($request->except('pictureUrl') + [
'phoneNumber' => $request->phoneNumber,
'subscriptionDate' => $free_plan ? now() : NULL,
'will_expire' => $free_plan ? now()->addDays($free_plan->duration) : NULL,
'pictureUrl' => $request->pictureUrl ? $this->upload($request, 'pictureUrl') : NULL
]);
PaymentType::create([
'name' => "Cash",
'business_id' => $business->id
]);
$user->update([
'business_id' => $business->id,
'phone' => $request->phoneNumber,
'name' => $business->companyName,
]);
$currency = Currency::where('is_default', 1)->first();
UserCurrency::create([
'business_id' => $business->id,
'currency_id' => $currency->id,
'name' => $currency->name,
'country_name' => $currency->country_name,
'code' => $currency->code,
'rate' => $currency->rate,
'symbol' => $currency->symbol,
'position' => $currency->position,
]);
if ($free_plan) {
$subscribe = PlanSubscribe::create([
'plan_id' => $free_plan->id,
'business_id' => $business->id,
'duration' => $free_plan->duration,
'allow_multibranch' => $free_plan->allow_multibranch,
]);
$business->update([
'plan_subscribe_id' => $subscribe->id,
]);
}
Cache::forget('plan-data-' . $business->id);
DB::commit();
return response()->json([
'message' => __('Business setup completed.'),
]);
} catch (\Throwable $th) {
DB::rollback();
return response()->json(__('Something went wrong, Please contact with admin.'), 403);
}
}
public function update(Request $request, Business $business)
{
$request->validate([
'address' => 'nullable|max:250',
'companyName' => 'nullable|required_if:sale_rounding_option,!=,null|max:250',
'business_category_id' => 'nullable|required_if:sale_rounding_option,!=,null|exists:business_categories,id',
'pictureUrl' => 'nullable|image|max:5120',
'show_company_name' => 'nullable|boolean',
'show_phone_number' => 'nullable|boolean',
'show_address' => 'nullable|boolean',
'show_email' => 'nullable|boolean',
'show_vat' => 'nullable|boolean',
'invoice_logo' => 'nullable|image|max:5120',
'a4_invoice_logo' => 'nullable|image|max:5120',
'thermal_invoice_logo' => 'nullable|image|max:5120',
'invoice_scanner_logo' => 'nullable|image|max:5120',
'sale_rounding_option' => 'nullable|in:none,round_up,nearest_whole_number,nearest_0.05,nearest_0.1,nearest_0.5',
'phoneNumber' => 'nullable',
'invoice_size' => 'nullable|string|max:100',
'invoice_language' => 'nullable|string|max:100',
'gratitude_message' => 'nullable|string|max:100',
'warranty_void_label' => 'nullable|string',
'warranty_void' => 'nullable|string',
'show_note' => 'nullable|boolean',
'show_gratitude_msg' => 'nullable|boolean',
'show_invoice_scanner_logo' => 'nullable|boolean',
'show_a4_invoice_logo' => 'nullable|boolean',
'show_thermal_invoice_logo' => 'nullable|boolean',
'show_warranty' => 'nullable|boolean',
]);
$business->update([
'meta' => [
'show_company_name' => (int) ($request->show_company_name ?? 0),
'show_phone_number' => (int) ($request->show_phone_number ?? 0),
'show_address' => (int) ($request->show_address ?? 0),
'show_email' => (int) ($request->show_email ?? 0),
'show_vat' => (int) ($request->show_vat ?? 0),
]
]);
// Update when sale_rounding_option is not provided
if (!$request->sale_rounding_option) {
auth()->user()->update([
'name' => $request->companyName,
'phone' => $request->phoneNumber,
]);
$business->update($request->except('pictureUrl', 'meta') + [
'pictureUrl' => $request->pictureUrl ? $this->upload($request, 'pictureUrl', $business->pictureUrl) : $business->pictureUrl,
]);
}
// Update or insert business settings
$setting = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business->id . '%')
->get()
->firstWhere('value.business_id', $business->id);
$invoiceLogo = $request->invoice_logo ? $this->upload($request, 'invoice_logo', $setting->value['invoice_logo'] ?? null) : ($setting->value['invoice_logo'] ?? null);
$a4_invoice_logo = $request->a4_invoice_logo ? $this->upload($request, 'a4_invoice_logo', $setting->value['a4_invoice_logo'] ?? null) : ($setting->value['a4_invoice_logo'] ?? null);
$thermal_invoice_logo = $request->thermal_invoice_logo ? $this->upload($request, 'thermal_invoice_logo', $setting->value['thermal_invoice_logo'] ?? null) : ($setting->value['thermal_invoice_logo'] ?? null);
$invoice_scanner_logo = $request->invoice_scanner_logo ? $this->upload($request, 'invoice_scanner_logo', $setting->value['invoice_scanner_logo'] ?? null) : ($setting->value['invoice_scanner_logo'] ?? null);
$settingData = [
'business_id' => $business->id,
'invoice_logo' => $invoiceLogo,
'a4_invoice_logo' => $a4_invoice_logo,
'thermal_invoice_logo' => $thermal_invoice_logo,
'invoice_scanner_logo' => $invoice_scanner_logo,
'sale_rounding_option' => $request->sale_rounding_option ?? 'none',
'note_label' => $request->note_label,
'note' => $request->note,
'gratitude_message' => $request->gratitude_message,
'warranty_void_label' => $request->warranty_void_label,
'warranty_void' => $request->warranty_void,
'vat_name' => $request->vat_name,
'vat_no' => $request->vat_no,
'show_note' => $request->show_note ?? 0,
'show_gratitude_msg' => $request->show_gratitude_msg ?? 0,
'show_invoice_scanner_logo' => $request->show_invoice_scanner_logo ?? 0,
'show_a4_invoice_logo' => $request->show_a4_invoice_logo ?? 0,
'show_thermal_invoice_logo' => $request->show_thermal_invoice_logo ?? 0,
'show_warranty' => $request->show_warranty ?? 0,
];
if ($setting) {
$setting->update([
'value' => array_merge($setting->value, $settingData),
]);
} else {
Option::create([
'key' => 'business-settings',
'value' => $settingData,
]);
}
// Update Invoice Settings
if ($request->filled('invoice_size')) {
$invoiceKey = 'invoice_setting_' . $business->id;
Option::updateOrCreate(
['key' => $invoiceKey],
['value' => $request->invoice_size]
);
Cache::forget($invoiceKey);
}
if ($request->filled('invoice_language')) {
$invoice_language_key = 'invoice_language_' . $business->id;
Option::updateOrCreate(
['key' => $invoice_language_key],
['value' => $request->invoice_language]
);
Cache::forget($invoice_language_key);
}
Cache::forget("business_setting_{$business->id}");
Cache::forget("business_sale_rounding_{$business->id}");
return response()->json([
'message' => __('Data saved successfully.'),
'business' => $business,
'invoice_logo' => $invoiceLogo,
'a4_invoice_logo' => $a4_invoice_logo,
'thermal_invoice_logo' => $thermal_invoice_logo,
'invoice_scanner_logo' => $invoice_scanner_logo,
'sale_rounding_option' => $request->sale_rounding_option,
'invoice_size' => $request->invoice_size,
'invoice_language' => $request->invoice_language,
'note_label' => $request->note_label,
'note' => $request->note,
'gratitude_message' => $request->gratitude_message,
'warranty_void_label' => $request->warranty_void_label,
'warranty_void' => $request->warranty_void,
'vat_name' => $request->vat_name,
'vat_no' => $request->vat_no,
'show_note' => (int) $request->show_note ?? 0,
'show_gratitude_msg' => (int) $request->show_gratitude_msg ?? 0,
'show_invoice_scanner_logo' => (int) $request->show_invoice_scanner_logo ?? 0,
'show_a4_invoice_logo' => (int) $request->show_a4_invoice_logo ?? 0,
'show_thermal_invoice_logo' => (int) $request->show_thermal_invoice_logo ?? 0,
'show_warranty' => (int) $request->show_warranty ?? 0,
]);
}
public function updateExpireDate(Request $request)
{
$days = $request->query('days', 0);
$operation = $request->query('operation');
$business = Business::where('id', auth()->user()->business_id)->first();
if (!$business) {
return response()->json([
'message' => 'Business not found.',
], 404);
}
if ($operation === 'add') {
$business->will_expire = now()->addDays($days);
} elseif ($operation === 'sub') {
$business->will_expire = now()->subDays($days);
} else {
return response()->json([
'message' => 'Invalid operation. Use "add" or "sub".',
], 400);
}
$business->save();
return response()->json([
'message' => 'Expiry date updated successfully.',
'will_expire' => $business->will_expire,
]);
}
public function deleteBusiness(Request $request)
{
$request->validate([
'password' => 'required|string',
]);
$user = auth()->user();
if (!Hash::check($request->password, $user->password)) {
return response()->json([
'message' => __('The provided password is incorrect.'),
], 406);
}
Business::where('id', $user->business_id)->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class BusinessCurrencySettingController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$currency_setting_key = 'currency_setting_' . $businessId;
$currency_setting = Option::where('key', $currency_setting_key)->first();
if ($currency_setting) {
return response()->json([
'message' => __('Currency data fetched successfully.'),
'currency_data' => $currency_setting->value,
]);
} else {
return response()->json([
'message' => __('Currency not found.'),
'currency_data' => null,
], 404);
}
}
public function store(Request $request)
{
$request->validate([
'currency' => 'required|string|max:100|in:us,european',
]);
$key = 'currency_setting_' . auth()->user()->business_id;
Option::updateOrCreate(
['key' => $key],
['value' => $request->currency]
);
Cache::forget($key);
return response()->json(__('Currency setting updated successfully.'));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Option;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
class BusinessInvoiceSettingController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$invoiceSetting = Option::where('key', 'invoice_settings')
->where('value', 'LIKE', '%"business_id":%' . $businessId . '%')
->get()
->firstWhere('value.business_id', $businessId);
if ($invoiceSetting && isset($invoiceSetting->value['invoice_size'])) {
return response()->json([
'message' => __('Invoice size fetched successfully.'),
'invoice_size' => $invoiceSetting->value['invoice_size'],
]);
} else {
return response()->json([
'message' => __('Invoice size not found.'),
'invoice_size' => null,
], 404);
}
}
public function updateInvoice(Request $request)
{
$request->validate([
'invoice_size' => 'required|string|max:100|in:a4,3_inch_80mm,2_inch_58mm',
]);
$key = 'invoice_setting_' . auth()->user()->business_id;
Option::updateOrCreate(
['key' => $key],
['value' => $request->invoice_size]
);
Cache::forget($key);
return response()->json([
'message' => __('Invoice size updated successfully.'),
]);
}
}

View File

@@ -0,0 +1,307 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use App\Models\Transaction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Carbon;
class CashController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
$data = Transaction::with('user:id,name')
->where('business_id', $business_id)
->whereIn('transaction_type', ['cash_payment', 'bank_to_cash', 'cash_to_bank', 'adjust_cash', 'cheque_to_cash'])
->when($request->duration, function ($query) use ($request) {
$today = Carbon::today();
if ($request->duration === 'today') {
$query->whereDate('date', $today);
} elseif ($request->duration === 'yesterday') {
$query->whereDate('date', Carbon::yesterday());
} elseif ($request->duration === 'last_seven_days') {
$startDate = Carbon::now()->subDays(7)->format('Y-m-d');
$endDate = Carbon::now()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'last_thirty_days') {
$startDate = Carbon::now()->subDays(30)->format('Y-m-d');
$endDate = Carbon::now()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
}
})
->latest()
->get();
$total_balance = cash_balance();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
'total_balance' => $total_balance,
]);
}
public function show($id)
{
$business_id = auth()->user()->business_id;
$transaction = Transaction::with('user:id,name', 'branch:id,name')
->where('business_id', $business_id)
->where('id', $id)
->first();
if (!$transaction) {
return response()->json([
'message' => __('Transaction not found.'),
], 404);
}
return response()->json([
'message' => __('Transaction fetched successfully.'),
'data' => $transaction,
]);
}
public function store(Request $request)
{
$request->validate([
'to' => 'nullable|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:cash_to_bank,adjust_cash',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$business_id = auth()->user()->business_id;
$amount = $request->amount ?? 0;
$type = 'transfer';
DB::beginTransaction();
try {
// Cash to Bank
if ($request->transaction_type === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
// increase target bank balance
$toBank->increment('balance', $amount);
// Adjust Cash
} elseif ($request->transaction_type === 'adjust_cash') {
$type = $request->type;
}
// Store transaction record
$data = Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => $type,
'platform' => 'cash',
'transaction_type' => $request->transaction_type,
'amount' => $amount,
'from_bank' => null,
'to_bank' => ($request->transaction_type === 'cash_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function update(Request $request, $id)
{
$request->validate([
'to' => 'nullable|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:cash_to_bank,adjust_cash',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$transaction = Transaction::findOrFail($id);
$newAmount = $request->amount ?? 0;
$newTransactionType = $request->transaction_type;
$type = 'transfer';
DB::beginTransaction();
try {
// Transaction type is the same
if ($transaction->transaction_type === $newTransactionType) {
if ($newTransactionType === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
// Adjust balance difference
$diff = $newAmount - $transaction->amount;
// Prevent negative balance
if ($toBank->balance + $diff < 0) {
return response()->json([
'message' => 'Cannot update: updated bank balance would be negative.'
], 400);
}
$toBank->increment('balance', $diff);
} elseif ($newTransactionType === 'adjust_cash') {
$type = $request->type;
}
}
// Transaction type changed
else {
// Reverse old transaction effect
if ($transaction->transaction_type === 'cash_to_bank' && $transaction->to_bank) {
$prevBank = PaymentType::find($transaction->to_bank);
if ($prevBank) {
if ($prevBank->balance < $transaction->amount) {
return response()->json([
'message' => 'Cannot update: insufficient balance in ' . $prevBank->name . ' to reverse previous transaction.'
], 400);
}
$prevBank->decrement('balance', $transaction->amount);
}
}
// Apply new transaction effect
if ($newTransactionType === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
$toBank->increment('balance', $newAmount);
$type = 'transfer';
} elseif ($newTransactionType === 'adjust_cash') {
$type = $request->type;
}
}
// Update transaction record
$transaction->update([
'type' => $type,
'transaction_type' => $newTransactionType,
'amount' => $newAmount,
'to_bank' => ($newTransactionType === 'cash_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : $transaction->image,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $transaction,
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function destroy(string $id)
{
$transaction = Transaction::findOrFail($id);
DB::beginTransaction();
try {
// Allow only "cash" platform transactions to be deleted
if ($transaction->platform !== 'cash') {
return response()->json([
'message' => 'Cannot delete here, please delete from ' . ucfirst($transaction->platform) . ' section.',
], 400);
}
$amount = $transaction->amount;
$toBank = $transaction->to_bank ? PaymentType::find($transaction->to_bank) : null;
// Reverse balance changes based on transaction type
switch ($transaction->transaction_type) {
case 'cash_to_bank':
if ($toBank) {
// Ensure bank has enough balance to reverse
if ($toBank->balance < $amount) {
return response()->json([
'message' => 'Cannot delete: bank balance would go negative.',
], 400);
}
$toBank->decrement('balance', $amount);
}
break;
case 'adjust_cash':
// Cash is static, so no bank adjustments needed
break;
}
if (file_exists($transaction->image)) {
Storage::delete($transaction->image);
}
$transaction->delete();
DB::commit();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
}

View File

@@ -0,0 +1,297 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Income;
use App\Models\Party;
use App\Models\PaymentType;
use App\Models\Sale;
use App\Models\Transaction;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class ChequeController extends Controller
{
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
$data = Transaction::with(['user:id,name','paymentType:id,name'])
->where('business_id', $business_id)
->whereIn('transaction_type', ['cheque_payment'])
->when($request->duration, function ($query) use ($request) {
$today = Carbon::today();
if ($request->duration === 'today') {
$query->whereDate('date', $today);
} elseif ($request->duration === 'yesterday') {
$query->whereDate('date', Carbon::yesterday());
} elseif ($request->duration === 'last_seven_days') {
$startDate = Carbon::now()->subDays(7)->format('Y-m-d');
$endDate = Carbon::now()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'last_thirty_days') {
$startDate = Carbon::now()->subDays(30)->format('Y-m-d');
$endDate = Carbon::now()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
} elseif ($request->duration === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
$query->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
}
})
->latest()
->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'transaction_id' => 'required|exists:transactions,id',
'date' => 'nullable|date',
'note' => 'nullable|string',
'payment_type' => [
'required',
function ($attribute, $value, $fail) {
if ($value !== 'cash' && !PaymentType::where('id', $value)->exists()) {
$fail(__('The selected payment type is invalid.'));
}
},
],
]);
$business_id = auth()->user()->business_id;
DB::beginTransaction();
try {
$transaction = Transaction::findOrFail($request->transaction_id);
$amount = $transaction->amount;
$platform = strtolower($transaction->platform);
$transaction->update([
'type' => 'deposit',
]);
$newTransactionData = [
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => 'credit',
'platform' => 'cheque',
'amount' => $amount,
'date' => $request->date ?? now(),
'note' => $request->note,
'meta' => [
'source_transaction_id' => $request->transaction_id ?? null,
],
];
// Cheque deposited into Cash
if ($request->payment_type === 'cash') {
$newTransactionData['transaction_type'] = 'cheque_to_cash';
$newTransactionData['payment_type_id'] = null;
} else {
// Cheque deposited into Bank
$toBank = PaymentType::findOrFail($request->payment_type);
$toBank->increment('balance', $amount);
$newTransactionData['transaction_type'] = 'cheque_to_bank';
$newTransactionData['payment_type_id'] = $toBank->id;
}
Transaction::create($newTransactionData);
// update related platform
if ($platform == 'sale') {
$sale = Sale::find($transaction->reference_id);
updateBalance($amount, 'increment');
$newPaid = $sale->paidAmount + $amount;
$expectedTotal = $sale->paidAmount + $sale->dueAmount;
$newChange = max($newPaid - $expectedTotal, 0);
$newDue = max($sale->dueAmount - $amount, 0);
$sale->update([
'paidAmount' => $newPaid,
'change_amount' => $sale->change_amount + $newChange,
'dueAmount' => $newDue,
'isPaid' => $newDue > 0 ? 0 : 1,
]);
// update party due
if ($sale->party_id) {
$party = Party::find($sale->party_id);
if ($party) {
$party->decrement('due', min($amount, $party->due));
}
}
} elseif ($transaction->platform == 'income') {
$income = Income::find($transaction->reference_id);
$income->increment('amount', $transaction->amount);
}
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $transaction
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage()
], 406);
}
}
public function show($id)
{
$business_id = auth()->user()->business_id;
$transaction = Transaction::with('user:id,name', 'branch:id,name')
->where('business_id', $business_id)
->where('id', $id)
->first();
if (!$transaction) {
return response()->json([
'message' => __('Transaction not found.'),
], 404);
}
return response()->json([
'message' => __('Transaction fetched successfully.'),
'data' => $transaction,
]);
}
public function reopen($TransactionId)
{
DB::beginTransaction();
try {
// original cheque payment
$originalTxn = Transaction::findOrFail($TransactionId);
// Must be cheque_payment
if ($originalTxn->transaction_type !== 'cheque_payment') {
return response()->json(['message' => __('This transaction cannot be reopened.')], 400);
}
$amount = $originalTxn->amount;
$business_id = auth()->user()->business_id;
$depositTxn = Transaction::where('platform', 'cheque')
->where('meta->source_transaction_id', $originalTxn->id)
->first();
if (!$depositTxn) {
return response()->json(['message' => __('Deposited cheque record not found.')], 400);
}
// Reverse bank balance only if cheque_to_bank
if ($depositTxn->transaction_type === 'cheque_to_bank' && $depositTxn->payment_type_id) {
$bank = PaymentType::find($depositTxn->payment_type_id);
if ($bank) {
$bank->decrement('balance', $amount);
}
}
$platform = strtolower($originalTxn->platform);
// Reverse Sale or Income
if ($platform == 'sale') {
$sale = Sale::find($originalTxn->reference_id);
if ($sale) {
// Reverse paid, change, and due amounts
$newPaid = $sale->paidAmount - $amount;
$newDue = $sale->dueAmount + $amount;
$newChange = max($sale->change_amount - max($sale->paidAmount - ($sale->paidAmount + $sale->dueAmount - $amount), 0), 0);
$sale->update([
'paidAmount' => max($newPaid, 0),
'change_amount' => max($newChange, 0),
'dueAmount' => $newDue,
'isPaid' => $newDue > 0 ? 0 : 1,
]);
// Reverse party due if exists
if ($sale->party_id) {
$party = Party::find($sale->party_id);
if ($party) {
$party->increment('due', $amount);
}
}
}
} elseif ($platform == 'income') {
$income = Income::find($originalTxn->reference_id);
if ($income) {
$income->decrement('amount', $amount);
}
}
$reverseType = $depositTxn->transaction_type === 'cheque_to_cash' ? 'cash_to_cheque' : 'bank_to_cheque';
// Create reverse debit transaction
Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => 'debit',
'platform' => 'cheque',
'amount' => $amount,
'date' => now(),
'note' => 'Cheque reopened',
'transaction_type' => $reverseType,
'payment_type_id' => $depositTxn->payment_type_id,
'meta' => [
'reverted_transaction_id' => $depositTxn->id
]
]);
$originalTxn->update([
'type' => 'pending',
]);
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $originalTxn
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => $e->getMessage()
], 406);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Party;
use App\Models\Business;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Http\Controllers\Controller;
use App\Services\PartyLedgerService;
use Illuminate\Support\Facades\Storage;
class DefaultLangController extends Controller
{
use HasUploader;
public function index()
{
$default_lang = get_option('general')['default_lang'] ?? '';
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $default_lang,
]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ExpenseCategory;
use Illuminate\Http\Request;
class ExpenseCategoryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = ExpenseCategory::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'categoryName' => 'required|unique:expense_categories,categoryName,NULL,id,business_id,' . auth()->user()->business_id,
]);
$data = ExpenseCategory::create($request->except('status') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status == 'true' ? 1 : 0,
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
$category = ExpenseCategory::findOrFail($id);
$request->validate([
'categoryName' => [
'required',
'unique:expense_categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$category->update($request->except('status') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status == 'true' ? 1 : 0,
]);
return response()->json([
'message' => __('Data saved successfully.'),
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
$category = ExpenseCategory::findOrFail($id);
$category->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,318 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Party;
use App\Models\Business;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Http\Controllers\Controller;
use App\Services\PartyLedgerService;
use Illuminate\Support\Facades\Storage;
class PartyController extends Controller
{
use HasUploader;
public function index()
{
$user = auth()->user();
$businessId = $user->business_id;
$activeBranch = $user->active_branch;
$parties = Party::where('business_id', $businessId)
->when(request('type'), function ($q) {
$q->where('type', request('type'));
})
->withCount([
'sales as sales_count' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
])
->withSum([
'sales as total_sale_amount' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'totalAmount')
->withSum([
'sales as total_sale_paid' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'paidAmount')
->withSum([
'sales as total_sale_profit' => function ($q) use ($activeBranch) {
$q->where('lossProfit', '>=', 0);
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'lossProfit')
->withSum([
'sales as total_sale_loss' => function ($q) use ($activeBranch) {
$q->where('lossProfit', '<', 0);
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'lossProfit')
->withCount([
'purchases as purchases_count' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
])
->withSum([
'purchases as total_purchase_amount' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'totalAmount')
->withSum([
'purchases as total_purchase_paid' => function ($q) use ($activeBranch) {
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$q->where('branch_id', $activeBranch->id);
}
}
], 'paidAmount')
->with([
'sales' => function ($sq) use ($activeBranch) {
$sq->select('id', 'party_id', 'branch_id');
if (moduleCheck('MultiBranchAddon') && $activeBranch) {
$sq->where('branch_id', $activeBranch->id);
}
$sq->with([
'details' => function ($dq) {
$dq->select(
'id',
'sale_id',
'product_id',
'quantities',
'productPurchasePrice',
'price',
'lossProfit'
)->with([
'product:id,productName'
]);
}
]);
}
])
->latest()
->get();
$accessToMultiBranch = auth()->user()->accessToMultiBranch();
foreach ($parties as $party) {
$branch_logic = $party->branch_id === ($activeBranch->id ?? false) || $accessToMultiBranch;
if ($activeBranch && moduleCheck('MultiBranchAddon')) {
if ($party->type === 'Supplier') {
if (!$branch_logic) {
$party->due = $party->purchases_dues
->where('branch_id', $activeBranch->id)
->sum('dueAmount');
}
} else {
if (!$branch_logic) {
$party->due = $party->sales_dues
->where('branch_id', $activeBranch->id)
->sum('dueAmount');
}
}
}
$party->wallet = $branch_logic ? $party->wallet : 0;
$party->opening_balance = $branch_logic ? $party->opening_balance : 0;
$party->opening_balance_type = $branch_logic ? $party->opening_balance_type : null;
if ($party->type === 'Supplier') {
$party->makeHidden([
'sales_count',
'total_sale_amount',
'total_sale_paid',
'total_sale_profit',
'total_sale_loss',
]);
} else {
$party->makeHidden([
'purchases_count',
'total_purchase_amount',
'total_purchase_paid',
]);
}
}
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $parties,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', $business_id),
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => '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',
]);
$data = Party::create($request->except('image', 'due', 'wallet', 'opening_balance', 'credit_limit') + [
'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' => $business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
public function show(Party $party)
{
if (env('MESSAGE_ENABLED')) {
if ($party->due) {
$business = Business::findOrFail($party->business_id);
$response = sendMessage($party->phone, dueMessage($party, $business->companyName));
if ($response->successful()) {
return response()->json([
'message' => __('Message has been send successfully.'),
]);
}
return response()->json([
'message' => __('Something went wrong, Please contact with admin.'),
], 406);
} else {
return response()->json([
'message' => __('This party has no due balance.'),
], 406);
}
} else {
return response()->json([
'message' => __('Message has been disabled by admin.'),
], 406);
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Party $party)
{
$request->validate([
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'phone' => 'nullable|max:20|unique:parties,phone,' . $party->id . ',id,business_id,' . auth()->user()->business_id,
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => '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',
]);
$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 → adjust by difference
if ($currentType == 'due') {
$due += ($currentOpening - $prevOpening);
} else {
$wallet += ($currentOpening - $prevOpening);
}
} else {
// Type changed → 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', '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,
]
);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $party,
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Party $party)
{
if (!$party->canBeDeleted()) {
return response()->json([
'message' => __('This party cannot be deleted.'),
], 400);
}
if (file_exists($party->image)) {
Storage::delete($party->image);
}
$party->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
public function partyLedger(Request $request, $partyId, PartyLedgerService $service)
{
$ledger = $service->list($request, $partyId);
return response()->json([
'data' => $ledger,
]);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class PaymentTypeController extends Controller
{
public function index()
{
$data = PaymentType::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => [
'required',
Rule::unique('payment_types')->where(function ($query) {
return $query->where('business_id', auth()->user()->business_id);
}),
],
]);
$data = PaymentType::create($request->all() + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
public function update(Request $request, string $id)
{
$payment_type = PaymentType::findOrFail($id);
$request->validate([
'name' => 'required|unique:payment_types,name,' . $payment_type->id . ',id,business_id,' . auth()->user()->business_id,
]);
$payment_type->update($request->all());
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $payment_type,
]);
}
public function destroy(string $id)
{
$payment_type = PaymentType::findOrFail($id);
$payment_type->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ProductModel;
use Illuminate\Http\Request;
class ProducModelController extends Controller
{
public function index()
{
$data = ProductModel::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|unique:product_models,name,NULL,id,business_id,' . auth()->user()->business_id,
]);
$data = ProductModel::create($request->all() + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
public function update(Request $request, string $id)
{
$product_model = ProductModel::find($id);
if (!$product_model) {
return response()->json([
'message' => __('Model not found.'),
'data' => null,
], 404);
}
$request->validate([
'name' => [
'required',
'unique:product_models,name,' . $product_model->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$product_model->update($request->all());
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $product_model,
]);
}
public function destroy(string $id)
{
$product_model = ProductModel::find($id);
if (!$product_model) {
return response()->json([
'message' => __('Model not found.'),
], 404);
}
$product_model->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ProductSetting;
use Illuminate\Http\Request;
class ProductSettingsController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = ProductSetting::where('business_id', auth()->user()->business_id)->first();
if ($data) {
$responseData = $data;
} else {
$responseData = [
'business_id' => auth()->user()->business_id,
'modules' => [
// Default fields = "1"
'show_product_type_single' => "1",
'show_product_category' => "1",
'show_alert_qty' => "1",
'show_product_unit' => "1",
'show_exclusive_price' => "1",
'show_inclusive_price' => "1",
'show_profit_percent' => "1",
'show_product_sale_price' => "1",
'show_product_price' => "1",
'show_product_stock' => "1",
'default_sale_price' => null,
'default_wholesale_price' => null,
'default_dealer_price' => null,
'default_batch_no' => null,
'expire_date_type' => null,
'mfg_date_type' => null,
'default_expired_date' => null,
'default_mfg_date' => null,
'show_product_code' => "0",
'show_product_brand' => "0",
'show_model_no' => "0",
'show_product_manufacturer' => "0",
'show_product_image' => "0",
'show_vat_id' => "0",
'show_vat_type' => "0",
'show_action' => "0",
'show_product_dealer_price' => "0",
'show_product_wholesale_price' => "0",
'show_batch_no' => "0",
'show_expire_date' => "0",
'show_mfg_date' => "0",
'show_product_type_variant' => "0",
'show_product_batch_no' => "0",
'show_product_expire_date' => "0",
'show_product_type_combo' => "0",
'show_warehouse' => "0",
'show_rack' => "0",
'show_shelf' => "0",
'show_guarantee' => "0",
'show_warranty' => "0",
'show_serial' => "0",
],
];
}
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $responseData,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'show_product_name' => 'nullable|integer',
'show_product_price' => 'nullable|integer',
'show_product_code' => 'nullable|integer',
'show_product_stock' => 'nullable|integer',
'show_product_sale_price' => 'nullable|integer',
'show_product_dealer_price' => 'nullable|integer',
'show_product_wholesale_price' => 'nullable|integer',
'show_product_unit' => 'nullable|integer',
'show_product_brand' => 'nullable|integer',
'show_product_category' => 'nullable|integer',
'show_product_manufacturer' => 'nullable|integer',
'show_product_image' => 'nullable|integer',
'show_expire_date' => 'nullable|integer',
'show_alert_qty' => 'nullable|integer',
'show_vat_id' => 'nullable|integer',
'show_vat_type' => 'nullable|integer',
'show_exclusive_price' => 'nullable|integer',
'show_inclusive_price' => 'nullable|integer',
'show_profit_percent' => 'nullable|integer',
'show_capacity' => 'nullable|integer',
'show_weight' => 'nullable|integer',
'show_color' => 'nullable|integer',
'show_type' => 'nullable|integer',
'show_size' => 'nullable|integer',
'show_batch_no' => 'nullable|integer',
'show_mfg_date' => 'nullable|integer',
'show_model_no' => 'nullable|integer',
'default_sale_price' => 'nullable|integer',
'default_wholesale_price' => 'nullable|integer',
'default_dealer_price' => 'nullable|integer',
'show_product_type_single' => 'nullable|integer',
'show_product_type_variant' => 'nullable|integer',
'show_product_type_combo' => 'nullable|integer',
'show_warehouse' => 'nullable|integer',
'show_action' => 'nullable|integer',
'show_rack' => 'nullable|integer',
'show_shelf' => 'nullable|integer',
'show_guarantee' => 'nullable|integer',
'show_warranty' => 'nullable|integer',
'show_serial' => 'nullable|integer',
'default_batch_no' => 'nullable|integer',
'default_expired_date' => 'nullable|integer',
'default_mfg_date' => 'nullable|integer',
'expire_date_type' => 'nullable|integer',
'mfg_date_type' => 'nullable|integer',
'show_product_batch_no' => 'nullable|integer',
'show_product_expire_date' => 'nullable|integer'
]);
ProductSetting::updateOrCreate(
['business_id' => auth()->user()->business_id],
['modules' => $request->all()]
);
return response()->json([
'message' => __('Data saved successfully.'),
]);
}
}

View File

@@ -0,0 +1,416 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\MultiPaymentProcessed;
use App\Events\PurchaseSms;
use App\Models\Party;
use App\Models\PaymentType;
use App\Models\Product;
use App\Models\Stock;
use App\Models\Purchase;
use App\Models\Transaction;
use App\Traits\DateFilterTrait;
use Illuminate\Http\Request;
use App\Models\PurchaseDetails;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Models\PurchaseReturn;
class PurchaseController extends Controller
{
Use DateFilterTrait;
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
// Build the query with all eager loads
$query = Purchase::with(['user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.product:id,productName,category_id,product_type,vat_id,vat_type,vat_amount', 'details.stock:id,batch_no,variant_name,warehouse_id', 'details.product.vat:id,name,rate', 'details.product.category:id,categoryName', 'purchaseReturns.details', 'vat:id,name,rate', 'branch:id,name,phone,address','transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name'])->where('business_id', $business_id);
// Filter returned sales (safer boolean check)
$query->when(request('returned-purchase') == "true", function ($query) {
$query->whereHas('purchaseReturns');
});
if ($request->filled('branch_id')) {
$query->where('branch_id', $request->branch_id);
}
// Apply date filter
if(request('duration')){
$this->applyDateFilter($query, request('duration'), 'purchaseDate', request('from_date'), request('to_date'));
}
// Search filter
if ($request->filled('search')) {
$query->where(function ($q) use ($request) {
$q->where('paymentType', 'like', "%{$request->search}%")
->orWhere('invoiceNumber', 'like', "%{$request->search}%")
->orWhereHas('party', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
})
->orWhereHas('payment_type', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
})
->orWhereHas('branch', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
});
});
}
// Finally fetch the data (preserves original structure)
$data = $query->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'total_amount' => $data->sum('totalAmount'),
'data' => $data,
]);
}
public function show($id)
{
$business_id = auth()->user()->business_id;
$purchase = Purchase::with(['user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.product:id,productName,category_id,product_type,vat_id,vat_type,vat_amount', 'details.stock:id,batch_no,variant_name,warehouse_id', 'details.product.vat:id,name,rate', 'details.product.category:id,categoryName', 'purchaseReturns.details', 'vat:id,name,rate', 'branch:id,name,phone,address','transactions:id,platform,transaction_type,amount,date,invoice_no,reference_id,payment_type_id,meta', 'transactions.paymentType:id,name'])
->where('business_id', $business_id)
->where('id', $id)
->first();
if (!$purchase) {
return response()->json([
'message' => __('Purchase not found.'),
], 404);
}
return response()->json([
'message' => __('Purchase fetched successfully.'),
'data' => $purchase,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'products' => 'required|array',
'products.*.product_id' => 'required|exists:products,id',
'party_id' => 'required|exists:parties,id'
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
// Party due update
if ($request->dueAmount > 0) {
$party = Party::findOrFail($request->party_id);
// Check party credit limit
$newTotalDue = $party->due + $request->dueAmount;
if ($party->credit_limit > 0 && $newTotalDue > $party->credit_limit) {
return response()->json([
'message' => __('Cannot create purchase. Party due will exceed credit limit!')
], 400);
}
$party->update([
'due' => $newTotalDue
]);
}
updateBalance($request->paidAmount, 'decrement');
$purchase = Purchase::create($request->all() + [
'user_id' => auth()->id(),
'business_id' => $business_id,
]);
$purchaseDetails = [];
foreach ($request->products as $key => $product_data) {
$batch_no = $product_data['batch_no'] ?? NULL;
$variantName = $product_data['variant_name'] ?? null;
$existingStock = Stock::where(['batch_no' => $batch_no, 'product_id' => $product_data['product_id']])
->when($variantName, fn($q) => $q->where('variant_name', $variantName))
->first();
// update or create stock
$stock = Stock::updateOrCreate(
[
'batch_no' => $batch_no,
'business_id' => $business_id,
'product_id' => $product_data['product_id'],
'variant_name' => $variantName
],
[
'product_id' => $product_data['product_id'],
'mfg_date' => $product_data['mfg_date'] ?? NULL,
'expire_date' => $product_data['expire_date'] ?? NULL,
'profit_percent' => $product_data['profit_percent'] ?? 0,
'productSalePrice' => $product_data['productSalePrice'] ?? 0,
'productDealerPrice' => $product_data['productDealerPrice'] ?? 0,
'productPurchasePrice' => $product_data['productPurchasePrice'] ?? 0,
'productWholeSalePrice' => $product_data['productWholeSalePrice'] ?? 0,
'productStock' => ($product_data['quantities'] ?? 0) + ($existingStock->productStock ?? 0),
'warehouse_id' => $product_data['warehouse_id'] ?? null,
]
);
$purchaseDetails[$key] = [
'stock_id' => $stock->id,
'purchase_id' => $purchase->id,
'product_id' => $product_data['product_id'],
'quantities' => $product_data['quantities'] ?? 0,
'productSalePrice' => $product_data['productSalePrice'] ?? 0,
'productDealerPrice' => $product_data['productDealerPrice'] ?? 0,
'productPurchasePrice' => $product_data['productPurchasePrice'] ?? 0,
'productWholeSalePrice' => $product_data['productWholeSalePrice'] ?? 0,
'profit_percent' => $product_data['profit_percent'] ?? 0,
'expire_date' => $product_data['expire_date'] ?? NULL,
'mfg_date' => $product_data['mfg_date'] ?? NULL,
];
}
PurchaseDetails::insert($purchaseDetails);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$request->payments ?? [],
$purchase->id,
'purchase',
$request->paidAmount ?? 0,
$request->party_id ?? null,
));
// Send SMS
event(new PurchaseSms($purchase));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $purchase->load('user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.stock:id,batch_no', 'details.product:id,productName,category_id,product_type', 'details.product.category:id,categoryName', 'purchaseReturns.details', 'vat:id,name,rate', 'payment_type:id,name', 'branch:id,name,phone,address'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Purchase $purchase)
{
$request->validate([
'products' => 'required|array',
'products.*.product_id' => 'required|exists:products,id',
'party_id' => 'required|exists:parties,id'
]);
DB::beginTransaction();
try {
$has_return = PurchaseReturn::where('purchase_id', $purchase->id)->count();
if ($has_return > 0) {
return response()->json([
'message' => __("You can not update this purchase because it has already been returned.")
], 400);
}
// Revert previous stock changes
foreach ($purchase->details as $detail) {
Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities);
}
// Delete existing purchase details
$purchase->details()->delete();
$business_id = auth()->user()->business_id;
$purchaseDetails = [];
foreach ($request->products as $key => $product_data) {
$batch_no = $product_data['batch_no'] ?? NULL;
$variantName = $cartItem->options['variant_name'] ?? null;
$existingStock = Stock::where(['batch_no' => $batch_no, 'product_id' => $product_data['product_id']])
->when($variantName, fn($q) => $q->where('variant_name', $variantName))
->first();
// update or create stock
$stock = Stock::updateOrCreate(
[
'batch_no' => $batch_no,
'business_id' => $business_id,
'product_id' => $product_data['product_id'],
'variant_name' => $variantName
],
[
'product_id' => $product_data['product_id'],
'mfg_date' => $product_data['mfg_date'] ?? NULL,
'expire_date' => $product_data['expire_date'] ?? NULL,
'profit_percent' => $product_data['profit_percent'] ?? 0,
'productSalePrice' => $product_data['productSalePrice'] ?? 0,
'productDealerPrice' => $product_data['productDealerPrice'] ?? 0,
'productPurchasePrice' => $product_data['productPurchasePrice'] ?? 0,
'productWholeSalePrice' => $product_data['productWholeSalePrice'] ?? 0,
'productStock' => ($product_data['quantities'] ?? 0) + ($existingStock->productStock ?? 0),
'warehouse_id' => $product_data['warehouse_id'] ?? null,
]
);
$purchaseDetails[$key] = [
'stock_id' => $stock->id,
'purchase_id' => $purchase->id,
'product_id' => $product_data['product_id'],
'quantities' => $product_data['quantities'] ?? 0,
'productSalePrice' => $product_data['productSalePrice'] ?? 0,
'productDealerPrice' => $product_data['productDealerPrice'] ?? 0,
'productPurchasePrice' => $product_data['productPurchasePrice'] ?? 0,
'productWholeSalePrice' => $product_data['productWholeSalePrice'] ?? 0,
'profit_percent' => $product_data['profit_percent'] ?? 0,
'expire_date' => $product_data['expire_date'] ?? NULL,
'mfg_date' => $product_data['mfg_date'] ?? NULL,
];
}
PurchaseDetails::insert($purchaseDetails);
if ($purchase->dueAmount || $request->dueAmount) {
$party = Party::findOrFail($request->party_id);
// Calculate new due for this party
$newDue = $request->party_id == $purchase->party_id ? (($party->due - $purchase->dueAmount) + $request->dueAmount) : ($party->due + $request->dueAmount);
// Check credit limit
if ($party->credit_limit > 0 && $newDue > $party->credit_limit) {
return response()->json([
'message' => __('Cannot update purchase. Party due will exceed credit limit!')
], 400);
}
$party->update([
'due' => $newDue
]);
// If changed to a new party, reduce previous partys due
if ($request->party_id != $purchase->party_id) {
$prev_party = Party::findOrFail($purchase->party_id);
$prev_party->update([
'due' => $prev_party->due - $purchase->dueAmount
]);
}
}
$balanceDiff = ($purchase->paidAmount ?? 0) - $request->paidAmount;
updateBalance($balanceDiff, 'decrement');
$purchase->update($request->all() + [
'user_id' => auth()->id(),
]);
// Multiple Payment Process
$oldTransactions = Transaction::where('business_id', $business_id)
->where('reference_id', $purchase->id)
->where('platform', 'purchase')
->get();
// Revert old transactions
foreach ($oldTransactions as $old) {
switch ($old->transaction_type) {
case 'bank_payment':
$paymentType = PaymentType::find($old->payment_type_id);
if ($paymentType) {
$paymentType->increment('balance', $old->amount);
}
break;
case 'wallet_payment':
if ($request->party_id) {
$party = Party::find($request->party_id);
if ($party) {
$party->decrement('wallet', $old->amount);
}
}
break;
case 'cash_payment':
case 'cheque_payment':
// nothing to revert
break;
}
}
// Delete old transactions
Transaction::where('business_id', $business_id)
->where('reference_id', $purchase->id)
->where('platform', 'purchase')
->delete();
event(new MultiPaymentProcessed(
$request->payments ?? [],
$purchase->id,
'purchase',
$request->paidAmount ?? 0,
$request->party_id ?? null,
));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $purchase->load('user:id,name,role', 'party:id,name,email,phone,type,address', 'details', 'details.stock:id,batch_no', 'details.product:id,productName,category_id,product_type', 'details.product.category:id,categoryName', 'purchaseReturns.details', 'vat:id,name,rate', 'payment_type:id,name'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
DB::beginTransaction();
try {
$purchase = Purchase::with('details')->findOrFail($id);
$has_return = PurchaseReturn::where('purchase_id', $purchase->id)->count();
if ($has_return > 0) {
return response()->json([
'message' => __("You can not update this purchase because it has already been returned.")
], 400);
}
if ($purchase->dueAmount) {
$party = Party::findOrFail($purchase->party_id);
$party->update([
'due' => $party->due - $purchase->dueAmount
]);
}
foreach ($purchase->details as $detail) {
Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities);
}
updateBalance($purchase->paidAmount, 'increment');
sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchase has been deleted.'), $purchase->business_id);
$purchase->delete();
DB::commit();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\MultiPaymentProcessed;
use App\Models\Party;
use App\Models\Stock;
use App\Models\Purchase;
use Illuminate\Http\Request;
use App\Models\PurchaseReturn;
use App\Models\PurchaseDetails;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Models\PurchaseReturnDetail;
class PurchaseReturnController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name', 'details')
->whereBetween('return_date', [request()->start_date, request()->end_date])
->where('business_id', auth()->user()->business_id)
->latest()
->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'purchase_id' => 'required|exists:purchases,id',
'return_qty' => 'required|array',
]);
DB::beginTransaction();
try {
$purchase = Purchase::with('details')
->where('business_id', auth()->user()->business_id)
->findOrFail($request->purchase_id);
// Calculate total discount factor
$total_discount = $purchase->discountAmount;
$total_purchase_amount = $purchase->details->sum(fn($detail) => $detail->productPurchasePrice * $detail->quantities);
$discount_per_unit_factor = $total_purchase_amount > 0 ? $total_discount / $total_purchase_amount : 0;
$purchase_return = PurchaseReturn::create([
'business_id' => auth()->user()->business_id,
'purchase_id' => $request->purchase_id,
'invoice_no' => $purchase->invoiceNumber,
'return_date' => now(),
]);
$purchase_return_detail_data = [];
$total_return_amount = 0;
$total_return_discount = 0;
// Loop through each purchase detail and process the return
foreach ($purchase->details as $key => $detail) {
$requested_qty = $request->return_qty[$key];
if ($requested_qty <= 0) {
continue;
}
// Check if return quantity exceeds the purchased quantity
if ($requested_qty > $detail->quantities) {
return response()->json([
'message' => "You can't return more than the ordered quantity of {$detail->quantities}.",
], 400);
}
// Calculate per-unit discount and return amounts
$unit_discount = $detail->productPurchasePrice * $discount_per_unit_factor;
$return_discount = $unit_discount * $requested_qty;
$return_amount = ($detail->productPurchasePrice - $unit_discount) * $requested_qty;
$total_return_amount += $return_amount;
$total_return_discount += $return_discount;
// Update stock & purchase details
Stock::where('id', $detail->stock_id)->decrement('productStock', $requested_qty);
$detail->quantities -= $requested_qty;
$detail->timestamps = false;
$detail->save();
// Collect return detail data
$purchase_return_detail_data[] = [
'purchase_detail_id' => $detail->id,
'purchase_return_id' => $purchase_return->id,
'return_qty' => $requested_qty,
'business_id' => auth()->user()->business_id,
'return_amount' => $return_amount,
];
}
// Insert purchase return details
if (!empty($purchase_return_detail_data)) {
PurchaseReturnDetail::insert($purchase_return_detail_data);
}
if ($total_return_amount <= 0) {
return response()->json("You cannot return an empty product.", 400);
}
// Update party dues (if applicable)
$party = Party::find($purchase->party_id);
if ($party) {
$refund_amount = $total_return_amount;
// If party has due, reduce it first
if ($party->due > 0) {
if ($party->due >= $refund_amount) {
$party->decrement('due', $refund_amount);
$refund_amount = 0;
} else {
$refund_amount -= $party->due;
$party->update(['due' => 0]);
}
}
// Any remaining amount should be deducted from wallet
if ($refund_amount > 0 && $party->wallet > 0) {
$deduct = min($party->wallet, $refund_amount);
$party->decrement('wallet', $deduct);
}
}
// Calculate remaining return amount
$remaining_return_amount = max(0, $total_return_amount - $purchase->dueAmount);
$new_total_amount = max(0, $purchase->totalAmount - $total_return_amount);
// Update purchase record
$purchase->update([
'change_amount' => 0,
'dueAmount' => max(0, $purchase->dueAmount - $total_return_amount),
'paidAmount' => max(0, $purchase->paidAmount - min($purchase->paidAmount, $total_return_amount)),
'totalAmount' => $new_total_amount,
'discountAmount' => max(0, $purchase->discountAmount - $total_return_discount),
'isPaid' => $remaining_return_amount > 0 ? 1 : $purchase->isPaid,
]);
$payments = $request->payments ?? [];
$payments = collect($payments)->map(function ($payment) use ($total_return_amount) {
$payment['amount'] = $total_return_amount;
return $payment;
})->toArray();
event(new MultiPaymentProcessed(
$payments,
$purchase_return->id,
'purchase_return',
$total_return_amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $purchase_return,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['error' => 'Transaction failed: ' . $e->getMessage()], 500);
}
}
public function show($id)
{
$data = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name', 'details')->findOrFail($id);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Rack;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class RackController extends Controller
{
public function index()
{
$data = Rack::with('shelves:id,name')->where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'shelf_id' => 'required|array',
'shelf_id.*' => 'exists:shelves,id',
'name' => 'required|string|max:255',
'status' => 'required|in:0,1',
]);
$rack = Rack::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
$rack->shelves()->sync($request->shelf_id);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $rack,
]);
}
public function update(Request $request, string $id)
{
$rack = Rack::find($id);
if (!$rack) {
return response()->json([
'message' => __('Rack not found.'),
'data' => null,
], 404);
}
$request->validate([
'shelf_id' => 'required|array',
'shelf_id.*' => 'exists:shelves,id',
'name' => 'required|string|max:255',
'status' => 'required|in:0,1',
]);
$rack->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
$rack->shelves()->sync($request->shelf_id);
return response()->json([
'message' => __('Data updated successfully.'),
'data' => $rack,
]);
}
public function destroy(string $id)
{
$rack = Rack::find($id);
if (!$rack) {
return response()->json([
'message' => __('Rack not found.'),
], 404);
}
$rack->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,255 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\MultiPaymentProcessed;
use App\Models\Sale;
use App\Models\Party;
use App\Models\Stock;
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
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = SaleReturn::with('sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details')
->whereBetween('return_date', [request()->start_date, request()->end_date])
->where('business_id', auth()->user()->business_id)
->latest()
->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'sale_id' => 'required|exists:sales,id',
'return_qty' => 'required|array',
]);
$business_id = auth()->user()->business_id;
DB::beginTransaction();
try {
$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);
// 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;
$sale_return = SaleReturn::create([
'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_loss_profit_adjustment = 0;
foreach ($sale->details as $key => $detail) {
$requested_qty = $request->return_qty[$key];
if ($requested_qty <= 0) {
continue;
}
if ($requested_qty > $detail->quantities) {
return response()->json([
'message' => "You can't return more than ordered quantity of {$detail->quantities}.",
], 400);
}
$product = $detail->product;
// Include SaleDetails discount in return calculation
$unit_discount = $detail->price * $discount_per_unit_factor;
$item_cart_discount = $detail->discount ?? 0;
$total_discount_per_unit = $unit_discount + $item_cart_discount;
$return_discount = $total_discount_per_unit * $requested_qty;
$return_amount = ($detail->price - $total_discount_per_unit + $rounding_amount_per_unit) * $requested_qty;
$total_return_amount += $return_amount;
$total_return_discount += $return_discount;
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;
}
else {
$stock = Stock::where('id', $detail->stock_id)->first();
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;
}
// Update sale details
$detail->quantities -= $requested_qty;
$detail->lossProfit -= $loss_profit_adjustment;
$detail->timestamps = false;
$detail->save();
$sale_return_detail_data[] = [
'business_id' => $business_id,
'sale_detail_id' => $detail->id,
'sale_return_id' => $sale_return->id,
'return_qty' => $requested_qty,
'return_amount' => $return_amount,
];
}
if (!empty($sale_return_detail_data)) {
SaleReturnDetails::insert($sale_return_detail_data);
}
if ($total_return_amount <= 0) {
return response()->json("You cannot return an empty product.", 400);
}
$remaining_refund = $total_return_amount;
// Adjust Due
$new_due = $sale->dueAmount;
if ($remaining_refund > 0) {
if ($remaining_refund >= $sale->dueAmount) {
$remaining_refund -= $sale->dueAmount;
$new_due = 0;
} else {
$new_due = $sale->dueAmount - $remaining_refund;
$remaining_refund = 0;
}
}
// Adjust Paid (refund part)
$new_paid = $sale->paidAmount;
if ($remaining_refund > 0) {
if ($remaining_refund >= $sale->paidAmount) {
$remaining_refund -= $sale->paidAmount;
$new_paid = 0;
} else {
$new_paid = $sale->paidAmount - $remaining_refund;
$remaining_refund = 0;
}
}
// 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,
'discountAmount' => max(0, $sale->discountAmount - $total_return_discount),
'lossProfit' => $sale->lossProfit - $total_loss_profit_adjustment,
]);
// Party Refund Logic
$party = Party::find($sale->party_id);
if ($party) {
// use leftover refund after due/paid adjustments
$refund_amount = $remaining_refund;
// Reduce party due
if ($party->due > 0) {
if ($party->due >= $refund_amount) {
$party->decrement('due', $refund_amount);
$refund_amount = 0;
} else {
$refund_amount -= $party->due;
$party->update(['due' => 0]);
}
}
// Add leftover refund to wallet
if ($refund_amount > 0) {
$party->increment('wallet', $refund_amount);
}
}
$payments = $request->payments ?? [];
$payments = collect($payments)->map(function ($payment) use ($total_return_amount) {
$payment['amount'] = $total_return_amount;
return $payment;
})->toArray();
event(new MultiPaymentProcessed(
$payments,
$sale_return->id,
'sale_return',
$total_return_amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $sale_return,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['error' => 'Transaction failed: ' . $e->getMessage()], 500);
}
}
public function show($id)
{
$data = SaleReturn::with('sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details')->findOrFail($id);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Shelf;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ShelfController extends Controller
{
public function index()
{
$data = Shelf::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'status' => 'required|in:0,1',
]);
$shelf = Shelf::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $shelf,
]);
}
public function update(Request $request, string $id)
{
$shelf = Shelf::find($id);
if (!$shelf) {
return response()->json([
'message' => __('Shelf not found.'),
'data' => null,
], 404);
}
$request->validate([
'name' => 'required|string|max:255',
'status' => 'required|in:0,1',
]);
$shelf->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data updated successfully.'),
'data' => $shelf,
]);
}
public function destroy(string $id)
{
$shelf = Shelf::find($id);
if (!$shelf) {
return response()->json([
'message' => __('Shelf not found.'),
], 404);
}
$shelf->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,238 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use App\Models\Sale;
use App\Models\Income;
use App\Models\Expense;
use App\Models\Product;
use App\Models\Category;
use App\Models\Purchase;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class StatisticsController extends Controller
{
public function summary()
{
$business_id = auth()->user()->business_id;
$date = request('date') ?? today();
$sale_profit = Sale::where('business_id', $business_id)->whereDate('created_at', $date)->where('lossProfit', '>', 0)->sum('lossProfit');
$data = [
'sales' => (float)Sale::where('business_id', $business_id)->whereDate('created_at', $date)->sum('totalAmount'),
'income' => (float)Income::where('business_id', $business_id)->whereDate('created_at', $date)->sum('amount') + $sale_profit,
'expense' => (float)Expense::where('business_id', $business_id)->whereDate('created_at', $date)->sum('amount'),
'purchase' => (float)Purchase::where('business_id', $business_id)->whereDate('created_at', $date)->sum('totalAmount'),
];
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function dashboard()
{
$currentDate = Carbon::now();
$business_id = auth()->user()->business_id;
$duration = request('duration');
// Set date range, format, and period based on selected duration
switch ($duration) {
case 'today':
$start = $currentDate->copy()->startOfDay();
$end = $currentDate->copy()->endOfDay();
$format = 'H';
$period = $start->hoursUntil($end);
break;
case 'yesterday':
$start = $currentDate->copy()->subDay()->startOfDay();
$end = $currentDate->copy()->subDay()->endOfDay();
$format = 'H';
$period = $start->hoursUntil($end);
break;
case 'last_seven_days':
$start = $currentDate->copy()->subDays(6)->startOfDay();
$end = $currentDate->copy()->endOfDay();
$format = 'd';
$period = $start->daysUntil($end);
break;
case 'last_thirty_days':
$start = $currentDate->copy()->subDays(29)->startOfDay();
$end = $currentDate->copy()->endOfDay();
$format = 'd';
$period = $start->daysUntil($end);
break;
case 'current_month':
$start = $currentDate->copy()->startOfMonth();
$end = $currentDate->copy()->endOfMonth();
$format = 'd';
$period = $start->daysUntil($end);
break;
case 'last_month':
$start = $currentDate->copy()->subMonthNoOverflow()->startOfMonth();
$end = $currentDate->copy()->subMonthNoOverflow()->endOfMonth();
$format = 'd';
$period = $start->daysUntil($end);
break;
case 'current_year':
$start = $currentDate->copy()->startOfYear();
$end = $currentDate->copy()->endOfYear();
$format = 'M';
$period = $start->monthsUntil($end);
break;
case 'custom_date':
if (request()->has('from_date') && request()->has('to_date')) {
$start = Carbon::parse(request('from_date'))->startOfDay();
$end = Carbon::parse(request('to_date'))->endOfDay();
$format = 'd';
$period = $start->daysUntil($end);
} else {
return response()->json(['error' => 'From and To dates are required for custom date.'], 400);
}
break;
default:
return response()->json(['error' => 'Invalid duration'], 400);
}
// SQL date format for grouping
$dateFormatSQL = match ($format) {
'H' => '%H',
'd' => '%Y-%m-%d',
'M' => '%Y-%m',
default => '%Y-%m-%d',
};
// Sales data fetch and map
$sales_data = Sale::selectRaw("DATE_FORMAT(created_at, '$dateFormatSQL') as date, SUM(totalAmount) as amount")
->where('business_id', $business_id)
->whereBetween('created_at', [$start, $end])
->groupBy('date')
->orderBy('date')
->get()
->map(function ($item) {
$item->amount = (float) $item->amount;
return $item;
})
->keyBy('date');
// Purchase data fetch and map
$purchase_data = Purchase::selectRaw("DATE_FORMAT(created_at, '$dateFormatSQL') as date, SUM(totalAmount) as amount")
->where('business_id', $business_id)
->whereBetween('created_at', [$start, $end])
->groupBy('date')
->orderBy('date')
->get()
->map(function ($item) {
$item->amount = (float) $item->amount;
return $item;
})
->keyBy('date');
$income_amount = Income::where('business_id', $business_id)
->whereBetween('created_at', [$start, $end])
->sum('amount');
$sale_profit = Sale::where('business_id', $business_id)
->whereBetween('created_at', [$start, $end])
->where('lossProfit', '>', 0)
->sum('lossProfit');
$expense_amount = Expense::where('business_id', $business_id)
->whereBetween('created_at', [$start, $end])
->sum('amount');
$productQuery = Product::select('id', 'business_id', 'productName', 'product_type', 'created_at')
->with(['stocks:id,business_id,product_id,productStock,productPurchasePrice', 'combo_products.stock'])
->where('business_id', $business_id);
$total_stock_value = 0;
$products = $productQuery->get()
->map(function ($item) {
$item->source = 'product';
return $item;
});
foreach ($products as $product) {
// SINGLE / VARIANT
if (in_array($product->product_type, ['single', 'variant'])) {
foreach ($product->stocks as $stock) {
$total_stock_value += $stock->productStock * $stock->productPurchasePrice;
}
}
// COMBO
if ($product->product_type === 'combo') {
foreach ($product->combo_products as $combo) {
$childStock = $combo->stock;
if ($childStock) {
$total_stock_value += ($childStock->productStock / $combo->quantity) * $combo->purchase_price;
}
}
}
}
$data = [
'total_expense' => (float) $expense_amount,
'total_income' => (float) $income_amount + $sale_profit,
'total_items' => Product::where('business_id', $business_id)->count(),
'total_categories' => Category::where('business_id', $business_id)->count(),
'stock_value' => $total_stock_value,
'total_due' => (float) Sale::where('business_id', $business_id)->whereBetween('saleDate', [$start, $end])->sum('dueAmount'),
'total_profit' => (float) Sale::where('business_id', $business_id)->whereBetween('created_at', [$start, $end])->where('lossProfit', '>', 0)->sum('lossProfit'),
'total_loss' => (float) Sale::where('business_id', $business_id)->whereBetween('created_at', [$start, $end])->where('lossProfit', '<', 0)->sum('lossProfit'),
'sales' => $this->formatData($period, $sales_data, $format),
'purchases' => $this->formatData($period, $purchase_data, $format),
];
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
private function formatData($period, $datas, $format)
{
$rows = [];
foreach ($period as $date) {
$key = $date->format($format);
if ($format === 'M') {
// Sum amounts for the month
$dateKey = $date->format('Y-m');
$amount = $datas->filter(fn($value) => strpos($value->date, $dateKey) === 0)->sum('amount');
} elseif ($format === 'd') {
// Get amount by full date
$fullDateKey = $date->format('Y-m-d');
$amount = $datas->get($fullDateKey)?->amount ?? 0;
} elseif ($format === 'H') {
// Get amount by hour
$amount = $datas->get($key)?->amount ?? 0;
} else {
// Default: treat as full date
$fullDateKey = $date->format('Y-m-d');
$amount = $datas->get($fullDateKey)?->amount ?? 0;
}
$rows[] = [
'date' => $key,
'amount' => $amount,
];
}
return $rows;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Stock;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class StockController extends Controller
{
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'productStock' => 'required|integer',
'stock_id' => 'required|exists:stocks,id'
]);
Stock::where('id', $request->stock_id)->increment('productStock', $request->productStock);
return response()->json([
'message' => __('Data saved successfully.'),
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
Stock::where('id', $id)->update($request->except('_method'));
return response()->json([
'message' => __('Data saved successfully.'),
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
Stock::where('id', $id)->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Transaction;
use App\Traits\DateFilterTrait;
use Illuminate\Http\Request;
class TransactionController extends Controller
{
use DateFilterTrait;
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$transactionsQuery = Transaction::with([
'paymentType',
'sale.party',
'purchase.party',
'dueCollect.party'
])
->where('business_id', $businessId);
if (request('duration')) {
$this->applyDateFilter($transactionsQuery, request('duration'), 'date', request('from_date'), request('to_date'));
}
// Platform filter
if (request('platform')) {
$transactionsQuery->where('platform', $request->platform);
}
// Party filter
if (request('party_id')) {
$transactionsQuery->where(function ($q) use ($request) {
$q->where(function ($saleQ) use ($request) {
$saleQ->where('platform', 'sale')
->whereHas('sale', fn($s) => $s->where('party_id', $request->party_id)
);
});
$q->orWhere(function ($purQ) use ($request) {
$purQ->where('platform', 'purchase')
->whereHas('purchase', fn($p) => $p->where('party_id', $request->party_id)
);
});
$q->orWhere(function ($dueQ) use ($request) {
$dueQ->where('platform', 'due_collect')
->whereHas('dueCollect', fn($d) => $d->where('party_id', $request->party_id)
);
});
});
}
// Summary
$total_tr_amount = (clone $transactionsQuery)->sum('amount');
$total_tr_money_in = (clone $transactionsQuery)
->where('type', 'credit')
->sum('amount');
$total_tr_money_out = (clone $transactionsQuery)
->where('type', 'debit')
->sum('amount');
// No pagination
$transactions = $transactionsQuery->latest()->get();
$transactions->transform(function ($transaction) {
$transaction->total_amount = match($transaction->platform) {
'sale' => $transaction->sale?->totalAmount ?? 0,
'purchase' => $transaction->purchase?->totalAmount ?? 0,
'due_collect' => $transaction->dueCollect?->totalDue ?? 0,
'sale_return' => $transaction->saleReturn?->sale?->totalAmount ?? 0,
'purchase_return' => $transaction->purchaseReturn?->purchase?->totalAmount ?? 0,
default => 0
};
return $transaction;
});
return response()->json([
'message' => __('Data fetched successfully.'),
'total_amount' => $total_tr_amount,
'money_in' => $total_tr_money_in,
'money_out' => $total_tr_money_out,
'data' => $transactions,
]);
}
public function moneyReceipt($id)
{
$transaction = Transaction::with([
'paymentType',
'sale.party',
'purchase.party',
'dueCollect.party'
])->find($id);
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $transaction,
]);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Unit;
use Illuminate\Http\Request;
class UnitController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$data = Unit::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'unitName' => 'required|unique:units,unitName,NULL,id,business_id,' . auth()->user()->business_id,
]);
$data = Unit::create($request->all() + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Unit $unit)
{
$request->validate([
'unitName' => [
'required',
'unique:units,unitName,' . $unit->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$unit = $unit->update($request->all());
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $unit,
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Unit $unit)
{
$unit->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Variation;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class VariationController extends Controller
{
public function index()
{
$data = Variation::where('business_id', auth()->user()->business_id)->latest()->get();
return response()->json([
'message' => __('Data fetched successfully.'),
'data' => $data,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'values' => 'required|string',
'status' => 'required|in:0,1',
]);
$valuesArray = array_map('trim', explode(',', $request->values));
$data = Variation::create([
'name' => $request->name,
'status' => $request->status,
'values' => $valuesArray,
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Data saved successfully.'),
'data' => $data,
]);
}
public function update(Request $request, string $id)
{
$variation = Variation::find($id);
if (!$variation) {
return response()->json([
'message' => __('Variation not found.'),
'data' => null,
], 404);
}
$request->validate([
'name' => 'required|string|max:255',
'values' => 'required|string',
'status' => 'required|in:0,1',
]);
$valuesArray = array_map('trim', explode(',', $request->values));
$variation->update([
'name' => $request->name,
'status' => $request->status,
'values' => $valuesArray,
]);
return response()->json([
'message' => __('Data updated successfully.'),
'data' => $variation,
]);
}
public function destroy(string $id)
{
$variation = Variation::find($id);
if (!$variation) {
return response()->json([
'message' => __('Variation not found.'),
], 404);
}
$variation->delete();
return response()->json([
'message' => __('Data deleted successfully.'),
]);
}
}