partial update create mutations
This commit is contained in:
262
resources/js/warehouse_management/mutations/create.js
Normal file
262
resources/js/warehouse_management/mutations/create.js
Normal file
@@ -0,0 +1,262 @@
|
||||
$(document).ready(function () {
|
||||
let productIndex = 1;
|
||||
|
||||
// Initialize Select2
|
||||
$(".select2").select2({
|
||||
placeholder: "Pilih...",
|
||||
allowClear: true,
|
||||
});
|
||||
|
||||
// Prevent same dealer selection
|
||||
$("#from_dealer_id, #to_dealer_id").on("change", function () {
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
const toDealerId = $("#to_dealer_id").val();
|
||||
|
||||
if (fromDealerId && toDealerId && fromDealerId === toDealerId) {
|
||||
$(this).val("").trigger("change");
|
||||
alert("Dealer asal dan tujuan tidak boleh sama!");
|
||||
}
|
||||
|
||||
// Update available stock when dealer changes
|
||||
updateAllAvailableStock();
|
||||
});
|
||||
|
||||
// Add new product row
|
||||
$("#add-product").on("click", function () {
|
||||
const newRow = createProductRow(productIndex);
|
||||
$("#products-tbody").append(newRow);
|
||||
|
||||
// Initialize Select2 for new row
|
||||
const newSelect = $(
|
||||
`#products-tbody tr[data-index="${productIndex}"] .product-select`
|
||||
);
|
||||
newSelect.select2({
|
||||
placeholder: "Pilih Produk...",
|
||||
allowClear: true,
|
||||
});
|
||||
|
||||
productIndex++;
|
||||
updateRemoveButtons();
|
||||
});
|
||||
|
||||
// Remove product row
|
||||
$(document).on("click", ".remove-product", function () {
|
||||
$(this).closest("tr").remove();
|
||||
updateRemoveButtons();
|
||||
reindexRows();
|
||||
});
|
||||
|
||||
// Handle product selection change
|
||||
$(document).on("change", ".product-select", function () {
|
||||
const row = $(this).closest("tr");
|
||||
const productId = $(this).val();
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
|
||||
if (productId && fromDealerId) {
|
||||
getAvailableStock(productId, fromDealerId, row);
|
||||
} else {
|
||||
row.find(".available-stock").text("-");
|
||||
row.find(".quantity-input").attr("max", "");
|
||||
}
|
||||
});
|
||||
|
||||
// Validate quantity input
|
||||
$(document).on("input", ".quantity-input", function () {
|
||||
const maxValue = parseFloat($(this).attr("max"));
|
||||
const currentValue = parseFloat($(this).val());
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
$(this).val(maxValue);
|
||||
$(this).addClass("is-invalid");
|
||||
|
||||
if (!$(this).siblings(".invalid-feedback").length) {
|
||||
$(this).after(
|
||||
'<div class="invalid-feedback">Quantity melebihi stock yang tersedia</div>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass("is-invalid");
|
||||
$(this).siblings(".invalid-feedback").remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission
|
||||
$("#mutation-form").on("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const submitBtn = $("#submit-btn");
|
||||
const originalText = submitBtn.html();
|
||||
|
||||
submitBtn
|
||||
.prop("disabled", true)
|
||||
.html('<i class="la la-spinner la-spin"></i> Menyimpan...');
|
||||
|
||||
// Submit form
|
||||
this.submit();
|
||||
});
|
||||
|
||||
function createProductRow(index) {
|
||||
// Get product options from the existing select
|
||||
const existingSelect = $(".product-select").first();
|
||||
const productOptions = existingSelect.html();
|
||||
|
||||
return `
|
||||
<tr class="product-row" data-index="${index}">
|
||||
<td>
|
||||
<select name="products[${index}][product_id]" class="form-control select2 product-select" required>
|
||||
${productOptions}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<span class="available-stock text-muted">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
name="products[${index}][quantity_requested]"
|
||||
class="form-control quantity-input"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
placeholder="0"
|
||||
required>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
name="products[${index}][notes]"
|
||||
class="form-control"
|
||||
placeholder="Catatan produk (opsional)">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-product">
|
||||
<i class="la la-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateRemoveButtons() {
|
||||
const rows = $(".product-row");
|
||||
$(".remove-product").prop("disabled", rows.length <= 1);
|
||||
}
|
||||
|
||||
function reindexRows() {
|
||||
$(".product-row").each(function (index) {
|
||||
$(this).attr("data-index", index);
|
||||
$(this)
|
||||
.find('select[name*="product_id"]')
|
||||
.attr("name", `products[${index}][product_id]`);
|
||||
$(this)
|
||||
.find('input[name*="quantity_requested"]')
|
||||
.attr("name", `products[${index}][quantity_requested]`);
|
||||
$(this)
|
||||
.find('input[name*="notes"]')
|
||||
.attr("name", `products[${index}][notes]`);
|
||||
});
|
||||
productIndex = $(".product-row").length;
|
||||
}
|
||||
|
||||
function getAvailableStock(productId, dealerId, row) {
|
||||
$.ajax({
|
||||
url: "/warehouse/mutations/get-product-stock",
|
||||
method: "GET",
|
||||
data: {
|
||||
product_id: productId,
|
||||
dealer_id: dealerId,
|
||||
},
|
||||
beforeSend: function () {
|
||||
row.find(".available-stock").html(
|
||||
'<i class="la la-spinner la-spin"></i>'
|
||||
);
|
||||
},
|
||||
success: function (response) {
|
||||
const stock = parseFloat(response.current_stock);
|
||||
row.find(".available-stock").text(stock.toLocaleString());
|
||||
row.find(".quantity-input").attr("max", stock);
|
||||
|
||||
// Set max value message
|
||||
if (stock <= 0) {
|
||||
row.find(".available-stock")
|
||||
.addClass("text-danger")
|
||||
.removeClass("text-muted");
|
||||
row.find(".quantity-input").attr("readonly", true).val("");
|
||||
} else {
|
||||
row.find(".available-stock")
|
||||
.removeClass("text-danger")
|
||||
.addClass("text-muted");
|
||||
row.find(".quantity-input").attr("readonly", false);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
row.find(".available-stock")
|
||||
.text("Error")
|
||||
.addClass("text-danger");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateAllAvailableStock() {
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
|
||||
$(".product-row").each(function () {
|
||||
const row = $(this);
|
||||
const productId = row.find(".product-select").val();
|
||||
|
||||
if (productId && fromDealerId) {
|
||||
getAvailableStock(productId, fromDealerId, row);
|
||||
} else {
|
||||
row.find(".available-stock").text("-");
|
||||
row.find(".quantity-input").attr("max", "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
const toDealerId = $("#to_dealer_id").val();
|
||||
|
||||
// Check dealers
|
||||
if (!fromDealerId) {
|
||||
alert("Pilih dealer asal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!toDealerId) {
|
||||
alert("Pilih dealer tujuan");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fromDealerId === toDealerId) {
|
||||
alert("Dealer asal dan tujuan tidak boleh sama");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check products
|
||||
const productRows = $(".product-row");
|
||||
if (productRows.length === 0) {
|
||||
alert("Tambahkan minimal satu produk");
|
||||
return false;
|
||||
}
|
||||
|
||||
let hasValidProduct = false;
|
||||
productRows.each(function () {
|
||||
const productId = $(this).find(".product-select").val();
|
||||
const quantity = $(this).find(".quantity-input").val();
|
||||
|
||||
if (productId && quantity && parseFloat(quantity) > 0) {
|
||||
hasValidProduct = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasValidProduct) {
|
||||
alert("Pilih minimal satu produk dengan quantity yang valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
});
|
||||
261
resources/js/warehouse_management/mutations/index.js
Normal file
261
resources/js/warehouse_management/mutations/index.js
Normal file
@@ -0,0 +1,261 @@
|
||||
$(document).ready(function () {
|
||||
// Initialize DataTable
|
||||
var table = $("#mutations-table").DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: $("#mutations-table").data("url"),
|
||||
type: "GET",
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: "DT_RowIndex",
|
||||
name: "DT_RowIndex",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
data: "mutation_number",
|
||||
name: "mutation_number",
|
||||
width: "12%",
|
||||
},
|
||||
{
|
||||
data: "created_at",
|
||||
name: "created_at",
|
||||
width: "12%",
|
||||
},
|
||||
{
|
||||
data: "from_dealer",
|
||||
name: "fromDealer.name",
|
||||
width: "15%",
|
||||
},
|
||||
{
|
||||
data: "to_dealer",
|
||||
name: "toDealer.name",
|
||||
width: "15%",
|
||||
},
|
||||
{
|
||||
data: "requested_by",
|
||||
name: "requestedBy.name",
|
||||
width: "12%",
|
||||
},
|
||||
{
|
||||
data: "total_items",
|
||||
name: "total_items",
|
||||
width: "8%",
|
||||
className: "text-center",
|
||||
},
|
||||
{
|
||||
data: "status",
|
||||
name: "status",
|
||||
width: "12%",
|
||||
className: "text-center",
|
||||
},
|
||||
{
|
||||
data: "action",
|
||||
name: "action",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: "15%",
|
||||
className: "text-center",
|
||||
},
|
||||
],
|
||||
order: [[2, "desc"]], // Order by created_at desc
|
||||
pageLength: 10,
|
||||
responsive: true,
|
||||
language: {
|
||||
processing: "Memuat data...",
|
||||
lengthMenu: "Tampilkan _MENU_ data per halaman",
|
||||
zeroRecords: "Data tidak ditemukan",
|
||||
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
|
||||
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
|
||||
infoFiltered: "(difilter dari _MAX_ total data)",
|
||||
},
|
||||
});
|
||||
|
||||
// Handle Receive Button Click
|
||||
$(document).on("click", ".btn-receive", function () {
|
||||
var mutationId = $(this).data("id");
|
||||
$("#receiveModal" + mutationId).modal("show");
|
||||
});
|
||||
|
||||
// Handle Approve Button Click
|
||||
$(document).on("click", ".btn-approve", function () {
|
||||
var mutationId = $(this).data("id");
|
||||
|
||||
// Load mutation details via AJAX
|
||||
$.ajax({
|
||||
url: "/warehouse/mutations/" + mutationId + "/details",
|
||||
type: "GET",
|
||||
beforeSend: function () {
|
||||
$("#mutation-details" + mutationId).html(
|
||||
'<div class="text-center">' +
|
||||
'<div class="spinner-border" role="status">' +
|
||||
'<span class="sr-only">Loading...</span>' +
|
||||
"</div>" +
|
||||
"<p>Memuat detail produk...</p>" +
|
||||
"</div>"
|
||||
);
|
||||
},
|
||||
success: function (response) {
|
||||
var detailsHtml = "<h6>Detail Produk:</h6>";
|
||||
detailsHtml += '<div class="table-responsive">';
|
||||
detailsHtml += '<table class="table table-sm">';
|
||||
detailsHtml += "<thead>";
|
||||
detailsHtml += "<tr>";
|
||||
detailsHtml += "<th>Produk</th>";
|
||||
detailsHtml += "<th>Diminta</th>";
|
||||
detailsHtml += "<th>Disetujui</th>";
|
||||
detailsHtml += "<th>Stock Tersedia</th>";
|
||||
detailsHtml += "</tr>";
|
||||
detailsHtml += "</thead>";
|
||||
detailsHtml += "<tbody>";
|
||||
|
||||
response.details.forEach(function (detail, index) {
|
||||
detailsHtml += "<tr>";
|
||||
detailsHtml += "<td>" + detail.product.name + "</td>";
|
||||
detailsHtml +=
|
||||
"<td>" +
|
||||
parseFloat(detail.quantity_requested).toLocaleString() +
|
||||
"</td>";
|
||||
detailsHtml += "<td>";
|
||||
detailsHtml +=
|
||||
'<input type="number" name="details[' +
|
||||
detail.id +
|
||||
'][quantity_approved]" ';
|
||||
detailsHtml += 'class="form-control form-control-sm" ';
|
||||
detailsHtml += 'value="' + detail.quantity_requested + '" ';
|
||||
detailsHtml +=
|
||||
'min="0" max="' +
|
||||
Math.min(
|
||||
detail.quantity_requested,
|
||||
detail.available_stock
|
||||
) +
|
||||
'" ';
|
||||
detailsHtml += 'step="0.01" required>';
|
||||
detailsHtml += "</td>";
|
||||
detailsHtml +=
|
||||
"<td>" +
|
||||
parseFloat(detail.available_stock).toLocaleString() +
|
||||
"</td>";
|
||||
detailsHtml += "</tr>";
|
||||
});
|
||||
|
||||
detailsHtml += "</tbody>";
|
||||
detailsHtml += "</table>";
|
||||
detailsHtml += "</div>";
|
||||
|
||||
$("#mutation-details" + mutationId).html(detailsHtml);
|
||||
},
|
||||
error: function () {
|
||||
$("#mutation-details" + mutationId).html(
|
||||
'<div class="alert alert-danger">Gagal memuat detail produk</div>'
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
$("#approveModal" + mutationId).modal("show");
|
||||
});
|
||||
|
||||
// Handle other button clicks
|
||||
$(document).on("click", ".btn-reject", function () {
|
||||
var mutationId = $(this).data("id");
|
||||
$("#rejectModal" + mutationId).modal("show");
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-complete", function () {
|
||||
var mutationId = $(this).data("id");
|
||||
$("#completeModal" + mutationId).modal("show");
|
||||
});
|
||||
|
||||
// Handle Cancel Button Click with SweetAlert
|
||||
$(document).on("click", ".btn-cancel", function () {
|
||||
var mutationId = $(this).data("id");
|
||||
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Batalkan Mutasi?",
|
||||
text: "Apakah Anda yakin ingin membatalkan mutasi ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#d33",
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonText: "Ya, Batalkan",
|
||||
cancelButtonText: "Batal",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
cancelMutation(mutationId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (confirm("Apakah Anda yakin ingin membatalkan mutasi ini?")) {
|
||||
cancelMutation(mutationId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function cancelMutation(mutationId) {
|
||||
$.ajax({
|
||||
url: "/warehouse/mutations/" + mutationId + "/cancel",
|
||||
type: "POST",
|
||||
data: {
|
||||
_token: $('meta[name="csrf-token"]').attr("content"),
|
||||
},
|
||||
success: function (response) {
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Berhasil!",
|
||||
text: "Mutasi berhasil dibatalkan",
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} else {
|
||||
alert("Mutasi berhasil dibatalkan");
|
||||
}
|
||||
table.ajax.reload();
|
||||
},
|
||||
error: function (xhr) {
|
||||
var errorMsg =
|
||||
xhr.responseJSON?.message || "Gagal membatalkan mutasi";
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: errorMsg,
|
||||
icon: "error",
|
||||
});
|
||||
} else {
|
||||
alert("Error: " + errorMsg);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle form submissions with loading state
|
||||
$(document).on("submit", ".approve-form", function () {
|
||||
$(this)
|
||||
.find('button[type="submit"]')
|
||||
.prop("disabled", true)
|
||||
.html("Memproses...");
|
||||
});
|
||||
|
||||
// Auto-calculate approved quantity based on available stock
|
||||
$(document).on("input", 'input[name*="quantity_approved"]', function () {
|
||||
var maxValue = parseFloat($(this).attr("max"));
|
||||
var currentValue = parseFloat($(this).val());
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
$(this).val(maxValue);
|
||||
$(this).addClass("is-invalid");
|
||||
if (!$(this).siblings(".invalid-feedback").length) {
|
||||
$(this).after(
|
||||
'<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass("is-invalid");
|
||||
$(this).siblings(".invalid-feedback").remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -215,9 +215,9 @@
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
@can('view', $menus['opnames.index'])
|
||||
@can('view', $menus['mutations.index'])
|
||||
<li class="kt-menu__item" aria-haspopup="true">
|
||||
<a href="{{ route('opnames.index') }}" class="kt-menu__link">
|
||||
<a href="{{ route('mutations.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">Mutasi Produk</span>
|
||||
</a>
|
||||
|
||||
214
resources/views/warehouse_management/mutations/_action.blade.php
Normal file
214
resources/views/warehouse_management/mutations/_action.blade.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<!-- View Button -->
|
||||
<a href="{{ route('mutations.show', $row->id) }}"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md"
|
||||
title="Lihat Detail">
|
||||
<i class="la la-eye"></i>
|
||||
</a>
|
||||
|
||||
@if($row->status->value === 'sent')
|
||||
<!-- Receive Button (untuk dealer tujuan) -->
|
||||
@if(auth()->user()->dealer_id == $row->to_dealer_id)
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-receive"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Terima Mutasi">
|
||||
<i class="la la-download text-primary"></i>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Cancel Button (untuk pengirim) -->
|
||||
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Batalkan Mutasi">
|
||||
<i class="la la-ban text-warning"></i>
|
||||
</button>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if($row->status->value === 'received')
|
||||
<!-- Approve Button (untuk pengirim atau admin) -->
|
||||
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-approve"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Setujui Mutasi">
|
||||
<i class="la la-check text-success"></i>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Reject Button (untuk pengirim atau admin) -->
|
||||
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-reject"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Tolak Mutasi">
|
||||
<i class="la la-times text-danger"></i>
|
||||
</button>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if($row->status->value === 'approved')
|
||||
<!-- Complete/Receive Button -->
|
||||
@can('complete-mutation')
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-complete"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Terima & Selesaikan Mutasi">
|
||||
<i class="la la-check-circle text-primary"></i>
|
||||
</button>
|
||||
@endcan
|
||||
|
||||
<!-- Cancel Button -->
|
||||
@can('edit-mutation')
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
|
||||
data-id="{{ $row->id }}"
|
||||
title="Batalkan Mutasi">
|
||||
<i class="la la-ban text-warning"></i>
|
||||
</button>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
@if(in_array($row->status->value, ['pending', 'approved']) && auth()->user()->id === $row->requested_by)
|
||||
<!-- Edit Button (only for creator and if still pending/approved) -->
|
||||
<a href="{{ route('mutations.edit', $row->id) }}"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md"
|
||||
title="Edit Mutasi">
|
||||
<i class="la la-edit text-info"></i>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($row->status->value === 'completed')
|
||||
<!-- Print Button -->
|
||||
<a href="{{ route('mutations.print', $row->id) }}"
|
||||
class="btn btn-sm btn-clean btn-icon btn-icon-md"
|
||||
target="_blank"
|
||||
title="Cetak Laporan">
|
||||
<i class="la la-print text-info"></i>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Modal untuk Approve -->
|
||||
<div class="modal fade" id="approveModal{{ $row->id }}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Setujui Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.approve', $row->id) }}" method="POST" class="approve-form">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Catatan Persetujuan</label>
|
||||
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Detail produk akan dimuat via AJAX -->
|
||||
<div id="mutation-details{{ $row->id }}">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<p>Memuat detail produk...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-success">Setujui Mutasi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal untuk Reject -->
|
||||
<div class="modal fade" id="rejectModal{{ $row->id }}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Tolak Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.reject', $row->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Peringatan!</strong> Mutasi yang ditolak tidak dapat diubah lagi.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Alasan Penolakan <span class="text-danger">*</span></label>
|
||||
<textarea name="rejection_reason" class="form-control" rows="3" required placeholder="Masukkan alasan penolakan..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-danger">Tolak Mutasi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal untuk Receive -->
|
||||
<div class="modal fade" id="receiveModal{{ $row->id }}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Terima Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.receive', $row->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $row->fromDealer->name }}</strong>.
|
||||
</div>
|
||||
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Ya, Terima</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal untuk Complete -->
|
||||
<div class="modal fade" id="completeModal{{ $row->id }}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Selesaikan Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.complete', $row->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<strong>Konfirmasi!</strong> Stock akan dipindahkan dari <strong>{{ $row->fromDealer->name }}</strong> ke <strong>{{ $row->toDealer->name }}</strong>.
|
||||
</div>
|
||||
<p>Apakah Anda yakin ingin menyelesaikan mutasi ini? Tindakan ini tidak dapat dibatalkan.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Ya, Selesaikan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
159
resources/views/warehouse_management/mutations/create.blade.php
Normal file
159
resources/views/warehouse_management/mutations/create.blade.php
Normal file
@@ -0,0 +1,159 @@
|
||||
@extends('layouts.backapp')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="kt-portlet">
|
||||
<div class="kt-portlet__head">
|
||||
<div class="kt-portlet__head-label">
|
||||
<h3 class="kt-portlet__head-title">
|
||||
Tambah Mutasi Baru
|
||||
</h3>
|
||||
</div>
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('mutations.index') }}" class="btn btn-clean btn-sm">
|
||||
<i class="la la-arrow-left"></i>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('mutations.store') }}" method="POST" id="mutation-form">
|
||||
@csrf
|
||||
<div class="kt-portlet__body">
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="from_dealer_id">Dealer Asal <span class="text-danger">*</span></label>
|
||||
<select name="from_dealer_id" id="from_dealer_id" class="form-control select2" required>
|
||||
<option value="">Pilih Dealer Asal</option>
|
||||
@foreach($dealers as $dealer)
|
||||
<option value="{{ $dealer->id }}" {{ old('from_dealer_id') == $dealer->id ? 'selected' : '' }}>
|
||||
{{ $dealer->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="to_dealer_id">Dealer Tujuan <span class="text-danger">*</span></label>
|
||||
<select name="to_dealer_id" id="to_dealer_id" class="form-control select2" required>
|
||||
<option value="">Pilih Dealer Tujuan</option>
|
||||
@foreach($dealers as $dealer)
|
||||
<option value="{{ $dealer->id }}" {{ old('to_dealer_id') == $dealer->id ? 'selected' : '' }}>
|
||||
{{ $dealer->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Catatan</label>
|
||||
<textarea name="notes" id="notes" class="form-control" rows="3" placeholder="Catatan untuk mutasi ini (opsional)">{{ old('notes') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="kt-separator kt-separator--border-dashed kt-separator--space-lg"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<label class="form-label mb-0">Detail Produk <span class="text-danger">*</span></label>
|
||||
<button type="button" class="btn btn-success btn-sm" id="add-product">
|
||||
<i class="la la-plus"></i> Tambah Produk
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" id="products-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%">Produk</th>
|
||||
<th width="15%">Stock Tersedia</th>
|
||||
<th width="15%">Quantity</th>
|
||||
<th width="25%">Catatan</th>
|
||||
<th width="10%">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="products-tbody">
|
||||
<tr class="product-row" data-index="0">
|
||||
<td>
|
||||
<select name="products[0][product_id]" class="form-control select2 product-select" required>
|
||||
<option value="">Pilih Produk</option>
|
||||
@foreach($products as $product)
|
||||
<option value="{{ $product->id }}">{{ $product->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<span class="available-stock text-muted">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
name="products[0][quantity_requested]"
|
||||
class="form-control quantity-input"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
placeholder="0"
|
||||
required>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
name="products[0][notes]"
|
||||
class="form-control"
|
||||
placeholder="Catatan produk (opsional)">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-product" disabled>
|
||||
<i class="la la-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="la la-info-circle"></i>
|
||||
<strong>Informasi:</strong>
|
||||
Mutasi akan dibuat dengan status "Menunggu Persetujuan" dan memerlukan approval sebelum stock dipindahkan.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kt-portlet__foot">
|
||||
<div class="kt-form__actions kt-form__actions--right">
|
||||
<button type="button" class="btn btn-secondary" onclick="window.history.back()">
|
||||
Batal
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" id="submit-btn">
|
||||
<i class="la la-save"></i>
|
||||
Simpan Mutasi
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script src="{{ mix('js/warehouse_management/mutations/create.js') }}"></script>
|
||||
@endsection
|
||||
@@ -0,0 +1,52 @@
|
||||
@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-list-1"></i>
|
||||
</span>
|
||||
<h3 class="kt-portlet__head-title">
|
||||
Tabel Mutasi
|
||||
</h3>
|
||||
</div>
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('mutations.create') }}" class="btn btn-bold btn-label-brand btn--sm">
|
||||
<i class="la la-plus"></i>
|
||||
Tambah Mutasi
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kt-portlet__body">
|
||||
<div class="table-responsive">
|
||||
<!--begin: Datatable -->
|
||||
<table class="table table-striped table-bordered table-hover" id="mutations-table" data-url="{{ route('mutations.index') }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No.</th>
|
||||
<th>No. Mutasi</th>
|
||||
<th>Tanggal</th>
|
||||
<th>Dari Dealer</th>
|
||||
<th>Ke Dealer</th>
|
||||
<th>Dibuat Oleh</th>
|
||||
<th>Total Item</th>
|
||||
<th>Status</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<!--end: Datatable -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script src="{{ mix('js/warehouse_management/mutations/index.js') }}"></script>
|
||||
@endsection
|
||||
457
resources/views/warehouse_management/mutations/show.blade.php
Normal file
457
resources/views/warehouse_management/mutations/show.blade.php
Normal file
@@ -0,0 +1,457 @@
|
||||
@extends('layouts.backapp')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<!-- Mutation Header -->
|
||||
<div class="kt-portlet">
|
||||
<div class="kt-portlet__head">
|
||||
<div class="kt-portlet__head-label">
|
||||
<h3 class="kt-portlet__head-title">
|
||||
Detail Mutasi - {{ $mutation->mutation_number }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('mutations.index') }}" class="btn btn-secondary btn-sm">
|
||||
<i class="la la-arrow-left"></i>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kt-portlet__body">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label><strong>No. Mutasi:</strong></label>
|
||||
<p>{{ $mutation->mutation_number }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Dari Dealer:</strong></label>
|
||||
<p>{{ $mutation->fromDealer->name }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Ke Dealer:</strong></label>
|
||||
<p>{{ $mutation->toDealer->name }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Status:</strong></label>
|
||||
<p>
|
||||
<span class="kt-badge kt-badge--{{ $mutation->status_color }} kt-badge--dot"></span>
|
||||
<span class="kt-font-bold kt-font-{{ $mutation->status_color }}">{{ $mutation->status_label }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label><strong>Dibuat Oleh:</strong></label>
|
||||
<p>{{ $mutation->requestedBy->name }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Tanggal Dibuat:</strong></label>
|
||||
<p>{{ $mutation->created_at->format('d/m/Y H:i:s') }}</p>
|
||||
</div>
|
||||
@if($mutation->receivedBy)
|
||||
<div class="form-group">
|
||||
<label><strong>Diterima Oleh:</strong></label>
|
||||
<p>{{ $mutation->receivedBy->name }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Tanggal Diterima:</strong></label>
|
||||
<p>{{ $mutation->received_at->format('d/m/Y H:i:s') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@if($mutation->approvedBy)
|
||||
<div class="form-group">
|
||||
<label><strong>Disetujui Oleh:</strong></label>
|
||||
<p>{{ $mutation->approvedBy->name }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><strong>Tanggal Disetujui:</strong></label>
|
||||
<p>{{ $mutation->approved_at->format('d/m/Y H:i:s') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if($mutation->notes)
|
||||
<div class="form-group">
|
||||
<label><strong>Catatan:</strong></label>
|
||||
<p>{{ $mutation->notes }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@if($mutation->rejection_reason)
|
||||
<div class="form-group">
|
||||
<label><strong>Alasan Penolakan:</strong></label>
|
||||
<div class="alert alert-danger">{{ $mutation->rejection_reason }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mutation Details -->
|
||||
<div class="kt-portlet">
|
||||
<div class="kt-portlet__head">
|
||||
<div class="kt-portlet__head-label">
|
||||
<h3 class="kt-portlet__head-title">
|
||||
Detail Produk
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kt-portlet__body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No.</th>
|
||||
<th>Nama Produk</th>
|
||||
<th>Jumlah Diminta</th>
|
||||
<th>Jumlah Disetujui</th>
|
||||
<th>Status Approval</th>
|
||||
<th>Catatan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($mutation->mutationDetails as $index => $detail)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ $detail->product->name }}</td>
|
||||
<td>{{ number_format($detail->quantity_requested, 2) }}</td>
|
||||
<td>
|
||||
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
|
||||
{{ number_format($detail->quantity_approved ?? 0, 2) }}
|
||||
@else
|
||||
<span class="text-muted">Belum ditentukan</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
|
||||
<span class="kt-badge kt-badge--{{ $detail->approval_status_color }} kt-badge--pill">
|
||||
{{ $detail->approval_status }}
|
||||
</span>
|
||||
@else
|
||||
<span class="kt-badge kt-badge--secondary kt-badge--pill">Menunggu</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $detail->notes ?? '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="kt-font-bold">
|
||||
<td colspan="2">Total</td>
|
||||
<td>{{ number_format($mutation->mutationDetails->sum('quantity_requested'), 2) }}</td>
|
||||
<td>
|
||||
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
|
||||
{{ number_format($mutation->mutationDetails->sum('quantity_approved'), 2) }}
|
||||
@else
|
||||
<span class="text-muted">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="kt-portlet">
|
||||
<div class="kt-portlet__body">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
|
||||
<!-- Receive Button for destination dealer -->
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#receiveModal">
|
||||
<i class="la la-download"></i>
|
||||
Terima Mutasi
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($mutation->status->value === 'received' && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
|
||||
<!-- Approve Button for sender or admin -->
|
||||
<button type="button" class="btn btn-success btn-approve" data-id="{{ $mutation->id }}">
|
||||
<i class="la la-check"></i>
|
||||
Setujui Mutasi
|
||||
</button>
|
||||
<!-- Reject Button for sender or admin -->
|
||||
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#rejectModal">
|
||||
<i class="la la-times"></i>
|
||||
Tolak Mutasi
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($mutation->status->value === 'approved')
|
||||
<!-- Complete Button -->
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#completeModal">
|
||||
<i class="la la-check-circle"></i>
|
||||
Selesaikan Mutasi
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($mutation->canBeCancelled() && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
|
||||
<!-- Cancel Button -->
|
||||
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#cancelModal">
|
||||
<i class="la la-ban"></i>
|
||||
Batalkan Mutasi
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($mutation->status->value === 'completed')
|
||||
<!-- Print Button -->
|
||||
<a href="{{ route('mutations.print', $mutation->id) }}" class="btn btn-info" target="_blank">
|
||||
<i class="la la-print"></i>
|
||||
Cetak Laporan
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
|
||||
<!-- Receive Modal -->
|
||||
<div class="modal fade" id="receiveModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Terima Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.receive', $mutation->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $mutation->fromDealer->name }}</strong>.
|
||||
</div>
|
||||
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Ya, Terima</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($mutation->status->value === 'received' && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
|
||||
<!-- Approve Modal -->
|
||||
<div class="modal fade" id="approveModal{{ $mutation->id }}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Setujui Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.approve', $mutation->id) }}" method="POST" class="approve-form">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Catatan Persetujuan</label>
|
||||
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Detail produk akan dimuat via AJAX -->
|
||||
<div id="mutation-details{{ $mutation->id }}">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<p>Memuat detail produk...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-success">Setujui Mutasi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reject Modal -->
|
||||
<div class="modal fade" id="rejectModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Tolak Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.reject', $mutation->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Peringatan!</strong> Mutasi yang ditolak tidak dapat diubah lagi.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Alasan Penolakan <span class="text-danger">*</span></label>
|
||||
<textarea name="rejection_reason" class="form-control" rows="3" required placeholder="Masukkan alasan penolakan..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-danger">Tolak Mutasi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($mutation->status->value === 'approved')
|
||||
<!-- Complete Modal -->
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Selesaikan Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.complete', $mutation->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<strong>Konfirmasi!</strong> Stock akan dipindahkan dari <strong>{{ $mutation->fromDealer->name }}</strong> ke <strong>{{ $mutation->toDealer->name }}</strong>.
|
||||
</div>
|
||||
<p>Apakah Anda yakin ingin menyelesaikan mutasi ini? Tindakan ini tidak dapat dibatalkan.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Ya, Selesaikan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($mutation->canBeCancelled() && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
|
||||
<!-- Cancel Modal -->
|
||||
<div class="modal fade" id="cancelModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Batalkan Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route('mutations.cancel', $mutation->id) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Peringatan!</strong> Mutasi yang dibatalkan tidak dapat diubah lagi.
|
||||
</div>
|
||||
<p>Apakah Anda yakin ingin membatalkan mutasi ini?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-warning">Ya, Batalkan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Handle Approve Button Click
|
||||
$(document).on('click', '.btn-approve', function() {
|
||||
var mutationId = $(this).data('id');
|
||||
|
||||
// Load mutation details via AJAX
|
||||
$.ajax({
|
||||
url: '/warehouse/mutations/' + mutationId + '/details',
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
$('#mutation-details' + mutationId).html(
|
||||
'<div class="text-center">' +
|
||||
'<div class="spinner-border" role="status">' +
|
||||
'<span class="sr-only">Loading...</span>' +
|
||||
'</div>' +
|
||||
'<p>Memuat detail produk...</p>' +
|
||||
'</div>'
|
||||
);
|
||||
},
|
||||
success: function(response) {
|
||||
var detailsHtml = '<h6>Detail Produk:</h6>';
|
||||
detailsHtml += '<div class="table-responsive">';
|
||||
detailsHtml += '<table class="table table-sm">';
|
||||
detailsHtml += '<thead>';
|
||||
detailsHtml += '<tr>';
|
||||
detailsHtml += '<th>Produk</th>';
|
||||
detailsHtml += '<th>Diminta</th>';
|
||||
detailsHtml += '<th>Disetujui</th>';
|
||||
detailsHtml += '<th>Stock Tersedia</th>';
|
||||
detailsHtml += '</tr>';
|
||||
detailsHtml += '</thead>';
|
||||
detailsHtml += '<tbody>';
|
||||
|
||||
response.details.forEach(function(detail, index) {
|
||||
detailsHtml += '<tr>';
|
||||
detailsHtml += '<td>' + detail.product.name + '</td>';
|
||||
detailsHtml += '<td>' + parseFloat(detail.quantity_requested).toLocaleString() + '</td>';
|
||||
detailsHtml += '<td>';
|
||||
detailsHtml += '<input type="number" name="details[' + detail.id + '][quantity_approved]" ';
|
||||
detailsHtml += 'class="form-control form-control-sm" ';
|
||||
detailsHtml += 'value="' + detail.quantity_requested + '" ';
|
||||
detailsHtml += 'min="0" max="' + Math.min(detail.quantity_requested, detail.available_stock) + '" ';
|
||||
detailsHtml += 'step="0.01" required>';
|
||||
detailsHtml += '</td>';
|
||||
detailsHtml += '<td>' + parseFloat(detail.available_stock).toLocaleString() + '</td>';
|
||||
detailsHtml += '</tr>';
|
||||
});
|
||||
|
||||
detailsHtml += '</tbody>';
|
||||
detailsHtml += '</table>';
|
||||
detailsHtml += '</div>';
|
||||
|
||||
$('#mutation-details' + mutationId).html(detailsHtml);
|
||||
},
|
||||
error: function() {
|
||||
$('#mutation-details' + mutationId).html('<div class="alert alert-danger">Gagal memuat detail produk</div>');
|
||||
}
|
||||
});
|
||||
|
||||
$('#approveModal' + mutationId).modal('show');
|
||||
});
|
||||
|
||||
// Auto-calculate approved quantity based on available stock
|
||||
$(document).on('input', 'input[name*="quantity_approved"]', function() {
|
||||
var maxValue = parseFloat($(this).attr('max'));
|
||||
var currentValue = parseFloat($(this).val());
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
$(this).val(maxValue);
|
||||
$(this).addClass('is-invalid');
|
||||
if (!$(this).siblings('.invalid-feedback').length) {
|
||||
$(this).after('<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>');
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass('is-invalid');
|
||||
$(this).siblings('.invalid-feedback').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -1,19 +0,0 @@
|
||||
<div class="d-flex">
|
||||
<button type="button" class="btn btn-success btn-sm mr-2"
|
||||
onclick="showAdjustStockModal({{ $stock->id }}, 'add')"
|
||||
title="Tambah Stok">
|
||||
<i class="la la-plus"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-warning btn-sm mr-2"
|
||||
onclick="showAdjustStockModal({{ $stock->id }}, 'reduce')"
|
||||
title="Kurangi Stok">
|
||||
<i class="la la-minus"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-info btn-sm"
|
||||
onclick="showStockHistory({{ $stock->id }})"
|
||||
title="Lihat Riwayat">
|
||||
<i class="la la-history"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,234 +0,0 @@
|
||||
@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-box"></i>
|
||||
</span>
|
||||
<h3 class="kt-portlet__head-title">Manajemen Stok</h3>
|
||||
</div>
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('opnames.create') }}" class="btn btn-primary">
|
||||
<i class="la la-plus"></i> Buat Opname
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kt-portlet__body">
|
||||
<!-- Filter -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="dealer_filter">Filter Dealer</label>
|
||||
<select class="form-control" id="dealer_filter">
|
||||
<option value="">Semua Dealer</option>
|
||||
@foreach($dealers as $dealer)
|
||||
<option value="{{ $dealer->id }}">{{ $dealer->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="product_filter">Filter Produk</label>
|
||||
<select class="form-control" id="product_filter">
|
||||
<option value="">Semua Produk</option>
|
||||
@foreach($products as $product)
|
||||
<option value="{{ $product->id }}">{{ $product->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabel Stok -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover" id="stocks-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Dealer</th>
|
||||
<th>Produk</th>
|
||||
<th>Stok</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Adjust Stock -->
|
||||
<div class="modal fade" id="adjustStockModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Adjust Stok</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="adjustStockForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="stock_id" name="stock_id">
|
||||
<input type="hidden" id="adjust_type" name="type">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Jumlah</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" name="quantity"
|
||||
step="0.01" min="0.01" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">pcs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Catatan</label>
|
||||
<textarea class="form-control" name="note" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Stock History -->
|
||||
<div class="modal fade" id="stockHistoryModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Riwayat Stok</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" id="history-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Tanggal</th>
|
||||
<th>User</th>
|
||||
<th>Perubahan</th>
|
||||
<th>Stok Lama</th>
|
||||
<th>Stok Baru</th>
|
||||
<th>Catatan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Inisialisasi DataTable
|
||||
var table = $('#stocks-table').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: '{{ route("stocks.index") }}',
|
||||
data: function(d) {
|
||||
d.dealer_id = $('#dealer_filter').val();
|
||||
d.product_id = $('#product_filter').val();
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{data: 'dealer_name', name: 'dealer_name'},
|
||||
{data: 'product_name', name: 'product_name'},
|
||||
{data: 'quantity', name: 'quantity'},
|
||||
{data: 'action', name: 'action', orderable: false, searchable: false}
|
||||
]
|
||||
});
|
||||
|
||||
// Filter change handler
|
||||
$('#dealer_filter, #product_filter').change(function() {
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
// Show adjust stock modal
|
||||
window.showAdjustStockModal = function(stockId, type) {
|
||||
$('#stock_id').val(stockId);
|
||||
$('#adjust_type').val(type);
|
||||
$('#adjustStockModal').modal('show');
|
||||
};
|
||||
|
||||
// Handle adjust stock form submit
|
||||
$('#adjustStockForm').submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("stocks.adjust") }}',
|
||||
method: 'POST',
|
||||
data: {
|
||||
_token: '{{ csrf_token() }}',
|
||||
stock_id: $('#stock_id').val(),
|
||||
type: $('#adjust_type').val(),
|
||||
quantity: $('input[name="quantity"]').val(),
|
||||
note: $('textarea[name="note"]').val()
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$('#adjustStockModal').modal('hide');
|
||||
table.ajax.reload();
|
||||
toastr.success(response.message);
|
||||
} else {
|
||||
toastr.error(response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
toastr.error(xhr.responseJSON?.message || 'Terjadi kesalahan');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Show stock history
|
||||
window.showStockHistory = function(stockId) {
|
||||
$.get('{{ route("stocks.history") }}', {
|
||||
stock_id: stockId
|
||||
})
|
||||
.done(function(response) {
|
||||
var tbody = $('#history-table tbody');
|
||||
tbody.empty();
|
||||
|
||||
response.logs.forEach(function(log) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${log.date}</td>
|
||||
<td>${log.user}</td>
|
||||
<td>${log.change}</td>
|
||||
<td>${log.old_quantity}</td>
|
||||
<td>${log.new_quantity}</td>
|
||||
<td>${log.note}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
$('#stockHistoryModal').modal('show');
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
toastr.error('Gagal memuat riwayat stok');
|
||||
});
|
||||
};
|
||||
|
||||
// Reset form when modal is closed
|
||||
$('#adjustStockModal').on('hidden.bs.modal', function() {
|
||||
$('#adjustStockForm')[0].reset();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user