diff --git a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http new file mode 100644 index 0000000..dfe2bf0 --- /dev/null +++ b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.http @@ -0,0 +1,42 @@ +@host = https://accone.aplikasi.web.id/one-api/ + +### Create jurnal PI cicilan order aset +POST {{host}}/mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal +Content-Type: application/json + +{ + "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 +Content-Type: application/json + +{ + "startDate": "2026-07-01", + "endDate": "2026-07-31" +} diff --git a/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php new file mode 100644 index 0000000..7d8c788 --- /dev/null +++ b/application/controllers/mockup/scheduler/JurnalPiCicilanOrderAset.php @@ -0,0 +1,432 @@ +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."); + } + + $branchCode = isset($para["branchCode"]) ? trim($para["branchCode"]) : ""; + if ($branchCode == "") { + throw new Exception("branchCode wajib diisi."); + } + + $title = isset($para["title"]) ? trim($para["title"]) : ""; + if ($title == "") { + throw new Exception("title wajib diisi."); + } + + $description = isset($para["description"]) ? trim($para["description"]) : ""; + if ($description == "") { + throw new Exception("description wajib diisi."); + } + + $details = isset($para["details"]) && is_array($para["details"]) ? $para["details"] : []; + if (count($details) == 0) { + throw new Exception("details wajib diisi minimal 1 baris."); + } + + $periodeID = isset($para["periodeID"]) && (int) $para["periodeID"] > 0 + ? (int) $para["periodeID"] + : $this->getPeriodeIDByDate($date); + if ($periodeID <= 0) { + throw new Exception("Periode jurnal tidak ditemukan untuk tanggal {$date}."); + } + + $regionalID = isset($para["regionalID"]) && (int) $para["regionalID"] > 0 + ? (int) $para["regionalID"] + : $this->getRegionalIDByBranchCode($branchCode); + if ($regionalID <= 0) { + throw new Exception("regionalID wajib diisi atau harus bisa diturunkan dari branchCode."); + } + + $branchCompanyID = isset($para["branchCompanyID"]) && (int) $para["branchCompanyID"] > 0 + ? (int) $para["branchCompanyID"] + : $this->getBranchCompanyIDByBranchCode($branchCode); + if ($branchCompanyID <= 0) { + throw new Exception("branchCompanyID wajib diisi atau harus bisa diturunkan dari branchCode."); + } + + $jurnalTypeID = isset($para["jurnalTypeID"]) && (int) $para["jurnalTypeID"] > 0 + ? (int) $para["jurnalTypeID"] + : $this->getJurnalTypeID(isset($para["jurnalTypeCode"]) && $para["jurnalTypeCode"] != "" ? $para["jurnalTypeCode"] : "PAYMENT"); + if ($jurnalTypeID <= 0) { + 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."); + } + + $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(); + + $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."); + exit; + } + + $jurnalID = $this->db->insert_id(); + $createdAddonCount = 0; + + 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; + } + + $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(); + + $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 + ]); + } catch (Exception $exc) { + $this->sys_error($exc->getMessage()); + } + } + + public function CreatePiInstallmentJournal() + { + $this->CreateJournal(); + } + + public function ListEligiblePurchaseOrderAssetContracts() + { + try { + $para = $this->sys_input; + + $startDate = isset($para["startDate"]) && $para["startDate"] != "" + ? $para["startDate"] + : date("Y-m-01"); + $endDate = isset($para["endDate"]) && $para["endDate"] != "" + ? $para["endDate"] + : date("Y-m-t"); + + if (!$this->isValidDate($startDate) || !$this->isValidDate($endDate)) { + throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD."); + } + + if (strtotime($startDate) > strtotime($endDate)) { + throw new Exception("startDate tidak boleh lebih besar dari endDate"); + } + + $records = $this->getEligiblePurchaseOrderAssetContracts($startDate, $endDate); + + $this->sys_ok([ + "startDate" => $startDate, + "endDate" => $endDate, + "total" => count($records), + "records" => $records + ]); + } catch (Exception $exc) { + $this->sys_error($exc->getMessage()); + } + } + + private function generateJournalNumber() + { + $qry = $this->db->query("SELECT fn_numbering('J') AS JNumber"); + if (!$qry || $qry->num_rows() == 0) { + return false; + } + return $qry->row_array()["JNumber"]; + } + + private function getPeriodeIDByDate($date) + { + $sql = "SELECT periodeID + FROM periode + WHERE periodeIsActive = 'Y' + AND DATE(?) BETWEEN periodeStartDate AND periodeEndDate + LIMIT 1"; + $qry = $this->db->query($sql, [$date]); + if (!$qry || $qry->num_rows() == 0) { + return 0; + } + return (int) $qry->row_array()["periodeID"]; + } + + private function getBranchCompanyIDByBranchCode($branchCode) + { + $sql = "SELECT M_BranchCompanyDetailM_BranchCompanyID AS branchCompanyID + FROM m_branch_companydetail + WHERE M_BranchCompanyDetailM_BranchCode = ? + AND M_BranchCompanyDetailIsActive = 'Y' + LIMIT 1"; + $qry = $this->db->query($sql, [$branchCode]); + if (!$qry || $qry->num_rows() == 0) { + return 0; + } + return (int) $qry->row_array()["branchCompanyID"]; + } + + private function getRegionalIDByBranchCode($branchCode) + { + $sql = "SELECT M_BranchS_RegionalID AS regionalID + FROM m_branch + WHERE M_BranchCode = ? + AND M_BranchIsActive = 'Y' + LIMIT 1"; + $qry = $this->db->query($sql, [$branchCode]); + if (!$qry || $qry->num_rows() == 0) { + return 0; + } + return (int) $qry->row_array()["regionalID"]; + } + + private function getJurnalTypeID($jurnalTypeCode) + { + $sql = "SELECT JurnalTypeID + FROM jurnal_type + WHERE JurnalTypeCode = ? + AND JurnalTypeIsActive = 'Y' + LIMIT 1"; + $qry = $this->db->query($sql, [$jurnalTypeCode]); + if (!$qry || $qry->num_rows() == 0) { + return 0; + } + return (int) $qry->row_array()["JurnalTypeID"]; + } + + private function getEligiblePurchaseOrderAssetContracts($startDate, $endDate) + { + $sql = "SELECT + c.PurchaseOrderAssetContractID, + c.PurchaseOrderAssetContractPurchaseOrderID, + c.PurchaseOrderAssetContractName, + c.PurchaseOrderAssetContractStartDate, + c.PurchaseOrderAssetContractEndDate, + c.PurchaseOrderAssetContractInstallmentNumber, + c.PurchaseOrderAssetContractInstallmentPaid, + c.PurchaseOrderAssetContractInstallmentDate, + c.PurchaseOrderAssetContractInstallmentPayAmount, + c.PurchaseOrderAssetContractReceiveOrderPoID, + po.PurchaseOrderID, + po.PurchaseOrderNumber, + po.PurchaseOrderSupplierID, + po.PurchaseOrderPaymentTerm, + po.PurchaseOrderWarehouseType, + po.PurchaseOrderWarehouseID, + wh.WarehouseCode, + wh.WarehouseName, + wh.WarehouseType, + wh.WarehouseS_RegionalID AS WarehouseS_RegionalID, + wh.WarehouseM_BranchID AS WarehouseM_BranchID, + ro.ReceiveOrderPoID, + ro.ReceiveOrderPoConfirmed, + m_branch.M_BranchCode, + m_branch.M_BranchName, + m_branch.M_BranchS_RegionalID, + m_branch_companydetail.M_BranchCompanyDetailM_BranchCompanyID AS M_BranchCompanyID, + ps.PurchaseOrderSummaryID, + ps.PurchaseOrderSummaryItemID, + ps.PurchaseOrderSummaryItemUnitID + FROM purchase_order_asset_contract c + JOIN purchase_order po + ON po.PurchaseOrderID = c.PurchaseOrderAssetContractPurchaseOrderID + AND po.PurchaseOrderIsActive = 'Y' + AND po.PurchaseOrderStatus = 'Approved' + LEFT JOIN warehouse wh + ON wh.WarehouseID = po.PurchaseOrderWarehouseID + JOIN ( + SELECT + rd.ReceiveOrderPoDetailPurchaseOrderID, + MIN(ro0.ReceiveOrderPoID) AS ReceiveOrderPoID, + MAX(ro0.ReceiveOrderPoConfirmed) AS ReceiveOrderPoConfirmed + FROM receive_order_po ro0 + JOIN receive_order_po_detail rd + ON rd.ReceiveOrderPoDetailReceiveOrderPoID = ro0.ReceiveOrderPoID + AND rd.ReceiveOrderPoDetailIsActive = 'Y' + WHERE ro0.ReceiveOrderPoIsActive = 'Y' + AND ro0.ReceiveOrderPoConfirmed = 'Y' + GROUP BY rd.ReceiveOrderPoDetailPurchaseOrderID + ) ro + ON ro.ReceiveOrderPoDetailPurchaseOrderID = po.PurchaseOrderID + LEFT JOIN m_branch + ON m_branch.M_BranchID = wh.WarehouseM_BranchID + AND m_branch.M_BranchIsActive = 'Y' + LEFT JOIN m_branch_companydetail + ON m_branch_companydetail.M_BranchCompanyDetailM_BranchCode = m_branch.M_BranchCode + AND m_branch_companydetail.M_BranchCompanyDetailIsActive = 'Y' + LEFT JOIN ( + SELECT ps0.* + FROM purchase_order_summary ps0 + JOIN ( + SELECT + PurchaseOrderSummaryPurchaseOrderID, + MIN(PurchaseOrderSummaryID) AS PurchaseOrderSummaryID + FROM purchase_order_summary + WHERE PurchaseOrderSummaryIsActive = 'Y' + GROUP BY PurchaseOrderSummaryPurchaseOrderID + ) psx + ON psx.PurchaseOrderSummaryID = ps0.PurchaseOrderSummaryID + ) ps + ON ps.PurchaseOrderSummaryPurchaseOrderID = po.PurchaseOrderID + WHERE c.PurchaseOrderAssetContractIsActive = 'Y' + AND c.PurchaseOrderAssetContractStatus = 'belum lunas' + AND IFNULL(c.PurchaseOrderAssetContractInstallmentPayAmount, 0) > 0 + AND IFNULL(c.PurchaseOrderAssetContractInstallmentPaid, 0) < IFNULL(c.PurchaseOrderAssetContractInstallmentNumber, 0) + AND DATE(c.PurchaseOrderAssetContractStartDate) <= DATE(?) + AND ( + c.PurchaseOrderAssetContractEndDate IS NULL + OR DATE(c.PurchaseOrderAssetContractEndDate) >= DATE(?) + ) + AND IFNULL(c.PurchaseOrderAssetContractInstallmentDate, 1) <= ? + ORDER BY c.PurchaseOrderAssetContractID ASC"; + + $qry = $this->db->query($sql, [$endDate, $startDate, (int) date("d", strtotime($endDate))]); + if (!$qry) { + $this->sys_error_db("Gagal mengambil data purchase_order_asset_contract."); + exit; + } + + return $qry->result_array(); + } + + private function isValidDate($tanggal) + { + $d = DateTime::createFromFormat("Y-m-d", $tanggal); + return $d && $d->format("Y-m-d") === $tanggal; + } +}