Compare commits
36 Commits
main
...
feat_suppl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82b93583d0 | ||
|
|
b29eb58db6 | ||
|
|
731b940fb5 | ||
|
|
73ea318079 | ||
|
|
d8a65bc5a8 | ||
|
|
e912adeee0 | ||
|
|
be7f3b42be | ||
|
|
048c35473c | ||
|
|
562caa467d | ||
| c83c42698a | |||
| 009aff7d44 | |||
| 6dbe0e400b | |||
| 0ea56f09fa | |||
| a6412c9688 | |||
| bcb553ea56 | |||
| 2874fab71d | |||
| 752c372c2f | |||
| c125289b92 | |||
| fe5e6dd6cb | |||
| 5fd6db863a | |||
| fc9947bce4 | |||
| c296d5598b | |||
| 5bd8e038fc | |||
| 8f07a65485 | |||
| 7e88cf247b | |||
| 7af9fb2ca4 | |||
| 77fc97e964 | |||
| b7a00bc242 | |||
| 834f23f26e | |||
| dc88589c78 | |||
| 3871a680dd | |||
| 13af691645 | |||
| 775a390bcb | |||
| 9c87e9ce25 | |||
| 0d1dcb1b5e | |||
| 22719de06f |
@@ -172,6 +172,302 @@ Content-Type: application/json
|
||||
"ID": ""
|
||||
}
|
||||
|
||||
###
|
||||
// upload attachment — requires multipart/form-data with file(s)
|
||||
// Gunakan REST Client atau Postman. Contoh format:
|
||||
// POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/uploadAttachment/
|
||||
// Content-Type: multipart/form-data; boundary=----boundary
|
||||
//
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="token"
|
||||
//
|
||||
// eyJ...
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="ponumber"
|
||||
//
|
||||
// PO24070001
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="poID"
|
||||
//
|
||||
// 1
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="contractID"
|
||||
//
|
||||
// 1
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="files"; filename="foto1.jpg"
|
||||
// Content-Type: image/jpeg
|
||||
//
|
||||
// < /path/to/foto1.jpg
|
||||
// ------boundary
|
||||
// Content-Disposition: form-data; name="files"; filename="foto2.jpg"
|
||||
// Content-Type: image/jpeg
|
||||
//
|
||||
// < /path/to/foto2.jpg
|
||||
// ------boundary--
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}}
|
||||
}
|
||||
|
||||
###
|
||||
// listing po asset
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/getDaftarPoAset/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"search": "",
|
||||
"currpage": 1,
|
||||
"startdate": "2026-01-01",
|
||||
"enddate": "2026-12-31",
|
||||
"status": "All"
|
||||
}
|
||||
|
||||
###
|
||||
// get daftar attachment
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/getDaftarAttachment/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"contractID": 1,
|
||||
"poID": 1
|
||||
}
|
||||
|
||||
###
|
||||
// update approval PO — verifikasi manager (level 1)
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/updateApprovalPO/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"poID": 1,
|
||||
"approvelevel": "1"
|
||||
}
|
||||
|
||||
###
|
||||
// get data kontrak po asset
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/getDataKontrakPoAset/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"poID": 1
|
||||
}
|
||||
|
||||
###
|
||||
// create po asset
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/createPoAsset/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"podate": "2026-07-09",
|
||||
"reference": "REF-ASSET-001",
|
||||
"supplierID": 1,
|
||||
"typepajak": "nominal",
|
||||
"valuepajak": 0,
|
||||
"typediskon": "nominal",
|
||||
"valuediskon": 0,
|
||||
"catatan": "PO Asset Test",
|
||||
"summary": {
|
||||
"subtotal": 775000000,
|
||||
"diskon": 37500000,
|
||||
"pajak": 81125000,
|
||||
"total": 818625000
|
||||
},
|
||||
"detail": [
|
||||
{
|
||||
"M_ItemID": 123,
|
||||
"ItemUnitID": 1,
|
||||
"RequestQty": 2,
|
||||
"SupplierPrice": 75000000,
|
||||
"TempTotal": 150000000,
|
||||
"DiskonType": "R",
|
||||
"DiskonAmount": 0,
|
||||
"detail": [
|
||||
{
|
||||
"PurchaseRequestID": 1,
|
||||
"PurchaseRequestDetailID": 1,
|
||||
"PurchaseRequestFlagID": 1,
|
||||
"M_ItemID": 123,
|
||||
"ItemUnitID": 1,
|
||||
"OriginalQty": 2,
|
||||
"RequestQty": 2,
|
||||
"SupplierPrice": 75000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"RequestQty": 5,
|
||||
"SupplierPrice": 25000000,
|
||||
"TempTotal": 125000000,
|
||||
"DiskonType": "P",
|
||||
"DiskonAmount": 10,
|
||||
"detail": [
|
||||
{
|
||||
"PurchaseRequestID": 2,
|
||||
"PurchaseRequestDetailID": 2,
|
||||
"PurchaseRequestFlagID": 2,
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"OriginalQty": 3,
|
||||
"RequestQty": 3,
|
||||
"SupplierPrice": 25000000
|
||||
},
|
||||
{
|
||||
"PurchaseRequestID": 2,
|
||||
"PurchaseRequestDetailID": 3,
|
||||
"PurchaseRequestFlagID": 3,
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"OriginalQty": 2,
|
||||
"RequestQty": 2,
|
||||
"SupplierPrice": 25000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"M_ItemID": 125,
|
||||
"ItemUnitID": 1,
|
||||
"RequestQty": 1,
|
||||
"SupplierPrice": 500000000,
|
||||
"TempTotal": 500000000,
|
||||
"DiskonType": "R",
|
||||
"DiskonAmount": 25000000,
|
||||
"detail": [
|
||||
{
|
||||
"PurchaseRequestID": 3,
|
||||
"PurchaseRequestDetailID": 4,
|
||||
"PurchaseRequestFlagID": 4,
|
||||
"M_ItemID": 125,
|
||||
"ItemUnitID": 1,
|
||||
"OriginalQty": 1,
|
||||
"RequestQty": 1,
|
||||
"SupplierPrice": 500000000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"assetAttachmentID": null,
|
||||
"contractName": "Kontrak Pengadaan Mesin X",
|
||||
"contractDate": "2026-07-09",
|
||||
"contractStart": "2026-07-09",
|
||||
"contractEnd": "2027-07-09",
|
||||
"contractDuration": 12,
|
||||
"installmentNumber": 12,
|
||||
"installmentDate": 1,
|
||||
"installmentPayAmount": 12500000,
|
||||
"installmentDownPaymentType": "nominal",
|
||||
"installmentDownPayment": 5000000
|
||||
}
|
||||
|
||||
###
|
||||
// edit po asset
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/editPoAsset/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"PurchaseOrderID": 1,
|
||||
"PurchaseOrderAssetContractID": 1,
|
||||
"podate": "2026-07-09",
|
||||
"reference": "REF-ASSET-001-UPDATED",
|
||||
"supplierID": 1,
|
||||
"typepajak": "percent",
|
||||
"valuepajak": 11,
|
||||
"typediskon": "nominal",
|
||||
"valuediskon": 0,
|
||||
"catatan": "PO Asset Updated",
|
||||
"summary": {
|
||||
"subtotal": 775000000,
|
||||
"diskon": 0,
|
||||
"pajak": 85250000,
|
||||
"total": 860250000
|
||||
},
|
||||
"detail": [
|
||||
{
|
||||
"M_ItemID": 123,
|
||||
"ItemUnitID": 1,
|
||||
"RequestQty": 3,
|
||||
"SupplierPrice": 75000000,
|
||||
"TempTotal": 225000000,
|
||||
"DiskonType": "R",
|
||||
"DiskonAmount": 0,
|
||||
"detail": [
|
||||
{
|
||||
"PurchaseRequestID": 1,
|
||||
"PurchaseRequestDetailID": 1,
|
||||
"PurchaseRequestFlagID": 1,
|
||||
"M_ItemID": 123,
|
||||
"ItemUnitID": 1,
|
||||
"OriginalQty": 3,
|
||||
"RequestQty": 3,
|
||||
"SupplierPrice": 75000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"RequestQty": 5,
|
||||
"SupplierPrice": 25000000,
|
||||
"TempTotal": 125000000,
|
||||
"DiskonType": "R",
|
||||
"DiskonAmount": 0,
|
||||
"detail": [
|
||||
{
|
||||
"PurchaseRequestID": 2,
|
||||
"PurchaseRequestDetailID": 2,
|
||||
"PurchaseRequestFlagID": 2,
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"OriginalQty": 3,
|
||||
"RequestQty": 3,
|
||||
"SupplierPrice": 25000000
|
||||
},
|
||||
{
|
||||
"PurchaseRequestID": 2,
|
||||
"PurchaseRequestDetailID": 3,
|
||||
"PurchaseRequestFlagID": 3,
|
||||
"M_ItemID": 124,
|
||||
"ItemUnitID": 2,
|
||||
"OriginalQty": 2,
|
||||
"RequestQty": 2,
|
||||
"SupplierPrice": 25000000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"assetAttachmentID": 1,
|
||||
"contractName": "Kontrak Pengadaan Mesin X - Revisi",
|
||||
"contractDate": "2026-07-10",
|
||||
"contractStart": "2026-07-10",
|
||||
"contractEnd": "2027-10-10",
|
||||
"contractDuration": 15,
|
||||
"installmentNumber": 15,
|
||||
"installmentDate": 5,
|
||||
"installmentPayAmount": 15000000,
|
||||
"installmentDownPaymentType": "persen",
|
||||
"installmentDownPayment": 20
|
||||
}
|
||||
|
||||
###
|
||||
// delete po asset
|
||||
POST https://{{host}}/mockup/purchase/order/PurchaseOrderAset/deletePoAsset/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": {{token}},
|
||||
"poID": 1,
|
||||
"contractID": 1,
|
||||
"ponumber": "PO24070001"
|
||||
}
|
||||
|
||||
### Test isValidMultiple Warehouse
|
||||
### Should return True karena UnitRequest = UnitPurchase
|
||||
GET https://{{host}}/mockup/purchase/order/PurchaseOrder/isValidMultipleWarehouse/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1946,18 +1946,18 @@ class ReceiveItemPoInventaris extends MY_Controller
|
||||
}
|
||||
|
||||
// insert stockid barcode barang
|
||||
$sql_update_barcode = "UPDATE t_barcode_barang SET
|
||||
T_BarcodeBarangStockID = ?
|
||||
WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
|
||||
$qry_update_barcode = $this->db->query($sql_update_barcode, [
|
||||
$stock_ID,
|
||||
$detail['ReceiveOrderPoDetailID']
|
||||
]);
|
||||
if (!$qry_update_barcode) {
|
||||
$this->db->trans_rollback();
|
||||
$this->sys_error_db("[Error] update barcode stockID", $this->db);
|
||||
exit;
|
||||
}
|
||||
// $sql_update_barcode = "UPDATE t_barcode_barang SET
|
||||
// T_BarcodeBarangStockID = ?
|
||||
// WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
|
||||
// $qry_update_barcode = $this->db->query($sql_update_barcode, [
|
||||
// $stock_ID,
|
||||
// $detail['ReceiveOrderPoDetailID']
|
||||
// ]);
|
||||
// if (!$qry_update_barcode) {
|
||||
// $this->db->trans_rollback();
|
||||
// $this->sys_error_db("[Error] update barcode stockID", $this->db);
|
||||
// exit;
|
||||
// }
|
||||
} else {
|
||||
$sql_insert_stock = "INSERT INTO stock (
|
||||
StockWarehouseAlmariID,
|
||||
@@ -1990,18 +1990,18 @@ class ReceiveItemPoInventaris extends MY_Controller
|
||||
$stock_qty_end = intval($batch['qty']);
|
||||
|
||||
// insert stockid barcode barang
|
||||
$sql_update_barcode = "UPDATE t_barcode_barang SET
|
||||
T_BarcodeBarangStockID = ?
|
||||
WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
|
||||
$qry_update_barcode = $this->db->query($sql_update_barcode, [
|
||||
$stockID,
|
||||
$detail['ReceiveOrderPoDetailID']
|
||||
]);
|
||||
if (!$qry_update_barcode) {
|
||||
$this->db->trans_rollback();
|
||||
$this->sys_error_db("[Error] update barcode stockID", $this->db);
|
||||
exit;
|
||||
}
|
||||
// $sql_update_barcode = "UPDATE t_barcode_barang SET
|
||||
// T_BarcodeBarangStockID = ?
|
||||
// WHERE T_BarcodeBarangReceiveOrderPoDetailID = ?";
|
||||
// $qry_update_barcode = $this->db->query($sql_update_barcode, [
|
||||
// $stockID,
|
||||
// $detail['ReceiveOrderPoDetailID']
|
||||
// ]);
|
||||
// if (!$qry_update_barcode) {
|
||||
// $this->db->trans_rollback();
|
||||
// $this->sys_error_db("[Error] update barcode stockID", $this->db);
|
||||
// exit;
|
||||
// }
|
||||
}
|
||||
|
||||
// insert stock card
|
||||
@@ -2736,6 +2736,47 @@ class ReceiveItemPoInventaris extends MY_Controller
|
||||
|
||||
$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 #
|
||||
$sql_insert_handover_detail = "INSERT INTO asset_handover_detail(
|
||||
AssetHandoverDetailAssetHandoverID,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 startDate–endDate
|
||||
* - PO sudah berstatus Approved
|
||||
* - Barang sudah diterima (receive_order_po sudah confirmed)
|
||||
*
|
||||
* Endpoint ini digunakan untuk preview/verifikasi sebelum proses generate jurnal
|
||||
* dijalankan secara massal oleh CRON.
|
||||
*
|
||||
* Parameter (GET/POST):
|
||||
* - startDate : Awal periode (YYYY-MM-DD). Default: tanggal 1 bulan ini.
|
||||
* - endDate : Akhir periode (YYYY-MM-DD). Default: tanggal akhir bulan ini.
|
||||
*
|
||||
* @return JSON { startDate, endDate, total, records[] }
|
||||
*/
|
||||
public function ListEligiblePurchaseOrderAssetContracts()
|
||||
{
|
||||
try {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
54
scripts/deploy-controllers.sh
Executable 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
|
||||
Reference in New Issue
Block a user