442 lines
17 KiB
PHP
Executable File
442 lines
17 KiB
PHP
Executable File
@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-secondary">
|
|
<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="row">
|
|
<div class="col-md-12">
|
|
<div class="form-group">
|
|
<label for="shipping_notes">Catatan Pengiriman</label>
|
|
<textarea name="shipping_notes" id="shipping_notes" class="form-control" rows="3"
|
|
placeholder="Catatan khusus untuk pengiriman mutasi ini (opsional)">{{ old('shipping_notes') }}</textarea>
|
|
<small class="form-text text-muted">Catatan ini akan dilihat oleh dealer penerima</small>
|
|
</div>
|
|
</div>
|
|
</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="40%">Produk</th>
|
|
<th width="20%">Stock Tersedia</th>
|
|
<th width="25%">Quantity</th>
|
|
<th width="15%">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 product-select select2" required>
|
|
<option value="">Pilih Produk</option>
|
|
@foreach($products as $product)
|
|
<option value="{{ $product->id }}">{{ $product->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</td>
|
|
<td class="text-center">
|
|
<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>
|
|
<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 dikirim ke dealer tujuan. Catatan dapat ditambahkan saat dealer tujuan menerima mutasi.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kt-portlet__foot">
|
|
<div class="kt-form__actions kt-form__actions--right">
|
|
<button type="submit" class="btn btn-primary" id="submit-btn">
|
|
Simpan Mutasi
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="window.history.back()">
|
|
Batal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('javascripts')
|
|
<script>
|
|
$(document).ready(function () {
|
|
let productIndex = 1;
|
|
let originalProductOptions = ""; // Store original product options
|
|
|
|
// Initialize Select2
|
|
$(".select2").select2({
|
|
placeholder: "Pilih...",
|
|
allowClear: true,
|
|
});
|
|
|
|
// Store original product options on page load
|
|
const firstSelect = $(".product-select").first();
|
|
if (firstSelect.length > 0) {
|
|
originalProductOptions = firstSelect.html();
|
|
}
|
|
|
|
// 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");
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "Dealer asal dan tujuan tidak boleh sama",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// 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 after it's added to DOM
|
|
const newSelect = $(
|
|
`#products-tbody tr[data-index="${productIndex}"] .product-select`
|
|
);
|
|
newSelect.select2({
|
|
placeholder: "Pilih...",
|
|
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) {
|
|
return `
|
|
<tr class="product-row" data-index="${index}">
|
|
<td>
|
|
<select name="products[${index}][product_id]" class="form-control product-select" required>
|
|
${originalProductOptions}
|
|
</select>
|
|
</td>
|
|
<td class="text-center">
|
|
<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>
|
|
<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]`);
|
|
});
|
|
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) {
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "Pilih dealer asal",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
if (!toDealerId) {
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "Pilih dealer tujuan",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
if (fromDealerId === toDealerId) {
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "Dealer asal dan tujuan tidak boleh sama",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// Check products
|
|
const productRows = $(".product-row");
|
|
if (productRows.length === 0) {
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "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) {
|
|
Swal.fire({
|
|
type: "error",
|
|
title: "Oops...",
|
|
text: "Pilih minimal satu produk dengan quantity yang valid",
|
|
});
|
|
return false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
});
|
|
|
|
</script>
|
|
@endsection
|