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; } }