28 Commits

Author SHA1 Message Date
Hanan Askarim
82b93583d0 add keterangan untuk http pi instalment 2026-07-14 16:20:35 +07:00
Hanan Askarim
b29eb58db6 update penomoran dari fn fungsi 2026-07-14 15:45:34 +07:00
Hanan Askarim
731b940fb5 fix http pembayaran 2026-07-14 15:06:30 +07:00
Hanan Askarim
73ea318079 add supplier invoice number 2026-07-14 14:43:17 +07:00
Hanan Askarim
d8a65bc5a8 gabungkan fungsi get data dengan create jurnal 2026-07-14 13:25:33 +07:00
Hanan Askarim
e912adeee0 add pengambilan co dari fa_class 2026-07-14 10:28:24 +07:00
Hanan Askarim
be7f3b42be add pembuatan jurnal pi cicilan order asset 2026-07-14 09:47:44 +07:00
Hanan Askarim
048c35473c fix query get cicilan dan insert supplier invoice 2026-07-12 15:15:21 +07:00
Hanan Askarim
562caa467d add generate po contract 2026-07-12 11:40:43 +07:00
c83c42698a delete: empty function 2026-07-09 11:36:32 +07:00
009aff7d44 Merge branch 'po-asset-crud' into develop
# Conflicts:
#	application/controllers/mockup/purchase/order/PurchaseOrder.http
#	application/controllers/mockup/purchase/order/PurchaseOrderAset.php
2026-07-09 11:29:36 +07:00
6dbe0e400b remove workflow add scripts compare file and upload to server 2026-07-09 11:25:16 +07:00
0ea56f09fa add parameter ssh key passphrase to workflow 2026-07-09 11:25:16 +07:00
a6412c9688 add workflow auto update to server 2026-07-09 11:25:16 +07:00
bcb553ea56 add query insert into table stock_inventory in func savehandover receiveitempo inventaris 2026-07-09 11:25:16 +07:00
2874fab71d add api file po asset, api receive item inventaris add insert into table stock_inventaris 2026-07-09 11:25:16 +07:00
c125289b92 add: getDaftarAttachment api for purchase order asset 2026-07-09 10:39:28 +07:00
fe5e6dd6cb add: uploadAttachment api 2026-07-09 10:39:28 +07:00
5fd6db863a add: getDataKontrakPoAset api 2026-07-09 10:39:28 +07:00
fc9947bce4 add: getDaftarPoAset api 2026-07-09 10:39:28 +07:00
c296d5598b add: deletePoAsset api 2026-07-09 10:39:28 +07:00
5bd8e038fc add: editPoAsset api 2026-07-09 10:39:28 +07:00
8f07a65485 add: createPoAsset api 2026-07-09 10:39:01 +07:00
b7a00bc242 remove workflow add scripts compare file and upload to server 2026-07-09 10:12:42 +07:00
13af691645 add parameter ssh key passphrase to workflow
Some checks failed
Deploy controllers / upload (push) Has been cancelled
2026-07-09 09:51:11 +07:00
775a390bcb add workflow auto update to server
Some checks failed
Deploy controllers / upload (push) Has been cancelled
2026-07-09 09:46:34 +07:00
0d1dcb1b5e add query insert into table stock_inventory in func savehandover receiveitempo inventaris 2026-07-09 08:22:16 +07:00
22719de06f add api file po asset, api receive item inventaris add insert into table stock_inventaris 2026-07-08 17:15:55 +07:00
8 changed files with 2306 additions and 163 deletions

View File

