fix structure product categories table and crud product
This commit is contained in:
@@ -24,11 +24,14 @@ class ProductCategoriesController extends Controller
|
|||||||
$data = ProductCategory::query();
|
$data = ProductCategory::query();
|
||||||
return DataTables::of($data)
|
return DataTables::of($data)
|
||||||
->addIndexColumn()
|
->addIndexColumn()
|
||||||
|
->addColumn('parent', function ($row) {
|
||||||
|
return $row->parent ? $row->parent->name : '-';
|
||||||
|
})
|
||||||
->addColumn('action', function ($row) use ($menu) {
|
->addColumn('action', function ($row) use ($menu) {
|
||||||
$btn = '';
|
$btn = '';
|
||||||
|
|
||||||
if (Auth::user()->can('delete', $menu)) {
|
if (Auth::user()->can('delete', $menu)) {
|
||||||
$btn .= '<button class="btn btn-danger btn-sm btn-destroy-product-category" data-action="' . route('product_categories.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
|
$btn .= '<button style="margin-right: 8px;" class="btn btn-danger btn-sm btn-destroy-product-category" data-action="' . route('product_categories.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Auth::user()->can('update', $menu)) {
|
if (Auth::user()->can('update', $menu)) {
|
||||||
@@ -63,6 +66,7 @@ class ProductCategoriesController extends Controller
|
|||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
|
'parent_id' => 'nullable|exists:product_categories,id',
|
||||||
]);
|
]);
|
||||||
ProductCategory::create($validated);
|
ProductCategory::create($validated);
|
||||||
return response()->json(['success' => true, 'message' => 'Kategori berhasil ditambahkan.']);
|
return response()->json(['success' => true, 'message' => 'Kategori berhasil ditambahkan.']);
|
||||||
@@ -121,4 +125,10 @@ class ProductCategoriesController extends Controller
|
|||||||
ProductCategory::findOrFail($id)->delete();
|
ProductCategory::findOrFail($id)->delete();
|
||||||
return response()->json(['success' => true, 'message' => 'Kategorii berhasil dihapus.']);
|
return response()->json(['success' => true, 'message' => 'Kategorii berhasil dihapus.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getParents(Request $request)
|
||||||
|
{
|
||||||
|
$parents = ProductCategory::whereNull('parent_id')->get(['id', 'name']);
|
||||||
|
return response()->json($parents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\WarehouseManagement;
|
namespace App\Http\Controllers\WarehouseManagement;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Dealer;
|
||||||
use App\Models\Menu;
|
use App\Models\Menu;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\ProductCategory;
|
use App\Models\ProductCategory;
|
||||||
@@ -34,16 +35,18 @@ class ProductsController extends Controller
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
->addColumn('action', function ($row) use ($menu) {
|
->addColumn('action', function ($row) use ($menu) {
|
||||||
$btn = '';
|
$btn = '<div class="d-flex">';
|
||||||
|
|
||||||
if (Auth::user()->can('delete', $menu)) {
|
if (Auth::user()->can('delete', $menu)) {
|
||||||
$btn .= '<button class="btn btn-danger btn-sm btn-destroy-product" data-action="' . route('products.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
|
$btn .= '<button style="margin-right: 8px;" class="btn btn-danger btn-sm btn-destroy-product" data-action="' . route('products.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Auth::user()->can('update', $menu)) {
|
if (Auth::user()->can('update', $menu)) {
|
||||||
$btn .= '<button class="btn btn-warning btn-sm btn-edit-product" data-url="' . route('products.edit', $row->id) . '" data-action="' . route('products.update', $row->id) . '" data-id="' . $row->id . '">Edit</button>';
|
$btn .= '<a href="' . route('products.edit', $row->id) . '" class="btn btn-warning btn-sm">Edit</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$btn .= '</div>';
|
||||||
|
|
||||||
return $btn;
|
return $btn;
|
||||||
})
|
})
|
||||||
->rawColumns(['action'])
|
->rawColumns(['action'])
|
||||||
@@ -59,8 +62,10 @@ class ProductsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
//
|
$categories = ProductCategory::with('children')->whereNull('parent_id')->get();
|
||||||
}
|
$dealers = Dealer::all();
|
||||||
|
return view('warehouse_management.products.create', compact('categories', 'dealers'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
@@ -70,8 +75,39 @@ class ProductsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
}
|
'code' => 'required|string|unique:products,code',
|
||||||
|
'name' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'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',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create product
|
||||||
|
$product = Product::create([
|
||||||
|
'code' => $request->code,
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'product_category_id' => $request->product_category_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Prepare dealer stock for pivot
|
||||||
|
$pivotData = [];
|
||||||
|
if ($request->has('dealer_stock')) {
|
||||||
|
foreach ($request->dealer_stock as $stockData) {
|
||||||
|
if (empty($stockData['dealer_id']) || !isset($stockData['quantity'])) continue;
|
||||||
|
|
||||||
|
$pivotData[$stockData['dealer_id']] = ['quantity' => $stockData['quantity']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach dealer stock using pivot table
|
||||||
|
$product->dealers()->attach($pivotData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('products.index')->with('success', 'Produk berhasil ditambahkan.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
@@ -92,7 +128,12 @@ class ProductsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
//
|
$product = Product::findOrFail($id);
|
||||||
|
return view('warehouse_management.products.edit', [
|
||||||
|
'product' => $product->load('dealers'),
|
||||||
|
'dealers' => Dealer::all(),
|
||||||
|
'categories' => ProductCategory::with('children')->get(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,10 +143,33 @@ class ProductsController extends Controller
|
|||||||
* @param int $id
|
* @param int $id
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, Product $product)
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
}
|
'code' => 'required|string|unique:products,code,' . $product->id,
|
||||||
|
'name' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'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->update($request->only(['code', 'name', 'description', 'product_category_id']));
|
||||||
|
|
||||||
|
// Prepare pivot sync data
|
||||||
|
$syncData = [];
|
||||||
|
if ($request->has('dealer_stock')) {
|
||||||
|
foreach ($request->dealer_stock as $item) {
|
||||||
|
$syncData[$item['dealer_id']] = ['quantity' => $item['quantity']];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync with pivot table
|
||||||
|
$product->dealers()->sync($syncData);
|
||||||
|
|
||||||
|
return redirect()->route('products.index')->with('success', 'Produk berhasil diperbarui.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* Remove the specified resource from storage.
|
||||||
@@ -113,8 +177,14 @@ class ProductsController extends Controller
|
|||||||
* @param int $id
|
* @param int $id
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy($id)
|
public function destroy(Product $product)
|
||||||
{
|
{
|
||||||
//
|
// Detach all dealer relationships (optional if using cascade on delete)
|
||||||
|
$product->dealers()->detach();
|
||||||
|
|
||||||
|
// Delete the product
|
||||||
|
$product->delete();
|
||||||
|
|
||||||
|
return response()->json(['success' => true, 'message' => 'Produk berhasil dihapus.']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,17 @@ class ProductCategory extends Model
|
|||||||
{
|
{
|
||||||
use HasFactory, SoftDeletes;
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
protected $fillable = ['name'];
|
protected $fillable = ['name','parent_id'];
|
||||||
|
|
||||||
public function products(){
|
public function products(){
|
||||||
return $this->hasMany(Product::class, 'product_category_id');
|
return $this->hasMany(Product::class, 'product_category_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function parent(){
|
||||||
|
return $this->belongsTo(ProductCategory::class, 'parent_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function children(){
|
||||||
|
return $this->hasMany(ProductCategory::class,'parent_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,33 +17,40 @@ class ProductAndCategorySeeder extends Seeder
|
|||||||
{
|
{
|
||||||
$categories = [
|
$categories = [
|
||||||
'Oli & Pelumas' => [
|
'Oli & Pelumas' => [
|
||||||
['code' => 'OLI001', 'name' => 'Oli Mesin 10W-40'],
|
['code' => 'OLI001', 'name' => 'Oli Mesin 10W-40', 'subcategory' => 'Oli Mesin'],
|
||||||
['code' => 'OLI002', 'name' => 'Oli Gardan'],
|
['code' => 'OLI002', 'name' => 'Oli Gardan', 'subcategory' => 'Oli Gardan'],
|
||||||
],
|
],
|
||||||
'Aki & Kelistrikan' => [
|
'Aki & Kelistrikan' => [
|
||||||
['code' => 'AKI001', 'name' => 'Aki Kering 12V'],
|
['code' => 'AKI001', 'name' => 'Aki Kering 12V', 'subcategory' => 'Aki'],
|
||||||
['code' => 'AKI002', 'name' => 'Regulator Rectifier'],
|
['code' => 'AKI002', 'name' => 'Regulator Rectifier', 'subcategory' => 'Kelistrikan'],
|
||||||
],
|
],
|
||||||
'Rem' => [
|
'Rem' => [
|
||||||
['code' => 'REM001', 'name' => 'Kampas Rem Belakang'],
|
['code' => 'REM001', 'name' => 'Kampas Rem Belakang', 'subcategory' => 'Kampas Rem'],
|
||||||
['code' => 'REM002', 'name' => 'Cakram Depan'],
|
['code' => 'REM002', 'name' => 'Cakram Depan', 'subcategory' => 'Cakram'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
foreach ($categories as $parentName => $products) {
|
||||||
foreach ($categories as $categoryName => $products) {
|
// Create parent category
|
||||||
$category = ProductCategory::firstOrCreate(
|
$parent = ProductCategory::firstOrCreate(
|
||||||
['name' => $categoryName],
|
['name' => $parentName],
|
||||||
['created_at' => now(), 'updated_at' => now()]
|
['created_at' => now(), 'updated_at' => now()]
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
|
// Create child category (sub-category)
|
||||||
|
$child = ProductCategory::firstOrCreate(
|
||||||
|
['name' => $product['subcategory'], 'parent_id' => $parent->id],
|
||||||
|
['created_at' => now(), 'updated_at' => now()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create product in the child category
|
||||||
Product::updateOrCreate(
|
Product::updateOrCreate(
|
||||||
['code' => $product['code']],
|
['code' => $product['code']],
|
||||||
[
|
[
|
||||||
'name' => $product['name'],
|
'name' => $product['name'],
|
||||||
'description' => $product['name'],
|
'description' => $product['name'],
|
||||||
'product_category_id' => $category->id
|
'product_category_id' => $child->id
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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
File diff suppressed because one or more lines are too long
@@ -2,5 +2,7 @@
|
|||||||
"/js/app.js": "/js/app.js",
|
"/js/app.js": "/js/app.js",
|
||||||
"/js/warehouse_management/product_categories/index.js": "/js/warehouse_management/product_categories/index.js",
|
"/js/warehouse_management/product_categories/index.js": "/js/warehouse_management/product_categories/index.js",
|
||||||
"/js/warehouse_management/products/index.js": "/js/warehouse_management/products/index.js",
|
"/js/warehouse_management/products/index.js": "/js/warehouse_management/products/index.js",
|
||||||
|
"/js/warehouse_management/products/create.js": "/js/warehouse_management/products/create.js",
|
||||||
|
"/js/warehouse_management/products/edit.js": "/js/warehouse_management/products/edit.js",
|
||||||
"/css/app.css": "/css/app.css"
|
"/css/app.css": "/css/app.css"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,8 @@ let table = $("#product-categories-table").DataTable({
|
|||||||
serverSide: true,
|
serverSide: true,
|
||||||
ajax: url,
|
ajax: url,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
data: "DT_RowIndex",
|
|
||||||
name: "DT_RowIndex",
|
|
||||||
orderable: false,
|
|
||||||
searchable: false,
|
|
||||||
},
|
|
||||||
{ data: "name", name: "name" },
|
{ data: "name", name: "name" },
|
||||||
|
{ data: "parent", name: "parent" },
|
||||||
{ data: "action", name: "action", orderable: false, searchable: false },
|
{ data: "action", name: "action", orderable: false, searchable: false },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -27,6 +22,7 @@ $(document).ready(function () {
|
|||||||
$("#category_id").val("");
|
$("#category_id").val("");
|
||||||
$("#modalTitle").text("Tambah Kategori");
|
$("#modalTitle").text("Tambah Kategori");
|
||||||
$("#productCategoryModal").modal("show");
|
$("#productCategoryModal").modal("show");
|
||||||
|
loadParentCategories();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Submit form (baik tambah maupun edit)
|
// Submit form (baik tambah maupun edit)
|
||||||
@@ -67,6 +63,26 @@ $(document).on("click", ".btn-edit-product-category", function () {
|
|||||||
success: function (response) {
|
success: function (response) {
|
||||||
$("#category_id").val(response.id);
|
$("#category_id").val(response.id);
|
||||||
$("#name").val(response.name);
|
$("#name").val(response.name);
|
||||||
|
// Get parent categories and populate select
|
||||||
|
$.ajax({
|
||||||
|
url: "/warehouse/categories/parents", // Adjust to match your route
|
||||||
|
method: "GET",
|
||||||
|
success: function (parents) {
|
||||||
|
let options =
|
||||||
|
'<option value="">-- Tidak ada (Parent)</option>';
|
||||||
|
parents.forEach(function (parent) {
|
||||||
|
// Avoid self-select
|
||||||
|
if (parent.id !== response.id) {
|
||||||
|
options += `<option value="${parent.id}" ${
|
||||||
|
response.parent_id === parent.id
|
||||||
|
? "selected"
|
||||||
|
: ""
|
||||||
|
}>${parent.name}</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#parent_id").html(options);
|
||||||
|
},
|
||||||
|
});
|
||||||
$("#modalTitle").text("Edit Kategori");
|
$("#modalTitle").text("Edit Kategori");
|
||||||
$("#productCategoryModal").modal("show");
|
$("#productCategoryModal").modal("show");
|
||||||
},
|
},
|
||||||
@@ -107,3 +123,24 @@ $(document).on("click", ".btn-destroy-product-category", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadParentCategories(selectedId = null) {
|
||||||
|
$.ajax({
|
||||||
|
url: "/warehouse/categories/parents", // create this route
|
||||||
|
type: "GET",
|
||||||
|
success: function (data) {
|
||||||
|
$("#parent_id")
|
||||||
|
.empty()
|
||||||
|
.append(
|
||||||
|
'<option value="">-- Tidak ada (Kategori Utama) --</option>'
|
||||||
|
);
|
||||||
|
data.forEach(function (category) {
|
||||||
|
$("#parent_id").append(
|
||||||
|
`<option value="${category.id}" ${
|
||||||
|
selectedId == category.id ? "selected" : ""
|
||||||
|
}>${category.name}</option>`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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("");
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -10,12 +10,6 @@ let table = $("#products-table").DataTable({
|
|||||||
serverSide: true,
|
serverSide: true,
|
||||||
ajax: url,
|
ajax: url,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
data: "DT_RowIndex",
|
|
||||||
name: "DT_RowIndex",
|
|
||||||
orderable: false,
|
|
||||||
searchable: false,
|
|
||||||
},
|
|
||||||
{ data: "code", name: "code" },
|
{ data: "code", name: "code" },
|
||||||
{ data: "name", name: "name" },
|
{ data: "name", name: "name" },
|
||||||
{ data: "category_name", name: "category.name" },
|
{ data: "category_name", name: "category.name" },
|
||||||
@@ -24,65 +18,9 @@ let table = $("#products-table").DataTable({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).on("click", ".btn-destroy-product", function () {
|
||||||
$("#addProductCategory").click(function () {
|
|
||||||
$("#productCategoryForm")[0].reset();
|
|
||||||
$("#category_id").val("");
|
|
||||||
$("#modalTitle").text("Tambah Kategori");
|
|
||||||
$("#productCategoryModal").modal("show");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit form (baik tambah maupun edit)
|
|
||||||
$("#productCategoryForm").submit(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
let id = $("#category_id").val();
|
|
||||||
let url = id
|
|
||||||
? `/warehouse/product_categories/${id}`
|
|
||||||
: `/warehouse/product_categories`;
|
|
||||||
let method = id ? "PUT" : "POST";
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
method: method,
|
|
||||||
data: {
|
|
||||||
name: $("#name").val(),
|
|
||||||
_token: $('meta[name="csrf-token"]').attr("content"),
|
|
||||||
...(id && { _method: "PUT" }),
|
|
||||||
},
|
|
||||||
success: function () {
|
|
||||||
$("#productCategoryModal").modal("hide");
|
|
||||||
$("#product-categories-table").DataTable().ajax.reload();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Gagal menyimpan data");
|
|
||||||
console.error(xhr.responseText);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$(document).on("click", ".btn-edit-product-category", function () {
|
|
||||||
const id = $(this).data("id");
|
|
||||||
const url = $(this).data("url");
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
method: "GET",
|
|
||||||
success: function (response) {
|
|
||||||
$("#category_id").val(response.id);
|
|
||||||
$("#name").val(response.name);
|
|
||||||
$("#modalTitle").text("Edit Kategori");
|
|
||||||
$("#productCategoryModal").modal("show");
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Gagal mengambil data");
|
|
||||||
console.error(xhr.responseText);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on("click", ".btn-destroy-product-category", function () {
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Hapus nama kategori?",
|
title: "Hapus produk?",
|
||||||
text: "Anda tidak akan bisa mengembalikannya!",
|
text: "Anda tidak akan bisa mengembalikannya!",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
confirmButtonColor: "#d33",
|
confirmButtonColor: "#d33",
|
||||||
@@ -99,11 +37,11 @@ $(document).on("click", ".btn-destroy-product-category", function () {
|
|||||||
_token: $('meta[name="csrf-token"]').attr("content"),
|
_token: $('meta[name="csrf-token"]').attr("content"),
|
||||||
},
|
},
|
||||||
success: function () {
|
success: function () {
|
||||||
alert("Kategori berhasil dihapus.");
|
alert("Produk berhasil dihapus.");
|
||||||
$("#product-categories-table").DataTable().ajax.reload();
|
$("#products-table").DataTable().ajax.reload();
|
||||||
},
|
},
|
||||||
error: function (xhr) {
|
error: function (xhr) {
|
||||||
alert("Gagal menghapus kategori.");
|
alert("Gagal menghapus produk.");
|
||||||
console.error(xhr.responseText);
|
console.error(xhr.responseText);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<i class="kt-font-brand flaticon2-line-chart"></i>
|
<i class="kt-font-brand flaticon2-line-chart"></i>
|
||||||
</span>
|
</span>
|
||||||
<h3 class="kt-portlet__head-title">
|
<h3 class="kt-portlet__head-title">
|
||||||
Kategori Produk
|
Tabel Kategori Produk
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@can('create', $menus['product_categories.index'])
|
@can('create', $menus['product_categories.index'])
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
<table class="table table-striped table-bordered table-hover" id="product-categories-table" data-url="{{ route("product_categories.index") }}">
|
<table class="table table-striped table-bordered table-hover" id="product-categories-table" data-url="{{ route("product_categories.index") }}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>NO</th>
|
|
||||||
<th>Nama Kategori</th>
|
<th>Nama Kategori</th>
|
||||||
|
<th>Parent</th>
|
||||||
<th>Aksi</th>
|
<th>Aksi</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -51,11 +51,18 @@
|
|||||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="hidden" id="category_id">
|
<input type="hidden" id="category_id" name="id">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Nama Kategori</label>
|
<label for="name" class="form-label">Nama Kategori</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" required>
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
</div>
|
</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">
|
||||||
|
<option value="">-- Tidak ada (Kategori Utama) --</option>
|
||||||
|
<!-- will be filled by JavaScript -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary" id="saveBtn">Simpan</button>
|
<button type="submit" class="btn btn-primary" id="saveBtn">Simpan</button>
|
||||||
@@ -64,7 +71,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- end modal --}}
|
{{-- end modal --}}
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,73 @@
|
|||||||
@extends('layouts.backapp')
|
@extends('layouts.backapp')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="kt-portlet kt-portlet--mobile" id="kt_blockui_datatable">
|
<div class="kt-portlet kt-portlet--mobile">
|
||||||
<div class="kt-portlet__head kt-portlet__head--lg">
|
<div class="kt-portlet__head kt-portlet__head--lg">
|
||||||
<div class="kt-portlet__head-label">
|
<div class="kt-portlet__head-label">
|
||||||
<span class="kt-portlet__head-icon">
|
<span class="kt-portlet__head-icon">
|
||||||
<i class="kt-font-brand flaticon2-line-chart"></i>
|
<i class="kt-font-brand flaticon2-line-chart"></i>
|
||||||
</span>
|
</span>
|
||||||
<h3 class="kt-portlet__head-title">
|
<h3 class="kt-portlet__head-title">Tambah Produk</h3>
|
||||||
Tambah Produk
|
</div>
|
||||||
</h3>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="kt-portlet__body">
|
||||||
|
<form action="{{ route('products.store') }}" method="POST" id="productForm">
|
||||||
<div class="kt-portlet__body">
|
@csrf
|
||||||
<div class="">
|
|
||||||
|
{{-- Product Info --}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="code"><strong>Kode Produk</strong></label>
|
||||||
|
<input type="text" name="code" id="code" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name"><strong>Nama Produk</strong></label>
|
||||||
|
<input type="text" name="name" id="name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description"><strong>Deskripsi</strong></label>
|
||||||
|
<textarea name="description" id="description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Product Category --}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="product_category_id"><strong>Kategori Produk</strong></label>
|
||||||
|
<select name="product_category_id" id="product_category_id" class="form-control" required>
|
||||||
|
<option value="">-- Pilih Kategori --</option>
|
||||||
|
@foreach ($categories as $category)
|
||||||
|
<option value="{{ $category->id }}" disabled style="background-color: #f0f0f0;">
|
||||||
|
{{ $category->name }}
|
||||||
|
</option>
|
||||||
|
@foreach ($category->children as $child)
|
||||||
|
<option value="{{ $child->id }}"> {{ $child->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</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>
|
||||||
|
<a href="{{ route('products.index') }}" class="btn btn-secondary">Batal</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
|
||||||
<!--begin: Datatable -->
|
|
||||||
<table class="table table-striped table-bordered table-hover" id="stock-dealers-table" data-url="{{ route("products.index") }}">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>NO</th>
|
|
||||||
<th>Kode</th>
|
|
||||||
<th>Nama</th>
|
|
||||||
<th>Address</th>
|
|
||||||
<th>Stok</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
</table>
|
|
||||||
<!--end: Datatable -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('javascripts')
|
||||||
|
<script src="{{ mix('js/warehouse_management/products/create.js') }}"></script>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
@extends('layouts.backapp')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="kt-portlet kt-portlet--mobile">
|
||||||
|
<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>
|
||||||
|
</span>
|
||||||
|
<h3 class="kt-portlet__head-title">Tambah Produk</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kt-portlet__body">
|
||||||
|
<form action="{{ route('products.update', $product->id) }}" method="POST" id="productForm">
|
||||||
|
@csrf
|
||||||
|
@method('PUT') {{-- Correctly spoof PUT method --}}
|
||||||
|
|
||||||
|
{{-- Product Info --}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="code"><strong>Kode Produk</strong></label>
|
||||||
|
<input type="text" name="code" id="code" class="form-control" value="{{ old('code', $product->code) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name"><strong>Nama Produk</strong></label>
|
||||||
|
<input type="text" name="name" id="name" class="form-control" value="{{ old('name', $product->name) }}" required>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Product Category --}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="product_category_id"><strong>Kategori Produk</strong></label>
|
||||||
|
<select name="product_category_id" id="product_category_id" class="form-control" required>
|
||||||
|
<option value="">-- Pilih Kategori --</option>
|
||||||
|
@foreach ($categories as $category)
|
||||||
|
<option value="{{ $category->id }}" disabled style="background-color: #f0f0f0;">
|
||||||
|
{{ $category->name }}
|
||||||
|
</option>
|
||||||
|
@foreach ($category->children as $child)
|
||||||
|
<option value="{{ $child->id }}" {{ $child->id == old('product_category_id', $product->product_category_id) ? 'selected' : '' }}>
|
||||||
|
{{ $child->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</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>
|
||||||
|
<a href="{{ route('products.index') }}" class="btn btn-secondary">Batal</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('javascripts')
|
||||||
|
<script src="{{ mix('js/warehouse_management/products/edit.js') }}"></script>
|
||||||
|
@endsection
|
||||||
@@ -8,14 +8,14 @@
|
|||||||
<i class="kt-font-brand flaticon2-line-chart"></i>
|
<i class="kt-font-brand flaticon2-line-chart"></i>
|
||||||
</span>
|
</span>
|
||||||
<h3 class="kt-portlet__head-title">
|
<h3 class="kt-portlet__head-title">
|
||||||
Produk
|
Tabel Produk
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@can('create', $menus['product_categories.index'])
|
@can('create', $menus['product_categories.index'])
|
||||||
<div class="kt-portlet__head-toolbar">
|
<div class="kt-portlet__head-toolbar">
|
||||||
<div class="kt-portlet__head-wrapper">
|
<div class="kt-portlet__head-wrapper">
|
||||||
<div class="kt-portlet__head-actions">
|
<div class="kt-portlet__head-actions">
|
||||||
<button type="button" class="btn btn-bold btn-label-brand btn-sm" id="addProductCategory"> Tambah </button>
|
<a href="{{ route('products.create') }}" class="btn btn-bold btn-label-brand btn--sm">Tambah</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
<table class="table table-striped table-bordered table-hover" id="products-table" data-url="{{ route("products.index") }}">
|
<table class="table table-striped table-bordered table-hover" id="products-table" data-url="{{ route("products.index") }}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>NO</th>
|
|
||||||
<th>Kode</th>
|
<th>Kode</th>
|
||||||
<th>Nama</th>
|
<th>Nama</th>
|
||||||
<th>Kategori</th>
|
<th>Kategori</th>
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ Route::group(['middleware' => 'auth'], function() {
|
|||||||
Route::prefix('warehouse')->group(function (){
|
Route::prefix('warehouse')->group(function (){
|
||||||
Route::resource('products', ProductsController::class);
|
Route::resource('products', ProductsController::class);
|
||||||
Route::resource('product_categories', ProductCategoriesController::class);
|
Route::resource('product_categories', ProductCategoriesController::class);
|
||||||
|
Route::get('categories/parents', [ProductCategoriesController::class, 'getParents']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,12 @@ mix.js("resources/js/app.js", "public/js")
|
|||||||
"resources/js/warehouse_management/products/index.js",
|
"resources/js/warehouse_management/products/index.js",
|
||||||
"public/js/warehouse_management/products"
|
"public/js/warehouse_management/products"
|
||||||
)
|
)
|
||||||
|
.js(
|
||||||
|
"resources/js/warehouse_management/products/create.js",
|
||||||
|
"public/js/warehouse_management/products"
|
||||||
|
)
|
||||||
|
.js(
|
||||||
|
"resources/js/warehouse_management/products/edit.js",
|
||||||
|
"public/js/warehouse_management/products"
|
||||||
|
)
|
||||||
.sourceMaps();
|
.sourceMaps();
|
||||||
|
|||||||
Reference in New Issue
Block a user