1164 lines
40 KiB
PHP
1164 lines
40 KiB
PHP
<div class="container mt-4 mb-4">
|
|
<div class="card modern-card shadow-sm border-0 rounded-4">
|
|
<div class="card-body p-4">
|
|
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<h5>Detail Invoice</h5>
|
|
<div>
|
|
<a href="<?= base_url('invoices/')?>" class="btn btn-secondary" id="btn-back">Kembali</a>
|
|
<button class="btn btn-danger btn-pdf" id="generate_pdf">Generate PDF</button>
|
|
<button class="btn btn-warning btn-cicilan" id="get_cicilan">Ambil Cicilan</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="customerInfo"></div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table modern-table align-middle w-100" id="tableDetail">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>To Account</th>
|
|
<th>No</th>
|
|
<th>Tanggal</th>
|
|
<th>Item</th>
|
|
<th>Qty</th>
|
|
<th>Harga</th>
|
|
<th>Subtotal</th>
|
|
<th>Keterangan</th>
|
|
<th>Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between mt-3">
|
|
<div id="btn-add-items"></div>
|
|
<div class="mb-2 mr-4">
|
|
<table class="table table-borderless table-sm mb-0 w-auto" style="zoom: 110%;">
|
|
<!-- <tr>
|
|
<td class="pe-3 text-muted">Total Tagihan</td>
|
|
<td class="pe-2">:</td>
|
|
<td><strong id="grandTotal">0</strong></td>
|
|
</tr> -->
|
|
<tbody id="grandTotal"></tbody>
|
|
<tbody id="othergrandTotal"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="riwayatPembayaran"></div>
|
|
</div>
|
|
|
|
<!--MODAL GET CICILAN-->
|
|
<div class="modal fade" id="modalCicilan" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content">
|
|
|
|
<!-- HEADER -->
|
|
<div class="modal-header bg-warning text-white">
|
|
<h5 class="modal-title">List Cicilan</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
|
|
<!-- BODY -->
|
|
<div class="modal-body">
|
|
<div id="cicilanContainer">
|
|
<div class="text-center text-muted">
|
|
Memuat data...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FOOTER -->
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">
|
|
Tutup
|
|
</button>
|
|
<button class="btn btn-primary" id="save_cicilan">
|
|
Pilih Cicilan
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MODAL (TIDAK DIUBAH STYLE) -->
|
|
<div class="modal fade" id="modalItem">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-warning text-white">
|
|
<h5 class="modal-title" id="modalTitle">Item Invoice</h5>
|
|
<button class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<input type="hidden" id="item_id">
|
|
<div id="item_mode_label" class="alert alert-secondary py-1 small mb-3">
|
|
Mode: Manual Input
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<label for="account_id" class="form-label">Tujuan Neraca (Account)</label>
|
|
<select id="account_id" class="form-control">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-2" id="wrap_nama_item">
|
|
<label for="nama_item" class="form-label">Nama Item</label>
|
|
<input type="text" id="nama_item" class="form-control" placeholder="Nama Item">
|
|
</div>
|
|
|
|
<div class="mb-2 d-none" id="wrap_warehouse">
|
|
<label for="warehouse_id" class="form-label">Gudang</label>
|
|
<select id="warehouse_id" class="form-control"></select>
|
|
</div>
|
|
|
|
<div class="mb-2 d-none" id="wrap_item_select">
|
|
<label for="item_select" class="form-label">Item dari Gudang</label>
|
|
<select id="item_select" class="form-control select-search"></select>
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<label for="qty" class="form-label">Qty</label>
|
|
<input type="number" id="qty" class="form-control" min="0">
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<label for="harga" class="form-label">Harga</label>
|
|
<input type="text" id="harga" class="form-control format-rupiah" min="0">
|
|
</div>
|
|
|
|
<!-- CICILAN -->
|
|
<div class="mb-3 mt-3" id="is_cicilan_group">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="is_cicilan">
|
|
<label class="form-check-label fw-semibold" for="is_cicilan">
|
|
Pembayaran Cicilan
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="wrap_cicilan" class="border rounded-3 p-3 bg-light d-none">
|
|
|
|
<div class="mb-2">
|
|
<label class="form-label">Tenor (Jumlah Cicilan)</label>
|
|
<input type="number" id="tenor" class="form-control" min="1" placeholder="Contoh: 3">
|
|
</div>
|
|
|
|
<div class="small text-muted">
|
|
Sistem akan otomatis membagi total harga menjadi beberapa cicilan.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<label for="tanggal_input" class="form-label">Tanggal</label>
|
|
<input type="date" id="tanggal_input" class="form-control">
|
|
</div>
|
|
|
|
|
|
<div class="mb-2">
|
|
<label for="keterangan" class="form-label">Keterangan</label>
|
|
<textarea class="form-control" id="keterangan" rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
|
<button class="btn btn-warning" id="btnSimpanItem">Simpan</button>
|
|
</div>
|
|
</div></div></div>
|
|
|
|
<script>
|
|
$(function(){
|
|
let invoice_id = "<?= $invoice_id ?>";
|
|
let action = "add";
|
|
let edit_id = null;
|
|
let isEditing = false;
|
|
let editData = null;
|
|
let modal = new bootstrap.Modal(document.getElementById('modalItem'));
|
|
|
|
// ================= FORMAT =================
|
|
function formatNumber(num){
|
|
return new Intl.NumberFormat('id-ID').format(num || 0);
|
|
}
|
|
|
|
// ================= FORMAT TANGGAL (BARU) =================
|
|
function formatTglIndo(tgl){
|
|
if(!tgl) return '-';
|
|
let date = new Date(tgl);
|
|
let day = String(date.getDate()).padStart(2, '0');
|
|
let month = String(date.getMonth() + 1).padStart(2, '0');
|
|
let year = date.getFullYear();
|
|
return `${day}/${month}/${year}`;
|
|
}
|
|
|
|
// ================= VALIDASI FORM =================
|
|
function validateForm(){
|
|
let errors = [];
|
|
|
|
if(!$('#tanggal_input').val()) errors.push('Tanggal wajib diisi');
|
|
if(!$('#account_id').val()) errors.push('Account wajib dipilih');
|
|
if(!$('#nama_item').val() && !$('#item_select').val()) errors.push('Nama item wajib diisi');
|
|
if(!$('#qty').val() || $('#qty').val() <= 0) errors.push('Qty harus lebih dari 0');
|
|
if(!$('#harga').val() || $('#harga').val() <= 0) errors.push('Harga harus lebih dari 0');
|
|
|
|
if($('#is_cicilan').is(':checked')){
|
|
let tenor = parseInt($('#tenor').val());
|
|
if(!tenor || tenor <= 0){
|
|
errors.push('Tenor cicilan wajib diisi');
|
|
}
|
|
}
|
|
|
|
if(errors.length > 0){
|
|
Swal.fire('Error', errors.join('<br>'), 'error');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ================= RESET UI =================
|
|
function resetForm(){
|
|
$('#wrap_warehouse').addClass('d-none');
|
|
$('#wrap_item_select').addClass('d-none');
|
|
$('#wrap_nama_item').removeClass('d-none');
|
|
|
|
$('#warehouse_id').html('<option value="">Pilih Gudang</option>');
|
|
$('#item_select').html('<option value="">Pilih Item</option>');
|
|
$('#nama_item').val('').show();
|
|
|
|
$('#tanggal_input').val('');
|
|
$('#qty').val('');
|
|
$('#harga').val('');
|
|
$('#keterangan').val('');
|
|
|
|
$('#is_cicilan').prop('checked', false);
|
|
$('#wrap_cicilan').addClass('d-none');
|
|
$('#tenor').val('');
|
|
|
|
setMode('manual');
|
|
}
|
|
|
|
// ================= COA =================
|
|
$.get("<?= base_url('invoices/get_accounts'); ?>", function(res){
|
|
let opt = '<option value="">Pilih Akun</option>';
|
|
if(res && res.length){
|
|
res.forEach(a=>{
|
|
opt += `<option value="${a.id}">${a.kode_akun} - ${a.nama_akun}</option>`;
|
|
});
|
|
}
|
|
$("#account_id").html(opt);
|
|
},'json').fail(function(){
|
|
$("#account_id").html('<option value="">Error loading accounts</option>');
|
|
});
|
|
|
|
// ================= ACCOUNT CHANGE =================
|
|
$('#account_id').on('change', function(){
|
|
let account_id = $(this).val();
|
|
|
|
if(!isEditing){
|
|
resetForm();
|
|
}
|
|
|
|
if(!account_id) return;
|
|
|
|
$.get("<?= base_url('invoices/check_items_by_account/'); ?>" + account_id, function(res){
|
|
if(res.has_item){
|
|
$('#wrap_warehouse').removeClass('d-none');
|
|
$('#wrap_nama_item').addClass('d-none');
|
|
loadWarehouse();
|
|
setMode('warehouse');
|
|
} else {
|
|
$('#wrap_nama_item').removeClass('d-none');
|
|
setMode('manual');
|
|
}
|
|
}, 'json').fail(function(){
|
|
Swal.fire('Error', 'Gagal memuat data account', 'error');
|
|
});
|
|
});
|
|
|
|
// ================= WAREHOUSE =================
|
|
function loadWarehouse(){
|
|
return $.get("<?= base_url('invoices/gudang_list'); ?>", function(res){
|
|
let opt = '<option value="">Pilih Gudang</option>';
|
|
if(res && res.length){
|
|
res.forEach(w=>{
|
|
opt += `<option value="${w.id}">${w.nama}</option>`;
|
|
});
|
|
}
|
|
$('#warehouse_id').html(opt);
|
|
}, 'json');
|
|
}
|
|
|
|
$('#warehouse_id').on('change', function(){
|
|
let warehouse_id = $(this).val();
|
|
let account_id = $('#account_id').val();
|
|
|
|
$('#wrap_item_select').addClass('d-none');
|
|
$('#item_select').html('<option value="">Loading...</option>');
|
|
|
|
if(!warehouse_id){
|
|
$('#wrap_nama_item').removeClass('d-none');
|
|
setMode('manual');
|
|
return;
|
|
}
|
|
|
|
loadItems(account_id, warehouse_id);
|
|
});
|
|
|
|
// ================= ITEMS =================
|
|
function loadItems(account_id, warehouse_id){
|
|
return $.get("<?= base_url('invoices/get_items_by_wh_account/'); ?>" + warehouse_id + "/" + account_id, function(res){
|
|
if(res && res.length){
|
|
let opt = '<option value="">Pilih Item</option>';
|
|
res.forEach(i=>{
|
|
opt += `<option value="${i.id}"
|
|
data-harga="${i.harga_jual}"
|
|
data-stok="${i.stok}"
|
|
data-nama="${i.nama_barang}">
|
|
${i.kode_detail} - ${i.nama_barang} (Stok: ${i.stok})
|
|
</option>`;
|
|
});
|
|
$('#item_select').html(opt);
|
|
$('#wrap_item_select').removeClass('d-none');
|
|
$('#wrap_nama_item').addClass('d-none');
|
|
setMode('item');
|
|
} else {
|
|
$('#wrap_item_select').addClass('d-none');
|
|
$('#wrap_nama_item').removeClass('d-none');
|
|
setMode('manual');
|
|
}
|
|
}, 'json');
|
|
}
|
|
|
|
// ================= AUTO HARGA =================
|
|
$('#item_select').on('change', function(){
|
|
let selected = $(this).find(':selected');
|
|
let harga = parseFloat(selected.data('harga')) || 0;
|
|
$('#harga').val(harga);
|
|
});
|
|
|
|
// ================= STOK CHECK =================
|
|
$('#qty').on('input', function(){
|
|
let qty = parseFloat($(this).val()) || 0;
|
|
let selected = $('#item_select option:selected');
|
|
let stok = selected.length ? parseFloat(selected.data('stok')) || 0 : 0;
|
|
|
|
if(stok > 0 && qty > stok){
|
|
Swal.fire('Warning','Stok tidak cukup! Max: ' + stok,'warning');
|
|
$(this).val(stok);
|
|
}
|
|
});
|
|
|
|
let invoiceData = null;
|
|
|
|
// ================= LOAD INVOICE =================
|
|
function loadInvoice(){
|
|
|
|
$.get("<?= base_url('invoices/get_invoice/'); ?>"+invoice_id,function(data){
|
|
|
|
if(!data) return;
|
|
|
|
invoiceData = data;
|
|
|
|
let backUrl = "<?= base_url('invoices') ?>";
|
|
|
|
if(data.status === 'draft'){
|
|
backUrl = "<?= base_url('invoices/draft') ?>";
|
|
$("#btn-add-items").html(`<button class="btn btn-warning btn-add">Tambah Item</button>`);
|
|
} else if(data.status === 'unpaid'){
|
|
backUrl = "<?= base_url('invoices/draft') ?>";
|
|
loadRiwayatPembayaran();
|
|
} else if(data.status === 'partial'){
|
|
backUrl = "<?= base_url('invoices/draft') ?>";
|
|
loadRiwayatPembayaran();
|
|
} else if(data.status === 'paid'){
|
|
backUrl = "<?= base_url('invoices/paid') ?>";
|
|
loadRiwayatPembayaran();
|
|
}
|
|
|
|
$('#btn-back').attr('href', backUrl);
|
|
|
|
$("#customerInfo").html(`
|
|
<hr>
|
|
<div class="px-2">
|
|
<h3 class="mt-2 fw-bold text-end">
|
|
<span class="${getStatusClass(data.status)}">
|
|
${(data.status || '').toUpperCase()}
|
|
</span>
|
|
</h3>
|
|
|
|
<div class="row">
|
|
|
|
<!-- CUSTOMER INFO -->
|
|
<div class="col-md-9">
|
|
<div class="p-2">
|
|
<div class="font-weight-bold mb-1" style="font-size:16px;">
|
|
${data.nama || '-'}
|
|
</div>
|
|
|
|
<div class="text-muted small" style="font-size:13px;">
|
|
<div>${data.telp || '-'}</div>
|
|
<div>${data.alamat || '-'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- INVOICE INFO -->
|
|
<div class="col-md-3 text-end">
|
|
<div class="p-2">
|
|
|
|
<table class="w-100" style="font-size:13px; line-height:1.6;">
|
|
<tr>
|
|
<td class="text-muted">Tanggal :</td>
|
|
<td class="text-right font-weight-bold text-dark">
|
|
${formatTglIndo(data.tanggal)}
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td class="text-muted">Due Date :</td>
|
|
<td class="text-right font-weight-bold text-dark">
|
|
${formatTglIndo(data.jatuh_tempo)}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
`);
|
|
|
|
renderTotal(data.total);
|
|
|
|
},'json');
|
|
}
|
|
|
|
function getStatusClass(status){
|
|
const classes = {
|
|
'draft': 'text-secondary',
|
|
'unpaid': 'text-warning',
|
|
'paid': 'text-success',
|
|
'partial': 'text-primary'
|
|
};
|
|
return classes[status] || 'text-dark';
|
|
}
|
|
|
|
// ================= TABLE =================
|
|
function loadInvoiceItems(){
|
|
$.get("<?= base_url('invoices/get_items/'); ?>" + invoice_id, function(data){
|
|
let html = '';
|
|
let total = 0;
|
|
let no = 1;
|
|
|
|
if(!data || data.length === 0){
|
|
$("#tableDetail tbody").html(`
|
|
<tr>
|
|
<td colspan="9" class="text-center py-4 text-muted">Tidak ada data item</td>
|
|
</tr>
|
|
`);
|
|
$("#grandTotal").text('0');
|
|
return;
|
|
}
|
|
|
|
data.forEach(v => {
|
|
let subtotal = parseFloat(v.subtotal) || 0;
|
|
total += subtotal;
|
|
|
|
let btnAction = '';
|
|
if(v.status == 'draft'){
|
|
|
|
let btnEditTable = '';
|
|
if(v.is_cicilan == 0) {
|
|
btnEditTable = `
|
|
<button class="btn btn-sm btn-secondary btn-editItem"
|
|
data-id="${v.id}"
|
|
data-tanggal_input="${v.tanggal}"
|
|
data-account="${v.account_id}"
|
|
data-item="${v.nama_item}"
|
|
data-qty="${v.qty}"
|
|
data-harga="${v.harga}"
|
|
data-warehouse="${v.warehouse_id || ''}"
|
|
data-item_id="${v.items_id || ''}"
|
|
data-keterangan="${v.keterangan || ''}"
|
|
data-is_cicilan="${v.is_cicilan || 0}">
|
|
Edit
|
|
</button>`;
|
|
}
|
|
btnAction = `
|
|
${btnEditTable}
|
|
<button class="btn btn-sm btn-danger btn-delete" data-id="${v.id}">
|
|
Hapus
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
<tr>
|
|
<td>
|
|
<small class="text-secondary">${v.kode_akun || ''} - ${v.nama_akun || ''}</small>
|
|
<br><small class="text-warning"><small>Created at : </small>${v.created_at || ''}</small>
|
|
</td>
|
|
<td>${no++}</td>
|
|
<td>${formatTglIndo(v.tanggal)}</td>
|
|
<td>${v.nama_item || '-'}</td>
|
|
<td>${v.qty || 0}</td>
|
|
<td>${formatNumber(v.harga)}</td>
|
|
<td>${formatNumber(subtotal)}</td>
|
|
<td style="max-width: 150px">${v.keterangan || '-'}</td>
|
|
<td>${btnAction}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
$("#tableDetail tbody").html(html);
|
|
renderTotal(total);
|
|
|
|
},'json').fail(function(){
|
|
$("#tableDetail tbody").html('<tr><td colspan="9" class="text-center text-danger">Error loading items</td></tr>');
|
|
});
|
|
}
|
|
|
|
function renderTotal(subtotal){
|
|
|
|
let tunggakan = invoiceData ? (invoiceData.tunggakan || 0) : 0;
|
|
|
|
$("#grandTotal").html(`
|
|
<tr>
|
|
<td class="pe-3 text-muted text-end">Subtotal</td>
|
|
<td class="pe-2">:</td>
|
|
<td><strong class="text-success">${formatNumber(subtotal)}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="pe-3 text-muted text-end">Akumulasi Tunggakan Invoice Bulan Lalu</td>
|
|
<td class="pe-2">:</td>
|
|
<td><strong class="text-danger">${formatNumber(tunggakan)}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="pe-3 text-muted text-end">Total Tagihan</td>
|
|
<td class="pe-2">:</td>
|
|
<td><strong class="text-dark">${formatNumber((parseFloat(subtotal) || 0) + (parseFloat(tunggakan) || 0))}</strong></td>
|
|
</tr>
|
|
`);
|
|
}
|
|
|
|
// ================= ADD BUTTON =================
|
|
$(document).on('click', '.btn-add', function () {
|
|
action = "add";
|
|
edit_id = null;
|
|
isEditing = false;
|
|
editData = null;
|
|
|
|
$('#modalTitle').text('Tambah Item');
|
|
$('#account_id').val('');
|
|
resetForm();
|
|
modal.show();
|
|
});
|
|
|
|
// ================= EDIT =================
|
|
$(document).on('click','.btn-editItem', function(){
|
|
action = "edit";
|
|
edit_id = $(this).data('id');
|
|
isEditing = true;
|
|
editData = $(this).data();
|
|
|
|
$('#modalTitle').text('Edit Item');
|
|
resetForm();
|
|
|
|
// Set basic fields first
|
|
$('#account_id').val(editData.account).trigger('change');
|
|
|
|
// Wait for account change to complete, then set other fields
|
|
setTimeout(() => {
|
|
if(editData.warehouse){
|
|
$('#warehouse_id').val(editData.warehouse).trigger('change');
|
|
|
|
// gak ada checkbox cicilan
|
|
$('#is_cicilan_group').addClass('d-none');
|
|
|
|
// Wait for warehouse items to load
|
|
setTimeout(() => {
|
|
$('#item_select').val(editData.item_id).trigger('change');
|
|
}, 500);
|
|
} else {
|
|
$('#nama_item').val(editData.item);
|
|
}
|
|
|
|
|
|
$('#qty').val(editData.qty);
|
|
$('#harga').val(editData.harga);
|
|
$('#tanggal_input').val(editData.tanggal_input);
|
|
$('#keterangan').val(editData.keterangan);
|
|
|
|
}, 500);
|
|
|
|
// if(editData.is_cicilan == 1){
|
|
// $('#qty').prop('disabled', true);
|
|
// $('#harga').prop('disabled', true);
|
|
// $('#is_cicilan').prop('disabled', true);
|
|
// $('#tenor').prop('disabled', true);
|
|
// }
|
|
|
|
modal.show();
|
|
});
|
|
|
|
// ================= SAVE =================
|
|
|
|
// ================= SAVE - FIXED SESUAI STYLE ANDA =================
|
|
$('#btnSimpanItem').on('click', function(){
|
|
|
|
if(!validateForm()) return;
|
|
|
|
let url = (action === 'add')
|
|
? "<?= base_url('invoices/add_item'); ?>"
|
|
: "<?= base_url('invoices/update_item'); ?>";
|
|
|
|
let selected = $('#item_select option:selected');
|
|
|
|
let nama_item = $('#nama_item').val().trim();
|
|
let item_id = selected.val() || '';
|
|
|
|
if(selected.length && selected.val()){
|
|
nama_item = selected.data('nama') || nama_item;
|
|
}
|
|
|
|
let warehouse_id = $('#warehouse_id').val() || '';
|
|
|
|
let data = {
|
|
invoice_id: invoice_id,
|
|
tanggal: $('#tanggal_input').val(), // ✅ FIXED
|
|
account_id: $('#account_id').val(),
|
|
warehouse_id: warehouse_id || null,
|
|
nama_item: nama_item,
|
|
item_id: item_id || null,
|
|
qty: parseFloat($('#qty').val()) || 0,
|
|
harga: parseFloat($('#harga').val()) || 0,
|
|
keterangan: $('#keterangan').val().trim(),
|
|
|
|
is_cicilan: $('#is_cicilan').is(':checked') ? 1 : 0,
|
|
tenor: parseInt($('#tenor').val()) || 0,
|
|
};
|
|
|
|
if(action === 'edit'){
|
|
data.id = edit_id;
|
|
}
|
|
|
|
let btn = $(this);
|
|
btn.prop('disabled', true).html('Menyimpan...');
|
|
|
|
$.post({
|
|
url: url,
|
|
data: data,
|
|
dataType: 'json' // ✅ FIXED
|
|
})
|
|
.done(function(res){
|
|
|
|
if(res.status === true || res.status === 1){
|
|
|
|
modal.hide();
|
|
loadInvoice();
|
|
loadInvoiceItems();
|
|
|
|
Swal.fire('Success', res.message || 'Data berhasil disimpan!', 'success');
|
|
|
|
} else {
|
|
|
|
Swal.fire('Error', res.message || 'Gagal menyimpan data', 'error');
|
|
}
|
|
})
|
|
.fail(function(){
|
|
Swal.fire('Error', 'Terjadi kesalahan koneksi', 'error');
|
|
})
|
|
.always(function(){
|
|
btn.prop('disabled', false).html('Simpan');
|
|
});
|
|
|
|
});
|
|
|
|
|
|
// ================= DELETE =================
|
|
$(document).on('click','.btn-delete', function(){
|
|
|
|
let id = $(this).data('id');
|
|
let btn = $(this);
|
|
|
|
Swal.fire({
|
|
title:'Hapus?',
|
|
text: "Data akan dihapus permanen!",
|
|
icon:'warning',
|
|
showCancelButton:true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Ya, Hapus!',
|
|
cancelButtonText: 'Batal'
|
|
}).then((result) => {
|
|
|
|
if(result.isConfirmed){
|
|
|
|
btn.prop('disabled', true).html('Menghapus...');
|
|
|
|
$.get({
|
|
url: "<?= base_url('invoices/delete_item/'); ?>" + id,
|
|
dataType: 'json' // ✅ FIXED
|
|
})
|
|
.done(function(res){
|
|
|
|
if(res.status === true || res.status === 1){
|
|
|
|
loadInvoice();
|
|
loadInvoiceItems();
|
|
|
|
Swal.fire('Success', res.message || 'Item berhasil dihapus', 'success');
|
|
|
|
} else {
|
|
|
|
Swal.fire('Error', res.message || 'Gagal menghapus', 'error');
|
|
}
|
|
})
|
|
.fail(function(){
|
|
Swal.fire('Error', 'Terjadi kesalahan koneksi', 'error');
|
|
})
|
|
.always(function(){
|
|
btn.prop('disabled', false).html('Hapus');
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// ================= MODE =================
|
|
function setMode(type){
|
|
let $label = $('#item_mode_label')
|
|
.removeClass('alert-secondary alert-info alert-success alert-warning');
|
|
|
|
switch(type){
|
|
case 'manual':
|
|
$label.addClass('alert-secondary').text('Mode: Manual Input');
|
|
break;
|
|
case 'warehouse':
|
|
$label.addClass('alert-info').text('Mode: Pilih Gudang');
|
|
break;
|
|
case 'item':
|
|
$label.addClass('alert-success').text('Mode: Item dari Gudang');
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ================= MODAL EVENTS =================
|
|
$('#modalItem').on('hidden.bs.modal', function(){
|
|
if(!isEditing){
|
|
resetForm();
|
|
}
|
|
action = "add";
|
|
edit_id = null;
|
|
isEditing = false;
|
|
editData = null;
|
|
});
|
|
|
|
// =========================
|
|
// SAVE CICILAN (POST)
|
|
// =========================
|
|
$(document).on('click', '#save_cicilan', function(){
|
|
|
|
let selected = [];
|
|
|
|
$('#modalCicilan input[type=radio]:checked').each(function(){
|
|
selected.push($(this).val());
|
|
});
|
|
|
|
if (selected.length === 0) {
|
|
Swal.fire('Warning', 'Pilih minimal 1 cicilan!', 'warning');
|
|
return;
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'Konfirmasi',
|
|
text: 'Simpan cicilan yang dipilih?',
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Ya, Simpan',
|
|
cancelButtonText: 'Batal'
|
|
}).then((result) => {
|
|
|
|
if (!result.isConfirmed) return;
|
|
|
|
$.ajax({
|
|
url: "<?= base_url('invoices/save_cicilan'); ?>",
|
|
type: "POST",
|
|
data: {
|
|
cicilan_id: selected,
|
|
customer_id: "<?= $customer_id ?>",
|
|
invoice_id: "<?= $invoice_id ?>"
|
|
},
|
|
dataType: "json",
|
|
beforeSend: function(){
|
|
$('#save_cicilan').prop('disabled', true).text('Menyimpan...');
|
|
|
|
Swal.fire({
|
|
title: 'Menyimpan...',
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
Swal.showLoading();
|
|
}
|
|
});
|
|
},
|
|
success: function(res){
|
|
|
|
if (res.status) {
|
|
Swal.fire('Berhasil', 'Cicilan berhasil disimpan', 'success');
|
|
$('#modalCicilan').modal('hide');
|
|
|
|
loadInvoice();
|
|
loadInvoiceItems();
|
|
|
|
} else {
|
|
Swal.fire('Gagal', res.message || 'Gagal menyimpan', 'error');
|
|
}
|
|
|
|
},
|
|
error: function(){
|
|
Swal.fire('Error', 'Terjadi kesalahan server', 'error');
|
|
},
|
|
complete: function(){
|
|
$('#save_cicilan').prop('disabled', false).text('Pilih Cicilan');
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// ================= INIT =================
|
|
loadInvoice();
|
|
loadInvoiceItems();
|
|
|
|
// Prevent double submit
|
|
$(document).on('submit', 'form', function(e){
|
|
e.preventDefault();
|
|
});
|
|
|
|
// ================= GENERATE PDF =================
|
|
$('#generate_pdf').on('click', function(){
|
|
|
|
let btn = $(this);
|
|
|
|
btn.prop('disabled', true).html('Generating...');
|
|
|
|
$.ajax({
|
|
url: "<?= base_url('generateinvoice/generate/'.$invoice_id); ?>",
|
|
type: "GET",
|
|
xhrFields: {
|
|
responseType: 'blob' // 🔥 penting untuk PDF
|
|
},
|
|
success: function(response){
|
|
|
|
let blob = new Blob([response], { type: 'application/pdf' });
|
|
let url = window.URL.createObjectURL(blob);
|
|
|
|
// buka di tab baru
|
|
window.open(url, '_blank');
|
|
|
|
},
|
|
error: function(){
|
|
Swal.fire('Error', 'Gagal generate PDF', 'error');
|
|
},
|
|
complete: function(){
|
|
btn.prop('disabled', false).html('Generate PDF');
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
function loadRiwayatPembayaran(){
|
|
|
|
$.ajax({
|
|
url: "<?= base_url('invoices/get_payment/'); ?>" + invoice_id,
|
|
type: "GET",
|
|
dataType: "json",
|
|
success: function(res){
|
|
|
|
let totalBayar = res.invoice?.total_bayar || 0;
|
|
let sisa = res.invoice?.sisa_piutang || 0;
|
|
|
|
let html = `
|
|
<div class="card modern-card shadow-sm border-0 rounded-4 mt-4">
|
|
<div class="card-body p-4">
|
|
|
|
<!-- HEADER -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0 fw-semibold">Riwayat Pembayaran</h5>
|
|
<span class="badge bg-light text-dark">
|
|
${res.data.length} transaksi
|
|
</span>
|
|
</div>
|
|
`;
|
|
|
|
if(res.data.length === 0){
|
|
|
|
html += `
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-receipt" style="font-size: 26px;"></i>
|
|
<div class="mt-2">Belum ada pembayaran</div>
|
|
</div>
|
|
`;
|
|
|
|
} else {
|
|
|
|
html += `<div class="position-relative ps-3">`;
|
|
|
|
res.data.forEach(function(row, i){
|
|
|
|
html += `
|
|
<div class="mb-3 position-relative">
|
|
|
|
<!-- DOT -->
|
|
<div style="
|
|
position:absolute;
|
|
left:-8px;
|
|
top:6px;
|
|
width:10px;
|
|
height:10px;
|
|
background:#198754;
|
|
border-radius:50%;
|
|
"></div>
|
|
|
|
<div class="p-3 border rounded-3 bg-white shadow-sm d-flex justify-content-between align-items-center">
|
|
|
|
<!-- LEFT -->
|
|
<div>
|
|
<div class="fw-semibold">
|
|
${row.tanggal}
|
|
</div>
|
|
<small class="text-muted">
|
|
${row.created_at}
|
|
</small>
|
|
</div>
|
|
|
|
<!-- RIGHT -->
|
|
<div class="text-end">
|
|
<div class="fw-bold text-success fs-6">
|
|
+ ${row.jumlah}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += `</div>`;
|
|
}
|
|
|
|
// ================= SUMMARY =================
|
|
html += `
|
|
<hr class="my-4">
|
|
|
|
<div class="d-flex flex-column gap-2">
|
|
|
|
<div class="p-3 rounded-3 bg-light d-flex justify-content-between align-items-center">
|
|
<span class="text-muted">Total Pembayaran</span>
|
|
<span class="fw-bold text-success fs-5">
|
|
Rp ${formatNumber(totalBayar)}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="p-3 rounded-3 d-flex justify-content-between align-items-center"
|
|
style="background: ${sisa > 0 ? '#fff5f5' : '#f0fff4'};">
|
|
<span class="text-muted">Sisa Tagihan</span>
|
|
<span class="fw-bold ${sisa > 0 ? 'text-danger' : 'text-success'} fs-5">
|
|
Rp ${formatNumber(sisa)}
|
|
</span>
|
|
</div>
|
|
|
|
</div>
|
|
`;
|
|
|
|
html += `
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('#riwayatPembayaran').html(html);
|
|
|
|
},
|
|
error:function(){
|
|
$('#riwayatPembayaran').html(`
|
|
<div class="text-danger text-center mt-3">
|
|
Gagal load data
|
|
</div>
|
|
`);
|
|
}
|
|
});
|
|
|
|
}
|
|
});
|
|
|
|
// ================= CICILAN TOGGLE =================
|
|
$('#is_cicilan').on('change', function(){
|
|
if(this.checked){
|
|
$('#wrap_cicilan').removeClass('d-none');
|
|
} else {
|
|
$('#wrap_cicilan').addClass('d-none');
|
|
$('#tenor').val('');
|
|
}
|
|
});
|
|
|
|
// =========================
|
|
// GET CICILAN
|
|
// =========================
|
|
$(document).on('click', '#get_cicilan', function() {
|
|
|
|
let url = "<?= base_url('invoices/get_cicilan/'.$customer_id); ?>";
|
|
|
|
$('#cicilanContainer').html('<div class="text-center">Loading...</div>');
|
|
|
|
$.get(url, function(res){
|
|
|
|
let data;
|
|
try {
|
|
data = typeof res === 'object' ? res : JSON.parse(res);
|
|
} catch (e) {
|
|
Swal.fire('Error', 'Format response salah', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!data || data.length === 0) {
|
|
Swal.fire('Info', 'Tidak ada cicilan tersedia', 'info');
|
|
return;
|
|
}
|
|
|
|
let grouped = {};
|
|
|
|
data.forEach(item => {
|
|
if (!grouped[item.invoice_detail_id]) {
|
|
grouped[item.invoice_detail_id] = [];
|
|
}
|
|
grouped[item.invoice_detail_id].push(item);
|
|
});
|
|
|
|
let html = '';
|
|
|
|
Object.keys(grouped).forEach((key) => {
|
|
|
|
let items = grouped[key];
|
|
|
|
let total = items.reduce((sum, i) => {
|
|
return sum + (parseFloat(i.nominal) - parseFloat(i.paid_amount));
|
|
}, 0);
|
|
|
|
html += `
|
|
<div class="mb-4">
|
|
|
|
<div class="bg-light p-2 fw-bold d-flex justify-content-between">
|
|
<span>Nama Barang : ${items[0].nama_barang}</span>
|
|
<span class="text-primary">
|
|
Total: Rp ${total.toLocaleString()}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-sm mb-0">
|
|
<thead class="table-warning text-center">
|
|
<tr>
|
|
<th width="40">Pilih</th>
|
|
<th>Angsuran</th>
|
|
<th>Jatuh Tempo</th>
|
|
<th>Nominal</th>
|
|
<th>Dibayar</th>
|
|
<th>Sisa</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
items.forEach((item) => {
|
|
|
|
let nominal = parseFloat(item.nominal);
|
|
let paid = parseFloat(item.paid_amount);
|
|
let sisa = nominal - paid;
|
|
|
|
html += `
|
|
<tr class="cicilan-row" data-id="${item.id}" data-group="${key}">
|
|
|
|
<td class="text-center">
|
|
<input
|
|
type="radio"
|
|
name="cicilan_group_${key}"
|
|
value="${item.id}"
|
|
id="radio_${item.id}"
|
|
>
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
${item.angsuran_ke} / ${item.total_angsuran}
|
|
</td>
|
|
|
|
<td>${item.jatuh_tempo}</td>
|
|
|
|
<td class="text-end">
|
|
Rp ${nominal.toLocaleString()}
|
|
</td>
|
|
|
|
<td class="text-end">
|
|
Rp ${paid.toLocaleString()}
|
|
</td>
|
|
|
|
<td class="text-end text-success">
|
|
Rp ${sisa.toLocaleString()}
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
<span class="badge bg-${item.status === 'unpaid' ? 'danger' : 'warning'}">
|
|
${item.status}
|
|
</span>
|
|
</td>
|
|
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
$('#cicilanContainer').html(html);
|
|
|
|
$('#modalCicilan').modal('show');
|
|
|
|
}).fail(function(){
|
|
Swal.fire('Error', 'Gagal ambil data', 'error');
|
|
});
|
|
|
|
});
|
|
|
|
|
|
// =========================
|
|
// CLICK ROW (PILIH RADIO + HIGHLIGHT)
|
|
// =========================
|
|
$(document).on('click', '.cicilan-row', function(){
|
|
|
|
let id = $(this).data('id');
|
|
let group = $(this).data('group');
|
|
|
|
$(`input[name="cicilan_group_${group}"]`).prop('checked', false);
|
|
$(`#radio_${id}`).prop('checked', true);
|
|
|
|
$(`tr[data-group="${group}"]`).removeClass('table-secondary');
|
|
$(this).addClass('table-secondary');
|
|
});
|
|
|
|
|
|
</script> |