partial update products

This commit is contained in:
2025-06-04 16:58:50 +07:00
parent 215792ce63
commit 8305e44c42
35 changed files with 179 additions and 707 deletions

View File

@@ -126,7 +126,7 @@ class ProductCategoriesController extends Controller
return response()->json(['success' => true, 'message' => 'Kategorii berhasil dihapus.']);
}
public function getParents(Request $request)
public function product_category_parents(Request $request)
{
$parents = ProductCategory::whereNull('parent_id')->get(['id', 'name']);
return response()->json($parents);

View File

@@ -7,10 +7,10 @@ use App\Models\Dealer;
use App\Models\Menu;
use App\Models\Product;
use App\Models\ProductCategory;
use App\Models\StockMutation;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Yajra\DataTables\Facades\DataTables;
use Illuminate\Validation\Rule;
@@ -49,9 +49,11 @@ class ProductsController extends Controller
$btn .= '<button class="btn btn-sm btn-toggle-active '
. ($row->active ? 'btn-danger' : 'btn-success') . '"
data-url="' . route('products.toggleActive', $row->id) . '" data-active="'.$row->active.'">'
data-url="' . route('products.toggleActive', $row->id) . '" data-active="'.$row->active.'" style="margin-right: 8px;">'
. ($row->active ? 'Nonaktifkan' : 'Aktifkan') . '</button>';
$btn .= '<button class="btn btn-sm btn-secondary btn-product-stock-dealers">Stock</button>';
$btn .= '</div>';
return $btn;
@@ -93,10 +95,7 @@ class ProductsController extends Controller
'description' => 'nullable|string',
'unit' => 'nullable|string',
'active' => 'required|boolean',
'product_category_id' => 'required|exists:product_categories,id',
'dealer_stock' => 'nullable|array',
'dealer_stock.*.dealer_id' => 'required|exists:dealers,id',
'dealer_stock.*.quantity' => 'required|integer|min:0',
'product_category_id' => 'required|exists:product_categories,id'
]);
// Create product
@@ -109,34 +108,6 @@ class ProductsController extends Controller
'product_category_id' => $request->product_category_id,
]);
// Prepare dealer stock for pivot and create mutation records
$pivotData = [];
if ($request->has('dealer_stock')) {
foreach ($request->dealer_stock as $stockData) {
if (empty($stockData['dealer_id']) || !isset($stockData['quantity'])) continue;
$dealerId = $stockData['dealer_id'];
$quantity = $stockData['quantity'];
$pivotData[$dealerId] = ['quantity' => $quantity];
// Create stock mutation for initial stock "in"
StockMutation::create([
'product_id' => $product->id,
'dealer_id' => $dealerId,
'mutation_type' => 'in', // karena ini penambahan stok awal
'quantity' => $quantity,
'description' => 'Initial stock added when product created',
'user_id' => Auth::id(),
]);
}
// Attach dealer stock using pivot table
$product->dealers()->attach($pivotData);
}
return redirect()->route('products.index')->with('success', 'Produk berhasil ditambahkan.');
}catch(\Exception $ex){
throw $ex;
@@ -179,6 +150,7 @@ class ProductsController extends Controller
*/
public function update(Request $request, Product $product)
{
try{
$request->validate([
'code' => [
'required',
@@ -189,67 +161,15 @@ class ProductsController extends Controller
'description' => 'nullable|string',
'unit' => 'nullable|string',
'active' => 'required|boolean',
'product_category_id' => 'required|exists:product_categories,id',
'dealer_stock' => 'nullable|array',
'dealer_stock.*.dealer_id' => 'required|exists:dealers,id',
'dealer_stock.*.quantity' => 'required|integer|min:0',
'product_category_id' => 'required|exists:product_categories,id'
]);
$product->update($request->only(['code', 'name', 'description', 'unit', 'product_category_id']));
// Ambil stok lama dari pivot
$oldStocks = $product->dealers()->pluck('quantity', 'dealer_id')->toArray();
// Data baru untuk sync pivot
$syncData = [];
$newStocks = [];
if ($request->has('dealer_stock')) {
foreach ($request->dealer_stock as $item) {
$dealerId = $item['dealer_id'];
$newQty = $item['quantity'];
$syncData[$dealerId] = ['quantity' => $newQty];
$newStocks[$dealerId] = $newQty;
}
}
// Sync pivot table
$product->dealers()->sync($syncData);
// Hitung mutasi stok (selisih)
// Mutasi stok untuk stok baru atau perubahan stok
foreach ($newStocks as $dealerId => $newQty) {
$oldQty = $oldStocks[$dealerId] ?? 0;
$diff = $newQty - $oldQty;
if ($diff != 0) {
StockMutation::create([
'product_id' => $product->id,
'dealer_id' => $dealerId,
'mutation_type' => $diff > 0 ? 'in' : 'out',
'quantity' => abs($diff),
'description' => 'Stock updated via product update',
'user_id' => auth()->id(),
]);
}
}
// Mutasi stok untuk dealer yang dihapus (stok jadi 0)
$deletedDealers = array_diff_key($oldStocks, $newStocks);
foreach ($deletedDealers as $dealerId => $oldQty) {
if ($oldQty > 0) {
StockMutation::create([
'product_id' => $product->id,
'dealer_id' => $dealerId,
'mutation_type' => 'out',
'quantity' => $oldQty,
'description' => 'Stock removed via product update (dealer removed)',
'user_id' => auth()->id(),
]);
}
}
$product->update($request->only(['code', 'name', 'description', 'unit','active', 'product_category_id']));
return redirect()->route('products.index')->with('success', 'Produk berhasil diperbarui.');
}catch(\Exception $ex){
Log::error($ex->getMessage());
}
}
/**

View File

@@ -1,41 +0,0 @@
<?php
namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller;
use App\Models\StockMutation;
use Illuminate\Http\Request;
use Yajra\DataTables\Facades\DataTables;
class StockMutationsController extends Controller
{
public function index(Request $request){
if ($request->ajax()) {
$query = StockMutation::with(['product', 'dealer', 'user']);
return DataTables::of($query)
->addIndexColumn()
->addColumn('product_name', function ($row) {
return $row->product ? $row->product->name : '-';
})
->addColumn('dealer_name', function ($row) {
return $row->dealer ? $row->dealer->name : '-';
})
->addColumn('user_name', function ($row) {
return $row->user ? $row->user->name : '-';
})
->addColumn('mutation_type_label', function ($row) {
return $row->mutation_type == 'in'
? '<span class="badge bg-success">Masuk</span>'
: '<span class="badge bg-danger">Keluar</span>';
})
->editColumn('created_at', function ($row) {
return $row->created_at->format('d M Y H:i');
})
->rawColumns(['mutation_type_label'])
->make(true);
}
return view('warehouse_management.stock_mutations.index');
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller;
use App\Models\StockOpname;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Yajra\DataTables\Facades\DataTables;
class StockOpnamesController extends Controller
{
public function index(Request $request){
try{
if ($request->ajax()) {
$query = StockOpname::with(['product', 'dealer', 'user']);
return DataTables::of($query)
->addIndexColumn()
->addColumn('product_name', function ($row) {
return $row->product ? $row->product->name : '-';
})
->addColumn('dealer_name', function ($row) {
return $row->dealer ? $row->dealer->name : '-';
})
->addColumn('user_name', function ($row) {
return $row->user ? $row->user->name : '-';
})
->editColumn('opname_date', function ($row) {
return $row->opname_date->format('d M Y');
})
->make(true);
}
return view('warehouse_management.stock_opnames.index');
}catch(\Exception $ex){
Log::error($ex->getMessage());
}
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Stock extends Model
{
use HasFactory;
protected $table = 'stock';
protected $fillable = [
'product_id',
'dealer_id',
'quantity',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
}

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StockMutation extends Model
{
use HasFactory;
protected $fillable = [
'product_id',
'dealer_id',
'mutation_type',
'quantity',
'description',
'user_id',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StockOpname extends Model
{
use HasFactory;
protected $fillable = [
'product_id',
'dealer_id',
'system_quantity',
'physical_quantity',
'difference',
'opname_date',
'user_id',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@@ -16,6 +16,7 @@ class CreateProductCategoriesTable extends Migration
Schema::create('product_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('parent_id')->nullable()->constrained('product_categories')->nullOnDelete();
$table->softDeletes();
$table->timestamps();
});

View File

@@ -17,6 +17,8 @@ class CreateProductsTable extends Migration
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->boolean('active')->default(true);
$table->string('unit')->nullable();
$table->text('description')->nullable();
$table->foreignId('product_category_id')->constrained()->onDelete('cascade');
$table->softDeletes();

View File

@@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStockTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('stock', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('dealer_id')->constrained()->onDelete('cascade');
$table->integer('quantity')->default(0);
$table->timestamps();
$table->unique(['product_id','dealer_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('stock');
}
}

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddParentIdToProductCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('product_categories', function (Blueprint $table) {
$table->foreignId('parent_id')->nullable()->after('name')->constrained('product_categories')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('product_categories', function (Blueprint $table) {
$table->dropForeign(['parent_id']);
$table->dropColumn('parent_id');
});
}
}

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStockMutationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('stock_mutations', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('dealer_id')->constrained()->onDelete('cascade');
$table->enum('mutation_type',['in','out','adjustment']);
$table->integer('quantity');
$table->text('description')->nullable();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('stock_mutations');
}
}

View File

@@ -1,38 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStockOpnamesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('stock_opnames', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('dealer_id')->constrained()->onDelete('cascade');
$table->integer('system_quantity');
$table->integer('physical_quantity');
$table->integer('difference');
$table->date('opname_date');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('stock_opnames');
}
}

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUnitToProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->string('unit')->nullable()->after('description');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('unit');
});
}
}

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddActiveToProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->boolean('active')->default(true)->after('unit');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('active');
});
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./resources/js/warehouse_management/stock_opnames/create.js":
/*!*******************************************************************!*\
!*** ./resources/js/warehouse_management/stock_opnames/create.js ***!
\*******************************************************************/
/***/ (() => {
eval("//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6W10sInNvdXJjZXMiOlsid2VicGFjazovLy8uL3Jlc291cmNlcy9qcy93YXJlaG91c2VfbWFuYWdlbWVudC9zdG9ja19vcG5hbWVzL2NyZWF0ZS5qcz9kYzA1Il0sInNvdXJjZXNDb250ZW50IjpbIiJdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiIuL3Jlc291cmNlcy9qcy93YXJlaG91c2VfbWFuYWdlbWVudC9zdG9ja19vcG5hbWVzL2NyZWF0ZS5qcyIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./resources/js/warehouse_management/stock_opnames/create.js\n");
/***/ })
/******/ });
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval-source-map devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./resources/js/warehouse_management/stock_opnames/create.js"]();
/******/
/******/ })()
;

