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

27
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
$schedule->command('demo:restore-public-images')->everyThirtyMinutes();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\DueCollect;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class DuePaymentReceived
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $due_collect;
/**
* Create a new event instance.
*/
public function __construct(DueCollect $due_collect)
{
$this->due_collect = $due_collect;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MultiPaymentProcessed
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $payments;
public $business_id;
public $reference_id;
public $platform;
public $receiveAmount;
public $party_id;
/**
* Create a new event instance.
*/
public function __construct($payments, $reference_id, $platform, $receiveAmount = 0, $party_id = null)
{
$this->payments = $payments;
$this->reference_id = $reference_id;
$this->platform = $platform;
$this->receiveAmount = $receiveAmount;
$this->party_id = $party_id;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\Purchase;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PurchaseSms
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $purchase;
/**
* Create a new event instance.
*/
public function __construct(Purchase $purchase)
{
$this->purchase = $purchase;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

39
app/Events/SaleSms.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\Sale;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class SaleSms
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $sale;
/**
* Create a new event instance.
*/
public function __construct(Sale $sale)
{
$this->sale = $sale;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
trait HasUploader
{
private function upload(Request $request, $input, $oldFile = null, $disk = null)
{
$file = $request->file($input);
$ext = $file->getClientOriginalExtension();
$filename = now()->timestamp . '-' . rand(1, 1000) . '.' . $ext;
$path = 'uploads/' . date('y') . '/' . date('m') . '/';
$filePath = $path . $filename;
if ($oldFile) {
if (file_exists($oldFile)) {
Storage::delete($oldFile);
}
}
Storage::disk($disk ?? config('filesystems.default'))->put($filePath, file_get_contents($file));
return $filePath;
}
private function uploadWithFileName(Request $request, $input, $oldFile = null, $disk = null)
{
$file = $request->file($input);
$filename = $file->getClientOriginalName();
$path = 'files/';
$filePath = $path . $filename;
if ($oldFile) {
if (file_exists($oldFile)) {
Storage::delete($oldFile);
}
}
Storage::disk($disk ?? config('filesystems.default'))->put($filePath, file_get_contents($file));
return $filePath;
}
private function multipleUpload(Request $request, $input, $oldFiles = [], $disk = null)
{
$uploadedFiles = [];
foreach ($request->file($input) as $file) {
$ext = $file->getClientOriginalExtension();
$filename = now()->timestamp . '_' . uniqid() . '.' . $ext;
$path = 'uploads/' . date('y') . '/' . date('m') . '/';
$filePath = $path . $filename;
foreach ($oldFiles as $oldFile) {
if (file_exists($oldFile)) {
Storage::delete($oldFile);
}
}
Storage::disk($disk ?? config('filesystems.default'))->put($filePath, file_get_contents($file));
$uploadedFiles[] = $filePath;
}
return $uploadedFiles;
}
}

793
app/Helpers/Helper.php Normal file
View File

@@ -0,0 +1,793 @@
<?php
use App\Models\User;
use App\Models\Branch;
use App\Models\Option;
use App\Models\Business;
use App\Models\Currency;
use App\Models\Transaction;
use App\Models\UserCurrency;
use Kreait\Firebase\Factory;
use App\Models\PlanSubscribe;
use App\Models\ProductSetting;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Nwidart\Modules\Facades\Module;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use App\Notifications\SendNotification;
use Illuminate\Support\Facades\Artisan;
use Modules\MarketingAddon\App\Models\Sim;
use Kreait\Firebase\Messaging\CloudMessage;
use Illuminate\Support\Facades\Notification;
use Modules\MarketingAddon\App\Models\Device;
use Modules\MarketingAddon\App\Models\Smsgateway;
function cache_remember(string $key, callable $callback, int $ttl = 5000): mixed
{
return cache()->remember($key, env('CACHE_LIFETIME', $ttl), $callback);
}
function get_option($key)
{
return cache_remember($key, function () use ($key) {
return Option::where('key', $key)->first()->value ?? [];
});
}
function invoice_setting()
{
return get_option('invoice_setting_' . auth()->user()->business_id);
}
function invoice_language()
{
return get_option('invoice_language_' . auth()->user()->business_id);
}
function product_setting()
{
$businessId = auth()->user()->business_id;
$cacheKey = 'product_setting_' . $businessId;
return cache()->remember($cacheKey, 60, function () use ($businessId) {
$productSetting = ProductSetting::where('business_id', $businessId)->first();
if ($productSetting) {
$productSetting->modules = $productSetting->modules ?? [];
}
return $productSetting;
});
}
function is_module_enabled(?array $modules, string $key): bool
{
// Keys that should default to true if not set
$defaultTrueKeys = [
'show_product_category',
'show_product_stock',
'show_exclusive_price',
'show_inclusive_price',
'show_profit_percent',
'show_product_sale_price',
'show_product_wholesale_price',
'show_product_dealer_price',
'show_action',
];
if (in_array($key, $defaultTrueKeys)) {
return !isset($modules[$key]) || (bool) $modules[$key];
}
// All other keys: show only if explicitly set to true
return isset($modules[$key]) && (bool) $modules[$key];
}
function formatted_date(?string $date = null, string $format = 'd M, Y'): ?string
{
return !empty($date) ? Date::parse($date)->format($format) : null;
}
function formatted_time(?string $time = null, string $format = 'h:i A'): ?string
{
return !empty($time) ? Date::parse($time)->format($format) : null;
}
function sendNotification($id, $url, $message, $user = null)
{
$notify = [
'id' => $id,
'url' => $url,
'user' => $user,
'message' => $message,
];
$notify_user = User::where('role', 'superadmin')->first();
Notification::send($notify_user, new SendNotification($notify));
}
function sendNotifyToUser($id, $url, $message, $user)
{
$notify = [
'id' => $id,
'url' => $url,
'user' => $user,
'message' => $message,
];
$notify_user = User::where('business_id', $user)->first();
Notification::send($notify_user, new SendNotification($notify));
}
function currency_format($amount, $type = "icon", $decimals = 2, $currency = null, $abbreviate = false, $apply_rounding = false)
{
$currency = $currency ?? default_currency();
if ($apply_rounding) {
$amount = sale_rounding((float) $amount);
}
if ($abbreviate) {
$amount = format_number($amount, $decimals);
} else {
$has_fraction = $amount != floor($amount);
$amount = $has_fraction ? number_format($amount, $decimals) : number_format($amount, 0);
}
if ($type == "icon" || $type == "symbol") {
if ($currency->position == "right") {
return $amount . $currency->symbol;
} else {
return $currency->symbol . $amount;
}
} else {
if ($currency->position == "right") {
return $amount . ' ' . $currency->code;
} else {
return $currency->code . ' ' . $amount;
}
}
}
function format_number(float|int $number, int $decimals = 2): string
{
if ($number >= 1e9) {
return remove_trailing_zeros($number / 1e9, $decimals) . "B";
} elseif ($number >= 1e6) {
return remove_trailing_zeros($number / 1e6, $decimals) . "M";
} elseif ($number >= 1e3) {
return remove_trailing_zeros($number / 1e3, $decimals) . "K";
} else {
return remove_trailing_zeros($number, $decimals);
}
}
function remove_trailing_zeros(float|int $number, int $decimals = 2): string
{
return rtrim(rtrim(number_format($number, $decimals, '.', ''), '0'), '.');
}
function amountInWords(float $amount, int $decimals = 2): string
{
if (!extension_loaded('intl')) {
return '';
}
$has_fraction = fmod($amount, 1) != 0;
$amount = $has_fraction ? round($amount, $decimals) : round($amount);
$formatter = new \NumberFormatter('en_US', \NumberFormatter::SPELLOUT);
$words = $formatter->format($amount);
return $words . ' ' . (business_currency()->name ?? '');
}
function convert_money($amount, $currency)
{
if ($currency->code == default_currency('code') || $amount == 0) {
return round($amount, 2);
} else {
return $amount * $currency->rate / default_currency()->rate;
}
}
function default_currency($key = null, ?Currency $currency = null): object|int|string
{
$currency = $currency ?? cache_remember('default_currency', function () {
$currency = Currency::whereIsDefault(1)->first();
if (!$currency) {
$currency = (object) ['name' => 'US Dollar', 'code' => 'USD', 'rate' => 1, 'symbol' => '$', 'position' => 'left', 'status' => true, 'is_default' => true,];
}
return $currency;
});
return $key ? $currency->$key : $currency;
}
function paymentReminderMessage($sale, $business, $customer_name = 'Customer')
{
$message = get_option('sms-templates-' . $business->id)['purchase_sms'] ?? "Hello [customer_name], kindly clear your outstanding balance of [amount]. For details, contact us at [business_phone].";
$data = [
'customer_name' => $customer_name,
'invoice_no' => $sale->invoiceNumber,
'due_amount' => $sale->totalAmount,
'date' => formatted_date($sale->saleDate),
'business_name' => $business->companyName,
'business_phone' => $business->phoneNumber,
];
foreach ($data as $key => $value) {
$message = str_replace("[$key]", $value, $message);
}
return $message;
}
function sendMessage($numbers, $contents, $business_id = null)
{
$gateways = Smsgateway::where('business_id', $business_id ?? auth()->user()->business_id)->where('status', 1)->get();
foreach ($gateways as $gateway) {
$gateway->namespace::send_message($gateway, $numbers, $contents);
session()->put('gateway_id', $gateway->id);
return [
'status' => true,
'message' => "Message send successfully."
];
}
}
function sendPushNotify($request, int|string $total_numbers, ?int $device_id = null): bool
{
$credentialPath = public_path('uploads/service-account-credentials.json');
if (!file_exists($credentialPath))
return false;
$firebase = (new Factory)->withServiceAccount($credentialPath)->createMessaging();
$tokens = [];
if ($device_id) {
$tokens = Device::whereNotNull('device_token')
->where('business_id', auth()->user()->business_id)
->where('id', $device_id)
->pluck('device_token');
} else {
$device_ids = Sim::whereIn('id', $request->sim_ids)->pluck('device_id')->unique();
$tokens = Device::whereNotNull('device_token')->whereIn('id', $device_ids)->pluck('device_token');
}
foreach ($tokens as $token) {
$message = CloudMessage::fromArray([
'notification' => [
'title' => 'New ' . $request->type . ' has been created.',
'body' => $total_numbers . ' ' . $request->type . ' has been created.',
],
'data' => ['type' => $request->type],
'token' => $token,
]);
try {
$firebase->send($message);
} catch (\Kreait\Firebase\Exception\MessagingException $e) {
Log::error('Firebase Notification Error', ['error' => $e->getMessage()]);
return false;
}
}
return true;
}
function restorePublicImages()
{
if (!env('DEMO_MODE')) {
return true;
}
DB::table('sales')->where('business_id', 1)->delete();
DB::table('sale_returns')->where('business_id', 1)->delete();
DB::table('purchases')->where('business_id', 1)->delete();
DB::table('purchase_returns')->where('business_id', 1)->delete();
DB::table('due_collects')->where('business_id', 1)->delete();
DB::table('parties')->where('business_id', 1)->delete();
DB::table('expense_categories')->where('business_id', 1)->delete();
DB::table('income_categories')->where('business_id', 1)->delete();
Artisan::call('db:seed', ['--class' => 'DemoSeeder']);
}
if (!function_exists('formatTimeToWords')) {
function formatTimeToWords(string|null $time): string
{
if (empty($time)) {
return '0';
}
if (!preg_match('/^\d{1,2}:\d{2}(:\d{2})?$/', $time)) {
return '0';
}
$parts = explode(':', $time);
$hours = isset($parts[0]) ? (int) $parts[0] : 0;
$minutes = isset($parts[1]) ? (int) $parts[1] : 0;
$hourString = $hours == 1 ? 'hour' : 'hours';
$minuteString = $minutes == 1 ? 'minute' : 'minutes';
$formattedTime = [];
if ($hours > 0) {
$formattedTime[] = "$hours $hourString";
}
if ($minutes > 0) {
$formattedTime[] = "$minutes $minuteString";
}
return empty($formattedTime) ? '0' : implode(' and ', $formattedTime);
}
}
function languages()
{
return [
'en' => ['name' => 'English', 'flag' => 'us'],
'ar' => ['name' => 'Arabic', 'flag' => 'sa'],
'bn' => ['name' => 'Bengali', 'flag' => 'bd'],
'zh' => ['name' => 'Chinese', 'flag' => 'cn'],
'fr' => ['name' => 'French', 'flag' => 'fr'],
'de' => ['name' => 'German', 'flag' => 'de'],
'hi' => ['name' => 'Hindi', 'flag' => 'in'],
'es' => ['name' => 'Spanish', 'flag' => 'es'],
'ja' => ['name' => 'Japanese', 'flag' => 'jp'],
'rum' => ['name' => 'Romanian', 'flag' => 'ro'],
'vi' => ['name' => 'Vietnamese', 'flag' => 'vn'],
'it' => ['name' => 'Italian', 'flag' => 'it'],
'th' => ['name' => 'Thai', 'flag' => 'th'],
'bs' => ['name' => 'Bosnian', 'flag' => 'ba'],
'nl' => ['name' => 'Dutch', 'flag' => 'nl'],
'pt' => ['name' => 'Portuguese', 'flag' => 'pt'],
'pl' => ['name' => 'Polish', 'flag' => 'pl'],
'he' => ['name' => 'Hebrew', 'flag' => 'il'],
'hu' => ['name' => 'Hungarian', 'flag' => 'hu'],
'fi' => ['name' => 'Finnish', 'flag' => 'fi'],
'el' => ['name' => 'Greek', 'flag' => 'gr'],
'ko' => ['name' => 'Korean', 'flag' => 'kr'],
'ms' => ['name' => 'Malay', 'flag' => 'my'],
'id' => ['name' => 'Indonesian', 'flag' => 'id'],
'fa' => ['name' => 'Persian', 'flag' => 'ir'],
'tr' => ['name' => 'Turkish', 'flag' => 'tr'],
'sr' => ['name' => 'Serbian', 'flag' => 'rs'],
'km' => ['name' => 'Khmer', 'flag' => 'khm'],
'uk' => ['name' => 'Ukrainian', 'flag' => 'ua'],
'lo' => ['name' => 'Lao', 'flag' => 'la'],
'ru' => ['name' => 'Russian', 'flag' => 'ru'],
'cs' => ['name' => 'Czech', 'flag' => 'cz'],
'kn' => ['name' => 'Kannada', 'flag' => 'ka'],
'mr' => ['name' => 'Marathi', 'flag' => 'mh'],
'sv' => ['name' => 'Swedish', 'flag' => 'se'],
'da' => ['name' => 'Danish', 'flag' => 'dk'],
'ur' => ['name' => 'Urdu', 'flag' => 'pk'],
'sq' => ['name' => 'Albanian', 'flag' => 'al'],
'sk' => ['name' => 'Slovak', 'flag' => 'sk'],
'bur' => ['name' => 'Burmese', 'flag' => 'mm'],
'ti' => ['name' => 'Tigrinya', 'flag' => 'er'],
'kz' => ['name' => 'Kazakh', 'flag' => 'kz'],
'az' => ['name' => 'Azerbaijani', 'flag' => 'az'],
'zh-cn' => ['name' => 'Chinese (CN)', 'flag' => 'zh-cn'],
'zh-tw' => ['name' => 'Chinese (TW)', 'flag' => 'zh-tw'],
'pt-br' => ['name' => 'Portuguese (BR)', 'flag' => 'pt-br'],
'tz' => ['name' => 'Swahili', 'flag' => 'tz'],
'ps' => ['name' => 'Pashto', 'flag' => 'af'],
'prs' => ['name' => 'Dari', 'flag' => 'afdari'],
'ca' => ['name' => 'Catalan', 'flag' => 'ad'],
'bt' => ['name' => 'Dzongkha', 'flag' => 'dz'],
'drcfr' => ['name' => 'Congo (DRC)', 'flag' => 'drc'],
'cgfr' => ['name' => 'Congo (Republic)', 'flag' => 'cg'],
'escr' => ['name' => 'Costa Rica (Spanish)', 'flag' => 'cr'],
'enbw' => ['name' => 'Botswana (English)', 'flag' => 'bw'],
'bws' => ['name' => 'Botswana (Setswana)', 'flag' => 'bws'],
'deat' => ['name' => 'Austria(German)', 'flag' => 'at'],
'enbs' => ['name' => 'Bahamas(English)', 'flag' => 'bs'],
'arbh' => ['name' => 'Bahrain(Arabic)', 'flag' => 'bh'],
'pt-ao' => ['name' => 'Angola(Portuguese)', 'flag' => 'ao'],
'es-ar' => ['name' => 'Argentina(Spanish)', 'flag' => 'ar'],
'hy' => ['name' => 'Armenian', 'flag' => 'am'],
'au-en' => ['name' => 'Australia', 'flag' => 'au'],
'bb-en' => ['name' => 'Barbados(English)', 'flag' => 'bb'],
'be' => ['name' => 'Belarusian', 'flag' => 'by'],
'nl-be' => ['name' => 'Belgium(Dutch)', 'flag' => 'be'],
'bz-en' => ['name' => 'Belize(English)', 'flag' => 'bz'],
'bj-fr' => ['name' => 'Benin(French)', 'flag' => 'bj'],
'bo-es' => ['name' => 'Bolivia(Spanish)', 'flag' => 'bo'],
'bn-ms' => ['name' => 'Brunei(Malay)', 'flag' => 'bn'],
'bg' => ['name' => 'Bulgarian', 'flag' => 'bg'],
'bf-fr' => ['name' => 'Burkina Faso(French)', 'flag' => 'bf'],
'cm-fr' => ['name' => 'Cameroon(French)', 'flag' => 'cm'],
'ca-en' => ['name' => 'Canada(English)', 'flag' => 'ca'],
'cl-es' => ['name' => 'Chile(Spanish)', 'flag' => 'cl'],
'co-es' => ['name' => 'Colombia(Spanish)', 'flag' => 'co'],
'km-ar' => ['name' => 'Comoros(Arabic)', 'flag' => 'km'],
'hr' => ['name' => 'Croatian', 'flag' => 'hr'],
'cu-es' => ['name' => 'Cuba(Spanish)', 'flag' => 'cu'],
'cy-el' => ['name' => 'Cyprus(Greek)', 'flag' => 'cy'],
'dj-fr' => ['name' => 'Djibouti(French)', 'flag' => 'dj'],
'dm-en' => ['name' => 'Dominica(English)', 'flag' => 'dm'],
'tet' => ['name' => 'Tetum', 'flag' => 'tl'],
'ec-es' => ['name' => 'Ecuador(Spanish)', 'flag' => 'ec'],
'eg-ar' => ['name' => 'Egypt(Arabic)', 'flag' => 'eg'],
'sv-es' => ['name' => 'El Salvador(Spanish)', 'flag' => 'sv'],
'gq-es' => ['name' => 'Equatorial Guinea(Spanish)', 'flag' => 'gq'],
'et' => ['name' => 'Estonian', 'flag' => 'ee'],
'ss' => ['name' => 'Swati', 'flag' => 'sz'],
'am' => ['name' => 'Amharic', 'flag' => 'et'],
'fj' => ['name' => 'Fijian', 'flag' => 'fj'],
'ga-fr' => ['name' => 'Gabon(French)', 'flag' => 'ga'],
'gm-en' => ['name' => 'Gambia(English)', 'flag' => 'gm'],
'ka' => ['name' => 'Georgian', 'flag' => 'ge'],
'gh-en' => ['name' => 'Ghana(English)', 'flag' => 'gh'],
'gd-en' => ['name' => 'Grenada(English)', 'flag' => 'gd'],
'gt-en' => ['name' => 'Guatemala(English)', 'flag' => 'gt'],
'gn-fr' => ['name' => 'Guinea(French)', 'flag' => 'gn'],
'gy-en' => ['name' => 'Guyana(English)', 'flag' => 'gy'],
'ht-fr' => ['name' => 'Haiti(French)', 'flag' => 'ht'],
'hn-es' => ['name' => 'Honduras(Spanish)', 'flag' => 'hn'],
];
}
// BUSINESS PANEL
// user role permission
if (!function_exists('visible_permission')) {
function visible_permission($permission)
{
$user = auth()->user();
// Ensure the user is authenticated and has a business_id
if (!$user || !$user->business_id) {
return false;
}
// Handle visibility field directly as an array or decode it if it's a string
$permissions = is_array($user->visibility)
? $user->visibility
: json_decode($user->visibility, true);
return $permissions[$permission] ?? false;
}
}
function get_business_option($key)
{
$cacheKey = "business_setting_" . auth()->user()->business_id;
return Cache::remember($cacheKey, now()->addDay(), function () use ($key) {
if ($key == 'business-settings') {
$business_id = auth()->user()->business_id;
$option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id);
return $option ? $option->value : null;
}
return null;
});
}
function plan_data($business_id = null)
{
$business_id = $business_id ?? auth()->user()->business_id;
return cache_remember('plan-data-' . $business_id, function () use ($business_id) {
$planSubscribe = PlanSubscribe::with('plan:id,subscriptionName')->where('business_id', $business_id)->latest()->first();
if ($planSubscribe) {
$business = Business::findOrFail($planSubscribe->business_id);
$planSubscribe->will_expire = $business->will_expire;
}
return $planSubscribe;
});
}
function branch_count()
{
$business_id = auth()->user()->business_id;
return cache_remember('branch-count-' . $business_id, function () use ($business_id) {
$totalBranch = Branch::where('business_id', $business_id)->count();
return $totalBranch;
});
}
function multibranch_active()
{
return plan_data()['allow_multibranch'] ?? false;
}
function business_currency($business_id = null)
{
$business_id = $business_id ?? auth()->user()->business_id;
return cache_remember("business_currency_{$business_id}", function () use ($business_id) {
$businessCurrency = UserCurrency::where('business_id', $business_id)->first() ?? Currency::where('is_default', 1)->first();
;
if ($businessCurrency) {
return (object) [
'name' => $businessCurrency->name,
'rate' => $businessCurrency->rate,
'code' => $businessCurrency->code,
'symbol' => $businessCurrency->symbol,
'position' => $businessCurrency->position,
];
}
return default_currency();
});
}
function sale_rounding(?float $amount = null, ?string $round_option = null): float|string
{
$business_id = auth()->user()->business_id;
// If $round_option is not passed, try to fetch from settings
if (is_null($round_option)) {
$round_option = cache_remember("business_sale_rounding_{$business_id}", function () use ($business_id) {
$option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id);
return $option ? ($option->value['sale_rounding_option'] ?? 'none') : 'none';
});
}
if (is_null($amount)) {
return $round_option;
}
// Apply rounding if amount is provided
return match ($round_option) {
'round_up' => ceil($amount),
'nearest_whole_number' => round($amount),
'nearest_0.05' => round($amount * 20) / 20,
'nearest_0.1' => round($amount * 10) / 10,
'nearest_0.5' => round($amount * 2) / 2,
default => $amount,
};
}
function moduleCheck($module)
{
$module = Module::find($module);
if ($module && $module->isEnabled()) {
return true;
}
return false;
}
function remaining_days($date)
{
$today = \Carbon\Carbon::today();
$expiry = \Carbon\Carbon::parse($date);
$diff = $today->diffInDays($expiry, false);
return $diff > 0 ? "$diff days" : "";
}
// update RemainingBalance
function updateBalance($amount, string $type, $branch_id = null)
{
$amount = is_numeric($amount) ? (float) $amount : 0;
$businessId = auth()->user()->business_id;
// if active branch, then update active branch
$branch = auth()->user()->active_branch;
if ($branch) {
if ($type == 'increment') {
$branch->increment('branchRemainingBalance', $amount);
} elseif ($type == 'decrement') {
$branch->decrement('branchRemainingBalance', $amount);
}
return;
}
//If branch_id is provided, update that branch
if ($branch_id) {
$branch = Branch::find($branch_id);
if ($branch) {
if ($type == 'increment') {
$branch->increment('branchRemainingBalance', $amount);
} elseif ($type == 'decrement') {
$branch->decrement('branchRemainingBalance', $amount);
}
}
return;
}
// If no branch, update business balance
$business = Business::find($businessId);
if ($business) {
if ($type == 'increment') {
$business->increment('remainingShopBalance', $amount);
} elseif ($type == 'decrement') {
$business->decrement('remainingShopBalance', $amount);
}
}
}
function manipulateBranchData($business_id)
{
$business = auth()->user()->business;
$shop_owner = User::where(['business_id' => $business_id, 'role' => 'shop-owner'])->firstOrFail();
$branch = Branch::create([
'is_main' => 1,
'email' => $shop_owner->email,
'name' => $business->companyName,
'phone' => $business->phoneNumber,
'address' => $business->address
]);
$updates = [
'users' => ['branch_id' => $branch->id, 'where' => ['role' => 'staff']],
'stocks' => ['branch_id' => $branch->id, 'where' => ['business_id' => $business_id]],
'product_settings' => ['branch_id' => $branch->id],
'sale_returns' => ['branch_id' => $branch->id],
'purchase_returns' => ['branch_id' => $branch->id],
'expenses' => ['branch_id' => $branch->id],
'incomes' => ['branch_id' => $branch->id],
'sales' => ['branch_id' => $branch->id],
'purchases' => ['branch_id' => $branch->id],
'due_collects' => ['branch_id' => $branch->id],
'parties' => ['branch_id' => $branch->id],
'combo_products' => ['branch_id' => $branch->id],
'transactions' => ['branch_id' => $branch->id],
];
foreach ($updates as $table => $data) {
$query = DB::table($table);
if (!empty($data['where'])) {
$query->where($data['where']);
unset($data['where']);
}
$query->update($data);
}
if (moduleCheck('HrmAddon')) {
DB::table('holidays')->update(['branch_id' => $branch->id]);
DB::table('attendances')->update(['branch_id' => $branch->id]);
DB::table('leaves')->update(['branch_id' => $branch->id]);
DB::table('payrolls')->update(['branch_id' => $branch->id]);
DB::table('employees')->update(['branch_id' => $branch->id]);
}
if (moduleCheck('WarehouseAddon')) {
DB::table('warehouses')->update(['branch_id' => $branch->id]);
DB::table('transfers')->update([
'from_branch_id' => $branch->id,
'to_branch_id' => $branch->id
]);
}
return true;
}
function get_root_domain()
{
$appUrl = config('app.url');
return parse_url($appUrl, PHP_URL_HOST);
}
function checkDomainStatus($domain)
{
$result = [
'domain' => $domain,
'exists' => false,
'http' => false,
'https' => false,
];
// 1. Check if domain resolves (DNS record exists)
if (dns_get_record($domain, DNS_A) || dns_get_record($domain, DNS_AAAA)) {
$result['exists'] = true;
// 2. Check HTTP (port 80)
try {
$response = Http::timeout(5)->get("http://{$domain}");
if ($response->successful()) {
$result['http'] = true;
}
} catch (\Exception $e) {
$result['http'] = false;
}
// 3. Check HTTPS (port 443)
try {
$response = Http::timeout(5)->get("https://{$domain}");
if ($response->successful()) {
$result['https'] = true;
}
} catch (\Exception $e) {
$result['https'] = false;
}
}
return $result;
}
function custom_reports()
{
$business_id = auth()->user()->business_id;
if (moduleCheck('CustomReportsAddon')) {
return cache_remember('custom-reports-' . $business_id, function () use ($business_id) {
return Modules\CustomReportsAddon\App\Models\CustomReport::where('business_id', auth()->user()->business_id)->where('status', 1)->get();
});
} else {
return [];
}
}
function cash_balance()
{
$businessId = auth()->user()->business_id;
if (!$businessId) {
return 0;
}
// Sum of all credits and bank_to_cash
$totalCredit = Transaction::where('business_id', $businessId)
->where(function ($q) {
$q->where('type', 'credit')
->orWhere('transaction_type', 'bank_to_cash');
})
->sum('amount');
// Sum of all debits and cash_to_bank
$totalDebit = Transaction::where('business_id', $businessId)
->where(function ($q) {
$q->where('type', 'debit')
->orWhere('transaction_type', 'cash_to_bank');
})
->sum('amount');
return $totalCredit - $totalDebit;
}
function transaction_types($transactions): string
{
if (!$transactions) {
return '';
}
return $transactions
->map(function ($transaction) {
if (
$transaction->transaction_type === 'bank_payment' &&
!empty($transaction->paymentType?->name)
) {
return $transaction->paymentType->name;
}
return $transaction->transaction_type
? ucfirst(explode('_', $transaction->transaction_type)[0])
: '';
})
->filter()
->unique()
->implode(', ');
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\Models\Plan;
use App\Models\Option;
use App\Models\Feature;
class AboutController extends Controller
{
public function index()
{
$page_data = get_option('manage-pages');
$general = Option::where('key','general')->first();
$features = Feature::whereStatus(1)->latest()->get();
$plans = Plan::where('status',1)->latest()->get();
return view('web.about.index',compact('page_data','general','features','plans'));
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Business;
use App\Models\PaymentType;
use Illuminate\Http\Request;
use App\Models\BusinessCategory;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Session;
use Modules\AffiliateAddon\App\Models\Affiliate;
class AcnooBusinessController extends Controller
{
public function index()
{
$categories = BusinessCategory::select('id', 'name')->whereStatus(1)->get();
return response()->json($categories);
}
public function getBusinessCategories()
{
$categories = BusinessCategory::select('id', 'name')->whereStatus(1)->get();
return response()->json($categories);
}
public function store(Request $request)
{
$validate_data = $request->validate([
'companyName' => 'required|max:250',
'business_category_id' => 'required|exists:business_categories,id',
'plan_id' => 'required|exists:plans,id',
'address' => 'nullable|max:250',
'password' => 'required|min:6|max:100',
'email' => 'required|email|unique:users,email',
]);
if (moduleCheck('AffiliateAddon')) {
$refCode = Cookie::get('ref_code');
if ($refCode) {
$affiliator = Affiliate::where('ref_code', $refCode)->first();
if ($affiliator) {
$validate_data['affiliator_id'] = $affiliator->user_id;
}
}
}
$business = Business::create($validate_data);
PaymentType::create([
'name' => "Cash",
'business_id' => $business->id
]);
Session::put('plan_id', $request->plan_id);
return response()->json([
'message' => 'Business created successfully.',
'redirect' => route('payments-gateways.index', ['plan_id' => $request->plan_id, 'business_id' => $business->id]),
]);
}
public function verifyCode(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([
'remember_token' => NULL,
'email_verified_at' => now(),
]);
return response()->json([
'message' => 'Logged In successfully!',
'is_setup' => $is_setup,
'token' => $token,
]);
} else {
return response()->json([
'error' => __('The verification otp has been expired.')
], 400);
}
} else {
return response()->json([
'error' => __('Invalid otp.')
], 404);
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Plan;
use App\Models\Gateway;
use App\Models\Business;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\AffiliateAddon\App\Models\Affiliate;
class AcnooAffiliateController extends Controller
{
public function index()
{
$plans = Plan::latest()->get();
$gateways = Gateway::latest()->get();
$affiliates = Affiliate::with(['user:id,business_id,name,email','user.business:id,plan_subscribe_id,will_expire,companyName,phoneNumber,address,subscriptionDate,created_at,business_category_id:id,name','user.business.enrolled_plan:id,plan_id,business_id,plan_id','user.business.enrolled_plan.plan:id,subscriptionName'])->latest()->paginate(20);
return view('admin.affiliate-modules.affiliate.index', compact('affiliates','plans','gateways'));
}
public function acnooFilter(Request $request)
{
$search = $request->input('search');
$affiliates = Affiliate::with(['user:id,business_id,name,email','user.business:id,plan_subscribe_id,will_expire,companyName,phoneNumber,address,subscriptionDate,created_at,business_category_id:id,name','user.business.enrolled_plan:id,plan_id,business_id,plan_id','user.business.enrolled_plan.plan:id,subscriptionName'])->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('balance', 'like', '%' . $search . '%')
->orWhereHas('user', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%')
->orwhere('email', 'like', '%' . $search . '%');
})
->orWhereHas('user.business.enrolled_plan.plan', function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('admin.affiliate-modules.affiliate.datas', compact('affiliates'))->render()
]);
}
return redirect(url()->previous());
}
public function destroy($id)
{
Affiliate::findOrFail($id)->delete();
return response()->json([
'message' => __('Affiliate deleted successfully'),
'redirect' => route('admin.affiliates.index')
]);
}
public function deleteAll(Request $request)
{
Affiliate::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Affiliate deleted successfully'),
'redirect' => route('admin.affiliates.index')
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Business;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooAffiliateReportController extends Controller
{
public function index()
{
$items = Business::with(['enrolled_plan:id,plan_id', 'enrolled_plan.plan:id,subscriptionName', 'category:id,name'])->latest()->paginate(20);
return view('admin.affiliate-modules.reports.index', compact('items'));
}
public function acnooFilter(Request $request)
{
$search = $request->input('search');
$items = Business::with(['enrolled_plan:id,plan_id', 'enrolled_plan.plan:id,subscriptionName', 'category:id,name'])->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('companyName', 'like', '%' . $search . '%')
->orWhereHas('enrolled_plan.plan', function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('admin.affiliate-modules.reports.datas', compact('items'))->render()
]);
}
return redirect(url()->previous());
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Banner;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooBannerController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('permission:banners-create')->only('create', 'store');
$this->middleware('permission:banners-read')->only('index');
$this->middleware('permission:banners-update')->only('edit', 'update','status');
$this->middleware('permission:banners-delete')->only('destroy','deleteAll');
}
public function index(Request $request)
{
$banners = Banner::latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.banners.search', compact('banners'))->render()
]);
}
return view('admin.banners.index', compact('banners'));
}
public function create()
{
return view('admin.banners.create');
}
public function store(Request $request)
{
$request->validate([
'status' => 'nullable|in:on',
'imageUrl' => 'required|image|mimes:jpeg,png,jpg,gif,svg',
]);
Banner::create([
'imageUrl' => $request->imageUrl ? $this->upload($request, 'imageUrl') : NULL,
'status' => $request->status ? 1 : 0,
]);
return response()->json([
'message' => __('Banner saved successfully'),
'redirect' => route('admin.banners.index')
]);
}
public function edit(string $id)
{
$banners = Banner::findOrFail($id);
return view('admin.banners.search',compact('banners'));
}
public function update(Request $request, string $id)
{
$request->validate([
'status' => 'nullable|in:on',
'imageUrl' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
]);
$banner = Banner::findOrFail($id);
$banner->update([
'imageUrl' => $request->imageUrl ? $this->upload($request, 'imageUrl', $banner->imageUrl) : $banner->imageUrl,
'status' => $request->status ? 1 : 0,
]);
return response()->json([
'message' => __('Banner updated successfully'),
'redirect' => route('admin.banners.index')
]);
}
public function destroy(string $id)
{
$banner = Banner::findOrFail($id);
if (file_exists($banner->imageUrl)) {
Storage::delete($banner->imageUrl);
}
$banner->delete();
return response()->json([
'message' => __('Banners deleted successfully'),
'redirect' => route('admin.banners.index')
]);
}
public function status(Request $request, $id)
{
$banner = Banner::findOrFail($id);
$banner->update(['status' => $request->status]);
return response()->json(['message' => 'Banner']);
}
public function deleteAll(Request $request)
{
$idsToDelete = $request->input('ids');
DB::beginTransaction();
try {
$banners = Banner::whereIn('id', $idsToDelete)->get();
foreach ($banners as $banner) {
if (file_exists($banner->imageUrl)) {
Storage::delete($banner->imageUrl);
}
}
Banner::whereIn('id', $idsToDelete)->delete();
DB::commit();
return response()->json([
'message' => __('Selected Banners deleted successfully'),
'redirect' => route('admin.banners.index')
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(__('Something went wrong.'), 400);
}
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Blog;
use App\Models\Comment;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class AcnooBlogController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$blogs = Blog::when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('title', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.website-setting.blogs.datas', compact('blogs'))->render()
]);
}
return view('admin.website-setting.blogs.index', compact('blogs'));
}
public function create()
{
return view('admin.website-setting.blogs.create');
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:blogs,title',
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg',
'status' => 'boolean',
'descriptions' => 'nullable|string',
'tags' => 'nullable|array',
'meta.title' => 'nullable|string',
'meta.description' => 'nullable|string',
]);
Blog::create($request->except('image') + [
'user_id' => Auth::id(),
'image' => $request->image ? $this->upload($request, 'image') : null,
]);
return response()->json([
'message' => __('BLog created successfully'),
'redirect' => route('admin.blogs.index')
]);
}
public function edit(Blog $blog)
{
return view('admin.website-setting.blogs.edit', compact('blog'));
}
public function update(Request $request, Blog $blog)
{
$request->validate([
'title' => 'required|unique:blogs,title,' . $blog->id,
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
'status' => 'boolean',
'descriptions' => 'nullable|string',
'tags' => 'nullable|array',
'meta.title' => 'nullable|string',
'meta.description' => 'nullable|string',
]);
$blog->update($request->except('image') + [
'user_id' => Auth::id(),
'image' => $request->image ? $this->upload($request, 'image', $blog->image) : $blog->image,
]);
return response()->json([
'message' => __('BLog updated successfully'),
'redirect' => route('admin.blogs.index')
]);
}
public function destroy(Blog $blog)
{
if (file_exists($blog->image)) {
Storage::delete($blog->image);
}
$blog->delete();
return response()->json([
'message' => __('Blog deleted successfully'),
'redirect' => route('admin.blogs.index')
]);
}
public function status(Request $request, $id)
{
$blog_status = Blog::findOrFail($id);
$blog_status->update(['status' => $request->status]);
return response()->json(['message' => 'Blog']);
}
public function deleteAll(Request $request)
{
Blog::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Blog deleted successfully'),
'redirect' => route('admin.blogs.index')
]);
}
public function filterComment($id, Request $request)
{
$blog = Blog::findOrFail($id);
$comments = Comment::with('blog:id')->whereStatus(1)->where('blog_id', $blog->id)->when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%')
->orwhere('email', 'like', '%' . $request->search . '%')
->orwhere('comment', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.website-setting.blogs.comments.datas', compact('comments','blog'))->render()
]);
}
return view('admin.website-setting.blogs.comments.index', compact('comments','blog'));
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\BusinessCategory;
use Illuminate\Http\Request;
class AcnooBusinessCategoryController extends Controller
{
public function index(Request $request)
{
$categories = BusinessCategory::when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%')
->orWhere('description', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.business-categories.datas', compact('categories'))->render()
]);
}
return view('admin.business-categories.index', compact('categories'));
}
public function create()
{
return view('admin.business-categories.create');
}
public function store(Request $request)
{
$request->validate([
'status' => 'nullable|in:on',
'description' => 'nullable|string|max:255',
'name' => 'required|string|unique:business_categories|max:255',
]);
BusinessCategory::create($request->except('status') + [
'status' => $request->status ? 1 : 0,
]);
return response()->json([
'message' => __('Category saved successfully'),
'redirect' => route('admin.business-categories.index')
]);
}
public function edit($id)
{
$category = BusinessCategory::find($id);
return view('admin.business-categories.edit', compact('category'));
}
public function update(Request $request, $id)
{
$request->validate([
'status' => 'in:on',
'description' => 'nullable|string|max:255',
'name' => 'required|string|max:255|unique:business_categories,name,' . $id,
]);
$category = BusinessCategory::find($id);
$category->update($request->except('status') + [
'status' => $request->status ? 1 : 0,
]);
return response()->json([
'message' => __('Category updated successfully'),
'redirect' => route('admin.business-categories.index')
]);
}
public function destroy($id)
{
$category = BusinessCategory::findOrFail($id);
$category->delete();
return response()->json([
'message' => __('Category deleted successfully'),
'redirect' => route('admin.business-categories.index')
]);
}
public function deleteAll(Request $request)
{
BusinessCategory::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Category deleted successfully'),
'redirect' => route('admin.business-categories.index')
]);
}
public function status(Request $request, $id)
{
$category = BusinessCategory::findOrFail($id);
$category->update(['status' => $request->status]);
return response()->json(['message' => 'Business category']);
}
}

