Compare commits
6 Commits
b03ae67f75
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e5727cc78 | |||
| 796fa04a37 | |||
| a008204865 | |||
| 5347d89acb | |||
| 8d8a5e48d5 | |||
| 805cb44530 |
104
README.md
104
README.md
@@ -1,2 +1,106 @@
|
|||||||
# be-accone
|
# be-accone
|
||||||
|
|
||||||
|
Backend service for Accone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Generate Monthly Installments (Cicilan Aset)
|
||||||
|
|
||||||
|
Automatically creates `supplier_installment` rows for all eligible active contracts every month. This is designed to be executed via **CRON daily at 1:00 AM**.
|
||||||
|
|
||||||
|
> **Note:** No journal entries are created here — that happens at cashier payment time. The contract's `InstallmentPaid` field is also **not** updated here — it's updated when payment is received.
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /one-api/mockup/scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---------------|--------------------|
|
||||||
|
| **Method** | `POST` |
|
||||||
|
| **Content-Type** | `application/json` |
|
||||||
|
| **Body** | `{}` (optional `startDate` / `endDate`) |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST "https://accone.aplikasi.web.id/one-api/mockup/scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Optional body parameters:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"startDate": "2026-07-01",
|
||||||
|
"endDate": "2026-07-31"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If omitted, defaults to the current month (`startDate` = first day, `endDate` = last day).
|
||||||
|
|
||||||
|
### Eligibility Logic (Steps 1–5)
|
||||||
|
|
||||||
|
A contract is eligible for installment generation only if **all** of the following conditions are met:
|
||||||
|
|
||||||
|
| Step | Condition |
|
||||||
|
|------|-----------|
|
||||||
|
| **1** | Contract is **active** (`IsActive = 'Y'`), status is **"belum lunas"**, has a positive `InstallmentPayAmount`, `InstallmentPaid < InstallmentNumber`, falls within the date range, and the scheduled day of month has been reached |
|
||||||
|
| **2** | Associated **Purchase Order** is **Approved** and **Active** |
|
||||||
|
| **3** | Associated **Receive Order** is **Confirmed** and **Active** |
|
||||||
|
| **4** | A parent **Supplier Invoice** exists with `IsInstallment = 'Y'` |
|
||||||
|
| **5** | No `supplier_installment` record already exists for the same PO in the same month |
|
||||||
|
|
||||||
|
### Step 6 — Insert
|
||||||
|
|
||||||
|
For each eligible contract, a row is inserted into `supplier_installment` with:
|
||||||
|
|
||||||
|
- `SupplierInstallmentStatus` = `"Pending"`
|
||||||
|
- `SupplierInstallmentIsLunas` = `"N"`
|
||||||
|
- `SupplierInstallmentDate` = the contract's scheduled day of month (capped to the last day of the month)
|
||||||
|
- `SupplierInstallmentDueDate` = same as InstallmentDate, can be changed at payment instrutions manu
|
||||||
|
|
||||||
|
### Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"data": {
|
||||||
|
"startDate": "2026-07-01",
|
||||||
|
"endDate": "2026-07-31",
|
||||||
|
"totalEligible": 1,
|
||||||
|
"totalInserted": 1,
|
||||||
|
"created": [
|
||||||
|
{
|
||||||
|
"installmentID": 6,
|
||||||
|
"contractID": "11",
|
||||||
|
"purchaseOrderID": "20",
|
||||||
|
"parentInvoiceID": "16",
|
||||||
|
"parentInvoiceNumber": "PI-016/SDM-14/SM-1/VII/2026",
|
||||||
|
"amount": 5000000,
|
||||||
|
"installDate": "2026-07-22",
|
||||||
|
"dueDate": "2026-07-22"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `status` | `string` | `"OK"` on success |
|
||||||
|
| `data.startDate` | `string` | Start of the billing period (`YYYY-MM-DD`) |
|
||||||
|
| `data.endDate` | `string` | End of the billing period (`YYYY-MM-DD`) |
|
||||||
|
| `data.totalEligible` | `number` | Total contracts that passed eligibility |
|
||||||
|
| `data.totalInserted` | `number` | Total installments successfully created |
|
||||||
|
| `data.created` | `array` | List of generated installment records |
|
||||||
|
| `data.created[].installmentID` | `number` | ID of the created `supplier_installment` row |
|
||||||
|
| `data.created[].contractID` | `string` | Associated contract ID (`PurchaseOrderAssetContract`) |
|
||||||
|
| `data.created[].purchaseOrderID` | `string` | Associated purchase order ID |
|
||||||
|
| `data.created[].parentInvoiceID` | `string` | Source supplier invoice ID |
|
||||||
|
| `data.created[].parentInvoiceNumber` | `string` | Source supplier invoice number |
|
||||||
|
| `data.created[].amount` | `number` | Installment amount (IDR) |
|
||||||
|
| `data.created[].installDate` | `string` | Date the installment is issued |
|
||||||
|
| `data.created[].dueDate` | `string` | Payment due date (`installDate + 7 days`) |
|
||||||
|
|||||||
@@ -1635,55 +1635,12 @@ class Fakturv4 extends MY_Controller
|
|||||||
0,
|
0,
|
||||||
$kredit
|
$kredit
|
||||||
);
|
);
|
||||||
if (!$status) {
|
if (!$status['status']) {
|
||||||
$this->sys_error_db($status['msg']);
|
$this->sys_error_db($status['msg']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
} else if ($item['M_ItemItem_CategoryID'] == '2') {
|
} else if ($item['M_ItemItem_CategoryID'] == '2') {
|
||||||
if (empty($item['M_ItemM_InventarisGolID'])) {
|
$this->InsertJurnalTxInventaris($jurnalID, $item, $inv, $user['M_UserID']);
|
||||||
$this->db->trans_rollback();
|
|
||||||
$this->sys_error_db("[Error] item golongan inventaris belum ditentukan");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql_coainv = "SELECT
|
|
||||||
CoaMapInventarisHutangCoaID,
|
|
||||||
CoaMapInventarisHutangCoaNo,
|
|
||||||
CoaMapInventarisHutangCoaDesc
|
|
||||||
FROM coa_map_inventaris
|
|
||||||
WHERE CoaMapInventarisM_InventarisGolID = ?
|
|
||||||
AND CoaMapInventarisIsActive = 'Y'";
|
|
||||||
$que_coainv = $this->db->query($sql_coainv, [$item['M_ItemM_InventarisGolID']]);
|
|
||||||
if (!$que_coainv) {
|
|
||||||
$this->db->trans_rollback();
|
|
||||||
$this->sys_error_db("[Error] failed to get coa inventaris gol");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$item_coainv = $que_coainv->row_array();
|
|
||||||
if (empty($item_coainv)) {
|
|
||||||
$this->db->trans_rollback();
|
|
||||||
$this->sys_error_db("[Error] item inventaris coa not found");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$price = (doubleval($inv['SupplierInvoiceDetailTotal']) - doubleval($inv['SupplierInvoiceDetailDiscountPoProrata']));
|
|
||||||
// $taxPPH = doubleval($inv['SupplierInvoiceTaxPercentPph']) * $price / 100;
|
|
||||||
$taxPPN = doubleval($inv['SupplierInvoiceTaxPercentPpn']) * $price / 100;
|
|
||||||
|
|
||||||
$kredit = round($price + $taxPPN, 2);
|
|
||||||
|
|
||||||
$status = $this->InsertJurnalTx(
|
|
||||||
$jurnalID,
|
|
||||||
$item_coainv['CoaMapInventarisHutangCoaID'],
|
|
||||||
$item_coainv['CoaMapInventarisHutangCoaDesc'],
|
|
||||||
$user['M_UserID'],
|
|
||||||
0,
|
|
||||||
$kredit
|
|
||||||
);
|
|
||||||
if (!$status) {
|
|
||||||
$this->sys_error_db($status['msg']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
} else if ($item['M_ItemItem_CategoryID'] == '3') {
|
} else if ($item['M_ItemItem_CategoryID'] == '3') {
|
||||||
$this->InsertJurnalTxAsset($jurnalID, $item['M_ItemID'], $inv, $user['M_UserID']);
|
$this->InsertJurnalTxAsset($jurnalID, $item['M_ItemID'], $inv, $user['M_UserID']);
|
||||||
} else if ($item['M_ItemItem_CategoryID'] == '4') {
|
} else if ($item['M_ItemItem_CategoryID'] == '4') {
|
||||||
@@ -1720,6 +1677,71 @@ class Fakturv4 extends MY_Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function InsertJurnalTxInventaris($jurnalID, $items, $invoice, $userid)
|
||||||
|
{
|
||||||
|
if (empty($items['M_ItemM_InventarisGolID'])) {
|
||||||
|
$this->db->trans_rollback();
|
||||||
|
$this->sys_error_db("[Error] item golongan inventaris belum ditentukan");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_coa = "SELECT
|
||||||
|
COALESCE(
|
||||||
|
i.M_InventarisItemCoaMappingID,
|
||||||
|
g.M_InventarisCoaMappingID
|
||||||
|
) AS MappingID,
|
||||||
|
COALESCE(
|
||||||
|
i.M_InventarisItemCoaMappingCoaHutangID,
|
||||||
|
g.M_InventarisCoaMappingCoaHutangID
|
||||||
|
) AS CoaHutangID,
|
||||||
|
c.coaDescription AS CoaHutangDesc
|
||||||
|
FROM (SELECT 1) AS inventory
|
||||||
|
LEFT JOIN m_inventaris_item_coa_mapping i
|
||||||
|
ON i.M_InventarisItemCoaMappingM_ItemID = ?
|
||||||
|
AND i.M_InventarisItemCoaMappingIsActive = 'Y'
|
||||||
|
LEFT JOIN m_inventaris_coa_mapping g
|
||||||
|
ON g.M_InventarisCoaMappingM_InventarisGolID = ?
|
||||||
|
AND g.M_InventarisCoaMappingIsActive = 'Y'
|
||||||
|
LEFT JOIN coa c
|
||||||
|
ON c.coaID = COALESCE(i.M_InventarisItemCoaMappingCoaHutangID, g.M_InventarisCoaMappingCoaHutangID)
|
||||||
|
AND c.coaIsActive = 'Y'";
|
||||||
|
$que_coa = $this->db->query($sql_coa, [
|
||||||
|
$items['M_ItemID'],
|
||||||
|
$items['M_ItemM_InventarisGolID'],
|
||||||
|
]);
|
||||||
|
if (!$que_coa) {
|
||||||
|
$this->db->trans_rollback();
|
||||||
|
$this->sys_error_db("[Error] failed query coa hutang inventaris");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$item_coainv = $que_coa->row_array();
|
||||||
|
if (empty($item_coainv['CoaHutangID'])) {
|
||||||
|
$this->db->trans_rollback();
|
||||||
|
$this->sys_error_db("[Error] coa ivnentory item {$items['M_ItemDesc']} not found");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invtotal = doubleval($invoice['SupplierInvoiceDetailTotal']);
|
||||||
|
$disprorata = doubleval($invoice['SupplierInvoiceDetailDiscountPoProrata']);
|
||||||
|
$price = $invtotal - $disprorata;
|
||||||
|
$taxPPN = doubleval($invoice['SupplierInvoiceTaxPercentPpn']) * $price / 100;
|
||||||
|
$kredit = round($price + $taxPPN, 2);
|
||||||
|
|
||||||
|
$insert = $this->InsertJurnalTx(
|
||||||
|
$jurnalID,
|
||||||
|
$item_coainv['CoaHutangID'],
|
||||||
|
$item_coainv['CoaHutangDesc'],
|
||||||
|
$userid,
|
||||||
|
0,
|
||||||
|
$kredit
|
||||||
|
);
|
||||||
|
if (!$insert['status']) {
|
||||||
|
$this->db->trans_rollback();
|
||||||
|
$this->sys_error_db($insert['msg']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function InsertJurnalTxJasa($jurnalID, $itemid, $invoice, $userid)
|
private function InsertJurnalTxJasa($jurnalID, $itemid, $invoice, $userid)
|
||||||
{
|
{
|
||||||
$sql_coa = "SELECT
|
$sql_coa = "SELECT
|
||||||
@@ -1759,7 +1781,7 @@ class Fakturv4 extends MY_Controller
|
|||||||
0,
|
0,
|
||||||
$kredit
|
$kredit
|
||||||
);
|
);
|
||||||
if (!$insert) {
|
if (!$insert['status']) {
|
||||||
$this->db->trans_rollback();
|
$this->db->trans_rollback();
|
||||||
$this->sys_error_db($insert['msg']);
|
$this->sys_error_db($insert['msg']);
|
||||||
exit;
|
exit;
|
||||||
@@ -1804,7 +1826,7 @@ class Fakturv4 extends MY_Controller
|
|||||||
0,
|
0,
|
||||||
$finalValue
|
$finalValue
|
||||||
);
|
);
|
||||||
if (!$insert) {
|
if (!$insert['status']) {
|
||||||
$this->db->trans_rollback();
|
$this->db->trans_rollback();
|
||||||
$this->sys_error_db($insert['msg']);
|
$this->sys_error_db($insert['msg']);
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -786,8 +786,8 @@ class PurchaseOrderAset extends MY_Controller {
|
|||||||
$PurchaseOrderID,
|
$PurchaseOrderID,
|
||||||
$para['supplierID'],
|
$para['supplierID'],
|
||||||
$summary_downpayment ?: 0.00,
|
$summary_downpayment ?: 0.00,
|
||||||
$para['contractStart'],
|
$para['contractDate'],
|
||||||
$para['contractStart'],
|
$para['contractDate'],
|
||||||
'Draft',
|
'Draft',
|
||||||
$user['M_UserID'],
|
$user['M_UserID'],
|
||||||
$user['M_UserID']
|
$user['M_UserID']
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,7 @@ class ReceiveItemPoInventaris extends MY_Controller
|
|||||||
$qry = $this->db->query($sql, [$user['M_BranchID'], $search]);
|
$qry = $this->db->query($sql, [$user['M_BranchID'], $search]);
|
||||||
|
|
||||||
if (!$qry) {
|
if (!$qry) {
|
||||||
$this->sys_error_db("[Error] query listing ruangan");;
|
$this->sys_error_db("[Error] query listing ruangan");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2693,36 +2693,6 @@ class ReceiveItemPoInventaris extends MY_Controller
|
|||||||
|
|
||||||
# generate barcode number each inventory item #
|
# generate barcode number each inventory item #
|
||||||
$noBarcode = $this->generateNoBarcode($user, $param['ruanganID'], 'BNP', $item['ReceiveOrderPoItemID']);
|
$noBarcode = $this->generateNoBarcode($user, $param['ruanganID'], 'BNP', $item['ReceiveOrderPoItemID']);
|
||||||
$sql_insert_barcode = "INSERT INTO t_barcode_barang(
|
|
||||||
T_BarcodeBarangReceiveOrderPoID,
|
|
||||||
T_BarcodeBarangReceiveOrderPoDetailID,
|
|
||||||
T_BarcodeBarangRefType,
|
|
||||||
T_BarcodeBarangM_ItemID,
|
|
||||||
T_BarcodeBarangItemUnitID,
|
|
||||||
T_BarcodeBarangNumber,
|
|
||||||
T_BarcodeBarangM_RuanganID,
|
|
||||||
T_BarcodeBarangM_BranchID,
|
|
||||||
T_BarcodeBarangIsActive,
|
|
||||||
T_BarcodeBarangUserID,
|
|
||||||
T_BarcodeBarangCreated
|
|
||||||
) VALUES(?,?,'PO',?,?,?,?,?,'Y',?,NOW())";
|
|
||||||
$que_insert_barcode = $this->db->query($sql_insert_barcode, [
|
|
||||||
$param['receiveOrderPoID'],
|
|
||||||
$item['ReceiveOrderPoDetailID'],
|
|
||||||
$item['ReceiveOrderPoItemID'],
|
|
||||||
$item['ReceiveOrderPoItemUnitID'],
|
|
||||||
$noBarcode,
|
|
||||||
$param['ruanganID'],
|
|
||||||
$user['M_BranchID'],
|
|
||||||
$user['M_UserID']
|
|
||||||
]);
|
|
||||||
if (!$que_insert_barcode) {
|
|
||||||
$this->db->trans_rollback();
|
|
||||||
$this->sys_error_db("[Error] insert no barcode invetaris");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$barcodeID = $this->db->insert_id();
|
|
||||||
|
|
||||||
# check if item already in stock #
|
# check if item already in stock #
|
||||||
$sql_cekstock = "SELECT StockID
|
$sql_cekstock = "SELECT StockID
|
||||||
@@ -2744,6 +2714,39 @@ class ReceiveItemPoInventaris extends MY_Controller
|
|||||||
}
|
}
|
||||||
$stockID = $que_cekstock->row_array()['StockID'];
|
$stockID = $que_cekstock->row_array()['StockID'];
|
||||||
|
|
||||||
|
$sql_insert_barcode = "INSERT INTO t_barcode_barang(
|
||||||
|
T_BarcodeBarangStockID,
|
||||||
|
T_BarcodeBarangReceiveOrderPoID,
|
||||||
|
T_BarcodeBarangReceiveOrderPoDetailID,
|
||||||
|
T_BarcodeBarangRefType,
|
||||||
|
T_BarcodeBarangM_ItemID,
|
||||||
|
T_BarcodeBarangItemUnitID,
|
||||||
|
T_BarcodeBarangNumber,
|
||||||
|
T_BarcodeBarangM_RuanganID,
|
||||||
|
T_BarcodeBarangM_BranchID,
|
||||||
|
T_BarcodeBarangIsActive,
|
||||||
|
T_BarcodeBarangUserID,
|
||||||
|
T_BarcodeBarangCreated
|
||||||
|
) VALUES(?,?,?,'PO',?,?,?,?,?,'Y',?,NOW())";
|
||||||
|
$que_insert_barcode = $this->db->query($sql_insert_barcode, [
|
||||||
|
$stockID,
|
||||||
|
$param['receiveOrderPoID'],
|
||||||
|
$item['ReceiveOrderPoDetailID'],
|
||||||
|
$item['ReceiveOrderPoItemID'],
|
||||||
|
$item['ReceiveOrderPoItemUnitID'],
|
||||||
|
$noBarcode,
|
||||||
|
$param['ruanganID'],
|
||||||
|
$user['M_BranchID'],
|
||||||
|
$user['M_UserID']
|
||||||
|
]);
|
||||||
|
if (!$que_insert_barcode) {
|
||||||
|
$this->db->trans_rollback();
|
||||||
|
$this->sys_error_db("[Error] insert no barcode invetaris");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$barcodeID = $this->db->insert_id();
|
||||||
|
|
||||||
# insert stock inventaris #
|
# insert stock inventaris #
|
||||||
$sql_stockinventaris = "INSERT INTO stock_inventory (
|
$sql_stockinventaris = "INSERT INTO stock_inventory (
|
||||||
StockInventoryStockID,
|
StockInventoryStockID,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class PurchaseInvoiceInstallment extends MY_Controller
|
|||||||
$row["SupplierInvoiceID"],
|
$row["SupplierInvoiceID"],
|
||||||
$amount,
|
$amount,
|
||||||
$installDate,
|
$installDate,
|
||||||
$dueDate,
|
$installDate,
|
||||||
$createdBy
|
$createdBy
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -441,10 +441,6 @@ class PaymentV2 extends MY_Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// "PI Cicilan Aset - {$invoiceNumber}";
|
|
||||||
$description = '';
|
|
||||||
// "Jurnal cicilan ke-{$cicilanKe} dari {$totalCicilan} | {$namaKontrak} | PO: {$nomorPO} | Periode {$periode}";
|
|
||||||
|
|
||||||
switch ($param['type']) {
|
switch ($param['type']) {
|
||||||
case 'DP':
|
case 'DP':
|
||||||
$data_bayar['jurnaltitle'] .= " | Pembayaran Uang Muka Aset - {$param['dpkode']}";
|
$data_bayar['jurnaltitle'] .= " | Pembayaran Uang Muka Aset - {$param['dpkode']}";
|
||||||
|
|||||||
Reference in New Issue
Block a user