fix
This commit is contained in:
parent
0898eac1e3
commit
512cfb485a
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
|
@ -0,0 +1,58 @@
|
|||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
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}"
|
|
@ -0,0 +1,11 @@
|
|||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
|
@ -0,0 +1,18 @@
|
|||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
|
@ -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('inspire')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
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 MessageCreated implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new Channel('salon');
|
||||
|
||||
// return [
|
||||
// new PrivateChannel('channel-name'),
|
||||
// ];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A 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) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class UnitConverter
|
||||
{
|
||||
// Base units untuk konversi
|
||||
const BASE_UNITS = [
|
||||
'weight' => 'g', // gram sebagai base unit untuk berat
|
||||
'volume' => 'ml', // mililiter sebagai base unit untuk volume
|
||||
'quantity' => 'pcs' // pieces sebagai base unit untuk jumlah
|
||||
];
|
||||
|
||||
// Mapping unit ke kategori
|
||||
const UNIT_CATEGORIES = [
|
||||
'g' => 'weight',
|
||||
'kg' => 'weight',
|
||||
'ml' => 'volume',
|
||||
'l' => 'volume',
|
||||
'pcs' => 'quantity'
|
||||
];
|
||||
|
||||
// Konversi rate ke base unit
|
||||
const CONVERSION_RATES = [
|
||||
'g' => 1, // 1 gram = 1 gram (base)
|
||||
'kg' => 1000, // 1 kg = 1000 gram
|
||||
'ml' => 1, // 1 ml = 1 ml (base)
|
||||
'l' => 1000, // 1 liter = 1000 ml
|
||||
'pcs' => 1 // 1 pcs = 1 pcs (base)
|
||||
];
|
||||
|
||||
const UNIT_LABELS = [
|
||||
'g' => 'Gram',
|
||||
'kg' => 'Kilogram',
|
||||
'ml' => 'Mililiter',
|
||||
'l' => 'Liter',
|
||||
'pcs' => 'Pieces'
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert value from one unit to another
|
||||
*/
|
||||
public static function convert($value, $fromUnit, $toUnit)
|
||||
{
|
||||
// Check if units are in the same category
|
||||
if (!self::areUnitsCompatible($fromUnit, $toUnit)) {
|
||||
throw new \InvalidArgumentException("Cannot convert between {$fromUnit} and {$toUnit} - different categories");
|
||||
}
|
||||
|
||||
// If same unit, return as is
|
||||
if ($fromUnit === $toUnit) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Convert to base unit first, then to target unit
|
||||
$baseValue = $value * self::CONVERSION_RATES[$fromUnit];
|
||||
$convertedValue = $baseValue / self::CONVERSION_RATES[$toUnit];
|
||||
|
||||
return round($convertedValue, 3); // Round to 3 decimal places
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert value to base unit
|
||||
*/
|
||||
public static function toBaseUnit($value, $unit)
|
||||
{
|
||||
return $value * self::CONVERSION_RATES[$unit];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert value from base unit
|
||||
*/
|
||||
public static function fromBaseUnit($value, $unit)
|
||||
{
|
||||
return $value / self::CONVERSION_RATES[$unit];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two units are compatible (same category)
|
||||
*/
|
||||
public static function areUnitsCompatible($unit1, $unit2)
|
||||
{
|
||||
if (!isset(self::UNIT_CATEGORIES[$unit1]) || !isset(self::UNIT_CATEGORIES[$unit2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::UNIT_CATEGORIES[$unit1] === self::UNIT_CATEGORIES[$unit2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unit category
|
||||
*/
|
||||
public static function getUnitCategory($unit)
|
||||
{
|
||||
return self::UNIT_CATEGORIES[$unit] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base unit for a category
|
||||
*/
|
||||
public static function getBaseUnit($category)
|
||||
{
|
||||
return self::BASE_UNITS[$category] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available units
|
||||
*/
|
||||
public static function getAllUnits()
|
||||
{
|
||||
return array_keys(self::CONVERSION_RATES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get units by category
|
||||
*/
|
||||
public static function getUnitsByCategory($category)
|
||||
{
|
||||
$units = [];
|
||||
foreach (self::UNIT_CATEGORIES as $unit => $cat) {
|
||||
if ($cat === $category) {
|
||||
$units[] = $unit;
|
||||
}
|
||||
}
|
||||
return $units;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unit label
|
||||
*/
|
||||
public static function getUnitLabel($unit)
|
||||
{
|
||||
return self::UNIT_LABELS[$unit] ?? $unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format value with unit
|
||||
*/
|
||||
public static function formatValue($value, $unit)
|
||||
{
|
||||
return number_format($value, 3) . ' ' . self::getUnitLabel($unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recommended unit for display (convert small values to appropriate unit)
|
||||
*/
|
||||
public static function getRecommendedDisplayUnit($value, $currentUnit)
|
||||
{
|
||||
$category = self::getUnitCategory($currentUnit);
|
||||
|
||||
if ($category === 'weight') {
|
||||
// If value is >= 1000g, recommend kg
|
||||
if ($value >= 1000 && $currentUnit === 'g') {
|
||||
return 'kg';
|
||||
}
|
||||
// If value is < 1 kg, recommend g
|
||||
if ($value < 1 && $currentUnit === 'kg') {
|
||||
return 'g';
|
||||
}
|
||||
} elseif ($category === 'volume') {
|
||||
// If value is >= 1000ml, recommend l
|
||||
if ($value >= 1000 && $currentUnit === 'ml') {
|
||||
return 'l';
|
||||
}
|
||||
// If value is < 1 l, recommend ml
|
||||
if ($value < 1 && $currentUnit === 'l') {
|
||||
return 'ml';
|
||||
}
|
||||
}
|
||||
|
||||
return $currentUnit;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
protected $redirectTo = RouteServiceProvider::HOME;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest')->except('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a login request to the application.
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$this->validateLogin($request);
|
||||
|
||||
if ($this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
return $this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
if ($this->attemptLogin($request)) {
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
|
||||
$this->incrementLoginAttempts($request);
|
||||
|
||||
return $this->sendFailedLoginResponse($request);
|
||||
}
|
||||
|
||||
protected function validateLogin(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
$this->username() => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function authenticated(Request $request, $user)
|
||||
{
|
||||
// Update last login timestamp
|
||||
$user->update([
|
||||
'last_login' => Carbon::now()
|
||||
]);
|
||||
|
||||
// Check if user is soft deleted
|
||||
if ($user->deleted_at !== null) {
|
||||
Auth::logout();
|
||||
return redirect()->route('login')
|
||||
->withErrors(['username' => 'Akun ini tidak aktif.']);
|
||||
}
|
||||
|
||||
// Check if user has a valid role
|
||||
if (!$user->hasValidRole()) {
|
||||
Auth::logout();
|
||||
return redirect()->route('login')
|
||||
->withErrors(['username' => 'Role tidak valid.']);
|
||||
}
|
||||
|
||||
// Redirect based on role with specific messages
|
||||
switch ($user->roles) {
|
||||
case 'admin':
|
||||
return redirect()->route('indexDashboard')
|
||||
->with('success', 'Selamat datang, Admin!');
|
||||
case 'karyawan':
|
||||
return redirect()->route('indexDashboard')
|
||||
->with('success', 'Selamat datang, Karyawan!');
|
||||
default:
|
||||
Auth::logout();
|
||||
return redirect()->route('login')
|
||||
->withErrors(['username' => 'Role tidak valid.']);
|
||||
}
|
||||
}
|
||||
|
||||
public function username()
|
||||
{
|
||||
return 'username';
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use RealRashid\SweetAlert\Facades\Alert;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('auth/login');
|
||||
}
|
||||
|
||||
public function authenticate(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string|min:3|max:50|regex:/^[a-zA-Z0-9_]+$/',
|
||||
'password' => 'required|string|min:6',
|
||||
], [
|
||||
'username.required' => 'Username harus diisi',
|
||||
'username.min' => 'Username minimal 3 karakter',
|
||||
'username.max' => 'Username maksimal 50 karakter',
|
||||
'username.regex' => 'Username hanya boleh berisi huruf, angka, dan underscore',
|
||||
'password.required' => 'Password harus diisi',
|
||||
'password.min' => 'Password minimal 6 karakter',
|
||||
]);
|
||||
|
||||
$credentials = [
|
||||
'username' => $request->username,
|
||||
'password' => $request->password,
|
||||
];
|
||||
|
||||
if (Auth::attempt($credentials)) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user->roles) {
|
||||
Auth::logout();
|
||||
return back()->with('loginError', 'Akun tidak memiliki hak akses!');
|
||||
}
|
||||
|
||||
// Redirect sesuai role
|
||||
Alert::success('Success', 'Login Berhasil');
|
||||
|
||||
if ($user->roles === 'karyawan') {
|
||||
return redirect()->intended(route('produk.index'));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('indexDashboard'));
|
||||
}
|
||||
|
||||
return back()->with('loginError', 'Username atau password salah!');
|
||||
}
|
||||
|
||||
public function create(){
|
||||
// Hanya admin yang bisa mengakses halaman registrasi
|
||||
if (!Auth::check() || Auth::user()->roles !== 'admin') {
|
||||
Alert::error('Error', 'Anda tidak memiliki akses ke halaman ini!');
|
||||
return redirect()->route('indexDashboard');
|
||||
}
|
||||
return view('auth/registrasi');
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
// Validasi akses
|
||||
if (!Auth::check() || Auth::user()->roles !== 'admin') {
|
||||
Alert::error('Error', 'Anda tidak memiliki akses untuk membuat akun!');
|
||||
return redirect()->route('indexDashboard');
|
||||
}
|
||||
|
||||
$validated = $request->validate(
|
||||
[
|
||||
'email' => 'required|unique:users|email',
|
||||
'password' => 'required|string|min:6|confirmed',
|
||||
'password_confirmation' => 'required|string',
|
||||
'nama' => 'required|string|max:255',
|
||||
'no_telp' => 'required|string|max:15',
|
||||
'alamat' => 'required|string',
|
||||
],
|
||||
[
|
||||
'email.unique' => 'Email telah terdaftar.',
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||
'password.min' => 'Password minimal 6 karakter.',
|
||||
'nama.required' => 'Nama karyawan wajib diisi.',
|
||||
'no_telp.required' => 'Nomor telepon wajib diisi.',
|
||||
'alamat.required' => 'Alamat wajib diisi.',
|
||||
]
|
||||
);
|
||||
|
||||
$user = User::create([
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
'roles' => 'karyawan', // Set role default sebagai karyawan
|
||||
'nama' => $validated['nama'],
|
||||
'no_telp' => $validated['no_telp'],
|
||||
'alamat' => $validated['alamat'],
|
||||
]);
|
||||
|
||||
Alert::success('Success', 'Akun karyawan berhasil dibuat');
|
||||
return redirect()->route('indexDashboard');
|
||||
}
|
||||
|
||||
public function out()
|
||||
{
|
||||
Auth::logout();
|
||||
request()->session()->invalidate();
|
||||
request()->session()->regenerateToken();
|
||||
return redirect()->route('indexAuth');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?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;
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Admin;
|
||||
use App\Models\Karyawan;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\Super;
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\User;
|
||||
use App\Models\Produk;
|
||||
use App\Models\RawMaterial;
|
||||
use App\Models\ProductRecipe;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$myData = auth()->user();
|
||||
|
||||
if ($myData->roles === 'super') {
|
||||
$myData = Super::where('user_id', auth()->id())->first();
|
||||
|
||||
// Get statistics for super dashboard
|
||||
$totalUsers = User::whereIn('roles', ['admin', 'karyawan'])->count();
|
||||
$totalAdmins = User::where('roles', 'admin')->count();
|
||||
$totalKaryawan = User::where('roles', 'karyawan')->count();
|
||||
|
||||
// Get recent users
|
||||
$recentUsers = User::whereIn('roles', ['admin', 'karyawan'])
|
||||
->with(['admin', 'karyawan'])
|
||||
->latest()
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return view('pages.dashboard.dashboard', compact(
|
||||
'myData',
|
||||
'totalUsers',
|
||||
'totalAdmins',
|
||||
'totalKaryawan',
|
||||
'recentUsers'
|
||||
));
|
||||
} elseif ($myData->roles === 'admin') {
|
||||
// Get daily transactions count
|
||||
$dailyTransactions = Transaksi::whereDate('tanggal_transaksi', Carbon::today())->count();
|
||||
|
||||
// Get monthly revenue and expenses
|
||||
$monthlyRevenue = Transaksi::whereYear('tanggal_transaksi', Carbon::now()->year)
|
||||
->whereMonth('tanggal_transaksi', Carbon::now()->month)
|
||||
->sum('total_harga');
|
||||
|
||||
$monthlyExpenses = DB::table('raw_material_logs')
|
||||
->where('type', 'in')
|
||||
->whereYear('created_at', Carbon::now()->year)
|
||||
->whereMonth('created_at', Carbon::now()->month)
|
||||
->sum('subtotal');
|
||||
|
||||
// Get user statistics
|
||||
$userStats = [
|
||||
'total' => User::count(),
|
||||
'admin' => User::where('roles', 'admin')->count(),
|
||||
'kasir' => User::where('roles', 'karyawan')->count(),
|
||||
'active' => User::whereIn('roles', ['admin', 'karyawan'])->count()
|
||||
];
|
||||
|
||||
// Get product recipe statistics
|
||||
$productRecipeStats = [
|
||||
'total_products' => Produk::count(),
|
||||
'with_recipe' => Produk::has('recipes')->count(),
|
||||
'without_recipe' => Produk::doesntHave('recipes')->count()
|
||||
];
|
||||
|
||||
// Get low stock materials
|
||||
$lowStockMaterials = RawMaterial::select(
|
||||
'name',
|
||||
'stock as current_stock',
|
||||
'minimum_stock',
|
||||
'unit'
|
||||
)
|
||||
->whereColumn('stock', '<=', 'minimum_stock')
|
||||
->get();
|
||||
|
||||
// Get products with low materials
|
||||
$productsWithLowMaterials = DB::table('produks')
|
||||
->join('product_recipes', 'produks.id', '=', 'product_recipes.produk_id')
|
||||
->join('raw_materials', 'product_recipes.raw_material_id', '=', 'raw_materials.id')
|
||||
->select(
|
||||
'produks.nama_produk',
|
||||
'raw_materials.name as material_name',
|
||||
'raw_materials.stock as current_stock',
|
||||
'raw_materials.minimum_stock'
|
||||
)
|
||||
->whereColumn('raw_materials.stock', '<=', 'raw_materials.minimum_stock')
|
||||
->get();
|
||||
|
||||
// Get revenue data for the last 30 days
|
||||
$revenueData = $this->getRevenueData();
|
||||
|
||||
// Get summary data (produk terjual dan terpopuler)
|
||||
$summary = [
|
||||
'produk_terjual' => DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->whereMonth('transaksi.tanggal_transaksi', Carbon::now()->month)
|
||||
->whereYear('transaksi.tanggal_transaksi', Carbon::now()->year)
|
||||
->sum('transaksi_details.quantity'),
|
||||
'produk_terpopuler' => DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->join('produks', 'produks.id', '=', 'transaksi_details.produk_id')
|
||||
->whereMonth('transaksi.tanggal_transaksi', Carbon::now()->month)
|
||||
->whereYear('transaksi.tanggal_transaksi', Carbon::now()->year)
|
||||
->select(
|
||||
'produks.nama_produk',
|
||||
DB::raw('SUM(transaksi_details.quantity) as total_terjual')
|
||||
)
|
||||
->groupBy('produks.nama_produk')
|
||||
->orderBy('total_terjual', 'desc')
|
||||
->first()
|
||||
];
|
||||
|
||||
return view('pages.dashboard.dashboard', compact(
|
||||
'myData',
|
||||
'dailyTransactions',
|
||||
'monthlyRevenue',
|
||||
'monthlyExpenses',
|
||||
'userStats',
|
||||
'productRecipeStats',
|
||||
'lowStockMaterials',
|
||||
'productsWithLowMaterials',
|
||||
'revenueData',
|
||||
'summary'
|
||||
));
|
||||
} elseif ($myData->roles === 'karyawan') {
|
||||
// Get daily transactions count for karyawan
|
||||
$dailyTransactions = Transaksi::whereDate('tanggal_transaksi', Carbon::today())->count();
|
||||
|
||||
// Get product recipe statistics
|
||||
$productRecipeStats = [
|
||||
'total_products' => Produk::count(),
|
||||
'with_recipe' => Produk::has('recipes')->count(),
|
||||
'without_recipe' => Produk::doesntHave('recipes')->count()
|
||||
];
|
||||
|
||||
// Get low stock materials
|
||||
$lowStockMaterials = RawMaterial::select(
|
||||
'name',
|
||||
'stock as current_stock',
|
||||
'minimum_stock',
|
||||
'unit'
|
||||
)
|
||||
->whereColumn('stock', '<=', 'minimum_stock')
|
||||
->get();
|
||||
|
||||
// Get products with low materials
|
||||
$productsWithLowMaterials = DB::table('produks')
|
||||
->join('product_recipes', 'produks.id', '=', 'product_recipes.produk_id')
|
||||
->join('raw_materials', 'product_recipes.raw_material_id', '=', 'raw_materials.id')
|
||||
->select(
|
||||
'produks.nama_produk',
|
||||
'raw_materials.name as material_name',
|
||||
'raw_materials.stock as current_stock',
|
||||
'raw_materials.minimum_stock'
|
||||
)
|
||||
->whereColumn('raw_materials.stock', '<=', 'raw_materials.minimum_stock')
|
||||
->get();
|
||||
|
||||
return view('pages.dashboard.dashboard', compact(
|
||||
'myData',
|
||||
'dailyTransactions',
|
||||
'productRecipeStats',
|
||||
'lowStockMaterials',
|
||||
'productsWithLowMaterials'
|
||||
));
|
||||
} elseif ($myData->roles === 'pelanggan') {
|
||||
// Get customer specific data
|
||||
$countReservasi = Transaksi::where('pelanggan_id', $myData->id)
|
||||
->whereDate('tanggal_transaksi', '>=', Carbon::today())
|
||||
->count();
|
||||
|
||||
return view('pages.dashboard.dashboard', compact('myData', 'countReservasi'));
|
||||
} elseif ($myData->roles === 'super') {
|
||||
// Get all data for super admin
|
||||
$totalTransaksi = Transaksi::count();
|
||||
$totalUser = User::count();
|
||||
$totalProduk = Produk::count();
|
||||
$totalMaterial = RawMaterial::count();
|
||||
|
||||
// Get daily, monthly, and yearly statistics
|
||||
$dailyStats = $this->getDailyStats();
|
||||
$monthlyStats = $this->getMonthlyStats();
|
||||
$yearlyStats = $this->getYearlyStats();
|
||||
|
||||
return view('pages.dashboard.dashboard', compact(
|
||||
'myData',
|
||||
'totalTransaksi',
|
||||
'totalUser',
|
||||
'totalProduk',
|
||||
'totalMaterial',
|
||||
'dailyStats',
|
||||
'monthlyStats',
|
||||
'yearlyStats'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRevenueData()
|
||||
{
|
||||
$dates = [];
|
||||
$amounts = [];
|
||||
|
||||
for ($i = 29; $i >= 0; $i--) {
|
||||
$date = Carbon::now()->subDays($i);
|
||||
$amount = Transaksi::whereDate('tanggal_transaksi', $date)
|
||||
->sum('total_harga');
|
||||
|
||||
$dates[] = $date->format('d M');
|
||||
$amounts[] = $amount;
|
||||
}
|
||||
|
||||
return [
|
||||
'dates' => $dates,
|
||||
'amounts' => $amounts
|
||||
];
|
||||
}
|
||||
|
||||
private function getDailyStats()
|
||||
{
|
||||
// Implementasi pengambilan data statistik harian
|
||||
}
|
||||
|
||||
private function getMonthlyStats()
|
||||
{
|
||||
// Implementasi pengambilan data statistik bulanan
|
||||
}
|
||||
|
||||
private function getYearlyStats()
|
||||
{
|
||||
// Implementasi pengambilan data statistik tahunan
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use App\Models\Karyawan;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RealRashid\SweetAlert\Facades\Alert;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class KaryawanController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Mengambil data karyawan beserta data user
|
||||
$karyawans = Karyawan::with(['user' => function($query) {
|
||||
$query->withTrashed();
|
||||
}])->get();
|
||||
|
||||
// Menampilkan data karyawan di view
|
||||
return view('pages.manajemen.karyawan.index', compact('karyawans'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
// Menampilkan form untuk menambahkan karyawan
|
||||
return view('pages.manajemen.karyawan.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
// Validasi input data
|
||||
$validated = $request->validate([
|
||||
'email' => 'required|unique:users,email,NULL,id,deleted_at,NULL',
|
||||
'nama' => 'required|string|max:255',
|
||||
'no_telp' => 'nullable|string|max:20',
|
||||
'alamat' => 'nullable|string',
|
||||
'password' => 'required|min:6'
|
||||
], [
|
||||
'email.unique' => 'Email sudah digunakan.',
|
||||
'nama.required' => 'Nama harus diisi.',
|
||||
'password.required' => 'Password harus diisi.',
|
||||
'password.min' => 'Password minimal 6 karakter.',
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Membuat data user terlebih dahulu
|
||||
$user = User::create([
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make($request->password),
|
||||
'roles' => 'karyawan',
|
||||
]);
|
||||
|
||||
// Membuat data karyawan
|
||||
$karyawan = new Karyawan([
|
||||
'nama' => $validated['nama'],
|
||||
'no_telp' => $validated['no_telp'],
|
||||
'alamat' => $request->alamat,
|
||||
]);
|
||||
|
||||
// Menyimpan foto karyawan jika ada
|
||||
if ($request->hasFile('imageKaryawan')) {
|
||||
$foto = $request->file('imageKaryawan');
|
||||
$filegambar = time() . "_" . $foto->getClientOriginalName();
|
||||
$foto->move(public_path('img/DataKaryawan'), $filegambar); // Menyimpan gambar di folder public/img/DataKaryawan
|
||||
$karyawan->foto = $filegambar; // Menyimpan nama file gambar ke database
|
||||
}
|
||||
|
||||
// Menyimpan data karyawan yang terhubung dengan user
|
||||
$user->karyawan()->save($karyawan);
|
||||
|
||||
DB::commit();
|
||||
Alert::success('Success', 'Berhasil Menambahkan Data');
|
||||
return redirect()->route('indexKaryawan');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
Alert::error('Error', 'Gagal menambahkan data: ' . $e->getMessage());
|
||||
return back()->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
// Menampilkan form untuk mengedit data karyawan
|
||||
$karyawan = Karyawan::findOrFail($id);
|
||||
return view('pages.manajemen.karyawan.edit', compact('karyawan'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
// Validasi data input
|
||||
$karyawan = Karyawan::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'email' => 'nullable|unique:users,email,' . $karyawan->user->id . ',id,deleted_at,NULL',
|
||||
'nama' => 'required|string|max:255',
|
||||
'no_telp' => 'nullable|string|max:20',
|
||||
'alamat' => 'nullable|string',
|
||||
], [
|
||||
'email.unique' => 'Email sudah digunakan.',
|
||||
'nama.required' => 'Nama harus diisi.',
|
||||
]);
|
||||
|
||||
// Menyimpan foto karyawan baru jika ada
|
||||
if ($request->hasFile('imageKaryawan')) {
|
||||
// Hapus foto lama jika ada
|
||||
if ($karyawan->foto && file_exists(public_path('img/DataKaryawan/' . $karyawan->foto))) {
|
||||
unlink(public_path('img/DataKaryawan/' . $karyawan->foto)); // Hapus foto lama dari folder
|
||||
}
|
||||
|
||||
// Menyimpan foto baru
|
||||
$foto = $request->file('imageKaryawan');
|
||||
$filegambar = time() . "_" . $foto->getClientOriginalName();
|
||||
$foto->move(public_path('img/DataKaryawan'), $filegambar); // Menyimpan file di folder public/img/DataKaryawan
|
||||
$karyawan->foto = $filegambar; // Update nama file foto di database
|
||||
}
|
||||
|
||||
// Update data karyawan
|
||||
$karyawan->nama = $validated['nama'];
|
||||
$karyawan->no_telp = $validated['no_telp'];
|
||||
$karyawan->alamat = $request->alamat;
|
||||
$karyawan->save();
|
||||
|
||||
// Update data email user jika berbeda
|
||||
if ($request->email && $karyawan->user->email !== $request->email) {
|
||||
$karyawan->user->email = $request->email;
|
||||
$karyawan->user->save();
|
||||
}
|
||||
|
||||
Alert::success('Success', 'Berhasil Memperbarui Data');
|
||||
return redirect()->route('indexKaryawan');
|
||||
}
|
||||
|
||||
public function delete(User $id)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$karyawan = $id->karyawan;
|
||||
if ($karyawan && $karyawan->foto) {
|
||||
// Hapus foto jika ada
|
||||
if (file_exists(public_path('img/DataKaryawan/' . $karyawan->foto))) {
|
||||
unlink(public_path('img/DataKaryawan/' . $karyawan->foto)); // Hapus foto karyawan
|
||||
}
|
||||
}
|
||||
|
||||
// Soft delete both user and karyawan
|
||||
$id->delete(); // Ini akan cascade ke karyawan karena foreign key constraint
|
||||
|
||||
DB::commit();
|
||||
Alert::success('Success', 'Data karyawan berhasil dihapus.');
|
||||
return redirect()->back();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
Alert::error('Error', 'Gagal menghapus data: ' . $e->getMessage());
|
||||
return back();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\TransaksiDetail;
|
||||
use App\Models\Produk;
|
||||
use App\Models\LaporanPenjualan;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class LaporanPenjualanController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
// Filter tanggal dari request, jika tidak ada set ke bulan ini
|
||||
$startDate = $request->input('start_date', Carbon::now()->startOfMonth()->toDateString());
|
||||
$endDate = $request->input('end_date', Carbon::now()->endOfMonth()->toDateString());
|
||||
|
||||
// Adjust the date range to include the full day
|
||||
$startDateTime = Carbon::parse($startDate)->startOfDay();
|
||||
$endDateTime = Carbon::parse($endDate)->endOfDay();
|
||||
|
||||
$viewMode = $request->input('view_mode', 'daily');
|
||||
|
||||
// Query untuk data chart (penghasilan per periode)
|
||||
if ($viewMode == 'daily') {
|
||||
$chartData = DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDateTime, $endDateTime])
|
||||
->select(
|
||||
DB::raw('DATE(tanggal_transaksi) as period'),
|
||||
DB::raw('SUM(total_harga) as total')
|
||||
)
|
||||
->groupBy('period')
|
||||
->orderBy('period')
|
||||
->get();
|
||||
|
||||
$chartLabels = $chartData->pluck('period');
|
||||
$chartValues = $chartData->pluck('total');
|
||||
} else {
|
||||
$chartData = DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDateTime, $endDateTime])
|
||||
->select(
|
||||
DB::raw('MONTH(tanggal_transaksi) as month'),
|
||||
DB::raw('YEAR(tanggal_transaksi) as year'),
|
||||
DB::raw('SUM(total_harga) as total')
|
||||
)
|
||||
->groupBy('year', 'month')
|
||||
->orderBy('year')
|
||||
->orderBy('month')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$monthName = Carbon::createFromDate($item->year, $item->month, 1)->format('M Y');
|
||||
return [
|
||||
'period' => $monthName,
|
||||
'total' => $item->total
|
||||
];
|
||||
});
|
||||
|
||||
$chartLabels = $chartData->pluck('period');
|
||||
$chartValues = $chartData->pluck('total');
|
||||
}
|
||||
|
||||
// Check if customer columns exist
|
||||
$hasCustomerColumns = Schema::hasColumns('transaksi', ['customer_name', 'customer_phone']);
|
||||
|
||||
// Base query
|
||||
$query = DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->join('produks', 'produks.id', '=', 'transaksi_details.produk_id');
|
||||
|
||||
// Select fields based on available columns
|
||||
$selectFields = [
|
||||
'transaksi.id as transaksi_id',
|
||||
'transaksi.tanggal_transaksi',
|
||||
'produks.nama_produk',
|
||||
'transaksi_details.quantity as total_terjual',
|
||||
'transaksi_details.subtotal as total_penjualan'
|
||||
];
|
||||
|
||||
if ($hasCustomerColumns) {
|
||||
$selectFields[] = 'transaksi.customer_name';
|
||||
$selectFields[] = 'transaksi.customer_phone';
|
||||
}
|
||||
|
||||
// Build and execute query
|
||||
$laporan = $query->select($selectFields)
|
||||
->whereBetween('transaksi.tanggal_transaksi', [$startDateTime, $endDateTime])
|
||||
->orderBy('transaksi.tanggal_transaksi', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
// Statistik ringkasan
|
||||
$summary = [
|
||||
'total_penjualan' => DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDateTime, $endDateTime])
|
||||
->sum('total_harga'),
|
||||
'jumlah_transaksi' => DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDateTime, $endDateTime])
|
||||
->count()
|
||||
];
|
||||
|
||||
return view('pages.admin.laporan_penjualan.index', compact(
|
||||
'laporan',
|
||||
'startDate',
|
||||
'endDate',
|
||||
'viewMode',
|
||||
'chartLabels',
|
||||
'chartValues',
|
||||
'summary',
|
||||
'hasCustomerColumns'
|
||||
));
|
||||
}
|
||||
|
||||
public function generatePdf(Request $request)
|
||||
{
|
||||
$startDate = $request->input('start_date', Carbon::now()->startOfMonth()->toDateString());
|
||||
$endDate = $request->input('end_date', Carbon::now()->endOfMonth()->toDateString());
|
||||
|
||||
// Check if customer columns exist
|
||||
$hasCustomerColumns = Schema::hasColumns('transaksi', ['customer_name', 'customer_phone']);
|
||||
|
||||
// Base query
|
||||
$query = DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->join('produks', 'produks.id', '=', 'transaksi_details.produk_id');
|
||||
|
||||
// Select fields based on available columns
|
||||
$selectFields = [
|
||||
'transaksi.id as transaksi_id',
|
||||
'transaksi.tanggal_transaksi',
|
||||
DB::raw('GROUP_CONCAT(produks.nama_produk SEPARATOR ", ") as nama_produk'),
|
||||
DB::raw('SUM(transaksi_details.quantity) as total_terjual'),
|
||||
DB::raw('SUM(transaksi_details.subtotal) as total_penjualan')
|
||||
];
|
||||
|
||||
$groupByFields = ['transaksi.id', 'transaksi.tanggal_transaksi'];
|
||||
|
||||
if ($hasCustomerColumns) {
|
||||
$selectFields[] = 'transaksi.customer_name';
|
||||
$selectFields[] = 'transaksi.customer_phone';
|
||||
$groupByFields[] = 'transaksi.customer_name';
|
||||
$groupByFields[] = 'transaksi.customer_phone';
|
||||
}
|
||||
|
||||
// Build and execute query
|
||||
$laporan = $query->select($selectFields)
|
||||
->whereBetween('transaksi.tanggal_transaksi', [$startDate, $endDate])
|
||||
->groupBy($groupByFields)
|
||||
->orderBy('transaksi.tanggal_transaksi', 'desc')
|
||||
->get();
|
||||
|
||||
// Statistik untuk PDF
|
||||
$summary = [
|
||||
'total_penjualan' => DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDate, $endDate])
|
||||
->sum('total_harga'),
|
||||
'jumlah_transaksi' => DB::table('transaksi')
|
||||
->whereBetween('tanggal_transaksi', [$startDate, $endDate])
|
||||
->count(),
|
||||
'produk_terjual' => DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->whereBetween('transaksi.tanggal_transaksi', [$startDate, $endDate])
|
||||
->sum('transaksi_details.quantity')
|
||||
];
|
||||
|
||||
$data = [
|
||||
'laporan' => $laporan,
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'summary' => $summary,
|
||||
'tanggal_cetak' => Carbon::now()->format('d-m-Y H:i:s'),
|
||||
'hasCustomerColumns' => $hasCustomerColumns
|
||||
];
|
||||
|
||||
$pdf = Pdf::loadView('pages.admin.laporan_penjualan.pdf', $data);
|
||||
|
||||
return $pdf->download('laporan-penjualan-' . Carbon::now()->format('Y-m-d') . '.pdf');
|
||||
}
|
||||
|
||||
// Menyimpan data laporan penjualan ke database (jika diperlukan)
|
||||
public function store($tanggal)
|
||||
{
|
||||
// Generate laporan untuk tanggal tertentu dan simpan ke database
|
||||
$data = DB::table('transaksi')
|
||||
->whereDate('tanggal_transaksi', $tanggal)
|
||||
->select(
|
||||
DB::raw('COUNT(id) as jumlah_transaksi'),
|
||||
DB::raw('SUM(total_harga) as total_penjualan')
|
||||
)
|
||||
->first();
|
||||
|
||||
$produkTerjual = DB::table('transaksi_details')
|
||||
->join('transaksi', 'transaksi.id', '=', 'transaksi_details.transaksi_id')
|
||||
->whereDate('transaksi.tanggal_transaksi', $tanggal)
|
||||
->sum('transaksi_details.quantity');
|
||||
|
||||
// Simpan ke database
|
||||
LaporanPenjualan::updateOrCreate(
|
||||
['tanggal' => $tanggal],
|
||||
[
|
||||
'total_penjualan' => $data->total_penjualan ?? 0,
|
||||
'total_produk_terjual' => $produkTerjual ?? 0,
|
||||
'jumlah_transaksi' => $data->jumlah_transaksi ?? 0
|
||||
]
|
||||
);
|
||||
|
||||
return redirect()->back()->with('success', 'Laporan berhasil disimpan');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Produk;
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\TransaksiDetail;
|
||||
use App\Models\RawMaterial;
|
||||
use App\Models\RawMaterialLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class PosController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Check and add customer columns if they don't exist
|
||||
if (!Schema::hasColumn('transaksi', 'customer_name')) {
|
||||
DB::statement('ALTER TABLE transaksi ADD COLUMN customer_name VARCHAR(255) AFTER tanggal_transaksi');
|
||||
}
|
||||
if (!Schema::hasColumn('transaksi', 'customer_phone')) {
|
||||
DB::statement('ALTER TABLE transaksi ADD COLUMN customer_phone VARCHAR(255) AFTER customer_name');
|
||||
}
|
||||
}
|
||||
public function index()
|
||||
{
|
||||
$produks = Produk::with(['recipes.rawMaterial'])->get();
|
||||
|
||||
// Transform products with availability info
|
||||
$produks = $produks->map(function ($produk) {
|
||||
return [
|
||||
'id' => $produk->id,
|
||||
'nama_produk' => $produk->nama_produk,
|
||||
'harga_produk' => $produk->harga_produk,
|
||||
'stok_produk' => $produk->stok_produk,
|
||||
'foto' => $produk->foto,
|
||||
'deskripsi_produk' => $produk->deskripsi_produk,
|
||||
'recipes' => $produk->recipes,
|
||||
'can_sell' => $this->canSellProduct($produk),
|
||||
'max_sellable' => $this->getMaxSellableQuantity($produk),
|
||||
'missing_materials' => $this->getMissingMaterialsForSale($produk),
|
||||
'has_recipe' => $produk->hasCompleteRecipe(),
|
||||
];
|
||||
});
|
||||
|
||||
return view('pages.admin.pos.index', compact('produks'));
|
||||
}
|
||||
|
||||
private function generateTransactionCode()
|
||||
{
|
||||
$today = now()->format('Ymd');
|
||||
$lastTransaction = Transaksi::whereDate('created_at', today())->orderBy('kode_transaksi', 'desc')->first();
|
||||
|
||||
if ($lastTransaction) {
|
||||
// Extract the numeric part and increment
|
||||
$lastNumber = (int) substr($lastTransaction->kode_transaksi, -4);
|
||||
$newNumber = str_pad($lastNumber + 1, 4, '0', STR_PAD_LEFT);
|
||||
} else {
|
||||
$newNumber = '0001';
|
||||
}
|
||||
|
||||
return 'TRX-' . $today . '-' . $newNumber;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'customer_name' => 'required|string',
|
||||
'customer_phone' => 'required|string',
|
||||
'product_id' => 'required|array',
|
||||
'quantity' => 'required|array',
|
||||
'price' => 'required|array',
|
||||
'total_harga' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Generate kode transaksi
|
||||
$kodeTransaksi = $this->generateTransactionCode();
|
||||
|
||||
// Buat transaksi
|
||||
$transaksi = Transaksi::create([
|
||||
'kode_transaksi' => $kodeTransaksi,
|
||||
'tanggal_transaksi' => now(),
|
||||
'total_harga' => $request->total_harga,
|
||||
'customer_name' => $request->customer_name,
|
||||
'customer_phone' => $request->customer_phone,
|
||||
'user_id' => auth()->id(), // Tambahkan user_id dari user yang login
|
||||
'status' => 'completed',
|
||||
]);
|
||||
|
||||
// Proses setiap item
|
||||
foreach ($request->product_id as $index => $productId) {
|
||||
$produk = Produk::findOrFail($productId);
|
||||
$quantity = $request->quantity[$index];
|
||||
$price = $request->price[$index];
|
||||
|
||||
// Validasi stok produk
|
||||
if ($produk->stok_produk < $quantity) {
|
||||
throw new \Exception("Stok tidak mencukupi untuk produk {$produk->nama_produk}");
|
||||
}
|
||||
|
||||
// Hanya kurangi stok produk
|
||||
$produk->stok_produk -= $quantity;
|
||||
$produk->save();
|
||||
|
||||
// Buat detail transaksi
|
||||
TransaksiDetail::create([
|
||||
'transaksi_id' => $transaksi->id,
|
||||
'produk_id' => $productId,
|
||||
'quantity' => $quantity,
|
||||
'price' => $price,
|
||||
'subtotal' => $price * $quantity,
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Transaksi berhasil',
|
||||
'transaction_id' => $transaksi->id,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
Log::error('Transaction error: ' . $e->getMessage());
|
||||
return response()->json(
|
||||
[
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function printReceipt($id)
|
||||
{
|
||||
try {
|
||||
$transaction = Transaksi::with('details.produk')->findOrFail($id);
|
||||
return view('pages.admin.pos.receipt', compact('transaction'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Print receipt error: ' . $e->getMessage());
|
||||
return response()->json(
|
||||
[
|
||||
'success' => false,
|
||||
'message' => 'Gagal memuat struk: ' . $e->getMessage(),
|
||||
],
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function printThermalReceipt($id)
|
||||
{
|
||||
try {
|
||||
$transaction = Transaksi::with('details.produk')->findOrFail($id);
|
||||
|
||||
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pages.admin.pos.thermal-receipt', compact('transaction'));
|
||||
|
||||
// Set paper size for thermal receipt (58mm width)
|
||||
$pdf->setPaper([0, 0, 164.41, 'auto'], 'portrait'); // 58mm = 164.41 points
|
||||
|
||||
return $pdf->stream('struk-' . $transaction->id . '.pdf');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Print thermal receipt error: ' . $e->getMessage());
|
||||
return response()->json(
|
||||
[
|
||||
'success' => false,
|
||||
'message' => 'Gagal membuat PDF struk: ' . $e->getMessage(),
|
||||
],
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product can be sold (considering both product stock and raw materials)
|
||||
*/
|
||||
private function canSellProduct($produk, $quantity = 1)
|
||||
{
|
||||
// Check product stock
|
||||
if ($produk->stok_produk < $quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If product has recipe, check raw materials
|
||||
if ($produk->hasCompleteRecipe()) {
|
||||
return $produk->canProduce($quantity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum sellable quantity considering both product stock and raw materials
|
||||
*/
|
||||
private function getMaxSellableQuantity($produk)
|
||||
{
|
||||
$maxFromStock = $produk->stok_produk;
|
||||
|
||||
// If no recipe, return product stock
|
||||
if (!$produk->hasCompleteRecipe()) {
|
||||
return $maxFromStock;
|
||||
}
|
||||
|
||||
// Get max producible from raw materials
|
||||
$maxFromMaterials = $produk->getMaxProducibleQuantity();
|
||||
|
||||
// Return the minimum of both
|
||||
return min($maxFromStock, $maxFromMaterials);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get missing materials for selling a product
|
||||
*/
|
||||
private function getMissingMaterialsForSale($produk, $quantity = 1)
|
||||
{
|
||||
if (!$produk->hasCompleteRecipe()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $produk->getMissingMaterials($quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check product availability via AJAX
|
||||
*/
|
||||
public function checkAvailability(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'product_id' => 'required|exists:produks,id',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$produk = Produk::with(['recipes.rawMaterial'])->findOrFail($request->product_id);
|
||||
$quantity = $request->quantity;
|
||||
|
||||
$canSell = $this->canSellProduct($produk, $quantity);
|
||||
$maxSellable = $this->getMaxSellableQuantity($produk);
|
||||
$missingMaterials = $this->getMissingMaterialsForSale($produk, $quantity);
|
||||
|
||||
return response()->json([
|
||||
'can_sell' => $canSell,
|
||||
'max_sellable' => $maxSellable,
|
||||
'product_stock' => $produk->stok_produk,
|
||||
'has_recipe' => $produk->hasCompleteRecipe(),
|
||||
'missing_materials' => $missingMaterials,
|
||||
'message' => $canSell ? 'Produk tersedia' : 'Stok tidak mencukupi',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Produk;
|
||||
use App\Models\RawMaterial;
|
||||
use App\Models\ProductRecipe;
|
||||
use App\Models\RawMaterialLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ProdukController extends Controller
|
||||
{
|
||||
// Tampilkan daftar produk
|
||||
public function index()
|
||||
{
|
||||
$produks = Produk::orderBy('created_at', 'desc')->get();
|
||||
return view('pages.admin.produk.index', compact('produks'));
|
||||
}
|
||||
|
||||
// Tampilkan form tambah produk
|
||||
public function create()
|
||||
{
|
||||
$materials = RawMaterial::orderBy('name')->get();
|
||||
return view('pages.admin.produk.create', compact('materials'));
|
||||
}
|
||||
|
||||
// Simpan data produk baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'nama_produk' => 'required',
|
||||
'harga_produk' => 'required|numeric',
|
||||
'stok_produk' => 'required|numeric',
|
||||
'deskripsi_produk' => 'nullable',
|
||||
'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
|
||||
'recipes' => 'array'
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
if (Produk::where('nama_produk', $validatedData['nama_produk'])->exists()) {
|
||||
return back()->with('error', 'Nama produk sudah ada. Silakan gunakan nama yang berbeda.')->withInput();
|
||||
}
|
||||
|
||||
// Create product
|
||||
if ($request->hasFile('foto')) {
|
||||
$foto = $request->file('foto');
|
||||
$fotoName = time() . '.' . $foto->getClientOriginalExtension();
|
||||
$foto->move(public_path('img/produk'), $fotoName);
|
||||
$validatedData['foto'] = $fotoName;
|
||||
}
|
||||
|
||||
$produk = Produk::create($validatedData);
|
||||
|
||||
// Save recipes if provided
|
||||
if ($request->has('recipes')) {
|
||||
foreach ($request->recipes as $recipe) {
|
||||
if (!empty($recipe['material_id']) && !empty($recipe['quantity']) && !empty($recipe['unit'])) {
|
||||
// Validate unit compatibility
|
||||
$material = RawMaterial::find($recipe['material_id']);
|
||||
if (!$material) {
|
||||
throw new \Exception("Material not found");
|
||||
}
|
||||
|
||||
// Check unit compatibility using helper
|
||||
$recipeUnit = $recipe['unit'];
|
||||
$materialUnit = $material->unit;
|
||||
|
||||
if (!\App\Helpers\UnitConverter::areUnitsCompatible($recipeUnit, $materialUnit)) {
|
||||
throw new \Exception("Unit {$recipeUnit} is not compatible with material unit {$materialUnit} for {$material->name}");
|
||||
}
|
||||
|
||||
ProductRecipe::create([
|
||||
'produk_id' => $produk->id,
|
||||
'raw_material_id' => $recipe['material_id'],
|
||||
'quantity' => $recipe['quantity'],
|
||||
'unit' => $recipe['unit'],
|
||||
'notes' => $recipe['notes'] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pengurangan stok bahan baku sesuai resep dan stok produk yang ditambahkan
|
||||
if ($produk->recipes()->exists() && $produk->stok_produk > 0) {
|
||||
foreach ($produk->recipes as $recipe) {
|
||||
$material = RawMaterial::find($recipe->raw_material_id);
|
||||
if (!$material) {
|
||||
throw new \Exception("Bahan baku tidak ditemukan untuk resep.");
|
||||
}
|
||||
|
||||
$requiredQuantity = $recipe->quantity * $produk->stok_produk;
|
||||
|
||||
// Validasi stok bahan baku mencukupi
|
||||
if ($material->stock < $requiredQuantity) {
|
||||
throw new \Exception("Stok bahan baku {$material->name} tidak mencukupi untuk menambah stok produk. Dibutuhkan: {$requiredQuantity} {$material->unit}, Tersedia: {$material->stock} {$material->unit}");
|
||||
}
|
||||
|
||||
// Kurangi stok bahan baku
|
||||
$material->decrement('stock', $requiredQuantity);
|
||||
|
||||
// Catat pengurangan di log
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $material->id,
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'production',
|
||||
'quantity' => $requiredQuantity,
|
||||
'price' => $material->price,
|
||||
'subtotal' => $requiredQuantity * $material->price,
|
||||
'notes' => "Pengurangan untuk produksi {$produk->nama_produk} (Penambahan stok: {$produk->stok_produk})"
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('produk.index')->with('success', 'Produk berhasil ditambahkan');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage())->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// Tampilkan form edit produk
|
||||
public function edit($id)
|
||||
{
|
||||
$produk = Produk::with('recipes.rawMaterial')->findOrFail($id);
|
||||
$materials = RawMaterial::orderBy('name')->get();
|
||||
return view('pages.admin.produk.edit', compact('produk', 'materials'));
|
||||
}
|
||||
|
||||
// Update data produk
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'nama_produk' => 'required',
|
||||
'harga_produk' => 'required|numeric',
|
||||
'stok_produk' => 'required|numeric',
|
||||
'deskripsi_produk' => 'nullable',
|
||||
'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
|
||||
'recipes' => 'array'
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$produk = Produk::findOrFail($id);
|
||||
$oldStock = $produk->stok_produk;
|
||||
$newStock = $validatedData['stok_produk'];
|
||||
$stockDifference = $newStock - $oldStock;
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
if ($produk->foto && $produk->foto !== 'default_foto.jpg') {
|
||||
$oldPhotoPath = public_path('img/produk/' . $produk->foto);
|
||||
if (file_exists($oldPhotoPath)) {
|
||||
unlink($oldPhotoPath);
|
||||
}
|
||||
}
|
||||
$foto = $request->file('foto');
|
||||
$fotoName = time() . '.' . $foto->getClientOriginalExtension();
|
||||
$foto->move(public_path('img/produk'), $fotoName);
|
||||
$validatedData['foto'] = $fotoName;
|
||||
}
|
||||
|
||||
// Update recipes terlebih dahulu
|
||||
$oldRecipes = $produk->recipes()->get(); // Simpan resep lama
|
||||
|
||||
if ($request->has('recipes')) {
|
||||
// Delete existing recipes
|
||||
ProductRecipe::where('produk_id', $produk->id)->delete();
|
||||
|
||||
// Add new recipes
|
||||
foreach ($request->recipes as $recipe) {
|
||||
if (!empty($recipe['material_id'])) {
|
||||
ProductRecipe::create([
|
||||
'produk_id' => $produk->id,
|
||||
'raw_material_id' => $recipe['material_id'],
|
||||
'quantity' => $recipe['quantity'],
|
||||
'unit' => $recipe['unit'],
|
||||
'notes' => $recipe['notes'] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh produk data untuk mendapatkan resep terbaru
|
||||
$produk->refresh();
|
||||
|
||||
// Jika ada penambahan stok
|
||||
if ($stockDifference > 0) {
|
||||
// Jika produk memiliki resep
|
||||
if ($produk->recipes()->exists()) {
|
||||
foreach ($produk->recipes as $recipe) {
|
||||
$material = RawMaterial::find($recipe->raw_material_id);
|
||||
if (!$material) {
|
||||
throw new \Exception("Bahan baku tidak ditemukan untuk resep.");
|
||||
}
|
||||
|
||||
$requiredQuantity = $recipe->quantity * $stockDifference;
|
||||
|
||||
// Validasi stok bahan baku mencukupi
|
||||
if ($material->stock < $requiredQuantity) {
|
||||
throw new \Exception("Stok bahan baku {$material->name} tidak mencukupi untuk menambah stok produk. Dibutuhkan: {$requiredQuantity} {$material->unit}, Tersedia: {$material->stock} {$material->unit}");
|
||||
}
|
||||
|
||||
// Kurangi stok bahan baku
|
||||
$material->decrement('stock', $requiredQuantity);
|
||||
|
||||
// Catat pengurangan di log
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $material->id,
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'production',
|
||||
'quantity' => $requiredQuantity,
|
||||
'price' => $material->price,
|
||||
'subtotal' => $requiredQuantity * $material->price,
|
||||
'notes' => "Pengurangan untuk produksi {$produk->nama_produk} (Penambahan stok: {$stockDifference})"
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update produk setelah semua validasi berhasil
|
||||
$produk->update($validatedData);
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('produk.index')->with('success', 'Produk berhasil diperbarui');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Hapus produk
|
||||
public function destroy($id)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$produk = Produk::findOrFail($id);
|
||||
|
||||
// Delete recipes first
|
||||
ProductRecipe::where('produk_id', $produk->id)->delete();
|
||||
|
||||
// Delete photo if exists
|
||||
if ($produk->foto && $produk->foto !== 'default_foto.jpg') {
|
||||
$photoPath = public_path('img/produk/' . $produk->foto);
|
||||
if (file_exists($photoPath)) {
|
||||
unlink($photoPath);
|
||||
}
|
||||
}
|
||||
|
||||
$produk->delete();
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('produk.index')->with('success', 'Produk berhasil dihapus');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Admin;
|
||||
use App\Models\Karyawan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function edit()
|
||||
{
|
||||
return view('profile.edit');
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// Validasi input
|
||||
$rules = [
|
||||
'nama' => 'required|string|max:255',
|
||||
// 'email' => 'required|email|unique:users,email,' . $user->id,
|
||||
'password' => 'nullable|min:6|confirmed',
|
||||
];
|
||||
|
||||
// Tambah validasi foto untuk karyawan
|
||||
if ($user->roles === 'karyawan') {
|
||||
$rules['foto'] = 'nullable|image|mimes:jpeg,png,jpg|max:2048';
|
||||
}
|
||||
|
||||
$request->validate($rules);
|
||||
|
||||
// Update user email dan password
|
||||
User::where('id', $user->id)
|
||||
->update([
|
||||
// 'email' => $request->email,
|
||||
'password' => $request->filled('password') ? Hash::make($request->password) : $user->password
|
||||
]);
|
||||
|
||||
// Update profil berdasarkan role
|
||||
if ($user->roles === 'admin') {
|
||||
Admin::updateOrCreate(
|
||||
['user_id' => $user->id],
|
||||
['nama' => $request->nama]
|
||||
);
|
||||
} elseif ($user->roles === 'karyawan') {
|
||||
$karyawan = Karyawan::where('user_id', $user->id)->first();
|
||||
|
||||
$updateData = ['nama' => $request->nama];
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
// Hapus foto lama jika ada
|
||||
if ($karyawan && $karyawan->foto) {
|
||||
$oldPhotoPath = public_path('img/DataKaryawan/' . $karyawan->foto);
|
||||
if (file_exists($oldPhotoPath)) {
|
||||
unlink($oldPhotoPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate nama file yang unik
|
||||
$foto = $request->file('foto');
|
||||
$extension = $foto->getClientOriginalExtension();
|
||||
$fotoName = 'profile_' . Str::random(10) . '_' . time() . '.' . $extension;
|
||||
|
||||
// Upload foto baru ke public path
|
||||
$foto->move(public_path('img/DataKaryawan'), $fotoName);
|
||||
|
||||
$updateData['foto'] = $fotoName;
|
||||
}
|
||||
|
||||
// Add alamat to updateData if it exists in request
|
||||
if ($request->has('alamat')) {
|
||||
$updateData['alamat'] = $request->alamat;
|
||||
}
|
||||
|
||||
Karyawan::updateOrCreate(
|
||||
['user_id' => $user->id],
|
||||
$updateData
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Profil berhasil diperbarui');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers; // Deklarasi namespace untuk kelas kontroler ini. Ini membantu mengorganisir kode dan mencegah konflik nama kelas.
|
||||
|
||||
use App\Models\RawMaterial; // Mengimpor model `RawMaterial`. Model ini merepresentasikan tabel 'raw_materials' di database dan digunakan untuk berinteraksi dengan data bahan baku.
|
||||
use App\Models\RawMaterialLog; // Mengimpor model `RawMaterialLog`. Model ini merepresentasikan tabel 'raw_material_logs' dan digunakan untuk mencatat setiap perubahan stok bahan baku (misalnya, masuk, keluar, penyesuaian).
|
||||
use App\Models\Supplier; // Mengimpor model `Supplier`. Meskipun tidak digunakan secara langsung di semua metode yang ditampilkan, model ini mungkin digunakan di bagian lain aplikasi atau untuk tampilan terkait (misalnya, di form pembelian untuk memilih supplier).
|
||||
use Illuminate\Http\Request; // Mengimpor kelas `Request`. Objek Request digunakan untuk mengakses data yang dikirimkan melalui permintaan HTTP (seperti data dari form, parameter URL, dll.).
|
||||
use Illuminate\Support\Facades\DB; // Mengimpor facade `DB`. Ini menyediakan metode untuk berinteraksi langsung dengan database, termasuk menjalankan transaksi database. Transaksi sangat penting untuk memastikan beberapa operasi database berhasil atau gagal secara bersamaan (atomik).
|
||||
|
||||
class RawMaterialController extends Controller // Deklarasi kelas `RawMaterialController` yang mewarisi (extends) dari kelas `Controller` dasar Laravel. Ini memberikan akses ke berbagai fitur dasar kontroler.
|
||||
{
|
||||
/**
|
||||
* Metode `index()`: Menampilkan daftar semua bahan baku.
|
||||
* Metode ini berfungsi sebagai halaman utama untuk manajemen bahan baku, menampilkan semua item yang tersedia.
|
||||
*
|
||||
* @return \Illuminate\View\View Mengembalikan instance view Laravel yang akan ditampilkan ke pengguna.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Mengambil semua data bahan baku dari tabel 'raw_materials'.
|
||||
// `RawMaterial::orderBy('name')` mengurutkan hasil berdasarkan kolom 'name' secara ascending (A-Z).
|
||||
// `->get()` mengeksekusi query dan mengembalikan koleksi (Collection) dari objek RawMaterial.
|
||||
$materials = RawMaterial::orderBy('created_at', 'desc')->get();
|
||||
|
||||
// Mengirim data bahan baku ($materials) ke view.
|
||||
// `view('raw-materials.index')` merujuk ke file view Blade di `resources/views/raw-materials/index.blade.php`.
|
||||
// `compact('materials')` adalah cara singkat untuk mengirim variabel `$materials` ke view dengan nama yang sama.
|
||||
return view('raw-materials.index', compact('materials'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `create()`: Menampilkan form untuk membuat bahan baku baru.
|
||||
* Metode ini bertanggung jawab untuk menampilkan antarmuka pengguna untuk menambahkan bahan baku baru ke sistem.
|
||||
*
|
||||
* @return \Illuminate\View\View Mengembalikan instance view Laravel yang berisi form.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// Mengirim pengguna ke view 'raw-materials.create', yang berisi form input untuk data bahan baku baru.
|
||||
// View ini biasanya ada di `resources/views/raw-materials/create.blade.php`.
|
||||
return view('raw-materials.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `store()`: Menyimpan data bahan baku baru yang dikirim dari form `create()`.
|
||||
* Metode ini menerima data dari permintaan POST dan menyimpannya ke database setelah validasi.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Objek Request yang berisi semua data input dari form.
|
||||
* @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali ke halaman lain dengan pesan status.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
// Melakukan validasi data yang diterima dari request.
|
||||
// Ini memastikan bahwa data yang masuk ke database sesuai dengan aturan yang ditentukan.
|
||||
$request->validate([
|
||||
'name' => 'required|string', // Nama bahan baku wajib diisi.
|
||||
'stock' => 'required|numeric|min:0', // Stok wajib diisi, harus berupa angka, dan tidak boleh kurang dari 0.
|
||||
'unit' => 'required', // Satuan (misalnya, kg, liter) wajib diisi.
|
||||
'price' => 'required|numeric|min:0', // Harga wajib diisi, harus berupa angka, dan tidak boleh kurang dari 0.
|
||||
'description' => 'nullable', // Deskripsi boleh kosong (tidak wajib).
|
||||
'type' => 'required|in:in,adjustment' // Tipe transaksi awal harus 'in' (masuk) atau 'adjustment' (penyesuaian).
|
||||
]);
|
||||
|
||||
if (RawMaterial::where('name', $request['name'])->exists()) {
|
||||
return back()->with('error', 'Nama bahan baku sudah ada. Silakan gunakan nama yang berbeda.')->withInput();
|
||||
}
|
||||
// Membuat entri bahan baku baru di tabel 'raw_materials' menggunakan data yang tervalidasi.
|
||||
// `RawMaterial::create()` adalah metode Eloquent yang membuat dan menyimpan record baru ke database.
|
||||
$material = RawMaterial::create([
|
||||
'name' => $request->name, // Mengambil nilai 'name' dari request.
|
||||
'description' => $request->description, // Mengambil nilai 'description' dari request.
|
||||
'stock' => $request->stock, // Mengambil nilai 'stock' dari request.
|
||||
'unit' => $request->unit, // Mengambil nilai 'unit' dari request.
|
||||
'price' => $request->price, // Mengambil nilai 'price' dari request.
|
||||
'minimum_stock' => 10 // Menetapkan nilai default 'minimum_stock' menjadi 10.
|
||||
]);
|
||||
|
||||
// Memeriksa apakah stok awal yang dimasukkan lebih dari 0.
|
||||
if ($material->stock > 0) {
|
||||
// Jika stok lebih dari 0, buat log di tabel 'raw_material_logs'.
|
||||
// Ini mencatat entri awal bahan baku ke dalam sistem.
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $material->id, // ID bahan baku yang baru saja dibuat.
|
||||
'user_id' => auth()->id(), // ID pengguna yang sedang login (yang melakukan operasi ini).
|
||||
'type' => $request->type, // Tipe log, diambil dari input 'type' form (e.g., 'in' atau 'adjustment').
|
||||
'quantity' => $material->stock, // Kuantitas stok yang dicatat dalam log.
|
||||
'price' => $material->price, // Harga per unit bahan baku saat log dibuat.
|
||||
'subtotal' => $material->stock * $material->price, // Subtotal (kuantitas * harga).
|
||||
// Catatan log disesuaikan berdasarkan tipe: 'Stok awal bahan baku' jika 'in', 'Penyesuaian stok awal' jika 'adjustment'.
|
||||
'notes' => $request->type === 'in' ? 'Stok awal bahan baku' : 'Penyesuaian stok awal'
|
||||
]);
|
||||
}
|
||||
|
||||
// Mengarahkan pengguna kembali ke halaman index bahan baku setelah operasi berhasil.
|
||||
// `with('success', ...)` menambahkan pesan flash 'success' yang bisa ditampilkan di view.
|
||||
return redirect()->route('raw-materials.index')
|
||||
->with('success', 'Bahan baku berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `edit()`: Menampilkan form untuk mengedit bahan baku yang sudah ada.
|
||||
* Metode ini mengambil data bahan baku berdasarkan ID dan menyediakannya untuk pengeditan.
|
||||
*
|
||||
* @param \App\Models\RawMaterial $rawMaterial Ini adalah fitur Route Model Binding Laravel. Laravel secara otomatis mencari objek `RawMaterial` berdasarkan ID yang ada di URL dan menyuntikkannya ke metode ini.
|
||||
* @return \Illuminate\View\View Mengembalikan instance view Laravel yang berisi form pengeditan.
|
||||
*/
|
||||
public function edit(RawMaterial $rawMaterial)
|
||||
{
|
||||
// Mengirim data bahan baku ($rawMaterial) yang akan diedit ke view.
|
||||
// View ini biasanya ada di `resources/views/raw-materials/edit.blade.php`.
|
||||
return view('raw-materials.edit', compact('rawMaterial'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `update()`: Memperbarui data bahan baku yang sudah ada.
|
||||
* Metode ini menerima data dari form pengeditan dan memperbarui record di database.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Objek Request yang berisi data input yang diperbarui.
|
||||
* @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang akan diperbarui (didapat dari Route Model Binding).
|
||||
* @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status.
|
||||
*/
|
||||
public function update(Request $request, RawMaterial $rawMaterial)
|
||||
{
|
||||
// Melakukan validasi data yang diterima untuk pembaruan.
|
||||
// Aturannya mirip dengan `store()`, dengan tambahan `minimum_stock`.
|
||||
$request->validate([
|
||||
'name' => 'required',
|
||||
'stock' => 'required|numeric|min:0',
|
||||
'unit' => 'required',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'minimum_stock' => 'required|numeric|min:0',
|
||||
'description' => 'nullable'
|
||||
]);
|
||||
|
||||
// Memeriksa apakah ada perubahan pada nilai stok.
|
||||
// Jika stok yang diinputkan berbeda dengan stok yang ada di database saat ini, maka buat log perubahan.
|
||||
if ($request->stock != $rawMaterial->stock) {
|
||||
// Menghitung selisih antara stok baru dan stok lama.
|
||||
$difference = $request->stock - $rawMaterial->stock;
|
||||
// Menentukan tipe log: 'in' jika stok bertambah, 'out' jika stok berkurang.
|
||||
$type = $difference > 0 ? 'in' : 'out';
|
||||
// Mengambil nilai absolut dari selisih untuk kuantitas log.
|
||||
$quantity = abs($difference);
|
||||
|
||||
// Membuat entri log di tabel 'raw_material_logs' untuk mencatat penyesuaian stok.
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $rawMaterial->id, // ID bahan baku yang stoknya diubah.
|
||||
'user_id' => auth()->id(), // ID pengguna yang melakukan perubahan.
|
||||
'type' => $type, // Tipe log ('in' atau 'out').
|
||||
'quantity' => $quantity, // Jumlah kuantitas yang berubah.
|
||||
'price' => $request->price, // Harga per unit saat perubahan (diambil dari input form).
|
||||
'subtotal' => $quantity * $request->price, // Subtotal perubahan.
|
||||
'notes' => 'Penyesuaian stok melalui edit bahan baku' // Catatan default untuk log ini.
|
||||
]);
|
||||
}
|
||||
|
||||
// Memperbarui data bahan baku di database dengan semua data dari request.
|
||||
// `update($request->all())` akan memperbarui semua kolom yang ada di `$request->all()` yang sesuai dengan fillable di model.
|
||||
$rawMaterial->update($request->all());
|
||||
|
||||
// Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses.
|
||||
return redirect()->route('raw-materials.index')
|
||||
->with('success', 'Data bahan baku berhasil diperbarui.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `destroy()`: Menghapus bahan baku dari database.
|
||||
* Metode ini digunakan untuk menghapus record bahan baku secara permanen.
|
||||
*
|
||||
* @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang akan dihapus.
|
||||
* @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status.
|
||||
*/
|
||||
public function destroy(RawMaterial $rawMaterial)
|
||||
{
|
||||
// Menghapus data bahan baku dari database.
|
||||
$rawMaterial->delete();
|
||||
|
||||
// Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses.
|
||||
return redirect()->route('raw-materials.index')
|
||||
->with('success', 'Bahan baku sudah di hapus.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `adjustStock()`: Menyesuaikan stok bahan baku (menambah atau mengurangi).
|
||||
* Metode ini dirancang untuk penyesuaian stok manual (misalnya, koreksi inventaris), bukan untuk pembelian.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Objek Request yang berisi data penyesuaian stok.
|
||||
* @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang stoknya akan disesuaikan.
|
||||
* @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status.
|
||||
*/
|
||||
public function adjustStock(Request $request, RawMaterial $rawMaterial)
|
||||
{
|
||||
// Melakukan validasi input untuk penyesuaian stok.
|
||||
$validated = $request->validate([
|
||||
'type' => 'required|in:in,out', // Tipe penyesuaian: 'in' (masuk) atau 'out' (keluar).
|
||||
'quantity' => 'required|numeric|min:1', // Kuantitas penyesuaian, harus angka positif.
|
||||
'notes' => 'nullable', // Catatan penyesuaian (opsional).
|
||||
]);
|
||||
|
||||
// Menggunakan transaksi database (`DB::transaction`).
|
||||
// Ini penting untuk menjaga integritas data: jika ada langkah yang gagal, semua perubahan dalam blok ini akan dibatalkan.
|
||||
DB::transaction(function () use ($rawMaterial, $validated) {
|
||||
// Menghitung subtotal berdasarkan harga bahan baku saat ini dan kuantitas yang disesuaikan.
|
||||
$subtotal = $rawMaterial->price * $validated['quantity'];
|
||||
|
||||
// Logika penyesuaian stok:
|
||||
if ($validated['type'] === 'in') {
|
||||
// Jika tipe adalah 'in' (masuk), tambahkan kuantitas ke stok bahan baku.
|
||||
$rawMaterial->increment('stock', $validated['quantity']);
|
||||
} else {
|
||||
// Jika tipe adalah 'out' (keluar), kurangi kuantitas dari stok.
|
||||
// Penting: Lakukan pemeriksaan stok untuk mencegah stok negatif.
|
||||
if ($rawMaterial->stock < $validated['quantity']) {
|
||||
// Jika stok tidak mencukupi, lemparkan Exception (ini akan memicu rollback transaksi).
|
||||
throw new \Exception('Insufficient stock.');
|
||||
}
|
||||
// Kurangi stok bahan baku.
|
||||
$rawMaterial->decrement('stock', $validated['quantity']);
|
||||
}
|
||||
|
||||
// Mencatat log penyesuaian stok di tabel 'raw_material_logs'.
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $rawMaterial->id, // ID bahan baku yang disesuaikan.
|
||||
'user_id' => auth()->id(), // ID pengguna yang melakukan penyesuaian.
|
||||
'type' => $validated['type'], // Tipe penyesuaian ('in' atau 'out').
|
||||
'quantity' => $validated['quantity'], // Kuantitas yang disesuaikan.
|
||||
'price' => $rawMaterial->price, // Harga bahan baku saat itu (dari database).
|
||||
'subtotal' => $subtotal, // Subtotal penyesuaian.
|
||||
'notes' => $validated['notes'] // Catatan dari pengguna.
|
||||
]);
|
||||
});
|
||||
|
||||
// Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses.
|
||||
return redirect()->route('raw-materials.index')
|
||||
->with('success', 'Stock adjusted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `report()`: Menampilkan laporan log bahan baku (mutasi stok).
|
||||
* Metode ini memungkinkan pengguna melihat riwayat perubahan stok dengan filter tertentu.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Objek Request yang berisi filter (tanggal, ID bahan baku).
|
||||
* @return \Illuminate\View\View Mengembalikan view laporan dengan data log.
|
||||
*/
|
||||
public function report(Request $request)
|
||||
{
|
||||
// Membangun query dasar untuk mengambil log bahan baku.
|
||||
// `with(['rawMaterial', 'user.admin', 'user.karyawan'])` memuat relasi terkait (bahan baku, pengguna, admin/karyawan yang terkait dengan pengguna)
|
||||
// untuk menghindari N+1 query problem dan menampilkan detail yang relevan di laporan.
|
||||
// `orderBy('created_at', 'desc')` mengurutkan log berdasarkan waktu pembuatan terbaru.
|
||||
$query = RawMaterialLog::with(['rawMaterial', 'user.admin', 'user.karyawan'])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
// Menerapkan filter berdasarkan tanggal mulai jika `start_date` ada di request.
|
||||
if ($request->filled('start_date')) {
|
||||
$query->whereDate('created_at', '>=', $request->start_date);
|
||||
}
|
||||
|
||||
// Menerapkan filter berdasarkan tanggal selesai jika `end_date` ada di request.
|
||||
if ($request->filled('end_date')) {
|
||||
$query->whereDate('created_at', '<=', $request->end_date);
|
||||
}
|
||||
|
||||
// Menerapkan filter berdasarkan ID bahan baku jika `material_id` ada di request.
|
||||
if ($request->filled('material_id')) {
|
||||
$query->where('raw_material_id', $request->material_id);
|
||||
}
|
||||
|
||||
// Mengeksekusi query dan mendapatkan hasil log.
|
||||
$logs = $query->get();
|
||||
// Filter agar hanya log yang bahan bakunya masih ada yang ditampilkan
|
||||
$logs = $logs->filter(function($log) {
|
||||
return $log->rawMaterial !== null;
|
||||
});
|
||||
// Mengambil semua bahan baku untuk digunakan dalam dropdown filter di tampilan laporan.
|
||||
$materials = RawMaterial::orderBy('name')->get();
|
||||
|
||||
// Mengirim data log dan daftar bahan baku ke view 'raw-materials.report'.
|
||||
return view('raw-materials.report', compact('logs', 'materials'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `lowStock()`: Menampilkan daftar bahan baku dengan stok rendah.
|
||||
* Metode ini berguna untuk mengidentifikasi bahan baku yang perlu segera di-restock.
|
||||
*
|
||||
* @return \Illuminate\View\View Mengembalikan view yang menampilkan bahan baku stok rendah.
|
||||
*/
|
||||
public function lowStock()
|
||||
{
|
||||
// minimum stok jika jumlah di field stok lebih kesil atau sama dengan jumlah di field minimum_stock
|
||||
$materials = RawMaterial::whereRaw('stock <= minimum_stock')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
// Mengirim data bahan baku stok rendah ke view 'raw-materials.low-stock'.
|
||||
return view('raw-materials.low-stock', compact('materials'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `purchase()`: Menampilkan form untuk mencatat pembelian bahan baku.
|
||||
* Metode ini menyediakan antarmuka untuk mencatat pembelian bahan baku baru dari supplier.
|
||||
*
|
||||
* @return \Illuminate\View\View Mengembalikan view form pembelian.
|
||||
*/
|
||||
public function purchase()
|
||||
{
|
||||
// Mengambil semua bahan baku untuk dropdown pilihan di form pembelian.
|
||||
$materials = RawMaterial::orderBy('name')->get();
|
||||
// Mengambil semua supplier (pemasok) untuk dropdown pilihan di form pembelian.
|
||||
$suppliers = Supplier::orderBy('name')->get();
|
||||
|
||||
// Mengirim data bahan baku dan supplier ke view 'raw-materials.purchase'.
|
||||
return view('raw-materials.purchase', compact('materials', 'suppliers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Metode `storePurchase()`: Menyimpan data pembelian bahan baku.
|
||||
* Metode ini memproses data dari form pembelian, memperbarui stok, dan mencatat log pembelian.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Objek Request yang berisi data pembelian.
|
||||
* @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan sukses.
|
||||
*/
|
||||
public function storePurchase(Request $request)
|
||||
{
|
||||
// Melakukan validasi input untuk data pembelian.
|
||||
// 'items' diharapkan berupa array, dan setiap item dalam array tersebut harus memiliki 'material_id', 'quantity', dan 'subtotal' yang valid.
|
||||
$validated = $request->validate([
|
||||
'items' => 'required|array', // 'items' wajib ada dan harus berupa array.
|
||||
'items.*.material_id' => 'required|exists:raw_materials,id', // Setiap item harus memiliki 'material_id' yang valid (ada di tabel 'raw_materials').
|
||||
'items.*.quantity' => 'required|numeric|min:0.01', // Kuantitas wajib, harus angka, minimal 0.01.
|
||||
'items.*.subtotal' => 'required|numeric|min:1', // Subtotal wajib, harus angka, minimal 1.
|
||||
'notes' => 'nullable|string' // Catatan pembelian (opsional).
|
||||
]);
|
||||
|
||||
// Menggunakan transaksi database (`DB::transaction`).
|
||||
// Ini memastikan bahwa semua pembaruan stok dan log pembelian untuk semua item berjalan secara atomik.
|
||||
// Jika ada masalah saat memproses satu item, semua perubahan akan dibatalkan.
|
||||
DB::transaction(function () use ($validated) {
|
||||
// Melakukan iterasi (loop) untuk setiap item bahan baku yang dibeli.
|
||||
foreach ($validated['items'] as $item) {
|
||||
// Mencari objek RawMaterial berdasarkan 'material_id' dari item yang dibeli.
|
||||
// `findOrFail()` akan melempar error 404 jika bahan baku tidak ditemukan.
|
||||
$material = RawMaterial::findOrFail($item['material_id']);
|
||||
// Menghitung harga per unit baru. Ini penting karena harga beli bisa bervariasi.
|
||||
// `round(..., 2)` membulatkan hasil ke dua tempat desimal.
|
||||
$newPrice = round($item['subtotal'] / $item['quantity'], 2);
|
||||
|
||||
// Memperbarui stok dan harga di tabel `raw_materials`.
|
||||
$material->update([
|
||||
'stock' => $material->stock + $item['quantity'], // Menambahkan kuantitas yang dibeli ke stok yang ada.
|
||||
'price' => $newPrice // Memperbarui harga bahan baku dengan harga rata-rata baru dari pembelian ini.
|
||||
]);
|
||||
|
||||
// Membuat entri log di tabel `raw_material_logs` untuk mencatat transaksi pembelian ini.
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $item['material_id'], // ID bahan baku yang dibeli.
|
||||
'user_id' => auth()->id(), // ID pengguna yang sedang login yang melakukan pembelian.
|
||||
'type' => 'in', // Tipe log adalah 'in' (masuk), menunjukkan penambahan stok.
|
||||
'quantity' => $item['quantity'], // Kuantitas yang dibeli.
|
||||
'price' => $newPrice, // Harga per unit yang baru dihitung.
|
||||
'subtotal' => $item['subtotal'], // Subtotal total untuk item ini.
|
||||
// Catatan log: jika ada input 'notes', gunakan itu; jika tidak, gunakan 'Pembelian bahan baku'.
|
||||
'notes' => $validated['notes'] ? $validated['notes'] : 'Pembelian bahan baku'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses setelah semua item berhasil disimpan.
|
||||
return redirect()->route('raw-materials.index')
|
||||
->with('success', 'Pembelian bahan baku berhasil disimpan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan form pemakaian bahan baku
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
$materials = \App\Models\RawMaterial::orderBy('name')->get();
|
||||
return view('raw-materials.usage', compact('materials'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Proses pemakaian bahan baku (multi-item)
|
||||
*/
|
||||
public function storeUsage(\Illuminate\Http\Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'items' => 'required|array',
|
||||
'items.*.material_id' => 'required|exists:raw_materials,id',
|
||||
'items.*.quantity' => 'required|numeric|min:0.01',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
$items = $request->items;
|
||||
$notes = $request->notes;
|
||||
DB::transaction(function () use ($items, $notes) {
|
||||
foreach ($items as $item) {
|
||||
$material = \App\Models\RawMaterial::findOrFail($item['material_id']);
|
||||
if ($material->stock < $item['quantity']) {
|
||||
throw new \Exception('Stok bahan baku ' . $material->name . ' tidak mencukupi.');
|
||||
}
|
||||
$material->decrement('stock', $item['quantity']);
|
||||
\App\Models\RawMaterialLog::create([
|
||||
'raw_material_id' => $material->id,
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'pemakaian',
|
||||
'quantity' => $item['quantity'],
|
||||
'price' => 0,
|
||||
'subtotal' => 0,
|
||||
'notes' => $notes ?? 'Pemakaian bahan baku',
|
||||
]);
|
||||
}
|
||||
});
|
||||
return redirect()->route('raw-materials.usage')->with('success', 'Pemakaian bahan baku berhasil diproses.');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Admin;
|
||||
use App\Models\Karyawan;
|
||||
use App\Models\Super;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RealRashid\SweetAlert\Facades\Alert;
|
||||
|
||||
class SuperController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$myData = Super::where('user_id', auth()->id())->first();
|
||||
|
||||
// Get statistics
|
||||
$totalUsers = User::whereIn('roles', ['admin', 'karyawan'])->count();
|
||||
$totalAdmins = User::where('roles', 'admin')->count();
|
||||
$totalKaryawan = User::where('roles', 'karyawan')->count();
|
||||
|
||||
// Get recent users
|
||||
$recentUsers = User::whereIn('roles', ['admin', 'karyawan'])
|
||||
->with(['admin', 'karyawan'])
|
||||
->latest()
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return view('pages.super.dashboard', compact('myData', 'totalUsers', 'totalAdmins', 'totalKaryawan', 'recentUsers'));
|
||||
}
|
||||
|
||||
public function manageUsers()
|
||||
{
|
||||
$users = User::whereIn('roles', ['admin', 'karyawan'])
|
||||
->with(['admin', 'karyawan'])
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
return view('pages.super.manage-users', compact('users'));
|
||||
}
|
||||
|
||||
public function createUser()
|
||||
{
|
||||
return view('pages.super.create-user');
|
||||
}
|
||||
|
||||
public function storeUser(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
// 'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|min:6',
|
||||
'roles' => 'required|in:admin,karyawan',
|
||||
'username' => 'required|string|max:255',
|
||||
'nama' => 'required|string|max:255',
|
||||
'no_telp' => 'required|string|max:20',
|
||||
'alamat' => 'required|string',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Create user
|
||||
$user = User::create([
|
||||
// 'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
'roles' => $request->roles,
|
||||
'username' => $request->username,
|
||||
]);
|
||||
|
||||
// Create profile based on role
|
||||
if ($request->roles === 'admin') {
|
||||
Admin::create([
|
||||
'user_id' => $user->id,
|
||||
'nama' => $request->nama,
|
||||
'no_telp' => $request->no_telp,
|
||||
'alamat' => $request->alamat,
|
||||
]);
|
||||
} else {
|
||||
Karyawan::create([
|
||||
'user_id' => $user->id,
|
||||
'nama' => $request->nama,
|
||||
'no_telp' => $request->no_telp,
|
||||
'alamat' => $request->alamat,
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
Alert::success('Berhasil', 'User berhasil ditambahkan');
|
||||
return redirect()->route('super.manage-users');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
Alert::error('Gagal', 'Terjadi kesalahan saat menambahkan user');
|
||||
return back()->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function editUser($id)
|
||||
{
|
||||
$user = User::with(['admin', 'karyawan'])->findOrFail($id);
|
||||
|
||||
if (!in_array($user->roles, ['admin', 'karyawan'])) {
|
||||
Alert::error('Gagal', 'User tidak dapat diedit');
|
||||
return redirect()->route('super.manage-users');
|
||||
}
|
||||
|
||||
return view('pages.super.edit-user', compact('user'));
|
||||
}
|
||||
|
||||
public function updateUser(Request $request, $id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
// 'email' => 'required|email|unique:users,email,' . $id,
|
||||
'password' => 'nullable|min:6',
|
||||
'roles' => 'required|in:admin,karyawan',
|
||||
'username' => 'required|string|max:255',
|
||||
'nama' => 'required|string|max:255',
|
||||
'no_telp' => 'required|string|max:20',
|
||||
'alamat' => 'required|string',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Update user
|
||||
$userData = [
|
||||
// 'email' => $request->email,
|
||||
'roles' => $request->roles,
|
||||
'username' => $request->username,
|
||||
];
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$userData['password'] = Hash::make($request->password);
|
||||
}
|
||||
|
||||
$user->update($userData);
|
||||
|
||||
// Update profile based on current role
|
||||
$profileData = [
|
||||
'nama' => $request->nama,
|
||||
'no_telp' => $request->no_telp,
|
||||
'alamat' => $request->alamat,
|
||||
];
|
||||
|
||||
// If role changed, delete old profile and create new one
|
||||
if ($user->roles !== $request->roles) {
|
||||
if ($user->admin) {
|
||||
$user->admin->delete();
|
||||
}
|
||||
if ($user->karyawan) {
|
||||
$user->karyawan->delete();
|
||||
}
|
||||
|
||||
if ($request->roles === 'admin') {
|
||||
Admin::create(array_merge($profileData, ['user_id' => $user->id]));
|
||||
} else {
|
||||
Karyawan::create(array_merge($profileData, ['user_id' => $user->id]));
|
||||
}
|
||||
} else {
|
||||
// Update existing profile
|
||||
if ($user->admin) {
|
||||
$user->admin->update($profileData);
|
||||
} elseif ($user->karyawan) {
|
||||
$user->karyawan->update($profileData);
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
Alert::success('Berhasil', 'User berhasil diperbarui');
|
||||
return redirect()->route('super.manage-users');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
Alert::error('Gagal', 'Terjadi kesalahan saat memperbarui user');
|
||||
return back()->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteUser($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
if (!in_array($user->roles, ['admin', 'karyawan'])) {
|
||||
Alert::error('Gagal', 'User tidak dapat dihapus');
|
||||
return redirect()->route('super.manage-users');
|
||||
}
|
||||
|
||||
try {
|
||||
$user->delete();
|
||||
Alert::success('Berhasil', 'User berhasil dihapus');
|
||||
} catch (\Exception $e) {
|
||||
Alert::error('Gagal', 'Terjadi kesalahan saat menghapus user');
|
||||
}
|
||||
|
||||
return redirect()->route('super.manage-users');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\Produk;
|
||||
use App\Models\TransaksiDetail;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TransaksiController extends Controller
|
||||
{
|
||||
// Tampilkan daftar transaksi
|
||||
public function index()
|
||||
{
|
||||
$transaksis = Transaksi::orderBy('tanggal_transaksi', 'desc')->get();
|
||||
return view('pages.admin.Transaksi.index', compact('transaksis'));
|
||||
}
|
||||
|
||||
// Tampilkan form tambah transaksi
|
||||
public function create()
|
||||
{
|
||||
$produks = Produk::all();
|
||||
return view('pages.admin.Transaksi.create', compact('produks'));
|
||||
}
|
||||
|
||||
// Simpan transaksi baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'tanggal_transaksi' => 'required|date',
|
||||
'waktu_transaksi' => 'required',
|
||||
'total_harga' => 'required|numeric|min:0',
|
||||
'nama_pelanggan' => 'required|string|max:255',
|
||||
'no_telp' => 'required|string|max:15',
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.produk_id' => 'required|exists:produks,id',
|
||||
'items.*.jumlah' => 'required|numeric|min:1',
|
||||
'items.*.harga' => 'required|numeric|min:0',
|
||||
'items.*.subtotal' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Generate kode transaksi
|
||||
$lastTransaksi = Transaksi::latest()->first();
|
||||
$lastNumber = $lastTransaksi ? intval(substr($lastTransaksi->kode_transaksi, -4)) : 0;
|
||||
$newNumber = str_pad($lastNumber + 1, 4, '0', STR_PAD_LEFT);
|
||||
$kodeTransaksi = 'TRX-' . date('Ymd') . '-' . $newNumber;
|
||||
|
||||
// Buat transaksi baru
|
||||
$transaksi = Transaksi::create([
|
||||
'kode_transaksi' => $kodeTransaksi,
|
||||
'tanggal_transaksi' => Carbon::parse($request->tanggal_transaksi . ' ' . $request->waktu_transaksi),
|
||||
'total_harga' => $request->total_harga,
|
||||
'customer_name' => $request->nama_pelanggan,
|
||||
'customer_phone' => $request->no_telp,
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Simpan detail transaksi
|
||||
foreach ($request->items as $item) {
|
||||
$produk = Produk::findOrFail($item['produk_id']);
|
||||
|
||||
// Validasi stok
|
||||
if ($produk->stok_produk < $item['jumlah']) {
|
||||
throw new \Exception("Stok {$produk->nama_produk} tidak mencukupi!");
|
||||
}
|
||||
|
||||
// Kurangi stok produk
|
||||
$produk->decrement('stok_produk', $item['jumlah']);
|
||||
|
||||
// Simpan detail transaksi
|
||||
TransaksiDetail::create([
|
||||
'transaksi_id' => $transaksi->id,
|
||||
'produk_id' => $item['produk_id'],
|
||||
'quantity' => $item['jumlah'],
|
||||
'price' => $item['harga'],
|
||||
'subtotal' => $item['subtotal']
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('transaksi.index')->with('success', 'Transaksi berhasil ditambahkan.');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return redirect()->back()->with('error', $e->getMessage())->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// Tampilkan form edit transaksi
|
||||
public function edit($id)
|
||||
{
|
||||
$transaksi = Transaksi::with('details.produk')->findOrFail($id);
|
||||
$produks = Produk::all();
|
||||
return view('pages.admin.Transaksi.edit', compact('transaksi', 'produks'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'tanggal_transaksi' => 'required|date',
|
||||
'waktu_transaksi' => 'required',
|
||||
'total_harga' => 'required|numeric|min:0',
|
||||
'nama_pelanggan' => 'required|string|max:255',
|
||||
'no_telp' => 'required|string|max:15',
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.produk_id' => 'required|exists:produks,id',
|
||||
'items.*.jumlah' => 'required|numeric|min:1',
|
||||
'items.*.harga' => 'required|numeric|min:0',
|
||||
'items.*.subtotal' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$transaksi = Transaksi::findOrFail($id);
|
||||
|
||||
// Update data transaksi
|
||||
$transaksi->update([
|
||||
'tanggal_transaksi' => Carbon::parse($request->tanggal_transaksi . ' ' . $request->waktu_transaksi),
|
||||
'total_harga' => $request->total_harga,
|
||||
'customer_name' => $request->nama_pelanggan,
|
||||
'customer_phone' => $request->no_telp,
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Kembalikan stok produk dari detail transaksi lama
|
||||
foreach ($transaksi->details as $detail) {
|
||||
$detail->produk->increment('stok_produk', $detail->quantity);
|
||||
}
|
||||
|
||||
// Hapus detail transaksi lama
|
||||
$transaksi->details()->delete();
|
||||
|
||||
// Simpan detail transaksi baru
|
||||
foreach ($request->items as $item) {
|
||||
$produk = Produk::findOrFail($item['produk_id']);
|
||||
|
||||
// Validasi stok
|
||||
if ($produk->stok_produk < $item['jumlah']) {
|
||||
throw new \Exception("Stok {$produk->nama_produk} tidak mencukupi!");
|
||||
}
|
||||
|
||||
// Kurangi stok produk
|
||||
$produk->decrement('stok_produk', $item['jumlah']);
|
||||
|
||||
// Simpan detail transaksi
|
||||
TransaksiDetail::create([
|
||||
'transaksi_id' => $transaksi->id,
|
||||
'produk_id' => $item['produk_id'],
|
||||
'quantity' => $item['jumlah'],
|
||||
'price' => $item['harga'],
|
||||
'subtotal' => $item['subtotal']
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('transaksi.index')->with('success', 'Transaksi berhasil diupdate.');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return redirect()->back()->with('error', $e->getMessage())->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// Hapus transaksi
|
||||
public function destroy($id)
|
||||
{
|
||||
$transaksi = Transaksi::findOrFail($id);
|
||||
$transaksi->delete();
|
||||
|
||||
return redirect()->route('transaksi.index')->with('success', 'Transaksi berhasil dihapus.');
|
||||
}
|
||||
|
||||
// Export to CSV
|
||||
public function exportExcel()
|
||||
{
|
||||
$transaksis = Transaksi::with('details.produk')->orderBy('tanggal_transaksi', 'desc')->get();
|
||||
|
||||
$filename = 'laporan_transaksi_' . date('Y-m-d') . '.csv';
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => "attachment; filename=\"$filename\"",
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
|
||||
'Expires' => '0'
|
||||
];
|
||||
|
||||
$columns = ['No', 'ID Transaksi', 'Tanggal', 'Total Harga', 'Detail Produk'];
|
||||
|
||||
$callback = function() use ($transaksis, $columns) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fputcsv($file, $columns);
|
||||
|
||||
foreach ($transaksis as $index => $transaksi) {
|
||||
$details = [];
|
||||
foreach($transaksi->details as $detail) {
|
||||
$details[] = $detail->produk->nama_produk . ' (' . $detail->jumlah . ' x ' . number_format($detail->harga_satuan, 0, ',', '.') . ')';
|
||||
}
|
||||
|
||||
fputcsv($file, [
|
||||
$index + 1,
|
||||
'#' . $transaksi->id,
|
||||
$transaksi->tanggal_transaksi,
|
||||
$transaksi->total_harga,
|
||||
implode(', ', $details)
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\TransaksiDetail;
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\Produk;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TransaksiDetailController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$details = TransaksiDetail::with(['transaksi', 'produk'])->get();
|
||||
return view('pages.admin.Transaksi_detail.index', compact('details'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$transaksis = Transaksi::all();
|
||||
$produks = Produk::all();
|
||||
return view('pages.admin.Transaksi_detail.create', compact('transaksis', 'produks'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'transaksi_id' => 'required|exists:transaksi,id',
|
||||
'produk_id' => 'required|exists:produks,id',
|
||||
'price' => 'required|integer|min:0',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
TransaksiDetail::create($request->all());
|
||||
|
||||
return redirect()->route('transaksidetail.index')->with('success', 'Detail transaksi berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$detail = TransaksiDetail::findOrFail($id);
|
||||
$transaksis = Transaksi::all();
|
||||
$produks = Produk::all();
|
||||
return view('pages.admin.Transaksi_detail.edit', compact('detail', 'transaksis', 'produks'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'transaksi_id' => 'required|exists:transaksi,id',
|
||||
'produk_id' => 'required|exists:produks,id',
|
||||
'price' => 'required|integer|min:0',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$detail = TransaksiDetail::findOrFail($id);
|
||||
$detail->update($request->all());
|
||||
|
||||
return redirect()->route('transaksidetail.index')->with('success', 'Detail transaksi berhasil diupdate.');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$detail = TransaksiDetail::findOrFail($id);
|
||||
$detail->delete();
|
||||
|
||||
return redirect()->route('transaksidetail.index')->with('success', 'Detail transaksi berhasil dihapus.');
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$transaksi = Transaksi::with(['details.produk'])->findOrFail($id);
|
||||
$details = $transaksi->details;
|
||||
|
||||
return view('pages.admin.Transaksi_detail.show', [
|
||||
'details' => $details,
|
||||
'transaksi' => $transaksi,
|
||||
'tanggal' => \Carbon\Carbon::parse($transaksi->tanggal_transaksi)->format('d-m-Y'),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\RealRashid\SweetAlert\ToSweetAlert::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'admin' => \App\Http\Middleware\AdminMiddleware::class,
|
||||
'admin-karyawan' => \App\Http\Middleware\AdminKaryawanMiddleware::class,
|
||||
'super' => \App\Http\Middleware\SuperMiddleware::class,
|
||||
];
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdminKaryawanMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (Auth::check() && (Auth::user()->roles === 'admin' || Auth::user()->roles === 'karyawan') || Auth::user()->roles === 'super') {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return redirect('/dashboard')->with('error', 'Anda tidak memiliki akses ke halaman ini');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdminMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (Auth::check() && Auth::user()->roles === 'admin' || Auth::user()->roles === 'super') {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return redirect('/dashboard')->with('error', 'Anda tidak memiliki akses ke halaman ini');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*/
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
return $request->expectsJson() ? null : route('login');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class SuperMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (Auth::check() && Auth::user()->roles === 'super') {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return redirect('/dashboard')->with('error', 'Anda tidak memiliki akses ke halaman ini');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts(): array
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorelaporanpenjualanRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatelaporanpenjualanRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Admin extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'nama',
|
||||
'no_telp',
|
||||
'alamat'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Karyawan extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
// Tentukan nama tabel
|
||||
protected $table = 'karyawan';
|
||||
|
||||
// Kolom yang bisa diisi secara massal
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'nama',
|
||||
'no_telp',
|
||||
'alamat',
|
||||
'foto',
|
||||
];
|
||||
|
||||
// Menyertakan kolom deleted_at untuk soft delete
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
// Relasi ke User (satu user bisa memiliki satu karyawan)
|
||||
public function user()
|
||||
{
|
||||
// Relasi karyawan ke user dengan soft delete
|
||||
return $this->belongsTo(User::class, 'user_id')->withTrashed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Helpers\UnitConverter;
|
||||
|
||||
class ProductRecipe extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'produk_id',
|
||||
'raw_material_id',
|
||||
'quantity',
|
||||
'unit',
|
||||
'notes'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'decimal:3'
|
||||
];
|
||||
|
||||
// Available units for recipes
|
||||
const AVAILABLE_UNITS = ['g', 'kg', 'ml', 'l', 'pcs'];
|
||||
|
||||
public function produk()
|
||||
{
|
||||
return $this->belongsTo(Produk::class);
|
||||
}
|
||||
|
||||
public function rawMaterial()
|
||||
{
|
||||
return $this->belongsTo(RawMaterial::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quantity in material's unit
|
||||
*/
|
||||
public function getQuantityInMaterialUnit()
|
||||
{
|
||||
if (!$this->rawMaterial) {
|
||||
throw new \Exception("Raw material not found for this recipe.");
|
||||
}
|
||||
try {
|
||||
return UnitConverter::convert(
|
||||
$this->quantity,
|
||||
$this->unit,
|
||||
$this->rawMaterial->unit
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new \Exception("Cannot convert recipe unit {$this->unit} to material unit " . ($this->rawMaterial ? $this->rawMaterial->unit : 'null'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if material has sufficient stock for this recipe
|
||||
*/
|
||||
public function hasSufficientStock($multiplier = 1)
|
||||
{
|
||||
// Handle if rawMaterial is null
|
||||
if (!$this->rawMaterial) {
|
||||
return false;
|
||||
}
|
||||
return $this->rawMaterial->hasSufficientStock(
|
||||
$this->quantity * $multiplier,
|
||||
$this->unit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required amount in material's unit
|
||||
*/
|
||||
public function getRequiredAmountInMaterialUnit($multiplier = 1)
|
||||
{
|
||||
if (!$this->rawMaterial) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return UnitConverter::convert(
|
||||
$this->quantity * $multiplier,
|
||||
$this->unit,
|
||||
$this->rawMaterial->unit
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume materials for production
|
||||
*/
|
||||
public function consumeForProduction($multiplier = 1, $notes = null)
|
||||
{
|
||||
if (!$this->rawMaterial) {
|
||||
throw new \Exception("Raw material not found for this recipe.");
|
||||
}
|
||||
$requiredAmount = $this->quantity * $multiplier;
|
||||
$productName = $this->produk->nama_produk ?? 'Unknown Product';
|
||||
$defaultNotes = "Production of {$productName} - Recipe item: {$this->rawMaterial->name}";
|
||||
|
||||
return $this->rawMaterial->reduceStock(
|
||||
$requiredAmount,
|
||||
$this->unit,
|
||||
$notes ?? $defaultNotes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted quantity with unit
|
||||
*/
|
||||
public function getFormattedQuantity()
|
||||
{
|
||||
return UnitConverter::formatValue($this->quantity, $this->unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if recipe unit is compatible with material unit
|
||||
*/
|
||||
public function validateUnitCompatibility()
|
||||
{
|
||||
if (!$this->rawMaterial) {
|
||||
return false;
|
||||
}
|
||||
return UnitConverter::areUnitsCompatible($this->unit, $this->rawMaterial->unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available units for this recipe based on material
|
||||
*/
|
||||
public function getAvailableUnits()
|
||||
{
|
||||
if (!$this->rawMaterial) {
|
||||
return [];
|
||||
}
|
||||
$materialCategory = $this->rawMaterial->getUnitCategory();
|
||||
return UnitConverter::getUnitsByCategory($materialCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot method to add model events
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Validate unit compatibility before saving
|
||||
static::saving(function ($recipe) {
|
||||
if ($recipe->rawMaterial && !$recipe->validateUnitCompatibility()) {
|
||||
throw new \Exception(
|
||||
"Recipe unit '{$recipe->unit}' is not compatible with material unit '{$recipe->rawMaterial->unit}'"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Helpers\UnitConverter;
|
||||
|
||||
class RawMaterial extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'price',
|
||||
'stock',
|
||||
'minimum_stock',
|
||||
'unit',
|
||||
'description'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'stock' => 'decimal:3',
|
||||
'price' => 'decimal:2',
|
||||
'minimum_stock' => 'decimal:3',
|
||||
];
|
||||
|
||||
// Available units
|
||||
const AVAILABLE_UNITS = ['g', 'kg', 'ml', 'l', 'pcs'];
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(RawMaterialLog::class);
|
||||
}
|
||||
|
||||
public function recipes()
|
||||
{
|
||||
return $this->hasMany(ProductRecipe::class);
|
||||
}
|
||||
|
||||
public function isLowStock()
|
||||
{
|
||||
return $this->stock <= $this->minimum_stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock in different unit
|
||||
*/
|
||||
public function getStockInUnit($targetUnit)
|
||||
{
|
||||
try {
|
||||
return UnitConverter::convert($this->stock, $this->unit, $targetUnit);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null; // Units not compatible
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if stock is sufficient for required amount in any unit
|
||||
*/
|
||||
public function hasSufficientStock($requiredAmount, $requiredUnit)
|
||||
{
|
||||
try {
|
||||
$requiredInCurrentUnit = UnitConverter::convert($requiredAmount, $requiredUnit, $this->unit);
|
||||
return $this->stock >= $requiredInCurrentUnit;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return false; // Units not compatible
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce stock by amount in specified unit
|
||||
*/
|
||||
public function reduceStock($amount, $unit, $notes = null)
|
||||
{
|
||||
try {
|
||||
$amountInCurrentUnit = UnitConverter::convert($amount, $unit, $this->unit);
|
||||
|
||||
if ($this->stock < $amountInCurrentUnit) {
|
||||
throw new \Exception("Insufficient stock. Available: {$this->stock} {$this->unit}, Required: {$amountInCurrentUnit} {$this->unit}");
|
||||
}
|
||||
|
||||
$this->stock -= $amountInCurrentUnit;
|
||||
$this->save();
|
||||
|
||||
// Log the reduction
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $this->id,
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'production',
|
||||
'quantity' => -$amountInCurrentUnit,
|
||||
'notes' => $notes ?? "Stock reduced for production: {$amount} {$unit}"
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new \Exception("Cannot convert units: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add stock by amount in specified unit
|
||||
*/
|
||||
public function addStock($amount, $unit, $price = null, $notes = null)
|
||||
{
|
||||
try {
|
||||
$amountInCurrentUnit = UnitConverter::convert($amount, $unit, $this->unit);
|
||||
|
||||
$this->stock += $amountInCurrentUnit;
|
||||
$this->save();
|
||||
|
||||
// Log the addition
|
||||
RawMaterialLog::create([
|
||||
'raw_material_id' => $this->id,
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'in',
|
||||
'quantity' => $amountInCurrentUnit,
|
||||
'price' => $price,
|
||||
'subtotal' => $price ? $price * $amount : 0,
|
||||
'notes' => $notes ?? "Stock added: {$amount} {$unit}"
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new \Exception("Cannot convert units: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted stock with unit
|
||||
*/
|
||||
public function getFormattedStock()
|
||||
{
|
||||
return UnitConverter::formatValue($this->stock, $this->unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unit category
|
||||
*/
|
||||
public function getUnitCategory()
|
||||
{
|
||||
return UnitConverter::getUnitCategory($this->unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compatible units for this material
|
||||
*/
|
||||
public function getCompatibleUnits()
|
||||
{
|
||||
$category = $this->getUnitCategory();
|
||||
return UnitConverter::getUnitsByCategory($category);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class RawMaterialLog extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'raw_material_id',
|
||||
'user_id',
|
||||
'type',
|
||||
'quantity',
|
||||
'price',
|
||||
'subtotal',
|
||||
'notes'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'decimal:2',
|
||||
'price' => 'decimal:2',
|
||||
'subtotal' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function rawMaterial()
|
||||
{
|
||||
return $this->belongsTo(RawMaterial::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Super extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'nama',
|
||||
'no_telp',
|
||||
'alamat'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Supplier extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'phone',
|
||||
'address',
|
||||
'user_id'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function rawMaterialLogs()
|
||||
{
|
||||
return $this->hasMany(RawMaterialLog::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
// 'email',
|
||||
'username',
|
||||
'name',
|
||||
'password',
|
||||
'roles',
|
||||
'last_login',
|
||||
];
|
||||
protected $dates = ['deleted_at', 'last_login'];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'last_login' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Valid roles for the application
|
||||
*/
|
||||
const VALID_ROLES = ['admin', 'karyawan', 'super'];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function ($user) {
|
||||
// Soft delete related models when user is soft deleted
|
||||
if ($user->karyawan) {
|
||||
$user->karyawan->delete();
|
||||
}
|
||||
if ($user->super) {
|
||||
$user->super->delete();
|
||||
}
|
||||
// if ($user->pelanggan) {
|
||||
// $user->pelanggan->delete();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
public function karyawan()
|
||||
{
|
||||
return $this->hasOne(Karyawan::class);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->hasOne(Admin::class);
|
||||
}
|
||||
|
||||
public function super()
|
||||
{
|
||||
return $this->hasOne(Super::class);
|
||||
}
|
||||
|
||||
public function suppliers()
|
||||
{
|
||||
return $this->hasMany(Supplier::class);
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->roles === 'admin';
|
||||
}
|
||||
|
||||
public function getProfileData()
|
||||
{
|
||||
switch ($this->roles) {
|
||||
case 'super':
|
||||
return $this->super;
|
||||
case 'admin':
|
||||
return $this->admin;
|
||||
case 'karyawan':
|
||||
return $this->karyawan;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has a specific role
|
||||
*/
|
||||
public function hasRole($role)
|
||||
{
|
||||
return $this->roles === $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user's role is valid
|
||||
*/
|
||||
public function hasValidRole()
|
||||
{
|
||||
return in_array($this->roles, self::VALID_ROLES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class LaporanPenjualan extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'laporanpenjualans';
|
||||
|
||||
protected $fillable = [
|
||||
'tanggal',
|
||||
'total_penjualan',
|
||||
'total_produk_terjual',
|
||||
'jumlah_transaksi',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'tanggal',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at'
|
||||
];
|
||||
|
||||
public $timestamps = true;
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Produk extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'produks';
|
||||
|
||||
protected $fillable = [
|
||||
'nama_produk',
|
||||
'harga_produk',
|
||||
'stok_produk',
|
||||
'foto',
|
||||
'deskripsi_produk',
|
||||
];
|
||||
|
||||
public function recipes()
|
||||
{
|
||||
return $this->hasMany(ProductRecipe::class, 'produk_id');
|
||||
}
|
||||
|
||||
public function hasCompleteRecipe()
|
||||
{
|
||||
return $this->recipes()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all materials are available for production
|
||||
*/
|
||||
public function canProduce($quantity = 1)
|
||||
{
|
||||
if (!$this->hasCompleteRecipe()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->recipes as $recipe) {
|
||||
if (!$recipe->hasSufficientStock($quantity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get missing materials for production
|
||||
*/
|
||||
public function getMissingMaterials($quantity = 1)
|
||||
{
|
||||
$missing = [];
|
||||
|
||||
foreach ($this->recipes as $recipe) {
|
||||
if (!$recipe->hasSufficientStock($quantity)) {
|
||||
$required = $recipe->getRequiredAmountInMaterialUnit($quantity);
|
||||
$rawMaterial = $recipe->rawMaterial;
|
||||
$available = $rawMaterial ? $rawMaterial->stock : 0;
|
||||
$shortage = $required - $available;
|
||||
|
||||
$missing[] = [
|
||||
'material' => $rawMaterial,
|
||||
'required' => $required,
|
||||
'available' => $available,
|
||||
'shortage' => $shortage,
|
||||
'unit' => $rawMaterial ? $rawMaterial->unit : null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the product (consume materials and increase stock)
|
||||
*/
|
||||
public function produce($quantity = 1, $notes = null)
|
||||
{
|
||||
if (!$this->canProduce($quantity)) {
|
||||
$missing = $this->getMissingMaterials($quantity);
|
||||
$missingList = collect($missing)->map(function($item) {
|
||||
return $item['material']->name . ' (shortage: ' . $item['shortage'] . ' ' . $item['unit'] . ')';
|
||||
})->join(', ');
|
||||
|
||||
throw new \Exception("Cannot produce {$quantity} units. Missing materials: " . $missingList);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Consume all materials
|
||||
foreach ($this->recipes as $recipe) {
|
||||
$recipe->consumeForProduction($quantity, $notes);
|
||||
}
|
||||
|
||||
// Increase product stock
|
||||
$this->stok_produk += $quantity;
|
||||
$this->save();
|
||||
|
||||
DB::commit();
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get production cost based on current material prices
|
||||
*/
|
||||
public function getProductionCost($quantity = 1)
|
||||
{
|
||||
$cost = 0;
|
||||
|
||||
foreach ($this->recipes as $recipe) {
|
||||
$requiredAmount = $recipe->getRequiredAmountInMaterialUnit($quantity);
|
||||
$materialCost = $requiredAmount * $recipe->rawMaterial->price;
|
||||
$cost += $materialCost;
|
||||
}
|
||||
|
||||
return $cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum producible quantity based on available materials
|
||||
*/
|
||||
public function getMaxProducibleQuantity()
|
||||
{
|
||||
if (!$this->hasCompleteRecipe()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$maxQuantity = PHP_INT_MAX;
|
||||
|
||||
foreach ($this->recipes as $recipe) {
|
||||
if (!$recipe->rawMaterial) {
|
||||
return 0; // Raw material not found
|
||||
}
|
||||
$availableInRecipeUnit = $recipe->rawMaterial->getStockInUnit($recipe->unit);
|
||||
if ($availableInRecipeUnit === null) {
|
||||
return 0; // Unit conversion not possible
|
||||
}
|
||||
|
||||
$possibleQuantity = floor($availableInRecipeUnit / $recipe->quantity);
|
||||
$maxQuantity = min($maxQuantity, $possibleQuantity);
|
||||
}
|
||||
|
||||
return $maxQuantity === PHP_INT_MAX ? 0 : $maxQuantity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Transaksi extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'transaksi';
|
||||
|
||||
protected $fillable = [
|
||||
'kode_transaksi',
|
||||
'tanggal_transaksi',
|
||||
'total_harga',
|
||||
'customer_name',
|
||||
'customer_phone',
|
||||
'user_id'
|
||||
];
|
||||
|
||||
protected $dates = ['tanggal_transaksi', 'created_at', 'updated_at', 'deleted_at'];
|
||||
|
||||
public function details()
|
||||
{
|
||||
return $this->hasMany(TransaksiDetail::class, 'transaksi_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TransaksiDetail extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'transaksi_details';
|
||||
|
||||
protected $fillable = [
|
||||
'transaksi_id',
|
||||
'produk_id',
|
||||
'quantity',
|
||||
'price',
|
||||
'subtotal'
|
||||
];
|
||||
|
||||
public function transaksi()
|
||||
{
|
||||
return $this->belongsTo(Transaksi::class, 'transaksi_id');
|
||||
}
|
||||
|
||||
public function produk()
|
||||
{
|
||||
return $this->belongsTo(Produk::class, 'produk_id');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\laporanpenjualan;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class LaporanpenjualanPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, laporanpenjualan $laporanpenjualan): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, laporanpenjualan $laporanpenjualan): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, laporanpenjualan $laporanpenjualan): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, laporanpenjualan $laporanpenjualan): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, laporanpenjualan $laporanpenjualan): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
// use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The model to policy mappings for the application.
|
||||
*
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [
|
||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
require base_path('routes/channels.php');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event to listener mappings for the application.
|
||||
*
|
||||
* @var array<class-string, array<int, class-string>>
|
||||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if events and listeners should be automatically discovered.
|
||||
*/
|
||||
public function shouldDiscoverEvents(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The path to the "home" route for your application.
|
||||
*
|
||||
* Typically, users are redirected here after authentication.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/dashboard';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, and other route configuration.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::middleware('api')
|
||||
->prefix('api')
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
->group(base_path('routes/web.php'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*/
|
||||
protected function configureRateLimiting(): void
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Models\SettingSistem;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class ViewServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Menggunakan View Composer untuk membagikan data ke semua view
|
||||
// View::composer('*', function ($view) {
|
||||
// $sistemData = SettingSistem::first();
|
||||
// $view->with('sistemData', $sistemData);
|
||||
// });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register The Auto Loader
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Composer provides a convenient, automatically generated class loader
|
||||
| for our application. We just need to utilize it! We'll require it
|
||||
| into the script here so that we do not have to worry about the
|
||||
| loading of any of our classes manually. It's great to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run The Artisan Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When we run the console application, the current CLI command will be
|
||||
| executed in this console and the response sent back to a terminal
|
||||
| or another output device for the developers. Here goes nothing!
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
|
||||
$status = $kernel->handle(
|
||||
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||
new Symfony\Component\Console\Output\ConsoleOutput
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Shutdown The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Once Artisan has finished running, we will fire off the shutdown events
|
||||
| so that any final work may be done by the application before we shut
|
||||
| down the process. This is the last thing to happen to the request.
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel->terminate($input, $status);
|
||||
|
||||
exit($status);
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The first thing we will do is create a new Laravel application instance
|
||||
| which serves as the "glue" for all the components of Laravel, and is
|
||||
| the IoC container for the system binding all of the various parts.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = new Illuminate\Foundation\Application(
|
||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bind Important Interfaces
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, we need to bind some important interfaces into the container so
|
||||
| we will be able to resolve them when needed. The kernels serve the
|
||||
| incoming requests to this application from both the web and CLI.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Http\Kernel::class,
|
||||
App\Http\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
App\Console\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||
App\Exceptions\Handler::class
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Return The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This script returns the application instance. The instance is given to
|
||||
| the calling script so we can separate the building of the instances
|
||||
| from the actual running of the application and sending responses.
|
||||
|
|
||||
*/
|
||||
|
||||
return $app;
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"keywords": ["framework", "laravel"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.6",
|
||||
"pusher/pusher-php-server": "^7.2",
|
||||
"realrashid/sweet-alert": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "10.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application. This value is used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| any other location as required by the application or its packages.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| your application so that it is used when running Artisan tasks.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
'asset_url' => env('ASSET_URL', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. We have gone
|
||||
| ahead and set this to a sensible default for you out of the box.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'Asia/Jakarta',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by the translation service provider. You are free to set this value
|
||||
| to any of the locales which will be supported by the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Fallback Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The fallback locale determines the locale to use when the current one
|
||||
| is not available. You may change the value to correspond to any of
|
||||
| the language folders that are provided through your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'fallback_locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Faker Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This locale will be used by the Faker PHP library when generating fake
|
||||
| data for your database seeds. For example, this will be used to get
|
||||
| localized telephone numbers, street address information and more.
|
||||
|
|
||||
*/
|
||||
|
||||
'faker_locale' => 'en_US',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is used by the Illuminate encrypter service and should be set
|
||||
| to a random, 32 character string, otherwise these encrypted strings
|
||||
| will not be safe. Please do this before deploying an application!
|
||||
|
|
||||
*/
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => 'file',
|
||||
// 'store' => 'redis',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The service providers listed here will be automatically loaded on the
|
||||
| request to your application. Feel free to add your own services to
|
||||
| this array to grant expanded functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
|
||||
/*
|
||||
* Laravel Framework Service Providers...
|
||||
*/
|
||||
Illuminate\Auth\AuthServiceProvider::class,
|
||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
||||
Illuminate\Bus\BusServiceProvider::class,
|
||||
Illuminate\Cache\CacheServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
||||
Illuminate\Cookie\CookieServiceProvider::class,
|
||||
Illuminate\Database\DatabaseServiceProvider::class,
|
||||
Illuminate\Encryption\EncryptionServiceProvider::class,
|
||||
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
||||
Illuminate\Hashing\HashServiceProvider::class,
|
||||
Illuminate\Mail\MailServiceProvider::class,
|
||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||
Illuminate\Pipeline\PipelineServiceProvider::class,
|
||||
Illuminate\Queue\QueueServiceProvider::class,
|
||||
Illuminate\Redis\RedisServiceProvider::class,
|
||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||
Illuminate\Session\SessionServiceProvider::class,
|
||||
Illuminate\Translation\TranslationServiceProvider::class,
|
||||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
RealRashid\SweetAlert\SweetAlertServiceProvider::class,
|
||||
Barryvdh\DomPDF\ServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\ViewServiceProvider::class, //mendaftarkan viewservise untuk view composer
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Aliases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array of class aliases will be registered when this application
|
||||
| is started. However, feel free to register as many as you wish as
|
||||
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||
|
|
||||
*/
|
||||
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
'Alert' => RealRashid\SweetAlert\Facades\Alert::class,
|
||||
'PDF' => Barryvdh\DomPDF\Facade\Pdf::class,
|
||||
])->toArray(),
|
||||
|
||||
];
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| separate password reset settings based on the specific user types.
|
||||
|
|
||||
| The expire time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => 'password_reset_tokens',
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
];
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Broadcaster
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default broadcaster that will be used by the
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| any of the connections defined in the "connections" array below.
|
||||
|
|
||||
| Supported: "pusher", "ably", "redis", "log", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('BROADCAST_DRIVER', 'null'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcast Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the broadcast connections that will be used
|
||||
| to broadcast events to other systems or over websockets. Samples of
|
||||
| each available type of connection are provided inside this array.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'ably' => [
|
||||
'driver' => 'ably',
|
||||
'key' => env('ABLY_KEY'),
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache connection that gets used while
|
||||
| using this caching library. This connection is used when another is
|
||||
| not explicitly specified when executing a given caching function.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_DRIVER', 'file'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "apc", "array", "database", "file",
|
||||
| "memcached", "redis", "dynamodb", "octane", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'apc' => [
|
||||
'driver' => 'apc',
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'cache',
|
||||
'connection' => null,
|
||||
'lock_connection' => null,
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'cache',
|
||||
'lock_connection' => 'default',
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
|
||||
| stores there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
|
||||
];
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your settings for cross-origin resource sharing
|
||||
| or "CORS". This determines what cross-origin operations may execute
|
||||
| in web browsers. You are free to adjust these settings as needed.
|
||||
|
|
||||
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => ['*'],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
'supports_credentials' => false,
|
||||
|
||||
];
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for all database work. Of course
|
||||
| you may use many connections at once using the Database library.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here are each of the database connections setup for your application.
|
||||
| Of course, examples of configuring each database platform that is
|
||||
| supported by Laravel is shown below to make development simple.
|
||||
|
|
||||
|
|
||||
| All database work in Laravel is done through the PHP PDO facilities
|
||||
| so make sure you have the driver for your particular database of
|
||||
| choice installed on your machine before you begin development.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run in the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => 'migrations',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set some default values. It is possible to add all defines that can be set
|
||||
| in dompdf_config.inc.php. You can also override the entire config file.
|
||||
|
|
||||
*/
|
||||
'show_warnings' => false, // Throw an Exception on warnings from dompdf
|
||||
|
||||
'public_path' => null, // Override the public path if needed
|
||||
|
||||
/*
|
||||
* Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show € and £.
|
||||
*/
|
||||
'convert_entities' => true,
|
||||
|
||||
'options' => [
|
||||
/**
|
||||
* The location of the DOMPDF font directory
|
||||
*
|
||||
* The location of the directory where DOMPDF will store fonts and font metrics
|
||||
* Note: This directory must exist and be writable by the webserver process.
|
||||
* *Please note the trailing slash.*
|
||||
*
|
||||
* Notes regarding fonts:
|
||||
* Additional .afm font metrics can be added by executing load_font.php from command line.
|
||||
*
|
||||
* Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must
|
||||
* be embedded in the pdf file or the PDF may not display correctly. This can significantly
|
||||
* increase file size unless font subsetting is enabled. Before embedding a font please
|
||||
* review your rights under the font license.
|
||||
*
|
||||
* Any font specification in the source HTML is translated to the closest font available
|
||||
* in the font directory.
|
||||
*
|
||||
* The pdf standard "Base 14 fonts" are:
|
||||
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
|
||||
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
|
||||
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
|
||||
* Symbol, ZapfDingbats.
|
||||
*/
|
||||
'font_dir' => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
|
||||
|
||||
/**
|
||||
* The location of the DOMPDF font cache directory
|
||||
*
|
||||
* This directory contains the cached font metrics for the fonts used by DOMPDF.
|
||||
* This directory can be the same as DOMPDF_FONT_DIR
|
||||
*
|
||||
* Note: This directory must exist and be writable by the webserver process.
|
||||
*/
|
||||
'font_cache' => storage_path('fonts'),
|
||||
|
||||
/**
|
||||
* The location of a temporary directory.
|
||||
*
|
||||
* The directory specified must be writeable by the webserver process.
|
||||
* The temporary directory is required to download remote images and when
|
||||
* using the PDFLib back end.
|
||||
*/
|
||||
'temp_dir' => sys_get_temp_dir(),
|
||||
|
||||
/**
|
||||
* ==== IMPORTANT ====
|
||||
*
|
||||
* dompdf's "chroot": Prevents dompdf from accessing system files or other
|
||||
* files on the webserver. All local files opened by dompdf must be in a
|
||||
* subdirectory of this directory. DO NOT set it to '/' since this could
|
||||
* allow an attacker to use dompdf to read any files on the server. This
|
||||
* should be an absolute path.
|
||||
* This is only checked on command line call by dompdf.php, but not by
|
||||
* direct class use like:
|
||||
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
|
||||
*/
|
||||
'chroot' => realpath(base_path()),
|
||||
|
||||
/**
|
||||
* Protocol whitelist
|
||||
*
|
||||
* Protocols and PHP wrappers allowed in URIs, and the validation rules
|
||||
* that determine if a resouce may be loaded. Full support is not guaranteed
|
||||
* for the protocols/wrappers specified
|
||||
* by this array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
'allowed_protocols' => [
|
||||
'data://' => ['rules' => []],
|
||||
'file://' => ['rules' => []],
|
||||
'http://' => ['rules' => []],
|
||||
'https://' => ['rules' => []],
|
||||
],
|
||||
|
||||
/**
|
||||
* Operational artifact (log files, temporary files) path validation
|
||||
*/
|
||||
'artifactPathValidation' => null,
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
'log_output_file' => null,
|
||||
|
||||
/**
|
||||
* Whether to enable font subsetting or not.
|
||||
*/
|
||||
'enable_font_subsetting' => false,
|
||||
|
||||
/**
|
||||
* The PDF rendering backend to use
|
||||
*
|
||||
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
|
||||
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
|
||||
* fall back on CPDF. 'GD' renders PDFs to graphic files.
|
||||
* {@link * Canvas_Factory} ultimately determines which rendering class to
|
||||
* instantiate based on this setting.
|
||||
*
|
||||
* Both PDFLib & CPDF rendering backends provide sufficient rendering
|
||||
* capabilities for dompdf, however additional features (e.g. object,
|
||||
* image and font support, etc.) differ between backends. Please see
|
||||
* {@link PDFLib_Adapter} for more information on the PDFLib backend
|
||||
* and {@link CPDF_Adapter} and lib/class.pdf.php for more information
|
||||
* on CPDF. Also see the documentation for each backend at the links
|
||||
* below.
|
||||
*
|
||||
* The GD rendering backend is a little different than PDFLib and
|
||||
* CPDF. Several features of CPDF and PDFLib are not supported or do
|
||||
* not make any sense when creating image files. For example,
|
||||
* multiple pages are not supported, nor are PDF 'objects'. Have a
|
||||
* look at {@link GD_Adapter} for more information. GD support is
|
||||
* experimental, so use it at your own risk.
|
||||
*
|
||||
* @link http://www.pdflib.com
|
||||
* @link http://www.ros.co.nz/pdf
|
||||
* @link http://www.php.net/image
|
||||
*/
|
||||
'pdf_backend' => 'CPDF',
|
||||
|
||||
/**
|
||||
* html target media view which should be rendered into pdf.
|
||||
* List of types and parsing rules for future extensions:
|
||||
* http://www.w3.org/TR/REC-html40/types.html
|
||||
* screen, tty, tv, projection, handheld, print, braille, aural, all
|
||||
* Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3.
|
||||
* Note, even though the generated pdf file is intended for print output,
|
||||
* the desired content might be different (e.g. screen or projection view of html file).
|
||||
* Therefore allow specification of content here.
|
||||
*/
|
||||
'default_media_type' => 'screen',
|
||||
|
||||
/**
|
||||
* The default paper size.
|
||||
*
|
||||
* North America standard is "letter"; other countries generally "a4"
|
||||
*
|
||||
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
|
||||
*/
|
||||
'default_paper_size' => 'a4',
|
||||
|
||||
/**
|
||||
* The default paper orientation.
|
||||
*
|
||||
* The orientation of the page (portrait or landscape).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
'default_paper_orientation' => 'portrait',
|
||||
|
||||
/**
|
||||
* The default font family
|
||||
*
|
||||
* Used if no suitable fonts can be found. This must exist in the font folder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
'default_font' => 'serif',
|
||||
|
||||
/**
|
||||
* Image DPI setting
|
||||
*
|
||||
* This setting determines the default DPI setting for images and fonts. The
|
||||
* DPI may be overridden for inline images by explictly setting the
|
||||
* image's width & height style attributes (i.e. if the image's native
|
||||
* width is 600 pixels and you specify the image's width as 72 points,
|
||||
* the image will have a DPI of 600 in the rendered PDF. The DPI of
|
||||
* background images can not be overridden and is controlled entirely
|
||||
* via this parameter.
|
||||
*
|
||||
* For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
|
||||
* If a size in html is given as px (or without unit as image size),
|
||||
* this tells the corresponding size in pt.
|
||||
* This adjusts the relative sizes to be similar to the rendering of the
|
||||
* html page in a reference browser.
|
||||
*
|
||||
* In pdf, always 1 pt = 1/72 inch
|
||||
*
|
||||
* Rendering resolution of various browsers in px per inch:
|
||||
* Windows Firefox and Internet Explorer:
|
||||
* SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:?
|
||||
* Linux Firefox:
|
||||
* about:config *resolution: Default:96
|
||||
* (xorg screen dimension in mm and Desktop font dpi settings are ignored)
|
||||
*
|
||||
* Take care about extra font/image zoom factor of browser.
|
||||
*
|
||||
* In images, <img> size in pixel attribute, img css style, are overriding
|
||||
* the real image dimension in px for rendering.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
'dpi' => 96,
|
||||
|
||||
/**
|
||||
* Enable embedded PHP
|
||||
*
|
||||
* If this setting is set to true then DOMPDF will automatically evaluate embedded PHP contained
|
||||
* within <script type="text/php"> ... </script> tags.
|
||||
*
|
||||
* ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages)
|
||||
* is a security risk.
|
||||
* Embedded scripts are run with the same level of system access available to dompdf.
|
||||
* Set this option to false (recommended) if you wish to process untrusted documents.
|
||||
* This setting may increase the risk of system exploit.
|
||||
* Do not change this settings without understanding the consequences.
|
||||
* Additional documentation is available on the dompdf wiki at:
|
||||
* https://github.com/dompdf/dompdf/wiki
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
'enable_php' => false,
|
||||
|
||||
/**
|
||||
* Rnable inline JavaScript
|
||||
*
|
||||
* If this setting is set to true then DOMPDF will automatically insert JavaScript code contained
|
||||
* within <script type="text/javascript"> ... </script> tags as written into the PDF.
|
||||
* NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
|
||||
* not browser-based JavaScript executed by Dompdf.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
'enable_javascript' => true,
|
||||
|
||||
/**
|
||||
* Enable remote file access
|
||||
*
|
||||
* If this setting is set to true, DOMPDF will access remote sites for
|
||||
* images and CSS files as required.
|
||||
*
|
||||
* ==== IMPORTANT ====
|
||||
* This can be a security risk, in particular in combination with isPhpEnabled and
|
||||
* allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
|
||||
* This allows anonymous users to download legally doubtful internet content which on
|
||||
* tracing back appears to being downloaded by your server, or allows malicious php code
|
||||
* in remote html pages to be executed by your server with your account privileges.
|
||||
*
|
||||
* This setting may increase the risk of system exploit. Do not change
|
||||
* this settings without understanding the consequences. Additional
|
||||
* documentation is available on the dompdf wiki at:
|
||||
* https://github.com/dompdf/dompdf/wiki
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
'enable_remote' => false,
|
||||
|
||||
/**
|
||||
* List of allowed remote hosts
|
||||
*
|
||||
* Each value of the array must be a valid hostname.
|
||||
*
|
||||
* This will be used to filter which resources can be loaded in combination with
|
||||
* isRemoteEnabled. If enable_remote is FALSE, then this will have no effect.
|
||||
*
|
||||
* Leave to NULL to allow any remote host.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
'allowed_remote_hosts' => null,
|
||||
|
||||
/**
|
||||
* A ratio applied to the fonts height to be more like browsers' line height
|
||||
*/
|
||||
'font_height_ratio' => 1.1,
|
||||
|
||||
/**
|
||||
* Use the HTML5 Lib parser
|
||||
*
|
||||
* @deprecated This feature is now always on in dompdf 2.x
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
'enable_html5_parser' => true,
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application. Just store away!
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure as many filesystem "disks" as you wish, and you
|
||||
| may even configure multiple disks of the same driver. Defaults have
|
||||
| been set up for each driver as an example of the required values.
|
||||
|
|
||||
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Hash Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default hash driver that will be used to hash
|
||||
| passwords for your application. By default, the bcrypt algorithm is
|
||||
| used; however, you remain free to modify this option if you wish.
|
||||
|
|
||||
| Supported: "bcrypt", "argon", "argon2id"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'bcrypt',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bcrypt Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Bcrypt algorithm. This will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'bcrypt' => [
|
||||
'rounds' => env('BCRYPT_ROUNDS', 10),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Argon Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Argon algorithm. These will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'argon' => [
|
||||
'memory' => 65536,
|
||||
'threads' => 1,
|
||||
'time' => 4,
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that gets used when writing
|
||||
| messages to the logs. The name specified in this option should match
|
||||
| one of the channels defined in the "channels" configuration array.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Out of
|
||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
| you a variety of powerful log handlers / formatters to utilize.
|
||||
|
|
||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog",
|
||||
| "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => 14,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => 'Laravel Log',
|
||||
'emoji' => ':boom:',
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send any email
|
||||
| messages sent by your application. Alternative mailers may be setup
|
||||
| and used as needed; however, this mailer will be used by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'smtp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers to be used while
|
||||
| sending an e-mail. You will specify which one you are using for your
|
||||
| mailers below. You are free to add additional mailers as required.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
||||
| "postmark", "log", "array", "failover"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'transport' => 'mailgun',
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all e-mails sent by your application to be sent from
|
||||
| the same address. Here, you may specify a name and address that is
|
||||
| used globally for all e-mails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Markdown Mail Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you are using Markdown based email rendering, you may configure your
|
||||
| theme and component paths here, allowing you to customize the design
|
||||
| of the emails. Or, you may simply stick with the Laravel defaults!
|
||||
|
|
||||
*/
|
||||
|
||||
'markdown' => [
|
||||
'theme' => 'default',
|
||||
|
||||
'paths' => [
|
||||
resource_path('views/vendor/mail'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue API supports an assortment of back-ends via a single
|
||||
| API, giving you convenient access to each back-end using the same
|
||||
| syntax for every one. Here you may define a default connection.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'sync'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection information for each server that
|
||||
| is used by your application. A default configuration has been added
|
||||
| for each back-end shipped with Laravel. You are free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => 'localhost',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 90,
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control which database and table are used to store the jobs that
|
||||
| have failed. You may change them to any database / table you wish.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort()
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. If this value is null, personal access tokens do
|
||||
| not expire. This won't tweak the lifetime of first-party sessions.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
||||
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'mailgun' => [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default session "driver" that will be used on
|
||||
| requests. By default, we will use the lightweight native driver but
|
||||
| you may specify any of the other wonderful drivers provided here.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "apc",
|
||||
| "memcached", "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'file'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to immediately expire on the browser closing, set that option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it is stored. All encryption will be run
|
||||
| automatically by Laravel and you can use the Session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the native session driver, we need a location where session
|
||||
| files may be stored. A default has been set for you but a different
|
||||
| location may be specified. This is only needed for file sessions.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table we
|
||||
| should use to manage the sessions. Of course, a sensible default is
|
||||
| provided for you; however, you are free to change this as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => 'sessions',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using one of the framework's cache driven session backends you may
|
||||
| list a cache store that should be used for these sessions. This value
|
||||
| must match with one of the application's configured cache "stores".
|
||||
|
|
||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the cookie used to identify a session
|
||||
| instance by ID. The name specified here will get used every time a
|
||||
| new session cookie is created by the framework for every driver.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application but you are free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => '/',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the domain of the cookie used to identify a session
|
||||
| in your application. This will determine which domains the cookie is
|
||||
| available to in your application. A sensible default has been set.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. You are free to modify this option if needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" since this is a secure default value.
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => 'lax',
|
||||
|
||||
];
|
|
@ -0,0 +1,269 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Theme
|
||||
|--------------------------------------------------------------------------
|
||||
| The theme to use for SweetAlert2 popups.
|
||||
| Available themes: dark, minimal, borderless, bootstrap-4, material-ui, wordpress-admin, bulma.
|
||||
|
|
||||
*/
|
||||
|
||||
'theme' => env('SWEET_ALERT_THEME', 'default'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| CDN LINK
|
||||
|--------------------------------------------------------------------------
|
||||
| By default SweetAlert2 use its local sweetalert.all.js
|
||||
| file.
|
||||
| However, you can use its cdn if you want.
|
||||
|
|
||||
*/
|
||||
|
||||
'cdn' => env('SWEET_ALERT_CDN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Always load the sweetalert.all.js
|
||||
|--------------------------------------------------------------------------
|
||||
| There might be situations where you will always want the sweet alert
|
||||
| js package to be there for you. (for eg. you might use it heavily to
|
||||
| show notifications or you might want to use the native js) then this
|
||||
| might be handy.
|
||||
|
|
||||
*/
|
||||
|
||||
'alwaysLoadJS' => env('SWEET_ALERT_ALWAYS_LOAD_JS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Never load the sweetalert.all.js
|
||||
|--------------------------------------------------------------------------
|
||||
| If you want to handle the sweet alert js package by yourself
|
||||
| (for eg. you might want to use laravel mix) then this can be
|
||||
| handy.
|
||||
| If you set always load js to true & never load js to false,
|
||||
| it's going to prioritize the never load js.
|
||||
|
|
||||
| alwaysLoadJs = true & neverLoadJs = true => js will not be loaded
|
||||
| alwaysLoadJs = true & neverLoadJs = false => js will be loaded
|
||||
| alwaysLoadJs = false & neverLoadJs = false => js will be loaded when
|
||||
| you set alert/toast by using the facade/helper functions.
|
||||
*/
|
||||
|
||||
'neverLoadJS' => env('SWEET_ALERT_NEVER_LOAD_JS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| AutoClose Timer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is for the all Modal windows.
|
||||
| For specific modal just use the autoClose() helper method.
|
||||
|
|
||||
*/
|
||||
|
||||
'timer' => env('SWEET_ALERT_TIMER', 5000),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Width
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Modal window width, including paddings (box-sizing: border-box).
|
||||
| Can be in px or %.
|
||||
| The default width is 32rem.
|
||||
| This is for the all Modal windows.
|
||||
| for particular modal just use the width() helper method.
|
||||
*/
|
||||
|
||||
'width' => env('SWEET_ALERT_WIDTH', '32rem'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Height Auto
|
||||
|--------------------------------------------------------------------------
|
||||
| By default, SweetAlert2 sets html's and body's CSS height to auto !important.
|
||||
| If this behavior isn't compatible with your project's layout,
|
||||
| set heightAuto to false.
|
||||
|
|
||||
*/
|
||||
|
||||
'height_auto' => env('SWEET_ALERT_HEIGHT_AUTO', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Padding
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Modal window padding.
|
||||
| Can be in px or %.
|
||||
| The default padding is 1.25rem.
|
||||
| This is for the all Modal windows.
|
||||
| for particular modal just use the padding() helper method.
|
||||
*/
|
||||
|
||||
'padding' => env('SWEET_ALERT_PADDING', '1.25rem'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Background
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Modal window background
|
||||
| (CSS background property).
|
||||
| The default background is '#fff'.
|
||||
*/
|
||||
|
||||
'background' => env('SWEET_ALERT_BACKGROUND', '#fff'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Animation
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom animation with [Animate.css](https://daneden.github.io/animate.css/)
|
||||
| If set to false, modal CSS animation will be use default ones.
|
||||
| For specific modal just use the animation() helper method.
|
||||
|
|
||||
*/
|
||||
|
||||
'animation' => [
|
||||
'enable' => env('SWEET_ALERT_ANIMATION_ENABLE', false),
|
||||
],
|
||||
|
||||
'animatecss' => env('SWEET_ALERT_ANIMATECSS', 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ShowConfirmButton
|
||||
|--------------------------------------------------------------------------
|
||||
| If set to false, a "Confirm"-button will not be shown.
|
||||
| It can be useful when you're using custom HTML description.
|
||||
| This is for the all Modal windows.
|
||||
| For specific modal just use the showConfirmButton() helper method.
|
||||
|
|
||||
*/
|
||||
|
||||
'show_confirm_button' => env('SWEET_ALERT_CONFIRM_BUTTON', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ShowCloseButton
|
||||
|--------------------------------------------------------------------------
|
||||
| If set to true, a "Close"-button will be shown,
|
||||
| which the user can click on to dismiss the modal.
|
||||
| This is for the all Modal windows.
|
||||
| For specific modal just use the showCloseButton() helper method.
|
||||
|
|
||||
*/
|
||||
|
||||
'show_close_button' => env('SWEET_ALERT_CLOSE_BUTTON', false),
|
||||
|
||||
/*
|
||||
|-----------------------------------------------------------------------
|
||||
| Confirm/Cancel Button Text
|
||||
|-----------------------------------------------------------------------
|
||||
| Change the default text of the modal buttons.
|
||||
| The texts translations will be handled by Laravel at runtime.
|
||||
| This is for the all Modal windows.
|
||||
| For specific modal just use the confirmButtonText() and
|
||||
| cancelButtonText() helper methods.
|
||||
*/
|
||||
|
||||
'button_text' => [
|
||||
'confirm' => env('SWEET_ALERT_CONFIRM_BUTTON_TEXT', 'OK'),
|
||||
'cancel' => env('SWEET_ALERT_CANCEL_BUTTON_TEXT', 'Cancel'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Toast position
|
||||
|--------------------------------------------------------------------------
|
||||
| Modal window or toast position, can be 'top',
|
||||
| 'top-start', 'top-end', 'center', 'center-start',
|
||||
| 'center-end', 'bottom', 'bottom-start', or 'bottom-end'.
|
||||
| For specific modal just use the position() helper method.
|
||||
|
|
||||
*/
|
||||
|
||||
'toast_position' => env('SWEET_ALERT_TOAST_POSITION', 'top-end'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Progress Bar
|
||||
|--------------------------------------------------------------------------
|
||||
| If set to true, a progress bar at the bottom of a popup will be shown.
|
||||
| It can be useful with toasts.
|
||||
|
|
||||
*/
|
||||
|
||||
'timer_progress_bar' => env('SWEET_ALERT_TIMER_PROGRESS_BAR', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
| Modal window or toast, config for the Middleware
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
|
||||
'autoClose' => env('SWEET_ALERT_MIDDLEWARE_AUTO_CLOSE', false),
|
||||
|
||||
'toast_position' => env('SWEET_ALERT_MIDDLEWARE_TOAST_POSITION', 'top-end'),
|
||||
|
||||
'toast_close_button' => env('SWEET_ALERT_MIDDLEWARE_TOAST_CLOSE_BUTTON', true),
|
||||
|
||||
'timer' => env('SWEET_ALERT_MIDDLEWARE_ALERT_CLOSE_TIME', 6000),
|
||||
|
||||
'auto_display_error_messages' => env('SWEET_ALERT_AUTO_DISPLAY_ERROR_MESSAGES', true),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Class
|
||||
|--------------------------------------------------------------------------
|
||||
| A custom CSS class for the modal:
|
||||
|
|
||||
*/
|
||||
|
||||
'customClass' => [
|
||||
|
||||
'container' => env('SWEET_ALERT_CONTAINER_CLASS'),
|
||||
'popup' => env('SWEET_ALERT_POPUP_CLASS'),
|
||||
'header' => env('SWEET_ALERT_HEADER_CLASS'),
|
||||
'title' => env('SWEET_ALERT_TITLE_CLASS'),
|
||||
'closeButton' => env('SWEET_ALERT_CLOSE_BUTTON_CLASS'),
|
||||
'icon' => env('SWEET_ALERT_ICON_CLASS'),
|
||||
'image' => env('SWEET_ALERT_IMAGE_CLASS'),
|
||||
'content' => env('SWEET_ALERT_CONTENT_CLASS'),
|
||||
'input' => env('SWEET_ALERT_INPUT_CLASS'),
|
||||
'actions' => env('SWEET_ALERT_ACTIONS_CLASS'),
|
||||
'confirmButton' => env('SWEET_ALERT_CONFIRM_BUTTON_CLASS'),
|
||||
'cancelButton' => env('SWEET_ALERT_CANCEL_BUTTON_CLASS'),
|
||||
'footer' => env('SWEET_ALERT_FOOTER_CLASS'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| confirmDelete
|
||||
|--------------------------------------------------------------------------
|
||||
| customize the configuration options of the confirmation popup.
|
||||
|
|
||||
*/
|
||||
|
||||
'confirm_delete_confirm_button_text' => env('SWEET_ALERT_CONFIRM_DELETE_CONFIRM_BUTTON_TEXT', 'Yes, delete it!'),
|
||||
'confirm_delete_confirm_button_color' => env('SWEET_ALERT_CONFIRM_DELETE_CONFIRM_BUTTON_COLOR'),
|
||||
'confirm_delete_cancel_button_color' => env('SWEET_ALERT_CONFIRM_DELETE_CANCEL_BUTTON_COLOR', '#d33'),
|
||||
'confirm_delete_cancel_button_text' => env('SWEET_ALERT_CONFIRM_DELETE_CANCEL_BUTTON_TEXT', 'Cancel'),
|
||||
'confirm_delete_show_cancel_button' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_CANCEL_BUTTON', true),
|
||||
'confirm_delete_show_close_button' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_CLOSE_BUTTON', false),
|
||||
'confirm_delete_icon' => env('SWEET_ALERT_CONFIRM_DELETE_ICON', 'warning'),
|
||||
'confirm_delete_show_loader_on_confirm' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_LOADER_ON_CONFIRM', true),
|
||||
|
||||
|
||||
];
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| View Storage Paths
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Most templating systems load templates from disk. Here you may specify
|
||||
| an array of paths that should be checked for your views. Of course
|
||||
| the usual Laravel view path has already been registered for you.
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => [
|
||||
resource_path('views'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Compiled View Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines where all the compiled Blade templates will be
|
||||
| stored for your application. Typically, this is within the storage
|
||||
| directory. However, as usual, you are free to change this value.
|
||||
|
|
||||
*/
|
||||
|
||||
'compiled' => env(
|
||||
'VIEW_COMPILED_PATH',
|
||||
realpath(storage_path('framework/views'))
|
||||
),
|
||||
|
||||
];
|
|
@ -0,0 +1 @@
|
|||
*.sqlite*
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\laporanpenjualan>
|
||||
*/
|
||||
class LaporanpenjualanFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
// $table->string('email');
|
||||
$table->string('username')->unique();
|
||||
$table->string('password');
|
||||
$table->enum('roles', ['admin', 'karyawan', 'pelanggan', 'super']);
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
$table->timestamp('last_login')->nullable();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('produks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_produk');
|
||||
$table->decimal('harga_produk', 10, 2);
|
||||
$table->integer('stok_produk')->default(0);
|
||||
$table->string('foto')->nullable();
|
||||
$table->text('deskripsi_produk')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('raw_materials', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->decimal('price', 10, 2);
|
||||
$table->decimal('stock', 10, 2)->default(0);
|
||||
$table->decimal('minimum_stock', 10, 2)->default(10);
|
||||
$table->enum('unit', ['g', 'kg', 'ml', 'l', 'pcs']);
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
// Create suppliers table
|
||||
Schema::create('suppliers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->string('name');
|
||||
$table->string('phone')->nullable();
|
||||
$table->text('address')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
// Create raw_material_logs table
|
||||
Schema::create('raw_material_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('raw_material_id')->constrained('raw_materials')->onDelete('cascade');
|
||||
$table->foreignId('supplier_id')->nullable()->constrained('suppliers')->onDelete('set null');
|
||||
$table->enum('type', ['in', 'out', 'adjustment', 'production', 'expired', 'damaged']);
|
||||
$table->decimal('quantity', 10, 2);
|
||||
$table->decimal('price', 10, 2)->nullable();
|
||||
$table->decimal('subtotal', 10, 2)->default(0);
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
// Create product_recipes table
|
||||
Schema::create('product_recipes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('produk_id')->constrained('produks')->onDelete('cascade');
|
||||
$table->foreignId('raw_material_id')->constrained('raw_materials')->onDelete('cascade');
|
||||
$table->decimal('quantity', 10, 3);
|
||||
$table->enum('unit', ['g', 'kg', 'ml', 'l', 'pcs']);
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Create transaksi table
|
||||
Schema::create('transaksi', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('kode_transaksi')->unique();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->decimal('total_harga', 10, 2)->default(0);
|
||||
$table->string('metode_pembayaran')->default('cash');
|
||||
$table->enum('status', ['pending', 'completed', 'cancelled'])->default('pending');
|
||||
$table->timestamp('tanggal_transaksi')->useCurrent();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
// Create transaksi_details table
|
||||
Schema::create('transaksi_details', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('transaksi_id')->constrained('transaksi')->onDelete('cascade');
|
||||
$table->foreignId('produk_id')->constrained('produks')->onDelete('cascade');
|
||||
$table->integer('quantity')->default(1);
|
||||
$table->decimal('price', 10, 2)->default(0);
|
||||
$table->decimal('subtotal', 10, 2)->default(0);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('transaksi_details');
|
||||
Schema::dropIfExists('transaksi');
|
||||
Schema::dropIfExists('product_recipes');
|
||||
Schema::dropIfExists('raw_material_logs');
|
||||
Schema::dropIfExists('suppliers');
|
||||
Schema::dropIfExists('raw_materials');
|
||||
Schema::dropIfExists('produks');
|
||||
// Don't drop users table as it might be needed by other parts
|
||||
}
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('karyawan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->string('nama');
|
||||
$table->string('no_telp')->nullable();
|
||||
$table->text('alamat')->nullable();
|
||||
$table->string('foto')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('karyawan');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('admins', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->string('nama');
|
||||
$table->string('no_telp');
|
||||
$table->text('alamat');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('admins');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
$table->dropForeign(['supplier_id']);
|
||||
// Rename the column and add new foreign key
|
||||
$table->renameColumn('supplier_id', 'user_id');
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
// Drop the new foreign key
|
||||
$table->dropForeign(['user_id']);
|
||||
// Rename back to original
|
||||
$table->renameColumn('user_id', 'supplier_id');
|
||||
$table->foreign('supplier_id')->references('id')->on('suppliers')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::hasTable('transaksi')) {
|
||||
Schema::create('transaksi', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->datetime('tanggal_transaksi');
|
||||
$table->integer('total_harga');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
} else {
|
||||
// Check if columns exist and add them if they don't
|
||||
if (!Schema::hasColumn('transaksi', 'total_harga')) {
|
||||
Schema::table('transaksi', function (Blueprint $table) {
|
||||
$table->integer('total_harga')->after('tanggal_transaksi');
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('transaksi', 'deleted_at')) {
|
||||
Schema::table('transaksi', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('transaksi');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pelanggan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama', 50);
|
||||
$table->string('no_telp', 15)->unique();
|
||||
$table->string('foto')->default('default_foto.jpg');
|
||||
$table->unsignedBigInteger('id_user')->unique()->nullable();
|
||||
$table->foreign('id_user')->references('id')->on('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pelanggan');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('laporanpenjualans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->date('tanggal');
|
||||
$table->integer('total_penjualan');
|
||||
$table->integer('total_produk_terjual');
|
||||
$table->integer('jumlah_transaksi');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('laporanpenjualans');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('supers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->string('nama');
|
||||
$table->string('no_telp');
|
||||
$table->text('alamat');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('supers');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
$table->decimal('price', 18, 2)->nullable()->change();
|
||||
$table->decimal('subtotal', 20, 2)->default(0)->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
$table->decimal('price', 10, 2)->nullable()->change();
|
||||
$table->decimal('subtotal', 10, 2)->default(0)->change();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('product_recipes', function (Blueprint $table) {
|
||||
$table->decimal('quantity', 10)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('product_recipes', function (Blueprint $table) {
|
||||
$table->decimal('quantity', 10, 3)->change();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
$table->enum('type', ['in', 'out', 'adjustment', 'production', 'expired', 'damaged', 'pemakaian'])->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('raw_material_logs', function (Blueprint $table) {
|
||||
$table->enum('type', ['in', 'out', 'adjustment', 'production', 'expired', 'damaged', 'usage'])->change();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('user', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('user', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
|
||||
use App\Models\SettingSistem;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use App\Models\Admin;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
UserSeeder::class,
|
||||
RawMaterialSeeder::class,
|
||||
ProductSeeder::class,
|
||||
ProductRecipeSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class LaporanpenjualanSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ProductRecipe;
|
||||
use App\Models\Produk;
|
||||
use App\Models\RawMaterial;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ProductRecipeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Get all products and raw materials
|
||||
$products = Produk::all()->keyBy('nama_produk');
|
||||
$materials = RawMaterial::all()->keyBy('name');
|
||||
|
||||
// Define recipes for each product
|
||||
$recipes = [
|
||||
'Ayam Geprek Original' => [
|
||||
// Base ayam crispy
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 100, 'unit' => 'ml'],
|
||||
// Sambal merah
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 25, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 1, 'unit' => 'g'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Ayam Geprek Sambal Ijo' => [
|
||||
// Base ayam crispy (sama seperti original)
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 100, 'unit' => 'ml'],
|
||||
// Sambal hijau
|
||||
['material' => 'Cabai Hijau Besar', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Hijau', 'quantity' => 8, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 1, 'unit' => 'g'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Ayam Geprek Sambal Terasi' => [
|
||||
// Base ayam crispy
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 100, 'unit' => 'ml'],
|
||||
// Sambal terasi
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 8, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 12, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 4, 'unit' => 'g'],
|
||||
['material' => 'Terasi', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 1, 'unit' => 'g'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Ayam Geprek Sambal Kecap' => [
|
||||
// Base ayam crispy
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 100, 'unit' => 'ml'],
|
||||
// Sambal kecap
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 8, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Kecap Manis', 'quantity' => 15, 'unit' => 'ml'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 1, 'unit' => 'g'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Ayam Geprek Level 5' => [
|
||||
// Base ayam crispy
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 100, 'unit' => 'ml'],
|
||||
// Sambal super pedas
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 35, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Hijau', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 12, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 25, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 8, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Paket Geprek + Telur' => [
|
||||
// Base ayam crispy
|
||||
['material' => 'Ayam Fillet', 'quantity' => 150, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 1, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 120, 'unit' => 'ml'],
|
||||
// Sambal
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 25, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 10, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 1, 'unit' => 'g'],
|
||||
// Telur
|
||||
['material' => 'Telur Ayam', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
// Nasi dan lalapan
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Paket Geprek Jumbo' => [
|
||||
// Base ayam crispy (porsi jumbo)
|
||||
['material' => 'Ayam Fillet', 'quantity' => 250, 'unit' => 'g'],
|
||||
['material' => 'Tepung Terigu', 'quantity' => 50, 'unit' => 'g'],
|
||||
['material' => 'Tepung Maizena', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Merica Bubuk', 'quantity' => 2, 'unit' => 'g'],
|
||||
['material' => 'Penyedap Rasa', 'quantity' => 3, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 8, 'unit' => 'g'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 150, 'unit' => 'ml'],
|
||||
// Sambal
|
||||
['material' => 'Cabai Merah Keriting', 'quantity' => 35, 'unit' => 'g'],
|
||||
['material' => 'Cabai Rawit Merah', 'quantity' => 8, 'unit' => 'g'],
|
||||
['material' => 'Bawang Merah', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Bawang Putih', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Gula Pasir', 'quantity' => 5, 'unit' => 'g'],
|
||||
['material' => 'Garam', 'quantity' => 2, 'unit' => 'g'],
|
||||
// Telur
|
||||
['material' => 'Telur Ayam', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
// Nasi dan lalapan (porsi jumbo)
|
||||
['material' => 'Beras', 'quantity' => 120, 'unit' => 'g'],
|
||||
['material' => 'Timun', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 8, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Es Teh Manis' => [
|
||||
['material' => 'Teh Celup', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
['material' => 'Gula Pasir (Minuman)', 'quantity' => 15, 'unit' => 'g'],
|
||||
['material' => 'Es Batu', 'quantity' => 3, 'unit' => 'pcs'],
|
||||
],
|
||||
|
||||
'Es Teh Tawar' => [
|
||||
['material' => 'Teh Celup', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
['material' => 'Es Batu', 'quantity' => 3, 'unit' => 'pcs'],
|
||||
],
|
||||
|
||||
'Teh Anget' => [
|
||||
['material' => 'Teh Celup', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
['material' => 'Gula Pasir (Minuman)', 'quantity' => 10, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Nasi Putih' => [
|
||||
['material' => 'Beras', 'quantity' => 80, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Telur Ceplok' => [
|
||||
['material' => 'Telur Ayam', 'quantity' => 1, 'unit' => 'pcs'],
|
||||
['material' => 'Minyak Goreng', 'quantity' => 10, 'unit' => 'ml'],
|
||||
['material' => 'Garam', 'quantity' => 0.5, 'unit' => 'g'],
|
||||
],
|
||||
|
||||
'Lalapan' => [
|
||||
['material' => 'Timun', 'quantity' => 30, 'unit' => 'g'],
|
||||
['material' => 'Tomat', 'quantity' => 20, 'unit' => 'g'],
|
||||
['material' => 'Daun Kemangi', 'quantity' => 10, 'unit' => 'g'],
|
||||
],
|
||||
];
|
||||
|
||||
// Create recipes
|
||||
foreach ($recipes as $productName => $productRecipes) {
|
||||
if (!isset($products[$productName])) {
|
||||
continue; // Skip if product doesn't exist
|
||||
}
|
||||
|
||||
$product = $products[$productName];
|
||||
|
||||
foreach ($productRecipes as $recipe) {
|
||||
if (!isset($materials[$recipe['material']])) {
|
||||
continue; // Skip if material doesn't exist
|
||||
}
|
||||
|
||||
$material = $materials[$recipe['material']];
|
||||
|
||||
ProductRecipe::create([
|
||||
'produk_id' => $product->id,
|
||||
'raw_material_id' => $material->id,
|
||||
'quantity' => $recipe['quantity'],
|
||||
'unit' => $recipe['unit'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue