Files
2026-09-11 16:03:00 +07:00

1433 lines
42 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function ($) {
"use strict";
var C = window.FIXED_ASSET_PAGE || {},
B = C.base || "/",
P = C.permissions || {},
files = [],
table,
currentAsset = null,
warehouseUnitCost = 0;
function esc(v) {
return $("<div>")
.text(v == null ? "" : v)
.html();
}
function rp(v) {
return (
"Rp " +
Number(v || 0).toLocaleString("id-ID", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
);
}
function q(v) {
return Number(v || 0).toLocaleString("id-ID", { maximumFractionDigits: 4 });
}
function fd(obj) {
var f = new FormData();
Object.keys(obj || {}).forEach(function (k) {
f.append(k, obj[k] == null ? "" : obj[k]);
});
if (window.APP_CSRF) f.append(APP_CSRF.name, APP_CSRF.value);
return f;
}
function request(url, opt) {
opt = opt || {};
if (
opt.body instanceof FormData &&
window.APP_CSRF &&
!opt.body.has(APP_CSRF.name)
)
opt.body.append(APP_CSRF.name, APP_CSRF.value);
opt.headers = Object.assign(
{ Accept: "application/json", "X-Requested-With": "XMLHttpRequest" },
opt.headers || {},
);
return fetch(url, opt).then(function (r) {
return r.text().then(function (t) {
var x;
try {
x = JSON.parse(t);
} catch (e) {
throw Error("Server tidak mengirim JSON yang valid.");
}
if (!r.ok || x.status === false)
throw Error(x.message || "Proses gagal.");
return x;
});
});
}
function toast(m, i) {
return AppAlert.toast(m, i || "success");
}
function fail(e) {
toast(e.message || String(e), "error");
}
function reload() {
if (table) table.ajax.reload(null, false);
}
function status(v) {
var label = {
draft: "DRAFT",
submitted: "MENUNGGU APPROVAL",
approved: "SIAP KAPITALISASI",
capitalized: "TERKAPITALISASI",
rejected: "DITOLAK",
active: "AKTIF",
fully_depreciated: "TERSUSUT PENUH",
disposed: "DILEPAS",
lost: "HILANG",
damaged: "RUSAK",
};
return (
'<span class="status-pill status-' +
esc(v) +
'">' +
esc(label[v] || String(v || "-").toUpperCase()) +
"</span>"
);
}
function initTable() {
table = $("#assetTable").DataTable({
processing: true,
serverSide: true,
responsive: true,
searching: false,
pageLength: 10,
order: [],
ajax: {
url: B + "fixedassets/data",
data: function (d) {
d.search = { value: $("#assetSearch").val() };
d.category_id = $("#filterCategory").val();
d.workflow_status = $("#filterWorkflow").val();
d.lifecycle_status = $("#filterLifecycle").val();
},
},
columns: [
{
data: null,
render: function (_, t, r) {
return (
"<strong>" +
esc(r.kode_asset) +
"</strong><br><small>" +
esc(r.nama_asset) +
"</small>"
);
},
},
{
data: null,
render: function (_, t, r) {
return (
esc(r.category_name || "-") +
"<br><small>" +
esc(r.location_name || "-") +
"</small>"
);
},
},
{
data: "source_type",
render: function (v, t, r) {
return (
esc(v === "warehouse" ? "Gudang" : "Input langsung") +
(r.barcode ? "<br><small>" + esc(r.barcode) + "</small>" : "")
);
},
},
{
data: null,
render: function (_, t, r) {
return (
status(r.workflow_status) + "<br>" + status(r.lifecycle_status)
);
},
},
{
data: "nilai_perolehan",
className: "text-end",
render: function (v) {
return rp(v);
},
},
{
data: "nilai_buku",
className: "text-end",
render: function (v) {
return rp(v);
},
},
{
data: "id",
orderable: false,
render: function (id) {
return (
'<button class="btn btn-sm btn-outline-primary btn-detail" data-id="' +
id +
'"><i class="bi bi-eye"></i> Detail</button>'
);
},
},
],
language: {
processing: "Memuat data…",
emptyTable: "Belum ada aset.",
info: "Menampilkan _START_–_END_ dari _TOTAL_",
infoEmpty: "Tidak ada data",
lengthMenu: "Tampilkan _MENU_",
paginate: { previous: "‹", next: "›" },
},
});
var timer;
$("#assetSearch").on("input", function () {
clearTimeout(timer);
timer = setTimeout(function () {
table.ajax.reload();
}, 350);
});
$("#filterCategory,#filterWorkflow,#filterLifecycle").on(
"change",
function () {
table.ajax.reload();
},
);
}
function initSelects() {
if (!$.fn.select2) return;
$("#inventoryLookup")
.select2({
width: "100%",
dropdownParent: $("#assetFormModal"),
placeholder: "Cari barang/barcode gudang",
ajax: {
url: B + "fixedassets/lookup_inventory",
delay: 300,
data: function (p) {
return { q: p.term || "", page: p.page || 1 };
},
processResults: function (r) {
return {
results: r.results || [],
pagination: r.pagination || { more: false },
};
},
},
})
.on("select2:select", function (e) {
var r = e.params.data.data || {};
$("[name=item_id]").val(r.item_id);
$("[name=barcode_id]").val(r.barcode_id);
$("[name=warehouse_id]").val(r.warehouse_id);
$("[name=bin_id]").val(r.bin_id || "");
$("[name=nama_asset]").val(r.nama_barang);
warehouseUnitCost = Number(r.ledger_unit_cost || 0);
$("[name=qty]").val(
q(r.tracking_type === "UNIT" ? 1 : r.available_qty),
);
updateWarehouseValue();
$("#inventoryInfo").html(
"<b>" +
esc(r.kode_detail) +
" - " +
esc(r.nama_barang) +
"</b><br>Barcode " +
esc(r.barcode) +
(r.serial_number ? " · SN " + esc(r.serial_number) : "") +
" · " +
esc(r.warehouse_name) +
" · tersedia " +
q(r.available_qty) +
" · biaya ledger per unit " +
rp(warehouseUnitCost),
);
});
$("#employeeLookup").select2({
width: "100%",
dropdownParent: $("#assetFormModal"),
allowClear: true,
placeholder: "Cari karyawan",
ajax: {
url: B + "fixedassets/lookup_employees",
delay: 300,
data: function (p) {
return { q: p.term || "", page: p.page || 1 };
},
processResults: function (r) {
return {
results: r.results || [],
pagination: r.pagination || { more: false },
};
},
},
});
$("#journalLookup").select2({
width: "100%",
dropdownParent: $("#assetFormModal"),
placeholder: "Cari nomor jurnal",
ajax: {
url: B + "fixedassets/lookup_journals",
delay: 300,
data: function (p) {
return { q: p.term || "" };
},
processResults: function (r) {
return { results: r.results || [] };
},
},
});
}
function resetForm() {
var f = document.getElementById("assetForm");
f.reset();
warehouseUnitCost = 0;
files = [];
renderFiles();
f.elements.idempotency_key.value =
"AST-" + Date.now() + "-" + Math.random().toString(36).slice(2);
f.elements.tanggal_perolehan.value = new Date().toISOString().slice(0, 10);
f.elements.capitalization_date.value = f.elements.tanggal_perolehan.value;
$("#inventoryLookup,#employeeLookup,#journalLookup")
.val(null)
.trigger("change");
toggleSource();
toggleJournal();
}
function updateWarehouseValue() {
if ($("[name=source_type]:checked").val() !== "warehouse") return;
var qty =
Number(
window.cleanNumber
? window.cleanNumber($("[name=qty]").val())
: $("[name=qty]").val(),
) || 0,
value = warehouseUnitCost * qty;
$("[name=nilai_perolehan]").val(
window.formatNumber
? window.formatNumber(value.toFixed(2))
: value.toFixed(2),
);
}
function toggleSource() {
var w = $("[name=source_type]:checked").val() === "warehouse",
value = $("[name=nilai_perolehan]");
$(".warehouse-source").toggleClass("d-none", !w);
$(".manual-source").toggleClass("d-none", w);
value.prop("readonly", w);
$("#acquisitionValueLabel").text(
w ? "Nilai perolehan dari ledger" : "Nilai perolehan",
);
if (w && warehouseUnitCost <= 0 && $("[name=barcode_id]").val()) {
var qty =
Number(
window.cleanNumber
? window.cleanNumber($("[name=qty]").val())
: $("[name=qty]").val(),
) || 0,
total =
Number(
window.cleanNumber ? window.cleanNumber(value.val()) : value.val(),
) || 0;
warehouseUnitCost = qty > 0 ? total / qty : 0;
}
if (w) updateWarehouseValue();
$("[name=source_journal_mode]").val(
w
? "post_journal"
: $("[name=source_journal_mode]").val() || "post_journal",
);
toggleJournal();
}
function toggleJournal() {
var mode = $("[name=source_journal_mode]").val();
$(".linked-journal").toggleClass("d-none", mode !== "linked_journal");
$(".credit-account").toggleClass("d-none", mode !== "post_journal");
}
$(document).on("input", "[name=qty]", updateWarehouseValue);
function renderFiles() {
var add = $("#assetFiles .file-add").detach();
$("#assetFiles").empty();
files.forEach(function (f, i) {
var tile = $('<div class="file-tile"></div>');
if (f.type.indexOf("image/") === 0) {
var img = $('<img alt="Preview">');
img.attr("src", URL.createObjectURL(f));
tile.append(img);
} else
tile.append(
'<i class="bi bi-file-earmark-pdf pdf-icon"></i><small>PDF</small>',
);
tile.append(
$('<button type="button" aria-label="Hapus">×</button>').on(
"click",
function () {
files.splice(i, 1);
renderFiles();
},
),
);
$("#assetFiles").append(tile);
});
$("#assetFiles").append(add);
}
function uploadFiles(id) {
return files.reduce(function (chain, file) {
return chain.then(function () {
var f = new FormData();
f.append("entity_type", "asset");
f.append("entity_id", id);
f.append("attachment", file);
if (window.APP_CSRF) f.append(APP_CSRF.name, APP_CSRF.value);
return request(B + "accountingattachments/upload", {
method: "POST",
body: f,
});
});
}, Promise.resolve());
}
function uploadEntity(type, id, file) {
var f = new FormData();
f.append("entity_type", type);
f.append("entity_id", id);
f.append("attachment", file);
return request(B + "accountingattachments/upload", {
method: "POST",
body: f,
});
}
function saveAsset(e) {
e.preventDefault();
var form = e.currentTarget,
button = $(form).find("[type=submit]").prop("disabled", true);
request(B + "fixedassets/save", {
method: "POST",
body: new FormData(form),
})
.then(function (r) {
var id = r.result;
return uploadFiles(id).then(function () {
return r;
});
})
.then(function (r) {
bootstrap.Modal.getInstance(
document.getElementById("assetFormModal"),
).hide();
toast(r.message);
reload();
})
.catch(fail)
.finally(function () {
button.prop("disabled", false);
});
}
function attachments(list) {
if (!list || !list.length)
return '<div class="text-muted">Belum ada lampiran.</div>';
return (
'<div class="file-grid">' +
list
.map(function (f) {
var isPdf = (f.mime_type || "").indexOf("pdf") >= 0;
return (
'<button class="file-tile preview-file" data-url="' +
esc(f.preview_url) +
'">' +
(isPdf
? '<i class="bi bi-file-earmark-pdf pdf-icon"></i>'
: '<img src="' + esc(f.preview_url) + '" alt="Lampiran">') +
"<small>" +
esc(f.original_name) +
"</small></button>"
);
})
.join("") +
"</div>"
);
}
function detailHtml(a) {
var ev =
(a.events || [])
.map(function (x) {
return (
"<article><b>" +
esc(x.event_no || x.event_type) +
"</b><div>" +
esc(x.event_type) +
" · " +
esc(x.event_date) +
" · " +
rp(x.amount) +
"</div><small>" +
esc(x.notes || "-") +
(x.journal_no ? " · Jurnal " + esc(x.journal_no) : "") +
"</small></article>"
);
})
.join("") || '<span class="text-muted">Belum ada event.</span>';
return (
'<div class="detail-grid"><section class="detail-panel"><h6>' +
esc(a.kode_asset) +
" · " +
esc(a.nama_asset) +
'</h6><div class="detail-kv"><span>Dokumen</span><b>' +
esc(a.document_no) +
"</b><span>Status</span><div>" +
status(a.workflow_status) +
" " +
status(a.lifecycle_status) +
"</div><span>Kategori</span><b>" +
esc(a.category_name || "-") +
"</b><span>Lokasi / PIC</span><b>" +
esc(a.location_name || "-") +
" / " +
esc(a.responsible_name || "-") +
"</b><span>Sumber</span><b>" +
esc(a.source_type) +
(a.kode_detail ? " · " + esc(a.kode_detail) : "") +
"</b><span>Barcode / SN</span><b>" +
esc(a.barcode || "-") +
" / " +
esc(a.serial_number || "-") +
"</b><span>Perolehan</span><b>" +
rp(a.nilai_perolehan) +
"</b><span>Residu</span><b>" +
rp(a.nilai_residu) +
"</b><span>Akumulasi</span><b>" +
rp(a.akumulasi_penyusutan) +
"</b><span>Nilai buku</span><b>" +
rp(a.nilai_buku) +
"</b><span>Pembuat</span><b>" +
esc(a.creator_name || "-") +
"</b><span>Penyetuju</span><b>" +
esc(a.approver_name || "-") +
'</b></div></section><section class="detail-panel"><h6>Dokumen Pendukung</h6>' +
attachments(a.attachments) +
'</section><section class="detail-panel"><h6>Riwayat Mutasi & Jurnal</h6><div class="timeline">' +
ev +
'</div></section><section class="detail-panel"><h6>Catatan</h6><p>' +
esc(a.keterangan || "-") +
"</p><h6>Jadwal Penyusutan</h6><p>" +
(a.schedule || []).length +
" periode tercatat · " +
(a.maintenance || []).length +
" maintenance</p></section></div>"
);
}
function detailActions(a) {
var out =
'<button class="btn btn-light" data-bs-dismiss="modal">Tutup</button>';
if (P.export)
out +=
'<button class="btn btn-outline-danger btn-pdf" data-url="' +
B +
"fixedassets/asset_pdf/" +
a.id +
'">PDF Kartu Aset</button><button class="btn btn-outline-dark" onclick="window.open(\'' +
B +
"fixedassets/qr/" +
a.id +
"','_blank')\">QR</button>";
if (P.update && a.workflow_status === "draft")
out +=
'<button class="btn btn-outline-primary edit-asset">Edit Draft</button>';
if (P.create && a.workflow_status === "draft")
out +=
'<button class="btn btn-primary action-post" data-action="submit" data-id="' +
a.id +
'">Ajukan</button>';
if (P.approve && a.workflow_status === "submitted")
out +=
'<button class="btn btn-outline-danger action-post" data-action="reject" data-id="' +
a.id +
'">Tolak</button><button class="btn btn-success action-post" data-action="approve" data-id="' +
a.id +
'">Setujui</button>';
if (P.post && a.workflow_status === "approved")
out +=
'<button class="btn btn-success action-post" data-action="capitalize" data-id="' +
a.id +
'">Kapitalisasi</button>';
if (
P.post &&
a.workflow_status === "capitalized" &&
["active", "fully_depreciated"].indexOf(a.lifecycle_status) >= 0
)
out +=
'<button class="btn btn-primary asset-process" data-kind="depreciate" data-id="' +
a.id +
'">Penyusutan</button><button class="btn btn-warning asset-process" data-kind="mutate" data-id="' +
a.id +
'">Mutasi Nilai</button>';
if (P.update && a.workflow_status === "capitalized")
out +=
'<button class="btn btn-outline-primary asset-process" data-kind="transfer" data-id="' +
a.id +
'">Transfer</button><button class="btn btn-outline-secondary asset-process" data-kind="maintenance" data-id="' +
a.id +
'">Maintenance</button>';
return out;
}
function openDetail(id) {
request(B + "fixedassets/detail/" + id)
.then(function (r) {
currentAsset = r.asset;
$("#assetDetailBody").html(detailHtml(r.asset));
$("#assetDetailActions").html(detailActions(r.asset));
bootstrap.Modal.getOrCreateInstance(
document.getElementById("assetDetailModal"),
).show();
})
.catch(fail);
}
function editAsset() {
var a = currentAsset;
if (!a) return;
resetForm();
var f = document.getElementById("assetForm"),
names = [
"id",
"idempotency_key",
"nama_asset",
"tanggal_perolehan",
"capitalization_date",
"category_id",
"lokasi_asset_id",
"source_reference_no",
"qty",
"nilai_perolehan",
"nilai_residu",
"masa_manfaat",
"depreciation_method",
"asset_account_id",
"accumulated_depreciation_account_id",
"depreciation_expense_account_id",
"source_journal_mode",
"account_kredit_id",
"source_journal_id",
"item_id",
"barcode_id",
"warehouse_id",
"bin_id",
"keterangan",
];
names.forEach(function (n) {
if (f.elements[n] && a[n] != null) $(f.elements[n]).val(a[n]);
});
$('[name=source_type][value="' + a.source_type + '"]').prop(
"checked",
true,
);
if (a.responsible_employee_id)
$("#employeeLookup")
.append(
new Option(
a.responsible_name || "Karyawan",
a.responsible_employee_id,
true,
true,
),
)
.trigger("change");
if (a.source_journal_id)
$("#journalLookup")
.append(
new Option(
a.source_journal_no || "Jurnal sumber",
a.source_journal_id,
true,
true,
),
)
.trigger("change");
if (a.barcode_id)
$("#inventoryLookup")
.append(
new Option(
(a.kode_detail || "Barang") + " - " + (a.barcode || ""),
a.barcode_id,
true,
true,
),
)
.trigger("change");
toggleSource();
toggleJournal();
bootstrap.Modal.getInstance(
document.getElementById("assetDetailModal"),
).hide();
bootstrap.Modal.getOrCreateInstance(
document.getElementById("assetFormModal"),
).show();
}
function postAction(action, id, extra) {
var labels = {
submit: "Ajukan dokumen aset?",
approve: "Setujui dokumen aset?",
capitalize: "Kapitalisasi aset dan posting jurnal?",
};
if (action === "reject")
return AppAlert.prompt({
title: "Tolak dokumen aset",
input: "textarea",
inputLabel: "Alasan penolakan wajib diisi",
inputValidator: function (v) {
if (!v) return "Alasan wajib diisi.";
},
}).then(function (x) {
if (x.isConfirmed) doPost(action, id, { reason: x.value });
});
AppAlert.confirm({
title: labels[action] || "Lanjutkan proses?",
text: "Tindakan akan dicatat pada audit trail.",
}).then(function (x) {
if (x.isConfirmed) doPost(action, id, extra || {});
});
}
function doPost(action, id, obj) {
obj = obj || {};
var uploads = obj._files || [];
delete obj._files;
request(B + "fixedassets/" + action + "/" + id, {
method: "POST",
body: fd(obj),
})
.then(function (r) {
if (!uploads.length) return r;
return Promise.all(
uploads.map(function (file) {
return uploadEntity("asset_maintenance", r.result, file);
}),
).then(function () {
return r;
});
})
.then(function (r) {
toast(r.message);
var modal = bootstrap.Modal.getInstance(
document.getElementById("assetDetailModal"),
);
if (modal) modal.hide();
reload();
})
.catch(fail);
}
function process(kind, id) {
var today = new Date().toISOString().slice(0, 10),
html = "",
title = "";
if (kind === "depreciate") {
title = "Proses Penyusutan";
html =
'<input id="sw-date" type="date" class="swal2-input" value="' +
today +
'">';
}
if (kind === "transfer") {
title = "Transfer Lokasi";
html =
'<input id="sw-date" type="date" class="swal2-input" value="' +
today +
'"><select id="sw-location" class="swal2-select"><option value="">Pilih lokasi</option>' +
C.locations
.filter(function (x) {
return Number(x.is_active);
})
.map(function (x) {
return (
'<option value="' +
x.id +
'">' +
esc(x.code + " - " + x.nama) +
"</option>"
);
})
.join("") +
'</select><textarea id="sw-notes" class="swal2-textarea" placeholder="Alasan transfer"></textarea>';
}
if (kind === "maintenance") {
title = "Catat Maintenance";
html =
'<input id="sw-date" type="date" class="swal2-input" value="' +
today +
'"><input id="sw-vendor" class="swal2-input" placeholder="Vendor"><textarea id="sw-description" class="swal2-textarea" placeholder="Pekerjaan maintenance"></textarea><input id="sw-cost" class="swal2-input" inputmode="decimal" placeholder="Biaya"><input id="sw-next" type="date" class="swal2-input"><label class="d-block text-start mx-4 mt-2">Bukti maintenance (gambar/PDF)</label><input id="sw-file" type="file" class="swal2-file" accept="image/*,.pdf" multiple>';
}
if (kind === "mutate") {
title = "Mutasi Bernilai";
html =
'<select id="sw-type" class="swal2-select"><option value="addition">Penambahan Nilai</option><option value="impairment">Penurunan Nilai</option><option value="revaluation">Revaluasi</option><option value="disposal">Penjualan/Pelepasan</option><option value="lost">Hilang</option><option value="damaged">Rusak</option></select><input id="sw-date" type="date" class="swal2-input" value="' +
today +
'"><input id="sw-amount" class="swal2-input" inputmode="decimal" placeholder="Nilai/proceeds"><select id="sw-account" class="swal2-select"><option value="">Pilih akun lawan</option>' +
C.accounts
.map(function (x) {
return (
'<option value="' +
x.id +
'">' +
esc(x.kode_akun + " - " + x.nama_akun) +
"</option>"
);
})
.join("") +
'</select><textarea id="sw-notes" class="swal2-textarea" placeholder="Alasan/rincian wajib"></textarea>';
}
AppAlert.prompt({
title: title,
html: html,
preConfirm: function () {
var o = { date: $("#sw-date").val() };
if (kind === "depreciate") o.through_date = o.date;
if (kind === "transfer") {
o.location_id = $("#sw-location").val();
o.notes = $("#sw-notes").val();
}
if (kind === "maintenance") {
o.vendor = $("#sw-vendor").val();
o.description = $("#sw-description").val();
o.cost = $("#sw-cost").val();
o.next_due_date = $("#sw-next").val();
o.idempotency_key = "MNT-" + Date.now();
o._files = Array.from(($("#sw-file")[0] || {}).files || []);
}
if (kind === "mutate") {
o.type = $("#sw-type").val();
o.amount = $("#sw-amount").val();
o.account_id = $("#sw-account").val();
o.notes = $("#sw-notes").val();
}
return o;
},
}).then(function (x) {
if (x.isConfirmed) doPost(kind, id, x.value);
});
}
function pdf(url) {
$("#pdfModal iframe").attr("src", url);
bootstrap.Modal.getOrCreateInstance(
document.getElementById("pdfModal"),
).show();
}
function master(type, row) {
row = row || {};
var account =
'<option value="">Pilih akun</option>' +
C.accounts
.map(function (a) {
return (
'<option value="' +
a.id +
'">' +
esc(a.kode_akun + " - " + a.nama_akun) +
"</option>"
);
})
.join("");
var h =
'<input type="hidden" name="id" value="' +
esc(row.id || "") +
'"><input type="hidden" name="master_type" value="' +
type +
'"><div class="row g-3"><div class="col-md-4"><label class="form-label">Kode</label><input class="form-control" name="code" value="' +
esc(row.code || "") +
'" required></div><div class="col-md-8"><label class="form-label">Nama</label><input class="form-control" name="name" value="' +
esc(row.name || row.nama || "") +
'" required></div>';
if (type === "category")
h +=
'<div class="col-md-4"><label class="form-label">Masa manfaat (bulan)</label><input type="number" class="form-control" name="life" value="' +
esc(row.default_useful_life_months || 48) +
'"></div><div class="col-md-4"><label class="form-label">Nilai residu (%)</label><input class="form-control" name="residual_percent" value="' +
esc(row.default_residual_percent || 0) +
'"></div><div class="col-md-4"><label class="form-label">Metode</label><select class="form-select" name="method"><option value="straight_line">Garis lurus</option><option value="declining_balance">Saldo menurun</option></select></div><div class="col-md-4"><label class="form-label">Akun aset</label><select class="form-select" name="asset_account_id">' +
account +
'</select></div><div class="col-md-4"><label class="form-label">Akumulasi</label><select class="form-select" name="accumulated_account_id">' +
account +
'</select></div><div class="col-md-4"><label class="form-label">Beban penyusutan</label><select class="form-select" name="expense_account_id">' +
account +
"</select></div>";
else
h +=
'<div class="col-12"><label class="form-label">Alamat/keterangan lokasi</label><textarea class="form-control" name="address">' +
esc(row.address || "") +
"</textarea></div>";
h +=
'<div class="col-12"><div class="form-check"><input type="checkbox" class="form-check-input" name="is_active" value="1" ' +
(row.id && !Number(row.is_active) ? "" : "checked") +
'><label class="form-check-label">Aktif</label></div></div></div>';
$("#masterFields").html(h);
if (type === "category") {
$("[name=method]").val(row.depreciation_method || "straight_line");
$("[name=asset_account_id]").val(row.asset_account_id || "");
$("[name=accumulated_account_id]").val(
row.accumulated_depreciation_account_id || "",
);
$("[name=expense_account_id]").val(
row.depreciation_expense_account_id || "",
);
}
bootstrap.Modal.getOrCreateInstance(
document.getElementById("masterModal"),
).show();
}
function saveMaster(e) {
e.preventDefault();
var form = e.currentTarget,
type = form.elements.master_type.value;
request(B + "fixedassets/save_" + type, {
method: "POST",
body: new FormData(form),
})
.then(function (r) {
toast(r.message);
location.reload();
})
.catch(fail);
}
function newOpname() {
var opts = C.locations
.filter(function (x) {
return Number(x.is_active);
})
.map(function (x) {
return (
'<option value="' +
x.id +
'">' +
esc(x.code + " - " + x.nama) +
"</option>"
);
})
.join("");
AppAlert.prompt({
title: "Buat Asset Opname",
html:
'<select id="op-location" class="swal2-select"><option value="">Pilih lokasi</option>' +
opts +
'</select><input id="op-date" type="date" class="swal2-input" value="' +
new Date().toISOString().slice(0, 10) +
'"><textarea id="op-notes" class="swal2-textarea" placeholder="Ruang lingkup/catatan"></textarea>',
preConfirm: function () {
return {
location_id: $("#op-location").val(),
date: $("#op-date").val(),
notes: $("#op-notes").val(),
idempotency_key: "AOP-" + Date.now(),
};
},
}).then(function (x) {
if (x.isConfirmed)
request(B + "fixedassets/opname_create", {
method: "POST",
body: fd(x.value),
})
.then(function (r) {
toast(r.message);
location.reload();
})
.catch(fail);
});
}
function openOpname(id) {
request(B + "fixedassets/opname_detail/" + id)
.then(function (r) {
var o = r.opname,
lines = (o.lines || [])
.map(function (l) {
return (
'<div class="opname-line-grid" data-line="' +
l.id +
'"><b>' +
esc(l.kode_asset) +
"<br><small>" +
esc(l.nama_asset) +
'</small></b><label><input type="checkbox" class="found" ' +
(Number(l.is_found) ? "checked" : "") +
'> Ditemukan</label><select class="form-select actual-location">' +
C.locations
.map(function (x) {
return (
'<option value="' +
x.id +
'" ' +
(x.id == l.actual_location_id ? "selected" : "") +
">" +
esc(x.code + " - " + x.nama) +
"</option>"
);
})
.join("") +
'</select><select class="form-select actual-status"><option value="active">Baik/Aktif</option><option value="fully_depreciated">Tersusut penuh</option><option value="lost">Hilang</option><option value="damaged">Rusak</option></select><input class="form-control notes" value="' +
esc(l.notes || "") +
'" placeholder="Catatan hasil cek"></div>'
);
})
.join("");
$("#opnameBody").html(
'<div class="detail-kv mb-3"><span>Dokumen</span><b>' +
esc(o.opname_no) +
"</b><span>Lokasi/Tanggal</span><b>" +
esc(o.location_name) +
" / " +
esc(o.opname_date) +
"</b><span>Status</span><div>" +
status(o.status) +
'</div></div><div class="alert alert-info">Hasil hilang/rusak tidak langsung membuat jurnal. Setelah opname diposting, proses mutasi Hilang/Rusak pada aset terkait agar nilai buku dan jurnal tetap terkontrol.</div>' +
lines,
);
$(".opname-line-grid").each(function (i) {
$(this).find(".actual-status").val(o.lines[i].actual_status);
});
var a =
'<button class="btn btn-light" data-bs-dismiss="modal">Tutup</button>';
if (P.update && o.status === "draft")
a +=
'<button class="btn btn-primary save-opname" data-id="' +
o.id +
'">Simpan Hasil</button>';
if (P.create && o.status === "draft")
a +=
'<button class="btn btn-success state-opname" data-action="submit" data-id="' +
o.id +
'">Ajukan</button>';
if (P.approve && o.status === "submitted")
a +=
'<button class="btn btn-danger state-opname" data-action="reject" data-id="' +
o.id +
'">Tolak</button><button class="btn btn-success state-opname" data-action="approve" data-id="' +
o.id +
'">Setujui</button>';
if (P.post && o.status === "approved")
a +=
'<button class="btn btn-success state-opname" data-action="post" data-id="' +
o.id +
'">Posting</button>';
$("#opnameActions").html(a);
bootstrap.Modal.getOrCreateInstance(
document.getElementById("opnameModal"),
).show();
})
.catch(fail);
}
function reportUrl(kind) {
var p = new URLSearchParams({
type: $("#reportType").val(),
date_from: $("#reportFrom").val(),
date_to: $("#reportTo").val(),
});
return B + "fixedassets/" + kind + "?" + p.toString();
}
function runReport() {
request(reportUrl("report_data"))
.then(function (r) {
var data = r.data || [];
if (!data.length) {
$("#reportResult").html(
'<div class="empty-state">Tidak ada data.</div>',
);
return;
}
var keys = Object.keys(data[0]).filter(function (k) {
return typeof data[0][k] !== "object";
}),
h =
'<table class="table table-sm"><thead><tr>' +
keys
.map(function (k) {
return "<th>" + esc(k.replaceAll("_", " ")) + "</th>";
})
.join("") +
"</tr></thead><tbody>";
data.forEach(function (x) {
h +=
"<tr>" +
keys
.map(function (k) {
return "<td>" + esc(x[k]) + "</td>";
})
.join("") +
"</tr>";
});
$("#reportResult").html(h + "</tbody></table>");
})
.catch(fail);
}
$(function () {
initTable();
initSelects();
$("#btnNewAsset").on("click", function () {
resetForm();
bootstrap.Modal.getOrCreateInstance(
document.getElementById("assetFormModal"),
).show();
});
$("[name=source_type]").on("change", toggleSource);
$("[name=source_journal_mode]").on("change", toggleJournal);
$("[name=category_id]").on("change", function () {
var o = this.options[this.selectedIndex];
$("[name=masa_manfaat]").val(o.dataset.life || "");
$("[name=depreciation_method]").val(o.dataset.method || "straight_line");
$("[name=asset_account_id]").val(o.dataset.asset || "");
$("[name=accumulated_depreciation_account_id]").val(
o.dataset.accum || "",
);
$("[name=depreciation_expense_account_id]").val(o.dataset.expense || "");
});
$("#assetFileInput").on("change", function () {
Array.from(this.files).forEach(function (f) {
if (files.length < 10) files.push(f);
});
this.value = "";
renderFiles();
});
$("#assetForm").on("submit", saveAsset);
$(document)
.on("click", ".btn-detail", function () {
openDetail(this.dataset.id);
})
.on("click", ".btn-pdf", function () {
pdf(this.dataset.url);
})
.on("click", ".preview-file", function () {
pdf(this.dataset.url);
})
.on("click", ".action-post", function () {
postAction(this.dataset.action, this.dataset.id);
})
.on("click", ".asset-process", function () {
process(this.dataset.kind, this.dataset.id);
})
.on("click", "[data-master]", function () {
master(this.dataset.master);
})
.on("click", ".edit-category", function () {
master("category", JSON.parse(this.dataset.row));
})
.on("click", ".edit-location", function () {
master("location", JSON.parse(this.dataset.row));
})
.on("click", ".btn-opname", function () {
openOpname(this.dataset.id);
});
$("#masterForm").on("submit", saveMaster);
$("#btnNewOpname").on("click", newOpname);
$("#btnRunReport").on("click", runReport);
$("#btnReportPdf").on("click", function () {
pdf(reportUrl("report_pdf"));
});
$("#btnReportExcel").on("click", function () {
location.href = reportUrl("report_excel");
});
$(document)
.on("click", ".save-opname", function () {
var id = this.dataset.id,
lines = [];
$(".opname-line-grid").each(function () {
lines.push({
id: this.dataset.line,
is_found: $(this).find(".found").prop("checked") ? 1 : 0,
actual_location_id: $(this).find(".actual-location").val(),
actual_status: $(this).find(".actual-status").val(),
notes: $(this).find(".notes").val(),
});
});
request(B + "fixedassets/opname_save/" + id, {
method: "POST",
body: fd({ lines: JSON.stringify(lines) }),
})
.then(function (r) {
toast(r.message);
openOpname(id);
})
.catch(fail);
})
.on("click", ".state-opname", function () {
var id = this.dataset.id,
action = this.dataset.action;
AppAlert.confirm({
title: "Proses " + action + " opname?",
text: "Status dan audit trail akan diperbarui.",
}).then(function (x) {
if (x.isConfirmed)
request(B + "fixedassets/opname_state/" + id, {
method: "POST",
body: fd({ action: action }),
})
.then(function (r) {
toast(r.message);
location.reload();
})
.catch(fail);
});
});
});
var currentOpnameId = 0,
assetScanner = null;
var assetSwalLabels = {
"sw-date": "Tanggal proses",
"sw-location": "Lokasi tujuan",
"sw-notes": "Alasan / keterangan",
"sw-vendor": "Vendor maintenance",
"sw-description": "Pekerjaan maintenance",
"sw-cost": "Biaya maintenance",
"sw-next": "Jadwal maintenance berikutnya",
"sw-file": "Bukti maintenance",
"sw-type": "Jenis mutasi",
"sw-amount": "Nilai transaksi / hasil pelepasan",
"sw-account": "Akun lawan",
"op-location": "Lokasi yang akan diperiksa",
"op-date": "Tanggal asset opname",
"op-notes": "Ruang lingkup / catatan",
"pic-select": "Penanggung jawab baru",
"pic-date": "Tanggal efektif",
"pic-notes": "Alasan pergantian",
"rev-date": "Tanggal reversal",
"rev-reason": "Alasan reversal",
};
function styleAssetSwal(root) {
Object.keys(assetSwalLabels).forEach(function (id) {
var input = (root || document).querySelector("#" + id);
if (
!input ||
(input.previousElementSibling &&
input.previousElementSibling.classList.contains("asset-swal-label"))
)
return;
var label = document.createElement("label");
label.className = "asset-swal-label";
label.htmlFor = id;
label.textContent = assetSwalLabels[id];
input.parentNode.insertBefore(label, input);
});
}
new MutationObserver(function (records) {
records.forEach(function (record) {
Array.from(record.addedNodes || []).forEach(function (node) {
if (node.nodeType === 1) styleAssetSwal(node);
});
});
}).observe(document.body, { childList: true, subtree: true });
var baseDetailActions = detailActions;
detailActions = function (a) {
var out = baseDetailActions(a);
if (
P.update &&
a.workflow_status === "capitalized" &&
["active", "fully_depreciated"].indexOf(a.lifecycle_status) >= 0
)
out +=
'<button class="btn btn-outline-secondary change-pic" data-id="' +
a.id +
'">Ganti PIC</button>';
var latest = (a.events || [])[0];
if (
P.post &&
latest &&
latest.event_type !== "acquisition" &&
latest.event_type !== "reversal"
)
out +=
'<button class="btn btn-outline-danger reverse-event" data-id="' +
latest.id +
'">Reversal Event Terakhir</button>';
return out;
};
$(document).on("click", ".change-pic", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var id = this.dataset.id,
today = new Date().toISOString().slice(0, 10);
AppAlert.prompt({
title: "Ganti Penanggung Jawab",
html:
'<select id="pic-select" style="width:100%"></select><input id="pic-date" type="date" class="swal2-input" value="' +
today +
'"><textarea id="pic-notes" class="swal2-textarea" placeholder="Alasan pergantian PIC"></textarea>',
didOpen: function () {
$("#pic-select").select2({
dropdownParent: $(".swal2-container"),
placeholder: "Cari karyawan",
ajax: {
url: B + "fixedassets/lookup_employees",
delay: 250,
data: function (p) {
return { q: p.term || "", page: p.page || 1 };
},
processResults: function (r) {
return {
results: r.results || [],
pagination: r.pagination || { more: false },
};
},
},
});
},
preConfirm: function () {
return {
employee_id: $("#pic-select").val(),
date: $("#pic-date").val(),
notes: $("#pic-notes").val(),
};
},
}).then(function (x) {
if (x.isConfirmed) doPost("responsibility", id, x.value);
});
});
$(document).on("click", ".reverse-event", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var id = this.dataset.id,
today = new Date().toISOString().slice(0, 10);
AppAlert.prompt({
title: "Reversal Event Aset",
html:
'<input id="rev-date" type="date" class="swal2-input" value="' +
today +
'"><textarea id="rev-reason" class="swal2-textarea" placeholder="Alasan reversal wajib dan harus jelas"></textarea>',
preConfirm: function () {
var reason = $("#rev-reason").val();
if (!reason) {
Swal.showValidationMessage("Alasan reversal wajib diisi.");
return false;
}
return { date: $("#rev-date").val(), reason: reason };
},
}).then(function (x) {
if (x.isConfirmed) doPost("reverse", id, x.value);
});
});
$(document).on("click", ".btn-opname", function () {
currentOpnameId = this.dataset.id || 0;
});
$("#opnameModal")
.on("shown.bs.modal", function () {
if (!currentOpnameId) return;
request(B + "fixedassets/opname_detail/" + currentOpnameId)
.then(function (r) {
var o = r.opname,
box = $(
'<div class="form-section mt-3" id="opnameProof"><h6>Pindai & Bukti Asset Opname</h6></div>',
);
if (o.status === "draft")
box.append(
'<div class="input-group mb-2"><input id="assetScanValue" class="form-control" placeholder="Pindai/ketik kode aset atau QR"><button class="btn btn-outline-primary apply-asset-scan">Tandai Ditemukan</button><button class="btn btn-outline-dark start-asset-camera"><i class="bi bi-camera"></i> Kamera</button></div><div id="assetQrReader" class="d-none mb-2"></div>',
);
box.append(attachments(o.attachments));
if (o.status === "draft" && P.create)
box.append(
'<label class="btn btn-outline-primary mt-2">Tambah gambar/PDF<input type="file" class="opname-upload" accept="image/*,.pdf" multiple hidden></label>',
);
$("#opnameProof").remove();
$("#opnameBody").append(box);
})
.catch(fail);
})
.on("hidden.bs.modal", function () {
if (assetScanner && assetScanner.clear)
assetScanner.clear().catch(function () {});
assetScanner = null;
});
function applyAssetScan(raw) {
var code = decodeURIComponent(
String(raw || "")
.trim()
.split("/")
.filter(Boolean)
.pop() || "",
).replace(/^ASSET:/i, ""),
found = false;
$(".opname-line-grid").each(function () {
var label = $(this).find("b").text().trim();
if (label.indexOf(code) === 0) {
$(this).find(".found").prop("checked", true);
this.scrollIntoView({ behavior: "smooth", block: "center" });
$(this).addClass("approval-focus");
found = true;
return false;
}
});
toast(
found
? "Aset " + code + " ditandai ditemukan."
: "Kode aset tidak ada pada dokumen opname.",
found ? "success" : "warning",
);
}
$(document)
.on("click", ".apply-asset-scan", function () {
applyAssetScan($("#assetScanValue").val());
})
.on("keydown", "#assetScanValue", function (e) {
if (e.key === "Enter") {
e.preventDefault();
applyAssetScan(this.value);
}
})
.on("click", ".start-asset-camera", function () {
if (typeof Html5QrcodeScanner === "undefined") {
toast(
"Library kamera QR belum tersedia. Gunakan scanner atau input kode.",
"warning",
);
return;
}
$("#assetQrReader").removeClass("d-none");
if (assetScanner) return;
assetScanner = new Html5QrcodeScanner(
"assetQrReader",
{ fps: 10, qrbox: { width: 220, height: 220 } },
false,
);
assetScanner.render(
function (text) {
$("#assetScanValue").val(text);
applyAssetScan(text);
},
function () {},
);
});
$(document).on("change", ".opname-upload", function () {
var input = this,
id = currentOpnameId,
list = Array.from(input.files || []);
list
.reduce(function (chain, file) {
return chain.then(function () {
return uploadEntity("asset_opname", id, file);
});
}, Promise.resolve())
.then(function () {
toast("Bukti opname berhasil diunggah.");
input.value = "";
$("#opnameModal").trigger("shown.bs.modal");
})
.catch(fail);
});
$(document).on("click", '.state-opname[data-action="reject"]', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var id = this.dataset.id;
AppAlert.prompt({
title: "Tolak Asset Opname",
input: "textarea",
inputLabel: "Alasan penolakan wajib diisi",
inputValidator: function (v) {
if (!v) return "Alasan wajib diisi.";
},
}).then(function (x) {
if (x.isConfirmed)
request(B + "fixedassets/opname_state/" + id, {
method: "POST",
body: fd({ action: "reject", reason: x.value }),
})
.then(function (r) {
toast(r.message);
location.reload();
})
.catch(fail);
});
});
$(document).on("click", ".edit-asset", editAsset);
})(jQuery);