@@ -22,7 +22,7 @@ class PurchaseOrderAset extends MY_Controller {
$sql = "SELECT $sql = "SELECT
SupplierID, SupplierID,
SupplierName SupplierName
FROM supplier FROM supplier
WHERE SupplierIsActive = 'Y'"; WHERE SupplierIsActive = 'Y'";
$que = $this->db->query($sql, []); $que = $this->db->query($sql, []);
if (!$que) { if (!$que) {
@@ -46,19 +46,19 @@ class PurchaseOrderAset extends MY_Controller {
$user = $this->sys_user; $user = $this->sys_user;
$sql = "SELECT $sql = "SELECT
M_BranchID, M_BranchID,
M_BranchCode, M_BranchCode,
M_BranchName M_BranchName
FROM m_branch FROM m_branch
WHERE M_BranchIsActive = 'Y' WHERE M_BranchIsActive = 'Y'
AND M_BranchS_RegionalID = ?"; AND M_BranchS_RegionalID = ?";
$que = $this->db->query($sql, [$user['S_RegionalID']]); $que = $this->db->query($sql, [$user['S_RegionalID']]);
if (!$que) { if (!$que) {
$this->sys_error_db("[Error] failed get list cabang"); $this->sys_error_db("[Error] failed get list cabang");
exit; exit;
} }
$data = $que->result_array(); $data = $que->result_array();
$this->sys_ok($data); $this->sys_ok($data);
} catch (Exception $exc) { } catch (Exception $exc) {
@@ -74,7 +74,7 @@ class PurchaseOrderAset extends MY_Controller {
} }
$para = $this->sys_input; $para = $this->sys_input;
$keyword = "%"; $keyword = "%";
if ($para['search'] != '') { if ($para['search'] != '') {
$keyword .= $para['search'] . "%"; $keyword .= $para['search'] . "%";
@@ -86,13 +86,13 @@ class PurchaseOrderAset extends MY_Controller {
$offset = ($para['currpage'] - 1) * $limit; $offset = ($para['currpage'] - 1) * $limit;
} }
$sql = "SELECT $sql = "SELECT
PurchaseRequestID, PurchaseRequestID,
PurchaseRequestNumber, PurchaseRequestNumber,
PurchaseRequestDetailID, PurchaseRequestDetailID,
PurchaseRequestFlagID, PurchaseRequestFlagID,
PurchaseRequestFlagM_BranchCode AS BranchCode, PurchaseRequestFlagM_BranchCode AS BranchCode,
PurchaseRequestItemCategoryID AS ItemCategoryID, PurchaseRequestItemCategoryID AS ItemCategoryID,
PurchaseRequestFlagQtyRest - PurchaseRequestFlagQtyProses AS UnprocessFlagQty, PurchaseRequestFlagQtyRest - PurchaseRequestFlagQtyProses AS UnprocessFlagQty,
PurchaseRequestDetailQty AS RequestQty, PurchaseRequestDetailQty AS RequestQty,
PurchaseRequestDetailQty AS OriginalQty, PurchaseRequestDetailQty AS OriginalQty,
@@ -114,7 +114,7 @@ class PurchaseOrderAset extends MY_Controller {
AND PurchaseRequestFlagIsActive = 'Y' AND PurchaseRequestFlagIsActive = 'Y'
JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID
JOIN itemunit ON ItemUnitID = PurchaseRequestDetailItemUnitID JOIN itemunit ON ItemUnitID = PurchaseRequestDetailItemUnitID
JOIN supplier_price ON SupplierPriceSupplierID = ? JOIN supplier_price ON SupplierPriceSupplierID = ?
AND SupplierPriceM_ItemID = M_ItemID AND SupplierPriceM_ItemID = M_ItemID
AND SupplierPriceItemUnitID = ItemUnitID AND SupplierPriceItemUnitID = ItemUnitID
AND SupplierPriceIsActive = 'Y' AND SupplierPriceIsActive = 'Y'
@@ -122,14 +122,14 @@ class PurchaseOrderAset extends MY_Controller {
AND M_BranchIsActive = 'Y' AND M_BranchIsActive = 'Y'
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT 1 SELECT 1
FROM purchase_order_detail FROM purchase_order_detail
JOIN purchase_order ON PurchaseOrderDetailPurchaseOrderID = PurchaseOrderID JOIN purchase_order ON PurchaseOrderDetailPurchaseOrderID = PurchaseOrderID
AND PurchaseOrderStatus = 'Approved' AND PurchaseOrderStatus = 'Approved'
AND PurchaseOrderIsActive = 'Y' AND PurchaseOrderIsActive = 'Y'
AND PurchaseOrderDetailIsActive = 'Y' AND PurchaseOrderDetailIsActive = 'Y'
WHERE PurchaseOrderDetailPurchaseRequestDetailID = PurchaseRequestDetailID WHERE PurchaseOrderDetailPurchaseRequestDetailID = PurchaseRequestDetailID
)"; )";
$sql_data = $sql . " LIMIT ? OFFSET ? "; $sql_data = $sql . " LIMIT ? OFFSET ? ";
$que_data = $this->db->query($sql_data, [ $que_data = $this->db->query($sql_data, [
$para['branchcode'], $keyword, $para['supplierID'], $para['branchcode'], $keyword, $para['supplierID'],
@@ -177,7 +177,7 @@ class PurchaseOrderAset extends MY_Controller {
$keyword .= $para['search'] . "%"; $keyword .= $para['search'] . "%";
} }
$sql_base = "SELECT $sql_base = "SELECT
PurchaseOrderID, PurchaseOrderID,
PurchaseOrderDate, PurchaseOrderDate,
PurchaseOrderNote, PurchaseOrderNote,
@@ -194,9 +194,9 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderAssetContractEndDate AS contractEnd, PurchaseOrderAssetContractEndDate AS contractEnd,
SupplierID, SupplierID,
SupplierName SupplierName
FROM purchase_order FROM purchase_order
JOIN supplier ON SupplierID = PurchaseOrderSupplierID JOIN supplier ON SupplierID = PurchaseOrderSupplierID
JOIN purchase_order_asset_contract JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID
WHERE PurchaseOrderIsActive = 'Y' WHERE PurchaseOrderIsActive = 'Y'
AND PurchaseOrderItemCategoryID = '3' AND PurchaseOrderItemCategoryID = '3'
@@ -254,8 +254,8 @@ class PurchaseOrderAset extends MY_Controller {
$para = $this->sys_input; $para = $this->sys_input;
$sql = "SELECT $sql = "SELECT
PurchaseOrderAssetAttachmentID AS attach_id, PurchaseOrderAssetAttachmentID AS attach_id,
PurchaseOrderAssetAttachmentName AS img_url, PurchaseOrderAssetAttachmentName AS img_url,
PurchaseOrderAssetAttachmentCreated AS created PurchaseOrderAssetAttachmentCreated AS created
FROM purchase_order_asset_attachment FROM purchase_order_asset_attachment
@@ -321,7 +321,7 @@ class PurchaseOrderAset extends MY_Controller {
$detail_asset = $que_detail->result_array(); $detail_asset = $que_detail->result_array();
# get data from purchase order summary # # get data from purchase order summary #
$sql_sumry = "SELECT $sql_sumry = "SELECT
PurchaseOrderID, PurchaseOrderID,
PurchaseOrderSummaryID, PurchaseOrderSummaryID,
PurchaseOrderSummaryQty AS RequestQty, PurchaseOrderSummaryQty AS RequestQty,
@@ -359,11 +359,11 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderStatus, PurchaseOrderStatus,
PurchaseOrderRefNumber AS reference, PurchaseOrderRefNumber AS reference,
PurchaseOrderGrandTotal AS grandtotal, PurchaseOrderGrandTotal AS grandtotal,
CASE CASE
WHEN PurchaseOrderTaxPpnType = 'P' THEN 'percent' WHEN PurchaseOrderTaxPpnType = 'P' THEN 'percent'
ELSE 'absolute' ELSE 'absolute'
END AS typepajak, END AS typepajak,
CASE CASE
WHEN PurchaseOrderTaxPpnType = 'P' THEN PurchaseOrderTaxPercentPpn WHEN PurchaseOrderTaxPpnType = 'P' THEN PurchaseOrderTaxPercentPpn
ELSE PurchaseOrderTaxAmountPpn ELSE PurchaseOrderTaxAmountPpn
END AS valuepajak, END AS valuepajak,
@@ -392,9 +392,9 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderAssetContractInstallmentDownPaymentType AS installmentDownPaymentType, PurchaseOrderAssetContractInstallmentDownPaymentType AS installmentDownPaymentType,
PurchaseOrderAssetContractInstallmentDownPayment AS installmentDownPayment, PurchaseOrderAssetContractInstallmentDownPayment AS installmentDownPayment,
PurchaseOrderApprovedManagerUserID AS verifiedby PurchaseOrderApprovedManagerUserID AS verifiedby
FROM purchase_order FROM purchase_order
JOIN supplier ON SupplierID = PurchaseOrderSupplierID JOIN supplier ON SupplierID = PurchaseOrderSupplierID
JOIN purchase_order_asset_contract JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID
WHERE PurchaseOrderIsActive = 'Y' WHERE PurchaseOrderIsActive = 'Y'
AND PurchaseOrderID = ?"; AND PurchaseOrderID = ?";
@@ -481,7 +481,7 @@ class PurchaseOrderAset extends MY_Controller {
$areatype = 'R'; $areatype = 'R';
} }
$sqlusrdivisi = "SELECT M_UserDivisionDivisionID FROM m_userdivision $sqlusrdivisi = "SELECT M_UserDivisionDivisionID FROM m_userdivision
WHERE M_UserDivisionM_UserID = ? AND M_UserDivisionIsActive = 'Y'"; WHERE M_UserDivisionM_UserID = ? AND M_UserDivisionIsActive = 'Y'";
$queusrdivisi = $this->db->query($sqlusrdivisi, [$user['M_UserID']]); $queusrdivisi = $this->db->query($sqlusrdivisi, [$user['M_UserID']]);
if (!$queusrdivisi) { if (!$queusrdivisi) {
@@ -630,8 +630,8 @@ class PurchaseOrderAset extends MY_Controller {
} }
/* purchase request update flag */ /* purchase request update flag */
$sql_flag = "UPDATE purchase_request_flag SET $sql_flag = "UPDATE purchase_request_flag SET
PurchaseRequestFlagQtyProses = ? PurchaseRequestFlagQtyProses = ?
WHERE PurchaseRequestFlagID = ?"; WHERE PurchaseRequestFlagID = ?";
$que_flag = $this->db->query($sql_flag, [ $que_flag = $this->db->query($sql_flag, [
$obj['RequestQty'], $obj['PurchaseRequestFlagID'] $obj['RequestQty'], $obj['PurchaseRequestFlagID']
@@ -727,7 +727,7 @@ class PurchaseOrderAset extends MY_Controller {
$summary_total = floatval($para['summary']['total']); $summary_total = floatval($para['summary']['total']);
# update po header # # update po header #
$sql_po = "UPDATE purchase_order SET $sql_po = "UPDATE purchase_order SET
PurchaseOrderDate = ?, PurchaseOrderDate = ?,
PurchaseOrderRefNumber = ?, PurchaseOrderRefNumber = ?,
PurchaseOrderSupplierID = ?, PurchaseOrderSupplierID = ?,
@@ -741,7 +741,7 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderTaxAmountPpn = ?, PurchaseOrderTaxAmountPpn = ?,
PurchaseOrderGrandTotal = ?, PurchaseOrderGrandTotal = ?,
PurchaseOrderLastUpdated = NOW() PurchaseOrderLastUpdated = NOW()
WHERE PurchaseOrderID = ? WHERE PurchaseOrderID = ?
AND PurchaseOrderIsActive = 'Y'"; AND PurchaseOrderIsActive = 'Y'";
$que_po = $this->db->query($sql_po, [ $que_po = $this->db->query($sql_po, [
$para['podate'], $para['reference'], $para['supplierID'], $user['S_RegionalID'], $para['podate'], $para['reference'], $para['supplierID'], $user['S_RegionalID'],
@@ -755,7 +755,7 @@ class PurchaseOrderAset extends MY_Controller {
} }
# update purchase_order_asset_contract # # update purchase_order_asset_contract #
$sql_contract = "UPDATE purchase_order_asset_contract SET $sql_contract = "UPDATE purchase_order_asset_contract SET
PurchaseOrderAssetContractTAssetAttachmentID = ?, PurchaseOrderAssetContractTAssetAttachmentID = ?,
PurchaseOrderAssetContractName = ?, PurchaseOrderAssetContractName = ?,
PurchaseOrderAssetContractDate = ?, PurchaseOrderAssetContractDate = ?,
@@ -768,7 +768,7 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderAssetContractInstallmentDownPaymentType = ?, PurchaseOrderAssetContractInstallmentDownPaymentType = ?,
PurchaseOrderAssetContractInstallmentDownPayment = ?, PurchaseOrderAssetContractInstallmentDownPayment = ?,
PurchaseOrderAssetContractLastUpdated = NOW() PurchaseOrderAssetContractLastUpdated = NOW()
WHERE PurchaseOrderAssetContractID = ? WHERE PurchaseOrderAssetContractID = ?
AND PurchaseOrderAssetContractIsActive = 'Y'"; AND PurchaseOrderAssetContractIsActive = 'Y'";
$que_contract = $this->db->query($sql_contract, [ $que_contract = $this->db->query($sql_contract, [
$para['assetAttachmentID'], $para['assetAttachmentID'],
@@ -791,9 +791,9 @@ class PurchaseOrderAset extends MY_Controller {
} }
# update status old po summary isActive to 'N' # # update status old po summary isActive to 'N' #
$sql_active = "UPDATE purchase_order_summary SET $sql_active = "UPDATE purchase_order_summary SET
PurchaseOrderSummaryIsActive = 'N' PurchaseOrderSummaryIsActive = 'N'
WHERE PurchaseOrderSummaryPurchaseOrderID = ? WHERE PurchaseOrderSummaryPurchaseOrderID = ?
AND PurchaseOrderSummaryIsActive = 'Y'"; AND PurchaseOrderSummaryIsActive = 'Y'";
$que_active = $this->db->query($sql_active, [$para['PurchaseOrderID']]); $que_active = $this->db->query($sql_active, [$para['PurchaseOrderID']]);
if (!$que_active) { if (!$que_active) {
@@ -848,7 +848,7 @@ class PurchaseOrderAset extends MY_Controller {
} }
# update status po detail isActive to 'N' # # update status po detail isActive to 'N' #
$sql_active = "UPDATE purchase_order_detail SET $sql_active = "UPDATE purchase_order_detail SET
PurchaseOrderDetailIsActive = 'N' PurchaseOrderDetailIsActive = 'N'
WHERE PurchaseOrderDetailPurchaseOrderID = ? WHERE PurchaseOrderDetailPurchaseOrderID = ?
AND PurchaseOrderDetailIsActive = 'Y'"; AND PurchaseOrderDetailIsActive = 'Y'";
@@ -892,7 +892,7 @@ class PurchaseOrderAset extends MY_Controller {
} }
$sql_flag = "UPDATE purchase_request_flag SET $sql_flag = "UPDATE purchase_request_flag SET
PurchaseRequestFlagQtyProses = ? PurchaseRequestFlagQtyProses = ?
WHERE PurchaseRequestFlagID = ?"; WHERE PurchaseRequestFlagID = ?";
$que_flag = $this->db->query($sql_flag, [ $que_flag = $this->db->query($sql_flag, [
$obj['RequestQty'], $obj['PurchaseRequestFlagID'] $obj['RequestQty'], $obj['PurchaseRequestFlagID']
@@ -927,11 +927,11 @@ class PurchaseOrderAset extends MY_Controller {
$para = $this->sys_input; $para = $this->sys_input;
$user = $this->sys_user; $user = $this->sys_user;
$sql_detail = "SELECT $sql_detail = "SELECT
PurchaseOrderDetailPurchaseRequestFlagID, PurchaseOrderDetailPurchaseRequestFlagID,
PurchaseOrderDetailQty PurchaseOrderDetailQty
FROM purchase_order_detail FROM purchase_order_detail
WHERE PurchaseOrderDetailPurchaseOrderID = ? WHERE PurchaseOrderDetailPurchaseOrderID = ?
AND PurchaseOrderDetailIsActive = 'Y'"; AND PurchaseOrderDetailIsActive = 'Y'";
$que_detail = $this->db->query($sql_detail, [$para['poID']]); $que_detail = $this->db->query($sql_detail, [$para['poID']]);
if (!$que_detail) { if (!$que_detail) {
@@ -958,10 +958,10 @@ class PurchaseOrderAset extends MY_Controller {
} }
/* soft delete purchase order detail */ /* soft delete purchase order detail */
$sql_deldetail = "UPDATE purchase_order_detail SET $sql_deldetail = "UPDATE purchase_order_detail SET
PurchaseOrderDetailIsActive = 'N', PurchaseOrderDetailIsActive = 'N',
PurchaseOrderDetailDeleted = NOW(), PurchaseOrderDetailDeleted = NOW(),
PurchaseOrderDetailDeletedUserID = ? PurchaseOrderDetailDeletedUserID = ?
WHERE PurchaseOrderDetailPurchaseOrderID = ?"; WHERE PurchaseOrderDetailPurchaseOrderID = ?";
$que_deldetail = $this->db->query($sql_deldetail, [ $que_deldetail = $this->db->query($sql_deldetail, [
$user['M_UserID'], $para['poID'] $user['M_UserID'], $para['poID']
@@ -976,7 +976,7 @@ class PurchaseOrderAset extends MY_Controller {
$sql_delsummary = "UPDATE purchase_order_summary SET $sql_delsummary = "UPDATE purchase_order_summary SET
PurchaseOrderSummaryIsActive = 'N', PurchaseOrderSummaryIsActive = 'N',
PurchaseOrderSummaryDeleted = NOW(), PurchaseOrderSummaryDeleted = NOW(),
PurchaseOrderSummaryDeletedUserID = ? PurchaseOrderSummaryDeletedUserID = ?
WHERE PurchaseOrderSummaryPurchaseOrderID = ?"; WHERE PurchaseOrderSummaryPurchaseOrderID = ?";
$que_delsummary = $this->db->query($sql_delsummary, [ $que_delsummary = $this->db->query($sql_delsummary, [
$user['M_UserID'], $para['poID'] $user['M_UserID'], $para['poID']
@@ -988,10 +988,10 @@ class PurchaseOrderAset extends MY_Controller {
} }
/* soft delete purchase_order_asset_contract */ /* soft delete purchase_order_asset_contract */
$sql_delcontract = "UPDATE purchase_order_asset_contract SET $sql_delcontract = "UPDATE purchase_order_asset_contract SET
PurchaseOrderAssetContractIsActive = 'N', PurchaseOrderAssetContractIsActive = 'N',
PurchaseOrderAssetContractLastUpdated = NOW() PurchaseOrderAssetContractLastUpdated = NOW()
WHERE PurchaseOrderAssetContractID = ? WHERE PurchaseOrderAssetContractID = ?
AND PurchaseOrderAssetContractPurchaseOrderID = ?"; AND PurchaseOrderAssetContractPurchaseOrderID = ?";
$que_delcontract = $this->db->query($sql_delcontract, [ $que_delcontract = $this->db->query($sql_delcontract, [
$para['contractID'], $para['poID'] $para['contractID'], $para['poID']
@@ -1003,10 +1003,10 @@ class PurchaseOrderAset extends MY_Controller {
} }
/* soft delete purchase order */ /* soft delete purchase order */
$sql_delorder = "UPDATE purchase_order SET $sql_delorder = "UPDATE purchase_order SET
PurchaseOrderIsActive = 'N', PurchaseOrderIsActive = 'N',
PurchaseOrderDeleted = NOW(), PurchaseOrderDeleted = NOW(),
PurchaseOrderDeletedUserID = ? PurchaseOrderDeletedUserID = ?
WHERE PurchaseOrderID = ?"; WHERE PurchaseOrderID = ?";
$que_delorder = $this->db->query($sql_delorder, [ $que_delorder = $this->db->query($sql_delorder, [
$user['M_UserID'], $para['poID'] $user['M_UserID'], $para['poID']
@@ -1119,7 +1119,7 @@ class PurchaseOrderAset extends MY_Controller {
$sql = "UPDATE purchase_order SET $sql = "UPDATE purchase_order SET
PurchaseOrderStatus = 'Pending', PurchaseOrderStatus = 'Pending',
PurchaseOrderLastUpdated = NOW() PurchaseOrderLastUpdated = NOW()
WHERE PurchaseOrderID = ? WHERE PurchaseOrderID = ?
AND PurchaseOrderIsActive = 'Y'"; AND PurchaseOrderIsActive = 'Y'";
$que = $this->db->query($sql, [$para['poID']]); $que = $this->db->query($sql, [$para['poID']]);
if (!$que) { if (!$que) {
@@ -1134,7 +1134,7 @@ class PurchaseOrderAset extends MY_Controller {
$sql = "UPDATE purchase_order SET $sql = "UPDATE purchase_order SET
PurchaseOrderApprovedManagerUserID = ?, PurchaseOrderApprovedManagerUserID = ?,
PurchaseOrderLastUpdated = NOW() PurchaseOrderLastUpdated = NOW()
WHERE PurchaseOrderID = ? WHERE PurchaseOrderID = ?
AND PurchaseOrderIsActive = 'Y'"; AND PurchaseOrderIsActive = 'Y'";
$que = $this->db->query($sql, [$user['M_UserID'], $para['poID']]); $que = $this->db->query($sql, [$user['M_UserID'], $para['poID']]);
if (!$que) { if (!$que) {
@@ -1146,11 +1146,11 @@ class PurchaseOrderAset extends MY_Controller {
/* approval oleh kepala regional */ /* approval oleh kepala regional */
if ($approvelevel == '2') { if ($approvelevel == '2') {
$sql = "UPDATE purchase_order SET $sql = "UPDATE purchase_order SET
PurchaseOrderStatus = 'Approved', PurchaseOrderStatus = 'Approved',
PurchaseOrderLastUpdated = NOW(), PurchaseOrderLastUpdated = NOW(),
PurchaseOrderApprovedUserID = ? PurchaseOrderApprovedUserID = ?
WHERE PurchaseOrderID = ? WHERE PurchaseOrderID = ?
AND PurchaseOrderIsActive = 'Y'"; AND PurchaseOrderIsActive = 'Y'";
$que = $this->db->query($sql, [$user['M_UserID'], $para['poID']]); $que = $this->db->query($sql, [$user['M_UserID'], $para['poID']]);
if (!$que) { if (!$que) {
@@ -1176,7 +1176,7 @@ class PurchaseOrderAset extends MY_Controller {
} }
$user = $this->sys_user; $user = $this->sys_user;
$sql = "SELECT M_UserM_ApproveLevelID FROM m_user $sql = "SELECT M_UserM_ApproveLevelID FROM m_user
WHERE M_UserIsActive = 'Y' AND M_UserID = ? "; WHERE M_UserIsActive = 'Y' AND M_UserID = ? ";
$que = $this->db->query($sql, [$user['M_UserID']]); $que = $this->db->query($sql, [$user['M_UserID']]);
if (!$que) { if (!$que) {
@@ -1211,4 +1211,4 @@ class PurchaseOrderAset extends MY_Controller {
exit; exit;
} }
} }
} }

View File

@@ -1514,23 +1514,23 @@ class ReceiveItemPoInventaris extends MY_Controller
# GET DATA RO # # GET DATA RO #
$json_ro_before = $this->getDataROPO($ROID); $json_ro_before = $this->getDataROPO($ROID);
$sql_ro = "SELECT $sql_ro = "SELECT
receive_order_po.*, receive_order_po.*,
DATE_FORMAT(ReceiveOrderPoIDate, '%d-%m-%Y') AS formatedDate, DATE_FORMAT(ReceiveOrderPoIDate, '%d-%m-%Y') AS formatedDate,
CASE CASE
WHEN WarehouseType = 'B' WHEN WarehouseType = 'B'
THEN CONCAT(WarehouseCode,' ', WarehouseName, ' - ', M_BranchName) THEN CONCAT(WarehouseCode,' ', WarehouseName, ' - ', M_BranchName)
WHEN WarehouseType = 'R' WHEN WarehouseType = 'R'
THEN CONCAT(WarehouseCode,' ', WarehouseName, ' - ', S_RegionalName) THEN CONCAT(WarehouseCode,' ', WarehouseName, ' - ', S_RegionalName)
ELSE '' ELSE ''
END WarehouseName, END WarehouseName,
WarehouseID WarehouseID
FROM receive_order_po FROM receive_order_po
JOIN warehouse ON ReceiveOrderPoWarehouseID = WarehouseID JOIN warehouse ON ReceiveOrderPoWarehouseID = WarehouseID
JOIN s_regional ON WarehouseS_RegionalID = S_RegionalID JOIN s_regional ON WarehouseS_RegionalID = S_RegionalID
AND S_RegionalIsActive = 'Y' AND S_RegionalIsActive = 'Y'
LEFT JOIN m_branch ON WarehouseM_BranchID = M_BranchID LEFT JOIN m_branch ON WarehouseM_BranchID = M_BranchID
AND M_BranchIsActive = 'Y' AND M_BranchIsActive = 'Y'
WHERE ReceiveOrderPoID = ?"; WHERE ReceiveOrderPoID = ?";
$que_ro = $this->db->query($sql_ro, [$ROID]); $que_ro = $this->db->query($sql_ro, [$ROID]);
if (!$que_ro) { if (!$que_ro) {
$this->db->trans_rollback(); $this->db->trans_rollback();
@@ -1545,23 +1545,23 @@ class ReceiveItemPoInventaris extends MY_Controller
} }
$sql_detail_ro = "SELECT $sql_detail_ro = "SELECT
receive_order_po_detail.*, receive_order_po_detail.*,
PurchaseOrderID, PurchaseOrderID,
PurchaseOrderNumber, PurchaseOrderNumber,
PurchaseOrderTaxPercentPph, PurchaseOrderTaxPercentPph,
PurchaseOrderTaxPercentPpn, PurchaseOrderTaxPercentPpn,
PurchaseOrderSummaryDiscountAmount as DiscountPerItem, PurchaseOrderSummaryDiscountAmount as DiscountPerItem,
PurchaseOrderSummaryQty as QtyAllPO, PurchaseOrderSummaryQty as QtyAllPO,
PurchaseOrderSummaryTotal as TotalAllPO, PurchaseOrderSummaryTotal as TotalAllPO,
PurchaseOrderDiscountPercent AS DiscPOPercent, PurchaseOrderDiscountPercent AS DiscPOPercent,
PurchaseOrderDiscountAmount AS DiscPORupiah PurchaseOrderDiscountAmount AS DiscPORupiah
FROM receive_order_po_detail FROM receive_order_po_detail
JOIN purchase_order ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID JOIN purchase_order ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID
JOIN purchase_order_summary ON ReceiveOrderPoDetailPurchaseOrderSummaryID = PurchaseOrderSummaryID JOIN purchase_order_summary ON ReceiveOrderPoDetailPurchaseOrderSummaryID = PurchaseOrderSummaryID
AND PurchaseOrderSummaryIsActive = 'Y' AND PurchaseOrderSummaryIsActive = 'Y'
WHERE ReceiveOrderPoDetailReceiveOrderPoID = ? WHERE ReceiveOrderPoDetailReceiveOrderPoID = ?
AND ReceiveOrderPoDetailIsActive='Y' AND ReceiveOrderPoDetailIsActive='Y'
GROUP BY ReceiveOrderPoDetailID"; GROUP BY ReceiveOrderPoDetailID";
$que_detail_ro = $this->db->query($sql_detail_ro, [$ROID]); $que_detail_ro = $this->db->query($sql_detail_ro, [$ROID]);
if (!$que_detail_ro) { if (!$que_detail_ro) {
$this->db->trans_rollback(); $this->db->trans_rollback();
@@ -1572,10 +1572,10 @@ class ReceiveItemPoInventaris extends MY_Controller
# UPDATE STATUS RO # # UPDATE STATUS RO #
$sql_update_ro = "UPDATE receive_order_po SET $sql_update_ro = "UPDATE receive_order_po SET
ReceiveOrderPoConfirmed = 'Y', ReceiveOrderPoConfirmed = 'Y',
ReceiveOrderPoConfirmedDate = NOW(), ReceiveOrderPoConfirmedDate = NOW(),
ReceiveOrderPoConfirmedUserID = ? ReceiveOrderPoConfirmedUserID = ?
WHERE ReceiveOrderPoID = ?"; WHERE ReceiveOrderPoID = ?";
$que_update_ro = $this->db->query($sql_update_ro, [$user['M_UserID'], $ROID]); $que_update_ro = $this->db->query($sql_update_ro, [$user['M_UserID'], $ROID]);
if (!$que_update_ro) { if (!$que_update_ro) {
$this->db->trans_rollback(); $this->db->trans_rollback();
@@ -1594,8 +1594,8 @@ class ReceiveItemPoInventaris extends MY_Controller
$jurnal_number = $que_jrnl_num->row_array()['numbering']; $jurnal_number = $que_jrnl_num->row_array()['numbering'];
$sql_periode = "SELECT periodeID FROM periode $sql_periode = "SELECT periodeID FROM periode
WHERE DATE(NOW()) BETWEEN periodeStartDate AND periodeEndDate WHERE DATE(NOW()) BETWEEN periodeStartDate AND periodeEndDate
AND periodeIsActive= 'Y' AND periodeIsClosed = 'N'"; AND periodeIsActive= 'Y' AND periodeIsClosed = 'N'";
$que_periode = $this->db->query($sql_periode, []); $que_periode = $this->db->query($sql_periode, []);
if (!$que_periode) { if (!$que_periode) {
$this->sys_error_db("[Error] get periode", $this->db); $this->sys_error_db("[Error] get periode", $this->db);
@@ -1631,17 +1631,17 @@ class ReceiveItemPoInventaris extends MY_Controller
# INSERT JURNAL HEADER # # INSERT JURNAL HEADER #
$sql_head_jurnal = "INSERT INTO jurnal ( $sql_head_jurnal = "INSERT INTO jurnal (
jurnalM_BranchCompanyID, jurnalM_BranchCompanyID,
JurnalS_RegionalID, JurnalS_RegionalID,
jurnalM_BranchCode, jurnalM_BranchCode,
jurnalperiodeID, jurnalperiodeID,
jurnalNo, jurnalNo,
jurnalTitle, jurnalTitle,
jurnalDescription, jurnalDescription,
jurnalDate, jurnalDate,
jurnalJurnalTypeID, jurnalJurnalTypeID,
jurnalM_UserID jurnalM_UserID
) VALUES (?,?,?,?,?,?,?,NOW(),?,?)"; ) VALUES (?,?,?,?,?,?,?,NOW(),?,?)";
$que_head_jurnal = $this->db->query($sql_head_jurnal, [ $que_head_jurnal = $this->db->query($sql_head_jurnal, [
$user['M_BranchCompanyID'], $user['M_BranchCompanyID'],
$user['S_RegionalID'], $user['S_RegionalID'],
@@ -1668,9 +1668,9 @@ class ReceiveItemPoInventaris extends MY_Controller
foreach ($data_detail_ro as $key => $detail) { foreach ($data_detail_ro as $key => $detail) {
# UPDATE PO Summary # # UPDATE PO Summary #
$sql_posummary = "UPDATE purchase_order_summary SET $sql_posummary = "UPDATE purchase_order_summary SET
PurchaseOrderSummaryQtyReceived = PurchaseOrderSummaryQtyReceived + ? PurchaseOrderSummaryQtyReceived = PurchaseOrderSummaryQtyReceived + ?
WHERE PurchaseOrderSummaryIsActive = 'Y' WHERE PurchaseOrderSummaryIsActive = 'Y'
AND PurchaseOrderSummaryID = ?"; AND PurchaseOrderSummaryID = ?";
$que_posummary = $this->db->query($sql_posummary, [ $que_posummary = $this->db->query($sql_posummary, [
intval($detail['ReceiveOrderPoDetailQty']), intval($detail['ReceiveOrderPoDetailQty']),
$detail['ReceiveOrderPoDetailPurchaseOrderSummaryID'] $detail['ReceiveOrderPoDetailPurchaseOrderSummaryID']
@@ -1862,8 +1862,8 @@ class ReceiveItemPoInventaris extends MY_Controller
// save diskon prorata // save diskon prorata
$sql_save_diskon = "UPDATE receive_order_po_detail SET $sql_save_diskon = "UPDATE receive_order_po_detail SET
ReceiveOrderPoDetailDiskonPoProrata = ? ReceiveOrderPoDetailDiskonPoProrata = ?
WHERE ReceiveOrderPoDetailID = ?"; WHERE ReceiveOrderPoDetailID = ?";
$que_save_diskon = $this->db->query($sql_save_diskon, [ $que_save_diskon = $this->db->query($sql_save_diskon, [
$diskon_prorata_item, $diskon_prorata_item,
$detail['ReceiveOrderPoDetailID'] $detail['ReceiveOrderPoDetailID']
@@ -1898,9 +1898,9 @@ class ReceiveItemPoInventaris extends MY_Controller
// update stock based on batch // update stock based on batch
foreach ($batch_list as $key => $batch) { foreach ($batch_list as $key => $batch) {
$sql_cek_stock = "SELECT * $sql_cek_stock = "SELECT *
FROM stock s FROM stock s
JOIN m_item i ON s.StockItemID = i.M_ItemID JOIN m_item i ON s.StockItemID = i.M_ItemID
WHERE s.StockItemID = ? WHERE s.StockItemID = ?
AND s.StockItemUnitID = ? AND s.StockItemUnitID = ?
AND s.StockWarehouseID = ? AND s.StockWarehouseID = ?
AND i.M_ItemItem_CategoryID = ?"; AND i.M_ItemItem_CategoryID = ?";
@@ -1946,18 +1946,18 @@ class ReceiveItemPoInventaris extends MY_Controller
} }
// insert stockid barcode barang // insert stockid barcode barang
$sql_update_barcode = "UPDATE t_barcode_barang SET // $sql_update_barcode = "UPDATE t_barcode_barang SET
T_BarcodeBarangStockID = ? // T_BarcodeBarangStockID = ?
WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?"; // WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
$qry_update_barcode = $this->db->query($sql_update_barcode, [ // $qry_update_barcode = $this->db->query($sql_update_barcode, [
$stock_ID, // $stock_ID,
$detail['ReceiveOrderPoDetailID'] // $detail['ReceiveOrderPoDetailID']
]); // ]);
if (!$qry_update_barcode) { // if (!$qry_update_barcode) {
$this->db->trans_rollback(); // $this->db->trans_rollback();
$this->sys_error_db("[Error] update barcode stockID", $this->db); // $this->sys_error_db("[Error] update barcode stockID", $this->db);
exit; // exit;
} // }
} else { } else {
$sql_insert_stock = "INSERT INTO stock ( $sql_insert_stock = "INSERT INTO stock (
StockWarehouseAlmariID, StockWarehouseAlmariID,
@@ -1990,18 +1990,18 @@ class ReceiveItemPoInventaris extends MY_Controller
$stock_qty_end = intval($batch['qty']); $stock_qty_end = intval($batch['qty']);
// insert stockid barcode barang // insert stockid barcode barang
$sql_update_barcode = "UPDATE t_barcode_barang SET // $sql_update_barcode = "UPDATE t_barcode_barang SET
T_BarcodeBarangStockID = ? // T_BarcodeBarangStockID = ?
WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?"; // WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
$qry_update_barcode = $this->db->query($sql_update_barcode, [ // $qry_update_barcode = $this->db->query($sql_update_barcode, [
$stockID, // $stockID,
$detail['ReceiveOrderPoDetailID'] // $detail['ReceiveOrderPoDetailID']
]); // ]);
if (!$qry_update_barcode) { // if (!$qry_update_barcode) {
$this->db->trans_rollback(); // $this->db->trans_rollback();
$this->sys_error_db("[Error] update barcode stockID", $this->db); // $this->sys_error_db("[Error] update barcode stockID", $this->db);
exit; // exit;
} // }
} }
// insert stock card // insert stock card
@@ -2095,14 +2095,14 @@ class ReceiveItemPoInventaris extends MY_Controller
// insert jurnal tx for GRNI // insert jurnal tx for GRNI
$total_grni = round($total_debet - ($total_discount_item + $total_discount_po), 2) + $ship_cost; $total_grni = round($total_debet - ($total_discount_item + $total_discount_po), 2) + $ship_cost;
$sql_grni = "INSERT INTO jurnal_tx ( $sql_grni = "INSERT INTO jurnal_tx (
jurnalTxJurnalID, jurnalTxJurnalID,
jurnalTxCoaID, jurnalTxCoaID,
jurnalTxDescription, jurnalTxDescription,
jurnalTxDebit, jurnalTxDebit,
jurnalTxCredit, jurnalTxCredit,
jurnalTxM_UserID jurnalTxM_UserID
) SELECT ?, coaID, coaDescription, 0, ?, ? ) SELECT ?, coaID, coaDescription, 0, ?, ?
FROM coa WHERE coaAccountNo = '2110100030' LIMIT 1"; FROM coa WHERE coaAccountNo = '2110100030' LIMIT 1";
$que_grni = $this->db->query($sql_grni, [$jurnal_ID, $total_grni, $user['M_UserID']]); $que_grni = $this->db->query($sql_grni, [$jurnal_ID, $total_grni, $user['M_UserID']]);
if (!$que_grni) { if (!$que_grni) {
$this->db->trans_rollback(); $this->db->trans_rollback();
@@ -2112,13 +2112,13 @@ class ReceiveItemPoInventaris extends MY_Controller
$grni_jurnal_id = $this->db->insert_id(); $grni_jurnal_id = $this->db->insert_id();
$sql_addon_grni = "INSERT INTO jurnal_addon ( $sql_addon_grni = "INSERT INTO jurnal_addon (
jurnalAddOnJurnalID, jurnalAddOnJurnalID,
jurnalAddOnJurnalTxID, jurnalAddOnJurnalTxID,
jurnalAddOnCode, jurnalAddOnCode,
jurnalAddOnValue, jurnalAddOnValue,
jurnalAddOnCreated, jurnalAddOnCreated,
jurnalAddOnCreatedUserID jurnalAddOnCreatedUserID
) VALUES (?,?,'RONUMB',?,NOW(),?)"; ) VALUES (?,?,'RONUMB',?,NOW(),?)";
$que_addon_grni = $this->db->query($sql_addon_grni, [ $que_addon_grni = $this->db->query($sql_addon_grni, [
$jurnal_ID, $jurnal_ID,
$grni_jurnal_id, $grni_jurnal_id,
@@ -2344,22 +2344,22 @@ class ReceiveItemPoInventaris extends MY_Controller
$this->db->trans_begin(); $this->db->trans_begin();
$sql_insert = "INSERT INTO receive_order_po_inspeksi ( $sql_insert = "INSERT INTO receive_order_po_inspeksi (
ReceiveOrderPoInspeksiReceiveOrderPoDetailID, ReceiveOrderPoInspeksiReceiveOrderPoDetailID,
ReceiveOrderPoInspeksiQtyPesan, ReceiveOrderPoInspeksiQtyPesan,
ReceiveOrderPoInspeksiQtyActual, ReceiveOrderPoInspeksiQtyActual,
ReceiveOrderPoInspeksiPricePesan, ReceiveOrderPoInspeksiPricePesan,
ReceiveOrderPoInspeksiPriceActual, ReceiveOrderPoInspeksiPriceActual,
ReceiveOrderPoInspeksiDatePesan, ReceiveOrderPoInspeksiDatePesan,
ReceiveOrderPoInspeksiDateActual, ReceiveOrderPoInspeksiDateActual,
ReceiveOrderPoInspeksiExpireDate, ReceiveOrderPoInspeksiExpireDate,
ReceiveOrderPoInspeksiKeadaanKemasan, ReceiveOrderPoInspeksiKeadaanKemasan,
ReceiveOrderPoInspeksiKondisiPengiriman, ReceiveOrderPoInspeksiKondisiPengiriman,
ReceiveOrderPoInspeksiSimpulan, ReceiveOrderPoInspeksiSimpulan,
ReceiveOrderPoInspeksiCatatan, ReceiveOrderPoInspeksiCatatan,
ReceiveOrderPoInspeksiStaffPenerima, ReceiveOrderPoInspeksiStaffPenerima,
ReceiveOrderPoInspeksiStaffPengirim, ReceiveOrderPoInspeksiStaffPengirim,
ReceiveOrderPoInspeksiUserID ReceiveOrderPoInspeksiUserID
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
$que_insert = $this->db->query($sql_insert, [ $que_insert = $this->db->query($sql_insert, [
$ROdetailID, $ROdetailID,
$inspeksi['qty_po'], $inspeksi['qty_po'],
@@ -2736,6 +2736,47 @@ class ReceiveItemPoInventaris extends MY_Controller
$barcodeID = $this->db->insert_id(); $barcodeID = $this->db->insert_id();
# check if item already in stock #
$sql_cekstock = "SELECT StockID
FROM stock
JOIN warehouse ON WarehouseID = StockWarehouseID
AND WarehouseIsActive = 'Y'
WHERE WarehouseM_BranchID = ?
AND StockItemID = ?
AND StockItemUnitID = ?";
$que_cekstock = $this->db->query($sql_cekstock, [
$user['M_BranchID'],
$item['ReceiveOrderPoItemID'],
$item['ReceiveOrderPoItemUnitID'],
]);
if (!$que_cekstock) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] query cek stock");
exit;
}
$stockID = $que_cekstock->row_array()['StockID'];
# insert stock inventaris #
$sql_stockinventaris = "INSERT INTO stock_inventory (
StockInventoryStockID,
StockInventoryRuanganID,
StockInventoryBarcode,
StockInventoryBranchID,
StockInventoryCreatedUserID
) VALUES (?,?,?,?,?)";
$que_stockinventaris = $this->db->query($sql_stockinventaris, [
$stockID,
$param['ruanganID'],
$noBarcode,
$user['M_BranchID'],
$user['M_UserID']
]);
if (!$que_stockinventaris) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] insert into table stock inventaris");
exit;
}
# insert each inventory handover detail # # insert each inventory handover detail #
$sql_insert_handover_detail = "INSERT INTO asset_handover_detail( $sql_insert_handover_detail = "INSERT INTO asset_handover_detail(
AssetHandoverDetailAssetHandoverID, AssetHandoverDetailAssetHandoverID,

View File

@@ -0,0 +1,90 @@
@host = https://accone.aplikasi.web.id/one-api
@date = 2026-07-14
@coaBayarID = 1110100001
@coaHutangID = 2110100030
# =============================================================================
# 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
# =============================================================================
### 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}}",
"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}}",
"dryRun" : "Y",
"contractID" : 00
}
### 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
}
]
}

View File

@@ -0,0 +1,970 @@
<?php
/**
* JurnalPiCicilanOrderAset
*
* 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 (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: "PIINSTALMENT"), addonValue, mItemID
*
* Endpoint:
* POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal
* POST /mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts
* 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: "PIINSTALMENT")
* - 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: "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.
* - 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 {
$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.");
}
$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 = 0;
$result = $this->insertJournal(
$date,
$branchCode,
$title,
$description,
$details,
$periodeID,
$regionalID,
$branchCompanyID,
$jurnalTypeID,
$userID
);
if ($result === false) {
exit;
}
$this->sys_ok($result);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
/**
* GenerateJournalsFromContracts — Generate jurnal otomatis dari seluruh kontrak aset eligible.
*
* Fungsi ini mengintegrasikan data dari getEligiblePurchaseOrderAssetContracts()
* dengan proses pembuatan jurnal (insertJournal()) secara otomatis.
*
* 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 GenerateJournalsFromContracts()
{
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));
$invoiceNumber = $kontrak["SupplierInvoiceNumber"] ?? "";
$invoiceID = (int) ($kontrak["SupplierInvoiceID"] ?? 0);
// 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,
"invoiceNumber" => $invoiceNumber,
"jumlah" => $jumlah,
"coaHutangID" => $coaHutangID,
"coaBayarID" => $coaBayarID,
"keterangan" => "Mode simulasi (dryRun), data tidak disimpan."
];
continue;
}
// Susun title dan description jurnal
$title = "PI Cicilan Aset - {$invoiceNumber}";
$description = "Jurnal cicilan ke-{$cicilanKe} dari {$totalCicilan} | {$namaKontrak} | PO: {$nomorPO} | 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("PAYMENTINV");
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" => $invoiceNumber,
"mItemID" => $itemID,
"addonCode" => "PIINSTALMENT"
],
[
"coaID" => $coaBayarID,
"description" => $descBayar,
"debit" => 0,
"credit" => $jumlah,
"addonValue" => $invoiceNumber,
"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.
*
* Syarat kontrak eligible:
* - Kontrak aktif dan berstatus "belum lunas"
* - Nilai cicilan > 0
* - Jumlah cicilan terbayar < total cicilan
* - Tanggal kontrak masuk dalam rentang startDateendDate
* - 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 {
$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());
}
}
// =========================================================================
// 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.
*
* 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");
if (!$qry || $qry->num_rows() == 0) {
return false;
}
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
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"];
}
/**
* 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
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"];
}
/**
* 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
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"];
}
/**
* 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
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"];
}
/**
* 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
c.PurchaseOrderAssetContractID,
si.SupplierInvoiceID,
si.SupplierInvoiceNumber,
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,
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,
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,
m_branch_companydetail.M_BranchCompanyDetailM_BranchCompanyID AS M_BranchCompanyID,
ps.PurchaseOrderSummaryID,
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
-- Ambil 1 item PO pertama sebagai referensi baris jurnal
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
-- Hubungkan dengan detail supplier_invoice_detail terlebih dahulu
JOIN supplier_invoice_detail sid
ON sid.SupplierInvoiceDetailPurchaseOrderID = po.PurchaseOrderID
AND sid.SupplierInvoiceDetailPurchaseOrderSummaryID = ps.PurchaseOrderSummaryID
AND sid.SupplierInvoiceDetailIsActive = 'Y'
-- Baru hubungkan ke header supplier_invoice untuk mengambil nomor invoice
JOIN supplier_invoice si
ON si.SupplierInvoiceID = sid.SupplierInvoiceDetailSupplierInvoiceID
AND si.SupplierInvoiceDate >= DATE(?)
AND si.SupplierInvoiceDate <= DATE(?)
AND si.SupplierInvoiceIsActive = 'Y'
-- Pastikan barang sudah diterima (receive order confirmed)
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
-- 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'
-- 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'
-- 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
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) <= ?
-- Pastikan belum pernah dibuatkan jurnal untuk invoice cicilan ini
AND NOT EXISTS (
SELECT 1
FROM jurnal_addon ja
JOIN jurnal j ON j.jurnalID = ja.jurnalAddOnJurnalID
WHERE ja.jurnalAddOnCode = 'PIINSTALMENT'
AND ja.jurnalAddOnValue = si.SupplierInvoiceNumber
)
ORDER BY c.PurchaseOrderAssetContractID ASC";
$qry = $this->db->query($sql, [
$startDate,
$endDate,
$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();
}
/**
* 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);
return $d && $d->format("Y-m-d") === $tanggal;
}
}

View File

@@ -0,0 +1,127 @@
# ============================================================
# FILE : PurchaseInvoiceInstallment.http
# MODUL : Scheduler — Generate Purchase Invoice Cicilan Aset
# ============================================================
#
# DESKRIPSI
# ---------
# File ini berisi kumpulan HTTP request untuk menguji endpoint
# scheduler yang secara otomatis membuat Purchase Invoice (PI)
# cicilan bulanan berdasarkan kontrak aset aktif.
#
# CARA PAKAI
# ----------
# 1. Gunakan ekstensi "REST Client" di VS Code (humao.rest-client)
# atau tool sejenis (IntelliJ HTTP Client, Postman, dll).
# 2. Ganti nilai @host sesuai environment (local / staging / prod).
# 3. Klik "Send Request" di atas tiap blok ### untuk menjalankan
# request tersebut secara individual.
# 4. Jalankan request secara BERURUTAN sesuai urutan yang disarankan
# (lihat keterangan tiap endpoint di bawah).
#
# ALUR PENGGUNAAN YANG DISARANKAN
# --------------------------------
# [1] ListEligibleContracts → Lihat kontrak mana saja yang akan diproses
# [2] GenerateMonthlyInvoices → Jalankan proses generate PI cicilan sekaligus
# [3] InsertSupplierInvoice → (Opsional) Insert manual 1 PI jika diperlukan
#
# CATATAN
# -------
# - startDate & endDate menentukan periode bulan yang diproses.
# Biasanya diisi dengan tanggal awal dan akhir bulan berjalan.
# - Scheduler ini AMAN dijalankan ulang (idempotent). Kontrak yang
# sudah punya PI di bulan yang sama akan dilewati otomatis.
# - userID pada InsertSupplierInvoice akan diabaikan; sistem
# mengambil userID dari PurchaseOrderAssetContractCreatedUserID.
# ============================================================
@host = https://accone.aplikasi.web.id/one-api
# ============================================================
# [1] LIST KONTRAK ELIGIBLE
# ============================================================
# Gunakan endpoint ini SEBELUM generate untuk melihat preview
# kontrak mana saja yang akan dibuatkan PI pada periode ini.
#
# Response berisi daftar kontrak beserta detail cicilan,
# status GRNI, dan apakah PI bulan ini sudah ada atau belum.
# ============================================================
### [1] List kontrak cicilan eligible yang belum diproses bulan ini
POST {{host}}/mockup/scheduler/PurchaseInvoiceInstallment/ListEligibleContracts
Content-Type: application/json
{
"startDate": "2026-07-01",
"endDate": "2026-07-31"
}
# ============================================================
# [2] GENERATE PI CICILAN BULANAN (MAIN ENDPOINT)
# ============================================================
# Endpoint utama scheduler. Satu request ini akan:
# 1. Mengambil semua kontrak aset aktif yang eligible.
# 2. Mengecek duplikasi — melewati kontrak yang sudah ada PI-nya.
# 3. Membuat nomor PI via fn_penomoran() berdasarkan user
# pembuat kontrak (PurchaseOrderAssetContractCreatedUserID).
# 4. Insert supplier_invoice + supplier_invoice_detail.
#
# Response berisi:
# - berhasil : daftar PI yang berhasil dibuat (dengan nomorPI, supplierInvoiceID, dll)
# - dilewati : daftar kontrak yang dilewati beserta alasannya
# ============================================================
### [2] Generate PI cicilan untuk semua kontrak pada periode ini
POST {{host}}/mockup/scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
Content-Type: application/json
{
"startDate": "2026-07-01",
"endDate": "2026-07-31"
}
# ============================================================
# [3] INSERT MANUAL SATU PI (INTERNAL / DEBUG)
# ============================================================
# Endpoint ini dipanggil INTERNAL oleh GenerateMonthlyInvoices
# via cURL. Gunakan hanya untuk debugging atau insert manual
# satu PI tertentu tanpa menjalankan proses batch.
#
# FIELD WAJIB:
# - nomorPI : Nomor PI (format dari fn_penomoran)
# - tanggalPI : Tanggal invoice (YYYY-MM-DD)
# - tanggalJatuhTempo : Tanggal jatuh tempo (YYYY-MM-DD)
# - jumlahCicilan : Nominal cicilan (angka, dalam Rupiah)
# - catatan : Catatan singkat pada header invoice
# - deskripsi : Deskripsi baris detail invoice
# - userID : ID user pembuat (ambil dari kontrak)
# - purchaseOrderAssetContractID : ID kontrak aset
# - purchaseOrderID : ID Purchase Order
# - receiveOrderPoID : ID Receive Order PO (GRNI)
# - supplierID : ID Supplier
# - purchaseOrderSummaryID : ID summary item PO
# - purchaseOrderSummaryItemID : ID item (barang/aset)
# - purchaseOrderSummaryItemUnitID: ID satuan item
# ============================================================
### [3] Insert satu supplier_invoice cicilan secara manual (debug)
POST {{host}}/mockup/scheduler/PurchaseInvoiceInstallmentInsert/InsertSupplierInvoice
Content-Type: application/json
{
"nomorPI": "PI-2026-07-0001",
"tanggalPI": "2026-07-31",
"tanggalJatuhTempo": "2026-08-14",
"jumlahCicilan": 15000000,
"catatan": "PI Cicilan Otomatis — Kontrak Aset ID 123 periode 2026-07",
"deskripsi": "Cicilan Kontrak Aset — Kendaraan Operasional periode 2026-07",
"userID": 1,
"purchaseOrderAssetContractID": 123,
"purchaseOrderID": 456,
"receiveOrderPoID": 789,
"supplierID": 12,
"purchaseOrderSummaryID": 111,
"purchaseOrderSummaryItemID": 222,
"purchaseOrderSummaryItemUnitID": 333
}

View File

@@ -0,0 +1,641 @@
<?php
/**
* PurchaseInvoiceInstallment
*
* Digunakan oleh CRON untuk membuat Purchase Invoice (PI) cicilan aset
* secara otomatis setiap bulan berdasarkan kontrak yang aktif.
*
* Endpoint utama:
* POST /scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
* POST /scheduler/PurchaseInvoiceInstallment/CurlGenerateMonthlyInvoices
*/
class PurchaseInvoiceInstallment extends MY_Controller
{
var $db;
var $baseUrl = "https://accone.aplikasi.web.id/one-api/";
public function index()
{
echo "Purchase Invoice Installment — Auto Generate PI Cicilan Aset";
}
public function __construct()
{
parent::__construct();
}
/**
* GenerateMonthlyInvoices
*
* Membuat Purchase Invoice cicilan untuk semua kontrak aset yang aktif
* dan belum lunas pada bulan yang ditentukan.
*
* Parameter (POST JSON):
* - startDate : Tanggal awal periode (format YYYY-MM-DD). Default: awal bulan ini.
* - endDate : Tanggal akhir periode (format YYYY-MM-DD). Default: akhir bulan ini.
* userID diambil otomatis dari token (sys_user["M_UserID"]).
* Jika CRON berjalan tanpa token, fallback ke user ID 0.
*
* Syarat kontrak diproses:
* 1. Kontrak aktif dan berstatus "belum lunas"
* 2. Nilai cicilan > 0
* 3. Jumlah cicilan terbayar < total cicilan
* 4. Tanggal kontrak masuk dalam bulan yang dituju
* 5. RO sudah confirmed
* 6. PO sudah berstatus Approved
*/
public function GenerateMonthlyInvoices()
{
try {
$para = $this->sys_input;
// Ambil userID dari token JWT (sys_user). Fallback ke 0 jika CRON berjalan tanpa token.
$userID = !empty($this->sys_user["M_UserID"]) ? (int) $this->sys_user["M_UserID"] : 0;
$user = $this->getCronUser($userID);
$startDate = isset($para["startDate"]) && $para["startDate"] != ""
? $para["startDate"]
: (isset($para["date"]) && $para["date"] != "" ? date("Y-m-01", strtotime($para["date"])) : date("Y-m-01"));
$endDate = isset($para["endDate"]) && $para["endDate"] != ""
? $para["endDate"]
: (isset($para["date"]) && $para["date"] != "" ? date("Y-m-t", strtotime($para["date"])) : date("Y-m-t"));
// Validasi format tanggal
if (!$this->isValidDate($startDate) || !$this->isValidDate($endDate)) {
throw new Exception("Format tanggal tidak valid. Gunakan format YYYY-MM-DD, contoh: 2025-07-01");
}
if (strtotime($startDate) > strtotime($endDate)) {
throw new Exception("startDate tidak boleh lebih besar dari endDate");
}
// Tentukan rentang bulan berdasarkan tanggal acuan
$monthStart = $startDate;
$monthEnd = $endDate;
$dayOfMonth = (int) date("d", strtotime($endDate));
// -------------------------------------------------------------------------
// Ambil semua kontrak cicilan yang memenuhi syarat pada bulan ini
// -------------------------------------------------------------------------
$sqlKontrak = "SELECT
c.PurchaseOrderAssetContractID,
c.PurchaseOrderAssetContractPurchaseOrderID,
ro.ReceiveOrderPoID AS PurchaseOrderAssetContractReceiveOrderPoID,
c.PurchaseOrderAssetContractName,
c.PurchaseOrderAssetContractStartDate,
c.PurchaseOrderAssetContractEndDate,
c.PurchaseOrderAssetContractInstallmentNumber,
c.PurchaseOrderAssetContractInstallmentPaid,
c.PurchaseOrderAssetContractInstallmentDate,
c.PurchaseOrderAssetContractInstallmentPayAmount,
c.PurchaseOrderAssetContractCreatedUserID,
po.PurchaseOrderID,
po.PurchaseOrderNumber,
po.PurchaseOrderSupplierID,
po.PurchaseOrderPaymentTerm,
po.PurchaseOrderWarehouseType,
po.PurchaseOrderWarehouseID,
ro.ReceiveOrderPoConfirmed,
ps.PurchaseOrderSummaryID,
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'
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
-- Ambil 1 item PO pertama sebagai referensi baris detail PI
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'
-- 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 bulan ini
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 melewati atau sama dengan hari ini
AND IFNULL(c.PurchaseOrderAssetContractInstallmentDate, 1) <= ?";
$params = [$monthEnd, $monthStart, $dayOfMonth];
$qryKontrak = $this->db->query($sqlKontrak, $params);
if (!$qryKontrak) {
$this->sys_error_db("Gagal mengambil daftar kontrak cicilan dari database.");
exit;
}
$berhasil = []; // PI yang berhasil dibuat
$dilewati = []; // PI yang dilewati beserta alasannya
// -------------------------------------------------------------------------
// Proses tiap kontrak satu per satu
// -------------------------------------------------------------------------
$kontraks = $qryKontrak->result_array();
foreach ($kontraks as $kontrak) {
// Lewati jika item/satuan PO tidak ditemukan (tidak bisa buat baris detail)
if (empty($kontrak["PurchaseOrderSummaryItemID"]) || empty($kontrak["PurchaseOrderSummaryItemUnitID"])) {
$dilewati[] = [
"kontrakID" => $kontrak["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $kontrak["PurchaseOrderID"],
"alasan" => "Item atau satuan pada Purchase Order tidak ditemukan, tidak bisa membuat baris detail PI."
];
continue;
}
// -------------------------------------------------------
// Cek apakah PI cicilan bulan ini sudah pernah dibuat
// -------------------------------------------------------
$sqlCekDuplikat = "SELECT SupplierInvoiceID, SupplierInvoiceNumber
FROM supplier_invoice
WHERE SupplierInvoiceIsActive = 'Y'
AND SupplierInvoiceDate >= DATE(?)
AND SupplierInvoiceDate <= DATE(?)
AND SupplierInvoiceStatus = 'Draft'
AND (
SupplierInvoiceReceiveOrderPoID = ?
OR EXISTS (
SELECT 1
FROM supplier_invoice_detail sid
WHERE sid.SupplierInvoiceDetailSupplierInvoiceID = SupplierInvoiceID
AND sid.SupplierInvoiceDetailReceiveOrderPoID = ?
AND sid.SupplierInvoiceDetailIsActive = 'Y'
)
)
LIMIT 1";
$qryCekDuplikat = $this->db->query($sqlCekDuplikat, [
$monthStart,
$monthEnd,
$kontrak["PurchaseOrderAssetContractReceiveOrderPoID"],
$kontrak["PurchaseOrderAssetContractReceiveOrderPoID"]
]);
if (!$qryCekDuplikat) {
$this->sys_error_db("Gagal memeriksa duplikasi PI cicilan untuk kontrak ID " . $kontrak["PurchaseOrderAssetContractID"] . ".");
exit;
}
if ($qryCekDuplikat->num_rows() > 0) {
$piExisting = $qryCekDuplikat->row_array();
$dilewati[] = [
"kontrakID" => $kontrak["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $kontrak["PurchaseOrderID"],
"receiveOrderPoID" => $kontrak["PurchaseOrderAssetContractReceiveOrderPoID"],
"supplierInvoiceID" => $piExisting["SupplierInvoiceID"],
"nomorInvoice" => $piExisting["SupplierInvoiceNumber"],
"alasan" => "PI cicilan untuk bulan ini sudah dibuat sebelumnya, tidak perlu dibuat ulang."
];
continue;
}
$contractUserID = (int) ($kontrak["PurchaseOrderAssetContractCreatedUserID"] ?? 0);
$contractUser = $this->getCronUser($contractUserID);
// Generate nomor PI otomatis via stored function fn_penomoran
$nomorPI = $this->generateNomorPI($kontrak, $contractUser);
$jumlahCicilan = (float) $kontrak["PurchaseOrderAssetContractInstallmentPayAmount"];
$paymentTerm = isset($kontrak["PurchaseOrderPaymentTerm"]) && $kontrak["PurchaseOrderPaymentTerm"] !== null
? (int) $kontrak["PurchaseOrderPaymentTerm"]
: 0;
$tanggalJatuhTempo = $this->hitungJatuhTempo($endDate, $paymentTerm);
$catatan = "PI Cicilan Otomatis — Kontrak Aset ID " . $kontrak["PurchaseOrderAssetContractID"] . " periode " . date("Y-m", strtotime($startDate));
// -------------------------------------------------------
// INSERT header + detail Purchase Invoice (supplier_invoice & supplier_invoice_detail)
// -------------------------------------------------------
$deskripsi = $kontrak["PurchaseOrderAssetContractName"] != ""
? $kontrak["PurchaseOrderAssetContractName"]
: "Cicilan Kontrak Aset ID " . $kontrak["PurchaseOrderAssetContractID"];
$deskripsi .= " periode " . date("Y-m", strtotime($startDate));
$payloadInsert = [
"nomorPI" => $nomorPI,
"tanggalPI" => $endDate,
"tanggalJatuhTempo" => $tanggalJatuhTempo,
"jumlahCicilan" => $jumlahCicilan,
"catatan" => $catatan,
"deskripsi" => $deskripsi,
"userID" => $contractUserID,
"purchaseOrderAssetContractID" => $kontrak["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $kontrak["PurchaseOrderID"],
"receiveOrderPoID" => $kontrak["PurchaseOrderAssetContractReceiveOrderPoID"],
"supplierID" => $kontrak["PurchaseOrderSupplierID"],
"purchaseOrderSummaryID" => $kontrak["PurchaseOrderSummaryID"],
"purchaseOrderSummaryItemID" => $kontrak["PurchaseOrderSummaryItemID"],
"purchaseOrderSummaryItemUnitID" => $kontrak["PurchaseOrderSummaryItemUnitID"]
];
$hasilInsert = $this->curlInsertSupplierInvoice($payloadInsert);
if ($hasilInsert === false) {
$this->sys_error_db("Gagal menyimpan Purchase Invoice cicilan untuk kontrak ID " . $kontrak["PurchaseOrderAssetContractID"] . ".");
exit;
}
if (!empty($hasilInsert["duplicate"])) {
$dilewati[] = [
"kontrakID" => $kontrak["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $kontrak["PurchaseOrderID"],
"receiveOrderPoID" => $kontrak["PurchaseOrderAssetContractReceiveOrderPoID"],
"supplierInvoiceID" => $hasilInsert["supplierInvoiceID"],
"nomorInvoice" => $hasilInsert["supplierInvoiceNumber"],
"alasan" => "PI cicilan untuk bulan ini sudah dibuat sebelumnya, tidak perlu dibuat ulang."
];
continue;
}
$supplierInvoiceID = $hasilInsert["supplierInvoiceID"];
// -------------------------------------------------------
// UPDATE jumlah cicilan terbayar pada kontrak
// -------------------------------------------------------
$this->db->trans_begin();
$cicilanTerbayarBaru = ((int) $kontrak["PurchaseOrderAssetContractInstallmentPaid"]) + 1;
$statusKontrakBaru = $cicilanTerbayarBaru >= (int) $kontrak["PurchaseOrderAssetContractInstallmentNumber"]
? "lunas"
: "belum lunas";
$sqlUpdateKontrak = "UPDATE purchase_order_asset_contract
SET PurchaseOrderAssetContractReceiveOrderPoID = ?,
PurchaseOrderAssetContractInstallmentPaid = ?,
PurchaseOrderAssetContractStatus = ?,
PurchaseOrderAssetContractLastUpdated = NOW()
WHERE PurchaseOrderAssetContractID = ?
AND PurchaseOrderAssetContractIsActive = 'Y'";
$qryUpdateKontrak = $this->db->query($sqlUpdateKontrak, [
$kontrak["PurchaseOrderAssetContractReceiveOrderPoID"],
$cicilanTerbayarBaru,
$statusKontrakBaru,
$kontrak["PurchaseOrderAssetContractID"]
]);
if (!$qryUpdateKontrak) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal memperbarui data cicilan terbayar pada kontrak ID " . $kontrak["PurchaseOrderAssetContractID"] . ".");
exit;
}
// Pastikan tidak ada error di dalam transaksi sebelum commit
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
$this->sys_error_db("Transaksi database gagal saat memproses kontrak ID " . $kontrak["PurchaseOrderAssetContractID"] . ". Semua perubahan dibatalkan.");
exit;
}
$this->db->trans_commit();
// Catat PI yang berhasil dibuat
$berhasil[] = [
"kontrakID" => $kontrak["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $kontrak["PurchaseOrderID"],
"supplierInvoiceID" => $supplierInvoiceID,
"nomorInvoice" => $nomorPI,
"jumlahCicilan" => $jumlahCicilan
];
}
// -------------------------------------------------------
// Response sukses — ringkasan hasil proses
// -------------------------------------------------------
$this->sys_ok([
"startDate" => $startDate,
"endDate" => $endDate,
"periodeAwal" => $monthStart,
"periodeAkhir" => $monthEnd,
"totalDibuat" => count($berhasil),
"totalDilewati" => count($dilewati),
"daftarDibuat" => $berhasil,
"daftarDilewati" => $dilewati
]);
} catch (Exception $exc) {
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
}
$this->sys_error($exc->getMessage());
}
}
/**
* Kirim payload insert supplier_invoice ke controller terpisah via cURL.
*
* @param array $payload
* @return array|false
*/
private function curlInsertSupplierInvoice($payload)
{
$endpoint = rtrim($this->baseUrl, "/") . "/mockup/scheduler/PurchaseInvoiceInstallmentInsert/InsertSupplierInvoice";
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Accept: application/json"
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_TIMEOUT => 120
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$pesanError = curl_error($ch);
curl_close($ch);
$this->sys_error("Gagal menghubungi endpoint insert supplier_invoice. Detail: " . $pesanError);
return false;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
$this->sys_error("Endpoint insert supplier_invoice mengembalikan HTTP " . $httpCode . ".");
return false;
}
$decoded = json_decode($response, true);
if (!is_array($decoded)) {
$this->sys_error("Response insert supplier_invoice tidak valid JSON.");
return false;
}
if (!isset($decoded["status"]) || strtoupper($decoded["status"]) !== "OK") {
$pesan = isset($decoded["message"]) ? $decoded["message"] : "Insert supplier_invoice gagal.";
$this->sys_error($pesan);
return false;
}
return isset($decoded["data"]) && is_array($decoded["data"]) ? $decoded["data"] : $decoded;
}
public function ListEligibleContracts()
{
try {
$para = $this->sys_input;
$startDate = isset($para["startDate"]) && $para["startDate"] != ""
? $para["startDate"]
: (isset($para["date"]) && $para["date"] != "" ? date("Y-m-01", strtotime($para["date"])) : date("Y-m-01"));
$endDate = isset($para["endDate"]) && $para["endDate"] != ""
? $para["endDate"]
: (isset($para["date"]) && $para["date"] != "" ? date("Y-m-t", strtotime($para["date"])) : date("Y-m-t"));
if (!$this->isValidDate($startDate) || !$this->isValidDate($endDate)) {
throw new Exception("Format tanggal tidak valid. Gunakan format YYYY-MM-DD, contoh: 2025-07-01");
}
if (strtotime($startDate) > strtotime($endDate)) {
throw new Exception("startDate tidak boleh lebih besar dari endDate");
}
$monthStart = $startDate;
$monthEnd = $endDate;
$dayOfMonth = (int) date("d", strtotime($endDate));
$sqlKontrak = "SELECT
c.PurchaseOrderAssetContractID,
c.PurchaseOrderAssetContractPurchaseOrderID,
ro.ReceiveOrderPoID AS PurchaseOrderAssetContractReceiveOrderPoID,
c.PurchaseOrderAssetContractName,
c.PurchaseOrderAssetContractStartDate,
c.PurchaseOrderAssetContractEndDate,
c.PurchaseOrderAssetContractInstallmentNumber,
c.PurchaseOrderAssetContractInstallmentPaid,
c.PurchaseOrderAssetContractInstallmentDate,
c.PurchaseOrderAssetContractInstallmentPayAmount,
po.PurchaseOrderID,
po.PurchaseOrderNumber,
po.PurchaseOrderSupplierID,
po.PurchaseOrderPaymentTerm,
po.PurchaseOrderWarehouseType,
po.PurchaseOrderWarehouseID,
ro.ReceiveOrderPoConfirmed,
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'
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 (
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";
$qryKontrak = $this->db->query($sqlKontrak, [$monthEnd, $monthStart, $dayOfMonth]);
if (!$qryKontrak) {
$this->sys_error_db("Gagal mengambil daftar kontrak cicilan eligible.");
exit;
}
$this->sys_ok([
"startDate" => $startDate,
"endDate" => $endDate,
"periodeAwal" => $monthStart,
"periodeAkhir" => $monthEnd,
"total" => $qryKontrak->num_rows(),
"records" => $qryKontrak->result_array()
]);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
/**
* CurlGenerateMonthlyInvoices
*
* Wrapper untuk memanggil GenerateMonthlyInvoices melalui HTTP request (cURL).
* Cocok digunakan oleh CRON eksternal yang tidak bisa memanggil function PHP langsung.
*
* Parameter (POST JSON):
* - baseUrl : Base URL server tujuan. Default: URL server ini sendiri.
* - startDate : Tanggal awal periode (YYYY-MM-DD). Default: awal bulan ini.
* - endDate : Tanggal akhir periode (YYYY-MM-DD). Default: akhir bulan ini.
* - userID : ID user yang menjalankan. Default: 0.
*/
// =========================================================================
// FUNGSI PRIVATE / HELPER
// =========================================================================
/**
* Generate nomor Purchase Invoice.
*
* @param array $kontrak Data baris kontrak dari query
* @param array $user Data user CRON
* @return string Nomor PI yang dihasilkan
*/
private function generateNomorPI($kontrak, $user)
{
$userID = isset($user["M_UserID"]) ? (int) $user["M_UserID"] : 0;
// Tentukan area ID dan type
$areaid = isset($user["M_BranchID"]) && (int) $user["M_BranchID"] > 0 ? (int) $user["M_BranchID"] : 0;
$areatype = 'B';
if (isset($user["loginLevel"]) && $user["loginLevel"] == 'regional') {
$areaid = isset($user["S_RegionalID"]) && (int) $user["S_RegionalID"] > 0 ? (int) $user["S_RegionalID"] : 0;
$areatype = 'R';
}
// Ambil divisi user
$userDivID = 0;
if ($userID > 0) {
$sqlusrdivisi = "SELECT M_UserDivisionDivisionID FROM m_userdivision
WHERE M_UserDivisionM_UserID = ? AND M_UserDivisionIsActive = 'Y' LIMIT 1";
$queusrdivisi = $this->db->query($sqlusrdivisi, [$userID]);
if ($queusrdivisi && $queusrdivisi->num_rows() > 0) {
$userDivID = (int) $queusrdivisi->row_array()['M_UserDivisionDivisionID'];
}
}
// Jalankan stored function fn_penomoran
$sqlnum = "SELECT `fn_penomoran`(?, ?, ?, ?, ?, ?) AS numpd;";
$quenum = $this->db->query($sqlnum, ['PI', $userDivID, $areatype, $areaid, 'SM', 'N']);
if ($quenum && $quenum->num_rows() > 0) {
return $quenum->row_array()['numpd'];
}
// Fallback jika stored function gagal
$contractID = isset($kontrak["PurchaseOrderAssetContractID"]) ? (int) $kontrak["PurchaseOrderAssetContractID"] : 0;
return "PI-INS-FALLBACK-" . $contractID . "-" . date("Ymd");
}
/**
* Ambil data user berdasarkan userID untuk keperluan CRON.
* Jika user tidak ditemukan, kembalikan data default (tanpa branch/regional).
*
* @param int $userID
* @return array
*/
private function getCronUser($userID)
{
$sql = "SELECT
M_UserID,
M_UserM_BranchID AS M_BranchID,
M_UserS_RegionalID AS S_RegionalID
FROM m_user
WHERE M_UserID = ?
LIMIT 1";
$qry = $this->db->query($sql, [$userID]);
if ($qry && $qry->num_rows() > 0) {
$user = $qry->row_array();
$user["loginLevel"] = "branch";
return $user;
}
// User tidak ditemukan, gunakan data kosong agar proses tetap berjalan
return [
"M_UserID" => $userID,
"M_BranchID" => 0,
"S_RegionalID" => 0,
"loginLevel" => "branch"
];
}
/**
* Hitung tanggal jatuh tempo berdasarkan tanggal acuan dan payment term (dalam hari).
*
* @param string $tanggal Format YYYY-MM-DD
* @param int $term Jumlah hari payment term
* @return string Tanggal jatuh tempo (YYYY-MM-DD)
*/
private function hitungJatuhTempo($tanggal, $term)
{
$hari = is_numeric($term) ? (int) $term : 0;
if ($hari < 0) {
$hari = 0;
}
return date("Y-m-d", strtotime($tanggal . " +" . $hari . " days"));
}
/**
* Validasi apakah string adalah tanggal yang valid dengan format YYYY-MM-DD.
*
* @param string $tanggal
* @return bool
*/
private function isValidDate($tanggal)
{
$d = DateTime::createFromFormat("Y-m-d", $tanggal);
return $d && $d->format("Y-m-d") === $tanggal;
}
}

View File

@@ -0,0 +1,220 @@
<?php
/**
* PurchaseInvoiceInstallmentInsert
*
* Endpoint khusus untuk menyimpan supplier_invoice dan supplier_invoice_detail
* dari payload yang dikirim controller generator via cURL.
*/
class PurchaseInvoiceInstallmentInsert extends MY_Controller
{
var $db;
public function index()
{
echo "Purchase Invoice Installment Insert";
}
public function __construct()
{
parent::__construct();
}
public function InsertSupplierInvoice()
{
try {
$para = $this->sys_input;
$required = [
"nomorPI",
"tanggalPI",
"tanggalJatuhTempo",
"jumlahCicilan",
"catatan",
"deskripsi",
"userID",
"purchaseOrderAssetContractID",
"purchaseOrderID",
"receiveOrderPoID",
"supplierID",
"purchaseOrderSummaryID",
"purchaseOrderSummaryItemID",
"purchaseOrderSummaryItemUnitID"
];
foreach ($required as $field) {
if (!isset($para[$field]) || $para[$field] === "" || $para[$field] === null) {
throw new Exception("Field wajib belum lengkap: " . $field);
}
}
$tanggalPI = $para["tanggalPI"];
$tanggalJatuhTempo = $para["tanggalJatuhTempo"];
if (!$this->isValidDate($tanggalPI) || !$this->isValidDate($tanggalJatuhTempo)) {
throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD.");
}
$jumlahCicilan = (float) $para["jumlahCicilan"];
if ($jumlahCicilan <= 0) {
throw new Exception("jumlahCicilan harus lebih besar dari 0.");
}
$receiveOrderPoID = (int) $para["receiveOrderPoID"];
$monthKey = date("Y-m", strtotime($tanggalPI));
$sqlDuplikat = "SELECT SupplierInvoiceID, SupplierInvoiceNumber
FROM supplier_invoice
WHERE SupplierInvoiceIsActive = 'Y'
AND SupplierInvoiceStatus = 'Draft'
AND SupplierInvoiceReceiveOrderPoID = ?
AND DATE_FORMAT(SupplierInvoiceDate, '%Y-%m') = ?
LIMIT 1";
$qryDuplikat = $this->db->query($sqlDuplikat, [$receiveOrderPoID, $monthKey]);
if (!$qryDuplikat) {
$this->sys_error_db("Gagal mengecek duplikasi supplier_invoice.");
exit;
}
if ($qryDuplikat->num_rows() > 0) {
$existing = $qryDuplikat->row_array();
$this->sys_ok([
"duplicate" => true,
"supplierInvoiceID" => $existing["SupplierInvoiceID"],
"supplierInvoiceNumber" => $existing["SupplierInvoiceNumber"]
]);
return;
}
$this->db->trans_begin();
$sqlHeader = "INSERT INTO supplier_invoice (
SupplierInvoiceNumber,
SupplierInvoiceReceiveOrderPoID,
SupplierInvoiceDate,
SupplierInvoiceDueDate,
SupplierInvoiceDraftPaymentDate,
SupplierInvoiceSupplierID,
SupplierInvoiceSupplierInvoiceNumber,
SupplierInvoiceSupplierInvoiceDate,
SupplierInvoiceSubTotal,
SupplierInvoiceDiscountPercent,
SupplierInvoiceDiscountAmount,
SupplierInvoiceTaxPercentPph,
SupplierInvoiceTaxAmountPph,
SupplierInvoiceTaxPercentPpn,
SupplierInvoiceTaxAmountPpn,
SupplierInvoiceShippingCost,
SupplierInvoiceAdjustmentAmount,
SupplierInvoiceAdjustmentNote,
SupplierInvoiceGrandTotal,
SupplierInvoiceUnpaid,
SupplierInvoiceNote,
SupplierInvoiceStatus,
SupplierInvoiceCreatedUserID
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
$qryHeader = $this->db->query($sqlHeader, [
$para["nomorPI"],
$receiveOrderPoID,
$tanggalPI,
$tanggalJatuhTempo,
$tanggalPI,
(int) $para["supplierID"],
null,
null,
$jumlahCicilan,
0,
0,
0,
0,
0,
0,
0,
0,
null,
$jumlahCicilan,
$jumlahCicilan,
$para["catatan"],
"Draft",
(int) $para["userID"]
]);
if (!$qryHeader) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert header supplier_invoice.");
exit;
}
$supplierInvoiceID = $this->db->insert_id();
$sqlDetail = "INSERT INTO supplier_invoice_detail (
SupplierInvoiceDetailSupplierInvoiceID,
SupplierInvoiceDetailPurchaseOrderID,
SupplierInvoiceDetailReceiveOrderPoID,
SupplierInvoiceDetailPurchaseOrderSummaryID,
SupplierInvoiceDetailItemID,
SupplierInvoiceDetailItemUnitID,
SupplierInvoiceDetailDescription,
SupplierInvoiceDetailQty,
SupplierInvoiceDetailPrice,
SupplierInvoiceDetailDiscountPercent,
SupplierInvoiceDetailDiscountDiscountRupiah,
SupplierInvoiceDetailDiscountDiscountType,
SupplierInvoiceDetailDiscountPoProrata,
SupplierInvoiceDetailDiscountAmount,
SupplierInvoiceDetailTotal,
SupplierInvoiceDetailUnpaid,
SupplierInvoiceDetailCreatedUserID
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
$qryDetail = $this->db->query($sqlDetail, [
$supplierInvoiceID,
(int) $para["purchaseOrderID"],
$receiveOrderPoID,
(int) $para["purchaseOrderSummaryID"],
(int) $para["purchaseOrderSummaryItemID"],
(int) $para["purchaseOrderSummaryItemUnitID"],
$para["deskripsi"],
1,
$jumlahCicilan,
0,
0,
"R",
0,
0,
$jumlahCicilan,
$jumlahCicilan,
(int) $para["userID"]
]);
if (!$qryDetail) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert detail supplier_invoice_detail.");
exit;
}
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
$this->sys_error_db("Transaksi insert supplier_invoice gagal.");
exit;
}
$this->db->trans_commit();
$this->sys_ok([
"duplicate" => false,
"supplierInvoiceID" => $supplierInvoiceID,
"supplierInvoiceNumber" => $para["nomorPI"]
]);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
private function isValidDate($tanggal)
{
$d = DateTime::createFromFormat("Y-m-d", $tanggal);
return $d && $d->format("Y-m-d") === $tanggal;
}
}

54
scripts/deploy-controllers.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="accone.aplikasi.web.id"
LOCAL_PATH="application/controllers/"
REMOTE_PATH="/home/one/project/accone/one-api/application/controllers/"
cd "$(dirname "$0")/.."
if ! command -v rsync >/dev/null 2>&1; then
echo "rsync is required but was not found in PATH." >&2
exit 1
fi
if ! command -v ssh >/dev/null 2>&1; then
echo "ssh is required but was not found in PATH." >&2
exit 1
fi
if ! command -v awk >/dev/null 2>&1; then
echo "awk is required but was not found in PATH." >&2
exit 1
fi
usage() {
echo "Usage: $0 <test|sync> [remote_user]" >&2
echo "Examples:" >&2
echo " $0 test" >&2
echo " $0 sync" >&2
echo " $0 test one" >&2
}
COMMAND="${1:-}"
REMOTE_USER="${2:-one}"
REMOTE="${REMOTE_USER}@${REMOTE_HOST}"
case "$COMMAND" in
test)
echo "Checking SSH connection to ${REMOTE}..."
ssh -o ConnectTimeout=10 "$REMOTE" "test -d '$REMOTE_PATH'"
echo
echo "Files/folders that would be uploaded:"
rsync -rzcin --out-format="%i %n%L" "$LOCAL_PATH" "${REMOTE}:${REMOTE_PATH}" \
| awk '$1 ~ /^</ || $1 ~ /^cd/ { print }'
;;
sync)
rsync -rzcv "$LOCAL_PATH" "${REMOTE}:${REMOTE_PATH}"
;;
*)
usage
exit 1
;;
esac