From d8a65bc5a8746ef3d7380047536d7d91ffc8f4b4 Mon Sep 17 00:00:00 2001 From: Hanan Askarim Date: Tue, 14 Jul 2026 13:25:33 +0700 Subject: [PATCH] gabungkan fungsi get data dengan create jurnal --- .../scheduler/JurnalPiCicilanOrderAset.http | 116 +++- .../scheduler/JurnalPiCicilanOrderAset.php | 597 +++++++++++++----- 2 files changed, 518 insertions(+), 195 deletions(-) diff --git a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http index dfe2bf0..36b2e0f 100644 --- a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http +++ b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http @@ -1,42 +1,92 @@ -@host = https://accone.aplikasi.web.id/one-api/ +@host = https://accone.aplikasi.web.id/one-api +@date = 2026-07-14 +@coaBayarID = 1110100001 +@coaHutangID = 2110100030 -### Create jurnal PI cicilan order aset -POST {{host}}/mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal -Content-Type: application/json +# ============================================================================= +# JurnalPiCicilanOrderAset — Contoh Pemanggilan Endpoint +# ============================================================================= +# Urutan penggunaan yang disarankan: +# 1. ListEligiblePurchaseOrderAssetContracts → lihat kontrak yang akan diproses +# 2. GenerateJournalsFromContracts (dryRun=Y) → simulasi tanpa simpan ke DB +# 3. GenerateJournalsFromContracts (dryRun=N) → generate jurnal sesungguhnya +# 4. CreateJournal → buat jurnal manual 1 per 1 +# ============================================================================= -{ - "date": "2026-07-14", - "branchCode": "BA", - "regionalID": 8, - "branchCompanyID": 1, - "jurnalTypeID": 11, - "title": "Jurnal PI Cicilan Order Aset", - "description": "Pembuatan jurnal PI cicilan order aset", - "userID": 0, - "details": [ - { - "coaID": 2110100030, - "description": "Hutang cicilan", - "debit": 15000000, - "credit": 0, - "addonCode": "PICICIL", - "addonValue": "PI-INS-202607140001", - "mItemID": 123 - }, - { - "coaID": 1110100001, - "description": "Cicilan", - "debit": 0, - "credit": 15000000 - } - ] -} -### List eligible purchase order asset contracts -POST {{host}}/mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts +### 1. Lihat kontrak aset yang eligible untuk dibuatkan jurnal bulan ini +GET {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts Content-Type: application/json { "startDate": "2026-07-01", "endDate": "2026-07-31" } + + +### 2. Simulasi generate jurnal (dryRun=Y) — tidak menyimpan ke database +POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts +Content-Type: application/json + +{ + "date" : "{{date}}", + "dryRun" : "Y" +} + + +### 3. Generate jurnal untuk semua kontrak eligible bulan ini (sesungguhnya) +POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts +Content-Type: application/json + +{ + "date" : "{{date}}", + "coaBayarID" : {{coaBayarID}}, + "dryRun" : "N" +} + + +### 4. Generate jurnal untuk 1 kontrak tertentu saja (contractID=99) +POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts +Content-Type: application/json + +{ + "date" : "{{date}}", + "coaBayarID" : {{coaBayarID}}, + "dryRun" : "N", + "contractID" : 99 +} + + +### 5. Buat jurnal manual (1 jurnal, input detail sendiri) +POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal +Content-Type: application/json + +{ + "date" : "{{date}}", + "branchCode" : "BA", + "regionalID" : 8, + "branchCompanyID": 1, + "jurnalTypeID" : 11, + "title" : "Jurnal PI Cicilan Order Aset", + "description" : "Cicilan ke-1 dari 12 | Kontrak Kendaraan Operasional | Periode 2026-07", + "details" : [ + { + "coaID" : {{coaHutangID}}, + "description": "Hutang cicilan aset - Kendaraan Operasional", + "debit" : 15000000, + "credit" : 0, + "addonCode" : "PIINSTALMENT", + "addonValue" : "42", + "mItemID" : 123 + }, + { + "coaID" : {{coaBayarID}}, + "description": "Pembayaran cicilan aset - Kendaraan Operasional", + "debit" : 0, + "credit" : 15000000, + "addonCode" : "PIINSTALMENT", + "addonValue" : "42", + "mItemID" : 123 + } + ] +} diff --git a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php index eb3b4e8..0bda645 100644 --- a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php +++ b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php @@ -15,15 +15,15 @@ * 3. Insert baris transaksi ke tabel `jurnal_tx` (untuk setiap baris cicilan) * - Referensi ke jurnalID header * - COA (Chart of Account) dan deskripsinya: - * * Sisi hutang cicilan → nilai DEBIT - * * Sisi pembayaran cicilan → nilai KREDIT + * * Sisi hutang cicilan → nilai DEBIT (diambil dari HutangCoaID di fa_class) + * * Sisi pembayaran cicilan → nilai KREDIT (diambil dari ItemCoaID di fa_class) * 4. Insert data tambahan ke tabel `jurnal_addon` (jika ada addonValue atau mItemID) * - Referensi ke jurnalID dan jurnalTxID - * - Berisi: addonCode (default: "PICICIL"), addonValue, mItemID + * - Berisi: addonCode (default: "PIINSTALMENT"), addonValue, mItemID * * Endpoint: * POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal - * POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreatePiInstallmentJournal + * POST /mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts * GET /mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts */ class JurnalPiCicilanOrderAset extends MY_Controller @@ -74,7 +74,7 @@ class JurnalPiCicilanOrderAset extends MY_Controller * 10. Jika ada addonValue atau mItemID, INSERT ke `jurnal_addon`: * - jurnalAddOnJurnalID : referensi ke header jurnal * - jurnalAddOnJurnalTxID: referensi ke baris jurnal_tx - * - jurnalAddOnCode : kode addon (default: "PICICIL") + * - jurnalAddOnCode : kode addon (default: "PIINSTALMENT") * - jurnalAddOnValue : nilai tambahan (misal: nomor PI/kontrak) * - jurnalAddOnM_ItemID : ID item aset yang terkait * @@ -90,7 +90,7 @@ class JurnalPiCicilanOrderAset extends MY_Controller * - credit : Nilai kredit (pembayaran cicilan). * - addonValue : (opsional) Nilai addon, misal nomor PI. * - mItemID : (opsional) ID item aset. - * - addonCode : (opsional) Kode addon. Default: "PICICIL". + * - addonCode : (opsional) Kode addon. Default: "PIINSTALMENT". * - periodeID : (opsional) ID periode. Default: diambil dari tanggal. * - regionalID : (opsional) ID regional. Default: diambil dari branchCode. * - branchCompanyID: (opsional) ID branch company. Default: diambil dari branchCode. @@ -158,180 +158,253 @@ class JurnalPiCicilanOrderAset extends MY_Controller throw new Exception("jurnalTypeID / jurnalTypeCode tidak valid."); } - $userID = isset($para["userID"]) ? (int) $para["userID"] : 0; - $jurnalNo = $this->generateJournalNumber(); - if ($jurnalNo === false || $jurnalNo == "") { - throw new Exception("Gagal generate nomor jurnal."); - } + $userID = 0; - // Validasi balance: total debit harus sama dengan total credit - $totalDebit = 0; - $totalCredit = 0; - foreach ($details as $idx => $detail) { - $debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0; - $credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0; - if ($debit < 0 || $credit < 0) { - throw new Exception("Nilai debit/credit tidak boleh negatif pada detail ke-" . ($idx + 1)); - } - $totalDebit += $debit; - $totalCredit += $credit; - } - - if (round($totalDebit, 2) !== round($totalCredit, 2)) { - throw new Exception("Total debit dan credit harus sama. Debit={$totalDebit}, Credit={$totalCredit}"); - } - - $this->db->trans_begin(); - - // ----------------------------------------------------------------- - // INSERT header jurnal ke tabel `jurnal` - // ----------------------------------------------------------------- - $sqlHeader = "INSERT INTO jurnal ( - jurnalM_BranchCompanyID, - JurnalS_RegionalID, - jurnalM_BranchCode, - jurnalperiodeID, - jurnalNo, - jurnalTitle, - jurnalDescription, - jurnalDate, - jurnalJurnalTypeID, - jurnalCreated, - jurnalM_UserID - ) VALUES (?,?,?,?,?,?,?,?,?,NOW(),?)"; - - $qryHeader = $this->db->query($sqlHeader, [ - $branchCompanyID, - $regionalID, + $result = $this->insertJournal( + $date, $branchCode, - $periodeID, - $jurnalNo, $title, $description, - $date, + $details, + $periodeID, + $regionalID, + $branchCompanyID, $jurnalTypeID, $userID - ]); - if (!$qryHeader) { - $this->db->trans_rollback(); - $this->sys_error_db("Gagal insert jurnal header."); + ); + + if ($result === false) { exit; } - $jurnalID = $this->db->insert_id(); - $createdAddonCount = 0; - - // ----------------------------------------------------------------- - // INSERT baris transaksi ke tabel `jurnal_tx` (per baris cicilan) - // ----------------------------------------------------------------- - foreach ($details as $idx => $detail) { - $coaID = isset($detail["coaID"]) ? (int) $detail["coaID"] : 0; - $descriptionTx = isset($detail["description"]) ? trim($detail["description"]) : ""; - $debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0; - $credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0; - - if ($coaID <= 0) { - $this->db->trans_rollback(); - $this->sys_error_db("coaID wajib diisi pada detail ke-" . ($idx + 1)); - exit; - } - - // Sisi debit = hutang cicilan (COA hutang) - // Sisi kredit = pembayaran cicilan (COA kas/bank) - $sqlTx = "INSERT INTO jurnal_tx ( - jurnalTxJurnalID, - jurnalTxCoaID, - jurnalTxDescription, - jurnalTxDebit, - jurnalTxCredit, - jurnalTxCreated, - jurnalTxM_UserID - ) VALUES (?,?,?,?,?,NOW(),?)"; - - $qryTx = $this->db->query($sqlTx, [ - $jurnalID, - $coaID, - $descriptionTx, - $debit, - $credit, - $userID - ]); - if (!$qryTx) { - $this->db->trans_rollback(); - $this->sys_error_db("Gagal insert jurnal_tx pada detail ke-" . ($idx + 1)); - exit; - } - - $jurnalTxID = $this->db->insert_id(); - - // ----------------------------------------------------------------- - // INSERT data tambahan ke tabel `jurnal_addon` (jika ada) - // Diisi jika ada addonValue (misal nomor PI) atau mItemID (ID aset) - // ----------------------------------------------------------------- - $addonValue = isset($detail["addonValue"]) ? trim($detail["addonValue"]) : ""; - $mItemID = isset($detail["mItemID"]) ? (int) $detail["mItemID"] : 0; - $addonCode = isset($detail["addonCode"]) && $detail["addonCode"] != "" ? trim($detail["addonCode"]) : "PICICIL"; - - if ($addonValue != "" || $mItemID > 0) { - $sqlAddon = "INSERT INTO jurnal_addon ( - jurnalAddOnJurnalID, - jurnalAddOnJurnalTxID, - jurnalAddOnCode, - jurnalAddOnValue, - jurnalAddOnM_ItemID, - jurnalAddOnCreated, - jurnalAddOnCreatedUserID - ) VALUES (?,?,?,?,?,NOW(),?)"; - - $qryAddon = $this->db->query($sqlAddon, [ - $jurnalID, - $jurnalTxID, - $addonCode, - $addonValue, - $mItemID > 0 ? $mItemID : null, - $userID - ]); - if (!$qryAddon) { - $this->db->trans_rollback(); - $this->sys_error_db("Gagal insert jurnal_addon pada detail ke-" . ($idx + 1)); - exit; - } - $createdAddonCount++; - } - } - - if ($this->db->trans_status() === false) { - $this->db->trans_rollback(); - $this->sys_error_db("Transaksi jurnal gagal."); - exit; - } - - $this->db->trans_commit(); - - $this->sys_ok([ - "jurnalID" => $jurnalID, - "jurnalNo" => $jurnalNo, - "detailCount" => count($details), - "addonCount" => $createdAddonCount - ]); + $this->sys_ok($result); } catch (Exception $exc) { $this->sys_error($exc->getMessage()); } } /** - * CreatePiInstallmentJournal — Alias dari CreateJournal. + * GenerateJournalsFromContracts — Generate jurnal otomatis dari seluruh kontrak aset eligible. * - * Endpoint alternatif dengan nama yang lebih spesifik untuk konteks - * pembuatan jurnal PI cicilan order aset. Memanggil CreateJournal secara langsung. + * Fungsi ini mengintegrasikan data dari getEligiblePurchaseOrderAssetContracts() + * dengan proses pembuatan jurnal (insertJournal()) secara otomatis. * - * @see CreateJournal() + * Untuk setiap kontrak yang eligible, dibuat 1 jurnal dengan 2 baris transaksi: + * - Baris DEBIT : COA hutang cicilan (HutangCoaID dari fa_class) — mencatat hutang cicilan + * - Baris KREDIT : COA kas/bank/aset (ItemCoaID dari fa_class) — mencatat pembayaran cicilan + * Addon (jurnal_addon): menyimpan ID kontrak sebagai addonValue dan ItemID sebagai mItemID + * + * Parameter (POST JSON): + * - date : Tanggal jurnal (YYYY-MM-DD). Default: hari ini. + * - userID : ID user yang membuat jurnal. + * - dryRun : "Y" = simulasi tanpa simpan ke DB. Default: "N". + * - contractID : (opsional) Filter hanya 1 kontrak tertentu. + * + * Syarat kontrak dilewati (skipped): + * - branchCode kosong (data cabang belum lengkap) + * - COA hutang (HutangCoaID) tidak ditemukan di fa_class + * - COA bayar/aset (ItemCoaID) tidak ditemukan di fa_class + * + * @return JSON { tanggal, totalDibuat, totalDilewati, daftarDibuat[], daftarDilewati[] } */ - public function CreatePiInstallmentJournal() + public function GenerateJournalsFromContracts() { - $this->CreateJournal(); + try { + $para = $this->sys_input; + + $date = isset($para["date"]) && $para["date"] != "" ? $para["date"] : date("Y-m-d"); + if (!$this->isValidDate($date)) { + throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD."); + } + + $userID = 0; + $dryRun = isset($para["dryRun"]) && ($para["dryRun"] === true || $para["dryRun"] == "Y" || $para["dryRun"] == "1"); + $filterContractID = isset($para["contractID"]) && (int) $para["contractID"] > 0 + ? (int) $para["contractID"] + : null; + + $monthStart = date("Y-m-01", strtotime($date)); + $monthEnd = date("Y-m-t", strtotime($date)); + + // Ambil semua kontrak yang eligible untuk periode bulan ini + $contracts = $this->getEligiblePurchaseOrderAssetContracts($monthStart, $monthEnd); + + $dibuat = []; + $dilewati = []; + + foreach ($contracts as $kontrak) { + $contractID = (int) $kontrak["PurchaseOrderAssetContractID"]; + + // Filter 1 kontrak jika diminta + if ($filterContractID !== null && $contractID !== $filterContractID) { + continue; + } + + $branchCode = $kontrak["M_BranchCode"] ?? ""; + $regionalID = (int) ($kontrak["M_BranchS_RegionalID"] ?? 0); + $branchCompanyID = (int) ($kontrak["M_BranchCompanyID"] ?? 0); + $coaHutangID = (int) ($kontrak["HutangCoaID"] ?? 0); // COA debit dari fa_class (Fa_ClassHutangCoaID) + $coaBayarID = (int) ($kontrak["ItemCoaID"] ?? 0); // COA kredit dari fa_class (Fa_ClassCoaID) + $jumlah = (float) $kontrak["PurchaseOrderAssetContractInstallmentPayAmount"]; + $itemID = (int) ($kontrak["PurchaseOrderSummaryItemID"] ?? 0); + $namaKontrak = $kontrak["PurchaseOrderAssetContractName"] != "" + ? $kontrak["PurchaseOrderAssetContractName"] + : "Kontrak ID {$contractID}"; + $nomorPO = $kontrak["PurchaseOrderNumber"] ?? ""; + $cicilanKe = ((int) $kontrak["PurchaseOrderAssetContractInstallmentPaid"]) + 1; + $totalCicilan = (int) $kontrak["PurchaseOrderAssetContractInstallmentNumber"]; + $periode = date("Y-m", strtotime($date)); + + // Validasi: branchCode harus ada + if ($branchCode == "") { + $dilewati[] = [ + "kontrakID" => $contractID, + "alasan" => "Kode cabang (branchCode) tidak ditemukan. Pastikan warehouse PO sudah terhubung ke data cabang." + ]; + continue; + } + + // Validasi: COA hutang harus ada (untuk sisi DEBIT) + if ($coaHutangID <= 0) { + $dilewati[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "alasan" => "COA hutang tidak ditemukan. Pastikan item PO memiliki Fa_Class dengan Fa_ClassHutangCoaID." + ]; + continue; + } + + // Validasi: COA bayar/aset harus ada (untuk sisi KREDIT) + if ($coaBayarID <= 0) { + $dilewati[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "alasan" => "COA bayar/aset tidak ditemukan. Pastikan item PO memiliki Fa_Class dengan Fa_ClassCoaID." + ]; + continue; + } + + // Mode simulasi: catat tanpa simpan ke DB + if ($dryRun) { + $dibuat[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "namaKontrak" => $namaKontrak, + "jumlah" => $jumlah, + "coaHutangID" => $coaHutangID, + "coaBayarID" => $coaBayarID, + "keterangan" => "Mode simulasi (dryRun), data tidak disimpan." + ]; + continue; + } + + // Susun title dan description jurnal + $title = "PI Cicilan Aset - {$nomorPO}"; + $description = "Jurnal cicilan ke-{$cicilanKe} dari {$totalCicilan} | {$namaKontrak} | Periode {$periode}"; + + // Ambil periodeID dari tanggal + $periodeID = $this->getPeriodeIDByDate($date); + if ($periodeID <= 0) { + $dilewati[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "alasan" => "Periode jurnal tidak ditemukan untuk tanggal {$date}." + ]; + continue; + } + + // Ambil jurnalTypeID untuk tipe PAYMENT + $jurnalTypeID = $this->getJurnalTypeID("PAYMENT"); + if ($jurnalTypeID <= 0) { + $dilewati[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "alasan" => "Tipe jurnal PAYMENT tidak ditemukan di tabel jurnal_type." + ]; + continue; + } + + // Ambil description detail dari database fa_class dengan fallback jika kosong + $descHutang = isset($kontrak["HutangCoaDesc"]) && $kontrak["HutangCoaDesc"] != "" + ? $kontrak["HutangCoaDesc"] + : "Hutang cicilan aset - {$namaKontrak}"; + $descBayar = isset($kontrak["ItemCoaDesc"]) && $kontrak["ItemCoaDesc"] != "" + ? $kontrak["ItemCoaDesc"] + : "Pembayaran cicilan aset - {$namaKontrak}"; + + // Susun 2 baris detail jurnal: + // Baris 1 — DEBIT : hutang cicilan aset (COA dari fa_class) + // Baris 2 — KREDIT : pembayaran cicilan (COA kas/bank dari parameter) + $details = [ + [ + "coaID" => $coaHutangID, + "description" => $descHutang, + "debit" => $jumlah, + "credit" => 0, + "addonValue" => (string) $contractID, + "mItemID" => $itemID, + "addonCode" => "PIINSTALMENT" + ], + [ + "coaID" => $coaBayarID, + "description" => $descBayar, + "debit" => 0, + "credit" => $jumlah, + "addonValue" => (string) $contractID, + "mItemID" => $itemID, + "addonCode" => "PIINSTALMENT" + ] + ]; + + + // Panggil insertJournal() untuk simpan ke DB + $result = $this->insertJournal( + $date, + $branchCode, + $title, + $description, + $details, + $periodeID, + $regionalID, + $branchCompanyID, + $jurnalTypeID, + $userID + ); + + if ($result === false) { + $dilewati[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "alasan" => "Gagal menyimpan jurnal ke database." + ]; + continue; + } + + $dibuat[] = [ + "kontrakID" => $contractID, + "branchCode" => $branchCode, + "namaKontrak" => $namaKontrak, + "jurnalID" => $result["jurnalID"], + "jurnalNo" => $result["jurnalNo"], + "jumlah" => $jumlah + ]; + } + + $this->sys_ok([ + "tanggal" => $date, + "periodeAwal" => $monthStart, + "periodeAkhir" => $monthEnd, + "totalDibuat" => count($dibuat), + "totalDilewati" => count($dilewati), + "daftarDibuat" => $dibuat, + "daftarDilewati" => $dilewati + ]); + } catch (Exception $exc) { + $this->sys_error($exc->getMessage()); + } } + /** * ListEligiblePurchaseOrderAssetContracts — Menampilkan daftar kontrak aset * yang memenuhi syarat untuk dibuatkan jurnal PI cicilan pada periode tertentu. @@ -390,6 +463,195 @@ class JurnalPiCicilanOrderAset extends MY_Controller // FUNGSI PRIVATE / HELPER // ========================================================================= + /** + * insertJournal — Menyimpan 1 jurnal lengkap ke database (header + tx + addon). + * + * Fungsi internal yang dipanggil oleh CreateJournal() dan GenerateJournalsFromContracts(). + * Mengelola transaksi DB secara penuh (begin → commit/rollback). + * + * Alur: + * 1. Validasi balance debit == kredit + * 2. Generate nomor jurnal via fn_numbering('J') + * 3. INSERT header ke tabel `jurnal` + * 4. INSERT baris transaksi ke `jurnal_tx` (per detail) + * 5. INSERT addon ke `jurnal_addon` (jika addonValue atau mItemID ada) + * + * @param string $date Tanggal jurnal (YYYY-MM-DD) + * @param string $branchCode Kode cabang + * @param string $title Judul jurnal + * @param string $description Keterangan jurnal + * @param array $details Baris transaksi [ coaID, description, debit, credit, addonValue?, mItemID?, addonCode? ] + * @param int $periodeID ID periode akuntansi + * @param int $regionalID ID regional + * @param int $branchCompanyID ID branch company + * @param int $jurnalTypeID ID tipe jurnal + * @param int $userID ID user pembuat jurnal + * + * @return array|false Array { jurnalID, jurnalNo, detailCount, addonCount }, atau false jika gagal + */ + private function insertJournal($date, $branchCode, $title, $description, $details, $periodeID, $regionalID, $branchCompanyID, $jurnalTypeID, $userID) + { + // Validasi balance sebelum menyimpan + $totalDebit = 0; + $totalCredit = 0; + foreach ($details as $idx => $detail) { + $debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0; + $credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0; + if ($debit < 0 || $credit < 0) { + $this->sys_error("Nilai debit/credit tidak boleh negatif pada detail ke-" . ($idx + 1)); + return false; + } + $totalDebit += $debit; + $totalCredit += $credit; + } + + if (round($totalDebit, 2) !== round($totalCredit, 2)) { + $this->sys_error("Total debit dan credit harus sama. Debit={$totalDebit}, Credit={$totalCredit}"); + return false; + } + + $jurnalNo = $this->generateJournalNumber(); + if ($jurnalNo === false || $jurnalNo == "") { + $this->sys_error("Gagal generate nomor jurnal."); + return false; + } + + $this->db->trans_begin(); + + // ----------------------------------------------------------------- + // INSERT header jurnal ke tabel `jurnal` + // ----------------------------------------------------------------- + $sqlHeader = "INSERT INTO jurnal ( + jurnalM_BranchCompanyID, + JurnalS_RegionalID, + jurnalM_BranchCode, + jurnalperiodeID, + jurnalNo, + jurnalTitle, + jurnalDescription, + jurnalDate, + jurnalJurnalTypeID, + jurnalCreated, + jurnalM_UserID + ) VALUES (?,?,?,?,?,?,?,?,?,NOW(),?)"; + + $qryHeader = $this->db->query($sqlHeader, [ + $branchCompanyID, + $regionalID, + $branchCode, + $periodeID, + $jurnalNo, + $title, + $description, + $date, + $jurnalTypeID, + $userID + ]); + if (!$qryHeader) { + $this->db->trans_rollback(); + $this->sys_error_db("Gagal insert jurnal header."); + return false; + } + + $jurnalID = $this->db->insert_id(); + $createdAddonCount = 0; + + // ----------------------------------------------------------------- + // INSERT baris transaksi ke tabel `jurnal_tx` (per baris cicilan) + // ----------------------------------------------------------------- + foreach ($details as $idx => $detail) { + $coaID = isset($detail["coaID"]) ? (int) $detail["coaID"] : 0; + $descriptionTx = isset($detail["description"]) ? trim($detail["description"]) : ""; + $debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0; + $credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0; + + if ($coaID <= 0) { + $this->db->trans_rollback(); + $this->sys_error_db("coaID wajib diisi pada detail ke-" . ($idx + 1)); + return false; + } + + // Sisi debit = hutang cicilan (COA hutang aset dari fa_class) + // Sisi kredit = pembayaran cicilan (COA kas/bank) + $sqlTx = "INSERT INTO jurnal_tx ( + jurnalTxJurnalID, + jurnalTxCoaID, + jurnalTxDescription, + jurnalTxDebit, + jurnalTxCredit, + jurnalTxCreated, + jurnalTxM_UserID + ) VALUES (?,?,?,?,?,NOW(),?)"; + + $qryTx = $this->db->query($sqlTx, [ + $jurnalID, + $coaID, + $descriptionTx, + $debit, + $credit, + $userID + ]); + if (!$qryTx) { + $this->db->trans_rollback(); + $this->sys_error_db("Gagal insert jurnal_tx pada detail ke-" . ($idx + 1)); + return false; + } + + $jurnalTxID = $this->db->insert_id(); + + // ----------------------------------------------------------------- + // INSERT addon ke `jurnal_addon` (jika addonValue atau mItemID ada) + // addonValue = ID kontrak aset (untuk tracing) + // mItemID = ID item aset dari purchase_order_summary + // ----------------------------------------------------------------- + $addonValue = isset($detail["addonValue"]) ? trim($detail["addonValue"]) : ""; + $mItemID = isset($detail["mItemID"]) ? (int) $detail["mItemID"] : 0; + $addonCode = isset($detail["addonCode"]) && $detail["addonCode"] != "" ? trim($detail["addonCode"]) : "PIINSTALMENT"; + + if ($addonValue != "" || $mItemID > 0) { + $sqlAddon = "INSERT INTO jurnal_addon ( + jurnalAddOnJurnalID, + jurnalAddOnJurnalTxID, + jurnalAddOnCode, + jurnalAddOnValue, + jurnalAddOnM_ItemID, + jurnalAddOnCreated, + jurnalAddOnCreatedUserID + ) VALUES (?,?,?,?,?,NOW(),?)"; + + $qryAddon = $this->db->query($sqlAddon, [ + $jurnalID, + $jurnalTxID, + $addonCode, + $addonValue, + $mItemID > 0 ? $mItemID : null, + $userID + ]); + if (!$qryAddon) { + $this->db->trans_rollback(); + $this->sys_error_db("Gagal insert jurnal_addon pada detail ke-" . ($idx + 1)); + return false; + } + $createdAddonCount++; + } + } + + if ($this->db->trans_status() === false) { + $this->db->trans_rollback(); + $this->sys_error_db("Transaksi jurnal gagal."); + return false; + } + + $this->db->trans_commit(); + + return [ + "jurnalID" => $jurnalID, + "jurnalNo" => $jurnalNo, + "detailCount" => count($details), + "addonCount" => $createdAddonCount + ]; + } + /** * generateJournalNumber — Generate nomor jurnal otomatis. * @@ -553,9 +815,12 @@ class JurnalPiCicilanOrderAset extends MY_Controller mi.M_ItemFa_ClassID, fc.Fa_ClassID AS ItemFaClassID, fc.Fa_ClassName AS ItemFaClassName, - fc.Fa_ClassCoaID AS ItemCoaID, - fc.Fa_ClassCoaAccountNo AS ItemCoaAccountNo, - fc.Fa_ClassCoaDesc AS ItemCoaDesc, + coa_bayar.coaID AS ItemCoaID, + coa_bayar.coaAccountNo AS ItemCoaAccountNo, + coa_bayar.coaDescription AS ItemCoaDesc, + coa_hutang.coaID AS HutangCoaID, + coa_hutang.coaAccountNo AS HutangCoaAccountNo, + coa_hutang.coaDescription AS HutangCoaDesc, m_branch.M_BranchCode, m_branch.M_BranchName, m_branch.M_BranchS_RegionalID, @@ -625,6 +890,14 @@ class JurnalPiCicilanOrderAset extends MY_Controller ON fc.Fa_ClassID = mi.M_ItemFa_ClassID AND fc.Fa_ClassIsActive = 'Y' + -- Join langsung ke tabel coa untuk memastikan kevalidan akun dan deskripsi + LEFT JOIN coa coa_hutang + ON coa_hutang.coaID = fc.Fa_ClassHutangCoaID + AND coa_hutang.coaIsActive = 'Y' + LEFT JOIN coa coa_bayar + ON coa_bayar.coaID = fc.Fa_ClassCoaID + AND coa_bayar.coaIsActive = 'Y' + WHERE c.PurchaseOrderAssetContractIsActive = 'Y' AND c.PurchaseOrderAssetContractStatus = 'belum lunas' -- Hanya kontrak yang ada nilai cicilannya