View File

@@ -0,0 +1,347 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Plan;
use App\Models\User;
use App\Models\Gateway;
use App\Models\Business;
use App\Models\PaymentType;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use App\Models\BusinessCategory;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use Modules\AffiliateAddon\App\Models\Affiliate;
use Modules\AffiliateAddon\App\Models\AffiliateTransaction;
use Illuminate\Validation\Rule;
class AcnooBusinessController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('permission:business-create')->only('create', 'store');
$this->middleware('permission:business-read')->only('index');
$this->middleware('permission:business-update')->only('edit', 'update', 'status');
$this->middleware('permission:business-delete')->only('destroy', 'deleteAll');
}
public function index(Request $request)
{
$plans = Plan::latest()->get();
$gateways = Gateway::latest()->get();
$search = $request->input('search');
$businesses = Business::with(['enrolled_plan:id,plan_id', 'enrolled_plan.plan:id,subscriptionName', 'category:id,name'])
->when($request->search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('companyName', 'like', '%' . $search . '%')
->orWhere('phoneNumber', 'like', '%' . $search . '%')
->orWhereHas('category', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
})
->orWhereHas('user', function ($q) use ($search) {
$q->where('email', 'like', '%' . $search . '%');
})
->orWhereHas('enrolled_plan.plan', function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%');
});
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.business.datas', compact('businesses'))->render()
]);
}
return view('admin.business.index', compact('businesses', 'gateways', 'plans'));
}
public function create()
{
$plans = Plan::where('status', 1)->latest()->get();
$categories = BusinessCategory::latest()->get();
return view('admin.business.create', compact('plans', 'categories'));
}
public function store(Request $request)
{
$request->validate([
'address' => 'nullable|max:250',
'companyName' => 'required|max:250',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:4|confirmed',
'pictureUrl' => 'nullable|image',
'phoneNumber' => 'nullable',
'shopOpeningBalance' => 'nullable|numeric',
'business_category_id' => 'required|exists:business_categories,id',
'plan_subscribe_id' => 'nullable|exists:plans,id',
]);
DB::beginTransaction();
try {
$user = auth()->user();
$business = Business::create([
'companyName' => $request->companyName,
'address' => $request->address,
'email' => $request->email,
'phoneNumber' => $request->phoneNumber,
'shopOpeningBalance' => $request->shopOpeningBalance,
'business_category_id' => $request->business_category_id,
'pictureUrl' => $request->pictureUrl ? $this->upload($request, 'pictureUrl') : NULL,
'user_id' => $user->id,
]);
PaymentType::create([
'name' => "Cash",
'business_id' => $business->id
]);
User::create([
'business_id' => $business->id,
'name' => $request->companyName,
'email' => $request->email,
'phone' => $request->phoneNumber,
'image' => $request->pictureUrl ? $this->upload($request, 'pictureUrl') : NULL,
'password' => Hash::make($request->password),
'user_id' => $user->id,
]);
if ($request->plan_subscribe_id) {
$plan = Plan::findOrFail($request->plan_subscribe_id);
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'notes' => $request->notes,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'gateway_id' => $request->gateway_id,
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0,
]);
$business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($plan->duration),
]);
sendNotification($subscribe->id, route('admin.subscription-reports.index', ['id' => $subscribe->id]), __('Plan subscribed by ' . $user->name));
}
DB::commit();
return response()->json([
'message' => __('Business created successfully.'),
'redirect' => route('admin.business.index'),
]);
} catch (\Throwable $th) {
DB::rollback();
return response()->json(__('Something went wrong.'), 403);
}
}
public function edit(string $id)
{
$plans = Plan::latest()->get();
$business = Business::findOrFail($id);
$categories = BusinessCategory::latest()->get();
$user = User::where('business_id', $business->id)->firstOrFail();
return view('admin.business.edit', compact('business', 'plans', 'categories', 'user'));
}
public function update(Request $request, string $id)
{
$business = Business::findOrFail($id);
$user = User::where('business_id', $business->id)->firstOrFail();
$request->validate([
'address' => 'nullable|max:250',
'companyName' => 'required|max:250',
'email' => [
'required',
'email',
Rule::unique('users', 'email')->ignore($user->id),
],
'password' => 'nullable|string|min:4|confirmed',
'pictureUrl' => 'nullable|image',
'phoneNumber' => 'nullable',
'shopOpeningBalance' => 'nullable|numeric',
'business_category_id' => 'required|exists:business_categories,id',
'plan_subscribe_id' => 'nullable|exists:plans,id',
]);
DB::beginTransaction();
try {
$business->update([
'companyName' => $request->companyName,
'address' => $request->address,
'email' => $request->email,
'phoneNumber' => $request->phoneNumber,
'shopOpeningBalance' => $request->shopOpeningBalance,
'business_category_id' => $request->business_category_id,
'pictureUrl' => $request->pictureUrl ? $this->upload($request, 'pictureUrl', $business->pictureUrl) : $business->pictureUrl,
]);
$user->update([
'name' => $request->companyName,
'email' => $request->email,
'phone' => $request->phoneNumber,
'image' => $request->pictureUrl ? $this->upload($request, 'pictureUrl') : $user->image,
'password' => $request->password ? Hash::make($request->password) : $user->password,
]);
$prev_subscription = PlanSubscribe::where('business_id', $business->id)->latest()->first();
if ($request->plan_subscribe_id && ($prev_subscription->plan_id != $request->plan_subscribe_id)) {
$plan = Plan::findOrFail($request->plan_subscribe_id);
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'notes' => $request->notes,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'gateway_id' => $request->gateway_id,
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0,
]);
$business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($plan->duration),
]);
sendNotification($subscribe->id, route('admin.subscription-reports.index', ['id' => $subscribe->id]), __('Plan subscribed by ' . auth()->user()->name));
}
DB::commit();
return response()->json([
'message' => __('Business updated successfully.'),
'redirect' => route('admin.business.index'),
]);
} catch (\Throwable $th) {
DB::rollback();
return response()->json(__('Something went wrong.'), 403);
}
}
public function destroy($id)
{
$business = Business::findOrFail($id);
$business->delete();
return response()->json([
'message' => __('Business deleted successfully'),
'redirect' => route('admin.business.index')
]);
}
public function deleteAll(Request $request)
{
Business::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Business deleted successfully'),
'redirect' => route('admin.business.index')
]);
}
// Upgrade plan code
public function upgradePlan(Request $request)
{
$request->validate([
'price' => 'required|string',
'notes' => 'required|string',
'plan_id' => 'required|exists:plans,id',
'business_id' => 'required|exists:businesses,id',
]);
DB::beginTransaction();
try {
$plan = Plan::findOrFail($request->plan_id);
$business = Business::findOrFail($request->business_id);
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'payment_status' => 'paid',
'notes' => $request->notes,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'gateway_id' => $request->gateway_id,
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0
]);
Cache::forget('plan-data-' . $business->id);
$business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($plan->duration),
]);
if (moduleCheck('AffiliateAddon') && $business->affiliator_id) {
$affiliateUser = User::find($business->affiliator_id);
if ($affiliateUser && $plan->affiliate_commission > 0) {
$commission = ($plan->subscriptionPrice * $plan->affiliate_commission) / 100;
Affiliate::where('user_id', $affiliateUser->id)->increment('balance', $commission);
AffiliateTransaction::create([
'user_id' => $affiliateUser->id,
'business_id' => $business->id,
'trx' => strtoupper(str()->random(10)),
'type' => 'credit',
'amount' => $commission,
'title' => 'Commission from Order #' . $subscribe->id,
'reference_id' => $subscribe->id,
'reference_type' => 'PlanSubscribe',
'note' => 'User purchased via your referral link',
]);
}
}
sendNotification($subscribe->id, route('admin.subscription-reports.index', ['id' => $subscribe->id]), __('Plan subscribed by ' . auth()->user()->name));
DB::commit();
return response()->json([
'message' => __('Subscription enrolled successfully.'),
'redirect' => route('admin.subscription-reports.index'),
]);
} catch (\Throwable $th) {
DB::rollback();
return response()->json(__('Something went wrong.'), 403);
}
}
public function status(Request $request, $id)
{
$business = Business::findOrFail($id);
$business->update(['status' => $request->status]);
return response()->json(['message' => 'Business']);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Comment;
use Illuminate\Http\Request;
class AcnooCommentController extends Controller
{
public function destroy(Comment $comment)
{
$comment->delete();
return response()->json([
'message' => __('Comment deleted successfully.'),
'redirect' => route('admin.blogs.index')
]);
}
public function deleteAll(Request $request)
{
Comment::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Comments deleted successfully'),
'redirect' => route('admin.blogs.index')
]);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Currency;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooCurrencyController extends Controller
{
public function __construct()
{
$this->middleware('permission:currencies-create')->only('create', 'store');
$this->middleware('permission:currencies-read')->only('index');
$this->middleware('permission:currencies-update')->only('edit', 'update', 'default');
$this->middleware('permission:currencies-delete')->only('destroy');
}
public function index(Request $request)
{
$currencies = Currency::orderBy('is_default', 'desc')->orderBy('status', 'desc')->when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%')
->orWhere('country_name', 'like', '%' . $request->search . '%')
->orWhere('code', 'like', '%' . $request->search . '%')
->orWhere('symbol', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.currencies.datas', compact('currencies'))->render()
]);
}
return view('admin.currencies.index', compact('currencies'));
}
public function create()
{
$countries = base_path('lang/countrylist.json');
$countries = json_decode(file_get_contents($countries), true);
return view('admin.currencies.create', compact('countries'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|unique:currencies',
'country_name' => 'nullable|string',
'code' => 'required|string|unique:currencies',
'rate' => 'nullable|numeric',
'symbol' => 'nullable|string',
'position' => 'nullable|string',
'status' => 'required|integer',
'is_default' => 'nullable|boolean',
]);
Currency::create($request->all());
return response()->json([
'message' => __('Currency Created successfully'),
'redirect' => route('admin.currencies.index')
]);
}
public function edit(Currency $currency)
{
$countries = base_path('lang/countrylist.json');
$countries = json_decode(file_get_contents($countries), true);
return view('admin.currencies.edit', compact('currency', 'countries'));
}
public function update(Request $request, Currency $currency)
{
$request->validate([
'name' => 'required|string|unique:currencies,name,' . $currency->id,
'country_name' => 'nullable|string',
'code' => 'required|string|unique:currencies,code,' . $currency->id,
'rate' => 'nullable|numeric',
'symbol' => 'nullable|string',
'position' => 'nullable|string',
'status' => 'required|integer',
'is_default' => 'nullable|boolean',
]);
$currency->update($request->all());
return response()->json([
'message' => __('Currency updated successfully'),
'redirect' => route('admin.currencies.index')
]);
}
public function default($id)
{
$currency = Currency::find($id);
if ($currency) {
Currency::where('id', '!=', $id)->update(['is_default' => 0]);
$currency->update(['is_default' => 1]);
cache()->forget('default_currency');
}
return redirect()->route('admin.currencies.index')->with('message', __('Default currency activated successfully'));
}
public function destroy(Currency $currency)
{
if ($currency->is_default) {
return response()->json([
'message' => __('You cannot delete it because it is default currency'),
'redirect' => route('admin.currencies.index')
], 400);
}
$currency->delete();
return response()->json([
'message' => __('Currency deleted successfully'),
'redirect' => route('admin.currencies.index')
], 200);
}
public function deleteAll(Request $request)
{
$default_currency_id = Currency::where('is_default', 1)->value('id');
if (count($request->ids) === 1 && in_array($default_currency_id, $request->ids)) {
return response()->json([
'message' => __('You cannot delete the default currency.'),
'redirect' => route('admin.currencies.index')
], 400);
}
$idsToDelete = array_filter($request->ids, function ($id) use ($default_currency_id) {
return $id != $default_currency_id;
});
Currency::whereIn('id', $idsToDelete)->delete();
return response()->json([
'message' => __('Selected currencies deleted successfully.'),
'redirect' => route('admin.currencies.index')
]);
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Feature;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooFeatureController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$features = Feature::when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('title', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.website-setting.features.datas', compact('features'))->render()
]);
}
return view('admin.website-setting.features.index', compact('features'));
}
public function create()
{
return view('admin.website-setting.features.create');
}
public function store(Request $request)
{
$request->validate([
'status' => 'required',
'title' => 'required',
'bg_color' => 'required',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
Feature::create($request->except('image') + [
'image' => $request->image ? $this->upload($request, 'image') : NULL
]);
return response()->json([
'message' => __('Feature created successfully'),
'redirect' => route('admin.features.index')
]);
}
public function edit(Feature $feature)
{
return view('admin.website-setting.features.edit', compact('feature'));
}
public function update(Request $request, Feature $feature)
{
$request->validate([
'status' => 'required',
'title' => 'required|string',
'bg_color' => 'required|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
$feature->update($request->except('image') + [
'image' => $request->image ? $this->upload($request, 'image', $feature->image) : $feature->image,
]);
return response()->json([
'message' => __('Feature updated successfully'),
'redirect' => route('admin.features.index')
]);
}
public function destroy(Feature $feature)
{
if (file_exists($feature->image)) {
Storage::delete($feature->image);
}
$feature->delete();
return response()->json([
'message' => __('Feature deleted successfully'),
'redirect' => route('admin.features.index')
]);
}
public function status(Request $request, $id)
{
$feature = Feature::findOrFail($id);
$feature->update(['status' => $request->status]);
return response()->json(['message' => 'Feature ']);
}
public function deleteAll(Request $request)
{
$features = Feature::whereIn('id', $request->ids)->get();
foreach ($features as $feature) {
if (file_exists($feature->image)) {
Storage::delete($feature->image);
}
}
$features->each->delete();
return response()->json([
'message' => __('Selected Feature deleted successfully'),
'redirect' => route('admin.features.index')
]);
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Models\PosAppInterface;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooInterfaceController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$interfaces = PosAppInterface::latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.website-setting.interfaces.datas', compact('interfaces'))->render()
]);
}
return view('admin.website-setting.interfaces.index', compact('interfaces'));
}
public function create()
{
return view('admin.website-setting.interfaces.create');
}
public function store(Request $request)
{
$request->validate([
'status' => 'required',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
PosAppInterface::create($request->except('image') + [
'image' => $request->image ? $this->upload($request, 'image') : NULL
]);
return response()->json([
'message' => __('Interfaces created successfully'),
'redirect' => route('admin.interfaces.index')
]);
}
public function edit($id)
{
$interface = PosAppInterface::findOrFail($id);
return view('admin.website-setting.interfaces.edit', compact('interface'));
}
public function update(Request $request, $id)
{
$interface = PosAppInterface::findOrFail($id);
$request->validate([
'status' => 'required',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
$interface->update($request->except('image') + [
'image' => $request->image ? $this->upload($request, 'image', $interface->image) : $interface->image,
]);
return response()->json([
'message' => __('Interface updated successfully'),
'redirect' => route('admin.interfaces.index')
]);
}
public function destroy($id)
{
$posAppInterface = PosAppInterface::findOrFail($id);
if (file_exists($posAppInterface->image)) {
Storage::delete($posAppInterface->image);
}
$posAppInterface->delete();
return response()->json([
'message' => __('Interface deleted successfully'),
'redirect' => route('admin.interfaces.index')
]);
}
public function status(Request $request,$id)
{
$posAppInterface = PosAppInterface::findOrFail($id);
$posAppInterface->update(['status' => $request->status]);
return response()->json(['message' => 'Interface ']);
}
public function deleteAll(Request $request)
{
$posAppInterfaces = PosAppInterface::whereIn('id', $request->ids)->get();
foreach ($posAppInterfaces as $posAppInterface) {
if (file_exists($posAppInterface->image)) {
Storage::delete($posAppInterface->image);
}
}
$posAppInterfaces->each->delete();
return response()->json([
'message' => __('Selected Interface deleted successfully'),
'redirect' => route('admin.interfaces.index')
]);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Language;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooLanguageController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$languages = Language::when($request->has('search'), function ($q) use ($request) {
$q->where(function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->search . '%');
});
})
->latest();
if ($request->ajax()) {
$languages = $languages->get();
return response()->json([
'data' => view('admin.website-setting.languages.datas', compact('languages'))->render()
]);
}
$languages = $languages->paginate(20);
return view('admin.website-setting.languages.index', compact('languages'));
}
public function create()
{
return view('admin.website-setting.languages.create');
}
public function store(Request $request)
{
$request->validate([
'status' => 'required',
'name' => 'required|string',
'icon' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
Language::create($request->except('icon') + [
'icon' => $request->icon ? $this->upload($request, 'icon') : NULL
]);
return response()->json([
'message' => __('Laguage created successfully'),
'redirect' => route('admin.languages.index')
]);
}
public function edit(Language $language)
{
return view('admin.website-setting.languages.edit', compact('language'));
}
public function update(Request $request, Language $language)
{
$request->validate([
'status' => 'required',
'name' => 'required|string',
'icon' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
$language->update($request->except('icon') + [
'icon' => $request->icon ? $this->upload($request, 'icon', $language->icon) : $language->icon,
]);
return response()->json([
'message' => __('Language updated successfully'),
'redirect' => route('admin.languages.index')
]);
}
public function destroy(Language $language)
{
if (file_exists($language->icon)) {
Storage::delete($language->icon);
}
$language->delete();
return response()->json([
'message' => __('Language deleted successfully'),
'redirect' => route('admin.languages.index')
]);
}
public function status(Request $request, $id)
{
$language = Language::findOrFail($id);
$language->update(['status' => $request->status]);
return response()->json(['message' => 'Language ']);
}
public function deleteAll(Request $request)
{
$languages = Language::whereIn('id', $request->ids)->get();
foreach ($languages as $language) {
if (file_exists($language->icon)) {
Storage::delete($language->icon);
}
}
$languages->each->delete();
return response()->json([
'message' => __('Selected Language deleted successfully'),
'redirect' => route('admin.languages.index')
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Message;
use Illuminate\Http\Request;
class AcnooMessageController extends Controller
{
public function __construct()
{
$this->middleware('permission:messages-read')->only('index');
$this->middleware('permission:messages-delete')->only('destroy');
}
public function index(Request $request)
{
$messages = Message::when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%')
->orWhere('phone', 'like', '%' . $request->search . '%')
->orWhere('email', 'like', '%' . $request->search . '%')
->orWhere('company_name', 'like', '%' . $request->search . '%')
->orWhere('message', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.messages.datas', compact('messages'))->render()
]);
}
return view('admin.messages.index', compact('messages'));
}
public function destroy(Message $message)
{
$message->delete();
return response()->json([
'message' => __('Message deleted successfully'),
'redirect' => route('admin.messages.index')
]);
}
public function deleteAll(Request $request)
{
Message::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Mesages deleted successfully'),
'redirect' => route('admin.messages.index')
]);
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\PlanSubscribe;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AcnooPlanController extends Controller
{
public function __construct()
{
$this->middleware('permission:plans-create')->only('create', 'store');
$this->middleware('permission:plans-read')->only('index');
$this->middleware('permission:plans-update')->only('edit', 'update', 'status');
$this->middleware('permission:plans-delete')->only('destroy', 'deleteAll');
}
public function index(Request $request)
{
$search = $request->input('search');
$plans = Plan::when($request->search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%')
->orWhere('duration', 'like', '%' . $search . '%')
->orWhere('subscriptionPrice', 'like', '%' . $search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.plans.datas', compact('plans'))->render()
]);
}
return view('admin.plans.index', compact('plans'));
}
public function create()
{
return view('admin.plans.create');
}
public function store(Request $request)
{
$request->validate([
'duration' => 'required|string',
'offerPrice' => 'nullable|numeric|min:0|max:9999999999999',
'subscriptionName' => 'required|string|max:255|unique:plans,subscriptionName',
'subscriptionPrice' => 'required|numeric|min:0|max:9999999999999',
'addon_domain_limit' => 'nullable|integer|min:0',
'subdomain_limit' => 'nullable|integer|min:0',
]);
Plan::create($request->except(['offerPrice', 'status', 'allow_multibranch']) + [
'offerPrice' => $request->offerPrice ?? NULL,
'status' => $request->status ? 1 : 0,
'allow_multibranch' => $request->allow_multibranch ? 1 : 0,
]);
return response()->json([
'message' => __('Subscription Plan created successfully'),
'redirect' => route('admin.plans.index')
]);
}
public function edit(Plan $plan)
{
return view('admin.plans.edit', compact('plan'));
}
public function update(Request $request, Plan $plan)
{
$request->validate([
'duration' => 'required|string',
'offerPrice' => 'nullable|numeric|min:0|max:9999999999999',
'subscriptionPrice' => 'required|numeric|min:0|max:9999999999999',
'subscriptionName' => 'required|string|max:255|unique:plans,subscriptionName,' . $plan->id,
'addon_domain_limit' => 'nullable|integer|min:0',
'subdomain_limit' => 'nullable|integer|min:0',
]);
if ($plan->subscriptionName == 'Free' && ($plan->subscriptionName != $request->subscriptionName || $plan->subscriptionPrice != $request->subscriptionPrice || $plan->offerPrice != $request->offerPrice)) {
return response()->json([
'message' => __('You can not change the package name & price of free plan.'),
], 406);
}
DB::beginTransaction();
try {
$plan->update($request->except(['offerPrice', 'status', 'allow_multibranch']) + [
'offerPrice' => $request->offerPrice ?? NULL,
'status' => $request->status ? 1 : 0,
'allow_multibranch' => $request->allow_multibranch ? 1 : 0,
]);
if ($request->allow_existing_subscriber){
$updateData = [
'allow_multibranch' => $request->allow_multibranch ? 1 : 0,
'addon_domain_limit' => $request->addon_domain_limit ?? 0,
'subdomain_limit' => $request->subdomain_limit ?? 0,
];
PlanSubscribe::where('plan_id', $plan->id)->update($updateData);
}
DB::commit();
return response()->json([
'message' => __('Subscription Plan updated successfully'),
'redirect' => route('admin.plans.index')
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Something went wrong.',
'error' => $e->getMessage()
], 500);
}
}
public function status(Request $request, $id)
{
$plan = Plan::findOrFail($id);
if ($plan->subscriptionName == 'Free') {
return response()->json([
'message' => __('You can not change the status for free plan.'),
], 406);
}
$plan->update(['status' => $request->status]);
return response()->json(['message' => 'Plan']);
}
public function destroy($id)
{
$plan = Plan::findOrFail($id);
if ($plan->subscriptionName == 'Free') {
return response()->json([
'message' => __('You can not delete free plan.'),
], 406);
}
$plan->delete();
return response()->json([
'message' => __('Subscription Plan deleted successfully'),
'redirect' => route('admin.plans.index')
]);
}
public function deleteAll(Request $request)
{
Plan::whereIn('id', $request->ids)->where('subscriptionName', '!=', 'Free')->delete();
return response()->json([
'message' => __('Subscription plan deleted successfully'),
'redirect' => route('admin.plans.index')
]);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class AcnooPrivacyPloicyController extends Controller
{
public function index()
{
$privacy_policy = Option::where('key', 'privacy-policy')->first();
return view('admin.settings.privacy-policy.index', compact('privacy_policy'));
}
public function store(Request $request)
{
$request->validate([
'privacy_title' => 'required|string',
'description_one' => 'required|string',
'description_two' => 'required|string',
]);
Option::updateOrCreate(
['key' => 'privacy-policy'],
['value' => [
'privacy_title' => $request->privacy_title,
'description_one' => $request->description_one,
'description_two' => $request->description_two
]]
);
Cache::forget('privacy-policy');
return response()->json(__('Privacy And Policy updated successfully.'));
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class AcnooSettingsManagerController extends Controller
{
public function index()
{
$otp = Option::where('key', 'email-varification')->first();
$domain = Option::where('key', 'domain-setting')->first();
return view('admin.manage-settings.index', compact('otp', 'domain'));
}
public function store(Request $request)
{
$request->validate([
'otp_status' => 'required|string|max:100|in:on,off',
'otp_expiration_time' => 'nullable|string|max:100',
'otp_duration_type' => 'nullable|string|max:100|in:minute,second',
]);
$otpExpiration = $request->otp_status === 'on' ? $request->otp_expiration_time : null;
$otpDurationType = $request->otp_status === 'on' ? $request->otp_duration_type : null;
Option::updateOrCreate(
['key' => 'email-varification'],
['value' => [
'otp_status' => $request->otp_status,
'otp_expiration_time' => $otpExpiration,
'otp_duration_type' => $otpDurationType,
]]
);
Cache::forget('email-varification');
return response()->json(__('Otp setting updated successfully.'));
}
public function domain(Request $request)
{
$request->validate([
'ssl_required' => 'required|string|max:100|in:on,off',
'automatic_approve' => 'required|string|max:100|in:on,off',
]);
Option::updateOrCreate(
['key' => 'domain-setting'],
['value' => [
'ssl_required' => $request->ssl_required,
'automatic_approve' => $request->automatic_approve,
]]
);
Cache::forget('domain-setting');
return response()->json(__('Domain setting updated successfully.'));
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\PlanSubscribe;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Modules\AffiliateAddon\App\Models\Affiliate;
use Modules\AffiliateAddon\App\Models\AffiliateTransaction;
class AcnooSubscriptionController extends Controller
{
public function index(Request $request)
{
$search = $request->input('search');
$subscribers = PlanSubscribe::with(['plan:id,subscriptionName', 'business:id,companyName,business_category_id,pictureUrl', 'business.category:id,name', 'gateway:id,name'])
->when($request->search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('duration', 'like', '%' . $search . '%')
->orWhereHas('plan', function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%');
})
->orWhereHas('gateway', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
})
->orWhereHas('business', function ($q) use ($search) {
$q->where('companyName', 'like', '%' . $search . '%')
->orWhereHas('category', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
});
});
});
})
->latest()
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.subscribe-order.datas', compact('subscribers'))->render()
]);
}
return view('admin.subscribe-order.index', compact('subscribers'));
}
public function reject(Request $request, string $id)
{
$request->validate([
'notes' => 'required|string|max:255',
]);
$reject = PlanSubscribe::findOrFail($id);
if ($reject) {
$reject->update([
'payment_status' => 'reject',
'notes' => $request->notes,
]);
return response()->json([
'message' => 'Status Unpaid',
'redirect' => route('admin.subscription-orders.index'),
]);
} else {
return response()->json(['message' => 'request not found'], 404);
}
}
public function paid(Request $request, string $id)
{
$request->validate([
'notes' => 'required|string|max:255',
]);
DB::beginTransaction();
try {
$subscribe = PlanSubscribe::findOrFail($id);
$existingNotes = $subscribe->notes ?? [];
$updatedNotes = array_merge($existingNotes, ['reason' => $request->notes]);
$subscribe->update($request->except('notes') + [
'payment_status' => 'paid',
'notes' => $updatedNotes,
]);
$subscribe->business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($subscribe->plan->duration),
]);
$business = $subscribe->business;
$plan = $subscribe->plan;
if (moduleCheck('AffiliateAddon') && $business->affiliator_id) {
$affiliateUser = User::find($business->affiliator_id);
if ($affiliateUser && $plan->affiliate_commission > 0) {
$commission = ($plan->subscriptionPrice * $plan->affiliate_commission) / 100;
Affiliate::where('user_id', $affiliateUser->id)->increment('balance', $commission);
AffiliateTransaction::create([
'user_id' => $affiliateUser->id,
'business_id' => $business->id,
'trx' => strtoupper(str()->random(10)),
'type' => 'credit',
'amount' => $commission,
'title' => 'Commission from Order #' . $subscribe->id,
'reference_id' => $subscribe->id,
'reference_type' => 'PlanSubscribe',
'note' => 'User purchased via your referral link',
]);
}
}
DB::commit();
Cache::forget('plan-data-'. $subscribe->business_id);
return response()->json([
'message' => 'Status Paid',
'redirect' => route('admin.subscription-orders.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => 'request not found'], 500);
}
}
public function getInvoice($invoice_id)
{
$subscriber = PlanSubscribe::with(['plan:id,subscriptionName', 'business:id,companyName,business_category_id,phoneNumber,address', 'business.category:id,name', 'gateway:id,name'])->findOrFail($invoice_id);
return view('admin.subscribe-order.invoice', compact('subscriber'));
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class AcnooTermConditionController extends Controller
{
public function index()
{
$term_condition = Option::where('key', 'term-condition')->first();
return view('admin.settings.term-condition.index', compact('term_condition'));
}
public function store(Request $request)
{
$request->validate([
'term_title' => 'required|string',
'description_one' => 'required|string',
'description_two' => 'required|string',
]);
Option::updateOrCreate(
['key' => 'term-condition'],
['value' => [
'term_title' => $request->term_title,
'description_one' => $request->description_one,
'description_two' => $request->description_two
]]
);
Cache::forget('term-condition');
return response()->json(__('Term And Condition updated successfully.'));
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Testimonial;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooTestimonialController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('permission:testimonials-read')->only('index');
$this->middleware('permission:testimonials-create')->only('create', 'store');
$this->middleware('permission:testimonials-update')->only('edit', 'update','status');
$this->middleware('permission:testimonials-delete')->only('destroy','deleteAll');
}
public function index(Request $request)
{
$testimonials = Testimonial::when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('text', 'like', '%' . $request->search . '%')
->orWhere('client_name', 'like', '%' . $request->search . '%')
->orWhere('work_at', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.testimonials.datas', compact('testimonials'))->render()
]);
}
return view('admin.testimonials.index', compact('testimonials'));
}
public function create()
{
return view('admin.testimonials.create');
}
public function store(Request $request)
{
$request->validate([
'work_at' => 'nullable|string',
'text' => 'nullable',
'star' => 'nullable',
'client_name' => 'required|string',
'client_image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
Testimonial::create($request->except('client_image') + [
'client_image' => $request->client_image ? $this->upload($request, 'client_image') : NULL
]);
return response()->json([
'message' => __('Testimonial created successfully'),
'redirect' => route('admin.testimonials.index')
]);
}
public function edit(Testimonial $testimonial)
{
return view('admin.testimonials.edit', compact('testimonial'));
}
public function update(Request $request, Testimonial $testimonial)
{
$request->validate([
'work_at' => 'nullable|string',
'text' => 'nullable|string',
'star' => 'nullable|integer',
'client_name' => 'required|string',
'client_image' => 'nullable|image|mimes:jpeg,png,jpg,gif',
]);
$testimonial->update($request->except('client_image') + [
'client_image' => $request->client_image ? $this->upload($request, 'client_image', $testimonial->client_image) : $testimonial->client_image,
]);
return response()->json([
'message' => __('Testimonial updated successfully'),
'redirect' => route('admin.testimonials.index')
]);
}
public function destroy(Testimonial $testimonial)
{
if (file_exists($testimonial->client_image)) {
Storage::delete($testimonial->client_image);
}
$testimonial->delete();
return response()->json([
'message' => __('Testimonial Deleted successfully'),
'redirect' => route('admin.testimonials.index')
]);
}
public function deleteAll(Request $request)
{
$testimonials = Testimonial::whereIn('id', $request->ids)->get();
foreach ($testimonials as $testimonial) {
if (file_exists($testimonial->image)) {
Storage::delete($testimonial->image);
}
}
$testimonials->each->delete();
return response()->json([
'message' => __('Selected Testimonial deleted successfully'),
'redirect' => route('admin.testimonials.index')
]);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Option;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class AcnooWebSettingController extends Controller
{
use HasUploader;
public function index()
{
$page_data = get_option('manage-pages');
return view('admin.website-setting.manage-pages', compact('page_data'));
}
public function update(Request $request, $key)
{
$option = Option::where('key', 'manage-pages')->first();
Option::updateOrCreate(
['key' => 'manage-pages'],
['value' => [
'headings' => $request->except('_token', '_method','slider_image','scanner_image','card_icons','compatible_image','firebase_image','contact_us_icon','footer_socials_icons','footer_scanner_image','footer_apple_app_image','footer_google_app_image','watch_image','about_image','evanto_logo', 'slider_bg_img'),
'slider_image' => $request->slider_image ? $this->upload($request, 'slider_image') : $option->value['slider_image'] ?? null,
'scanner_image' => $request->scanner_image ? $this->upload($request, 'scanner_image') : $option->value['scanner_image'] ?? null,
'watch_image' => $request->watch_image ? $this->upload($request, 'watch_image') : $option->value['watch_image'] ?? null,
'compatible_image' => $request->compatible_image ? $this->upload($request, 'compatible_image') : $option->value['compatible_image'] ?? null,
'firebase_image' => $request->firebase_image ? $this->upload($request, 'firebase_image') : $option->value['firebase_image'] ?? null,
'contact_us_icon' => $request->contact_us_icon ? $this->upload($request, 'contact_us_icon') : $option->value['contact_us_icon'] ?? null,
'footer_scanner_image' => $request->footer_scanner_image ? $this->upload($request, 'footer_scanner_image') : $option->value['footer_scanner_image'] ?? null,
'footer_apple_app_image' => $request->footer_apple_app_image ? $this->upload($request, 'footer_apple_app_image') : $option->value['footer_apple_app_image'] ?? null,
'footer_google_app_image' => $request->footer_google_app_image ? $this->upload($request, 'footer_google_app_image') : $option->value['footer_google_app_image'] ?? null,
'about_image' => $request->about_image ? $this->upload($request, 'about_image') : $option->value['about_image'] ?? null,
'evanto_logo' => $request->evanto_logo ? $this->upload($request, 'evanto_logo') : $option->value['evanto_logo'] ?? null,
'slider_bg_img' => $request->slider_bg_img ? $this->upload($request, 'slider_bg_img') : $option->value['slider_bg_img'] ?? null,
'card_icons' => $request->card_icons ? $this->multipleUpload($request, 'card_icons') : $option->value['card_icons'] ?? null,
'footer_socials_icons' => $request->footer_socials_icons ? $this->multipleUpload($request, 'footer_socials_icons') : $option->value['footer_socials_icons'] ?? null,
]
]);
Cache::forget('manage-pages');
return response()->json(__('Pages updated successfully.'));
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\AffiliateAddon\App\Models\AffiliateTransaction;
use Illuminate\Support\Facades\DB;
class AcnooWithdrawRequestController extends Controller
{
public function index()
{
$withdraws = AffiliateTransaction::with('user:id,name')->latest()->paginate(20);
return view('admin.affiliate-modules.withdraws.index', compact('withdraws'));
}
public function acnooFilter(Request $request)
{
$search = $request->input('search');
$withdraws = AffiliateTransaction::with('user:id,name')->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('amount', 'like', '%' . $search . '%')
->orWhereHas('user', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('admin.affiliate-modules.withdraws.datas', compact('withdraws'))->render()
]);
}
return redirect(url()->previous());
}
public function reject(Request $request, string $id)
{
$request->validate([
'note' => 'required|string|max:255',
]);
$reject = AffiliateTransaction::findOrFail($id);
if ($reject) {
$reject->update([
'status' => 'unpaid',
'note' => $request->note,
]);
return response()->json([
'message' => 'Status Unpaid',
'redirect' => route('admin.affiliate-withdrawals.index'),
]);
} else {
return response()->json(['message' => 'request not found'], 404);
}
}
public function paid(Request $request, string $id)
{
$request->validate([
'note' => 'nullable|string|max:255',
]);
$paid = AffiliateTransaction::findOrFail($id);
if ($paid) {
$paid->update([
'status' => 'paid',
'note' => $request->note,
]);
return response()->json([
'message' => 'Status Paid',
'redirect' => route('admin.affiliate-withdrawals.index'),
]);
} else {
return response()->json(['message' => 'request not found'], 404);
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Http\Controllers\Admin;
use ZipArchive;
use Illuminate\Http\Request;
use Nwidart\Modules\Facades\Module;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Artisan;
class AddonController extends Controller
{
public function index()
{
return view('admin.addons.index');
}
public function store(Request $request)
{
$request->validate([
'purchase_code' => 'required',
'file' => 'required|file|mimes:zip',
]);
try {
$is_valid = true;
$module_name = pathinfo($request->file('file')->getClientOriginalName(), PATHINFO_FILENAME);
if ($is_valid || $request->purchase_code == 'acnoo_license') {
$uploadedFile = $request->file('file');
// Open the ZIP file using ZipArchive without saving it first
$zip = new ZipArchive;
$tempFilePath = $uploadedFile->getRealPath();
// Check if the ZIP file can be opened
if ($zip->open($tempFilePath) === TRUE) {
// Define the path to the Modules folder
$destinationPath = base_path('Modules');
// Ensure the Modules folder exists
if (!File::exists($destinationPath)) {
File::makeDirectory($destinationPath, 0755, true);
}
$zip->extractTo($destinationPath);
$zip->close();
// Specify the path to the module's migrations folder
$moduleMigrationsPath = base_path('Modules/' . $module_name . '/Database/migrations');
// Check if the migrations folder exists and contains migration files
if (File::exists($moduleMigrationsPath)) {
// Dynamically add the module's migrations path to the migrator
$migrator = app('migrator');
$migrator->path($moduleMigrationsPath);
// Run the migrations from the module's migration path
Artisan::call('migrate', ['--force' => true]);
}
if (!moduleCheck($module_name)) {
Artisan::call('module:seed', ['module' => $module_name]);
}
// Update the modules_statuses.json file
$filePath = base_path('modules_statuses.json');
// Read the contents of the JSON file
$jsonContents = File::get($filePath);
// Decode the JSON into an associative array
$data = json_decode($jsonContents, true);
// Add the new key-value pair to the array
$data[$module_name] = true;
// Encode the array back into JSON format
$newJsonContents = json_encode($data, JSON_PRETTY_PRINT);
// Write the updated contents back to the file
File::put($filePath, $newJsonContents);
Artisan::call('cache:clear');
Artisan::call('config:clear');
Artisan::call('route:clear');
Artisan::call('view:clear');
return response()->json([
'message' => 'Addon installed successfully.',
'redirect' => route('admin.addons.index'),
]);
} else {
return response()->json('Failed to open ZIP file', 406);
}
} else {
return response()->json(['message' => __('Invalid purchase code.')], 406);
}
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()]);
}
}
public function show($module)
{
$module = Module::findOrFail($module);
if ($module->isEnabled()) {
$module->disable();
} else {
$module->enable();
}
return response()->json([
'message' => 'Addon'
]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Plan;
use App\Models\User;
use App\Models\Business;
use App\Models\PlanSubscribe;
use App\Models\BusinessCategory;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class DashboardController extends Controller
{
public function index()
{
$businesses = Business::with('enrolled_plan:id,plan_id', 'enrolled_plan.plan:id,subscriptionName', 'category:id,name')->latest()->take(5)->get();
return view('admin.dashboard.index', compact('businesses'));
}
public function getDashboardData()
{
$data['total_businesses'] = Business::count();
$data['expired_businesses'] = Business::where('will_expire', '<', now())->count();
$data['plan_subscribes'] = PlanSubscribe::count();
$data['business_categories'] = BusinessCategory::count();
$data['total_plans'] = Plan::count();
$data['total_staffs'] = User::whereNotIn('role', ['superadmin', 'staff', 'shop-owner'])->count();
return response()->json($data);
}
public function yearlySubscriptions()
{
$subscriptions = PlanSubscribe::whereYear('created_at', request('year') ?? date('Y'))
->selectRaw('MONTHNAME(created_at) as month, SUM(price) as total_amount')
->groupBy('month')
->get();
return response()->json($subscriptions);
}
public function plansOverview()
{
$subscription = PlanSubscribe::with('plan:id,subscriptionName')->select('plan_id', DB::raw('COUNT(*) as plan_count'))
->groupBy('plan_id')
->orderByDesc('plan_count')
->limit(4)
->get();
return response()->json($subscription);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\Currency;
use App\Models\Gateway;
use Illuminate\Http\Request;
class GatewayController extends Controller
{
use HasUploader;
/**
* Display a listing of the resource.
*/
public function index()
{
$gateways = Gateway::all();
$currencies = Currency::latest()->get();
return view('admin.gateways.index', compact('gateways', 'currencies'));
}
/**
* Store a newly created resource in storage.
*/
public function update(Request $request, $id)
{
Gateway::findOrFail($id)->update($request->except('image') + [
'image' => $request->hasFile('image') ? $this->upload($request, 'image') : NULL
]);
return response()->json('Gateway updated successfully');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Notification;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class NotificationController extends Controller
{
public function __construct()
{
$this->middleware('permission:notifications-read')->only('mtIndex');
}
public function mtIndex()
{
$notifications = auth()->user()->notifications()
->whereDate('created_at', today())
->latest()
->get();
return view('admin.notifications.index', compact('notifications'));
}
public function acnooFilter(Request $request)
{
$notifications = Notification::whereDate('created_at', today())->latest()->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('admin.notifications.datas', compact('notifications'))->render()
]);
}
return redirect(url()->previous());
}
public function mtView($id)
{
$notify = Notification::find($id);
if ($notify) {
$notify->read_at = now();
$notify->save();
return redirect($notify->data['url'] ?? '/');
}
return back()->with('error', __('Premission denied.'));
}
public function mtReadAll()
{
auth()->user()->unreadNotifications()->update(['read_at' => now()]);
return back();
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Spatie\Permission\Models\Role;
class PermissionController extends Controller
{
public function __construct()
{
$this->middleware('permission:permissions-read')->only('index','search');
$this->middleware('permission:permissions-create')->only('store');
}
public function index(Request $request)
{
$users = User::whereNotIn('role', ['shop-owner', 'staff', 'superadmin'])->get();
$roles = Role::where('name', '!=', 'superadmin')->get();
return view('admin.permissions.index', compact('roles', 'users'));
}
public function store(Request $request)
{
$request->validate([
'user' => ['required', 'exists:users,id'],
'roles' => ['required', 'exists:roles,id']
]);
$user = User::findOrFail($request->input('user'));
$user->roles()->sync($request->input('roles'));
return response()->json([
'message' => __('Role permissions assigned successfully.'),
'redirect' => route('admin.permissions.index')
]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class ProfileController extends Controller
{
use HasUploader;
public function index()
{
$user = User::where('id',Auth::user()->id)->first();
return view('admin.profile.index',compact('user'));
}
public function update(Request $request, string $id)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
'image' => 'nullable|image',
]);
$user = User::findOrFail($id);
if( $request->password || $request->current_password){
if(Hash::check($request->current_password,$user->password)){
$request->validate([
'current_password' => 'required|string',
'password' => 'required|string|confirmed',
]);
}
else{
return response()->json(__('Current Password does not match with old password'),404);
}
}
$user->update($request->except('image', 'password') + [
'image' => $request->image ? $this->upload($request, 'image', $user->image) : $user->image,
] + ($request->password ? ['password' => Hash::make($request->password)] : [])
);
return response()->json([
'message' => __('Profile updated successfully'),
'redirect' => route('admin.profiles.index')
]);
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RoleController extends Controller
{
private $role;
public function __construct()
{
$this->middleware('permission:roles-create')->only('create', 'store');
$this->middleware('permission:roles-read')->only('index', 'show');
$this->middleware('permission:roles-update')->only('edit', 'update');
$this->middleware('permission:roles-delete')->only('destroy');
}
public function index()
{
$roles = Role::with('users')->whereNotIn('name', ['Super Admin', 'superadmin', 'super admin'])->withCount('users')->get();
return view('admin.roles.index', compact('roles'));
}
public function create()
{
$groups = [];
foreach (Permission::all() as $index => $permission) {
$groups[ucwords(str($permission->name)->remove(['-create','-read','-update','-delete'])->replace('-', ' '))][] = $permission;
}
return view('admin.roles.create', compact('groups'));
}
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'unique:roles,name'],
'permissions' => ['required','array'],
'permissions.*' => ['required', 'exists:permissions,id']
]);
DB::transaction(function ()use ($request){
$this->role = Role::create([
'name' => $request->input('name')
]);
$this->role->permissions()->sync($request->input('permissions'));
});
return response()->json([
'message' => __('Role created successfully'),
'redirect' => route('admin.roles.index')
]);
}
public function edit(Role $role)
{
abort_if(in_array($role->name, ['Super Admin', 'superadmin', 'super admin']), 403, __("You are not allowed to mess with Super Admin"));
$role->load('permissions');
$groups = [];
foreach (Permission::all() as $index => $permission) {
$groups[ucwords(str($permission->name)->remove(['-', 'create','read','update','delete','status','list','folder']))][] = $permission;
}
return view('admin.roles.edit', compact('role', 'groups'));
}
public function update(Request $request, Role $role)
{
abort_if(in_array($role->name, ['Super Admin', 'superadmin', 'super admin']), 403, __("You are not allowed to mess with Super Admin"));
$request->validate([
'name' => ['required', 'string', Rule::unique('roles')->ignore($role->id)],
'permissions' => ['required','array'],
'permissions.*' => ['required', 'exists:permissions,id']
]);
$role->update([
'name' => $request->input('name')
]);
$role->permissions()->sync($request->input('permissions'));
return response()->json([
'message' => __('Role update successfully'),
'redirect' => route('admin.roles.index')
]);
}
public function destroy(Role $role)
{
abort_if(in_array($role->name, ['Super Admin', 'superadmin', 'super admin']), 403, __("You are not allowed to mess with Super Admin"));
$role->delete();
return response()->json([
'message' => __('Role deleted successfully'),
'redirect' => route('admin.roles.index')
]);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\Option;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class SettingController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('permission:settings-read')->only('index');
$this->middleware('permission:settings-update')->only('update');
}
public function index()
{
$general = Option::where('key', 'general')->first();
$languages = json_decode(file_get_contents(base_path('lang/langlist.json')), true);
return view('admin.settings.general', compact('general', 'languages'));
}
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|string|max:100',
'logo' => 'nullable|image',
'favicon' => 'nullable|image',
'common_header_logo' => 'nullable|image',
'footer_logo' => 'nullable|image',
'admin_logo' => 'nullable|image',
'login_page_logo' => 'nullable|image',
'login_page_image' => 'nullable|image',
'app_link' => 'nullable|url',
'whatsapp_number' => 'nullable|string|max:20',
]);
$general = Option::findOrFail($id);
Cache::forget($general->key);
$general->update([
'value' => $request->except('_token', '_method', 'logo', 'favicon', 'common_header_logo', 'footer_logo', 'admin_logo', 'login_page_logo', 'login_page_image') + [
'logo' => $request->logo ? $this->upload($request, 'logo', $general->value['logo'] ?? null) : ($general->value['logo'] ?? null),
'favicon' => $request->favicon ? $this->upload($request, 'favicon', $general->value['favicon'] ?? null) : ($general->value['favicon'] ?? null),
'common_header_logo' => $request->common_header_logo ? $this->upload($request, 'common_header_logo', $general->value['common_header_logo'] ?? null) : ($general->value['common_header_logo'] ?? null),
'footer_logo' => $request->footer_logo ? $this->upload($request, 'footer_logo', $general->value['footer_logo'] ?? null) : ($general->value['footer_logo'] ?? null),
'admin_logo' => $request->admin_logo ? $this->upload($request, 'admin_logo', $general->value['admin_logo'] ?? null) : ($general->value['admin_logo'] ?? null),
'login_page_logo' => $request->login_page_logo ? $this->upload($request, 'login_page_logo', $general->value['login_page_logo'] ?? null) : ($general->value['login_page_logo'] ?? null),
'login_page_image' => $request->login_page_image ? $this->upload($request, 'login_page_image', $general->value['login_page_image'] ?? null) : ($general->value['login_page_image'] ?? null),
]
]);
return response()->json([
'message' => __('General Setting updated successfully'),
'redirect' => route('admin.settings.index')
]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use App\Http\Controllers\Controller;
use Illuminate\Support\Carbon;
class SubscriptionReport extends Controller
{
public function index(Request $request)
{
$subscribeQuery = PlanSubscribe::with(['plan:id,subscriptionName', 'business:id,companyName,business_category_id,pictureUrl', 'business.category:id,name', 'gateway:id,name'])->whereDate('created_at', Carbon::today()->format('Y-m-d'));
// Date filter
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
switch ($request->custom_days) {
case 'yesterday':
$startDate = $endDate = Carbon::yesterday()->format('Y-m-d');
break;
case 'last_seven_days':
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
break;
case 'last_thirty_days':
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
break;
case 'current_month':
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
break;
case 'last_month':
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
break;
case 'current_year':
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
break;
case 'custom_date':
if ($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');
}
break;
}
$subscribeQuery->whereDate('created_at', '>=', $startDate)
->whereDate('created_at', '<=', $endDate);
// Search filter
if ($request->filled('search')) {
$search = $request->search;
$subscribeQuery->where(function ($query) use ($search) {
$query->where('duration', 'like', '%' . $search . '%')
->orWhereHas('plan', function ($q) use ($search) {
$q->where('subscriptionName', 'like', '%' . $search . '%');
})
->orWhereHas('gateway', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
})
->orWhereHas('business', function ($q) use ($search) {
$q->where('companyName', 'like', '%' . $search . '%')
->orWhereHas('category', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
});
});
});
}
$perPage = $request->input('per_page', 20);
$subscribers = $subscribeQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.subscribers.datas', compact('subscribers'))->render()
]);
}
return view('admin.subscribers.index', compact('subscribers'));
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Artisan;
class SystemSettingController extends Controller
{
public function __construct()
{
$this->middleware('permission:settings-read')->only('index');
$this->middleware('permission:settings-update')->only('update');
}
public function index()
{
return view('admin.settings.system');
}
public function store(Request $request)
{
$request->validate([
'SESSION_LIFETIME' => 'required|integer',
'service_account_credentials' => 'mimes:json,txt|max:100',
]);
if ($request->hasFile('service_account_credentials')) {
$file = $request->file('service_account_credentials');
$name = 'service-account-credentials.json';
$path = 'uploads/';
$file->move($path, $name);
}
$APP_NAME = '"' . $request->APP_NAME . '"';
$txt = "APP_NAME=" . $APP_NAME . "
APP_ENV=local
APP_KEY=" . env('APP_KEY') . "
APP_DEBUG=" . $request->APP_DEBUG . "
APP_URL=" . url('/') . "
SITE_KEY=" . env('SITE_KEY') . "
AUTHORIZED_KEY=" . env('AUTHORIZED_KEY') . "
CONTENT_EDITOR=" . $request->CONTENT_EDITOR . "
ANALYTICS_VIEW_ID=" . $request->ANALYTICS_VIEW_ID . "
GA_MEASUREMENT_ID=" . $request->GA_MEASUREMENT_ID . "
FORCE_USER_TO_PURCHASE_PLAN=" . $request->FORCE_USER_TO_PURCHASE_PLAN . "
UNSUBSCRIBE_AFTER_DAYS=" . $request->UNSUBSCRIBE_AFTER_DAYS . "
DB_CONNECTION=" . env("DB_CONNECTION") . "
DB_HOST=" . env("DB_HOST") . "
DB_PORT=" . env("DB_PORT") . "
DB_DATABASE=" . env("DB_DATABASE") . "
DB_USERNAME=" . env("DB_USERNAME") . "
DB_PASSWORD=" . env("DB_PASSWORD") . "
QUEUE_MAIL=" . $request->QUEUE_MAIL . "
" . $request->MAIL_DRIVER_TYPE . "=" . $request->MAIL_DRIVER . "
MAIL_DRIVER_TYPE=" . $request->MAIL_DRIVER_TYPE . "
MAIL_HOST=" . $request->MAIL_HOST . "
MAIL_PORT=" . $request->MAIL_PORT . "
MAIL_USERNAME=" . $request->MAIL_USERNAME . "
MAIL_PASSWORD=" . $request->MAIL_PASSWORD . "
MAIL_ENCRYPTION=" . $request->MAIL_ENCRYPTION . "
MAIL_FROM_ADDRESS=" . $request->MAIL_FROM_ADDRESS . "
MAIL_TO=" . $request->MAIL_TO . "
MAIL_FROM_NAME='" . $request->MAIL_FROM_NAME . "'
MAILCHIMP_DRIVER=" . $request->MAILCHIMP_DRIVER . "
MAILCHIMP_APIKEY=" . $request->MAILCHIMP_APIKEY . "
MAILCHIMP_LIST_ID=" . $request->MAILCHIMP_LIST_ID . "
NOCAPTCHA_SECRET=" . $request->NOCAPTCHA_SECRET . "
NOCAPTCHA_SITEKEY=" . $request->NOCAPTCHA_SITEKEY . "
BROADCAST_DRIVER=pusher
CACHE_DRIVER=" . $request->CACHE_DRIVER . "
QUEUE_CONNECTION=database
SESSION_DRIVER=" . $request->SESSION_DRIVER . "
SESSION_LIFETIME=" . $request->SESSION_LIFETIME . "
PUSHER_APP_ID=" . $request->PUSHER_APP_ID . "
PUSHER_APP_KEY=" . $request->PUSHER_APP_KEY . "
PUSHER_APP_SECRET=" . $request->PUSHER_APP_SECRET . "
PUSHER_APP_CLUSTER=" . $request->PUSHER_APP_CLUSTER . "
PUSHER_SCHEME=https
MIX_PUSHER_APP_KEY=" . '${PUSHER_APP_KEY}' . "
MIX_PUSHER_APP_CLUSTER=" . '${PUSHER_APP_CLUSTER}' . "
REDIS_HOST=" . $request->REDIS_HOST . "
REDIS_PORT=" . $request->REDIS_PORT . "
REDIS_URL=" . $request->REDIS_URL . "
REDIS_PASSWORD=" . $request->REDIS_PASSWORD . "
MEMCACHED_HOST=" . $request->MEMCACHED_HOST . "
MEMCACHED_PORT=" . $request->MEMCACHED_PORT . "
MEMCACHED_PERSISTENT_ID=" . $request->MEMCACHED_PERSISTENT_ID . "
MEMCACHED_USERNAME=" . $request->MEMCACHED_USERNAME . "
MEMCACHED_PASSWORD=" . $request->MEMCACHED_PASSWORD . "
AWS_ACCESS_KEY_ID=" . $request->AWS_ACCESS_KEY_ID . "
AWS_SECRET_ACCESS_KEY=" . $request->AWS_SECRET_ACCESS_KEY . "
AWS_DEFAULT_REGION=" . $request->AWS_DEFAULT_REGION . "
AWS_BUCKET=" . $request->AWS_BUCKET . "
WAS_ACCESS_KEY_ID=" . $request->WAS_ACCESS_KEY_ID . "
WAS_SECRET_ACCESS_KEY=" . $request->WAS_SECRET_ACCESS_KEY . "
WAS_DEFAULT_REGION=" . $request->WAS_DEFAULT_REGION . "
WAS_BUCKET=" . $request->WAS_BUCKET . "
WAS_ENDPOINT=" . $request->WAS_ENDPOINT . "
DISCUSS_COMMENT_KEY=" . $request->DISCUSS_COMMENT_KEY . "
LOG_CHANNEL=stack
LOG_LEVEL=debug
CACHE_LIFETIME=" . $request->CACHE_LIFETIME . "
TIMEZONE=" . $request->TIMEZONE . "
DEFAULT_LANG=" . $request->DEFAULT_LANG . "
DISCUSS_COMMENT_KEY=" . $request->DISCUSS_COMMENT_KEY . "
FILESYSTEM_DISK=" . $request->FILESYSTEM_DISK . "
VITE_PUSHER_APP_KEY=" . '${PUSHER_APP_KEY}' . "
VITE_PUSHER_HOST=" . '${PUSHER_HOST}' . "
VITE_PUSHER_PORT=" . '${PUSHER_PORT}' . "
VITE_PUSHER_SCHEME=" . '${PUSHER_SCHEME}' . "
VITE_PUSHER_APP_CLUSTER=" . '${PUSHER_APP_CLUSTER}' . "
APILAYER_API_KEY=" . $request->APILAYER_API_KEY . "
GOOGLE_CLIENT_ID=" . $request->GOOGLE_CLIENT_ID . "
GOOGLE_CLIENT_SECRET=" . $request->GOOGLE_CLIENT_SECRET . "
GOOGLE_REDIRECT_URI=" . url('login/google/callback') . "
TWITTER_CLIENT_ID=" . $request->TWITTER_CLIENT_ID . "
TWITTER_CLIENT_SECRET=" . $request->TWITTER_CLIENT_SECRET . "
TWITTER_REDIRECT_URI=" . url('login/twitter/callback') . "
WKHTMLTOPDF_BINARY=" . $request->WKHTMLTOPDF_BINARY . "
WKHTMLTOIMAGE_BINARY=" . $request->WKHTMLTOIMAGE_BINARY . "
WKHTMLTOPDF_TIMEOUT=" . $request->WKHTMLTOPDF_TIMEOUT . "
";
File::put(base_path('.env'), $txt);
Artisan::call('config:clear');
Artisan::call('cache:clear');
return response()->json([
'message' => __('Settings updated successfully.'),
'redirect' => route('admin.system-settings.index'),
]);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\User;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
class UserController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('permission:users-create')->only('create', 'store');
$this->middleware('permission:users-read')->only('index', 'show');
$this->middleware('permission:users-update')->only('edit', 'update');
$this->middleware('permission:users-delete')->only('destroy');
}
public function index(Request $request)
{
$users = User::whereNotIn('role', ['superadmin', 'staff', 'shop-owner'])->when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%')
->orWhere('email', 'like', '%' . $request->search . '%')
->orWhere('role', 'like', '%' . $request->search . '%')
->orWhere('phone', 'like', '%' . $request->search . '%');
});
})->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('admin.users.datas', compact('users'))->render()
]);
}
return view('admin.users.index', compact('users'));
}
public function create()
{
$roles = Role::where('name', '!=', 'superadmin')->latest()->get();
return view('admin.users.create', compact('roles'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'role' => 'required|string',
'phone' => 'nullable|string',
'email' => 'required|email|unique:users',
'password' => 'required|string|confirmed',
'image' => 'nullable|image',
]);
$user = User::create($request->except('image', 'password') + [
'image' => $request->image ? $this->upload($request, 'image') : null,
'password' => Hash::make($request->password),
]);
$role = Role::where('name', $request->role)->first();
$user->roles()->sync($role->id);
sendNotification($user->id, route('admin.users.index', ['users' => $request->role]), __(ucfirst($request->role) . ' has been created.'), 'action', null, null, true);
return response()->json([
'message' => __(ucfirst($request->role) . ' created successfully'),
'redirect' => route('admin.users.index', ['users' => $request->role])
]);
}
public function edit(User $user)
{
if ($user->role == 'superadmin') {
abort(403);
}
$roles = Role::latest()->get();
return view('admin.users.edit', compact('user', 'roles'));
}
public function update(Request $request, User $user)
{
if ($user->role == 'superadmin') {
return response()->json(__('You can not update a superadmin.'), 400);
}
$request->validate([
'role' => 'required|string',
'phone' => 'nullable|string',
'country' => 'nullable|string',
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $user->id,
'password' => 'nullable|string|confirmed',
'image' => 'nullable|image',
]);
$role = Role::where('name', $request->role)->first();
$user->roles()->sync($role->id);
$user->update($request->except('image', 'password') + [
'image' => $request->image ? $this->upload($request, 'image', $user->image) : $user->image,
'password' => $request->password ? Hash::make($request->password) : $user->password,
]);
return response()->json([
'message' => __('Staff updated successfully'),
'redirect' => route('admin.users.index')
]);
}
public function destroy(User $user)
{
if ($user->role == 'superadmin') {
return response()->json(__('You can not delete a superadmin.'), 400);
}
if (file_exists($user->image)) {
Storage::delete($user->image);
}
$user->delete();
return response()->json([
'message' => __('Staff deleted successfully'),
'redirect' => route('admin.users.index')
]);
}
public function deleteAll(Request $request)
{
User::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected Staff deleted successfully'),
'redirect' => route('admin.users.index')
]);
}
}

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.'),
]);
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Nwidart\Modules\Facades\Module;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use App\Http\Requests\Auth\LoginRequest;
use App\Models\Branch;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create()
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
$remember = $request->filled('remember') ? 1 : 0;
$redirect_url = url('/');
$user = auth()->user();
if ($user->role == 'shop-owner' || $user->role == 'staff') {
$module = Module::find('Business');
if ($module) {
if ($module->isEnabled()) {
$business = $user->business;
$branch = Branch::find($user->branch_id);
if ($business && !$business->status) {
Auth::logout();
return response()->json([
'message' => 'Your business is inactive. Please contact your administrator.',
'redirect' => route('login')
], 406);
}
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.',
'redirect' => route('login'),
], 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.',
'redirect' => route('login'),
], 406);
}
} elseif (!$branch && $user->branch_id) {
Auth::logout();
return response()->json([
'message' => 'Your current branch has been deleted, Please contact with manager.',
'redirect' => route('login'),
], 406);
}
$redirect_url = route('business.dashboard.index');
} else {
Auth::logout();
return response()->json([
'message' => 'Web addon is not active.',
'redirect' => route('login'),
], 406);
}
} else {
Auth::logout();
return response()->json([
'message' => 'Web addon is not installed.',
'redirect' => route('login'),
], 406);
}
} else if ($user->role == 'affiliator') {
$module = Module::find('AffiliateAddon');
if ($module) {
if ($module->isEnabled()) {
$redirect_url = route('business.dashboard.index');
} else {
Auth::logout();
return response()->json([
'message' => 'Affiliate addon is not active.',
'redirect' => route('login'),
], 406);
}
} else {
Auth::logout();
return response()->json([
'message' => 'affiliate addon is not installed.',
'redirect' => route('login'),
], 406);
}
} else {
$redirect_url = route('admin.dashboard.index');
}
return response()->json([
'message' => __('Logged In Successfully'),
'remember' => $remember,
'redirect' => $redirect_url,
]);
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\View\View;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Password;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Validation\ValidationException;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', 'string'],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
protected function sendResetLinkResponse(Request $request, $response)
{
return $request->wantsJson()
? new JsonResponse(['message' => trans($response), 'redirect' => route('login')])
: back()->with('status', trans($response));
}
protected function sendResetLinkFailedResponse(Request $request, $response)
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): View
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = Password::sendResetLink(
$request->only('email')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
protected function sendResetLinkResponse(Request $request, $response)
{
return $request->wantsJson()
? new JsonResponse(trans($response))
: back()->with('status', trans($response));
}
protected function sendResetLinkFailedResponse(Request $request, $response)
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
}

