From e912adeee02b8962d2cbff5316192f8edbef722d Mon Sep 17 00:00:00 2001 From: Hanan Askarim Date: Tue, 14 Jul 2026 10:28:24 +0700 Subject: [PATCH] add pengambilan co dari fa_class --- .../scheduler/JurnalPiCicilanOrderAset.php | 273 ++++++++++++++++-- 1 file changed, 253 insertions(+), 20 deletions(-) diff --git a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php index 7d8c788..eb3b4e8 100644 --- a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php +++ b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php @@ -3,23 +3,103 @@ /** * JurnalPiCicilanOrderAset * - * Endpoint scheduler untuk membuat jurnal PI cicilan order aset. - * Controller ini dibuat agar bisa dipanggil via cURL dari proses generator. + * Controller scheduler untuk membuat jurnal akuntansi atas Purchase Invoice (PI) + * cicilan order aset secara otomatis. + * + * Alur bisnis proses: + * 1. Validasi parameter input (tanggal, branchCode, title, description, details) + * 2. Insert header jurnal ke tabel `jurnal` + * - Nomor jurnal di-generate via fn_numbering('J') + * - Tipe jurnal: PAYMENT + * - Berisi: title, description, tanggal, regionalID, kode cabang, branchCompanyID + * 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 + * 4. Insert data tambahan ke tabel `jurnal_addon` (jika ada addonValue atau mItemID) + * - Referensi ke jurnalID dan jurnalTxID + * - Berisi: addonCode (default: "PICICIL"), addonValue, mItemID + * + * Endpoint: + * POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal + * POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreatePiInstallmentJournal + * GET /mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts */ class JurnalPiCicilanOrderAset extends MY_Controller { var $db; + /** + * Endpoint default — menampilkan nama controller sebagai penanda aktif. + */ public function index() { echo "Jurnal PI Cicilan Order Aset"; } + /** + * Konstruktor — inisialisasi parent controller. + */ public function __construct() { parent::__construct(); } + /** + * CreateJournal — Membuat jurnal PI cicilan order aset. + * + * Proses bisnis: + * 1. Validasi semua parameter wajib: date, branchCode, title, description, details + * 2. Ambil periodeID dari tanggal transaksi (tabel periode) + * 3. Ambil regionalID dari branchCode (tabel m_branch) + * 4. Ambil branchCompanyID dari branchCode (tabel m_branch_companydetail) + * 5. Ambil jurnalTypeID dari kode tipe jurnal (default: "PAYMENT") + * 6. Generate nomor jurnal otomatis via fn_numbering('J') + * 7. Validasi balance: total debit harus sama dengan total credit + * 8. INSERT header jurnal ke tabel `jurnal`: + * - jurnalNo : nomor jurnal hasil generate + * - jurnalJurnalTypeID: tipe PAYMENT + * - jurnalTitle : judul jurnal + * - jurnalDescription: keterangan jurnal + * - jurnalDate : tanggal transaksi + * - JurnalS_RegionalID: ID regional cabang + * - jurnalM_BranchCode: kode cabang + * 9. Untuk setiap baris detail, INSERT ke `jurnal_tx`: + * - jurnalTxJurnalID : referensi ke header jurnal + * - jurnalTxCoaID : COA yang digunakan (hutang cicilan / kas/bank) + * - jurnalTxDescription: keterangan baris (nama cicilan/aset) + * - jurnalTxDebit : nilai hutang (sisi debit) + * - jurnalTxCredit : nilai bayar (sisi kredit) + * 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") + * - jurnalAddOnValue : nilai tambahan (misal: nomor PI/kontrak) + * - jurnalAddOnM_ItemID : ID item aset yang terkait + * + * Parameter (POST JSON): + * - date : Tanggal jurnal (YYYY-MM-DD). Default: hari ini. + * - branchCode : Kode cabang. WAJIB. + * - title : Judul jurnal. WAJIB. + * - description : Keterangan jurnal. WAJIB. + * - details : Array baris transaksi. WAJIB, minimal 1 baris. + * - coaID : ID Chart of Account. WAJIB per baris. + * - description : Keterangan baris. + * - debit : Nilai debit (hutang cicilan). + * - credit : Nilai kredit (pembayaran cicilan). + * - addonValue : (opsional) Nilai addon, misal nomor PI. + * - mItemID : (opsional) ID item aset. + * - addonCode : (opsional) Kode addon. Default: "PICICIL". + * - 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. + * - jurnalTypeID : (opsional) ID tipe jurnal. Default: diambil dari jurnalTypeCode. + * - jurnalTypeCode: (opsional) Kode tipe jurnal. Default: "PAYMENT". + * - userID : ID user yang membuat jurnal. + * + * @return JSON { jurnalID, jurnalNo, detailCount, addonCount } + */ public function CreateJournal() { try { @@ -78,21 +158,22 @@ class JurnalPiCicilanOrderAset extends MY_Controller throw new Exception("jurnalTypeID / jurnalTypeCode tidak valid."); } - $userID = isset($para["userID"]) ? (int) $para["userID"] : 0; + $userID = isset($para["userID"]) ? (int) $para["userID"] : 0; $jurnalNo = $this->generateJournalNumber(); if ($jurnalNo === false || $jurnalNo == "") { throw new Exception("Gagal generate nomor jurnal."); } - $totalDebit = 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; + $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; + $totalDebit += $debit; $totalCredit += $credit; } @@ -102,6 +183,9 @@ class JurnalPiCicilanOrderAset extends MY_Controller $this->db->trans_begin(); + // ----------------------------------------------------------------- + // INSERT header jurnal ke tabel `jurnal` + // ----------------------------------------------------------------- $sqlHeader = "INSERT INTO jurnal ( jurnalM_BranchCompanyID, JurnalS_RegionalID, @@ -134,14 +218,17 @@ class JurnalPiCicilanOrderAset extends MY_Controller exit; } - $jurnalID = $this->db->insert_id(); + $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; + $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(); @@ -149,6 +236,8 @@ class JurnalPiCicilanOrderAset extends MY_Controller exit; } + // Sisi debit = hutang cicilan (COA hutang) + // Sisi kredit = pembayaran cicilan (COA kas/bank) $sqlTx = "INSERT INTO jurnal_tx ( jurnalTxJurnalID, jurnalTxCoaID, @@ -175,9 +264,13 @@ class JurnalPiCicilanOrderAset extends MY_Controller $jurnalTxID = $this->db->insert_id(); - $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"; + // ----------------------------------------------------------------- + // 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 ( @@ -216,21 +309,50 @@ class JurnalPiCicilanOrderAset extends MY_Controller $this->db->trans_commit(); $this->sys_ok([ - "jurnalID" => $jurnalID, - "jurnalNo" => $jurnalNo, + "jurnalID" => $jurnalID, + "jurnalNo" => $jurnalNo, "detailCount" => count($details), - "addonCount" => $createdAddonCount + "addonCount" => $createdAddonCount ]); } catch (Exception $exc) { $this->sys_error($exc->getMessage()); } } + /** + * CreatePiInstallmentJournal — Alias dari CreateJournal. + * + * Endpoint alternatif dengan nama yang lebih spesifik untuk konteks + * pembuatan jurnal PI cicilan order aset. Memanggil CreateJournal secara langsung. + * + * @see CreateJournal() + */ public function CreatePiInstallmentJournal() { $this->CreateJournal(); } + /** + * ListEligiblePurchaseOrderAssetContracts — Menampilkan daftar kontrak aset + * yang memenuhi syarat untuk dibuatkan jurnal PI cicilan pada periode tertentu. + * + * Syarat kontrak eligible: + * - Kontrak aktif dan berstatus "belum lunas" + * - Nilai cicilan > 0 + * - Jumlah cicilan terbayar < total cicilan + * - Tanggal kontrak masuk dalam rentang startDate–endDate + * - PO sudah berstatus Approved + * - Barang sudah diterima (receive_order_po sudah confirmed) + * + * Endpoint ini digunakan untuk preview/verifikasi sebelum proses generate jurnal + * dijalankan secara massal oleh CRON. + * + * Parameter (GET/POST): + * - startDate : Awal periode (YYYY-MM-DD). Default: tanggal 1 bulan ini. + * - endDate : Akhir periode (YYYY-MM-DD). Default: tanggal akhir bulan ini. + * + * @return JSON { startDate, endDate, total, records[] } + */ public function ListEligiblePurchaseOrderAssetContracts() { try { @@ -255,15 +377,27 @@ class JurnalPiCicilanOrderAset extends MY_Controller $this->sys_ok([ "startDate" => $startDate, - "endDate" => $endDate, - "total" => count($records), - "records" => $records + "endDate" => $endDate, + "total" => count($records), + "records" => $records ]); } catch (Exception $exc) { $this->sys_error($exc->getMessage()); } } + // ========================================================================= + // FUNGSI PRIVATE / HELPER + // ========================================================================= + + /** + * generateJournalNumber — Generate nomor jurnal otomatis. + * + * Memanggil stored function MySQL fn_numbering('J') untuk mendapatkan + * nomor jurnal berikutnya sesuai urutan penomoran yang berlaku. + * + * @return string|false Nomor jurnal (misal: "J/2025/07/0001"), atau false jika gagal. + */ private function generateJournalNumber() { $qry = $this->db->query("SELECT fn_numbering('J') AS JNumber"); @@ -273,6 +407,15 @@ class JurnalPiCicilanOrderAset extends MY_Controller return $qry->row_array()["JNumber"]; } + /** + * getPeriodeIDByDate — Ambil ID periode akuntansi berdasarkan tanggal transaksi. + * + * Periode diambil dari tabel `periode` yang aktif dan mencakup tanggal yang diberikan. + * Digunakan untuk mengisi field jurnalperiodeID pada header jurnal. + * + * @param string $date Tanggal transaksi (format YYYY-MM-DD) + * @return int periodeID yang sesuai, atau 0 jika tidak ditemukan + */ private function getPeriodeIDByDate($date) { $sql = "SELECT periodeID @@ -287,6 +430,15 @@ class JurnalPiCicilanOrderAset extends MY_Controller return (int) $qry->row_array()["periodeID"]; } + /** + * getBranchCompanyIDByBranchCode — Ambil ID branch company berdasarkan kode cabang. + * + * Data diambil dari tabel `m_branch_companydetail` yang aktif. + * Digunakan untuk mengisi field jurnalM_BranchCompanyID pada header jurnal. + * + * @param string $branchCode Kode cabang (misal: "JKT-01") + * @return int branchCompanyID yang sesuai, atau 0 jika tidak ditemukan + */ private function getBranchCompanyIDByBranchCode($branchCode) { $sql = "SELECT M_BranchCompanyDetailM_BranchCompanyID AS branchCompanyID @@ -301,6 +453,15 @@ class JurnalPiCicilanOrderAset extends MY_Controller return (int) $qry->row_array()["branchCompanyID"]; } + /** + * getRegionalIDByBranchCode — Ambil ID regional berdasarkan kode cabang. + * + * Data diambil dari tabel `m_branch` yang aktif. + * Digunakan untuk mengisi field JurnalS_RegionalID pada header jurnal. + * + * @param string $branchCode Kode cabang (misal: "JKT-01") + * @return int regionalID yang sesuai, atau 0 jika tidak ditemukan + */ private function getRegionalIDByBranchCode($branchCode) { $sql = "SELECT M_BranchS_RegionalID AS regionalID @@ -315,6 +476,15 @@ class JurnalPiCicilanOrderAset extends MY_Controller return (int) $qry->row_array()["regionalID"]; } + /** + * getJurnalTypeID — Ambil ID tipe jurnal berdasarkan kode tipe. + * + * Data diambil dari tabel `jurnal_type` yang aktif. + * Untuk PI cicilan order aset, kode yang digunakan adalah "PAYMENT". + * + * @param string $jurnalTypeCode Kode tipe jurnal (misal: "PAYMENT") + * @return int JurnalTypeID yang sesuai, atau 0 jika tidak ditemukan + */ private function getJurnalTypeID($jurnalTypeCode) { $sql = "SELECT JurnalTypeID @@ -329,6 +499,28 @@ class JurnalPiCicilanOrderAset extends MY_Controller return (int) $qry->row_array()["JurnalTypeID"]; } + /** + * getEligiblePurchaseOrderAssetContracts — Ambil kontrak aset yang layak dibuatkan jurnal. + * + * Query ini mengambil semua kontrak dari `purchase_order_asset_contract` yang memenuhi + * syarat berikut: + * - Kontrak aktif, status "belum lunas", nilai cicilan > 0 + * - Cicilan terbayar masih kurang dari total cicilan + * - Tanggal kontrak masuk dalam rentang periode yang diberikan + * - Tanggal jatuh tempo cicilan sudah lewat atau sama dengan hari ini + * - PO sudah Approved dan aktif + * - Barang sudah diterima dan receive order sudah confirmed + * + * Data yang dikembalikan mencakup: + * - Detail kontrak (ID, nama, tanggal, nilai cicilan) + * - Data PO (nomor PO, supplier, payment term, warehouse) + * - Data cabang (kode cabang, nama, regionalID, branchCompanyID) + * - Data item PO pertama (sebagai referensi baris jurnal) + * + * @param string $startDate Awal periode filter (YYYY-MM-DD) + * @param string $endDate Akhir periode filter (YYYY-MM-DD) + * @return array Array of row kontrak yang eligible + */ private function getEligiblePurchaseOrderAssetContracts($startDate, $endDate) { $sql = "SELECT @@ -355,6 +547,15 @@ class JurnalPiCicilanOrderAset extends MY_Controller wh.WarehouseM_BranchID AS WarehouseM_BranchID, ro.ReceiveOrderPoID, ro.ReceiveOrderPoConfirmed, + mi.M_ItemID AS ItemID, + mi.M_ItemCode AS ItemCode, + mi.M_ItemDesc AS ItemDesc, + 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, m_branch.M_BranchCode, m_branch.M_BranchName, m_branch.M_BranchS_RegionalID, @@ -363,12 +564,18 @@ class JurnalPiCicilanOrderAset extends MY_Controller ps.PurchaseOrderSummaryItemID, ps.PurchaseOrderSummaryItemUnitID FROM purchase_order_asset_contract c + + -- Pastikan PO sudah Approved dan aktif JOIN purchase_order po ON po.PurchaseOrderID = c.PurchaseOrderAssetContractPurchaseOrderID AND po.PurchaseOrderIsActive = 'Y' AND po.PurchaseOrderStatus = 'Approved' + + -- Data warehouse/gudang tujuan PO LEFT JOIN warehouse wh ON wh.WarehouseID = po.PurchaseOrderWarehouseID + + -- Pastikan barang sudah diterima (receive order confirmed) JOIN ( SELECT rd.ReceiveOrderPoDetailPurchaseOrderID, @@ -383,12 +590,18 @@ class JurnalPiCicilanOrderAset extends MY_Controller GROUP BY rd.ReceiveOrderPoDetailPurchaseOrderID ) ro ON ro.ReceiveOrderPoDetailPurchaseOrderID = po.PurchaseOrderID + + -- Data cabang dari warehouse LEFT JOIN m_branch ON m_branch.M_BranchID = wh.WarehouseM_BranchID AND m_branch.M_BranchIsActive = 'Y' + + -- Data branch company untuk header jurnal LEFT JOIN m_branch_companydetail ON m_branch_companydetail.M_BranchCompanyDetailM_BranchCode = m_branch.M_BranchCode AND m_branch_companydetail.M_BranchCompanyDetailIsActive = 'Y' + + -- Ambil 1 item PO pertama sebagai referensi baris jurnal LEFT JOIN ( SELECT ps0.* FROM purchase_order_summary ps0 @@ -403,15 +616,29 @@ class JurnalPiCicilanOrderAset extends MY_Controller ON psx.PurchaseOrderSummaryID = ps0.PurchaseOrderSummaryID ) ps ON ps.PurchaseOrderSummaryPurchaseOrderID = po.PurchaseOrderID + + -- COA jurnal diambil dari item -> fa_class + LEFT JOIN m_item mi + ON mi.M_ItemID = ps.PurchaseOrderSummaryItemID + AND mi.M_ItemIsActive = 'Y' + LEFT JOIN fa_class fc + ON fc.Fa_ClassID = mi.M_ItemFa_ClassID + AND fc.Fa_ClassIsActive = 'Y' + WHERE c.PurchaseOrderAssetContractIsActive = 'Y' AND c.PurchaseOrderAssetContractStatus = 'belum lunas' + -- Hanya kontrak yang ada nilai cicilannya AND IFNULL(c.PurchaseOrderAssetContractInstallmentPayAmount, 0) > 0 + -- Hanya kontrak yang belum selesai seluruh cicilannya AND IFNULL(c.PurchaseOrderAssetContractInstallmentPaid, 0) < IFNULL(c.PurchaseOrderAssetContractInstallmentNumber, 0) + -- Kontrak sudah mulai sebelum atau pada akhir periode AND DATE(c.PurchaseOrderAssetContractStartDate) <= DATE(?) + -- Kontrak belum berakhir (atau tidak ada tanggal akhir) AND ( c.PurchaseOrderAssetContractEndDate IS NULL OR DATE(c.PurchaseOrderAssetContractEndDate) >= DATE(?) ) + -- Tanggal jatuh tempo cicilan sudah tiba AND IFNULL(c.PurchaseOrderAssetContractInstallmentDate, 1) <= ? ORDER BY c.PurchaseOrderAssetContractID ASC"; @@ -424,6 +651,12 @@ class JurnalPiCicilanOrderAset extends MY_Controller return $qry->result_array(); } + /** + * isValidDate — Validasi format tanggal YYYY-MM-DD. + * + * @param string $tanggal String tanggal yang akan divalidasi + * @return bool true jika format valid, false jika tidak + */ private function isValidDate($tanggal) { $d = DateTime::createFromFormat("Y-m-d", $tanggal);