View File

@@ -6,5 +6,6 @@
"/js/warehouse_management/products/edit.js": "/js/warehouse_management/products/edit.js",
"/js/warehouse_management/stock_mutations/index.js": "/js/warehouse_management/stock_mutations/index.js",
"/js/warehouse_management/stock_opnames/index.js": "/js/warehouse_management/stock_opnames/index.js",
"/js/warehouse_management/stock_opnames/create.js": "/js/warehouse_management/stock_opnames/create.js",
"/css/app.css": "/css/app.css"
}

View File

@@ -125,8 +125,10 @@ $(document).on("click", ".btn-destroy-product-category", function () {
});
function loadParentCategories(selectedId = null) {
const selectElement = $("#parent_id");
let urlParents = selectElement.data("url");
$.ajax({
url: "/warehouse/categories/parents", // create this route
url: urlParents,
type: "GET",
success: function (data) {
$("#parent_id")

View File

@@ -1,43 +1 @@
document.addEventListener("DOMContentLoaded", function () {
const addBtn = document.getElementById("addDealerRow");
const dealerRows = document.getElementById("dynamicDealerRows");
// Get dealer data from Blade
const dealerDataDiv = document.getElementById("dealerData");
const dealers = JSON.parse(dealerDataDiv.getAttribute("data-dealers")); // 👈 this reads JSON from Blade
let rowCount = 0;
function createDealerRow(index) {
const dealerOptions = dealers
.map((d) => `<option value="${d.id}">${d.name}</option>`)
.join("");
return `
<div class="form-group row align-items-center dealer-row" data-index="${index}">
<div class="col-md-5">
<select name="dealer_stock[${index}][dealer_id]" class="form-control" required>
<option value="">-- Pilih Dealer --</option>
${dealerOptions}
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[${index}][quantity]" class="form-control" value="1" min="1" required>
</div>
<div class="col-md-3">
<button type="button" class="btn btn-danger btn-sm removeDealerRow">Hapus</button>
</div>
</div>
`;
}
addBtn.addEventListener("click", function () {
dealerRows.insertAdjacentHTML("beforeend", createDealerRow(rowCount));
rowCount++;
});
dealerRows.addEventListener("click", function (e) {
if (e.target.classList.contains("removeDealerRow")) {
e.target.closest(".dealer-row").remove();
}
});
});
document.addEventListener("DOMContentLoaded", function () {});

View File

@@ -1,48 +1 @@
document.addEventListener("DOMContentLoaded", function () {
let dealerRowContainer = document.getElementById("dynamicDealerRows");
let addDealerButton = document.getElementById("addDealerRow");
// Initial index from existing dealer rows
let rowIndex =
dealerRowContainer.querySelectorAll(".dealer-stock-row").length;
addDealerButton.addEventListener("click", function () {
const newRow = document.createElement("div");
newRow.className = "form-group row align-items-center dealer-stock-row";
newRow.innerHTML = `
<div class="col-md-6">
<select name="dealer_stock[${rowIndex}][dealer_id]" class="form-control">
<option value="">-- Pilih Dealer --</option>
${generateDealerOptions()}
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[${rowIndex}][quantity]" class="form-control" value="1" min="1" required placeholder="Jumlah Stok" />
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-sm remove-dealer-row"> Hapus </button>
</div>
`;
dealerRowContainer.appendChild(newRow);
rowIndex++;
});
// Handle removal of dealer row
dealerRowContainer.addEventListener("click", function (event) {
if (
event.target &&
event.target.classList.contains("remove-dealer-row")
) {
event.target.closest(".dealer-stock-row").remove();
}
});
// Function to generate dealer <option> elements dynamically
function generateDealerOptions() {
const dealerDataDiv = document.getElementById("dealerData");
const dealers = JSON.parse(dealerDataDiv.getAttribute("data-dealers"));
return dealers
.map((d) => `<option value="${d.id}">${d.name}</option>`)
.join("");
}
});
document.addEventListener("DOMContentLoaded", function () {});

View File

@@ -1,20 +0,0 @@
$.ajaxSetup({
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
},
});
let tableContainer = $("#stock-mutations-table");
let url = tableContainer.data("url");
let table = $("#stock-mutations-table").DataTable({
processing: true,
serverSide: true,
ajax: url,
columns: [
{ data: "product_name", name: "product_name" },
{ data: "dealer_name", name: "dealer_name" },
{ data: "user_name", name: "user_name" },
{ data: "mutation_type_label", name: "mutation_type_label" },
{ data: "quantity", name: "quantity" },
{ data: "created_at", name: "created_at" },
],
});

View File

@@ -1,21 +0,0 @@
$.ajaxSetup({
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
},
});
let tableContainer = $("#stock-opnames-table");
let url = tableContainer.data("url");
let table = $("#stock-opnames-table").DataTable({
processing: true,
serverSide: true,
ajax: url,
columns: [
{ data: "product_name", name: "product_name" },
{ data: "dealer_name", name: "dealer_name" },
{ data: "user_name", name: "user_name" },
{ data: "system_quantity", name: "system_quantity" },
{ data: "physical_quantity", name: "physical_quantity" },
{ data: "difference", name: "difference" },
{ data: "opname_date", name: "opname_date" },
],
});

View File

@@ -138,18 +138,18 @@
</div>
{{-- Submenu Items --}}
@can('view', $menus['dealer.index'])
@can('view', $menus['user.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('dealer.index') }}" class="kt-menu__link">
<a href="{{ route('user.index') }}" class="kt-menu__link">
<i class="fa fa-car" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Pengguna</span>
</a>
</li>
@endcan
@can('view', $menus['category.index'])
@can('view', $menus['roleprivileges.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('category.index') }}" class="kt-menu__link">
<a href="{{ route('roleprivileges.index') }}" class="kt-menu__link">
<i class="fa fa-users" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Role & Privileges</span>
</a>
@@ -163,9 +163,9 @@
</div>
{{-- Submenu Items --}}
@can('view', $menus['dealer.index'])
@can('view', $menus['work.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('dealer.index') }}" class="kt-menu__link">
<a href="{{ route('work.index') }}" class="kt-menu__link">
<i class="fa fa-car" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Pekerjaan</span>
</a>
@@ -181,9 +181,9 @@
</li>
@endcan
@can('view', $menus['work.index'])
@can('view', $menus['dealer.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('work.index') }}" class="kt-menu__link">
<a href="{{ route('dealer.index') }}" class="kt-menu__link">
<i class="fa fa-list" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Dealer</span>
</a>
@@ -224,9 +224,9 @@
</li>
@endcan
@can('view', $menus['work.index'])
@can('view', $menus['opnames.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('work.index') }}" class="kt-menu__link">
<a href="{{ route('opnames.index') }}" class="kt-menu__link">
<i class="fa fa-list" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Stock Opname</span>
</a>

View File

@@ -5,7 +5,7 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-list-1"></i>
</span>
<h3 class="kt-portlet__head-title">
Tabel Kategori Produk
@@ -58,7 +58,7 @@
</div>
<div class="mb-3">
<label for="parent_id" class="form-label">Induk Kategori (Opsional)</label>
<select class="form-control" id="parent_id" name="parent_id">
<select class="form-control" id="parent_id" name="parent_id" data-url="{{ route('product_categories.parents') }}">
<option value="">-- Tidak ada (Kategori Utama) --</option>
<!-- will be filled by JavaScript -->
</select>

View File

@@ -5,7 +5,7 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-plus"></i>
</span>
<h3 class="kt-portlet__head-title">Tambah Produk</h3>
</div>
@@ -60,17 +60,6 @@
</select>
</div>
{{-- Dynamic Dealer Stock Section --}}
<div id="dealerData" data-dealers='@json($dealers)'></div>
<div class="form-group">
<label><strong>Stok per Dealer</strong></label>
<div id="dynamicDealerRows"></div>
<button type="button" id="addDealerRow" class="btn btn-outline-primary mt-2">
Tambah Dealer
</button>
</div>
{{-- Submit --}}
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary">Simpan</button>

View File

@@ -5,9 +5,9 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-edit"></i>
</span>
<h3 class="kt-portlet__head-title">Tambah Produk</h3>
<h3 class="kt-portlet__head-title">Ubah Produk</h3>
</div>
</div>
@@ -32,6 +32,14 @@
<input type="text" name="unit" id="unit" class="form-control" value="{{ old('unit', $product->unit) }}" required>
</div>
<div class="form-group">
<label for="active"><strong>Status Produk</strong></label>
<select name="active" class="form-control" required>
<option value="1">Aktif</option>
<option value="0">Tidak Aktif</option>
</select>
</div>
<div class="form-group">
<label for="description"><strong>Deskripsi</strong></label>
<textarea name="description" id="description" class="form-control" rows="3">{{ old('description', $product->description) }}</textarea>
@@ -55,36 +63,6 @@
</select>
</div>
{{-- Stok per Dealer --}}
<div id="dealerData" data-dealers='@json($dealers)'></div>
<div class="form-group">
<label><strong>Stok per Dealer</strong></label>
<div id="dynamicDealerRows">
@foreach($product->dealers as $index => $dealer)
<div class="form-group row align-items-center dealer-stock-row">
<div class="col-md-6">
<select name="dealer_stock[{{ $index }}][dealer_id]" class="form-control">
<option value="">-- Pilih Dealer --</option>
@foreach ($dealers as $d)
<option value="{{ $d->id }}" {{ $d->id == $dealer->id ? 'selected' : '' }}>
{{ $d->name }}
</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[{{ $index }}][quantity]" value="{{ $dealer->pivot->quantity }}" class="form-control" placeholder="Jumlah Stok" />
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-sm remove-dealer-row">Hapus</button>
</div>
</div>
@endforeach
</div>
<button type="button" id="addDealerRow" class="btn btn-outline-primary mt-2"> Tambah Dealer</button>
</div>
{{-- Submit --}}
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary">Simpan</button>

View File

@@ -5,7 +5,7 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-list-1"></i>
</span>
<h3 class="kt-portlet__head-title">
Tabel Produk

View File

@@ -5,7 +5,7 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-list-1"></i>
</span>
<h3 class="kt-portlet__head-title">
Tabel Mutasi Stock Produk

View File

@@ -0,0 +1,56 @@
@extends('layouts.backapp')
@section('content')
<div class="kt-portlet kt-portlet--mobile" id="kt_blockui_datatable">
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-plus"></i>
</span>
<h3 class="kt-portlet__head-title">
Tambah Opname
</h3>
</div>
</div>
<div class="kt-portlet__body">
<form action="{{ route('opnames.store') }}" method="POST" id="opnameForm">
@csrf
<div class="form-group">
<label for="product"><strong>Produk</strong></label>
<input type="text" class="form-control" name="product" />
</div>
<div class="form-group">
<label for="dealer"><strong>Dealer</strong></label>
<input type="text" class="form-control" name="dealer" />
</div>
<div class="form-group">
<label for="system_quantity"><strong>Total Sistem</strong></label>
<input type="number" class="form-control" name="system_quantity" />
</div>
<div class="form-group">
<label for="phisycal_quantity"><strong>Total Sistem</strong></label>
<input type="number" class="form-control" name="phisycal_quantity" />
</div>
<div class="form-group">
<label for="difference"><strong>Perbedaan</strong></label>
<input type="number" class="form-control" name="difference" />
</div>
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary">Simpan</button>
<a href="{{ route('opnames.index') }}" class="btn btn-secondary">Batal</a>
</div>
</form>
</div>
</div>
@endsection
@section('javascripts')
<script src="{{mix('js/warehouse_management/stock_opnames/create.js')}}"></script>
@endsection

View File

@@ -5,13 +5,13 @@
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
<i class="kt-font-brand flaticon2-list-1"></i>
</span>
<h3 class="kt-portlet__head-title">
Tabel Mutasi Stock Produk
Tabel Stock Opname
</h3>
</div>
@can('create', $menus['mutations.index'])
@can('create', $menus['opnames.index'])
<div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions">

View File

@@ -9,8 +9,6 @@ use App\Http\Controllers\TransactionController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\WarehouseManagement\ProductCategoriesController;
use App\Http\Controllers\WarehouseManagement\ProductsController;
use App\Http\Controllers\WarehouseManagement\StockMutationsController;
use App\Http\Controllers\WarehouseManagement\StockOpnamesController;
use App\Http\Controllers\WorkController;
use App\Models\Menu;
use App\Models\Privilege;
@@ -206,12 +204,30 @@ Route::group(['middleware' => 'auth'], function() {
});
Route::prefix('warehouse')->group(function () {
Route::resource('products', ProductsController::class);
Route::resource('product_categories', ProductCategoriesController::class);
Route::get('categories/parents', [ProductCategoriesController::class, 'getParents'])->name('products.parents');
Route::post('products/{product}/toggle-active', [ProductsController::class, 'toggleActive'])->name('products.toggleActive');
Route::get('mutations/index',[StockMutationsController::class, 'index'])->name('mutations.index');
Route::get('opnames/index',[StockOpnamesController::class, 'index'])->name('opnames.index');
// ProductsController routes
Route::prefix('products')->controller(ProductsController::class)->group(function () {
Route::get('/', 'index')->name('products.index');
Route::get('create', 'create')->name('products.create');
Route::post('/', 'store')->name('products.store');
Route::get('{product}', 'show')->name('products.show');
Route::get('{product}/edit', 'edit')->name('products.edit');
Route::put('{product}', 'update')->name('products.update');
Route::delete('{product}', 'destroy')->name('products.destroy');
Route::post('{product}/toggle-active', 'toggleActive')->name('products.toggleActive');
});
// ProductCategoriesController routes
Route::prefix('product_categories')->controller(ProductCategoriesController::class)->group(function () {
Route::get('/', 'index')->name('product_categories.index');
Route::get('create', 'create')->name('product_categories.create');
Route::post('/', 'store')->name('product_categories.store');
Route::get('parents','product_category_parents')->name('product_categories.parents');
Route::get('{product_category}', 'show')->name('product_categories.show');
Route::get('{product_category}/edit', 'edit')->name('product_categories.edit');
Route::put('{product_category}', 'update')->name('product_categories.update');
Route::delete('{product_category}', 'destroy')->name('product_categories.destroy');
});
});
});

View File

@@ -29,14 +29,6 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/warehouse_management/products/edit.js",
"public/js/warehouse_management/products"
)
.js(
"resources/js/warehouse_management/stock_mutations/index.js",
"public/js/warehouse_management/stock_mutations"
)
.js(
"resources/js/warehouse_management/stock_opnames/index.js",
"public/js/warehouse_management/stock_opnames"
)
.sourceMaps();
mix.browserSync({