View File

@@ -0,0 +1,299 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\Plan;
use App\Models\User;
use App\Models\Business;
use App\Models\Currency;
use App\Mail\WelcomeMail;
use App\Models\UserCurrency;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use App\Mail\RegistrationMail;
use App\Models\BusinessCategory;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Cookie;
use Modules\AffiliateAddon\App\Models\Affiliate;
class RegisteredUserController extends Controller
{
public function store(Request $request)
{
$request->validate([
'email' => 'required|email|max:255',
'password' => 'required|max:25|min:4',
'plan_id' => 'required|exists:plans,id',
]);
DB::beginTransaction();
try {
$user = User::where('email', $request->email)->first();
if (($user ?? false) && $user->is_verified) {
return response()->json([
'message' => 'This email is already exists.',
], 406);
}
if (!$user) {
$user = User::create([
'email' => $request->email,
'password' => Hash::make($request->password),
]);
}
$otpSettings = get_option('email-varification');
$verify_email = ($otpSettings['otp_status'] ?? 'off') === 'on';
session()->put('user_id', $user->id);
session()->put('plan_id', $request->plan_id);
if ($verify_email) {
// Generate OTP
$code = random_int(100000, 999999);
$visibility_time = $this->getOtpTimeInSeconds();
$expire = now()->addSeconds($visibility_time);
$user->update([
'remember_token' => $code,
'email_verified_at' => $expire,
]);
// Send welcome mail
if (env('MAIL_USERNAME')) {
if (env('QUEUE_MAIL')) {
Mail::to($request->email)->queue(new RegistrationMail($code));
} else {
Mail::to($request->email)->send(new RegistrationMail($code));
}
} else {
return response()->json([
'message' => 'Mail service is not configured. Please contact your administrator.',
], 406);
}
} else {
$business_categories = BusinessCategory::where('status', 1)->latest()->get();
}
DB::commit();
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.',
'openModal' => true,
'email' => $request->email,
'business_categories' => $business_categories ?? [],
'otp_expiration' => $verify_email ? now()->diffInSeconds($expire) : false,
]);
} catch (\Throwable $th) {
DB::rollBack();
return response()->json([
'message' => 'Something went wrong. Please contact the admin.',
], 403);
}
}
public function otpResend(Request $request)
{
$request->validate([
'email' => 'required|email|exists:users,email',
]);
$code = random_int(100000, 999999);
$visibility_time = $this->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.',
'otp_expiration' => now()->diffInSeconds($expire),
]);
}
public function otpSubmit(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(['message' => __('User not found.')], 400);
}
if ($user->remember_token == $request->otp) {
if ($user->email_verified_at > now()) {
$business_categories = BusinessCategory::where('status', 1)->latest()->get();
$user->update([
'is_verified' => 1,
'remember_token' => NULL,
'email_verified_at' => now(),
]);
return response()->json([
'message' => 'The otp has been verified successfully!',
'business_categories' => $business_categories
]);
} else {
return response()->json(['message' => __('The verification otp has been expired.')], 400);
}
} else {
return response()->json(['message' => __('Invalid otp.')], 400);
}
}
public function businessSetup(Request $request)
{
$request->validate([
'address' => 'nullable|max:250',
'companyName' => 'required|max:250',
'shopOpeningBalance' => 'nullable|numeric',
'business_category_id' => 'required|exists:business_categories,id',
'phoneNumber' => 'required|max:20',
]);
DB::beginTransaction();
try {
$plan = Plan::find(session('plan_id'));
$user = User::find(session('user_id'));
if (!$user) {
return response()->json([
'message' => 'Something went wrong. Please try again.',
'redirect' => route('home'),
], 403);
}
if (moduleCheck('AffiliateAddon')) {
$refId = null;
$refCode = Cookie::get('ref_code');
if ($refCode) {
$affiliator = Affiliate::where('ref_code', $refCode)->first();
if ($affiliator) {
$refId = $affiliator->user_id;
}
}
$data['affiliator_id'] = $refId;
}
$data = [
'address' => $request->address,
'companyName' => $request->companyName,
'phoneNumber' => $request->phoneNumber,
'shopOpeningBalance' => $request->shopOpeningBalance ?? 0,
'business_category_id' => $request->business_category_id,
];
$business = Business::create($data);
$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,
]);
$user->update([
'business_id' => $business->id,
]);
if (moduleCheck('Business')) {
Auth::login($user);
$message = 'Your business setup is completed.';
$redirect_url = route('business.dashboard.index');
} else {
$success_modal = true;
$message = 'Your business setup is completed. Please download the apk for manage your business.';
}
if ($plan) {
$plan_price = $plan->offerPrice == 0 && $plan->offerPrice != null ? $plan->offerPrice : $plan->subscriptionPrice;
if ($plan_price <= 0) {
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'business_id' => $business->id,
'duration' => $plan->duration,
'allow_multibranch' => $plan->allow_multibranch
]);
$business->update([
'plan_subscribe_id' => $subscribe->id,
'subscriptionDate' => $plan ? now() : null,
'will_expire' => $plan ? now()->addDays($plan->duration) : null,
]);
} else {
$message = 'Your business setup is completed. Now you are going to the payment page.';
$redirect_url = route('payments-gateways.index', ['plan_id' => $plan->id, 'business_id' => $business->id]);
}
}
DB::commit();
return response()->json([
'message' => $message,
'redirect' => $redirect_url ?? false,
'success_modal' => $success_modal ?? false,
]);
} catch (\Throwable $th) {
DB::rollBack();
return response()->json([
'message' => 'Something went wrong. Please contact the admin.',
], 403);
}
}
public function getOtpTimeInSeconds()
{
$otpSettings = get_option('email-varification');
$time = $otpSettings['otp_expiration_time'] ?? null;
$durationType = $otpSettings['otp_duration_type'] ?? 'minute';
$defaultFromEnv = env('OTP_VISIBILITY_TIME', 3);
// Use default if DB value is null
if (empty($time)) {
return $defaultFromEnv * 60;
}
// Convert minutes to seconds
return $durationType == 'minute' ? $time * 60 : $time;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BkashTokenizePaymentController extends Controller
{
public function __invoke(Request $request)
{
return response()->json(['message' => 'Bkash placeholder']);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Models\Blog;
use App\Models\Option;
use App\Models\Comment;
use Illuminate\Http\Request;
class BlogController extends Controller
{
public function index()
{
$page_data = get_option('manage-pages');
$recent_blogs = Blog::with('user:id,name')->whereStatus(1)->latest()->take(3)->get();
$blogs = Blog::with('user:id,name')->whereStatus(1)->take(10)->get();
$general = Option::where('key','general')->first();
return view('web.blog.index', compact('recent_blogs', 'blogs', 'page_data','general'));
}
public function show(string $slug)
{
$page_data = get_option('manage-pages');
$blog = Blog::where('slug', $slug)->firstOrFail();
$recent_blogs = Blog::with('user:id,name')->select('id', 'title', 'slug', 'image', 'user_id', 'created_at', 'updated_at')->whereStatus(1)->latest()->limit(3)->get();
$comments = Comment::with('blog:id')->whereStatus(1)->where('blog_id', $blog->id)->latest()->limit(3)->get();
$general = Option::where('key','general')->first();
return view('web.blog.show', compact('page_data','blog', 'recent_blogs','comments','general'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
'comment' => 'required|string|max:255',
]);
Comment::create($request->except('blog_id') + [
'blog_id' => $request->blog_id,
]);
return response()->json([
'message' => __('Your Comment Submitted successfully'),
'redirect' => route('blogs.show', $request->blog_slug)
]);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Models\Option;
use App\Models\Message;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index()
{
$page_data = get_option('manage-pages');
$general = Option::where('key','general')->first();
return view('web.contact.index',compact('page_data','general'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'phone' => 'required|string|max:20',
'email' => 'required|email|max:255',
'company_name' => 'nullable|string|max:255',
'message' => 'required|string',
]);
Message::create($request->all());
return response()->json([
'message' => __('Your Message Submitted successfully'),
'redirect' => route('contact.index')
]);
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
protected $message =[
//response success ..0
[
'message'=>'Response Successfully!',
'alert-type'=>'success'
],
//created ..1
[
'message'=>'Data Created Successfully!',
'alert-type'=>'success'
],
// data found ..2
[
'message'=>'Get Data Successfully!',
'alert-type'=>'success'
],
// data updated ..3
[
'message'=>'Data Updated Successfully!',
'alert-type'=>'success'
],
// validator ..4
[
'message'=>'Sorry!!Data validation Failed!',
'alert-type'=>'error'
],
// bad request ..5
[
'message'=>'Sorry!! You\'ar Send Bad Request!',
'alert-type'=>'error'
],
// data not found ..6
[
'message'=>'Sorry!! Data Not Found!',
'alert-type'=>'error'
],
// data already exist ..7
[
'message'=>'Sorry!! Data Already Exists!',
'alert-type'=>'error'
],
// unauthorized request ..8
[
'message'=>'Sorry!! Unauthenticated Request!',
'alert-type'=>'error'
],
// time request error ..9
[
'message'=>'Sorry!! Try After Few Time!',
'alert-type'=>'error'
],
// check balance error ..10
[
'message'=>'Sorry!! Unavailable Balance!',
'alert-type'=>'error'
],
// requested accepted ..11
[
'message'=>'Data Request Accepted!',
'alert-type'=>'success'
],
// requested rejected ..12
[
'message'=>'Data Request Rejected!',
'alert-type'=>'success'
],
// requested not modify ..13
[
'message'=>'Data Not Modified!',
'alert-type'=>'warning'
],
// requested deleted ..14
[
'message'=>'Data deleted!',
'alert-type'=>'success'
],
// requested deleted ..15
[
'message'=>'Bad Request!',
'alert-type'=>'success'
]
];
protected function setSuccess($message)
{
session()->flash('type', 'success');
session()->flash('message', $message);
}
protected function setError($message)
{
session()->flash('type', 'warning');
session()->flash('message', $message);
}
protected function setDelete($message)
{
session()->flash('type', 'warning');
session()->flash('message', $message);
}
protected function respondWithValidatorError($message = '', $data = [], $code = 422)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithSuccess($message = '', $data = [], $code = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithCreated($message = '', $data = [], $code = 201)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithFound($message = '', $data = [], $code = 302)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithUpdate($message = '', $data = [], $code = 426)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithAccept($message = '', $data = [], $code = 202)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithReject($message = '', $data = [], $code = 406)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithNotModify($message = '', $data = [], $code = 304)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithError($message = '', $data = [], $code = 400)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithErrorNotFound($message = '', $data = [], $code = 404)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithAlreadyExists($message = '', $data = [], $code = 208)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithUnauthorized($message = '', $data = [], $code = 401)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithNotAcceptable($message = '', $data = [], $code = 406)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithUnavilableBalance($message = '', $data = [], $code = 451)
{
return response()->json([
'error' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithDelete($message = '', $data = [], $code = 204)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithBadRequest($message = '', $data = [], $code = 400)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
protected function respondWithNotExtanded($message = '', $data = [], $code = 510)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data
],$code);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Controllers;
use App\Models\Option;
use Illuminate\Http\Request;
class DataDeletionController extends Controller
{
public function index()
{
$page_data = get_option('manage-pages');
$general = Option::where('key','general')->first();
return view('web.data-deletion.index', compact('page_data', 'general'));
}
}

View File

@@ -0,0 +1,291 @@
<?php
namespace App\Http\Controllers;
use App\Models\Plan;
use App\Models\User;
use App\Models\Gateway;
use App\Models\Business;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Modules\AffiliateAddon\App\Models\Affiliate;
use Modules\AffiliateAddon\App\Models\AffiliateTransaction;
class PaymentController extends Controller
{
use HasUploader;
/**
* Display a listing of the resource.
*/
public function index($plan_id, $business_id)
{
$plan = Plan::findOrFail($plan_id);
session()->put('business_id', $business_id);
session()->put('platform', request('platform') ?? 'web');
$business = Business::findOrFail($business_id);
$plan_data = plan_data($business_id);
$has_free_subscriptions = Plan::where('subscriptionPrice', '<=', 0)->orWhere('offerPrice', '<=', 0)->first();
if ($plan->subscriptionPrice <= 0 && $has_free_subscriptions) {
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('Sorry, you cannot subscribe to a free plan again.'));
}
if (
session('platform') == 'web' && ($plan_data ?? false) && ($plan_data->plan_id == $plan->id && $business->will_expire > now()->addDays(7)) ||
($business->will_expire >= now()->addDays($plan->duration))
) {
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('You have already subscribed to this plan. Please try again after - ' . formatted_date($business->will_expire)));
}
$gateways = Gateway::with('currency:id,code,rate,symbol,position')->where('status', 1)->get();
return view('payments.index', compact('gateways', 'plan'));
}
/**
* Store a newly created resource in storage.
*/
public function payment(Request $request, $plan_id, $gateway_id)
{
$request->validate([
'phone' => 'max:15|min:5',
]);
$plan = Plan::findOrFail($plan_id);
$gateway = Gateway::findOrFail($gateway_id);
$business = Business::findOrFail(session("business_id"));
if ($gateway->is_manual) {
$request->validate([
'attachment' => 'required|mimes:jpg,jpeg,png,pdf|file',
]);
DB::beginTransaction();
try {
$attachment = $request->attachment ? $this->upload($request, 'attachment') : NULL;
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'gateway_id' => $gateway_id,
'payment_status' => 'unpaid',
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0,
'notes' => [
'manual_data' => $request->manual_data,
'attachment' => $attachment
],
]);
sendNotification($subscribe->id, route('admin.subscription-reports.index', ['id' => $subscribe->id]), __('New subscription purchased requested.'));
DB::commit();
return redirect(route('order.status', ['status' => 'success']))->with('message', __('New subscription purchased requested.'));
} catch (\Exception $e) {
DB::rollback();
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('Something went wrong!'));
}
}
$amount = $plan->offerPrice ?? $plan->subscriptionPrice;
if ($gateway->namespace == 'App\Library\SslCommerz') {
Session::put('fund_callback.success_url', '/ssl-commerz//payment/success');
Session::put('fund_callback.cancel_url', '/ssl-commerz//payment/failed');
} else {
Session::put('fund_callback.success_url', '/payment/success');
Session::put('fund_callback.cancel_url', '/payment/failed');
}
$currency = $gateway->currency;
$user = User::where('business_id', $business->id)->first();
$payment_data['country'] = $request->country ?? 'ZMW';
$payment_data['currency'] = $currency->code ?? 'USD';
$payment_data['email'] = $user->email;
$payment_data['name'] = $business->companyName;
$payment_data['phone'] = $request->phone ?? $business->phoneNumber;
$payment_data['billName'] = __('Make plan purchase payment');
$payment_data['amount'] = $amount;
$payment_data['mode'] = $gateway->mode;
$payment_data['charge'] = $gateway->charge ?? 0;
$payment_data['pay_amount'] = round(convert_money($amount, $currency) + $gateway->charge);
$payment_data['gateway_id'] = $gateway->id;
$payment_data['payment_type'] = 'plan_payment';
$payment_data['request_from'] = 'merchant';
$payment_data['plan_id'] = $plan_id;
$payment_data['business_id'] = $business->id;
$payment_data['platform'] = session('platform');
foreach ($gateway->data ?? [] as $key => $info) {
$payment_data[$key] = $info;
}
session()->put('gateway_id', $gateway->id);
session()->put('plan', $plan);
$redirect = $gateway->namespace::make_payment($payment_data);
return $redirect;
}
public function success()
{
DB::beginTransaction();
try {
$plan = session('plan');
$gateway_id = session('gateway_id');
if (!$plan && !session('plan_id')) {
return redirect(route('order.status', ['status' => 'failed']))->with('error', __('Transaction failed, Please try again.'));
}
if (session('plan_id') && !$plan) {
$plan = Plan::findOrFail(session('plan_id'));
}
$business = Business::findOrFail(session("business_id"));
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'payment_status' => 'paid',
'gateway_id' => $gateway_id,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0,
]);
$business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($plan->duration),
]);
if (moduleCheck('AffiliateAddon') && $business->affiliator_id) {
$affiliateUser = User::find($business->affiliator_id);
if ($affiliateUser && $plan->affiliate_commission > 0) {
$commission = ($plan->subscriptionPrice * $plan->affiliate_commission) / 100;
Affiliate::where('user_id', $affiliateUser->id)->increment('balance', $commission);
AffiliateTransaction::create([
'user_id' => $affiliateUser->id,
'business_id' => $business->id,
'trx' => strtoupper(str()->random(10)),
'type' => 'credit',
'amount' => $commission,
'title' => 'Commission from Order #' . $subscribe->id,
'reference_id' => $subscribe->id,
'reference_type' => 'PlanSubscribe',
'note' => 'User purchased via your referral link',
]);
}
}
session()->forget('plan');
session()->forget('plan_id');
session()->forget('gateway_id');
session()->forget('business_id');
Cache::forget('plan-data-' . $business->id);
DB::commit();
return redirect(route('order.status', ['status' => 'success']))->with('message', __('New subscription order successfully.'));
} catch (\Exception $e) {
DB::rollback();
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('Something went wrong!'));
}
}
public function failed()
{
if (session('platform') == 'web') {
$payment_msg = session('payment_msg');
session()->forget('payment_msg');
return redirect(route('business.subscriptions.index'))->with('error', $payment_msg ?? __('Transaction failed, Please try again.'));
}
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('Something went wrong!'));
}
public function sslCommerzSuccess(Request $request)
{
DB::beginTransaction();
try {
if (!$request->value_a || !$request->value_b || !$request->value_c) {
return redirect(route('order.status', ['status' => 'failed']))->with('error', __('Transaction failed, Please try again.'));
}
$plan = session('plan');
$gateway_id = session('gateway_id');
if (!$plan) {
return redirect(route('order.status', ['status' => 'failed']))->with('error', __('Transaction failed, Please try again.'));
}
$business = Business::findOrFail(session("business_id"));
$subscribe = PlanSubscribe::create([
'plan_id' => $plan->id,
'payment_status' => 'paid',
'gateway_id' => $gateway_id,
'duration' => $plan->duration,
'business_id' => $business->id,
'price' => $plan->subscriptionPrice,
'allow_multibranch' => $plan->allow_multibranch,
'addon_domain_limit' => $plan->addon_domain_limit ?? 0,
'subdomain_limit' => $plan->subdomain_limit ?? 0,
]);
$business->update([
'subscriptionDate' => now(),
'plan_subscribe_id' => $subscribe->id,
'will_expire' => now()->addDays($plan->duration),
]);
session()->forget('gateway_id');
session()->forget('plan');
Cache::forget('plan-data-' . $business->id);
DB::commit();
return redirect(route('order.status', ['status' => 'success']))->with('message', __('New subscription order successfully.'));
} catch (\Exception $e) {
DB::rollback();
return redirect(route('order.status', ['status' => 'failed']))->with('message', __('Something went wrong!'));
}
}
public function sslCommerzFailed()
{
return redirect(route('order.status', ['status' => 'failed']))->with('error', __('Transaction failed, Please try again.'));
}
public function orderStatus()
{
if (session('platform') == 'web') {
if (moduleCheck('Business')) {
if (session('user_id')) {
session()->forget('user_id');
return redirect(route('business.dashboard.index'))->with('message', request('message') ?? __('Subscription order completed.'));
} else {
return redirect(route('business.subscriptions.index'))->with('message', request('message') ?? __('New subscription order successfully.'));
}
} else {
return redirect(route('home', ['success_modal' => 1]))->with('message', request('message') ?? __('Subscription order successfully, Please download the apk for manage your business.'));
}
}
session()->forget('platform');
return request('status');
}
}

Some files were not shown because too many files have changed in this diff Show More