632 lines
23 KiB
PHP
632 lines
23 KiB
PHP
@extends('layouts.backapp')
|
|
|
|
@section('styles')
|
|
<style>
|
|
.filter-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
.filter-buttons .btn {
|
|
white-space: nowrap;
|
|
}
|
|
.datepicker {
|
|
width: 100% !important;
|
|
max-width: 100%;
|
|
}
|
|
.datepicker-dropdown {
|
|
width: auto !important;
|
|
min-width: 250px;
|
|
max-width: 300px;
|
|
}
|
|
input.datepicker {
|
|
width: 100% !important;
|
|
box-sizing: border-box;
|
|
}
|
|
</style>
|
|
@endsection
|
|
|
|
@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">
|
|
<h3 class="kt-portlet__head-title">
|
|
Histori Stock
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kt-portlet__body">
|
|
<!-- Filter Section -->
|
|
<div class="ke-form row">
|
|
<div class="form-group col-md-3">
|
|
<label class="form-label">Filter Dealer</label>
|
|
<select class="form-control select2" id="filter-dealer">
|
|
<option value="">Semua Dealer</option>
|
|
@foreach($dealers as $dealer)
|
|
<option value="{{ $dealer->name }}">{{ $dealer->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label class="form-label">Filter Produk</label>
|
|
<select class="form-control select2" id="filter-product">
|
|
<option value="">Semua Produk</option>
|
|
@foreach($products as $product)
|
|
<option value="{{ $product->name }}">{{ $product->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label class="form-label">Jenis Perubahan</label>
|
|
<select class="form-control select2" id="filter-change-type">
|
|
<option value="">Semua Jenis</option>
|
|
<option value="increase">Penambahan</option>
|
|
<option value="decrease">Pengurangan</option>
|
|
<option value="no_change">Tidak Ada Perubahan</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label class="form-label">Filter Tanggal</label>
|
|
<input type="text" class="form-control datepicker" id="filter-date" placeholder="Pilih tanggal">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons Row -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-12">
|
|
<div class="filter-buttons">
|
|
<button type="button" class="btn btn-primary btn-sm" id="apply-filters">
|
|
Filter
|
|
</button>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="reset-filters">
|
|
Reset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="stock-audit-table" class="table table-striped table-bordered"
|
|
data-route="{{ route('stock-audit.index') }}">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>Produk</th>
|
|
<th>Dealer</th>
|
|
<th>Jenis Perubahan</th>
|
|
<th>Perubahan Qty</th>
|
|
<th>Stock (Sebelum → Sesudah)</th>
|
|
<th>Sumber</th>
|
|
<th>User</th>
|
|
<th>Tanggal</th>
|
|
<th>Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Detail Modal -->
|
|
<div class="modal fade" id="auditDetailModal" tabindex="-1" role="dialog" aria-labelledby="auditDetailModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="auditDetailModalLabel">Detail Audit Stock</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" id="modal-close-btn">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="auditDetailContent">
|
|
<!-- Loading State -->
|
|
<div id="modal-loading" class="text-center">
|
|
<div class="spinner-border" role="status">
|
|
<span class="sr-only">Memuat...</span>
|
|
</div>
|
|
<p class="mt-2">Memuat data audit...</p>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div id="modal-error" class="alert alert-danger" style="display: none;">
|
|
<strong>Error:</strong> <span id="error-message">Gagal memuat detail audit</span>
|
|
</div>
|
|
|
|
<!-- Content Area -->
|
|
<div id="modal-content" style="display: none;">
|
|
<!-- Stock Information -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="font-weight-bold">Informasi Stock</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Produk:</strong></td>
|
|
<td id="product-name">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Dealer:</strong></td>
|
|
<td id="dealer-name">-</td>
|
|
</tr>
|
|
<tr></tr>
|
|
<td><strong>Stock Sebelum:</strong></td>
|
|
<td id="previous-quantity">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Stock Sesudah:</strong></td>
|
|
<td id="new-quantity">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Perubahan:</strong></td>
|
|
<td id="quantity-change">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Jenis Perubahan:</strong></td>
|
|
<td id="change-type">-</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="font-weight-bold">Informasi Sistem</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>User:</strong></td>
|
|
<td id="user-name">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Tanggal:</strong></td>
|
|
<td id="created-at">-</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Deskripsi:</strong></td>
|
|
<td id="description">-</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Source Detail (will be shown/hidden based on data) -->
|
|
<div id="source-detail" style="display: none;">
|
|
<hr>
|
|
<h6 class="font-weight-bold">Detail Sumber Perubahan</h6>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0" id="source-title">-</h6>
|
|
</div>
|
|
<div class="card-body" id="source-content">
|
|
<!-- Content will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modal-close-footer-btn">Tutup</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
@section('javascripts')
|
|
<script>
|
|
// Helper function to format date
|
|
function formatDate(dateString) {
|
|
if (!dateString) return "-";
|
|
|
|
const date = new Date(dateString);
|
|
const months = [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"Mei",
|
|
"Jun",
|
|
"Jul",
|
|
"Agu",
|
|
"Sep",
|
|
"Okt",
|
|
"Nov",
|
|
"Des",
|
|
];
|
|
|
|
const day = date.getDate().toString().padStart(2, "0");
|
|
const month = months[date.getMonth()];
|
|
const year = date.getFullYear();
|
|
const hours = date.getHours().toString().padStart(2, "0");
|
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
|
|
return `${day} ${month} ${year}, ${hours}:${minutes}`;
|
|
}
|
|
|
|
$(document).ready(function () {
|
|
console.log("Initializing stock audit table...");
|
|
|
|
// Initialize Select2 without any event handlers
|
|
$(".select2").select2({
|
|
placeholder: "Pilih...",
|
|
allowClear: true,
|
|
width: "100%",
|
|
});
|
|
|
|
// Initialize Datepicker
|
|
$(".datepicker").datepicker({
|
|
format: "yyyy-mm-dd",
|
|
autoclose: true,
|
|
todayHighlight: true,
|
|
orientation: "bottom auto",
|
|
language: "id",
|
|
clearBtn: true,
|
|
container: "body",
|
|
});
|
|
|
|
const $table = $("#stock-audit-table");
|
|
const indexRoute = $table.data("route");
|
|
|
|
console.log("Table route:", indexRoute);
|
|
|
|
let table = $table.DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
language: {
|
|
processing:
|
|
'<div class="d-flex justify-content-center"><div class="spinner-border text-primary" role="status"><span class="sr-only">Memproses...</span></div></div>',
|
|
loadingRecords: "Memuat data...",
|
|
zeroRecords: "Tidak ada data yang ditemukan",
|
|
emptyTable: "Tidak ada data tersedia",
|
|
},
|
|
ajax: {
|
|
url: indexRoute,
|
|
data: function (d) {
|
|
d.dealer = $("#filter-dealer").val();
|
|
d.product = $("#filter-product").val();
|
|
d.change_type = $("#filter-change-type").val();
|
|
d.date = $("#filter-date").val();
|
|
console.log("Ajax data with ordering:", d);
|
|
console.log("Order info:", d.order);
|
|
console.log("Columns info:", d.columns);
|
|
},
|
|
error: function (xhr, error, thrown) {
|
|
console.error("Ajax error:", error);
|
|
console.error("Response:", xhr.responseText);
|
|
},
|
|
},
|
|
columns: [
|
|
{
|
|
data: "DT_RowIndex",
|
|
name: "DT_RowIndex",
|
|
orderable: false,
|
|
searchable: false,
|
|
width: "5%",
|
|
},
|
|
{
|
|
data: "product_name",
|
|
name: "product_name",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "dealer_name",
|
|
name: "dealer_name",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "change_type",
|
|
name: "change_type",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "quantity_change",
|
|
name: "quantity_change",
|
|
className: "text-center",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "stock_before_after",
|
|
name: "stock_before_after",
|
|
className: "text-center",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "source_info",
|
|
name: "source_info",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "user_name",
|
|
name: "user_name",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "created_at",
|
|
name: "created_at",
|
|
orderable: true,
|
|
},
|
|
{
|
|
data: "action",
|
|
name: "action",
|
|
orderable: false,
|
|
searchable: false,
|
|
width: "10%",
|
|
},
|
|
],
|
|
order: [[8, "desc"]], // Order by created_at desc (column index 8)
|
|
pageLength: 10,
|
|
responsive: true,
|
|
ordering: true, // Enable column ordering
|
|
orderMulti: false, // Single column ordering only
|
|
});
|
|
|
|
console.log("Table initialized:", table);
|
|
|
|
// Add loading indicator for ordering
|
|
table.on("processing.dt", function (e, settings, processing) {
|
|
if (processing) {
|
|
console.log("DataTable processing started (ordering/filtering)");
|
|
} else {
|
|
console.log("DataTable processing finished");
|
|
}
|
|
});
|
|
|
|
// Debug order events
|
|
table.on("order.dt", function () {
|
|
console.log("Order changed:", table.order());
|
|
});
|
|
|
|
// Manual modal close handlers
|
|
$(document).on(
|
|
"click",
|
|
"#modal-close-btn, #modal-close-footer-btn",
|
|
function () {
|
|
console.log("Manual close button clicked");
|
|
$("#auditDetailModal").modal("hide");
|
|
}
|
|
);
|
|
|
|
// Modal backdrop click handler
|
|
$(document).on("click", "#auditDetailModal", function (e) {
|
|
if (e.target === this) {
|
|
console.log("Modal backdrop clicked");
|
|
$("#auditDetailModal").modal("hide");
|
|
}
|
|
});
|
|
|
|
// ESC key handler
|
|
$(document).on("keydown", function (e) {
|
|
if (e.keyCode === 27 && $("#auditDetailModal").hasClass("show")) {
|
|
console.log("ESC key pressed");
|
|
$("#auditDetailModal").modal("hide");
|
|
}
|
|
});
|
|
|
|
// Modal hidden event handler
|
|
$("#auditDetailModal").on("hidden.bs.modal", function () {
|
|
console.log("Modal hidden");
|
|
// Reset modal content
|
|
$("#modal-loading").show();
|
|
$("#modal-error").hide();
|
|
$("#modal-content").hide();
|
|
});
|
|
|
|
// Apply filters button - only way to trigger table reload
|
|
$("#apply-filters").click(function () {
|
|
console.log("Apply filters clicked, reloading table...");
|
|
console.log("Current filter values:", {
|
|
dealer: $("#filter-dealer").val(),
|
|
product: $("#filter-product").val(),
|
|
change_type: $("#filter-change-type").val(),
|
|
date: $("#filter-date").val(),
|
|
});
|
|
table.ajax.reload();
|
|
});
|
|
|
|
// Allow Enter key to apply filters on datepicker
|
|
$("#filter-date").keypress(function (e) {
|
|
if (e.which == 13) {
|
|
// Enter key
|
|
console.log("Enter pressed on date filter, applying filters...");
|
|
table.ajax.reload();
|
|
}
|
|
});
|
|
|
|
// Reset filters
|
|
$("#reset-filters").click(function () {
|
|
console.log("Resetting filters...");
|
|
|
|
// Reset select2 elements properly
|
|
$("#filter-dealer").val(null).trigger("change.select2");
|
|
$("#filter-product").val(null).trigger("change.select2");
|
|
$("#filter-change-type").val(null).trigger("change.select2");
|
|
|
|
// Reset datepicker properly
|
|
$("#filter-date").val("").datepicker("update");
|
|
|
|
console.log("Filters reset, values after reset:", {
|
|
dealer: $("#filter-dealer").val(),
|
|
product: $("#filter-product").val(),
|
|
change_type: $("#filter-change-type").val(),
|
|
date: $("#filter-date").val(),
|
|
});
|
|
|
|
// Reload table after reset
|
|
console.log("Reloading table after reset...");
|
|
table.ajax.reload();
|
|
});
|
|
});
|
|
|
|
window.showAuditDetail = function (id) {
|
|
console.log("Showing audit detail for ID:", id);
|
|
|
|
// Reset modal states first
|
|
$("#modal-loading").show();
|
|
$("#modal-error").hide();
|
|
$("#modal-content").hide();
|
|
|
|
// Show modal
|
|
$("#auditDetailModal").modal("show");
|
|
|
|
$.ajax({
|
|
url: `/warehouse/stock-audit/${id}/detail`,
|
|
method: "GET",
|
|
success: function (response) {
|
|
console.log("Detail response:", response);
|
|
$("#modal-loading").hide();
|
|
|
|
if (response.success) {
|
|
populateModalContent(response.data, response.source_detail);
|
|
$("#modal-content").show();
|
|
} else {
|
|
$("#error-message").text(response.message);
|
|
$("#modal-error").show();
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
console.error("Detail AJAX error:", xhr);
|
|
$("#modal-loading").hide();
|
|
$("#error-message").text("Gagal memuat detail audit");
|
|
$("#modal-error").show();
|
|
},
|
|
});
|
|
};
|
|
|
|
function populateModalContent(audit, sourceDetail) {
|
|
console.log("Populating modal content:", audit);
|
|
|
|
// Populate basic stock information
|
|
$("#product-name").text(audit.stock.product.name);
|
|
$("#dealer-name").text(audit.stock.dealer.name);
|
|
$("#previous-quantity").text(audit.previous_quantity);
|
|
$("#new-quantity").text(audit.new_quantity);
|
|
$("#user-name").text(audit.user ? audit.user.name : "-");
|
|
$("#created-at").text(audit.created_at_formatted);
|
|
$("#description").text(audit.description || "-");
|
|
|
|
// Set quantity change with styling
|
|
let quantityChangeClass = "";
|
|
let quantityChangeSign = "";
|
|
if (audit.quantity_change > 0) {
|
|
quantityChangeClass = "text-success";
|
|
quantityChangeSign = "+";
|
|
} else if (audit.quantity_change < 0) {
|
|
quantityChangeClass = "text-danger";
|
|
quantityChangeSign = "";
|
|
} else {
|
|
quantityChangeClass = "text-muted";
|
|
quantityChangeSign = "";
|
|
}
|
|
$("#quantity-change").html(
|
|
`<span class="${quantityChangeClass}">${quantityChangeSign}${audit.quantity_change}</span>`
|
|
);
|
|
|
|
// Set change type with styling
|
|
let changeTypeClass = "";
|
|
switch (audit.change_type) {
|
|
case "increase":
|
|
changeTypeClass = "text-success";
|
|
break;
|
|
case "decrease":
|
|
changeTypeClass = "text-danger";
|
|
break;
|
|
case "adjustment":
|
|
changeTypeClass = "text-warning";
|
|
break;
|
|
default:
|
|
changeTypeClass = "text-muted";
|
|
}
|
|
$("#change-type").html(
|
|
`<span class="font-weight-bold ${changeTypeClass}">${audit.change_type_label}</span>`
|
|
);
|
|
|
|
// Handle source detail
|
|
if (sourceDetail) {
|
|
$("#source-detail").show();
|
|
|
|
if (sourceDetail.type === "mutation") {
|
|
let mutation = sourceDetail.data;
|
|
$("#source-title").text(
|
|
`Mutasi Stock: ${mutation.mutation_number}`
|
|
);
|
|
|
|
let mutationContent = `
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Dari Dealer:</strong></td>
|
|
<td>${
|
|
mutation.from_dealer
|
|
? mutation.from_dealer.name
|
|
: "-"
|
|
}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Ke Dealer:</strong></td>
|
|
<td>${
|
|
mutation.to_dealer
|
|
? mutation.to_dealer.name
|
|
: "-"
|
|
}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Status:</strong></td>
|
|
<td>${mutation.status}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Diminta oleh:</strong></td>
|
|
<td>${
|
|
mutation.requested_by
|
|
? mutation.requested_by.name
|
|
: "-"
|
|
}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Disetujui oleh:</strong></td>
|
|
<td>${
|
|
mutation.approved_by
|
|
? mutation.approved_by.name
|
|
: "-"
|
|
}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Tanggal Disetujui:</strong></td>
|
|
<td>${
|
|
mutation.approved_at_formatted || "-"
|
|
}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
$("#source-content").html(mutationContent);
|
|
} else if (sourceDetail.type === "opname") {
|
|
let opname = sourceDetail.data;
|
|
$("#source-title").text("Opname");
|
|
|
|
let opnameContent = `
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Dealer:</strong></td>
|
|
<td>${opname.dealer ? opname.dealer.name : "-"}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>User:</strong></td>
|
|
<td>${opname.user ? opname.user.name : "-"}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Status:</strong></td>
|
|
<td>${opname.status || "-"}</td>
|
|
</tr>
|
|
</table>
|
|
`;
|
|
$("#source-content").html(opnameContent);
|
|
}
|
|
} else {
|
|
$("#source-detail").hide();
|
|
}
|
|
}
|
|
</script>
|
|
@endsection
|
|
|
|
|