14 Commits

Author SHA1 Message Date
Hanan Askarim
0823867277 fix report fpdf 2026-07-17 15:39:07 +07:00
Hanan Askarim
7d8dd0f660 add report payment voucher 2026-07-17 10:03:45 +07:00
Hanan Askarim
76258c7c61 add report receive order asset 2026-07-16 16:52:34 +07:00
Hanan Askarim
578371dc01 add report po asset 2026-07-16 16:26:25 +07:00
Hanan Askarim
e186b5f491 add report receive item po inventaris 2026-07-16 15:40:38 +07:00
Hanan Askarim
eac89f6ddd add report receive item po persediaan 2026-07-16 14:18:35 +07:00
Hanan Askarim
67441e9666 add reprot receive transfer 2026-07-16 11:42:17 +07:00
Hanan Askarim
0236c44349 fix report surat jalan 2026-07-16 11:14:48 +07:00
Hanan Askarim
eee6993679 add report stock request np dan surat jalan 2026-07-16 10:12:30 +07:00
Hanan Askarim
57642e5277 add report stock request 2026-07-15 14:24:03 +07:00
Hanan Askarim
9e7493c18b hapus bagian tanda tangan 2026-07-15 13:34:37 +07:00
Hanan Askarim
1ace1600ce create api rpt pr direct approval 2026-07-15 11:54:37 +07:00
Hanan Askarim
c0cfc3ea88 fix update report prd 2026-07-15 11:35:26 +07:00
Hanan Askarim
a2f3509d78 create report fpdf pr direct 2026-07-15 10:21:46 +07:00
24 changed files with 4909 additions and 11239 deletions

104
README.md
View File

@@ -1,106 +1,2 @@
# 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 15)
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`) |

View File

@@ -1,10 +1,8 @@
<?php
class InventarisCoaMapping extends MY_Controller
{
class InventarisCoaMapping extends MY_Controller {
var $db;
public function index()
{
public function index() {
echo "Inventaris COA Mapping API";
}
@@ -13,226 +11,8 @@ class InventarisCoaMapping extends MY_Controller
parent::__construct();
}
## QUERY ##
public function getListCoa()
{
try {
if (!$this->isLogin) {
throw new Exception('Invalid token');
}
$para = $this->sys_input;
$keyword = "%" . $para['keyword'] . "%";
$sql = "SELECT
coaID,
coaAccountNo,
coaDescription
FROM coa
WHERE coaIsInput = 'Y'
AND (
coaDescription LIKE ?
OR coaAccountNo LIKE ?
)
AND coaIsActive = 'Y'
LIMIT 15";
$que = $this->db->query($sql, [$keyword, $keyword]);
if (!$que) {
throw new Exception('failed to query data inventaris gol', 1);
}
$data = $que->result_array();
$this->sys_ok($data);
} catch (Exception $e) {
$msg = '[Error] ' . $e->getMessage();
$code = $e->getCode();
if ($code == 0) {
$this->sys_error($msg);
} else {
$this->sys_error_db($msg);
}
exit;
}
}
public function getListInventarisGol()
{
try {
if (!$this->isLogin) {
throw new Exception('Invalid token');
}
$sql = "SELECT
M_InventarisGolID,
M_InventarisGolCode,
M_InventarisGolName
FROM m_inventaris_gol gol
WHERE gol.M_InventarisGolIsActive = 'Y'
AND NOT EXISTS (
SELECT 1
FROM m_inventaris_coa_mapping m
WHERE m.M_InventarisCoaMappingM_InventarisGolID = gol.M_InventarisGolID
AND m.M_InventarisCoaMappingIsActive = 'Y'
);";
$que = $this->db->query($sql);
if (!$que) {
throw new Exception('failed to query data inventaris gol', 1);
}
$data = $que->result_array();
$output = [
'records' => $data,
'total' => count($data)
];
$this->sys_ok($output);
} catch (Exception $e) {
$msg = '[Error] ' . $e->getMessage();
$code = $e->getCode();
if ($code == 0) {
$this->sys_error($msg);
} else {
$this->sys_error_db($msg);
}
exit;
}
}
public function getListInventorygolMapping()
{
try {
if (!$this->isLogin) {
throw new Exception('invalid token');
}
$para = $this->sys_input;
$keyword = "%" . $para['keyword'] . "%";
$limit = 10;
$offset = 0;
if ($para['currpage'] > 0) {
$offset = ($para['currpage'] - 1) * $limit;
}
$sql = "SELECT
M_InventarisGolID,
M_InventarisGolCode,
M_InventarisGolName,
M_InventarisCoaMappingID,
M_InventarisCoaMappingCoaInventarisID,
coaInv.coaAccountNo AS CoaInventarisAccountNo,
coaInv.coaDescription AS CoaInventarisDescription,
M_InventarisCoaMappingCoaHutangID,
coaHtg.coaAccountNo AS CoaHutangAccountNo,
coaHtg.coaDescription AS CoaHutangDescription,
M_InventarisCoaMappingCoaBebanPenyusutanID,
coaBbn.coaAccountNo AS CoaBebanPenyusutanAccountNo,
coaBbn.coaDescription AS CoaBebanPenyusutanDescription,
M_InventarisCoaMappingCoaAkumulasiPenyusutanID,
coaAkm.coaAccountNo AS CoaAkumulasiPenyusutanAccountNo,
coaAkm.coaDescription AS CoaAkumulasiPenyusutanDescription,
M_InventarisCoaMappingCoaLabaPelepasanID,
coaLab.coaAccountNo AS CoaLabaPelepasanAccountNo,
coaLab.coaDescription AS CoaLabaPelepasanDescription,
M_InventarisCoaMappingCoaRugiPelepasanID,
coaRug.coaAccountNo AS CoaRugiPelepasanAccountNo,
coaRug.coaDescription AS CoaRugiPelepasanDescription
FROM m_inventaris_gol
JOIN m_inventaris_coa_mapping
ON M_InventarisCoaMappingM_InventarisGolID = M_InventarisGolID
AND M_InventarisCoaMappingIsActive = 'Y'
AND M_InventarisGolIsActive = 'Y'
AND M_InventarisGolName LIKE ?
LEFT JOIN coa AS coaInv
ON M_InventarisCoaMappingCoaInventarisID = coaInv.coaID
LEFT JOIN coa AS coaHtg
ON M_InventarisCoaMappingCoaHutangID = coaHtg.coaID
LEFT JOIN coa AS coaBbn
ON M_InventarisCoaMappingCoaBebanPenyusutanID = coaBbn.coaID
LEFT JOIN coa AS coaAkm
ON M_InventarisCoaMappingCoaAkumulasiPenyusutanID = coaAkm.coaID
LEFT JOIN coa AS coaLab
ON M_InventarisCoaMappingCoaLabaPelepasanID = coaLab.coaID
LEFT JOIN coa AS coaRug
ON M_InventarisCoaMappingCoaRugiPelepasanID = coaRug.coaID
WHERE M_InventarisGolIsActive = 'Y'";
$sql_data = $sql . " ORDER BY M_InventarisGolID LIMIT ? OFFSET ? ";
$que = $this->db->query($sql_data, [
$keyword,
$limit,
$offset
]);
if (!$que) {
throw new Exception('failed to query data mapping coa inventaris golongan', 1);
}
$sql_total = "SELECT COUNT(*) AS total FROM ($sql) AS x";
$que_total = $this->db->query($sql_total, [$keyword]);
if (!$que_total) {
throw new Exception('failed to get total rows data', 1);
}
$this->sys_ok([
"records" => $que->result_array(),
"total" => $que_total->row_array()['total']
]);
} catch (Exception $e) {
$msg = '[Error] ' . $e->getMessage();
$code = $e->getCode();
if ($code == 0) {
$this->sys_error($msg);
} else {
$this->sys_error_db($msg);
}
exit;
}
}
public function getInvCoaMappingDetail()
{
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
exit;
}
$para = $this->sys_input;
$sql = "SELECT
M_InventarisCoaMappingID,
M_InventarisCoaMappingM_InventarisGolID,
M_InventarisCoaMappingCoaInventarisID,
M_InventarisCoaMappingCoaHutangID,
M_InventarisCoaMappingCoaPembelianID,
M_InventarisCoaMappingCoaBebanPenyusutanID,
M_InventarisCoaMappingCoaAkumulasiPenyusutanID,
M_InventarisCoaMappingCoaLabaPelepasanID,
M_InventarisCoaMappingCoaRugiPelepasanID,
M_InventarisCoaMappingCreatedUserID,
M_InventarisCoaMappingCreated,
M_InventarisCoaMappingLastUpdated
FROM m_inventaris_coa_mapping
WHERE M_InventarisCoaMappingIsActive = 'Y'
AND M_InventarisCoaMappingID = ?";
$query = $this->db->query($sql, [$para['M_InventarisCoaMappingID']]);
if (!$query) {
$this->sys_error_db("[Error] get data m_inventaris_coa_mapping");
exit;
}
$data = $query->row_array();
$this->sys_ok($data);
} catch (Exception $exc) {
$msg = $exc->getMessage();
$this->sys_error($msg);
}
}
## MUTATIONS ##
public function createInvCoaMapping()
{
public function createInvCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
@@ -284,8 +64,48 @@ class InventarisCoaMapping extends MY_Controller
}
}
public function editInvCoaMapping()
{
## QUERY ##
public function getInvCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
exit;
}
$para = $this->sys_input;
$sql = "SELECT
M_InventarisCoaMappingID,
M_InventarisCoaMappingM_InventarisGolID,
M_InventarisCoaMappingCoaInventarisID,
M_InventarisCoaMappingCoaHutangID,
M_InventarisCoaMappingCoaPembelianID,
M_InventarisCoaMappingCoaBebanPenyusutanID,
M_InventarisCoaMappingCoaAkumulasiPenyusutanID,
M_InventarisCoaMappingCoaLabaPelepasanID,
M_InventarisCoaMappingCoaRugiPelepasanID,
M_InventarisCoaMappingCreatedUserID,
M_InventarisCoaMappingCreated,
M_InventarisCoaMappingLastUpdated
FROM m_inventaris_coa_mapping
WHERE M_InventarisCoaMappingIsActive = 'Y'
AND M_InventarisCoaMappingID = ?";
$query = $this->db->query($sql, [$para['M_InventarisCoaMappingID']]);
if (!$query) {
$this->sys_error_db("[Error] get data m_inventaris_coa_mapping");
exit;
}
$data = $query->row_array();
$this->sys_ok($data);
} catch (Exception $exc) {
$msg = $exc->getMessage();
$this->sys_error($msg);
}
}
## MUTATIONS ##
public function editInvCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
@@ -334,8 +154,7 @@ class InventarisCoaMapping extends MY_Controller
}
}
public function deleteInvCoaMapping()
{
public function deleteInvCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
@@ -366,129 +185,8 @@ class InventarisCoaMapping extends MY_Controller
}
}
## QUERY ITEM ##
public function getListItemInventaris()
{
try {
if (!$this->isLogin) {
throw new Exception('Invalid token');
}
$para = $this->sys_input;
$sql = "SELECT
M_ItemID,
M_ItemCode,
M_ItemDesc,
M_ItemM_InventarisGolID AS itemGolID,
IFNULL(M_InventarisItemCoaMappingID, 0) AS itemCoaMapID,
M_InventarisItemCoaMappingCoaInventarisID,
coaInv.coaAccountNo AS CoaInventarisAccountNo,
coaInv.coaDescription AS CoaInventarisDescription,
M_InventarisItemCoaMappingCoaHutangID,
coaHtg.coaAccountNo AS CoaHutangAccountNo,
coaHtg.coaDescription AS CoaHutangDescription,
M_InventarisItemCoaMappingCoaBebanPenyusutanID,
coaBbn.coaAccountNo AS CoaBebanPenyusutanAccountNo,
coaBbn.coaDescription AS CoaBebanPenyusutanDescription,
M_InventarisItemCoaMappingCoaAkumulasiPenyusutanID,
coaAkm.coaAccountNo AS CoaAkumulasiPenyusutanAccountNo,
coaAkm.coaDescription AS CoaAkumulasiPenyusutanDescription,
M_InventarisItemCoaMappingCoaLabaPelepasanID,
coaLab.coaAccountNo AS CoaLabaPelepasanAccountNo,
coaLab.coaDescription AS CoaLabaPelepasanDescription,
M_InventarisItemCoaMappingCoaRugiPelepasanID,
coaRug.coaAccountNo AS CoaRugiPelepasanAccountNo,
coaRug.coaDescription AS CoaRugiPelepasanDescription
FROM m_item
LEFT JOIN m_inventaris_item_coa_mapping
ON M_ItemID = M_InventarisItemCoaMappingM_ItemID
AND M_InventarisItemCoaMappingIsActive = 'Y'
LEFT JOIN coa AS coaInv
ON M_InventarisItemCoaMappingCoaInventarisID = coaInv.coaID
LEFT JOIN coa AS coaHtg
ON M_InventarisItemCoaMappingCoaHutangID = coaHtg.coaID
LEFT JOIN coa AS coaBbn
ON M_InventarisItemCoaMappingCoaBebanPenyusutanID = coaBbn.coaID
LEFT JOIN coa AS coaAkm
ON M_InventarisItemCoaMappingCoaAkumulasiPenyusutanID = coaAkm.coaID
LEFT JOIN coa AS coaLab
ON M_InventarisItemCoaMappingCoaLabaPelepasanID = coaLab.coaID
LEFT JOIN coa AS coaRug
ON M_InventarisItemCoaMappingCoaRugiPelepasanID = coaRug.coaID
WHERE M_ItemItem_CategoryID = 2
AND M_ItemM_InventarisGolID = ?
AND M_ItemIsActive = 'Y'";
$que = $this->db->query($sql, [
$para['golID']
]);
if (!$que) {
throw new Exception('failed to query data inventaris gol', 1);
}
$data = $que->result_array();
$output = [
'records' => $data,
'total' => count($data)
];
$this->sys_ok($output);
} catch (Exception $e) {
$msg = '[Error] ' . $e->getMessage();
$code = $e->getCode();
if ($code == 0) {
$this->sys_error($msg);
} else {
$this->sys_error_db($msg);
}
exit;
}
}
public function getInvItemCoaMapping()
{
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
exit;
}
$para = $this->sys_input;
$sql = "SELECT
M_InventarisItemCoaMappingID,
M_InventarisItemCoaMappingM_ItemID,
M_InventarisItemCoaMappingCoaInventarisID,
M_InventarisItemCoaMappingCoaHutangID,
M_InventarisItemCoaMappingCoaPembelianID,
M_InventarisItemCoaMappingCoaBebanPenyusutanID,
M_InventarisItemCoaMappingCoaAkumulasiPenyusutanID,
M_InventarisItemCoaMappingCoaLabaPelepasanID,
M_InventarisItemCoaMappingCoaRugiPelepasanID,
M_InventarisItemCoaMappingCreatedUserID,
M_InventarisItemCoaMappingCreated,
M_InventarisItemCoaMappingLastUpdated
FROM m_inventaris_item_coa_mapping
WHERE M_InventarisItemCoaMappingIsActive = 'Y'
AND M_InventarisItemCoaMappingID = ?";
$query = $this->db->query($sql, [$para['M_InventarisItemCoaMappingID']]);
if (!$query) {
$this->sys_error_db("[Error] get data m_inventaris_item_coa_mapping");
exit;
}
$data = $query->row_array();
$this->sys_ok($data);
} catch (Exception $exc) {
$msg = $exc->getMessage();
$this->sys_error($msg);
}
}
## MUTATIONS ITEM ##
public function createInvItemCoaMapping()
{
public function createInvItemCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
@@ -533,15 +231,55 @@ class InventarisCoaMapping extends MY_Controller
$insertID = $this->db->insert_id();
$this->db->trans_commit();
$this->sys_ok("[Success] insert coa item");
$this->sys_ok($insertID);
} catch (Exception $exc) {
$msg = $exc->getMessage();
$this->sys_error($msg);
}
}
public function editInvItemCoaMapping()
{
## QUERY ITEM ##
public function getInvItemCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
exit;
}
$para = $this->sys_input;
$sql = "SELECT
M_InventarisItemCoaMappingID,
M_InventarisItemCoaMappingM_ItemID,
M_InventarisItemCoaMappingCoaInventarisID,
M_InventarisItemCoaMappingCoaHutangID,
M_InventarisItemCoaMappingCoaPembelianID,
M_InventarisItemCoaMappingCoaBebanPenyusutanID,
M_InventarisItemCoaMappingCoaAkumulasiPenyusutanID,
M_InventarisItemCoaMappingCoaLabaPelepasanID,
M_InventarisItemCoaMappingCoaRugiPelepasanID,
M_InventarisItemCoaMappingCreatedUserID,
M_InventarisItemCoaMappingCreated,
M_InventarisItemCoaMappingLastUpdated
FROM m_inventaris_item_coa_mapping
WHERE M_InventarisItemCoaMappingIsActive = 'Y'
AND M_InventarisItemCoaMappingID = ?";
$query = $this->db->query($sql, [$para['M_InventarisItemCoaMappingID']]);
if (!$query) {
$this->sys_error_db("[Error] get data m_inventaris_item_coa_mapping");
exit;
}
$data = $query->row_array();
$this->sys_ok($data);
} catch (Exception $exc) {
$msg = $exc->getMessage();
$this->sys_error($msg);
}
}
## MUTATIONS ITEM ##
public function editInvItemCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
@@ -590,8 +328,7 @@ class InventarisCoaMapping extends MY_Controller
}
}
public function deleteInvItemCoaMapping()
{
public function deleteInvItemCoaMapping() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");

View File

@@ -496,7 +496,6 @@ class Fakturv4 extends MY_Controller
ELSE ''
END as WarehouseName,
ReceiveOrderPoID,
ReceiveOrderPoTypePurchase,
PurchaseOrderItemCategoryID
FROM supplier_invoice
JOIN supplier_invoice_detail ON SupplierInvoiceID = SupplierInvoiceDetailSupplierInvoiceID
@@ -589,11 +588,12 @@ class Fakturv4 extends MY_Controller
'P'
) AS DiscountType,
SupplierInvoiceReceiveOrderPoID,
ReceiveOrderPoNumber,
ReceiveOrderPoTypePurchase AS typePurchase
ReceiveOrderPoNumber
FROM supplier_invoice
JOIN supplier ON SupplierInvoiceSupplierID = SupplierID
JOIN receive_order_po ON ReceiveOrderPoID = SupplierInvoiceReceiveOrderPoID
-- JOIN purchase_order ON SupplierInvoicePurchaseOrderID = PurchaseOrderID
-- AND PurchaseOrderIsActive = 'Y'
WHERE SupplierInvoiceIsActive = 'Y'
AND SupplierInvoiceID = ?";
$que = $this->db->query($sql, $para['SInvoiceID']);
@@ -601,7 +601,6 @@ class Fakturv4 extends MY_Controller
$this->sys_error_db('[Error] get detail data invoice');
exit;
}
$data = $que->row_array();
$sqldet = "SELECT
SupplierInvoiceDetailID,
@@ -637,30 +636,7 @@ class Fakturv4 extends MY_Controller
exit;
}
if (isset($data['typePurchase']) && $data['typePurchase'] == 'aset') {
$sql_dp = "SELECT
IFNULL(SupplierDownpaymentAmount, 0) AS dp_amount
FROM supplier_invoice
JOIN receive_order_po
ON ReceiveOrderPoID = SupplierInvoiceReceiveOrderPoID
JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractReceiveOrderPoID = ReceiveOrderPoID
AND PurchaseOrderAssetContractIsActive = 'Y'
JOIN supplier_downpayment
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderAssetContractPurchaseOrderID
AND SupplierDownpaymentIsActive = 'Y'
WHERE SupplierInvoiceID = ?";
$que_dp = $this->db->query($sql_dp, [
$para['SInvoiceID']
]);
if (!$que_dp) {
$this->sys_error_db("[Error] get info contract asset");
exit;
}
$dp_amount = $que_dp->row_array()['dp_amount'];
$data['dp_amount'] = $dp_amount;
}
$data = $que->result_array()[0];
$data['detail'] = $quedet->result_array();
$result = $data;
@@ -1341,8 +1317,7 @@ class Fakturv4 extends MY_Controller
supplier_invoice_detail.*,
ReceiveOrderPoNumber,
ReceiveOrderPoM_BranchCode,
ReceiveOrderPoS_RegionalID,
ReceiveOrderPoTypePurchase AS typePurchase
ReceiveOrderPoS_RegionalID
FROM supplier_invoice
JOIN supplier_invoice_detail ON SupplierInvoiceDetailSupplierInvoiceID = SupplierInvoiceID
AND SupplierInvoiceDetailIsActive = 'Y'
@@ -1478,6 +1453,7 @@ class Fakturv4 extends MY_Controller
$coapph = $quepph->result_array()[0];
$pphval = doubleval($invoice[0]['SupplierInvoiceTaxAmountPph']);
if ($pphval > 0.00) {
$insertpph = $this->InsertJurnalTx(
$jurnalID,
@@ -1489,14 +1465,13 @@ class Fakturv4 extends MY_Controller
);
if (!$insertpph['status']) {
$this->db->trans_rollback();
$this->sys_error_db($insertpph['msg']);
$this->sys_error_db($insertpajak['msg']);
exit;
}
}
# insert shipping cost ke jurnal #
// ----------------------------------------------------------------------------------------
if ($ship_cost > 0.00) {
$queryshipcost = $this->db->query($sqlcoa, ['5320700001']);
if (!$queryshipcost) {
$this->db->trans_rollback();
@@ -1505,6 +1480,7 @@ class Fakturv4 extends MY_Controller
}
$coashipcost = $queryshipcost->result_array()[0];
if ($ship_cost > 0.00) {
$insertcost = $this->InsertJurnalTx(
$jurnalID,
$coashipcost['coaID'],
@@ -1520,63 +1496,6 @@ class Fakturv4 extends MY_Controller
}
}
/* Insert jurnal tx for down payment asset */
// ----------------------------------------------------------------------------------------
if (isset($invoice[0]['typePurchase']) && $invoice[0]['typePurchase'] == 'aset') {
$sql_dpasset = "SELECT
PurchaseOrderAssetContractID,
PurchaseOrderAssetContractName,
SupplierDownpaymentAmount,
coaID,
coaDescription
FROM purchase_order_asset_contract
JOIN purchase_order ON PurchaseOrderID = PurchaseOrderAssetContractPurchaseOrderID
JOIN supplier_downpayment
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderID
AND SupplierDownpaymentIsActive = 'Y'
JOIN purchase_order_detail
ON PurchaseOrderID = PurchaseOrderDetailPurchaseOrderID
AND PurchaseOrderDetailIsActive = 'Y'
JOIN m_item ON M_ItemID = PurchaseOrderDetailItemID
AND M_ItemItem_CategoryID = 3
JOIN fa_class ON Fa_ClassID = M_ItemFa_ClassID
AND Fa_ClassIsActive = 'Y'
JOIN coa ON coaID = Fa_ClassDownPaymentCoaID
AND coaIsActive = 'Y'
WHERE PurchaseOrderAssetContractReceiveOrderPoID = ?
AND PurchaseOrderAssetContractIsActive = 'Y'
LIMIT 1";
$que_dpasset = $this->db->query($sql_dpasset, [
$invoice[0]['SupplierInvoiceReceiveOrderPoID']
]);
if (!$que_dpasset) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] query dp asset not error");
exit;
}
$dpasset_coa = $que_dpasset->row_array();
if (!empty($dpasset_coa)) {
$totalDP = round($dpasset_coa['SupplierDownpaymentAmount'], 2);
if ($totalDP > 0) {
$insert_dp = $this->InsertJurnalTx(
$jurnalID,
$dpasset_coa['coaID'],
$dpasset_coa['coaDescription'],
$user['M_UserID'],
0,
$totalDP
);
if (!$insert_dp['status']) {
$this->db->trans_rollback();
$this->sys_error_db($insert_dp['msg']);
exit;
}
$invoice = $this->CalcProrateDownPaymentAsset($invoice, $totalDP);
}
}
}
// insert jurnal tx hutang per item (kredit)
// ----------------------------------------------------------------------------------------
foreach ($invoice as $key => $inv) {
@@ -1635,14 +1554,89 @@ class Fakturv4 extends MY_Controller
0,
$kredit
);
if (!$status['status']) {
if (!$status) {
$this->sys_error_db($status['msg']);
exit;
}
} else if ($item['M_ItemItem_CategoryID'] == '2') {
$this->InsertJurnalTxInventaris($jurnalID, $item, $inv, $user['M_UserID']);
} else if ($item['M_ItemItem_CategoryID'] == '3') {
$this->InsertJurnalTxAsset($jurnalID, $item['M_ItemID'], $inv, $user['M_UserID']);
if (empty($item['M_ItemM_InventarisGolID'])) {
$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' && !empty($item['M_ItemFa_ClassID'])) {
$sqlsgp = "SELECT * FROM fa_class WHERE Fa_ClassID = ? AND Fa_ClassIsActive = 'Y'";
$quesgp = $this->db->query($sqlsgp, [$item['M_ItemFa_ClassID']]);
if (!$quesgp) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] get map_nat_subgroup");
exit;
}
$itemGroup = $quesgp->row_array();
if (empty($itemGroup)) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] fa class item tidak ditemukan / belum dimapping");
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,
$itemGroup['Fa_ClassCoaID'],
$itemGroup['Fa_ClassCoaDesc'],
$user['M_UserID'],
0,
$kredit
);
if (!$status) {
$this->sys_error_db($status['msg']);
exit;
}
} else if ($item['M_ItemItem_CategoryID'] == '4') {
$this->InsertJurnalTxJasa($jurnalID, $item['M_ItemID'], $inv, $user['M_UserID']);
}
@@ -1677,71 +1671,6 @@ 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)
{
$sql_coa = "SELECT
@@ -1781,100 +1710,13 @@ class Fakturv4 extends MY_Controller
0,
$kredit
);
if (!$insert['status']) {
if (!$insert) {
$this->db->trans_rollback();
$this->sys_error_db($insert['msg']);
exit;
}
}
private function InsertJurnalTxAsset($jurnalID, $itemid, $invoice, $userid)
{
$sql_coa = "SELECT
Fa_ClassHutangCoaID,
Fa_ClassHutangCoaAccountNo,
Fa_ClassHutangCoaDesc
FROM m_item
JOIN fa_class ON Fa_ClassID = M_ItemFa_ClassID
WHERE M_ItemID = ?";
$que_coa = $this->db->query($sql_coa, $itemid);
if (!$que_coa) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed get coa hutang asset");
exit;
}
$coaasset = $que_coa->row_array();
if ($que_coa->num_rows() <= 0) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] aset debt coa 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);
$finalValue = round($kredit - doubleval($invoice['dpReduction']), 2);
$insert = $this->InsertJurnalTx(
$jurnalID,
$coaasset['Fa_ClassHutangCoaID'],
$coaasset['Fa_ClassHutangCoaDesc'],
$userid,
0,
$finalValue
);
if (!$insert['status']) {
$this->db->trans_rollback();
$this->sys_error_db($insert['msg']);
exit;
}
}
private function CalcProrateDownPaymentAsset($items, $dpAmount)
{
$totalKredit = 0;
foreach ($items as $key => $value) {
$invtotal = doubleval($value['SupplierInvoiceDetailTotal']);
$disprorata = doubleval($value['SupplierInvoiceDetailDiscountPoProrata']);
$price = $invtotal - $disprorata;
$taxPPN = doubleval($value['SupplierInvoiceTaxPercentPpn']) * $price / 100;
$kredit = round($price + $taxPPN, 2);
$totalKredit = $totalKredit + $kredit;
$items[$key]['kredit'] = $kredit;
}
if ($totalKredit <= 0) {
foreach ($items as $key => $value) {
$items[$key]['dpReduction'] = 0;
}
return $items;
}
$runningReduction = 0;
end($items);
$lastKey = key($items);
reset($items);
foreach ($items as $key => $value) {
if ($key === $lastKey) {
$reduction = round($dpAmount - $runningReduction, 2);
} else {
$ratio = $value['kredit'] / $totalKredit;
$reduction = round($dpAmount * $ratio, 2);
$runningReduction += $reduction;
}
$items[$key]['dpReduction'] = $reduction;
}
return $items;
}
function InsertJurnalTx($jurnalID, $coaID, $desc, $userID, $debit = 0, $kredit = 0): array
{
try {

View File

@@ -66,40 +66,6 @@ class PurchaseOrderAset extends MY_Controller {
}
}
public function getListGudang() {
try {
if (!$this->isLogin) {
$this->sys_error("invalid token");
exit;
}
$user = $this->sys_user;
$para = $this->sys_input;
$sql = "SELECT
WarehouseID,
WarehouseCode,
WarehouseName
FROM warehouse
WHERE WarehouseIsActive = 'Y'
AND WarehouseS_RegionalID = ?
AND WarehouseM_BranchID = ?
AND WarehouseIsTransit = 'N'";
$que = $this->db->query($sql, [
$user['S_RegionalID'], $para['M_BranchID']
]);
if (!$que) {
$this->sys_error_db("[Error] failed get list gudang");
exit;
}
$data = $que->result_array();
$this->sys_ok($data);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
public function searchRequestAset() {
try {
if (!$this->isLogin) {
@@ -221,34 +187,16 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderS_RegionalID,
PurchaseOrderApprovedManagerUserID AS verifiedby,
IFNULL(PurchaseOrderApprovedUserID, 0) AS approvedby,
PurchaseOrderAssetContractID AS contractID,
PurchaseOrderAssetContractID,
PurchaseOrderAssetContractName AS contractName,
PurchaseOrderAssetContractDate AS contractDate,
PurchaseOrderAssetContractStartDate AS contractStart,
PurchaseOrderAssetContractEndDate AS contractEnd,
IFNULL(attach.AttachmentCount, 0) AS AttachmentCount,
SupplierID,
SupplierName,
SupplierDownpaymentID
SupplierName
FROM purchase_order
JOIN supplier ON SupplierID = PurchaseOrderSupplierID
JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID
LEFT JOIN supplier_downpayment
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderID
AND SupplierDownpaymentIsActive = 'Y'
LEFT JOIN (
SELECT
ContractAssetAttachmentPurchaseOrderID,
ContractAssetAttachmentPurchaseOrderAssetContractID,
COUNT(*) AS AttachmentCount
FROM contract_asset_attachment
WHERE ContractAssetAttachmentIsActive = 'Y'
GROUP BY
ContractAssetAttachmentPurchaseOrderID,
ContractAssetAttachmentPurchaseOrderAssetContractID
) attach ON attach.ContractAssetAttachmentPurchaseOrderID = PurchaseOrderID
AND attach.ContractAssetAttachmentPurchaseOrderAssetContractID = PurchaseOrderAssetContractID
WHERE PurchaseOrderIsActive = 'Y'
AND PurchaseOrderItemCategoryID = '3'
AND PurchaseOrderS_RegionalID = ?
@@ -411,7 +359,7 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderGrandTotal AS grandtotal,
CASE
WHEN PurchaseOrderTaxPpnType = 'P' THEN 'percent'
ELSE 'nominal'
ELSE 'absolute'
END AS typepajak,
CASE
WHEN PurchaseOrderTaxPpnType = 'P' THEN PurchaseOrderTaxPercentPpn
@@ -419,7 +367,7 @@ class PurchaseOrderAset extends MY_Controller {
END AS valuepajak,
CASE
WHEN PurchaseOrderDiscountPercent > 0.00 THEN 'percent'
ELSE 'nominal'
ELSE 'absolute'
END AS typediskon,
CASE
WHEN PurchaseOrderDiscountPercent > 0.00 THEN PurchaseOrderDiscountPercent
@@ -429,8 +377,6 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderGrandTotal AS grandtotal,
SupplierID AS supplierID,
SupplierName,
WarehouseID AS gudangID,
WarehouseM_BranchID AS branchID,
PurchaseOrderAssetContractID,
PurchaseOrderAssetContractTAssetAttachmentID AS assetAttachmentID,
PurchaseOrderAssetContractName AS contractName,
@@ -443,16 +389,11 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderAssetContractInstallmentPayAmount AS installmentPayAmount,
PurchaseOrderAssetContractInstallmentDownPaymentType AS installmentDownPaymentType,
PurchaseOrderAssetContractInstallmentDownPayment AS installmentDownPayment,
PurchaseOrderApprovedManagerUserID AS verifiedby,
SupplierDownpaymentID
PurchaseOrderApprovedManagerUserID AS verifiedby
FROM purchase_order
JOIN supplier ON SupplierID = PurchaseOrderSupplierID
JOIN warehouse ON WarehouseID = PurchaseOrderWarehouseID
JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractPurchaseOrderID = PurchaseOrderID
LEFT JOIN supplier_downpayment
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderID
AND SupplierDownpaymentIsActive = 'Y'
WHERE PurchaseOrderIsActive = 'Y'
AND PurchaseOrderID = ?";
$que_poasset = $this->db->query($sql_poasset, [$para['poID']]);
@@ -485,7 +426,6 @@ class PurchaseOrderAset extends MY_Controller {
}
$subtotal = floatval($datapo['subtotal']);
$downpayment = floatval($datapo['installmentDownPayment']);
$grandtotal = floatval($datapo['grandtotal']);
$valuediskon = floatval($datapo['valuediskon']);
@@ -503,11 +443,8 @@ class PurchaseOrderAset extends MY_Controller {
"subtotal" => $subtotal,
"diskon" => $valuediskon,
"pajak" => $valuepajak,
"downpayment" => $downpayment,
"total_before_downpayment" => $downpayment + $grandtotal,
"total" => $grandtotal
];
/* map summary to purchase order */
$datapo['valuepajak'] = floatval($datapo['valuepajak']);
$datapo['valuediskon'] = floatval($datapo['valuediskon']);
@@ -600,7 +537,6 @@ class PurchaseOrderAset extends MY_Controller {
$summary_subtotal = floatval($para['summary']['subtotal']);
$summary_diskon = floatval($para['summary']['diskon']);
$summary_pajak = floatval($para['summary']['pajak']);
$summary_downpayment = floatval($para['summary']['downpayment']);
$summary_total = floatval($para['summary']['total']);
$sql_po = "INSERT INTO purchase_order (
@@ -614,20 +550,17 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderTaxPercentPpn,
PurchaseOrderDiscountPercent,
PurchaseOrderDiscountAmount,
PurchaseOrderWarehouseType,
PurchaseOrderWarehouseID,
PurchaseOrderNote,
PurchaseOrderSubTotal,
PurchaseOrderTaxAmountPpn,
PurchaseOrderGrandTotal,
PurchaseOrderUserID,
PurchaseOrderCreated
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
$que_po = $this->db->query($sql_po, [
$numpd, '3', $para['podate'], $para['reference'], $para['supplierID'],
$user['S_RegionalID'], $taxtype, $taxpercent, $discpercent, $summary_diskon,
'single', $para['gudangID'], $para['catatan'], $summary_subtotal,
$summary_pajak, $summary_total, $user['M_UserID']
$para['catatan'], $summary_subtotal, $summary_pajak, $summary_total, $user['M_UserID']
]);
if (!$que_po) {
$this->db->trans_rollback();
@@ -700,16 +633,15 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderDetailQty,
PurchaseOrderDetailPrice,
PurchaseOrderDetailTotal,
PurchaseOrderDetailWarehouseID,
PurchaseOrderDetailUserID,
PurchaseOrderDetailCreatedUserID,
PurchaseOrderDetailCreated
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
$que_detail = $this->db->query($sql_detail, [
$PurchaseOrderID, $obj['PurchaseSummaryID'], $obj['PurchaseRequestID'],
$obj['PurchaseRequestDetailID'], $obj['PurchaseRequestFlagID'],
$obj['M_ItemID'], $obj['ItemUnitID'], $obj['OriginalQty'], $obj['RequestQty'],
$obj['SupplierPrice'], $total, $para['gudangID'], $user['M_UserID'], $user['M_UserID']
$obj['SupplierPrice'], $total, $user['M_UserID'], $user['M_UserID']
]);
if (!$que_detail) {
$this->db->trans_rollback();
@@ -771,33 +703,6 @@ class PurchaseOrderAset extends MY_Controller {
exit;
}
# INSERT into table supplier_downpayment #
$sql_dp = "INSERT INTO supplier_downpayment (
SupplierDownpaymentPurchasOrderID,
SupplierDownpaymentSupplierID,
SupplierDownpaymentAmount,
SupplierDownpaymentDate,
SupplierDownpaymentDueDate,
SupplierDownpaymentStatus,
SupplierDownpaymentCreatedUserID,
SupplierDownpaymentLastUpdatedUserID
) VALUES (?,?,?,?,?,?,?,?)";
$que_dp = $this->db->query($sql_dp, [
$PurchaseOrderID,
$para['supplierID'],
$summary_downpayment ?: 0.00,
$para['contractDate'],
$para['contractDate'],
'Draft',
$user['M_UserID'],
$user['M_UserID']
]);
if (!$que_dp) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed insert supplier downpayment");
exit;
}
$this->insertLog(
$PurchaseOrderID, 'CREATE', $para, '',
[], $user['M_UserID'], 'create purchase order asset'
@@ -839,7 +744,6 @@ class PurchaseOrderAset extends MY_Controller {
$summary_subtotal = floatval($para['summary']['subtotal']);
$summary_diskon = floatval($para['summary']['diskon']);
$summary_pajak = floatval($para['summary']['pajak']);
$summary_downpayment = floatval($para['summary']['downpayment']);
$summary_total = floatval($para['summary']['total']);
# update po header #
@@ -852,8 +756,6 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderTaxPercentPpn = ?,
PurchaseOrderDiscountPercent = ?,
PurchaseOrderDiscountAmount = ?,
PurchaseOrderWarehouseType = ?,
PurchaseOrderWarehouseID = ?,
PurchaseOrderNote = ?,
PurchaseOrderSubTotal = ?,
PurchaseOrderTaxAmountPpn = ?,
@@ -863,8 +765,8 @@ class PurchaseOrderAset extends MY_Controller {
AND PurchaseOrderIsActive = 'Y'";
$que_po = $this->db->query($sql_po, [
$para['podate'], $para['reference'], $para['supplierID'], $user['S_RegionalID'],
$taxtype, $taxpercent, $discpercent, $summary_diskon, 'single', $para['gudangID'],
$para['catatan'], $summary_subtotal, $summary_pajak, $summary_total, $para['PurchaseOrderID']
$taxtype, $taxpercent, $discpercent, $summary_diskon, $para['catatan'],
$summary_subtotal, $summary_pajak, $summary_total, $para['PurchaseOrderID']
]);
if (!$que_po) {
$this->db->trans_rollback();
@@ -908,29 +810,6 @@ class PurchaseOrderAset extends MY_Controller {
exit;
}
## UPDATE existing downpayment ##
$sql = "UPDATE supplier_downpayment SET
SupplierDownpaymentAmount = ?,
SupplierDownpaymentDate = ?,
SupplierDownpaymentDueDate = ?,
SupplierDownpaymentStatus = ?,
SupplierDownpaymentLastUpdatedUserID = ?
WHERE SupplierDownpaymentID = ?
AND SupplierDownpaymentIsActive = 'Y'";
$que = $this->db->query($sql, [
$summary_downpayment ?: 0.00,
$para['contractStart'],
$para['contractStart'],
'Draft',
$user['M_UserID'],
$para['SupplierDownpaymentID']
]);
if (!$que) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed update supplier downpayment");
exit;
}
# update status old po summary isActive to 'N' #
$sql_active = "UPDATE purchase_order_summary SET
PurchaseOrderSummaryIsActive = 'N'
@@ -1016,16 +895,15 @@ class PurchaseOrderAset extends MY_Controller {
PurchaseOrderDetailQty,
PurchaseOrderDetailPrice,
PurchaseOrderDetailTotal,
PurchaseOrderDetailWarehouseID,
PurchaseOrderDetailUserID,
PurchaseOrderDetailCreatedUserID,
PurchaseOrderDetailCreated
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
$que_detail = $this->db->query($sql_detail, [
$para['PurchaseOrderID'], $obj['PurchaseSummaryID'], $obj['PurchaseRequestID'],
$obj['PurchaseRequestDetailID'], $obj['PurchaseRequestFlagID'],
$obj['M_ItemID'], $obj['ItemUnitID'], $obj['OriginalQty'], $obj['RequestQty'],
$obj['SupplierPrice'], $total, $para['gudangID'], $user['M_UserID'], $user['M_UserID']
$obj['SupplierPrice'], $total, $user['M_UserID'], $user['M_UserID']
]);
if (!$que_detail) {
$this->db->trans_rollback();
@@ -1144,34 +1022,6 @@ class PurchaseOrderAset extends MY_Controller {
exit;
}
$sql_delattac = "UPDATE contract_asset_attachment SET
ContractAssetAttachmentIsActive = 'N'
WHERE ContractAssetAttachmentPurchaseOrderAssetContractID = ?
AND ContractAssetAttachmentPurchaseOrderID = ?";
$que_delattac = $this->db->query($sql_delattac, [
$para['contractID'], $para['poID']
]);
if (!$que_delattac) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed soft delete data attachment");
exit;
}
/* soft delete supplier_downpayment */
$sql_deldp = "UPDATE supplier_downpayment SET
SupplierDownpaymentIsActive = 'N',
SupplierDownpaymentLastUpdatedUserID = ?
WHERE SupplierDownpaymentID = ?
AND SupplierDownpaymentIsActive = 'Y'";
$que_deldp = $this->db->query($sql_deldp, [
$user['M_UserID'], $para['SupplierDownpaymentID']
]);
if (!$que_deldp) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed soft delete supplier downpayment");
exit;
}
/* soft delete purchase order */
$sql_delorder = "UPDATE purchase_order SET
PurchaseOrderIsActive = 'N',
@@ -1244,32 +1094,16 @@ class PurchaseOrderAset extends MY_Controller {
ContractAssetAttachmentPurchaseOrderAssetContractID,
ContractAssetAttachmentPurchaseOrderID,
ContractAssetAttachmentFileName,
ContractAssetAttachmentDate,
ContractAssetAttachmentCreated
) VALUES (?,?,?,?,NOW())";
) VALUES (?,?,?,NOW())";
$que_insert = $this->db->query($sql_insert, [
$para['contractID'], $para['poID'],
$filename, $para['contractDate']
$para['contractID'], $para['poID'], $filename
]);
if (!$que_insert) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed insert attachment table contract asset attachment");
exit;
}
$attachmentID = $this->db->insert_id();
$sql_contract = "UPDATE purchase_order_asset_contract SET
PurchaseOrderAssetContractTAssetAttachmentID = ?
WHERE PurchaseOrderAssetContractID = ?
AND PurchaseOrderAssetContractPurchaseOrderID = ?";
$que_contract = $this->db->query($sql_contract, [
$attachmentID, $para['contractID'], $para['poID']
]);
if (!$que_contract) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed update attachmentid into asset contract table");
exit;
}
} else {
$error = $this->upload->display_errors();
$this->db->trans_rollback();

View File

@@ -73,7 +73,7 @@ class ReceiveItemPoInventaris extends MY_Controller
$qry = $this->db->query($sql, [$user['M_BranchID'], $search]);
if (!$qry) {
$this->sys_error_db("[Error] query listing ruangan");
$this->sys_error_db("[Error] query listing ruangan");;
exit;
}
@@ -1705,38 +1705,22 @@ class ReceiveItemPoInventaris extends MY_Controller
exit;
}
$sql_invcoa = "SELECT
COALESCE(
i.M_InventarisItemCoaMappingID,
g.M_InventarisCoaMappingID
) AS MappingID,
COALESCE(
i.M_InventarisItemCoaMappingCoaInventarisID,
g.M_InventarisCoaMappingCoaInventarisID
) AS CoaInventarisID,
c.coaDescription AS CoaInventarisDesc
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_InventarisItemCoaMappingCoaInventarisID, g.M_InventarisCoaMappingCoaInventarisID)
AND c.coaIsActive = 'Y'";
$que_invcoa = $this->db->query($sql_invcoa, [
$item_cek['M_ItemID'],
$item_cek['M_ItemM_InventarisGolID']
]);
if (!$que_invcoa) {
$sql_coa_inv = "SELECT
CoaMapInventarisCoaID,
CoaMapInventarisCoaNo,
CoaMapInventarisCoaDesc
FROM coa_map_inventaris
WHERE CoaMapInventarisM_InventarisGolID = ?
AND CoaMapInventarisIsActive = 'Y'";
$que_coa_inv = $this->db->query($sql_coa_inv, [$item_cek['M_ItemM_InventarisGolID']]);
if (!$que_coa_inv) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] failed to get coa biaya item inventaris");
exit;
}
$iteminv_coa = $que_invcoa->row_array();
$iteminv_coa = $que_coa_inv->row_array();
if (empty($iteminv_coa['CoaInventarisID'])) {
if (empty($iteminv_coa['CoaMapInventarisCoaID'])) {
$this->db->trans_rollback();
$this->sys_error_db("[Error] coa gol inventaris not found");
exit;
@@ -1756,8 +1740,8 @@ class ReceiveItemPoInventaris extends MY_Controller
) VALUES (?,?,?,?,?,?)";
$que_tx_jurnal = $this->db->query($sql_tx_jurnal, [
$jurnal_ID,
$iteminv_coa['CoaInventarisID'],
$iteminv_coa['CoaInventarisDesc'],
$iteminv_coa['CoaMapInventarisCoaID'],
$iteminv_coa['CoaMapInventarisCoaDesc'],
$debet,
0,
$user['M_UserID']
@@ -1770,7 +1754,7 @@ class ReceiveItemPoInventaris extends MY_Controller
}
$juranl_tx_ID = $this->db->insert_id();
$each_desc[] = "- Add detail Jurnal debet {$item_cek['M_ItemDesc']}
{$iteminv_coa['CoaInventarisDesc']} sejumlah {$this->formatRupiah($debet)}";
{$iteminv_coa['CoaMapInventarisCoaDesc']} sejumlah {$this->formatRupiah($debet)}";
# INSERT JURNAL ADDON #
$sql_addon_jurnal = "INSERT INTO jurnal_addon (
@@ -1960,6 +1944,20 @@ class ReceiveItemPoInventaris extends MY_Controller
$this->sys_error_db("[Error] insert update stock");
exit;
}
// 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;
// }
} else {
$sql_insert_stock = "INSERT INTO stock (
StockWarehouseAlmariID,
@@ -1987,9 +1985,23 @@ class ReceiveItemPoInventaris extends MY_Controller
exit;
}
$stock_ID = $this->db->insert_id();
$stockID = $this->db->insert_id();
$stock_qty_ori = 0;
$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;
// }
}
// insert stock card
@@ -2693,6 +2705,36 @@ class ReceiveItemPoInventaris extends MY_Controller
# generate barcode number each inventory item #
$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 #
$sql_cekstock = "SELECT StockID
@@ -2714,39 +2756,6 @@ class ReceiveItemPoInventaris extends MY_Controller
}
$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 #
$sql_stockinventaris = "INSERT INTO stock_inventory (
StockInventoryStockID,

View File

@@ -426,7 +426,7 @@ class ReceiveItemPoV2 extends MY_Controller {
AND ReceiveOrderPoIDate BETWEEN DATE(?) AND DATE(?)
AND ReceiveOrderPoS_RegionalID = ?
AND ReceiveOrderPoM_BranchCode LIKE ?
AND ReceiveOrderPoID IN (
AND ReceiveOrderPoID NOT IN (
SELECT DISTINCT ReceiveOrderPoDetailReceiveOrderPoID
FROM receive_order_po_detail
JOIN purchase_order ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID

View File

@@ -1,90 +0,0 @@
@host = https://accone.aplikasi.web.id/one-api
@date = 2026-07-14
@coaBayarID = 1110100001
@coaHutangID = 2110100030
# =============================================================================
# JurnalPiCicilanOrderAset — Contoh Pemanggilan Endpoint
# =============================================================================
# Urutan penggunaan yang disarankan:
# 1. ListEligiblePurchaseOrderAssetContracts → lihat kontrak yang akan diproses
# 2. GenerateJournalsFromContracts (dryRun=Y) → simulasi tanpa simpan ke DB
# 3. GenerateJournalsFromContracts (dryRun=N) → generate jurnal sesungguhnya
# 4. CreateJournal → buat jurnal manual 1 per 1
# =============================================================================
### 1. Lihat kontrak aset yang eligible untuk dibuatkan jurnal bulan ini
GET {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts
Content-Type: application/json
{
"startDate": "2026-07-01",
"endDate": "2026-07-31"
}
### 2. Simulasi generate jurnal (dryRun=Y) — tidak menyimpan ke database
POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts
Content-Type: application/json
{
"date" : "{{date}}",
"dryRun" : "Y"
}
### 3. Generate jurnal untuk semua kontrak eligible bulan ini (sesungguhnya)
POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts
Content-Type: application/json
{
"date" : "{{date}}",
"dryRun" : "N"
}
### 4. Generate jurnal untuk 1 kontrak tertentu saja (contractID=99)
POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts
Content-Type: application/json
{
"date" : "{{date}}",
"dryRun" : "Y",
"contractID" : 00
}
### 5. Buat jurnal manual (1 jurnal, input detail sendiri)
POST {{host}}/index.php/mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal
Content-Type: application/json
{
"date" : "{{date}}",
"branchCode" : "BA",
"regionalID" : 8,
"branchCompanyID": 1,
"jurnalTypeID" : 11,
"title" : "Jurnal PI Cicilan Order Aset",
"description" : "Cicilan ke-1 dari 12 | Kontrak Kendaraan Operasional | Periode 2026-07",
"details" : [
{
"coaID" : {{coaHutangID}},
"description": "Hutang cicilan aset - Kendaraan Operasional",
"debit" : 15000000,
"credit" : 0,
"addonCode" : "PIINSTALMENT",
"addonValue" : "42",
"mItemID" : 123
},
{
"coaID" : {{coaBayarID}},
"description": "Pembayaran cicilan aset - Kendaraan Operasional",
"debit" : 0,
"credit" : 15000000,
"addonCode" : "PIINSTALMENT",
"addonValue" : "42",
"mItemID" : 123
}
]
}

View File

@@ -1,970 +0,0 @@
<?php
/**
* JurnalPiCicilanOrderAset
*
* Controller scheduler untuk membuat jurnal akuntansi atas Purchase Invoice (PI)
* cicilan order aset secara otomatis.
*
* Alur bisnis proses:
* 1. Validasi parameter input (tanggal, branchCode, title, description, details)
* 2. Insert header jurnal ke tabel `jurnal`
* - Nomor jurnal di-generate via fn_numbering('J')
* - Tipe jurnal: PAYMENT
* - Berisi: title, description, tanggal, regionalID, kode cabang, branchCompanyID
* 3. Insert baris transaksi ke tabel `jurnal_tx` (untuk setiap baris cicilan)
* - Referensi ke jurnalID header
* - COA (Chart of Account) dan deskripsinya:
* * Sisi hutang cicilan → nilai DEBIT (diambil dari HutangCoaID di fa_class)
* * Sisi pembayaran cicilan → nilai KREDIT (diambil dari ItemCoaID di fa_class)
* 4. Insert data tambahan ke tabel `jurnal_addon` (jika ada addonValue atau mItemID)
* - Referensi ke jurnalID dan jurnalTxID
* - Berisi: addonCode (default: "PIINSTALMENT"), addonValue, mItemID
*
* Endpoint:
* POST /mockup/scheduler/JurnalPiCicilanOrderAset/CreateJournal
* POST /mockup/scheduler/JurnalPiCicilanOrderAset/GenerateJournalsFromContracts
* GET /mockup/scheduler/JurnalPiCicilanOrderAset/ListEligiblePurchaseOrderAssetContracts
*/
class JurnalPiCicilanOrderAset extends MY_Controller
{
var $db;
/**
* Endpoint default — menampilkan nama controller sebagai penanda aktif.
*/
public function index()
{
echo "Jurnal PI Cicilan Order Aset";
}
/**
* Konstruktor — inisialisasi parent controller.
*/
public function __construct()
{
parent::__construct();
}
/**
* CreateJournal — Membuat jurnal PI cicilan order aset.
*
* Proses bisnis:
* 1. Validasi semua parameter wajib: date, branchCode, title, description, details
* 2. Ambil periodeID dari tanggal transaksi (tabel periode)
* 3. Ambil regionalID dari branchCode (tabel m_branch)
* 4. Ambil branchCompanyID dari branchCode (tabel m_branch_companydetail)
* 5. Ambil jurnalTypeID dari kode tipe jurnal (default: "PAYMENT")
* 6. Generate nomor jurnal otomatis via fn_numbering('J')
* 7. Validasi balance: total debit harus sama dengan total credit
* 8. INSERT header jurnal ke tabel `jurnal`:
* - jurnalNo : nomor jurnal hasil generate
* - jurnalJurnalTypeID: tipe PAYMENT
* - jurnalTitle : judul jurnal
* - jurnalDescription: keterangan jurnal
* - jurnalDate : tanggal transaksi
* - JurnalS_RegionalID: ID regional cabang
* - jurnalM_BranchCode: kode cabang
* 9. Untuk setiap baris detail, INSERT ke `jurnal_tx`:
* - jurnalTxJurnalID : referensi ke header jurnal
* - jurnalTxCoaID : COA yang digunakan (hutang cicilan / kas/bank)
* - jurnalTxDescription: keterangan baris (nama cicilan/aset)
* - jurnalTxDebit : nilai hutang (sisi debit)
* - jurnalTxCredit : nilai bayar (sisi kredit)
* 10. Jika ada addonValue atau mItemID, INSERT ke `jurnal_addon`:
* - jurnalAddOnJurnalID : referensi ke header jurnal
* - jurnalAddOnJurnalTxID: referensi ke baris jurnal_tx
* - jurnalAddOnCode : kode addon (default: "PIINSTALMENT")
* - jurnalAddOnValue : nilai tambahan (misal: nomor PI/kontrak)
* - jurnalAddOnM_ItemID : ID item aset yang terkait
*
* Parameter (POST JSON):
* - date : Tanggal jurnal (YYYY-MM-DD). Default: hari ini.
* - branchCode : Kode cabang. WAJIB.
* - title : Judul jurnal. WAJIB.
* - description : Keterangan jurnal. WAJIB.
* - details : Array baris transaksi. WAJIB, minimal 1 baris.
* - coaID : ID Chart of Account. WAJIB per baris.
* - description : Keterangan baris.
* - debit : Nilai debit (hutang cicilan).
* - credit : Nilai kredit (pembayaran cicilan).
* - addonValue : (opsional) Nilai addon, misal nomor PI.
* - mItemID : (opsional) ID item aset.
* - addonCode : (opsional) Kode addon. Default: "PIINSTALMENT".
* - periodeID : (opsional) ID periode. Default: diambil dari tanggal.
* - regionalID : (opsional) ID regional. Default: diambil dari branchCode.
* - branchCompanyID: (opsional) ID branch company. Default: diambil dari branchCode.
* - jurnalTypeID : (opsional) ID tipe jurnal. Default: diambil dari jurnalTypeCode.
* - jurnalTypeCode: (opsional) Kode tipe jurnal. Default: "PAYMENT".
* - userID : ID user yang membuat jurnal.
*
* @return JSON { jurnalID, jurnalNo, detailCount, addonCount }
*/
public function CreateJournal()
{
try {
$para = $this->sys_input;
$date = isset($para["date"]) && $para["date"] != "" ? $para["date"] : date("Y-m-d");
if (!$this->isValidDate($date)) {
throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD.");
}
$branchCode = isset($para["branchCode"]) ? trim($para["branchCode"]) : "";
if ($branchCode == "") {
throw new Exception("branchCode wajib diisi.");
}
$title = isset($para["title"]) ? trim($para["title"]) : "";
if ($title == "") {
throw new Exception("title wajib diisi.");
}
$description = isset($para["description"]) ? trim($para["description"]) : "";
if ($description == "") {
throw new Exception("description wajib diisi.");
}
$details = isset($para["details"]) && is_array($para["details"]) ? $para["details"] : [];
if (count($details) == 0) {
throw new Exception("details wajib diisi minimal 1 baris.");
}
$periodeID = isset($para["periodeID"]) && (int) $para["periodeID"] > 0
? (int) $para["periodeID"]
: $this->getPeriodeIDByDate($date);
if ($periodeID <= 0) {
throw new Exception("Periode jurnal tidak ditemukan untuk tanggal {$date}.");
}
$regionalID = isset($para["regionalID"]) && (int) $para["regionalID"] > 0
? (int) $para["regionalID"]
: $this->getRegionalIDByBranchCode($branchCode);
if ($regionalID <= 0) {
throw new Exception("regionalID wajib diisi atau harus bisa diturunkan dari branchCode.");
}
$branchCompanyID = isset($para["branchCompanyID"]) && (int) $para["branchCompanyID"] > 0
? (int) $para["branchCompanyID"]
: $this->getBranchCompanyIDByBranchCode($branchCode);
if ($branchCompanyID <= 0) {
throw new Exception("branchCompanyID wajib diisi atau harus bisa diturunkan dari branchCode.");
}
$jurnalTypeID = isset($para["jurnalTypeID"]) && (int) $para["jurnalTypeID"] > 0
? (int) $para["jurnalTypeID"]
: $this->getJurnalTypeID(isset($para["jurnalTypeCode"]) && $para["jurnalTypeCode"] != "" ? $para["jurnalTypeCode"] : "PAYMENT");
if ($jurnalTypeID <= 0) {
throw new Exception("jurnalTypeID / jurnalTypeCode tidak valid.");
}
$userID = 0;
$result = $this->insertJournal(
$date,
$branchCode,
$title,
$description,
$details,
$periodeID,
$regionalID,
$branchCompanyID,
$jurnalTypeID,
$userID
);
if ($result === false) {
exit;
}
$this->sys_ok($result);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
/**
* GenerateJournalsFromContracts — Generate jurnal otomatis dari seluruh kontrak aset eligible.
*
* Fungsi ini mengintegrasikan data dari getEligiblePurchaseOrderAssetContracts()
* dengan proses pembuatan jurnal (insertJournal()) secara otomatis.
*
* Untuk setiap kontrak yang eligible, dibuat 1 jurnal dengan 2 baris transaksi:
* - Baris DEBIT : COA hutang cicilan (HutangCoaID dari fa_class) — mencatat hutang cicilan
* - Baris KREDIT : COA kas/bank/aset (ItemCoaID dari fa_class) — mencatat pembayaran cicilan
* Addon (jurnal_addon): menyimpan ID kontrak sebagai addonValue dan ItemID sebagai mItemID
*
* Parameter (POST JSON):
* - date : Tanggal jurnal (YYYY-MM-DD). Default: hari ini.
* - userID : ID user yang membuat jurnal.
* - dryRun : "Y" = simulasi tanpa simpan ke DB. Default: "N".
* - contractID : (opsional) Filter hanya 1 kontrak tertentu.
*
* Syarat kontrak dilewati (skipped):
* - branchCode kosong (data cabang belum lengkap)
* - COA hutang (HutangCoaID) tidak ditemukan di fa_class
* - COA bayar/aset (ItemCoaID) tidak ditemukan di fa_class
*
* @return JSON { tanggal, totalDibuat, totalDilewati, daftarDibuat[], daftarDilewati[] }
*/
public function GenerateJournalsFromContracts()
{
try {
$para = $this->sys_input;
$date = isset($para["date"]) && $para["date"] != "" ? $para["date"] : date("Y-m-d");
if (!$this->isValidDate($date)) {
throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD.");
}
$userID = 0;
$dryRun = isset($para["dryRun"]) && ($para["dryRun"] === true || $para["dryRun"] == "Y" || $para["dryRun"] == "1");
$filterContractID = isset($para["contractID"]) && (int) $para["contractID"] > 0
? (int) $para["contractID"]
: null;
$monthStart = date("Y-m-01", strtotime($date));
$monthEnd = date("Y-m-t", strtotime($date));
// Ambil semua kontrak yang eligible untuk periode bulan ini
$contracts = $this->getEligiblePurchaseOrderAssetContracts($monthStart, $monthEnd);
$dibuat = [];
$dilewati = [];
foreach ($contracts as $kontrak) {
$contractID = (int) $kontrak["PurchaseOrderAssetContractID"];
// Filter 1 kontrak jika diminta
if ($filterContractID !== null && $contractID !== $filterContractID) {
continue;
}
$branchCode = $kontrak["M_BranchCode"] ?? "";
$regionalID = (int) ($kontrak["M_BranchS_RegionalID"] ?? 0);
$branchCompanyID = (int) ($kontrak["M_BranchCompanyID"] ?? 0);
$coaHutangID = (int) ($kontrak["HutangCoaID"] ?? 0); // COA debit dari fa_class (Fa_ClassHutangCoaID)
$coaBayarID = (int) ($kontrak["ItemCoaID"] ?? 0); // COA kredit dari fa_class (Fa_ClassCoaID)
$jumlah = (float) $kontrak["PurchaseOrderAssetContractInstallmentPayAmount"];
$itemID = (int) ($kontrak["PurchaseOrderSummaryItemID"] ?? 0);
$namaKontrak = $kontrak["PurchaseOrderAssetContractName"] != ""
? $kontrak["PurchaseOrderAssetContractName"]
: "Kontrak ID {$contractID}";
$nomorPO = $kontrak["PurchaseOrderNumber"] ?? "";
$cicilanKe = ((int) $kontrak["PurchaseOrderAssetContractInstallmentPaid"]) + 1;
$totalCicilan = (int) $kontrak["PurchaseOrderAssetContractInstallmentNumber"];
$periode = date("Y-m", strtotime($date));
$invoiceNumber = $kontrak["SupplierInvoiceNumber"] ?? "";
$invoiceID = (int) ($kontrak["SupplierInvoiceID"] ?? 0);
// Validasi: branchCode harus ada
if ($branchCode == "") {
$dilewati[] = [
"kontrakID" => $contractID,
"alasan" => "Kode cabang (branchCode) tidak ditemukan. Pastikan warehouse PO sudah terhubung ke data cabang."
];
continue;
}
// Validasi: COA hutang harus ada (untuk sisi DEBIT)
if ($coaHutangID <= 0) {
$dilewati[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"alasan" => "COA hutang tidak ditemukan. Pastikan item PO memiliki Fa_Class dengan Fa_ClassHutangCoaID."
];
continue;
}
// Validasi: COA bayar/aset harus ada (untuk sisi KREDIT)
if ($coaBayarID <= 0) {
$dilewati[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"alasan" => "COA bayar/aset tidak ditemukan. Pastikan item PO memiliki Fa_Class dengan Fa_ClassCoaID."
];
continue;
}
// Mode simulasi: catat tanpa simpan ke DB
if ($dryRun) {
$dibuat[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"namaKontrak" => $namaKontrak,
"invoiceNumber" => $invoiceNumber,
"jumlah" => $jumlah,
"coaHutangID" => $coaHutangID,
"coaBayarID" => $coaBayarID,
"keterangan" => "Mode simulasi (dryRun), data tidak disimpan."
];
continue;
}
// Susun title dan description jurnal
$title = "PI Cicilan Aset - {$invoiceNumber}";
$description = "Jurnal cicilan ke-{$cicilanKe} dari {$totalCicilan} | {$namaKontrak} | PO: {$nomorPO} | Periode {$periode}";
// Ambil periodeID dari tanggal
$periodeID = $this->getPeriodeIDByDate($date);
if ($periodeID <= 0) {
$dilewati[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"alasan" => "Periode jurnal tidak ditemukan untuk tanggal {$date}."
];
continue;
}
// Ambil jurnalTypeID untuk tipe PAYMENT
$jurnalTypeID = $this->getJurnalTypeID("PAYMENTINV");
if ($jurnalTypeID <= 0) {
$dilewati[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"alasan" => "Tipe jurnal PAYMENT tidak ditemukan di tabel jurnal_type."
];
continue;
}
// Ambil description detail dari database fa_class dengan fallback jika kosong
$descHutang = isset($kontrak["HutangCoaDesc"]) && $kontrak["HutangCoaDesc"] != ""
? $kontrak["HutangCoaDesc"]
: "Hutang cicilan aset - {$namaKontrak}";
$descBayar = isset($kontrak["ItemCoaDesc"]) && $kontrak["ItemCoaDesc"] != ""
? $kontrak["ItemCoaDesc"]
: "Pembayaran cicilan aset - {$namaKontrak}";
// Susun 2 baris detail jurnal:
// Baris 1 — DEBIT : hutang cicilan aset (COA dari fa_class)
// Baris 2 — KREDIT : pembayaran cicilan (COA kas/bank dari parameter)
$details = [
[
"coaID" => $coaHutangID,
"description" => $descHutang,
"debit" => $jumlah,
"credit" => 0,
"addonValue" => $invoiceNumber,
"mItemID" => $itemID,
"addonCode" => "PIINSTALMENT"
],
[
"coaID" => $coaBayarID,
"description" => $descBayar,
"debit" => 0,
"credit" => $jumlah,
"addonValue" => $invoiceNumber,
"mItemID" => $itemID,
"addonCode" => "PIINSTALMENT"
]
];
// Panggil insertJournal() untuk simpan ke DB
$result = $this->insertJournal(
$date,
$branchCode,
$title,
$description,
$details,
$periodeID,
$regionalID,
$branchCompanyID,
$jurnalTypeID,
$userID
);
if ($result === false) {
$dilewati[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"alasan" => "Gagal menyimpan jurnal ke database."
];
continue;
}
$dibuat[] = [
"kontrakID" => $contractID,
"branchCode" => $branchCode,
"namaKontrak" => $namaKontrak,
"jurnalID" => $result["jurnalID"],
"jurnalNo" => $result["jurnalNo"],
"jumlah" => $jumlah
];
}
$this->sys_ok([
"tanggal" => $date,
"periodeAwal" => $monthStart,
"periodeAkhir" => $monthEnd,
"totalDibuat" => count($dibuat),
"totalDilewati" => count($dilewati),
"daftarDibuat" => $dibuat,
"daftarDilewati" => $dilewati
]);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
/**
* ListEligiblePurchaseOrderAssetContracts — Menampilkan daftar kontrak aset
* yang memenuhi syarat untuk dibuatkan jurnal PI cicilan pada periode tertentu.
*
* Syarat kontrak eligible:
* - Kontrak aktif dan berstatus "belum lunas"
* - Nilai cicilan > 0
* - Jumlah cicilan terbayar < total cicilan
* - Tanggal kontrak masuk dalam rentang startDateendDate
* - PO sudah berstatus Approved
* - Barang sudah diterima (receive_order_po sudah confirmed)
*
* Endpoint ini digunakan untuk preview/verifikasi sebelum proses generate jurnal
* dijalankan secara massal oleh CRON.
*
* Parameter (GET/POST):
* - startDate : Awal periode (YYYY-MM-DD). Default: tanggal 1 bulan ini.
* - endDate : Akhir periode (YYYY-MM-DD). Default: tanggal akhir bulan ini.
*
* @return JSON { startDate, endDate, total, records[] }
*/
public function ListEligiblePurchaseOrderAssetContracts()
{
try {
$para = $this->sys_input;
$startDate = isset($para["startDate"]) && $para["startDate"] != ""
? $para["startDate"]
: date("Y-m-01");
$endDate = isset($para["endDate"]) && $para["endDate"] != ""
? $para["endDate"]
: date("Y-m-t");
if (!$this->isValidDate($startDate) || !$this->isValidDate($endDate)) {
throw new Exception("Format tanggal tidak valid. Gunakan YYYY-MM-DD.");
}
if (strtotime($startDate) > strtotime($endDate)) {
throw new Exception("startDate tidak boleh lebih besar dari endDate");
}
$records = $this->getEligiblePurchaseOrderAssetContracts($startDate, $endDate);
$this->sys_ok([
"startDate" => $startDate,
"endDate" => $endDate,
"total" => count($records),
"records" => $records
]);
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// FUNGSI PRIVATE / HELPER
// =========================================================================
/**
* insertJournal — Menyimpan 1 jurnal lengkap ke database (header + tx + addon).
*
* Fungsi internal yang dipanggil oleh CreateJournal() dan GenerateJournalsFromContracts().
* Mengelola transaksi DB secara penuh (begin → commit/rollback).
*
* Alur:
* 1. Validasi balance debit == kredit
* 2. Generate nomor jurnal via fn_numbering('J')
* 3. INSERT header ke tabel `jurnal`
* 4. INSERT baris transaksi ke `jurnal_tx` (per detail)
* 5. INSERT addon ke `jurnal_addon` (jika addonValue atau mItemID ada)
*
* @param string $date Tanggal jurnal (YYYY-MM-DD)
* @param string $branchCode Kode cabang
* @param string $title Judul jurnal
* @param string $description Keterangan jurnal
* @param array $details Baris transaksi [ coaID, description, debit, credit, addonValue?, mItemID?, addonCode? ]
* @param int $periodeID ID periode akuntansi
* @param int $regionalID ID regional
* @param int $branchCompanyID ID branch company
* @param int $jurnalTypeID ID tipe jurnal
* @param int $userID ID user pembuat jurnal
*
* @return array|false Array { jurnalID, jurnalNo, detailCount, addonCount }, atau false jika gagal
*/
private function insertJournal($date, $branchCode, $title, $description, $details, $periodeID, $regionalID, $branchCompanyID, $jurnalTypeID, $userID)
{
// Validasi balance sebelum menyimpan
$totalDebit = 0;
$totalCredit = 0;
foreach ($details as $idx => $detail) {
$debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0;
$credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0;
if ($debit < 0 || $credit < 0) {
$this->sys_error("Nilai debit/credit tidak boleh negatif pada detail ke-" . ($idx + 1));
return false;
}
$totalDebit += $debit;
$totalCredit += $credit;
}
if (round($totalDebit, 2) !== round($totalCredit, 2)) {
$this->sys_error("Total debit dan credit harus sama. Debit={$totalDebit}, Credit={$totalCredit}");
return false;
}
$jurnalNo = $this->generateJournalNumber();
if ($jurnalNo === false || $jurnalNo == "") {
$this->sys_error("Gagal generate nomor jurnal.");
return false;
}
$this->db->trans_begin();
// -----------------------------------------------------------------
// INSERT header jurnal ke tabel `jurnal`
// -----------------------------------------------------------------
$sqlHeader = "INSERT INTO jurnal (
jurnalM_BranchCompanyID,
JurnalS_RegionalID,
jurnalM_BranchCode,
jurnalperiodeID,
jurnalNo,
jurnalTitle,
jurnalDescription,
jurnalDate,
jurnalJurnalTypeID,
jurnalCreated,
jurnalM_UserID
) VALUES (?,?,?,?,?,?,?,?,?,NOW(),?)";
$qryHeader = $this->db->query($sqlHeader, [
$branchCompanyID,
$regionalID,
$branchCode,
$periodeID,
$jurnalNo,
$title,
$description,
$date,
$jurnalTypeID,
$userID
]);
if (!$qryHeader) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert jurnal header.");
return false;
}
$jurnalID = $this->db->insert_id();
$createdAddonCount = 0;
// -----------------------------------------------------------------
// INSERT baris transaksi ke tabel `jurnal_tx` (per baris cicilan)
// -----------------------------------------------------------------
foreach ($details as $idx => $detail) {
$coaID = isset($detail["coaID"]) ? (int) $detail["coaID"] : 0;
$descriptionTx = isset($detail["description"]) ? trim($detail["description"]) : "";
$debit = isset($detail["debit"]) ? (float) $detail["debit"] : 0;
$credit = isset($detail["credit"]) ? (float) $detail["credit"] : 0;
if ($coaID <= 0) {
$this->db->trans_rollback();
$this->sys_error_db("coaID wajib diisi pada detail ke-" . ($idx + 1));
return false;
}
// Sisi debit = hutang cicilan (COA hutang aset dari fa_class)
// Sisi kredit = pembayaran cicilan (COA kas/bank)
$sqlTx = "INSERT INTO jurnal_tx (
jurnalTxJurnalID,
jurnalTxCoaID,
jurnalTxDescription,
jurnalTxDebit,
jurnalTxCredit,
jurnalTxCreated,
jurnalTxM_UserID
) VALUES (?,?,?,?,?,NOW(),?)";
$qryTx = $this->db->query($sqlTx, [
$jurnalID,
$coaID,
$descriptionTx,
$debit,
$credit,
$userID
]);
if (!$qryTx) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert jurnal_tx pada detail ke-" . ($idx + 1));
return false;
}
$jurnalTxID = $this->db->insert_id();
// -----------------------------------------------------------------
// INSERT addon ke `jurnal_addon` (jika addonValue atau mItemID ada)
// addonValue = ID kontrak aset (untuk tracing)
// mItemID = ID item aset dari purchase_order_summary
// -----------------------------------------------------------------
$addonValue = isset($detail["addonValue"]) ? trim($detail["addonValue"]) : "";
$mItemID = isset($detail["mItemID"]) ? (int) $detail["mItemID"] : 0;
$addonCode = isset($detail["addonCode"]) && $detail["addonCode"] != "" ? trim($detail["addonCode"]) : "PIINSTALMENT";
if ($addonValue != "" || $mItemID > 0) {
$sqlAddon = "INSERT INTO jurnal_addon (
jurnalAddOnJurnalID,
jurnalAddOnJurnalTxID,
jurnalAddOnCode,
jurnalAddOnValue,
jurnalAddOnM_ItemID,
jurnalAddOnCreated,
jurnalAddOnCreatedUserID
) VALUES (?,?,?,?,?,NOW(),?)";
$qryAddon = $this->db->query($sqlAddon, [
$jurnalID,
$jurnalTxID,
$addonCode,
$addonValue,
$mItemID > 0 ? $mItemID : null,
$userID
]);
if (!$qryAddon) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert jurnal_addon pada detail ke-" . ($idx + 1));
return false;
}
$createdAddonCount++;
}
}
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
$this->sys_error_db("Transaksi jurnal gagal.");
return false;
}
$this->db->trans_commit();
return [
"jurnalID" => $jurnalID,
"jurnalNo" => $jurnalNo,
"detailCount" => count($details),
"addonCount" => $createdAddonCount
];
}
/**
* generateJournalNumber — Generate nomor jurnal otomatis.
*
* Memanggil stored function MySQL fn_numbering('J') untuk mendapatkan
* nomor jurnal berikutnya sesuai urutan penomoran yang berlaku.
*
* @return string|false Nomor jurnal (misal: "J/2025/07/0001"), atau false jika gagal.
*/
private function generateJournalNumber()
{
$qry = $this->db->query("SELECT fn_numbering('J') AS JNumber");
if (!$qry || $qry->num_rows() == 0) {
return false;
}
return $qry->row_array()["JNumber"];
}
/**
* getPeriodeIDByDate — Ambil ID periode akuntansi berdasarkan tanggal transaksi.
*
* Periode diambil dari tabel `periode` yang aktif dan mencakup tanggal yang diberikan.
* Digunakan untuk mengisi field jurnalperiodeID pada header jurnal.
*
* @param string $date Tanggal transaksi (format YYYY-MM-DD)
* @return int periodeID yang sesuai, atau 0 jika tidak ditemukan
*/
private function getPeriodeIDByDate($date)
{
$sql = "SELECT periodeID
FROM periode
WHERE periodeIsActive = 'Y'
AND DATE(?) BETWEEN periodeStartDate AND periodeEndDate
LIMIT 1";
$qry = $this->db->query($sql, [$date]);
if (!$qry || $qry->num_rows() == 0) {
return 0;
}
return (int) $qry->row_array()["periodeID"];
}
/**
* getBranchCompanyIDByBranchCode — Ambil ID branch company berdasarkan kode cabang.
*
* Data diambil dari tabel `m_branch_companydetail` yang aktif.
* Digunakan untuk mengisi field jurnalM_BranchCompanyID pada header jurnal.
*
* @param string $branchCode Kode cabang (misal: "JKT-01")
* @return int branchCompanyID yang sesuai, atau 0 jika tidak ditemukan
*/
private function getBranchCompanyIDByBranchCode($branchCode)
{
$sql = "SELECT M_BranchCompanyDetailM_BranchCompanyID AS branchCompanyID
FROM m_branch_companydetail
WHERE M_BranchCompanyDetailM_BranchCode = ?
AND M_BranchCompanyDetailIsActive = 'Y'
LIMIT 1";
$qry = $this->db->query($sql, [$branchCode]);
if (!$qry || $qry->num_rows() == 0) {
return 0;
}
return (int) $qry->row_array()["branchCompanyID"];
}
/**
* getRegionalIDByBranchCode — Ambil ID regional berdasarkan kode cabang.
*
* Data diambil dari tabel `m_branch` yang aktif.
* Digunakan untuk mengisi field JurnalS_RegionalID pada header jurnal.
*
* @param string $branchCode Kode cabang (misal: "JKT-01")
* @return int regionalID yang sesuai, atau 0 jika tidak ditemukan
*/
private function getRegionalIDByBranchCode($branchCode)
{
$sql = "SELECT M_BranchS_RegionalID AS regionalID
FROM m_branch
WHERE M_BranchCode = ?
AND M_BranchIsActive = 'Y'
LIMIT 1";
$qry = $this->db->query($sql, [$branchCode]);
if (!$qry || $qry->num_rows() == 0) {
return 0;
}
return (int) $qry->row_array()["regionalID"];
}
/**
* getJurnalTypeID — Ambil ID tipe jurnal berdasarkan kode tipe.
*
* Data diambil dari tabel `jurnal_type` yang aktif.
* Untuk PI cicilan order aset, kode yang digunakan adalah "PAYMENT".
*
* @param string $jurnalTypeCode Kode tipe jurnal (misal: "PAYMENT")
* @return int JurnalTypeID yang sesuai, atau 0 jika tidak ditemukan
*/
private function getJurnalTypeID($jurnalTypeCode)
{
$sql = "SELECT JurnalTypeID
FROM jurnal_type
WHERE JurnalTypeCode = ?
AND JurnalTypeIsActive = 'Y'
LIMIT 1";
$qry = $this->db->query($sql, [$jurnalTypeCode]);
if (!$qry || $qry->num_rows() == 0) {
return 0;
}
return (int) $qry->row_array()["JurnalTypeID"];
}
/**
* getEligiblePurchaseOrderAssetContracts — Ambil kontrak aset yang layak dibuatkan jurnal.
*
* Query ini mengambil semua kontrak dari `purchase_order_asset_contract` yang memenuhi
* syarat berikut:
* - Kontrak aktif, status "belum lunas", nilai cicilan > 0
* - Cicilan terbayar masih kurang dari total cicilan
* - Tanggal kontrak masuk dalam rentang periode yang diberikan
* - Tanggal jatuh tempo cicilan sudah lewat atau sama dengan hari ini
* - PO sudah Approved dan aktif
* - Barang sudah diterima dan receive order sudah confirmed
*
* Data yang dikembalikan mencakup:
* - Detail kontrak (ID, nama, tanggal, nilai cicilan)
* - Data PO (nomor PO, supplier, payment term, warehouse)
* - Data cabang (kode cabang, nama, regionalID, branchCompanyID)
* - Data item PO pertama (sebagai referensi baris jurnal)
*
* @param string $startDate Awal periode filter (YYYY-MM-DD)
* @param string $endDate Akhir periode filter (YYYY-MM-DD)
* @return array Array of row kontrak yang eligible
*/
private function getEligiblePurchaseOrderAssetContracts($startDate, $endDate)
{
$sql = "SELECT
c.PurchaseOrderAssetContractID,
si.SupplierInvoiceID,
si.SupplierInvoiceNumber,
c.PurchaseOrderAssetContractPurchaseOrderID,
c.PurchaseOrderAssetContractName,
c.PurchaseOrderAssetContractStartDate,
c.PurchaseOrderAssetContractEndDate,
c.PurchaseOrderAssetContractInstallmentNumber,
c.PurchaseOrderAssetContractInstallmentPaid,
c.PurchaseOrderAssetContractInstallmentDate,
c.PurchaseOrderAssetContractInstallmentPayAmount,
c.PurchaseOrderAssetContractReceiveOrderPoID,
po.PurchaseOrderID,
po.PurchaseOrderNumber,
po.PurchaseOrderSupplierID,
po.PurchaseOrderPaymentTerm,
po.PurchaseOrderWarehouseType,
po.PurchaseOrderWarehouseID,
wh.WarehouseCode,
wh.WarehouseName,
wh.WarehouseType,
wh.WarehouseS_RegionalID AS WarehouseS_RegionalID,
wh.WarehouseM_BranchID AS WarehouseM_BranchID,
ro.ReceiveOrderPoID,
ro.ReceiveOrderPoConfirmed,
mi.M_ItemID AS ItemID,
mi.M_ItemCode AS ItemCode,
mi.M_ItemDesc AS ItemDesc,
mi.M_ItemFa_ClassID,
fc.Fa_ClassID AS ItemFaClassID,
fc.Fa_ClassName AS ItemFaClassName,
coa_bayar.coaID AS ItemCoaID,
coa_bayar.coaAccountNo AS ItemCoaAccountNo,
coa_bayar.coaDescription AS ItemCoaDesc,
coa_hutang.coaID AS HutangCoaID,
coa_hutang.coaAccountNo AS HutangCoaAccountNo,
coa_hutang.coaDescription AS HutangCoaDesc,
m_branch.M_BranchCode,
m_branch.M_BranchName,
m_branch.M_BranchS_RegionalID,
m_branch_companydetail.M_BranchCompanyDetailM_BranchCompanyID AS M_BranchCompanyID,
ps.PurchaseOrderSummaryID,
ps.PurchaseOrderSummaryItemID,
ps.PurchaseOrderSummaryItemUnitID
FROM purchase_order_asset_contract c
-- Pastikan PO sudah Approved dan aktif
JOIN purchase_order po
ON po.PurchaseOrderID = c.PurchaseOrderAssetContractPurchaseOrderID
AND po.PurchaseOrderIsActive = 'Y'
AND po.PurchaseOrderStatus = 'Approved'
-- Data warehouse/gudang tujuan PO
LEFT JOIN warehouse wh
ON wh.WarehouseID = po.PurchaseOrderWarehouseID
-- Ambil 1 item PO pertama sebagai referensi baris jurnal
LEFT JOIN (
SELECT ps0.*
FROM purchase_order_summary ps0
JOIN (
SELECT
PurchaseOrderSummaryPurchaseOrderID,
MIN(PurchaseOrderSummaryID) AS PurchaseOrderSummaryID
FROM purchase_order_summary
WHERE PurchaseOrderSummaryIsActive = 'Y'
GROUP BY PurchaseOrderSummaryPurchaseOrderID
) psx
ON psx.PurchaseOrderSummaryID = ps0.PurchaseOrderSummaryID
) ps
ON ps.PurchaseOrderSummaryPurchaseOrderID = po.PurchaseOrderID
-- Hubungkan dengan detail supplier_invoice_detail terlebih dahulu
JOIN supplier_invoice_detail sid
ON sid.SupplierInvoiceDetailPurchaseOrderID = po.PurchaseOrderID
AND sid.SupplierInvoiceDetailPurchaseOrderSummaryID = ps.PurchaseOrderSummaryID
AND sid.SupplierInvoiceDetailIsActive = 'Y'
-- Baru hubungkan ke header supplier_invoice untuk mengambil nomor invoice
JOIN supplier_invoice si
ON si.SupplierInvoiceID = sid.SupplierInvoiceDetailSupplierInvoiceID
AND si.SupplierInvoiceDate >= DATE(?)
AND si.SupplierInvoiceDate <= DATE(?)
AND si.SupplierInvoiceIsActive = 'Y'
-- Pastikan barang sudah diterima (receive order confirmed)
JOIN (
SELECT
rd.ReceiveOrderPoDetailPurchaseOrderID,
MIN(ro0.ReceiveOrderPoID) AS ReceiveOrderPoID,
MAX(ro0.ReceiveOrderPoConfirmed) AS ReceiveOrderPoConfirmed
FROM receive_order_po ro0
JOIN receive_order_po_detail rd
ON rd.ReceiveOrderPoDetailReceiveOrderPoID = ro0.ReceiveOrderPoID
AND rd.ReceiveOrderPoDetailIsActive = 'Y'
WHERE ro0.ReceiveOrderPoIsActive = 'Y'
AND ro0.ReceiveOrderPoConfirmed = 'Y'
GROUP BY rd.ReceiveOrderPoDetailPurchaseOrderID
) ro
ON ro.ReceiveOrderPoDetailPurchaseOrderID = po.PurchaseOrderID
-- Data cabang dari warehouse
LEFT JOIN m_branch
ON m_branch.M_BranchID = wh.WarehouseM_BranchID
AND m_branch.M_BranchIsActive = 'Y'
-- Data branch company untuk header jurnal
LEFT JOIN m_branch_companydetail
ON m_branch_companydetail.M_BranchCompanyDetailM_BranchCode = m_branch.M_BranchCode
AND m_branch_companydetail.M_BranchCompanyDetailIsActive = 'Y'
-- COA jurnal diambil dari item -> fa_class
LEFT JOIN m_item mi
ON mi.M_ItemID = ps.PurchaseOrderSummaryItemID
AND mi.M_ItemIsActive = 'Y'
LEFT JOIN fa_class fc
ON fc.Fa_ClassID = mi.M_ItemFa_ClassID
AND fc.Fa_ClassIsActive = 'Y'
-- Join langsung ke tabel coa untuk memastikan kevalidan akun dan deskripsi
LEFT JOIN coa coa_hutang
ON coa_hutang.coaID = fc.Fa_ClassHutangCoaID
AND coa_hutang.coaIsActive = 'Y'
LEFT JOIN coa coa_bayar
ON coa_bayar.coaID = fc.Fa_ClassCoaID
AND coa_bayar.coaIsActive = 'Y'
WHERE c.PurchaseOrderAssetContractIsActive = 'Y'
AND c.PurchaseOrderAssetContractStatus = 'belum lunas'
-- Hanya kontrak yang ada nilai cicilannya
AND IFNULL(c.PurchaseOrderAssetContractInstallmentPayAmount, 0) > 0
-- Hanya kontrak yang belum selesai seluruh cicilannya
AND IFNULL(c.PurchaseOrderAssetContractInstallmentPaid, 0) < IFNULL(c.PurchaseOrderAssetContractInstallmentNumber, 0)
-- Kontrak sudah mulai sebelum atau pada akhir periode
AND DATE(c.PurchaseOrderAssetContractStartDate) <= DATE(?)
-- Kontrak belum berakhir (atau tidak ada tanggal akhir)
AND (
c.PurchaseOrderAssetContractEndDate IS NULL
OR DATE(c.PurchaseOrderAssetContractEndDate) >= DATE(?)
)
-- Tanggal jatuh tempo cicilan sudah tiba
AND IFNULL(c.PurchaseOrderAssetContractInstallmentDate, 1) <= ?
-- Pastikan belum pernah dibuatkan jurnal untuk invoice cicilan ini
AND NOT EXISTS (
SELECT 1
FROM jurnal_addon ja
JOIN jurnal j ON j.jurnalID = ja.jurnalAddOnJurnalID
WHERE ja.jurnalAddOnCode = 'PIINSTALMENT'
AND ja.jurnalAddOnValue = si.SupplierInvoiceNumber
)
ORDER BY c.PurchaseOrderAssetContractID ASC";
$qry = $this->db->query($sql, [
$startDate,
$endDate,
$endDate,
$startDate,
(int) date("d", strtotime($endDate))
]);
if (!$qry) {
$this->sys_error_db("Gagal mengambil data purchase_order_asset_contract.");
exit;
}
return $qry->result_array();
}
/**
* isValidDate — Validasi format tanggal YYYY-MM-DD.
*
* @param string $tanggal String tanggal yang akan divalidasi
* @return bool true jika format valid, false jika tidak
*/
private function isValidDate($tanggal)
{
$d = DateTime::createFromFormat("Y-m-d", $tanggal);
return $d && $d->format("Y-m-d") === $tanggal;
}
}

View File

@@ -1,65 +0,0 @@
# ============================================================
# FILE : PurchaseInvoiceInstallment.http
# MODUL : Scheduler — Generate Supplier Installment Cicilan Aset
# ============================================================
#
# DESKRIPSI
# ---------
# File ini berisi HTTP request untuk menguji endpoint scheduler
# yang secara otomatis membuat baris cicilan ke tabel
# supplier_installment setiap bulan.
#
# 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.
# 3. Klik "Send Request" di atas tiap blok ###
#
# ALUR
# ----
# [1] GenerateMonthlyInvoices → Jalankan proses generate installment
#
# CATATAN
# -------
# - Default periode: bulan berjalan (tanggal 1 s/d akhir bulan)
# - Aman dijalankan ulang — dedup berdasarkan PO + bulan mencegah
# duplikasi
# - Tidak membuat jurnal — jurnal dibuat saat pembayaran cashier
# - Tidak update kontrak — InstallmentPaid diupdate saat pembayaran
# ============================================================
@host = https://accone.aplikasi.web.id/one-api
# ============================================================
# [1] GENERATE INSTALLMENT BULANAN (MAIN ENDPOINT)
# ============================================================
# Satu request ini akan:
# Step 1: Validasi kontrak (aktif, belum lunas, dalam rentang)
# Step 2: Validasi PO (Approved, Active)
# Step 3: Validasi RO (Confirmed, Active)
# Step 4: Validasi parent invoice (IsInstallment = 'Y')
# Step 5: Cek dedup (belum ada installment bulan ini)
# Step 6: INSERT ke supplier_installment (Status = 'Pending')
#
# Response:
# - totalEligible : jumlah kontrak lolos validasi
# - totalInserted : jumlah baris berhasil diinsert
# - created[] : detail tiap installment yang dibuat
# ============================================================
### [1] Generate installment untuk periode tertentu
POST {{host}}/mockup/scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
Content-Type: application/json
{
"startDate": "2026-07-01",
"endDate": "2026-07-31"
}
### [1a] Generate installment bulan berjalan (tanpa parameter)
POST {{host}}/mockup/scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
Content-Type: application/json
{}

View File

@@ -1,302 +0,0 @@
<?php
/**
* PurchaseInvoiceInstallment
*
* CRON scheduler untuk membuat baris cicilan otomatis ke tabel supplier_installment
* setiap bulan berdasarkan kontrak aset yang aktif dan belum lunas.
*
* Endpoint:
* POST /scheduler/PurchaseInvoiceInstallment/GenerateMonthlyInvoices
*/
class PurchaseInvoiceInstallment extends MY_Controller
{
var $db;
public function index()
{
echo "Purchase Invoice Installment — Auto Generate Installment Cicilan Aset";
}
public function __construct()
{
parent::__construct();
}
/**
* GenerateMonthlyInvoices — Step 6
*
* Creates supplier_installment rows for all eligible contracts this month.
* No journal is created here — that happens at cashier payment time.
* Contract InstallmentPaid is NOT updated here — that happens at payment time.
*
* Called by CRON daily at 1:00 AM.
* Defaults to current month if no startDate/endDate provided.
*/
public function GenerateMonthlyInvoices()
{
try {
$para = $this->sys_input;
$userID = !empty($this->sys_user["M_UserID"])
? (int) $this->sys_user["M_UserID"]
: 0;
$startDate = !empty($para["startDate"])
? $para["startDate"]
: date("Y-m-01");
$endDate = !empty($para["endDate"])
? $para["endDate"]
: date("Y-m-t");
if (!$this->isValidDate($startDate) || !$this->isValidDate($endDate)) {
throw new Exception("Format tanggal tidak valid.");
}
if (strtotime($startDate) > strtotime($endDate)) {
throw new Exception("startDate > endDate.");
}
// ── Steps 15: Get eligible contracts ────────────────────
$eligible = $this->getEligibleInstallments($startDate, $endDate);
$created = [];
$inserted = 0;
$this->db->trans_begin();
foreach ($eligible as $row) {
$amount = (float) $row["PurchaseOrderAssetContractInstallmentPayAmount"];
$dayOfMonth = (int) ($row["PurchaseOrderAssetContractInstallmentDate"] ?? 1);
$lastDay = (int) date("t", strtotime($endDate));
$dayOfMonth = min($dayOfMonth, $lastDay);
$installDate = date("Y-m", strtotime($endDate)) . "-" . str_pad($dayOfMonth, 2, "0", STR_PAD_LEFT);
$dueDate = date("Y-m-d", strtotime($installDate . " +7 days"));
$createdBy = (int) ($row["PurchaseOrderAssetContractCreatedUserID"] ?? $userID);
// ── Step 6: INSERT supplier_installment ──────────────
$sql = "INSERT INTO supplier_installment (
SupplierInstallmentPurchaseOrderID,
SupplierInstallmentSupplierID,
SupplierInstallmentSupplierInvoiceID,
SupplierInstallmentAmount,
SupplierInstallmentDate,
SupplierInstallmentDueDate,
SupplierInstallmentPaymentID,
SupplierInstallmentStatus,
SupplierInstallmentIsLunas,
SupplierInstallmentIsActive,
SupplierInstallmentCreated,
SupplierInstallmentCreatedUserID
) VALUES (?, ?, ?, ?, ?, ?, 0, 'Pending', 'N', 'Y', NOW(), ?)";
$que = $this->db->query($sql, [
$row["PurchaseOrderID"],
$row["PurchaseOrderSupplierID"],
$row["SupplierInvoiceID"],
$amount,
$installDate,
$installDate,
$createdBy
]);
if (!$que) {
$this->db->trans_rollback();
$this->sys_error_db("Gagal insert supplier_installment.");
exit;
}
$installmentID = $this->db->insert_id();
$inserted++;
$created[] = [
"installmentID" => $installmentID,
"contractID" => $row["PurchaseOrderAssetContractID"],
"purchaseOrderID" => $row["PurchaseOrderID"],
"parentInvoiceID" => $row["SupplierInvoiceID"],
"parentInvoiceNumber" => $row["SupplierInvoiceNumber"],
"amount" => $amount,
"installDate" => $installDate,
"dueDate" => $dueDate
];
}
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
$this->sys_error_db("Transaksi gagal.");
exit;
}
$this->db->trans_commit();
$this->sys_ok([
"startDate" => $startDate,
"endDate" => $endDate,
"totalEligible" => count($eligible),
"totalInserted" => $inserted,
"created" => $created
]);
} catch (Exception $exc) {
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
}
$this->sys_error($exc->getMessage());
}
}
/**
* 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;
}
/**
* getEligibleInstallments — Steps 15 combined
*
* Returns contracts that:
* Step 1: Are active, not paid off, within date range, due date reached
* Step 2: Have approved & active PO
* Step 3: Have confirmed & active RO
* Step 4: Have a parent supplier_invoice with IsInstallment = 'Y'
* Step 5: Do NOT already have a supplier_installment for this month
*
* @param string $startDate YYYY-MM-DD
* @param string $endDate YYYY-MM-DD
* @return array
*/
private function getEligibleInstallments($startDate, $endDate)
{
$dayOfMonth = (int) date("d", strtotime($endDate));
$sql = "SELECT
c.PurchaseOrderAssetContractID,
c.PurchaseOrderAssetContractInstallmentPayAmount,
c.PurchaseOrderAssetContractInstallmentDate,
c.PurchaseOrderAssetContractCreatedUserID,
po.PurchaseOrderID,
po.PurchaseOrderSupplierID,
si.SupplierInvoiceID,
si.SupplierInvoiceNumber
FROM purchase_order_asset_contract c
-- Step 2: PO must be Approved and Active
JOIN purchase_order po
ON po.PurchaseOrderID = c.PurchaseOrderAssetContractPurchaseOrderID
AND po.PurchaseOrderIsActive = 'Y'
AND po.PurchaseOrderStatus = 'Approved'
-- Step 3: RO must be Confirmed and Active
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
-- Step 4: Parent invoice must exist with IsInstallment = 'Y'
JOIN supplier_invoice si
ON si.SupplierInvoiceReceiveOrderPoID = ro.ReceiveOrderPoID
AND si.SupplierInvoiceIsActive = 'Y'
AND si.SupplierInvoiceIsInstallment = 'Y'
-- Step 1: Contract eligibility
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) <= ?
-- Step 5: Dedup — no existing supplier_installment this month (any status)
AND NOT EXISTS (
SELECT 1
FROM supplier_installment inst
WHERE inst.SupplierInstallmentIsActive = 'Y'
AND inst.SupplierInstallmentPurchaseOrderID = po.PurchaseOrderID
AND DATE_FORMAT(inst.SupplierInstallmentDate, '%Y-%m') = DATE_FORMAT(?, '%Y-%m')
)
ORDER BY c.PurchaseOrderAssetContractID ASC";
$params = [$endDate, $startDate, $dayOfMonth, $endDate];
$qry = $this->db->query($sql, $params);
if (!$qry) {
$this->sys_error_db("Gagal mengambil daftar installment eligible.");
exit;
}
return $qry->result_array();
}
}

View File

@@ -1,220 +0,0 @@
<?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;
}
}

View File

@@ -28,92 +28,29 @@ class Billv2 extends MY_Controller {
$offset = ($params['currentpage'] - 1) * $limit;
}
// ── UNION base — invoice + downpayment + installment ───
$sql_base = "
SELECT
sp.SupplierPaymentID,
sp.SupplierPaymentDate,
sp.SupplierPaymentNumber,
sp.SupplierPaymentAmount,
sp.SupplierPaymentStatus,
sp.SupplierPaymentIsVerif,
sp.SupplierPaymentIsApproved,
sp.SupplierPaymentIsActive,
si.SupplierInvoiceID,
si.SupplierInvoiceNumber,
si.SupplierInvoiceDraftPaymentDate,
sup.SupplierCode,
sup.SupplierName,
'INVOICE' AS type
FROM supplier_payment sp
JOIN supplier_invoice si
ON si.SupplierInvoiceID = sp.SupplierPaymentSupplierInvoiceID
JOIN supplier sup
ON sup.SupplierID = si.SupplierInvoiceSupplierID
WHERE sp.SupplierPaymentSupplierInvoiceID > 0
UNION ALL
SELECT
sp.SupplierPaymentID,
sp.SupplierPaymentDate,
sp.SupplierPaymentNumber,
sp.SupplierPaymentAmount,
sp.SupplierPaymentStatus,
sp.SupplierPaymentIsVerif,
sp.SupplierPaymentIsApproved,
sp.SupplierPaymentIsActive,
dp.SupplierDownpaymentID * -1 AS SupplierInvoiceID,
CONCAT('DP-', po.PurchaseOrderNumber) AS SupplierInvoiceNumber,
dp.SupplierDownpaymentDueDate AS SupplierInvoiceDraftPaymentDate,
dp_sup.SupplierCode AS SupplierCode,
dp_sup.SupplierName AS SupplierName,
'DP' AS type
FROM supplier_payment sp
JOIN supplier_downpayment dp
ON dp.SupplierDownpaymentID = sp.SupplierPaymentSupplierDownpaymentID
JOIN supplier dp_sup
ON dp_sup.SupplierID = dp.SupplierDownpaymentSupplierID
JOIN purchase_order po
ON po.PurchaseOrderID = dp.SupplierDownpaymentPurchasOrderID
WHERE sp.SupplierPaymentSupplierDownpaymentID IS NOT NULL
UNION ALL
SELECT
sp.SupplierPaymentID,
sp.SupplierPaymentDate,
sp.SupplierPaymentNumber,
sp.SupplierPaymentAmount,
sp.SupplierPaymentStatus,
sp.SupplierPaymentIsVerif,
sp.SupplierPaymentIsApproved,
sp.SupplierPaymentIsActive,
0 AS SupplierInvoiceID,
CONCAT('INST-', si_inst.SupplierInvoiceNumber) AS SupplierInvoiceNumber,
inst.SupplierInstallmentDueDate AS SupplierInvoiceDraftPaymentDate,
inst_sup.SupplierCode AS SupplierCode,
inst_sup.SupplierName AS SupplierName,
'INSTALLMENT' AS type
FROM supplier_payment sp
JOIN supplier_installment inst
ON inst.SupplierInstallmentID = sp.SupplierPaymentSupplierInstallmentID
JOIN supplier_invoice si_inst
ON si_inst.SupplierInvoiceID = inst.SupplierInstallmentSupplierInvoiceID
JOIN supplier inst_sup
ON inst_sup.SupplierID = inst.SupplierInstallmentSupplierID
WHERE sp.SupplierPaymentSupplierInstallmentID IS NOT NULL";
// ── Outer: common filters + ordering + pagination ───────
$sql_data = "
SELECT * FROM ($sql_base) AS combined
WHERE SupplierPaymentIsActive = 'Y'
$sql_base = "SELECT
SupplierPaymentID,
SupplierPaymentDate,
SupplierPaymentNumber,
SupplierPaymentAmount,
SupplierPaymentStatus,
SupplierPaymentIsVerif,
SupplierPaymentIsApproved,
SupplierInvoiceID,
SupplierInvoiceNumber,
SupplierInvoiceDraftPaymentDate,
SupplierCode,
SupplierName
FROM supplier_payment
JOIN supplier_invoice ON SupplierInvoiceID = SupplierPaymentSupplierInvoiceID
AND SupplierPaymentNumber LIKE ?
AND (SupplierPaymentDate BETWEEN DATE(?) AND DATE(?))
AND (SupplierPaymentStatus = ? OR ? = 'All')
ORDER BY SupplierPaymentID DESC
LIMIT ? OFFSET ?";
JOIN supplier ON SupplierID = SupplierInvoiceSupplierID
WHERE SupplierPaymentIsActive = 'Y'
ORDER BY SupplierPaymentID DESC";
$sql_data = $sql_base . " LIMIT ? OFFSET ? ";
$que_data = $this->db->query($sql_data, [
$keyword, $params['startdate'], $params['enddate'],
$params['status'], $params['status'], $limit, $offset
@@ -122,13 +59,7 @@ class Billv2 extends MY_Controller {
throw new Exception("[Error] failed get data supplier payment", 2);
}
// ── COUNT — wrap UNION in outer filter ──────────────────
$sql_total = "SELECT COUNT(*) AS total FROM ($sql_base) AS combined
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentNumber LIKE ?
AND (SupplierPaymentDate BETWEEN DATE(?) AND DATE(?))
AND (SupplierPaymentStatus = ? OR ? = 'All')";
$sql_total = "SELECT COUNT(*) AS total FROM ($sql_base) AS x";
$que_total = $this->db->query($sql_total, [
$keyword, $params['startdate'], $params['enddate'],
$params['status'], $params['status']
@@ -143,6 +74,7 @@ class Billv2 extends MY_Controller {
];
$this->sys_ok($output);
exit;
} catch (Exception $exc) {
$message = $exc->getMessage();
$code = $exc->getCode();
@@ -164,40 +96,28 @@ class Billv2 extends MY_Controller {
$para = $this->sys_input;
// ── Detect payment type ──────────────────────────────────
$sql_type = "SELECT
SupplierPaymentSupplierInvoiceID,
SupplierPaymentSupplierDownpaymentID,
SupplierPaymentSupplierInstallmentID
FROM supplier_payment
WHERE SupplierPaymentID = ? AND SupplierPaymentIsActive = 'Y'";
$que_type = $this->db->query($sql_type, [$para['paymentID']]);
if (!$que_type) {
throw new Exception("[Error] failed get payment header", 2);
}
$payment = $que_type->row_array();
if (!$payment) {
throw new Exception("[Error] payment not found", 2);
}
// ── INVOICE branch ──────────────────────────────────────
if ($payment['SupplierPaymentSupplierInvoiceID'] > 0) {
$sql = "SELECT
si.SupplierInvoiceSubTotal,
si.SupplierInvoiceShippingCost,
si.SupplierInvoiceDiscountPercent,
si.SupplierInvoiceDiscountAmount,
si.SupplierInvoiceTaxPercentPpn,
si.SupplierInvoiceTaxAmountPpn,
si.SupplierInvoiceGrandTotal,
si.SupplierInvoiceID,
'INVOICE' AS type
FROM supplier_payment sp
JOIN supplier_invoice si
ON si.SupplierInvoiceID = sp.SupplierPaymentSupplierInvoiceID
WHERE sp.SupplierPaymentID = ? AND sp.SupplierPaymentIsActive = 'Y'";
SupplierInvoiceID,
SupplierInvoiceRefNumber,
SupplierInvoiceDeliveryOrderNumber,
SupplierInvoiceSupplierInvoiceNumber,
SupplierInvoiceSupplierInvoiceDate,
SupplierInvoiceSubTotal,
SupplierInvoiceTaxPercentPph,
SupplierInvoiceTaxPercentPpn,
SupplierInvoiceTaxAmountPpn,
SupplierInvoiceDiscountAmount,
SupplierInvoiceDiscountPercent,
SupplierInvoiceShippingCost,
SupplierInvoiceGrandTotal,
SupplierInvoiceAdjustmentAmount,
SupplierInvoiceAdjustmentNote,
SupplierInvoiceNote,
IF (SupplierInvoiceDiscountAmount > 0, 'R', 'P') AS DiscountType
FROM supplier_payment
JOIN supplier_invoice ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID
AND SupplierPaymentID = ?
AND SupplierPaymentIsActive = 'Y'";
$que = $this->db->query($sql, [$para['paymentID']]);
if (!$que) {
throw new Exception("[Error] failed get row data", 2);
@@ -205,21 +125,33 @@ class Billv2 extends MY_Controller {
$data = $que->row_array();
$sql_detail = "SELECT
M_ItemDesc,
SupplierInvoiceDetailID,
SupplierInvoiceDetailSupplierInvoiceID,
SupplierInvoiceDetailPurchaseOrderID,
SupplierInvoiceDetailPurchaseOrderSummaryID,
SupplierInvoiceDetailReceiveOrderPoID,
SupplierInvoiceDetailReceiveOrderPoDetailID,
SupplierInvoiceDetailItemID,
SupplierInvoiceDetailItemUnitID,
SupplierInvoiceDetailDescription,
SupplierInvoiceDetailQty,
SupplierInvoiceDetailPrice,
SupplierInvoiceDetailDiscountPercent,
SupplierInvoiceDetailDiscountDiscountRupiah,
SupplierInvoiceDetailDiscountDiscountType,
SupplierInvoiceDetailDiscountAmount,
(SupplierInvoiceDetailPrice - SupplierInvoiceDetailDiscountAmount) AS DiscountedPrice,
SupplierInvoiceDetailTotal
SupplierInvoiceDetailDiscountPoProrata,
SupplierInvoiceDetailTotal,
M_ItemCode,
M_ItemDesc
FROM supplier_payment_detail
JOIN supplier_invoice_detail
ON SupplierInvoiceDetailIsActive = 'Y'
JOIN supplier_invoice_detail ON SupplierInvoiceDetailIsActive = 'Y'
AND SupplierPaymentDetailSupplierPaymentID = ?
AND SupplierInvoiceDetailSupplierInvoiceID = ?
JOIN m_item
ON M_ItemID = SupplierInvoiceDetailItemID AND M_ItemIsActive = 'Y'
JOIN m_item ON M_ItemID = SupplierInvoiceDetailItemID
AND M_ItemIsActive = 'Y'
GROUP BY SupplierInvoiceDetailID";
$que_detail = $this->db->query($sql_detail, [
$para['paymentID'], $data['SupplierInvoiceID']
]);
@@ -227,107 +159,8 @@ class Billv2 extends MY_Controller {
throw new Exception("[Error] failed to get item payments", 2);
}
unset($data['SupplierInvoiceID']);
$data['detail'] = $que_detail->result_array();
// ── DOWNPAYMENT branch ──────────────────────────────────
} elseif ($payment['SupplierPaymentSupplierDownpaymentID'] > 0) {
$sql = "SELECT
dp.SupplierDownpaymentPurchasOrderID,
dp.SupplierDownpaymentAmount AS SupplierInvoiceSubTotal,
0 AS SupplierInvoiceShippingCost,
0 AS SupplierInvoiceDiscountPercent,
0 AS SupplierInvoiceDiscountAmount,
0 AS SupplierInvoiceTaxPercentPpn,
0 AS SupplierInvoiceTaxAmountPpn,
dp.SupplierDownpaymentAmount AS SupplierInvoiceGrandTotal,
'DP' AS type
FROM supplier_payment sp
JOIN supplier_downpayment dp
ON dp.SupplierDownpaymentID = sp.SupplierPaymentSupplierDownpaymentID
WHERE sp.SupplierPaymentID = ? AND sp.SupplierPaymentIsActive = 'Y'";
$que = $this->db->query($sql, [$para['paymentID']]);
if (!$que) {
throw new Exception("[Error] failed get DP row data", 2);
}
$data = $que->row_array();
$sql_detail = "SELECT
CONCAT('DP-', M_ItemDesc) AS M_ItemDesc,
PurchaseOrderDetailQty AS SupplierInvoiceDetailQty,
PurchaseOrderDetailPrice AS SupplierInvoiceDetailPrice,
PurchaseOrderSummaryDiscountAmount AS SupplierInvoiceDetailDiscountAmount,
(PurchaseOrderDetailPrice - PurchaseOrderSummaryDiscountAmount) AS DiscountedPrice,
PurchaseOrderSummaryTotal AS SupplierInvoiceDetailTotal
FROM supplier_downpayment
JOIN purchase_order
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderID
JOIN purchase_order_detail
ON PurchaseOrderDetailPurchaseOrderID = PurchaseOrderID
AND PurchaseOrderDetailIsActive = 'Y'
JOIN purchase_order_summary
ON PurchaseOrderSummaryID = PurchaseOrderDetailPurchaseSummaryID
AND PurchaseOrderSummaryIsActive = 'Y'
JOIN m_item
ON M_ItemID = PurchaseOrderDetailItemID
WHERE SupplierDownpaymentPurchasOrderID = ?";
$que_detail = $this->db->query($sql_detail, [
$data['SupplierDownpaymentPurchasOrderID']
]);
if (!$que_detail) {
throw new Exception('failed to get dp detail', 2);
}
$data['detail'] = $que_detail->result_array();
} else {
$sql = "SELECT
inst.SupplierInstallmentAmount AS SupplierInvoiceSubTotal,
0 AS SupplierInvoiceShippingCost,
0 AS SupplierInvoiceDiscountPercent,
0 AS SupplierInvoiceDiscountAmount,
0 AS SupplierInvoiceTaxPercentPpn,
0 AS SupplierInvoiceTaxAmountPpn,
inst.SupplierInstallmentAmount AS SupplierInvoiceGrandTotal,
si.SupplierInvoiceID,
'INSTALLMENT' AS type
FROM supplier_payment sp
JOIN supplier_installment inst
ON inst.SupplierInstallmentID = sp.SupplierPaymentSupplierInstallmentID
JOIN supplier_invoice si
ON si.SupplierInvoiceID = inst.SupplierInstallmentSupplierInvoiceID
WHERE sp.SupplierPaymentID = ? AND sp.SupplierPaymentIsActive = 'Y'";
$que = $this->db->query($sql, [$para['paymentID']]);
if (!$que) {
throw new Exception("[Error] failed get Installment row data", 2);
}
$data = $que->row_array();
$sql_detail = "SELECT
M_ItemDesc,
SupplierInvoiceDetailQty,
SupplierInvoiceDetailPrice,
SupplierInvoiceDetailDiscountAmount,
(SupplierInvoiceDetailPrice - SupplierInvoiceDetailDiscountAmount) AS DiscountedPrice,
SupplierInvoiceDetailTotal
FROM supplier_invoice_detail
JOIN m_item
ON M_ItemID = SupplierInvoiceDetailItemID AND M_ItemIsActive = 'Y'
WHERE SupplierInvoiceDetailSupplierInvoiceID = ?
AND SupplierInvoiceDetailIsActive = 'Y'
GROUP BY SupplierInvoiceDetailID";
$que_detail = $this->db->query($sql_detail, [$data['SupplierInvoiceID']]);
if (!$que_detail) {
throw new Exception("[Error] failed to get installment item payments", 2);
}
unset($data['SupplierInvoiceID']);
$data['detail'] = $que_detail->result_array();
}
$this->sys_ok($data);
} catch (Exception $exc) {
$message = $exc->getMessage();

View File

@@ -12,8 +12,7 @@ class Bill extends MY_Controller
$this->db_onedev = $this->load->database("onedev", true);
}
public function add_notes($orderid)
{
public function add_notes($orderid){
$sql = " SELECT SupplierPaymentSupplierInvoiceID as note_order_id,
SupplierPaymentID as note_id,
SupplierPaymentDetailSupplierInvoiceDetailID as detail_id,
@@ -36,18 +35,15 @@ class Bill extends MY_Controller
CONCAT('Verified by : ',b.M_UserUsername, ' ',DATE_FORMAT(SupplierPaymentVerifDate,'%d-%m-%Y %H:%i')) as d_verif
FROM supplier_payment
JOIN supplier_payment_detail ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID AND SupplierPaymentDetailIsActive = 'Y'
LEFT JOIN coa
ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n
ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c
ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a
ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b
ON SupplierPaymentVerifUserID = b.M_UserID
WHERE SupplierPaymentSupplierInvoiceID = {$orderid}
AND SupplierPaymentIsActive = 'Y'
LEFT JOIN coa ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b ON SupplierPaymentVerifUserID = b.M_UserID
WHERE
SupplierPaymentSupplierInvoiceID = {$orderid}
AND
SupplierPaymentIsActive = 'Y'
GROUP BY SupplierPaymentID";
$query = $this->db_onedev->query($sql);
if ($query) {
@@ -58,149 +54,14 @@ class Bill extends MY_Controller
}
}
return $rows;
} else {
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function add_notes_downpayment($orderid)
{
$sql = "SELECT SupplierPaymentSupplierDownpaymentID AS note_order_id,
SupplierPaymentID AS note_id,
SupplierDownpaymentID AS detail_id,
SupplierPaymentDate AS note_date,
SupplierPaymentNumber AS note_number,
GROUP_CONCAT(DISTINCT coaDescription separator ' , ') AS paymenttypes_name,
SUM(SupplierDownpaymentAmount) AS note_amount,
n.M_UserUsername AS note_user,
SupplierDownpaymentIsActive AS note_active,
'N' AS show_detail,
SupplierPaymentNote AS keterangan,
SupplierPaymentCoaID,
coaID,
coaDescription,
SupplierPaymentIsConfirm,
CONCAT(
'Confirmed by : ',c.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentConfirmDate, '%d-%m-%Y %H:%i')
) AS d_confirm,
SupplierPaymentIsApproved,
CONCAT(
'Approved by : ',a.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentApprovedDate,'%d-%m-%Y %H:%i')
) AS d_approved,
CONCAT(
'Verified by : ',b.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentVerifDate,'%d-%m-%Y %H:%i')
) AS d_verif
FROM supplier_payment
JOIN supplier_downpayment
ON SupplierDownpaymentID = SupplierPaymentSupplierDownpaymentID
AND SupplierDownpaymentIsActive = 'Y'
LEFT JOIN coa
ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n
ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c
ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a
ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b
ON SupplierPaymentVerifUserID = b.M_UserID
WHERE SupplierPaymentSupplierDownpaymentID = {$orderid}
AND SupplierPaymentIsActive = 'Y'
GROUP BY SupplierPaymentID";
$query = $this->db_onedev->query($sql);
if ($query) {
$rows = $query->result_array();
return $rows ?: [];
} else {
$this->sys_error_db("get DP notes", $this->db_onedev);
exit;
}
}
public function add_notes_installment($orderid)
{
$sql = "SELECT SupplierPaymentSupplierInstallmentID as note_order_id,
SupplierPaymentID as note_id,
SupplierInstallmentID as detail_id,
SupplierPaymentDate as note_date,
SupplierPaymentNumber as note_number,
GROUP_CONCAT(DISTINCT coaDescription separator ' , ') as paymenttypes_name,
SUM(SupplierInstallmentAmount) as note_amount,
n.M_UserUsername as note_user,
SupplierInstallmentIsActive as note_active,
'N' as show_detail,
SupplierPaymentNote as keterangan,
SupplierPaymentCoaID,
coaID,
coaDescription,
SupplierPaymentIsConfirm,
CONCAT(
'Confirmed by : ',c.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentConfirmDate,'%d-%m-%Y %H:%i')
) AS d_confirm,
SupplierPaymentIsApproved,
CONCAT(
'Approved by : ',a.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentApprovedDate,'%d-%m-%Y %H:%i')
) AS d_approved,
CONCAT(
'Verified by : ',b.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentVerifDate,'%d-%m-%Y %H:%i')
) AS d_verif
FROM supplier_payment
JOIN supplier_installment
ON SupplierInstallmentID = SupplierPaymentSupplierInstallmentID
AND SupplierInstallmentIsActive = 'Y'
LEFT JOIN coa ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b ON SupplierPaymentVerifUserID = b.M_UserID
WHERE SupplierPaymentSupplierInstallmentID = {$orderid}
AND SupplierPaymentIsActive = 'Y'
GROUP BY SupplierPaymentID";
$query = $this->db_onedev->query($sql);
if ($query) {
$rows = $query->result_array();
return $rows ?: [];
} else {
$this->sys_error_db("get Installment notes", $this->db_onedev);
exit;
}
}
public function add_tagihans($orderid)
{
$sql_dp = "SELECT
Fa_ClassDownPaymentCoaID
FROM supplier_invoice_detail
JOIN m_item
ON M_ItemID = SupplierInvoiceDetailItemID
AND SupplierInvoiceDetailSupplierInvoiceID = ?
AND SupplierInvoiceDetailIsActive = 'Y'
AND SupplierInvoiceDetailIsVerified = 'Y'
JOIN fa_class
ON Fa_ClassID = M_ItemFa_ClassID
AND Fa_ClassIsActive = 'Y'";
$que_dp = $this->db_onedev->query($sql_dp, [$orderid]);
if (!$que_dp) {
$this->sys_error_db("failed to get coa id dp tagihans", $this->db_onedev);
exit;
}
$dataDP = $que_dp->result_array();
$excludeIds = array_column($dataDP, 'Fa_ClassDownPaymentCoaID');
$excludeIds[] = 563; // always excluded
$excludeIds = array_unique($excludeIds);
$excludeIds = array_values($excludeIds); // re-index
$notInPlaceholders = implode(',', array_fill(0, count($excludeIds), '?'));
$sql = "SELECT
SupplierInvoiceID as tagihan_id,
public function add_tagihans($orderid){
$sql = "SELECT SupplierInvoiceID as tagihan_id,
PurchaseOrderNumber as tagihan_number,
jurnalTxDescription as pasien,
jurnalTxCredit as tagihan_total,
@@ -212,43 +73,25 @@ class Bill extends MY_Controller
jurnalTxID SupplierInvoiceDetailID,
PurchaseOrderID SupplierInvoiceDetailPurchaseOrderID
FROM supplier_invoice
JOIN receive_order_po
ON ReceiveOrderPoID = SupplierInvoiceReceiveOrderPoID
JOIN receive_order_po_detail
ON ReceiveOrderPoDetailReceiveOrderPoID = ReceiveOrderPoID
JOIN purchase_order
ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID
JOIN jurnal_addon
ON jurnalAddOnValue = SupplierInvoiceNumber
AND jurnalAddOnIsActive = 'Y'
JOIN jurnal_tx
ON jurnalTxJurnalID = jurnalAddOnJurnalID
AND jurnalTxIsActive = 'Y'
AND jurnalTxCredit <> 0
AND jurnalTxCoaID NOT IN ($notInPlaceholders)
LEFT JOIN supplier_payment
ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID
AND SupplierInvoiceIsActive = 'Y'
LEFT JOIN supplier_payment_detail
ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
AND SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID
AND SupplierPaymentDetailIsActive = 'Y'
JOIN purchase_order ON SupplierInvoicePurchaseOrderID = PurchaseOrderID
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN jurnal_tx ON jurnalTxJurnalID = jurnalAddOnJurnalID AND jurnalTxCredit <> 0 AND jurnalTxCoaID <> 563
LEFT JOIN supplier_payment ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID AND SupplierInvoiceIsActive = 'Y'
LEFT JOIN supplier_payment_detail ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID AND SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID AND SupplierPaymentDetailIsActive = 'Y'
WHERE SupplierInvoiceID = ?
GROUP BY jurnalTxID";
$query = $this->db_onedev->query($sql, array_merge($excludeIds, [$orderid]));
$query = $this->db_onedev->query($sql, [$orderid]);
if ($query) {
$rows = $query->result_array();
return $rows;
} else {
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function add_tests($orderid)
{
$sql = "SELECT
SupplierPaymentSupplierInvoiceID as note_order_id,
public function add_tests($orderid){
$sql = " SELECT SupplierPaymentSupplierInvoiceID as note_order_id,
SupplierPaymentID as note_id,
SupplierPaymentDate as note_date,
SupplierPaymentNumber as note_number,
@@ -260,17 +103,13 @@ class Bill extends MY_Controller
SupplierInvoiceDetailTotal,
SupplierPaymentDetailAmount
FROM supplier_payment
JOIN supplier_payment_detail
ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
LEFT JOIN supplier_invoice_detail
ON SupplierPaymentDetailSupplierInvoiceDetailID = SupplierInvoiceDetailID
LEFT JOIN purchase_order
ON SupplierInvoiceDetailPurchaseOrderID = PurchaseOrderID
JOIN coa
ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user
ON SupplierPaymentDetailUserID = M_UserID
WHERE SupplierPaymentID = {$orderid}
JOIN supplier_payment_detail ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
LEFT JOIN supplier_invoice_detail ON SupplierPaymentDetailSupplierInvoiceDetailID = SupplierInvoiceDetailID
LEFT JOIN purchase_order ON SupplierInvoiceDetailPurchaseOrderID = PurchaseOrderID
JOIN coa ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user ON SupplierPaymentDetailUserID = M_UserID
WHERE
SupplierPaymentID = {$orderid}
GROUP BY SupplierPaymentDetailID";
$query = $this->db_onedev->query($sql);
if ($query) {
@@ -278,19 +117,19 @@ class Bill extends MY_Controller
if($rows){
}
return $rows;
} else {
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function search_old()
public function search()
{
//# cek token valid
if (! $this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
$prm = $this->sys_input;
$supplier = $prm["supplier"];
$search = $prm["search"];
@@ -300,42 +139,32 @@ class Bill extends MY_Controller
$regionalid = $this->sys_user['S_RegionalID'];
$number_limit = 10;
$number_offset = ((int)$prm['current_page'] - 1) * $number_limit;
$number_offset = ($prm['current_page'] - 1) * $number_limit ;
// ── WHERE ────────────────────────────────────────────────
$where = " si.SupplierInvoiceIsActive = 'Y'
AND sp.SupplierPaymentIsApproved = 'Y'
AND sp.SupplierPaymentIsVerif = 'Y'
AND sp.SupplierPaymentIsConfirm = ?
AND (si.SupplierInvoiceNumber LIKE ?
OR si.SupplierInvoiceSupplierInvoiceNumber LIKE ?)
AND sup.SupplierName LIKE ?
AND rop.ReceiveOrderPoS_RegionalID = ?
AND si.SupplierInvoiceDraftPaymentDate BETWEEN ? AND ? ";
$where = "SupplierInvoiceIsActive = 'Y'
AND SupplierPaymentIsApproved = 'Y'
AND SupplierPaymentIsVerif = 'Y'
AND SupplierPaymentIsConfirm = '{$status}'
AND (SupplierInvoiceNumber LIKE '%{$search}%' OR SupplierInvoiceSupplierInvoiceNumber LIKE '%{$search}%')
AND SupplierName LIKE '%{$supplier}%'
AND ReceiveOrderPoS_RegionalID = {$regionalid}
AND SupplierInvoiceDraftPaymentDate BETWEEN '{$startdate}' AND '{$enddate}'";
$where_params = [
$status,
'%' . $search . '%',
'%' . $search . '%',
'%' . $supplier . '%',
$regionalid,
$startdate,
$enddate,
];
// ── COUNT ────────────────────────────────────────────────
$sql_count = "
SELECT count(*) as total
FROM supplier_invoice si
JOIN jurnal_addon ja ON ja.jurnalAddOnValue = si.SupplierInvoiceNumber
JOIN receive_order_po rop ON rop.ReceiveOrderPoID = si.SupplierInvoiceReceiveOrderPoID
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInvoiceID = si.SupplierInvoiceID
AND sp.SupplierPaymentIsActive = 'Y'
LEFT JOIN supplier sup ON sup.SupplierID = si.SupplierInvoiceSupplierID
WHERE $where";
$query = $this->db_onedev->query($sql_count, $where_params);
$sql = " SELECT count(*) as total
FROM supplier_invoice
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
LEFT JOIN supplier_payment ON SupplierInvoiceID = SupplierPaymentSupplierInvoiceID AND SupplierPaymentIsActive = 'Y'
LEFT JOIN supplier ON SupplierInvoiceSupplierID = SupplierID
JOIN receive_order_po ON SupplierInvoiceReceiveOrderPoID = ReceiveOrderPoID
WHERE
$where
";
// echo $sql;
$query = $this->db_onedev->query($sql, $sql_param);
$tot_count = 0;
$tot_page = 0;
@@ -347,457 +176,80 @@ class Bill extends MY_Controller
exit;
}
// ── DATA — only columns the frontend actually reads ──────
$sql_data = "
SELECT
si.SupplierInvoiceID,
si.SupplierInvoiceNumber,
si.SupplierInvoiceDraftPaymentDate,
si.SupplierInvoiceIsLunas AS flaglunas,
si.SupplierInvoiceSupplierInvoiceNumber,
DATE_FORMAT(
IFNULL(si.SupplierInvoiceSupplierInvoiceDate,''),'%d-%m-%Y'
) AS tanggalinvoice,
sup.SupplierName,
sp.SupplierPaymentID,
sp.SupplierPaymentIsConfirm,
IFNULL(sp.SupplierPaymentCashierNumber,'') AS SupplierPaymentCashierNumber,
DATE_FORMAT(
si.SupplierInvoiceDraftPaymentDate,'%d-%m-%Y'
) AS tanggalbayar,
0 AS totalbill,
0 AS paid,
0 AS unpaid,
'' AS SupplierPaymentNumber,
0 AS SupplierPaymentAmount,
'' AS SupplierPaymentDate,
'' AS notes,
'' AS tagihans,
0 AS xrounding,
'' AS chex
FROM supplier_invoice si
JOIN jurnal_addon ja ON ja.jurnalAddOnValue = si.SupplierInvoiceNumber
JOIN receive_order_po rop ON rop.ReceiveOrderPoID = si.SupplierInvoiceReceiveOrderPoID
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInvoiceID = si.SupplierInvoiceID
AND sp.SupplierPaymentIsActive = 'Y'
LEFT JOIN supplier sup ON sup.SupplierID = si.SupplierInvoiceSupplierID
WHERE $where
GROUP BY si.SupplierInvoiceID
ORDER BY si.SupplierInvoiceID ASC
LIMIT ? OFFSET ?";
$data_params = array_merge($where_params, [$number_limit, $number_offset]);
$query = $this->db_onedev->query($sql_data, $data_params);
$rows = $query->result_array();
$sql = "SELECT supplier_invoice.*,
SupplierName,
'' M_MouName,
0 as totalbill,
0 as paid,
0 as unpaid,
SupplierInvoiceIsLunas as flaglunas,
'' as SupplierPaymentNumber,
0 as SupplierPaymentAmount,
'' as SupplierPaymentDate,
'' as SupplierInvoiceIssueRefNumber,
'' as notes,
'' as tagihans,
'N' as isbillterpusat,
DATE_FORMAT(SupplierInvoiceDraftPaymentDate,'%d-%m-%Y') as tanggalbayar,
DATE_FORMAT(SupplierInvoiceDraftPaymentDate,'%d%m%Y') as tanggalbayartext,
IF(SupplierPaymentID IS NULL,'N','Y') as status_invoice,
SupplierPaymentID,
SupplierPaymentIsConfirm,
DATE_FORMAT(IFNULL(SupplierInvoiceSupplierInvoiceDate,''),'%d-%m-%Y') as tanggalinvoice,
0 xrounding,
'' chex,
IFNULL(SupplierPaymentCashierNumber,'') SupplierPaymentCashierNumber
// ── ENRICH ───────────────────────────────────────────────
if ($rows) {
foreach ($rows as $k => $v) {
$rows[$k]['chex'] = false;
$inv_id = $v['SupplierInvoiceID'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierInvoiceID = ?",
[$inv_id]
)->row();
$s_jurnal = $this->db_onedev->query("
SELECT SUM(jurnalTxCredit) totalbill
FROM supplier_invoice si
JOIN jurnal_addon ja ON ja.jurnalAddOnValue = si.SupplierInvoiceNumber
JOIN jurnal_tx jt
ON jt.jurnalTxJurnalID = ja.jurnalAddOnJurnalID
AND jt.jurnalTxCredit <> 0
AND jt.jurnalTxCoaID <> 563
WHERE si.SupplierInvoiceID = ?
GROUP BY si.SupplierInvoiceID",
[$inv_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$unpaid = (float)$s_jurnal->totalbill - (float)$amount;
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber;
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['totalbill'] = $s_jurnal->totalbill
? $s_jurnal->totalbill
: "0.00";
$rows[$k]['unpaid'] = number_format($unpaid, 2, '.', '');
$rows[$k]['notes'] = $this->add_notes($inv_id);
$rows[$k]['tagihans'] = $this->add_tagihans($inv_id);
}
}
$result = array("total" => $tot_page, "records" => $rows);
$this->sys_ok($result);
exit;
}
public function search()
{
if (! $this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
$prm = $this->sys_input;
$supplier = $prm["supplier"];
$search = $prm["search"];
$status = $prm["status"];
$startdate = $prm["startdate"];
$enddate = $prm["enddate"];
$regionalid = $this->sys_user['S_RegionalID'];
$number_limit = 10;
$number_offset = ((int)$prm['current_page'] - 1) * $number_limit;
// ── INVOICE branch WHERE ─────────────────────────────────
$inv_where = " si.SupplierInvoiceIsActive = 'Y'
AND rop.ReceiveOrderPoS_RegionalID = ? ";
$inv_params = [$regionalid];
// ── DOWNPAYMENT branch WHERE ─────────────────────────────
$dp_where = " dp.SupplierDownpaymentIsActive = 'Y'
AND po.PurchaseOrderS_RegionalID = ? ";
$dp_params = [$regionalid];
// ── INSTALLMENT branch WHERE ────────────────────────────
$inst_where = " inst.SupplierInstallmentIsActive = 'Y'
AND rop.ReceiveOrderPoS_RegionalID = ? ";
$inst_params = [$regionalid];
// ── UNION base ───────────────────────────────────────────
$sql_base = "
SELECT
si.SupplierInvoiceID,
0 AS DownpaymentID,
0 AS InstallmentID,
0 AS parent_invoice_id,
si.SupplierInvoiceNumber,
si.SupplierInvoiceDraftPaymentDate,
si.SupplierInvoiceDraftPaymentDate AS filter_date,
si.SupplierInvoiceIsLunas AS flaglunas,
si.SupplierInvoiceSupplierInvoiceNumber,
DATE_FORMAT(
IFNULL(si.SupplierInvoiceSupplierInvoiceDate,''),'%d-%m-%Y'
) AS tanggalinvoice,
sup.SupplierName,
sp.SupplierPaymentID,
sp.SupplierPaymentIsConfirm,
sp.SupplierPaymentIsApproved,
sp.SupplierPaymentIsVerif,
sp.SupplierPaymentIsActive,
IFNULL(sp.SupplierPaymentCashierNumber,'') AS SupplierPaymentCashierNumber,
DATE_FORMAT(
si.SupplierInvoiceDraftPaymentDate,'%d-%m-%Y'
) AS tanggalbayar,
0 AS totalbill,
0 AS paid,
0 AS unpaid,
'' AS SupplierPaymentNumber,
0 AS SupplierPaymentAmount,
'' AS SupplierPaymentDate,
'' AS notes,
'' AS tagihans,
0 AS xrounding,
'' AS chex,
'INVOICE' AS type
FROM supplier_invoice si
JOIN jurnal_addon ja ON ja.jurnalAddOnValue = si.SupplierInvoiceNumber
JOIN receive_order_po rop ON rop.ReceiveOrderPoID = si.SupplierInvoiceReceiveOrderPoID
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInvoiceID = si.SupplierInvoiceID
AND sp.SupplierPaymentIsActive = 'Y'
LEFT JOIN supplier sup ON sup.SupplierID = si.SupplierInvoiceSupplierID
WHERE $inv_where
GROUP BY si.SupplierInvoiceID
UNION ALL
SELECT
0 AS SupplierInvoiceID,
dp.SupplierDownpaymentID AS DownpaymentID,
0 AS InstallmentID,
0 AS parent_invoice_id,
CONCAT('DP-', po.PurchaseOrderNumber) AS SupplierInvoiceNumber,
dp.SupplierDownpaymentDueDate AS SupplierInvoiceDraftPaymentDate,
dp.SupplierDownpaymentDueDate AS filter_date,
dp.SupplierDownpaymentIsLunas AS flaglunas,
'' AS SupplierInvoiceSupplierInvoiceNumber,
DATE_FORMAT(dp.SupplierDownpaymentDate,'%d-%m-%Y') AS tanggalinvoice,
dp_sup.SupplierName AS SupplierName,
sp.SupplierPaymentID,
sp.SupplierPaymentIsConfirm,
sp.SupplierPaymentIsApproved,
sp.SupplierPaymentIsVerif,
sp.SupplierPaymentIsActive,
IFNULL(sp.SupplierPaymentCashierNumber,'') AS SupplierPaymentCashierNumber,
DATE_FORMAT(
dp.SupplierDownpaymentDueDate,'%d-%m-%Y'
) AS tanggalbayar,
dp.SupplierDownpaymentAmount AS totalbill,
CASE WHEN dp.SupplierDownpaymentStatus = 'Paid'
THEN dp.SupplierDownpaymentAmount
ELSE 0 END AS paid,
CASE WHEN dp.SupplierDownpaymentStatus = 'Paid'
THEN 0
ELSE dp.SupplierDownpaymentAmount END AS unpaid,
'' AS SupplierPaymentNumber,
0 AS SupplierPaymentAmount,
'' AS SupplierPaymentDate,
'' AS notes,
'' AS tagihans,
0 AS xrounding,
'' AS chex,
'DP' AS type
FROM supplier_payment sp
JOIN supplier_downpayment dp
ON dp.SupplierDownpaymentID = sp.SupplierPaymentSupplierDownpaymentID
JOIN supplier dp_sup
ON dp_sup.SupplierID = dp.SupplierDownpaymentSupplierID
JOIN purchase_order po
ON po.PurchaseOrderID = dp.SupplierDownpaymentPurchasOrderID
WHERE $dp_where
UNION ALL
SELECT
0 AS SupplierInvoiceID,
0 AS DownpaymentID,
inst.SupplierInstallmentID AS InstallmentID,
si_inst.SupplierInvoiceID AS parent_invoice_id,
CONCAT('INST-', si_inst.SupplierInvoiceNumber) AS SupplierInvoiceNumber,
inst.SupplierInstallmentDueDate AS SupplierInvoiceDraftPaymentDate,
inst.SupplierInstallmentDueDate AS filter_date,
inst.SupplierInstallmentIsLunas AS flaglunas,
si_inst.SupplierInvoiceSupplierInvoiceNumber AS SupplierInvoiceSupplierInvoiceNumber,
DATE_FORMAT(
IFNULL(si_inst.SupplierInvoiceSupplierInvoiceDate,''),'%d-%m-%Y'
) AS tanggalinvoice,
inst_sup.SupplierName AS SupplierName,
IFNULL(sp.SupplierPaymentID,0) AS SupplierPaymentID,
IFNULL(sp.SupplierPaymentIsConfirm,'N') AS SupplierPaymentIsConfirm,
IFNULL(sp.SupplierPaymentIsApproved,'N') AS SupplierPaymentIsApproved,
IFNULL(sp.SupplierPaymentIsVerif,'N') AS SupplierPaymentIsVerif,
IFNULL(sp.SupplierPaymentIsActive,'Y') AS SupplierPaymentIsActive,
IFNULL(sp.SupplierPaymentCashierNumber,'') AS SupplierPaymentCashierNumber,
DATE_FORMAT(
inst.SupplierInstallmentDueDate,'%d-%m-%Y'
) AS tanggalbayar,
inst.SupplierInstallmentAmount AS totalbill,
CASE WHEN inst.SupplierInstallmentStatus = 'Paid'
THEN inst.SupplierInstallmentAmount
ELSE 0 END AS paid,
CASE WHEN inst.SupplierInstallmentStatus = 'Paid'
THEN 0
ELSE inst.SupplierInstallmentAmount END AS unpaid,
'' AS SupplierPaymentNumber,
0 AS SupplierPaymentAmount,
'' AS SupplierPaymentDate,
'' AS notes,
'' AS tagihans,
0 AS xrounding,
'' AS chex,
'INSTALLMENT' AS type
FROM supplier_installment inst
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInstallmentID = inst.SupplierInstallmentID
AND sp.SupplierPaymentIsActive = 'Y'
JOIN supplier_invoice si_inst
ON si_inst.SupplierInvoiceID = inst.SupplierInstallmentSupplierInvoiceID
JOIN receive_order_po rop
ON rop.ReceiveOrderPoID = si_inst.SupplierInvoiceReceiveOrderPoID
JOIN supplier inst_sup
ON inst_sup.SupplierID = inst.SupplierInstallmentSupplierID
WHERE $inst_where";
// ── Outer common filters ─────────────────────────────────
$outer_where = " SupplierPaymentIsActive = 'Y'
AND SupplierPaymentIsApproved = 'Y'
AND SupplierPaymentIsVerif = 'Y'
AND SupplierPaymentIsConfirm = ?
AND (SupplierInvoiceNumber LIKE ? OR SupplierInvoiceSupplierInvoiceNumber LIKE ?)
AND SupplierName LIKE ?
AND filter_date BETWEEN ? AND ? ";
$outer_params = [
$status,
'%' . $search . '%',
'%' . $search . '%',
'%' . $supplier . '%',
$startdate,
$enddate,
];
// ── COUNT ────────────────────────────────────────────────
$sql_count = "
SELECT COUNT(*) AS total
FROM ($sql_base) AS combined
WHERE $outer_where";
$count_params = array_merge($inv_params, $dp_params, $inst_params, $outer_params);
$query = $this->db_onedev->query($sql_count, $count_params);
$tot_count = 0;
$tot_page = 0;
if ($query) {
$tot_count = $query->result_array()[0]["total"];
$tot_page = ceil($tot_count / $number_limit);
} else {
$this->sys_error_db("supplier payment count", $this->db_onedev);
exit;
}
// ── DATA ─────────────────────────────────────────────────
$sql_data = "
SELECT * FROM ($sql_base) AS combined
WHERE $outer_where
FROM supplier_invoice
LEFT JOIN supplier ON SupplierInvoiceSupplierID = SupplierID
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN receive_order_po ON SupplierInvoiceReceiveOrderPoID = ReceiveOrderPoID
LEFT JOIN supplier_payment ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID AND SupplierPaymentIsActive = 'Y'
WHERE
$where
GROUP BY SupplierInvoiceID
ORDER BY SupplierInvoiceID ASC
LIMIT ? OFFSET ?";
$data_params = array_merge($inv_params, $dp_params, $inst_params, $outer_params, [$number_limit, $number_offset]);
$query = $this->db_onedev->query($sql_data, $data_params);
limit $number_limit offset $number_offset";
//echo $sql;
$query = $this->db_onedev->query($sql, $sql_param);
$rows = $query->result_array();
// ── ENRICH ───────────────────────────────────────────────
if($rows){
foreach($rows as $k => $v){
// ── Downpayment branch ──────────────────────────
if ($v['type'] === 'DP') {
$rows[$k]['chex'] = false;
$dp_id = $v['DownpaymentID'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
$s_payment = $this->db_onedev->query("SELECT GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
GROUP_CONCAT(DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y') SEPARATOR ', ') as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierDownpaymentID = ?",
[$dp_id]
)->row();
WHERE SupplierPaymentIsActive = 'Y' AND SupplierPaymentSupplierInvoiceID = {$v['SupplierInvoiceID']}")->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber;
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['notes'] = $this->add_notes_downpayment($dp_id);
$rows[$k]['tagihans'] = [];
continue;
}
// ── Installment branch ─────────────────────────
if ($v['type'] === 'INSTALLMENT') {
$rows[$k]['chex'] = false;
$inst_id = $v['InstallmentID'];
$parent_invoice_id = $v['parent_invoice_id'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierInstallmentID = ?",
[$inst_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber;
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['notes'] = $this->add_notes_installment($inst_id);
$rows[$k]['tagihans'] = $this->add_tagihans($parent_invoice_id);
continue;
}
// ── Invoice branch ──────────────────────────────
$rows[$k]['chex'] = false;
$inv_id = $v['SupplierInvoiceID'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierInvoiceID = ?",
[$inv_id]
)->row();
$s_jurnal = $this->db_onedev->query("
SELECT SUM(jurnalTxCredit) totalbill
FROM supplier_invoice si
JOIN jurnal_addon ja ON ja.jurnalAddOnValue = si.SupplierInvoiceNumber
JOIN jurnal_tx jt
ON jt.jurnalTxJurnalID = ja.jurnalAddOnJurnalID
AND jt.jurnalTxCredit <> 0
AND jt.jurnalTxCoaID <> 563
WHERE si.SupplierInvoiceID = ?
GROUP BY si.SupplierInvoiceID",
[$inv_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$s_jurnal = $this->db_onedev->query("SELECT SUM(jurnalTxCredit) totalbill
FROM supplier_invoice
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN jurnal_tx ON jurnalTxJurnalID = jurnalAddOnJurnalID AND jurnalTxCredit <> 0 AND jurnalTxCoaID <> 563
WHERE SupplierInvoiceID = {$v['SupplierInvoiceID']}
GROUP BY SupplierInvoiceID")->row();
$amount = $s_payment->SupplierPaymentAmount ? $s_payment->SupplierPaymentAmount : "0.00";
$unpaid = (float)$s_jurnal->totalbill - (float)$amount;
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber;
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['totalbill'] = $s_jurnal->totalbill
? $s_jurnal->totalbill
: "0.00";
$rows[$k]['totalbill'] = $s_jurnal->totalbill ? $s_jurnal->totalbill : "0.00";
$rows[$k]['unpaid'] = number_format($unpaid, 2, '.', '');
$rows[$k]['notes'] = $this->add_notes($inv_id);
$rows[$k]['tagihans'] = $this->add_tagihans($inv_id);
$rows[$k]['notes'] = $this->add_notes($v['SupplierInvoiceID']);
$rows[$k]['tagihans'] = $this->add_tagihans($v['SupplierInvoiceID']);
}
}
$result = array("total" => $tot_page, "records" => $rows);
$result = array("total" => $tot_page, "records" => $rows, "sql"=> $this->db_onedev->last_query());
$this->sys_ok($result);
exit;
}
}

View File

@@ -3,10 +3,9 @@
class PaymentV2 extends MY_Controller
{
var $db;
public function index()
{
echo 'API';
echo "API";
}
public function __construct()
@@ -18,8 +17,8 @@ class PaymentV2 extends MY_Controller
{
try {
if (!$this->isLogin) {
$this->sys_error('invalid token');
exit();
$this->sys_error("invalid token");
exit;
}
$this->db->trans_begin();
@@ -27,15 +26,15 @@ class PaymentV2 extends MY_Controller
$param = $this->sys_input;
$users = $this->sys_user;
// Generate number supplier payment #
$sql_gennumber = 'SELECT `fn_numbering`(?) as numberx';
# Generate number supplier payment #
$sql_gennumber = "SELECT `fn_numbering`(?) as numberx";
$paygroupnum = $this->db->query($sql_gennumber, ['PC'])->row()->numberx;
$paynumber = $this->db->query($sql_gennumber, ['PN'])->row()->numberx;
if ($paygroupnum == '' || $paynumber == '') {
$this->db->trans_rollback();
$this->sys_error_db('[Error] generate number supplier payment');
exit();
$this->sys_error_db("[Error] generate number supplier payment");
exit;
}
$rounding = 0;
@@ -44,7 +43,7 @@ class PaymentV2 extends MY_Controller
$rounding = round($sisa, 2);
}
// UPDATE header supplier payment #
# UPDATE header supplier payment #
$sql_updateheader = "UPDATE supplier_payment SET
SupplierPaymentIsConfirm = 'Y',
SupplierPaymentConfirmUserID = ?,
@@ -62,259 +61,67 @@ class PaymentV2 extends MY_Controller
$rounding,
$param['keterangan'],
$param['paymenttype'],
$param['orderid'],
$param['orderid']
]);
if (!$que_updateheader) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update supplier payment header');
exit();
$this->sys_error_db("[Error] update supplier payment header");
exit;
}
// UPDATE status lunas supplier invoice #
switch ($param['type']) {
case 'DP':
$sql_updatedp = "UPDATE supplier_downpayment
SET SupplierDownpaymentIsLunas = 'Y'
WHERE SupplierDownpaymentID = ?";
$que_updatedp = $this->db->query($sql_updatedp, [
abs($param['DownpaymentID']),
]);
if (!$que_updatedp) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update status lunas downpayment');
exit();
}
break;
case 'INSTALLMENT':
$sql_updinstlment = "UPDATE supplier_installment
SET SupplierInstallmentIsLunas = 'Y'
WHERE SupplierInstallmentID = ?";
$que_updinstlment = $this->db->query($sql_updinstlment, [
$param['InstallmentID'],
]);
if (!$que_updinstlment) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update status lunas installment');
exit();
}
$sql_updKontrak = "UPDATE purchase_order_asset_contract c
JOIN supplier_installment inst
ON inst.SupplierInstallmentPurchaseOrderID = c.PurchaseOrderAssetContractPurchaseOrderID
SET
c.PurchaseOrderAssetContractInstallmentPaid = c.PurchaseOrderAssetContractInstallmentPaid + 1,
c.PurchaseOrderAssetContractStatus = IF(
c.PurchaseOrderAssetContractInstallmentPaid + 1 >= c.PurchaseOrderAssetContractInstallmentNumber,
'lunas',
'belum lunas'
),
c.PurchaseOrderAssetContractLastUpdated = NOW()
WHERE inst.SupplierInstallmentID = ?
AND inst.SupplierInstallmentIsActive = 'Y'
AND c.PurchaseOrderAssetContractIsActive = 'Y'";
$que_updKontrak = $this->db->query($sql_updKontrak, [
$param['InstallmentID'],
]);
if (!$que_updKontrak) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update kontrak cicilan');
exit();
}
break;
default:
# UPDATE status lunas supplier invoice #
$sql_suppinvoice = "UPDATE supplier_invoice
SET SupplierInvoiceIsLunas = 'Y'
WHERE SupplierInvoiceID = ?";
$que_suppinvoice = $this->db->query($sql_suppinvoice, [
$param['SupplierInvoiceID'],
]);
$que_suppinvoice = $this->db->query($sql_suppinvoice, [$param['SupplierInvoiceID']]);
if (!$que_suppinvoice) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update status lunas invoice');
exit();
}
break;
$this->sys_error_db("[Error] update status lunas invoice");
exit;
}
// GET Latest data supplier payment for logging #
$sql_suppayment = 'SELECT * FROM supplier_payment WHERE SupplierPaymentID = ?';
# GET Latest data supplier payment #
$sql_suppayment = "SELECT * FROM supplier_payment WHERE SupplierPaymentID = ?";
$que_suppayment = $this->db->query($sql_suppayment, [$param['orderid']]);
if (!$que_suppayment) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier payment');
exit();
$this->sys_error_db("[Error] get data latest supplier payment");
exit;
}
$suppayment_header = $que_suppayment->row_array();
$suppayment_detail = [];
switch ($param['type']) {
case 'DP':
$sql_suppaymentdetail = "SELECT supplier_downpayment.*
FROM supplier_payment
JOIN supplier_downpayment
ON SupplierPaymentSupplierDownpaymentID = SupplierDownpaymentID
AND SupplierDownpaymentIsActive = 'Y'
WHERE SupplierPaymentID = ?";
$sql_suppaymentdetail = "SELECT * FROM supplier_payment_detail
WHERE SupplierPaymentDetailSupplierPaymentID = ?";
$que_suppaymentdetail = $this->db->query($sql_suppaymentdetail, [$param['orderid']]);
if (!$que_suppaymentdetail) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier downpayment');
exit();
$this->sys_error_db("[Error] get data latest supplier payment detail");
exit;
}
$suppayment_detail = $que_suppaymentdetail->result_array();
break;
case 'INSTALLMENT':
$sql_suppaymentdetail = "SELECT
supplier_installment.*,
PurchaseOrderAssetContractInstallmentNumber AS qtyPay,
PurchaseOrderAssetContractInstallmentPaid AS qtyPaid
FROM supplier_payment
JOIN supplier_installment
ON SupplierInstallmentID = SupplierPaymentSupplierInstallmentID
AND SupplierInstallmentIsActive = 'Y'
JOIN purchase_order_asset_contract
ON PurchaseOrderAssetContractPurchaseOrderID = SupplierInstallmentPurchaseOrderID
AND PurchaseOrderAssetContractIsActive = 'Y'
WHERE SupplierPaymentID = ?";
$que_suppaymentdetail = $this->db->query($sql_suppaymentdetail, [$param['orderid']]);
if (!$que_suppaymentdetail) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier installment');
exit();
}
$suppayment_detail = $que_suppaymentdetail->result_array();
break;
default:
$sql_suppaymentdetail = 'SELECT * FROM supplier_payment_detail
WHERE SupplierPaymentDetailSupplierPaymentID = ?';
$que_suppaymentdetail = $this->db->query($sql_suppaymentdetail, [$param['orderid']]);
if (!$que_suppaymentdetail) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier payment detail');
exit();
}
$suppayment_detail = $que_suppaymentdetail->result_array();
break;
}
$data_log = [
'header' => $suppayment_header,
'detail' => $suppayment_detail,
"header" => $suppayment_header,
"detail" => $suppayment_detail
];
// LOG activity confirm payment #
# LOG activity confirm payment #
$messg = "Pembayaran Faktur No: {$suppayment_header['SupplierPaymentNumber']}";
$messg .= " telah dikonfirmasi oleh {$users['M_UserUsername']} dengan nomor";
$messg .= " pembayaran kasir: {$suppayment_header['SupplierPaymentCashierNumber']}";
$this->insert_activity_log('PF', 'CONFIRM', $messg, $param['orderid'], $data_log, $users['M_UserID']);
$this->insert_activity_log(
"PF",
"CONFIRM",
$messg,
$param['orderid'],
$data_log,
$users['M_UserID']
);
// INSERT JURNAL #
# INSERT JURNAL #
$detail_transac = [];
switch ($param['type']) {
case 'DP':
$sql_coaDP = "SELECT
coaID,
coaDescription,
SupplierDownpaymentAmount,
M_ItemID,
SupplierPaymentNumber,
M_ItemID
FROM supplier_payment
JOIN supplier_downpayment
ON SupplierDownpaymentID = SupplierPaymentSupplierDownpaymentID
AND SupplierDownpaymentIsActive = 'Y'
JOIN purchase_order_detail
ON SupplierDownpaymentPurchasOrderID = PurchaseOrderDetailPurchaseOrderID
AND PurchaseOrderDetailIsActive = 'Y'
JOIN m_item
ON M_ItemID = PurchaseOrderDetailItemID
AND M_ItemIsActive = 'Y'
JOIN fa_class
ON Fa_ClassID = M_ItemFa_ClassID
AND M_ItemIsActive = 'Y'
JOIN coa
ON coaID = Fa_ClassDownPaymentCoaID
AND coaIsActive = 'Y'
WHERE SupplierPaymentID = ?";
$que_coaDP = $this->db->query($sql_coaDP, [
$param['orderid'],
]);
if (!$que_coaDP) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data hutang item dp');
exit();
}
$data_dp = $que_coaDP->result_array();
if (empty($data_dp)) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] no COA found for uang muka item');
exit();
}
foreach ($data_dp as $key => $dp) {
$detail_transac[] = [
'coaID' => $dp['coaID'],
'coaDescription' => $dp['coaDescription'],
'debit' => $dp['SupplierDownpaymentAmount'],
'credit' => 0,
'addoncode' => 'DP-JFA',
'addonvalue' => $dp['SupplierPaymentNumber'],
'addonitemid' => $dp['M_ItemID'],
];
}
break;
case 'INSTALLMENT':
$sql_datahutang = "SELECT
coaID,
coaDescription,
SupplierInstallmentAmount,
M_ItemID,
SupplierPaymentNumber,
M_ItemID
FROM supplier_payment
JOIN supplier_installment
ON SupplierInstallmentID = SupplierPaymentSupplierInstallmentID
AND SupplierInstallmentIsActive = 'Y'
JOIN purchase_order_detail
ON SupplierInstallmentPurchaseOrderID = PurchaseOrderDetailPurchaseOrderID
AND PurchaseOrderDetailIsActive = 'Y'
JOIN m_item
ON M_ItemID = PurchaseOrderDetailItemID
AND M_ItemIsActive = 'Y'
JOIN fa_class
ON Fa_ClassID = M_ItemFa_ClassID
AND M_ItemIsActive = 'Y'
JOIN coa
ON coaID = Fa_ClassHutangCoaID
AND coaIsActive = 'Y'
WHERE SupplierPaymentID = ?";
$que_datahutang = $this->db->query($sql_datahutang, [
$param['orderid'],
]);
if (!$que_datahutang) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data hutang item cicilan');
exit();
}
$data_cicil = $que_datahutang->result_array();
if (empty($data_cicil)) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] no COA found for installment item');
exit();
}
foreach ($data_cicil as $key => $cicil) {
$detail_transac[] = [
'coaID' => $cicil['coaID'],
'coaDescription' => $cicil['coaDescription'],
'debit' => $cicil['SupplierInstallmentAmount'],
'credit' => 0,
'addoncode' => 'INST-JFA',
'addonvalue' => $cicil['SupplierPaymentNumber'],
'addonitemid' => $cicil['M_ItemID'],
];
}
break;
default:
// GET data hutang #
# GET data hutang #
$sql_datahutang = "SELECT
SupplierPaymentNumber AS addonvalue,
SupplierPaymentDetailID,
@@ -323,43 +130,32 @@ class PaymentV2 extends MY_Controller
SupplierPaymentDetailAmount,
SupplierInvoiceDetailItemID
FROM supplier_payment
JOIN supplier_payment_detail
ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
JOIN supplier_invoice_detail
ON SupplierInvoiceDetailSupplierInvoiceID = SupplierPaymentSupplierInvoiceID
JOIN supplier_payment_detail ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
JOIN supplier_invoice_detail ON SupplierInvoiceDetailSupplierInvoiceID = SupplierPaymentSupplierInvoiceID
AND SupplierInvoiceDetailIsActive = 'Y'
JOIN jurnal_tx
ON SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID
AND jurnalTxIsActive = 'Y'
JOIN jurnal_tx ON SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID
WHERE SupplierPaymentID = ?
AND SupplierPaymentDetailIsActive = 'Y'";
$que_datahutang = $this->db->query($sql_datahutang, [$param['orderid']]);
if (!$que_datahutang) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data hutang item');
exit();
$this->sys_error_db("[Error] get data hutang item");
exit;
}
$data_hutang = $que_datahutang->result_array();
if (empty($data_hutang)) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] no COA found for hutang item');
exit();
}
foreach ($data_hutang as $key => $debt) {
$detail_transac[] = [
'coaID' => $debt['coaID'],
'coaDescription' => $debt['coaDescription'],
'debit' => $debt['SupplierPaymentDetailAmount'],
'credit' => 0,
'addoncode' => 'JFA',
'addonvalue' => $debt['addonvalue'],
'addonitemid' => $debt['SupplierInvoiceDetailItemID'],
"coaID" => $debt['coaID'],
"coaDescription" => $debt['coaDescription'],
"debit" => $debt['SupplierPaymentDetailAmount'],
"credit" => 0,
"addoncode" => "JFA",
"addonvalue" => $debt['addonvalue'],
"addonitemid" => $debt['SupplierInvoiceDetailItemID']
];
}
break;
}
// GET data bayar #
# GET data bayar #
$sql_databayar = "SELECT
CONCAT('Jurnal Payment Invoice Nomor : ',
SupplierPaymentNumber, DATE_FORMAT(now(),
@@ -383,82 +179,68 @@ class PaymentV2 extends MY_Controller
$que_databayar = $this->db->query($sql_databayar, [$param['orderid']]);
if (!$que_databayar) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data bayar');
exit();
$this->sys_error_db("[Error] get data bayar");
exit;
}
$data_bayar = $que_databayar->row_array();
$detail_transac[] = [
'coaID' => $data_bayar['coaID'],
'coaDescription' => $data_bayar['coaDescription'],
'debit' => 0,
'credit' => abs($data_bayar['bayar']),
'addoncode' => 'PAYINVSG',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $data_bayar['coaID'],
"coaDescription" => $data_bayar['coaDescription'],
"debit" => 0,
"credit" => abs($data_bayar['bayar']),
"addoncode" => "PAYINVSG",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
// rugi / untung dari rounding #
# rugi / untung dari rounding #
$sql_lossprofit = "SELECT coaID, coaDescription
FROM coa WHERE coaAccountNo = ? AND coaIsActive = 'Y'";
$data_round = doubleval($data_bayar['rounding']);
if ($data_round > 0) {
// loss #
# loss #
$que_lossprofit = $this->db->query($sql_lossprofit, ['6120402001']);
if (!$que_lossprofit) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data coa loss sisa kas');
exit();
$this->sys_error_db("[Error] get data coa loss sisa kas");
exit;
}
$loss_coa = $que_lossprofit->row_array();
$detail_transac[] = [
'coaID' => $loss_coa['coaID'],
'coaDescription' => $loss_coa['coaDescription'],
'debit' => abs($data_round),
'credit' => 0,
'addoncode' => 'PAYINVSG',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $loss_coa['coaID'],
"coaDescription" => $loss_coa['coaDescription'],
"debit" => abs($data_round),
"credit" => 0,
"addoncode" => "PAYINVSG",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
} elseif ($data_round < 0) {
// profit #
# profit #
$que_lossprofit = $this->db->query($sql_lossprofit, ['6110500001']);
if (!$que_lossprofit) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data coa profit sisa kas');
exit();
$this->sys_error_db("[Error] get data coa profit sisa kas");
exit;
}
$profit_coa = $que_lossprofit->row_array();
$detail_transac[] = [
'coaID' => $profit_coa['coaID'],
'coaDescription' => $profit_coa['coaDescription'],
'debit' => 0,
'credit' => abs($data_round),
'addoncode' => 'PAYINVSG',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $profit_coa['coaID'],
"coaDescription" => $profit_coa['coaDescription'],
"debit" => 0,
"credit" => abs($data_round),
"addoncode" => "PAYINVSG",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
}
switch ($param['type']) {
case 'DP':
$data_bayar['jurnaltitle'] .= " | Pembayaran Uang Muka Aset - {$param['dpkode']}";
break;
case 'INSTALLMENT':
$cicil = $suppayment_detail[0];
$data_bayar['jurnaltitle'] .= " | Pembayaran Cicilan Aset - {$param['installmentkode']}";
$data_bayar['jurnaldesc'] .= " | Jurnal cicilan ke-{$cicil['qtyPaid']} dari {$cicil['qtyPay']}";
break;
default:
// code
break;
}
$this->insertJurnal($users, $data_bayar, $detail_transac);
$this->db->trans_commit();
$this->sys_ok('success update v2 test');
$this->sys_ok("success update v2 test");
} catch (Exception $exc) {
$message = $exc->getMessage();
$this->sys_error($message);
@@ -469,22 +251,22 @@ class PaymentV2 extends MY_Controller
{
try {
if (!$this->isLogin) {
$this->sys_error('Invalid Token');
exit();
$this->sys_error("Invalid Token");
exit;
}
$this->db->trans_begin();
$param = $this->sys_input;
$users = $this->sys_user;
// Generate number supplier payment #
$sql_gennumber = 'SELECT `fn_numbering`(?) as numberx';
# Generate number supplier payment #
$sql_gennumber = "SELECT `fn_numbering`(?) as numberx";
$paygroupnum = $this->db->query($sql_gennumber, ['PC'])->row()->numberx;
if ($paygroupnum == '') {
$this->db->trans_rollback();
$this->sys_error_db('[Error] generate number group supplier payment');
exit();
$this->sys_error_db("[Error] generate number group supplier payment");
exit;
}
$rounding = 0;
@@ -493,26 +275,27 @@ class PaymentV2 extends MY_Controller
$rounding = round($sisa, 2);
}
$lastIndex = count($param['details']) - 1;
// LOOP details invoice payment #
# LOOP details invoice payment #
foreach ($param['details'] as $idx => $obj) {
$paynumber = $this->db->query($sql_gennumber, ['PN'])->row()->numberx;
if ($paynumber == '') {
$this->db->trans_rollback();
$this->sys_error_db('[Error] generate number kasir supplier payment');
exit();
$this->sys_error_db("[Error] generate number kasir supplier payment");
exit;
}
$SupplierPaymentID = $obj['SupplierPaymentID'];
$SupplierInvoiceID = $obj['SupplierInvoiceID'];
// CEK index invoice terakhir #
# CEK index invoice terakhir #
$roundvalue = 0;
if ($idx == $lastIndex) {
$roundvalue = $rounding;
}
// UPDATE header supplier payment #
# UPDATE header supplier payment #
$sql_updateheader = "UPDATE supplier_payment SET
SupplierPaymentIsConfirm = 'Y',
SupplierPaymentConfirmUserID = ?,
@@ -530,60 +313,67 @@ class PaymentV2 extends MY_Controller
$roundvalue,
$param['keterangan'],
$param['paymenttype'],
$SupplierPaymentID,
$SupplierPaymentID
]);
if (!$que_updateheader) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update supplier payment header');
exit();
$this->sys_error_db("[Error] update supplier payment header");
exit;
}
// UPDATE status lunas supplier invoice #
# UPDATE status lunas supplier invoice #
$sql_suppinvoice = "UPDATE supplier_invoice
SET SupplierInvoiceIsLunas = 'Y'
WHERE SupplierInvoiceID = ?";
$que_suppinvoice = $this->db->query($sql_suppinvoice, [$SupplierInvoiceID]);
if (!$que_suppinvoice) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] update status lunas invoice');
exit();
$this->sys_error_db("[Error] update status lunas invoice");
exit;
}
// GET Latest data supplier payment #
$sql_suppayment = 'SELECT * FROM supplier_payment WHERE SupplierPaymentID = ?';
# GET Latest data supplier payment #
$sql_suppayment = "SELECT * FROM supplier_payment WHERE SupplierPaymentID = ?";
$que_suppayment = $this->db->query($sql_suppayment, [$SupplierPaymentID]);
if (!$que_suppayment) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier payment');
exit();
$this->sys_error_db("[Error] get data latest supplier payment");
exit;
}
$suppayment_header = $que_suppayment->row_array();
$sql_suppaymentdetail = 'SELECT * FROM supplier_payment_detail
WHERE SupplierPaymentDetailSupplierPaymentID = ?';
$sql_suppaymentdetail = "SELECT * FROM supplier_payment_detail
WHERE SupplierPaymentDetailSupplierPaymentID = ?";
$que_suppaymentdetail = $this->db->query($sql_suppaymentdetail, [$SupplierPaymentID]);
if (!$que_suppaymentdetail) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data latest supplier payment detail');
exit();
$this->sys_error_db("[Error] get data latest supplier payment detail");
exit;
}
$suppayment_detail = $que_suppaymentdetail->result_array();
$data_log = [
'header' => $suppayment_header,
'detail' => $suppayment_detail,
"header" => $suppayment_header,
"detail" => $suppayment_detail
];
// LOG activity confirm payment #
# LOG activity confirm payment #
$messg = "Pembayaran Faktur No: {$suppayment_header['SupplierPaymentNumber']}";
$messg .= " telah dikonfirmasi oleh {$users['M_UserUsername']} dengan nomor";
$messg .= " pembayaran kasir: {$suppayment_header['SupplierPaymentCashierNumber']}";
$this->insert_activity_log('PF', 'CONFIRM', $messg, $SupplierPaymentID, $data_log, $users['M_UserID']);
$this->insert_activity_log(
"PF",
"CONFIRM",
$messg,
$SupplierPaymentID,
$data_log,
$users['M_UserID']
);
// # INSERT jurnal ##
## INSERT jurnal ##
$detail_transac = [];
// GET data hutang #
# GET data hutang #
$sql_datahutang = "SELECT
SupplierPaymentNumber AS addonvalue,
SupplierPaymentDetailID,
@@ -601,23 +391,23 @@ class PaymentV2 extends MY_Controller
$que_datahutang = $this->db->query($sql_datahutang, [$SupplierPaymentID]);
if (!$que_datahutang) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data hutang item');
exit();
$this->sys_error_db("[Error] get data hutang item");
exit;
}
$data_hutang = $que_datahutang->result_array();
foreach ($data_hutang as $key => $debt) {
$detail_transac[] = [
'coaID' => $debt['coaID'],
'coaDescription' => $debt['coaDescription'],
'debit' => $debt['SupplierPaymentDetailAmount'],
'credit' => 0,
'addoncode' => 'JFA',
'addonvalue' => $debt['addonvalue'],
'addonitemid' => $debt['SupplierInvoiceDetailItemID'],
"coaID" => $debt['coaID'],
"coaDescription" => $debt['coaDescription'],
"debit" => $debt['SupplierPaymentDetailAmount'],
"credit" => 0,
"addoncode" => "JFA",
"addonvalue" => $debt['addonvalue'],
"addonitemid" => $debt['SupplierInvoiceDetailItemID']
];
}
// GET data bayar #
# GET data bayar #
$sql_databayar = "SELECT
CONCAT('Jurnal Payment Invoice Nomor : ',
SupplierPaymentNumber, DATE_FORMAT(now(),
@@ -641,61 +431,61 @@ class PaymentV2 extends MY_Controller
$que_databayar = $this->db->query($sql_databayar, [$SupplierPaymentID]);
if (!$que_databayar) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data bayar');
exit();
$this->sys_error_db("[Error] get data bayar");
exit;
}
$data_bayar = $que_databayar->row_array();
$detail_transac[] = [
'coaID' => $data_bayar['coaID'],
'coaDescription' => $data_bayar['coaDescription'],
'debit' => 0,
'credit' => abs($data_bayar['bayar']),
'addoncode' => 'PAYINVGR',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $data_bayar['coaID'],
"coaDescription" => $data_bayar['coaDescription'],
"debit" => 0,
"credit" => abs($data_bayar['bayar']),
"addoncode" => "PAYINVGR",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
// rugi / untung dari rounding #
# rugi / untung dari rounding #
$sql_lossprofit = "SELECT coaID, coaDescription
FROM coa WHERE coaAccountNo = ? AND coaIsActive = 'Y'";
$data_round = doubleval($data_bayar['rounding']);
if ($data_round > 0) {
// loss #
# loss #
$que_lossprofit = $this->db->query($sql_lossprofit, ['6120402001']);
if (!$que_lossprofit) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data coa loss sisa kas');
exit();
$this->sys_error_db("[Error] get data coa loss sisa kas");
exit;
}
$loss_coa = $que_lossprofit->row_array();
$detail_transac[] = [
'coaID' => $loss_coa['coaID'],
'coaDescription' => $loss_coa['coaDescription'],
'debit' => abs($data_round),
'credit' => 0,
'addoncode' => 'PAYINVGR',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $loss_coa['coaID'],
"coaDescription" => $loss_coa['coaDescription'],
"debit" => abs($data_round),
"credit" => 0,
"addoncode" => "PAYINVGR",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
} elseif ($data_round < 0) {
// profit #
# profit #
$que_lossprofit = $this->db->query($sql_lossprofit, ['6110500001']);
if (!$que_lossprofit) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get data coa profit sisa kas');
exit();
$this->sys_error_db("[Error] get data coa profit sisa kas");
exit;
}
$profit_coa = $que_lossprofit->row_array();
$detail_transac[] = [
'coaID' => $profit_coa['coaID'],
'coaDescription' => $profit_coa['coaDescription'],
'debit' => 0,
'credit' => abs($data_round),
'addoncode' => 'PAYINVGR',
'addonvalue' => $data_bayar['addonvalue'],
'addonitemid' => 0,
"coaID" => $profit_coa['coaID'],
"coaDescription" => $profit_coa['coaDescription'],
"debit" => 0,
"credit" => abs($data_round),
"addoncode" => "PAYINVGR",
"addonvalue" => $data_bayar['addonvalue'],
"addonitemid" => 0
];
}
@@ -703,7 +493,7 @@ class PaymentV2 extends MY_Controller
}
$this->db->trans_commit();
$this->sys_ok('[Success] success multi payment invoice');
$this->sys_ok("[Success] success multi payment invoice");
} catch (Exception $exc) {
$message = $exc->getMessage();
$this->sys_error($message);
@@ -712,49 +502,49 @@ class PaymentV2 extends MY_Controller
private function insertJurnal($users, $databayar, $detailtrx)
{
// GET periode jurnal #
# GET periode jurnal #
$sql_periode = "SELECT periodeID FROM periode
WHERE DATE(NOW()) BETWEEN periodeStartDate AND periodeEndDate
AND periodeIsActive = 'Y' AND periodeIsClosed = 'N'";
$que_periode = $this->db->query($sql_periode, []);
if (!$que_periode) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] find periode id');
exit();
$this->sys_error_db("[Error] find periode id");
exit;
}
if ($que_periode->num_rows() === 0) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] Waktu periode tidak ditemukan');
exit();
$this->sys_error_db("[Error] Waktu periode tidak ditemukan");
exit;
}
$JurnalPeriode = $que_periode->row_array()['periodeID'];
// GENERATE nomor jurnal #
# GENERATE nomor jurnal #
$sql_jurnalno = "SELECT `fn_numbering`('J') AS jnumber";
$que_jurnalno = $this->db->query($sql_jurnalno, []);
if (!$que_jurnalno) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] generate jurnal number');
exit();
$this->sys_error_db("[Error] generate jurnal number");
exit;
}
$JurnalNumber = $que_jurnalno->row_array()['jnumber'];
$JurnalTitle = $databayar['jurnaltitle'];
$JurnalDescp = $databayar['jurnaldesc'];
// GET jurnal type #
# GET jurnal type #
$sql_jurnaltype = "SELECT JurnalTypeID FROM jurnal_type
WHERE JurnalTypeCode = 'PAYMENTINV' AND JurnalTypeIsActive = 'Y'";
$que_jurnaltype = $this->db->query($sql_jurnaltype, []);
if (!$que_jurnaltype) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] get jurnal type');
exit();
$this->sys_error_db("[Error] get jurnal type");
exit;
}
$JurnalType = $que_jurnaltype->row_array()['JurnalTypeID'];
// INSERT jurnal header #
$sql_insheader = 'INSERT INTO jurnal (
# INSERT jurnal header #
$sql_insheader = "INSERT INTO jurnal (
jurnalM_BranchCompanyID,
JurnalS_RegionalID,
jurnalM_BranchCode,
@@ -765,7 +555,7 @@ class PaymentV2 extends MY_Controller
jurnalDate,
jurnalJurnalTypeID,
jurnalM_UserID
) VALUES (?,?,?,?,?,?,?,NOW(),?,?)';
) VALUES (?,?,?,?,?,?,?,NOW(),?,?)";
$que_inserjurnal = $this->db->query($sql_insheader, [
$users['M_BranchCompanyID'],
$users['S_RegionalID'],
@@ -775,42 +565,42 @@ class PaymentV2 extends MY_Controller
$JurnalTitle,
$JurnalDescp,
$JurnalType,
$users['M_UserID'],
$users['M_UserID']
]);
if (!$que_inserjurnal) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] insert jurnal header');
exit();
$this->sys_error_db("[Error] insert jurnal header");
exit;
}
$JurnalID = $this->db->insert_id();
// INSERT jurnal tx #
# INSERT jurnal tx #
foreach ($detailtrx as $key => $trax) {
$sql_trax = 'INSERT INTO jurnal_tx (
$sql_trax = "INSERT INTO jurnal_tx (
jurnalTxJurnalID,
jurnalTxCoaID,
jurnalTxDescription,
jurnalTxDebit,
jurnalTxCredit,
jurnalTxM_UserID
) VALUES (?,?,?,?,?,?)';
) VALUES (?,?,?,?,?,?)";
$que_trax = $this->db->query($sql_trax, [
$JurnalID,
$trax['coaID'],
$trax['coaDescription'],
$trax['debit'],
$trax['credit'],
$users['M_UserID'],
$users['M_UserID']
]);
if (!$que_trax) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] insert jurnal tx');
exit();
$this->sys_error_db("[Error] insert jurnal tx");
exit;
}
$JurnalTxID = $this->db->insert_id();
// INSERT jurnal addon #
$sql_addon = 'INSERT INTO jurnal_addon (
# INSERT jurnal addon #
$sql_addon = "INSERT INTO jurnal_addon (
jurnalAddOnJurnalID,
jurnalAddOnJurnalTxID,
jurnalAddOnCode,
@@ -818,19 +608,19 @@ class PaymentV2 extends MY_Controller
jurnalAddOnM_ItemID,
jurnalAddOnCreated,
jurnalAddOnCreatedUserID
) VALUES (?,?,?,?,?,NOW(),?)';
) VALUES (?,?,?,?,?,NOW(),?)";
$que_addon = $this->db->query($sql_addon, [
$JurnalID,
$JurnalTxID,
$trax['addoncode'],
$trax['addonvalue'],
$trax['addonitemid'],
$users['M_UserID'],
$users['M_UserID']
]);
if (!$que_addon) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] insert jurnal addon');
exit();
$this->sys_error_db("[Error] insert jurnal addon");
exit;
}
}
}
@@ -840,11 +630,11 @@ class PaymentV2 extends MY_Controller
$json = json_encode($data);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->db->trans_rollback();
$this->sys_error('[Error] encode data into json for log activity');
exit();
$this->sys_error("[Error] encode data into json for log activity");
exit;
}
$sql = 'INSERT INTO user_activity(
$sql = "INSERT INTO user_activity(
UserActivityCode,
UserActivityStatus,
UserActivityDescription,
@@ -852,19 +642,19 @@ class PaymentV2 extends MY_Controller
UserActivityData,
UserActivityUserID,
UserActivityCreated
) VALUES (?,?,?,?,?,?,NOW())';
) VALUES (?,?,?,?,?,?,NOW())";
$que = $this->db->query($sql, [
$actcode,
$status,
$desc,
$refID,
$json,
$userID,
$userID
]);
if (!$que) {
$this->db->trans_rollback();
$this->sys_error_db('[Error] insert into table log activity');
exit();
$this->sys_error_db("[Error] insert into table log activity");
exit;
}
}
}

View File

@@ -6,15 +6,13 @@ class Bill extends MY_Controller
{
echo "Bill API";
}
public function __construct()
{
parent::__construct();
$this->db_onedev = $this->load->database("onedev", true);
}
public function add_notes($orderid)
{
public function add_notes($orderid){
$sql = " SELECT SupplierPaymentSupplierInvoiceID as note_order_id,
SupplierPaymentID as note_id,
SupplierPaymentDetailSupplierInvoiceDetailID as detail_id,
@@ -56,144 +54,14 @@ class Bill extends MY_Controller
}
}
return $rows;
} else {
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function add_notes_downpayment($orderid)
{
$sql = "SELECT SupplierPaymentSupplierDownpaymentID AS note_order_id,
SupplierPaymentID AS note_id,
SupplierDownpaymentID AS detail_id,
SupplierPaymentDate AS note_date,
SupplierPaymentNumber AS note_number,
GROUP_CONCAT(DISTINCT coaDescription separator ' , ') AS paymenttypes_name,
SUM(SupplierDownpaymentAmount) AS note_amount,
n.M_UserUsername AS note_user,
SupplierDownpaymentIsActive AS note_active,
'N' AS show_detail,
SupplierPaymentNote AS keterangan,
SupplierPaymentCoaID,
coaID,
coaDescription,
SupplierPaymentIsConfirm,
CONCAT(
'Confirmed by : ',c.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentConfirmDate, '%d-%m-%Y %H:%i')
) AS d_confirm,
SupplierPaymentIsApproved,
CONCAT(
'Approved by : ',a.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentApprovedDate,'%d-%m-%Y %H:%i')
) AS d_approved,
CONCAT(
'Verified by : ',b.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentVerifDate,'%d-%m-%Y %H:%i')
) AS d_verif
FROM supplier_payment
JOIN supplier_downpayment
ON SupplierDownpaymentID = SupplierPaymentSupplierDownpaymentID
AND SupplierDownpaymentIsActive = 'Y'
LEFT JOIN coa ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b ON SupplierPaymentVerifUserID = b.M_UserID
WHERE SupplierPaymentSupplierDownpaymentID = {$orderid}
AND SupplierPaymentIsActive = 'Y'
GROUP BY SupplierPaymentID";
$query = $this->db_onedev->query($sql);
if ($query) {
$rows = $query->result_array();
return $rows ?: [];
} else {
$this->sys_error_db("get DP notes", $this->db_onedev);
exit;
}
}
public function add_notes_installment($orderid)
{
$sql = "SELECT SupplierPaymentSupplierInstallmentID as note_order_id,
SupplierPaymentID as note_id,
SupplierInstallmentID as detail_id,
SupplierPaymentDate as note_date,
SupplierPaymentNumber as note_number,
GROUP_CONCAT(DISTINCT coaDescription separator ' , ') as paymenttypes_name,
SUM(SupplierInstallmentAmount) as note_amount,
n.M_UserUsername as note_user,
SupplierInstallmentIsActive as note_active,
'N' as show_detail,
SupplierPaymentNote as keterangan,
SupplierPaymentCoaID,
coaID,
coaDescription,
SupplierPaymentIsConfirm,
CONCAT(
'Confirmed by : ',c.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentConfirmDate,'%d-%m-%Y %H:%i')
) AS d_confirm,
SupplierPaymentIsApproved,
CONCAT(
'Approved by : ',a.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentApprovedDate,'%d-%m-%Y %H:%i')
) AS d_approved,
CONCAT(
'Verified by : ',b.M_UserUsername, ' ',
DATE_FORMAT(SupplierPaymentVerifDate,'%d-%m-%Y %H:%i')
) AS d_verif
FROM supplier_payment
JOIN supplier_installment
ON SupplierInstallmentID = SupplierPaymentSupplierInstallmentID
AND SupplierInstallmentIsActive = 'Y'
LEFT JOIN coa ON SupplierPaymentCoaID = coaID
LEFT JOIN m_user n ON SupplierPaymentUserID = n.M_UserID
LEFT JOIN m_user c ON SupplierPaymentConfirmUserID = c.M_UserID
LEFT JOIN m_user a ON SupplierPaymentApprovedUserID = a.M_UserID
LEFT JOIN m_user b ON SupplierPaymentVerifUserID = b.M_UserID
WHERE SupplierPaymentSupplierInstallmentID = {$orderid}
AND SupplierPaymentIsActive = 'Y'
GROUP BY SupplierPaymentID";
$query = $this->db_onedev->query($sql);
if ($query) {
$rows = $query->result_array();
return $rows ?: [];
} else {
$this->sys_error_db("get Installment notes", $this->db_onedev);
exit;
}
}
public function add_tagihans($orderid)
{
$sql_dp = "SELECT
Fa_ClassDownPaymentCoaID
FROM supplier_invoice_detail
JOIN m_item
ON M_ItemID = SupplierInvoiceDetailItemID
AND SupplierInvoiceDetailSupplierInvoiceID = ?
AND SupplierInvoiceDetailIsActive = 'Y'
AND SupplierInvoiceDetailIsVerified = 'Y'
JOIN fa_class
ON Fa_ClassID = M_ItemFa_ClassID
AND Fa_ClassIsActive = 'Y'";
$que_dp = $this->db_onedev->query($sql_dp, [$orderid]);
if (!$que_dp) {
$this->sys_error_db("failed to get coa id dp tagihans", $this->db_onedev);
exit;
}
$dataDP = $que_dp->result_array();
$excludeIds = array_column($dataDP, 'Fa_ClassDownPaymentCoaID');
$excludeIds[] = 563; // always excluded
$excludeIds = array_unique($excludeIds);
$excludeIds = array_values($excludeIds); // re-index
$notInPlaceholders = implode(',', array_fill(0, count($excludeIds), '?'));
$sql = "SELECT
SupplierInvoiceID as tagihan_id,
public function add_tagihans($orderid){
$sql = "SELECT SupplierInvoiceID as tagihan_id,
PurchaseOrderNumber as tagihan_number,
jurnalTxDescription as pasien,
jurnalTxCredit as tagihan_total,
@@ -204,42 +72,30 @@ class Bill extends MY_Controller
'N' as show_detail,
jurnalTxID SupplierInvoiceDetailID,
PurchaseOrderID SupplierInvoiceDetailPurchaseOrderID
FROM supplier_invoice
JOIN receive_order_po
ON ReceiveOrderPoID = SupplierInvoiceReceiveOrderPoID
JOIN receive_order_po_detail
ON ReceiveOrderPoDetailReceiveOrderPoID = ReceiveOrderPoID
JOIN purchase_order
ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID
JOIN jurnal_addon
ON jurnalAddOnValue = SupplierInvoiceNumber
AND jurnalAddOnIsActive = 'Y'
JOIN jurnal_tx
ON jurnalTxJurnalID = jurnalAddOnJurnalID
AND jurnalTxIsActive = 'Y'
AND jurnalTxCredit <> 0
AND jurnalTxCoaID NOT IN ($notInPlaceholders)
LEFT JOIN supplier_payment
ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID
AND SupplierInvoiceIsActive = 'Y'
LEFT JOIN supplier_payment_detail
ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID
AND SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID
AND SupplierPaymentDetailIsActive = 'Y'
WHERE SupplierInvoiceID = ?
GROUP BY jurnalTxID";
$query = $this->db_onedev->query($sql, array_merge($excludeIds, [$orderid]));
JOIN receive_order_po ON ReceiveOrderPoID = SupplierInvoiceReceiveOrderPoID
JOIN receive_order_po_detail ON ReceiveOrderPoDetailReceiveOrderPoID = ReceiveOrderPoID
JOIN purchase_order ON ReceiveOrderPoDetailPurchaseOrderID = PurchaseOrderID
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN jurnal_tx ON jurnalTxJurnalID = jurnalAddOnJurnalID AND jurnalTxCredit <> 0 AND jurnalTxCoaID <> 563
LEFT JOIN supplier_payment ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID AND SupplierInvoiceIsActive = 'Y'
LEFT JOIN supplier_payment_detail ON SupplierPaymentDetailSupplierPaymentID = SupplierPaymentID AND SupplierPaymentDetailSupplierInvoiceDetailID = jurnalTxID AND SupplierPaymentDetailIsActive = 'Y'
WHERE
SupplierInvoiceID = {$orderid}
GROUP BY jurnalTxID
";
$query = $this->db_onedev->query($sql);
if ($query) {
$rows = $query->result_array();
return $rows;
} else {
$this->sys_error_db("get tagihans", $this->db_onedev);
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function add_tests($orderid)
{
public function add_tests($orderid){
$sql = " SELECT SupplierPaymentSupplierInvoiceID as note_order_id,
SupplierPaymentID as note_id,
SupplierPaymentDate as note_date,
@@ -266,20 +122,19 @@ class Bill extends MY_Controller
if($rows){
}
return $rows;
} else {
$this->sys_error_db("get notes", $this->db_onedev);
exit;
}
}
public function search_old()
public function search()
{
//# cek token valid
if (! $this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
$prm = $this->sys_input;
$supplier = $prm["supplier"];
$search = $prm["search"];
@@ -388,6 +243,7 @@ class Bill extends MY_Controller
$rows[$k]['notes'] = $this->add_notes($v['SupplierInvoiceID']);
$rows[$k]['tagihans'] = $this->add_tagihans($v['SupplierInvoiceID']);
}
}
@@ -397,392 +253,5 @@ class Bill extends MY_Controller
exit;
}
/**
* search — unified invoice + downpayment list:
* - Only columns the frontend actually reads (no wildcard, no dead aliases)
* - UNION ALL merges supplier_invoice and supplier_downpayment
* - DP rows enriched via SupplierPaymentSupplierDownpaymentID FK
* - All queries use PDO parameterised placeholders
*/
public function search()
{
if (! $this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
$prm = $this->sys_input;
$supplier = $prm["supplier"];
$search = $prm["search"];
$status = $prm["status"];
$startdate = $prm["startdate"];
$enddate = $prm["enddate"];
$regionalid = $this->sys_user['S_RegionalID'];
$number_limit = 10;
$number_offset = ((int)$prm['current_page'] - 1) * $number_limit;
// ── INVOICE WHERE ──────────────────────────────────────────
$inv_where = " SupplierInvoiceIsActive = 'Y'
AND SupplierInvoiceStatus = 'Approved'
AND SupplierInvoiceGrandTotal > 0
AND SupplierInvoiceIsInstallment = 'N'
AND IF(SupplierPaymentID IS NULL,'N','Y') = ?
AND SupplierInvoiceNumber LIKE ?
AND SupplierName LIKE ?
AND ReceiveOrderPoS_RegionalID = ?
AND SupplierInvoiceDraftPaymentDate BETWEEN ? AND ? ";
$inv_params = [
$status,
'%' . $search . '%',
'%' . $supplier . '%',
$regionalid,
$startdate,
$enddate,
];
// ── DOWNPAYMENT WHERE ──────────────────────────────────────
$dp_where = " SupplierDownpaymentIsActive = 'Y'
AND IF(SupplierDownpaymentStatus = 'Paid','Y','N') = ?
AND PurchaseOrderNumber LIKE ?
AND SupplierName LIKE ?
AND PurchaseOrderS_RegionalID = ?
AND SupplierDownpaymentDueDate BETWEEN ? AND ? ";
$dp_params = [
$status,
'%' . $search . '%',
'%' . $supplier . '%',
$regionalid,
$startdate,
$enddate,
];
// ── INSTALLMENT WHERE ───────────────────────────────────
$inst_where = " inst.SupplierInstallmentIsActive = 'Y'
AND IF(sp.SupplierPaymentID IS NULL,'N','Y') = ?
AND CONCAT('INST-', si.SupplierInvoiceNumber) LIKE ?
AND sup.SupplierName LIKE ?
AND rop.ReceiveOrderPoS_RegionalID = ?
AND inst.SupplierInstallmentDueDate BETWEEN ? AND ? ";
$inst_params = [
$status,
'%' . $search . '%',
'%' . $supplier . '%',
$regionalid,
$startdate,
$enddate,
];
// ── COUNT query — UNION of all three sources ────────────
$sql_count = "SELECT SUM(cnt) as total FROM (
SELECT count(*) as cnt
FROM supplier_invoice
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
LEFT JOIN supplier_payment
ON SupplierInvoiceID = SupplierPaymentSupplierInvoiceID
AND SupplierPaymentIsActive = 'Y'
LEFT JOIN supplier ON SupplierInvoiceSupplierID = SupplierID
JOIN receive_order_po ON SupplierInvoiceReceiveOrderPoID = ReceiveOrderPoID
WHERE $inv_where
UNION ALL
SELECT count(*) as cnt
FROM supplier_downpayment
JOIN purchase_order ON PurchaseOrderID = SupplierDownpaymentPurchasOrderID
JOIN supplier ON SupplierID = SupplierDownpaymentSupplierID
WHERE $dp_where
UNION ALL
SELECT count(*) as cnt
FROM supplier_installment inst
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInstallmentID = inst.SupplierInstallmentID
AND sp.SupplierPaymentIsActive = 'Y'
JOIN supplier_invoice si
ON si.SupplierInvoiceID = inst.SupplierInstallmentSupplierInvoiceID
JOIN receive_order_po rop
ON rop.ReceiveOrderPoID = si.SupplierInvoiceReceiveOrderPoID
JOIN supplier sup
ON sup.SupplierID = inst.SupplierInstallmentSupplierID
WHERE $inst_where
) AS combined";
$count_params = array_merge($inv_params, $dp_params, $inst_params);
$query = $this->db_onedev->query($sql_count, $count_params);
$tot_count = 0;
$tot_page = 0;
if ($query) {
$tot_count = $query->result_array()[0]["total"];
$tot_page = ceil($tot_count / $number_limit);
} else {
$this->sys_error_db("payment instructions count", $this->db_onedev);
exit;
}
// ── DATA query — UNION with identical columns ──────────────
$sql_data = "
SELECT * FROM (
SELECT
SupplierInvoiceID,
0 as DownpaymentID,
0 as InstallmentID,
SupplierInvoiceNumber,
SupplierInvoiceDraftPaymentDate,
SupplierInvoiceIsLunas as flaglunas,
SupplierName,
DATE_FORMAT(SupplierInvoiceDraftPaymentDate,'%d-%m-%Y') as tanggalbayar,
IFNULL(SupplierPaymentIsApproved,'N') as SupplierPaymentIsApproved,
IFNULL(SupplierPaymentCashierNumber,'') as SupplierPaymentCashierNumber,
0 as totalbill,
0 as paid,
0 as unpaid,
0 as SupplierPaymentID,
'' as SupplierPaymentNumber,
0 as SupplierPaymentAmount,
'' as SupplierPaymentDate,
'' as notes,
'' as tagihans,
0 as parent_invoice_id,
'INVOICE' as type
FROM supplier_invoice
LEFT JOIN supplier ON SupplierInvoiceSupplierID = SupplierID
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN receive_order_po ON SupplierInvoiceReceiveOrderPoID = ReceiveOrderPoID
LEFT JOIN supplier_payment
ON SupplierPaymentSupplierInvoiceID = SupplierInvoiceID
AND SupplierPaymentIsActive = 'Y'
WHERE $inv_where
GROUP BY SupplierInvoiceID
UNION ALL
SELECT
0 as SupplierInvoiceID,
SupplierDownpaymentID as DownpaymentID,
0 as InstallmentID,
CONCAT('DP-', PurchaseOrderNumber) as SupplierInvoiceNumber,
SupplierDownpaymentDueDate as SupplierInvoiceDraftPaymentDate,
SupplierDownpaymentIsLunas as flaglunas,
SupplierName,
DATE_FORMAT(SupplierDownpaymentDueDate,'%d-%m-%Y') as tanggalbayar,
IFNULL(SupplierPaymentIsApproved,'N') as SupplierPaymentIsApproved,
IFNULL(SupplierPaymentCashierNumber,'') as SupplierPaymentCashierNumber,
SupplierDownpaymentAmount as totalbill,
CASE WHEN SupplierDownpaymentStatus = 'Paid'
THEN SupplierDownpaymentAmount
ELSE 0 END as paid,
CASE WHEN SupplierDownpaymentStatus = 'Paid'
THEN 0
ELSE SupplierDownpaymentAmount END as unpaid,
0 as SupplierPaymentID,
'' as SupplierPaymentNumber,
0 as SupplierPaymentAmount,
'' as SupplierPaymentDate,
'' as notes,
'' as tagihans,
0 as parent_invoice_id,
'DP' as type
FROM supplier_downpayment
JOIN purchase_order ON PurchaseOrderID = SupplierDownpaymentPurchasOrderID
JOIN supplier ON SupplierID = SupplierDownpaymentSupplierID
LEFT JOIN supplier_payment
ON SupplierPaymentSupplierDownpaymentID = SupplierDownpaymentID
AND SupplierPaymentIsActive = 'Y'
WHERE $dp_where
UNION ALL
SELECT
0 as SupplierInvoiceID,
0 as DownpaymentID,
inst.SupplierInstallmentID as InstallmentID,
CONCAT('INST-', si.SupplierInvoiceNumber) as SupplierInvoiceNumber,
inst.SupplierInstallmentDueDate as SupplierInvoiceDraftPaymentDate,
inst.SupplierInstallmentIsLunas as flaglunas,
sup.SupplierName,
DATE_FORMAT(inst.SupplierInstallmentDueDate,'%d-%m-%Y') as tanggalbayar,
IFNULL(sp.SupplierPaymentIsApproved,'N') as SupplierPaymentIsApproved,
IFNULL(sp.SupplierPaymentCashierNumber,'') as SupplierPaymentCashierNumber,
inst.SupplierInstallmentAmount as totalbill,
CASE WHEN inst.SupplierInstallmentStatus = 'Paid'
THEN inst.SupplierInstallmentAmount
ELSE 0 END as paid,
CASE WHEN inst.SupplierInstallmentStatus = 'Paid'
THEN 0
ELSE inst.SupplierInstallmentAmount END as unpaid,
0 as SupplierPaymentID,
'' as SupplierPaymentNumber,
0 as SupplierPaymentAmount,
'' as SupplierPaymentDate,
'' as notes,
'' as tagihans,
si.SupplierInvoiceID as parent_invoice_id,
'INSTALLMENT' as type
FROM supplier_installment inst
LEFT JOIN supplier_payment sp
ON sp.SupplierPaymentSupplierInstallmentID = inst.SupplierInstallmentID
AND sp.SupplierPaymentIsActive = 'Y'
JOIN supplier_invoice si
ON si.SupplierInvoiceID = inst.SupplierInstallmentSupplierInvoiceID
JOIN receive_order_po rop
ON rop.ReceiveOrderPoID = si.SupplierInvoiceReceiveOrderPoID
JOIN supplier sup
ON sup.SupplierID = inst.SupplierInstallmentSupplierID
WHERE $inst_where
) AS combined
ORDER BY SupplierInvoiceID ASC
LIMIT ? OFFSET ?";
$data_params = array_merge($inv_params, $dp_params, $inst_params, [$number_limit, $number_offset]);
$query = $this->db_onedev->query($sql_data, $data_params);
if (!$query) {
$this->sys_error_db("payment instructions data", $this->db_onedev);
exit;
}
$rows = $query->result_array();
// ── ENRICH — per-row sub-queries ───────────────────────────
if ($rows) {
foreach ($rows as $k => $v) {
// ── Downpayment branch ──────────────────────────
if ($v['type'] === 'DP') {
$dp_id = $v['DownpaymentID'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
IFNULL(SupplierPaymentID,0) as SupplierPaymentID,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierDownpaymentID = ?",
[$dp_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$rows[$k]['SupplierPaymentID'] = $s_payment->SupplierPaymentID
? $s_payment->SupplierPaymentID
: '0';
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber
? $s_payment->SupplierPaymentNumber
: '';
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['notes'] = $this->add_notes_downpayment($dp_id);
$rows[$k]['tagihans'] = [];
// totalbill, unpaid already correct from UNION CASE
continue;
}
// ── Installment branch ─────────────────────────
if ($v['type'] === 'INSTALLMENT') {
$inst_id = $v['InstallmentID'];
$parent_invoice_id = $v['parent_invoice_id'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
IFNULL(SupplierPaymentID,0) as SupplierPaymentID,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierInstallmentID = ?",
[$inst_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$rows[$k]['SupplierPaymentID'] = $s_payment->SupplierPaymentID
? $s_payment->SupplierPaymentID
: '0';
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber
? $s_payment->SupplierPaymentNumber
: '';
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['notes'] = $this->add_notes_installment($inst_id);
$rows[$k]['tagihans'] = $this->add_tagihans($parent_invoice_id);
// totalbill, unpaid already correct from UNION CASE
continue;
}
// ── Invoice branch ───────────────────────────────
$inv_id = $v['SupplierInvoiceID'];
$s_payment = $this->db_onedev->query("
SELECT
GROUP_CONCAT(SupplierPaymentNumber SEPARATOR ', ') as SupplierPaymentNumber,
SUM(IFNULL(SupplierPaymentAmount,0)) as SupplierPaymentAmount,
IFNULL(SupplierPaymentID,0) as SupplierPaymentID,
GROUP_CONCAT(
DATE_FORMAT(SupplierPaymentDate,'%d-%m-%Y')
SEPARATOR ', '
) as SupplierPaymentDate
FROM supplier_payment
WHERE SupplierPaymentIsActive = 'Y'
AND SupplierPaymentSupplierInvoiceID = ?",
[$inv_id]
)->row();
$s_jurnal = $this->db_onedev->query("
SELECT SUM(jurnalTxCredit) totalbill
FROM supplier_invoice
JOIN jurnal_addon ON jurnalAddOnValue = SupplierInvoiceNumber
JOIN jurnal_tx
ON jurnalTxJurnalID = jurnalAddOnJurnalID
AND jurnalTxCredit <> 0
AND jurnalTxCoaID <> 563
WHERE SupplierInvoiceID = ?
GROUP BY SupplierInvoiceID",
[$inv_id]
)->row();
$amount = $s_payment->SupplierPaymentAmount
? $s_payment->SupplierPaymentAmount
: "0.00";
$unpaid = (float)$s_jurnal->totalbill - (float)$amount;
$rows[$k]['SupplierPaymentID'] = $s_payment->SupplierPaymentID
? $s_payment->SupplierPaymentID
: '0';
$rows[$k]['SupplierPaymentNumber'] = $s_payment->SupplierPaymentNumber
? $s_payment->SupplierPaymentNumber
: '';
$rows[$k]['SupplierPaymentAmount'] = $amount;
$rows[$k]['SupplierPaymentDate'] = $s_payment->SupplierPaymentDate;
$rows[$k]['paid'] = $amount;
$rows[$k]['totalbill'] = $s_jurnal->totalbill
? $s_jurnal->totalbill
: "0.00";
$rows[$k]['unpaid'] = number_format($unpaid, 2, '.', '');
$rows[$k]['notes'] = $this->add_notes($inv_id);
$rows[$k]['tagihans'] = $this->add_tagihans($inv_id);
}
}
$result = array("total" => $tot_page, "records" => $rows);
$this->sys_ok($result);
exit;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,532 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class FakturFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// -- Posisi: 20mm dari bawah halaman ----------------------------------
$this->SetY(-20);
// -- Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ---------------
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// -- Baris 2: Tgl Print (kiri) ------------------------------------------
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_faktur extends MY_Controller
{
// -- Properti bersama antar fungsi PDF -------------------------------------
/** @var FakturFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Faktur (Supplier Invoice) Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (SupplierInvoiceID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// -- Ambil data header & detail dari DB ----------------------------
$header = $this->_get_header($id);
$details = $this->_get_detail($id);
// -- Inisialisasi FPDF custom --------------------------------------
$this->_pdf = new FakturFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// -- Susun isi halaman ---------------------------------------------
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_summary();
$this->_pdf_terms();
// -- Output --------------------------------------------------------
$filename = 'INV_' . str_replace('/', '-', $header['SupplierInvoiceNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
si.*,
sup.SupplierName,
sup.SupplierAddress,
sup.SupplierPhone,
IFNULL(uCr.M_UserUsername, '') AS CreatedByName,
IFNULL(uVe.M_UserUsername, '') AS VerifiedByName,
IFNULL(uAp.M_UserUsername, '') AS ApprovedByName,
IFNULL(uPa.M_UserUsername, '') AS PaidByName,
IFNULL(uRe.M_UserUsername, '') AS ReceivedByName
FROM supplier_invoice si
LEFT JOIN supplier sup ON sup.SupplierID = si.SupplierInvoiceSupplierID
AND sup.SupplierIsActive = 'Y'
LEFT JOIN m_user uCr ON uCr.M_UserID = si.SupplierInvoiceCreatedUserID
LEFT JOIN m_user uVe ON uVe.M_UserID = si.SupplierInvoiceVerifiedUserID
LEFT JOIN m_user uAp ON uAp.M_UserID = si.SupplierInvoiceApprovedUserID
LEFT JOIN m_user uPa ON uPa.M_UserID = si.SupplierInvoicePaidUserID
LEFT JOIN m_user uRe ON uRe.M_UserID = si.SupplierInvoiceReceivedBy
WHERE si.SupplierInvoiceID = ?
AND si.SupplierInvoiceIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Faktur tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
sid.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode
FROM supplier_invoice_detail sid
LEFT JOIN m_item i ON i.M_ItemID = sid.SupplierInvoiceDetailItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = sid.SupplierInvoiceDetailItemUnitID
WHERE sid.SupplierInvoiceDetailSupplierInvoiceID = ?
AND sid.SupplierInvoiceDetailIsActive = 'Y'
ORDER BY sid.SupplierInvoiceDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// -- Judul utama --------------------------------------------------------
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'FAKTUR (SUPPLIER INVOICE)', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 32;
$valW = $halfW - $lblW - 4;
// -- Kolom Kiri -----------------------------------------------------------
$pdf->SetY($startY);
$leftItems = array(
array('Nomor Faktur', $header['SupplierInvoiceNumber'], true),
array('Tgl Faktur', $this->_fmt_date($header['SupplierInvoiceDate']), false),
array('Tgl Jatuh Tempo', $this->_fmt_date($header['SupplierInvoiceDueDate']), false),
array('Status', $header['SupplierInvoiceStatus'], false),
array('Supplier', $header['SupplierName'] ?: '-', false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Alamat Supplier (MultiCell)
if (!empty($header['SupplierAddress'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Alamat Supplier', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->MultiCell($valW, 4, $header['SupplierAddress'], 0, 'L');
$pdf->SetFont('Arial_Narrow', '', 9);
}
// Keterangan
if (!empty($header['SupplierInvoiceNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->MultiCell($valW, 4, $header['SupplierInvoiceNote'], 0, 'L');
$pdf->SetFont('Arial_Narrow', '', 9);
}
$leftY = $pdf->GetY();
// -- Kolom Kanan ----------------------------------------------------------
$pdf->SetY($startY);
$rightX = 15 + $halfW;
// No. Supplier Invoice & tgl (jika ada)
$rightItems = array();
if (!empty($header['SupplierInvoiceSupplierInvoiceNumber'])) {
$rightItems[] = array('No. Inv. Supplier', $header['SupplierInvoiceSupplierInvoiceNumber']);
$rightItems[] = array('Tgl Inv. Supplier', $this->_fmt_date($header['SupplierInvoiceSupplierInvoiceDate']));
}
$rightItems[] = array('No. Delivery Order', $header['SupplierInvoiceDeliveryOrderNumber'] ?: '-');
$rightItems[] = array('Dibuat Oleh', $header['CreatedByName'] ?: '-');
$rightItems[] = array('Dibuat Tgl', $this->_fmt_datetime($header['SupplierInvoiceCreated']));
if ($header['SupplierInvoiceStatus'] === 'Verified' || in_array($header['SupplierInvoiceStatus'], ['Approved', 'Scheduled', 'Paid', 'Partially Paid'])) {
if (!empty($header['SupplierInvoiceVerifiedDate'])) {
$rightItems[] = array('Diverifikasi Oleh', $header['VerifiedByName'] ?: '-');
$rightItems[] = array('Tgl Verifikasi', $this->_fmt_datetime($header['SupplierInvoiceVerifiedDate']));
}
}
if (in_array($header['SupplierInvoiceStatus'], ['Approved', 'Scheduled', 'Paid', 'Partially Paid'])) {
if (!empty($header['SupplierInvoiceApprovedDate'])) {
$rightItems[] = array('Disetujui Oleh', $header['ApprovedByName'] ?: '-');
$rightItems[] = array('Tgl Disetujui', $this->_fmt_datetime($header['SupplierInvoiceApprovedDate']));
}
}
if ($header['SupplierInvoiceStatus'] === 'Paid' || $header['SupplierInvoiceStatus'] === 'Partially Paid') {
if (!empty($header['SupplierInvoicePaidDate'])) {
$rightItems[] = array('Dibayar Oleh', $header['PaidByName'] ?: '-');
$rightItems[] = array('Tgl Dibayar', $this->_fmt_datetime($header['SupplierInvoicePaidDate']));
}
}
// Tgl Terima & Penerima (jika ada)
if (!empty($header['SupplierInvoiceReceiveDate'])) {
$rightItems[] = array('Tgl Terima', $this->_fmt_date($header['SupplierInvoiceReceiveDate']));
$rightItems[] = array('Diterima Oleh', $header['ReceivedByName'] ?: '-');
}
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// -- Definisi kolom: [label, lebar, align] -------------------------------
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 62 + 15 + 20 + 25 + 25 + 25 = 180
$cols = array(
array('No', 8, 'C'),
array('Deskripsi', 62, 'L'),
array('Unit', 15, 'C'),
array('Qty', 20, 'R'),
array('Harga Satuan', 25, 'R'),
array('Diskon', 25, 'R'),
array('Total', 25, 'R'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 7);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 7.5);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
foreach ($details as $d) {
$qty = floatval($d['SupplierInvoiceDetailQty'] ?? 0);
$price = floatval($d['SupplierInvoiceDetailPrice'] ?? 0);
$discAmount = floatval($d['SupplierInvoiceDetailDiscountAmount'] ?? 0);
$total = floatval($d['SupplierInvoiceDetailTotal'] ?? 0);
$desc = $d['SupplierInvoiceDetailDescription'] ?: ($d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'));
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $desc, 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'),1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[4][1], 6, $this->_fmt_rp($price), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $discAmount > 0 ? $this->_fmt_rp($discAmount) : '-', 1, 0, 'R');
$pdf->Cell($cols[6][1], 6, $this->_fmt_rp($total), 1, 0, 'R');
$pdf->Ln();
$no++;
}
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Ringkasan Nilai (SubTotal, Pajak, Grand Total)
// =========================================================================
private function _pdf_summary()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// -- Ringkasan nilai (rata kanan) ----------------------------------------
$cW1 = 50;
$cW2 = 40;
$offsetX = 15 + $this->_pageW - $cW1 - $cW2;
// Garis pemisah
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.3);
$pdf->Line(15 + $pageW - $cW1 - $cW2 - 4, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$items = array();
// Sub Total
$items[] = array('Sub Total', $this->_fmt_rp($header['SupplierInvoiceSubTotal']));
// Diskon (jika ada)
$discPct = floatval($header['SupplierInvoiceDiscountPercent'] ?? 0);
$discAmt = floatval($header['SupplierInvoiceDiscountAmount'] ?? 0);
if ($discPct > 0 || $discAmt > 0) {
$label = 'Diskon';
if ($discPct > 0) $label .= ' (' . $this->_fmt_num($discPct) . '%)';
$items[] = array($label, $this->_fmt_rp($discAmt));
}
// PPh (jika ada)
$pphPct = floatval($header['SupplierInvoiceTaxPercentPph'] ?? 0);
$pphAmt = floatval($header['SupplierInvoiceTaxAmountPph'] ?? 0);
if ($pphPct > 0 || $pphAmt > 0) {
$label = 'PPh';
if ($pphPct > 0) $label .= ' (' . $this->_fmt_num($pphPct) . '%)';
$items[] = array($label, $this->_fmt_rp($pphAmt));
}
// PPN (jika ada)
$ppnPct = floatval($header['SupplierInvoiceTaxPercentPpn'] ?? 0);
$ppnAmt = floatval($header['SupplierInvoiceTaxAmountPpn'] ?? 0);
if ($ppnPct > 0 || $ppnAmt > 0) {
$label = 'PPN';
if ($ppnPct > 0) $label .= ' (' . $this->_fmt_num($ppnPct) . '%)';
$items[] = array($label, $this->_fmt_rp($ppnAmt));
}
// Biaya Kirim (jika ada)
$shipCost = floatval($header['SupplierInvoiceShippingCost'] ?? 0);
if ($shipCost > 0) {
$items[] = array('Biaya Kirim', $this->_fmt_rp($shipCost));
}
// Penyesuaian (jika ada)
$adjAmt = floatval($header['SupplierInvoiceAdjustmentAmount'] ?? 0);
if ($adjAmt != 0) {
$label = 'Penyesuaian';
if (!empty($header['SupplierInvoiceAdjustmentNote'])) {
$label .= ' (' . $header['SupplierInvoiceAdjustmentNote'] . ')';
}
$items[] = array($label, $this->_fmt_rp($adjAmt));
}
foreach ($items as $item) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW1, 5, $item[0], 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW2, 5, $item[1], 0, 1, 'R');
}
// Grand Total (tebal)
$pdf->SetX($offsetX);
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.3);
$pdf->Line($offsetX, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(1);
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', 'B', 10);
$pdf->Cell($cW1, 6, 'Grand Total', 0, 0, 'L');
$pdf->Cell($cW2, 6, $this->_fmt_rp($header['SupplierInvoiceGrandTotal']), 0, 1, 'R');
// Sudah dibayar / sisa
$paidAmt = floatval($header['SupplierInvoicePaidAmount'] ?? 0);
$unpaidAmt = floatval($header['SupplierInvoiceUnpaid'] ?? 0);
$isLunas = $header['SupplierInvoiceIsLunas'] ?? 'N';
if ($paidAmt > 0) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW1, 5, 'Dibayar', 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW2, 5, $this->_fmt_rp($paidAmt), 0, 1, 'R');
}
if ($isLunas === 'Y') {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->SetTextColor(0, 128, 0);
$pdf->Cell($cW1 + $cW2, 5, 'LUNAS', 0, 1, 'R');
$pdf->SetTextColor(0, 0, 0);
} elseif ($unpaidAmt > 0) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->Cell($cW1, 5, 'Sisa Tagihan', 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->Cell($cW2, 5, $this->_fmt_rp($unpaidAmt), 0, 1, 'R');
}
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Faktur ini diterbitkan berdasarkan barang/jasa yang telah diterima dan sesuai dengan purchase order.',
'Pembayaran harus dilakukan sesuai dengan tanggal jatuh tempo yang tercantum.',
'Keterlambatan pembayaran akan dikenakan sanksi sesuai ketentuan yang berlaku.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,531 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class PemakaianItemDivisiFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// -- Posisi: 20mm dari bawah halaman ----------------------------------
$this->SetY(-20);
// -- Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ---------------
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// -- Baris 2: Tgl Print (kiri) ------------------------------------------
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_pemakaian_item_divisi extends MY_Controller
{
// -- Properti bersama antar fungsi PDF -------------------------------------
/** @var PemakaianItemDivisiFpdf */
private $_pdf;
private $_pageW;
private $_start_date;
private $_end_date;
private $_division_id;
private $_division_name;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Pemakaian Item per Divisi Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: start_date, end_date, division_id (opsional), username (opsional)
// =========================================================================
public function pdf()
{
try {
$start_date = trim($this->input->get_post('start_date') ?? '');
$end_date = trim($this->input->get_post('end_date') ?? '');
$division_id = intval($this->input->get_post('division_id'));
$username = trim($this->input->get_post('username') ?? '');
if ($start_date === '' || $end_date === '') {
$this->sys_error("Periode tanggal harus diisi");
exit;
}
// -- Ambil data dari DB --------------------------------------------
$data = $this->_get_data($start_date, $end_date, $division_id);
if (empty($data)) {
$this->sys_error("Tidak ada data pemakaian item pada periode tersebut");
exit;
}
$this->_start_date = $start_date;
$this->_end_date = $end_date;
$this->_division_id = $division_id;
// Ambil nama divisi jika dipilih
if ($division_id > 0) {
$div = $this->_get_division($division_id);
$this->_division_name = $div ? $div['DivisionName'] : '';
} else {
$this->_division_name = 'Semua Divisi';
}
// -- Inisialisasi FPDF custom --------------------------------------
$this->_pdf = new PemakaianItemDivisiFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// -- Susun isi halaman ---------------------------------------------
$this->_pdf_header();
$this->_pdf_data($data);
$this->_pdf_terms();
// -- Output --------------------------------------------------------
$filename = 'PID_' . date('Ymd') . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data divisi
// =========================================================================
private function _get_division($id)
{
$sql = "
SELECT DivisionID, DivisionCode, DivisionName
FROM division
WHERE DivisionID = ?
AND DivisionIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
return $qry && $qry->num_rows() > 0 ? $qry->row_array() : null;
}
// =========================================================================
// DATABASE: Ambil data pemakaian item per divisi
// =========================================================================
private function _get_data($start_date, $end_date, $division_id = 0)
{
$sql_where = "
WHERE iu.ItemUsedIsActive = 'Y'
AND iu.ItemUsedIsConfirm = 'Y'
AND iu.ItemUsedDate BETWEEN ? AND ?
";
$params = array($start_date, $end_date);
if ($division_id > 0) {
// Filter by division melalui item_usage
$sql_where .= " AND iu.ItemUsedItemUsageID IN (
SELECT ItemUsageID FROM item_usage
WHERE ItemUsageDivisionID = ?
AND ItemUsageIsActive = 'Y'
)";
$params[] = $division_id;
}
$sql = "
SELECT
d.DivisionID,
d.DivisionCode,
d.DivisionName,
iu.ItemUsedID,
iu.ItemUsedDate,
iu.ItemUsedNumber,
iu.ItemUsedBatchNo,
iu.ItemUsedQty,
iu.ItemUsedPrice,
iu.ItemUsedTotal,
item.M_ItemID,
item.M_ItemCode,
item.M_ItemDesc,
iunit.ItemUnitName,
iunit.ItemUnitCode,
us.ItemUsageID,
us.ItemUsageDivisionID
FROM item_used iu
JOIN item_usage us ON us.ItemUsageID = iu.ItemUsedItemUsageID
AND us.ItemUsageIsActive = 'Y'
JOIN division d ON d.DivisionID = us.ItemUsageDivisionID
AND d.DivisionIsActive = 'Y'
JOIN m_item item ON item.M_ItemID = iu.ItemUsedM_ItemID
AND item.M_ItemIsActive = 'Y'
JOIN itemunit iunit ON iunit.ItemUnitID = iu.ItemUsedItemUnitID
AND iunit.ItemUnitIsActive = 'Y'
$sql_where
ORDER BY d.DivisionCode ASC, d.DivisionName ASC, iu.ItemUsedDate ASC, iu.ItemUsedNumber ASC
";
$qry = $this->db->query($sql, $params);
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Laporan
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// -- Judul utama -------------------------------------------------------
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'LAPORAN PEMAKAIAN ITEM PER DIVISI', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 30;
$valW = $halfW - $lblW - 4;
// -- Kolom Kiri --------------------------------------------------------
$pdf->SetY($startY);
$leftItems = array(
array('Periode Awal', $this->_fmt_date($this->_start_date), false),
array('Periode Akhir', $this->_fmt_date($this->_end_date), false),
array('Divisi', $this->_division_name, true),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
$leftY = $pdf->GetY();
// -- Kolom Kanan -------------------------------------------------------
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Print Oleh', $this->_username),
array('Tgl Print', $this->_pdf->printDate),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel pemakaian item per divisi
// =========================================================================
private function _pdf_data($data)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// -- Definisi kolom: [label, lebar, align] -----------------------------
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 8 + 68 + 15 + 15 + 22 + 22 + 22 = 180
$cols = array(
array('No', 6, 'C'),
array('Tgl', 16, 'C'),
array('Nama Item', 62, 'L'),
array('Batch No', 24, 'C'),
array('Unit', 14, 'C'),
array('Qty', 10, 'R'),
array('Harga', 18, 'R'),
array('Total', 30, 'R'),
);
$no = 1;
$prevDivID = null;
$divTotalQty = 0;
$divTotalVal = 0;
$grandTotalQty = 0;
$grandTotalVal = 0;
// Simpan posisi awal Y untuk setiap divisi — dipakai untuk bounding box
$divStartY = 0;
foreach ($data as $d) {
$divID = $d['DivisionID'];
// -- Jika berganti divisi, cetak subtotal divisi sebelumnya ----------
if ($prevDivID !== null && $divID !== $prevDivID) {
$this->_pdf_divisi_subtotal($divTotalQty, $divTotalVal);
$grandTotalQty += $divTotalQty;
$grandTotalVal += $divTotalVal;
$divTotalQty = 0;
$divTotalVal = 0;
}
// -- Jika divisi baru, cetak header nama divisi ---------------------
if ($prevDivID === null || $divID !== $prevDivID) {
// Cek sisa ruang: butuh minimal 8+7+6*n+8 ˜ 40mm untuk header+1 baris+subtotal
if ($pdf->GetY() > 230) {
$pdf->AddPage();
}
$divStartY = $pdf->GetY();
// Nama Divisi
$pdf->SetFillColor(50, 80, 130);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('Arial_Narrow', 'B', 10);
$pdf->Cell($pageW, 7, ' ' . $d['DivisionName'] . ' (' . $d['DivisionCode'] . ')', 1, 1, 'L', true);
$pdf->SetTextColor(0, 0, 0);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 7);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 6, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
$no = 1;
}
// -- Cek apakah perlu halaman baru (min 12mm untuk 1 baris + subtotal) -
if ($pdf->GetY() > 258) {
$pdf->AddPage();
// Ulang header kolom di halaman baru
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 7);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 6, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
}
// -- Baris data item -----------------------------------------------
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 7.5);
$pdf->SetFillColor(255, 255, 255);
$itemDesc = $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-');
$batchNo = $d['ItemUsedBatchNo'] ?: '-';
$qty = floatval($d['ItemUsedQty']);
$price = floatval($d['ItemUsedPrice']);
$total = floatval($d['ItemUsedTotal']);
// Simpan posisi Y sebelum cell untuk wrap detection
$beforeY = $pdf->GetY();
$pdf->Cell($cols[0][1], 5, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 5, $this->_fmt_date($d['ItemUsedDate']), 1, 0, 'C');
$pdf->Cell($cols[2][1], 5, $itemDesc, 1, 0, 'L');
$pdf->Cell($cols[3][1], 5, $batchNo, 1, 0, 'C');
$pdf->Cell($cols[4][1], 5, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[5][1], 5, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[6][1], 5, $this->_fmt_rp($price), 1, 0, 'R');
$pdf->Cell($cols[7][1], 5, $this->_fmt_rp($total), 1, 0, 'R');
$pdf->Ln();
$divTotalQty += $qty;
$divTotalVal += $total;
$prevDivID = $divID;
$no++;
}
// -- Subtotal divisi terakhir -----------------------------------------
if ($prevDivID !== null) {
$this->_pdf_divisi_subtotal($divTotalQty, $divTotalVal);
$grandTotalQty += $divTotalQty;
$grandTotalVal += $divTotalVal;
}
// -- Grand Total ------------------------------------------------------
$pdf->Ln(3);
// Cek ruang untuk grand total
if ($pdf->GetY() > 270) {
$pdf->AddPage();
}
$pdf->SetLineWidth(0.4);
$pdf->SetFont('Arial_Narrow', 'B', 10);
$pdf->SetFillColor(180, 200, 220);
$pdf->SetDrawColor(0, 0, 0);
$spanSum = array_sum(array_column(array_slice($cols, 0, 5), 1));
$pdf->Cell($spanSum, 7, 'GRAND TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[5][1], 7, $this->_fmt_qty($grandTotalQty), 1, 0, 'R', true);
$pdf->Cell($cols[6][1], 7, '', 1, 0, 'R', true);
$pdf->Cell($cols[7][1], 7, $this->_fmt_rp($grandTotalVal), 1, 0, 'R', true);
$pdf->Ln();
$pdf->Ln(4);
// -- Ringkasan Akhir --------------------------------------------------
$summaryX = 15 + $pageW - 90;
$cW1 = 50;
$cW2 = 40;
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->SetX($summaryX);
$pdf->Cell($cW1, 5, 'Total Item Digunakan', 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($cW2, 5, $this->_fmt_qty($grandTotalQty) . ' ' . 'pcs', 0, 1, 'R');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->SetX($summaryX);
$pdf->Cell($cW1, 5, 'Total Nilai Pemakaian', 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($cW2, 5, $this->_fmt_rp($grandTotalVal), 0, 1, 'R');
$pdf->Ln(4);
}
// =========================================================================
// PDF SUB-SECTION: Subtotal per Divisi
// =========================================================================
private function _pdf_divisi_subtotal($qty, $val)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// Gunakan lebar kolom yang sama persis dengan _pdf_data()
$cols = array(
array('No', 6, 'C'),
array('Tgl', 16, 'C'),
array('Nama Item', 62, 'L'),
array('Batch No', 24, 'C'),
array('Unit', 14, 'C'),
array('Qty', 10, 'R'),
array('Harga', 18, 'R'),
array('Total', 30, 'R'),
);
if ($pdf->GetY() > 270) {
$pdf->AddPage();
}
$spanSum = array_sum(array_column(array_slice($cols, 0, 5), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(235, 240, 248);
$pdf->SetDrawColor(0, 0, 0);
$pdf->Cell($spanSum, 6, 'Sub Total', 1, 0, 'R', true);
$pdf->Cell($cols[5][1], 6, $this->_fmt_qty($qty), 1, 0, 'R', true);
$pdf->Cell($cols[6][1], 6, '', 1, 0, 'R', true);
$pdf->Cell($cols[7][1], 6, $this->_fmt_rp($val), 1, 0, 'R', true);
$pdf->Ln();
$pdf->Ln(2);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Laporan ini menampilkan seluruh pemakaian item yang telah dikonfirmasi pada periode yang dipilih.',
'Data pemakaian dikelompokkan berdasarkan divisi yang melakukan pemakaian.',
'Harga yang ditampilkan adalah harga rata-rata pada saat pemakaian item.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan monitoring pemakaian item.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,427 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class MutasiPenerimaanFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// ── Posisi: 20mm dari bawah halaman ──────────────────────────────────
$this->SetY(-20);
// ── Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ───────────────
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// ── Baris 2: Tgl Print (kiri) ──────────────────────────────────────────
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_penerimaan_mutasi extends MY_Controller
{
// ── Properti bersama antar fungsi PDF ─────────────────────────────────────
/** @var MutasiPenerimaanFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Mutasi Penerimaan (Receive Mutasi) Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (MutasiHandoverID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// ── Ambil data header & detail dari DB ────────────────────────────
$header = $this->_get_header($id);
// Validasi: hanya dokumen berstatus Received yang bisa dicetak
if ($header['MutasiHandoverStatus'] !== 'Received') {
$this->sys_error("Dokumen belum diterima (Status: " . $header['MutasiHandoverStatus'] . ")");
exit;
}
$details = $this->_get_detail($id);
// ── Inisialisasi FPDF custom ──────────────────────────────────────
$this->_pdf = new MutasiPenerimaanFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// ── Susun isi halaman ─────────────────────────────────────────────
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_terms();
// ── Output ────────────────────────────────────────────────────────
$filename = 'PM_' . str_replace('/', '-', $header['MutasiHandoverNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
mh.*,
mr.MutasiRequestNumber,
ic.itemCategoryID,
ic.itemCategoryName,
a.M_BranchID AS branch_asal_id,
a.M_BranchCode AS branch_asal_code,
a.M_BranchName AS branch_asal_name,
b.M_BranchID AS branch_tujuan_id,
b.M_BranchCode AS branch_tujuan_code,
b.M_BranchName AS branch_tujuan_name,
IFNULL(u.M_UserUsername, '') AS CreatedByName,
IFNULL(ur.M_UserUsername, '') AS ReceiveByName,
CASE
WHEN wh.WarehouseType = 'B' THEN CONCAT(wh.WarehouseCode, ' ', wh.WarehouseName, ' - ', b.M_BranchName)
ELSE wh.WarehouseName
END AS WarehouseName,
r.M_RuanganName
FROM mutasi_handover mh
LEFT JOIN mutasi_request mr ON mr.MutasiRequestID = mh.MutasiHandoverMutasiRequestID
JOIN item_category ic ON ic.itemCategoryID = mh.MutasiHandoverItemCategoryID
AND ic.itemCategoryIsActive = 'Y'
JOIN m_branch a ON a.M_BranchID = mh.MutasiHandoverFromBranchID
AND a.M_BranchIsActive = 'Y'
JOIN m_branch b ON b.M_BranchID = mh.MutasiHandoverToBranchID
AND b.M_BranchIsActive = 'Y'
LEFT JOIN m_user u ON u.M_UserID = mh.MutasiHandoverUserID
LEFT JOIN m_user ur ON ur.M_UserID = mh.MutasiHandoverReceiveUserID
LEFT JOIN warehouse wh ON wh.WarehouseID = mh.MutasiHandoverReceiveWarehouseID
AND wh.WarehouseIsActive = 'Y'
LEFT JOIN m_ruangan r ON r.M_RuanganID = mh.MutasiHandoverReceiveM_RuanganID
AND r.M_RuanganIsActive = 'Y'
WHERE mh.MutasiHandoverID = ?
AND mh.MutasiHandoverIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Penerimaan Mutasi tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
mhd.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode,
st.StockStockNumber AS StockToNumber,
sf.StockStockNumber AS StockFromNumber
FROM mutasi_handover_detail mhd
LEFT JOIN m_item i ON i.M_ItemID = mhd.MutasiHandoverDetailM_ItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = mhd.MutasiHandoverDetailItemUnitID
LEFT JOIN stock sf ON sf.StockID = mhd.MutasiHandoverDetailStockID_From
LEFT JOIN stock st ON st.StockID = mhd.MutasiHandoverDetailStockID_To
WHERE mhd.MutasiHandoverDetailMutasiHandoverID = ?
AND mhd.MutasiHandoverDetailIsActive = 'Y'
ORDER BY mhd.MutasiHandoverDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Judul utama ───────────────────────────────────────────────────────
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'PENERIMAAN MUTASI', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 30;
$valW = $halfW - $lblW - 4;
// ── Kolom Kiri ────────────────────────────────────────────────────────
$pdf->SetY($startY);
$leftItems = array(
array('Nomor Handover', $header['MutasiHandoverNumber'], true),
array('No. Request', $header['MutasiRequestNumber'] ?: '-', false),
array('Tgl Kirim', $this->_fmt_date($header['MutasiHandoverDate']), false),
array('Tgl Terima', $this->_fmt_date($header['MutasiHandoverReceiveDate']), false),
array('Status', $header['MutasiHandoverStatus'], false),
array('Kategori', $header['itemCategoryName'], false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Keterangan (Note) langsung di bawah Kategori
if (!empty($header['MutasiHandoverNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->MultiCell($valW, 5, $header['MutasiHandoverNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// ── Kolom Kanan ───────────────────────────────────────────────────────
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Cabang Asal', $header['branch_asal_name']),
array('Cabang Tujuan', $header['branch_tujuan_name']),
array('Warehouse Tujuan', $header['WarehouseName'] ?: '-'),
array('Ruangan Tujuan', $header['M_RuanganName'] ?: '-'),
array('Diterima Oleh', $header['MutasiHandoverStatus'] === 'Received' ? $header['ReceiveByName'] : '-'),
array('Dibuat Oleh', $header['CreatedByName']),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item + ringkasan nilai
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Definisi kolom: [label, lebar, align] ─────────────────────────────
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 72 + 15 + 15 + 22 + 22 + 26 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama Item', 72, 'L'),
array('Unit', 15, 'C'),
array('Qty', 15, 'R'),
array('Nilai Buku', 22, 'R'),
array('Total', 22, 'R'),
array('Stock Tujuan', 26, 'C'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 7);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 7.5);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$total_val = 0;
foreach ($details as $d) {
$bookValue = floatval($d['MutasiHandoverDetailBookValue'] ?? 0);
$qty = floatval($d['MutasiHandoverDetailQty'] ?? 0);
$lineTotal = $qty * $bookValue;
$stockTo = $d['StockToNumber'] ?: '-';
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $this->_fmt_rp($lineTotal), 1, 0, 'R');
$pdf->Cell($cols[6][1], 6, $stockTo, 1, 0, 'C');
$pdf->Ln();
$total_val += $lineTotal;
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 6), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[6][1], 7, '', 1, 0, 'C', true);
$pdf->Ln();
// ── Ringkasan nilai (rata kanan) ──────────────────────────────────────
$pdf->Ln(3);
$summary = array(
array('Total Nilai Penerimaan', $this->_fmt_rp($header['MutasiHandoverTotalValue'])),
);
$cW1 = 50;
$cW2 = 40;
$offsetX = 15 + $this->_pageW - $cW1 - $cW2;
foreach ($summary as $s) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW1, 5, $s[0], 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->Cell($cW2, 5, $s[1], 0, 1, 'R');
}
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Penerimaan mutasi ini dilakukan berdasarkan dokumen handover (penyerahan) yang telah dikirim oleh cabang asal.',
'Penerima wajib memeriksa kesesuaian jumlah, jenis, dan kondisi barang dengan dokumen handover.',
'Setelah diterima, barang menjadi tanggung jawab cabang tujuan dan akan dicatat dalam stok tujuan.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,403 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class PengeluaranBarangFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// -- Posisi: 20mm dari bawah halaman ----------------------------------
$this->SetY(-20);
// -- Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ---------------
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// -- Baris 2: Tgl Print (kiri) ------------------------------------------
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_pengeluaran_barang extends MY_Controller
{
// -- Properti bersama antar fungsi PDF -------------------------------------
/** @var PengeluaranBarangFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Pengeluaran Barang Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (T_ItemOutID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// -- Ambil data header & detail dari DB ----------------------------
$header = $this->_get_header($id);
$details = $this->_get_detail($id);
// -- Inisialisasi FPDF custom --------------------------------------
$this->_pdf = new PengeluaranBarangFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// -- Susun isi halaman ---------------------------------------------
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_terms();
// -- Output --------------------------------------------------------
$filename = 'IO_' . str_replace('/', '-', $header['T_ItemOutNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
io.*,
IFNULL(u.M_UserUsername, '') AS CreatedByName,
IFNULL(ur.M_UserUsername, '') AS ReceiveByName,
IFNULL(ua.M_UserUsername, '') AS ApprovedByName,
wh.WarehouseCode,
wh.WarehouseName,
wh.WarehouseType,
d.DivisionCode,
d.DivisionName
FROM t_item_out io
LEFT JOIN m_user u ON u.M_UserID = io.T_ItemOutUserID
LEFT JOIN m_user ur ON ur.M_UserID = io.T_ItemOutReceiveUserID
LEFT JOIN m_user ua ON ua.M_UserID = io.T_ItemOutApproveID
LEFT JOIN warehouse wh ON wh.WarehouseID = io.T_ItemOutWarehouseID
AND wh.WarehouseIsActive = 'Y'
LEFT JOIN division d ON d.DivisionID = io.T_ItemOutDivisionID
AND d.DivisionIsActive = 'Y'
WHERE io.T_ItemOutID = ?
AND io.T_ItemOutIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Pengeluaran Barang tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
iod.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode,
rod.RequestItemOutDetailQty AS RequestQty,
rod.RequestItemOutDetailStatus AS RequestStatus
FROM t_item_out_detail iod
LEFT JOIN m_item i ON i.M_ItemID = iod.T_ItemOutDetailM_ItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = iod.T_ItemOutDetailItemUnitID
LEFT JOIN request_item_out_detail rod
ON rod.RequestItemOutDetailID = iod.T_ItemOutDetailRequestItemOutDetailID
AND rod.RequestItemOutDetailIsActive = 'Y'
WHERE iod.T_ItemOutDetailT_ItemOutID = ?
AND iod.T_ItemOutDetailIsActive = 'Y'
ORDER BY iod.T_ItemOutDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// -- Judul utama -------------------------------------------------------
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'PENGELUARAN BARANG (ITEM OUT)', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 30;
$valW = $halfW - $lblW - 4;
// -- Kolom Kiri --------------------------------------------------------
$pdf->SetY($startY);
$leftItems = array(
array('Nomor IO', $header['T_ItemOutNumber'], true),
array('Tanggal', $this->_fmt_date($header['T_ItemOutDate']), false),
array('Status', $header['T_ItemOutStatus'], false),
array('Confirm', $header['T_ItemOutIsConfirm'] === 'Y' ? 'Ya' : 'Tidak', false),
array('Divisi', $header['DivisionName'] ?: '-', false),
array('Gudang', $header['WarehouseName'] ?: '-', false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Keterangan (Note) langsung di bawah field terakhir
if (!empty($header['T_ItemOutNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->MultiCell($valW, 5, $header['T_ItemOutNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// -- Kolom Kanan -------------------------------------------------------
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Dibuat Oleh', $header['CreatedByName'] ?: '-'),
array('Dibuat Tgl', $this->_fmt_datetime($header['T_ItemOutCreated'])),
array('Penerima', $header['ReceiveByName'] ?: '-'),
array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'),
array('Tgl Approve', $header['T_ItemOutApproveDate'] ? $this->_fmt_datetime($header['T_ItemOutApproveDate']) : '-'),
array('Tgl Dikonfirmasi', $header['T_ItemOutIsConfirmDate'] ? $this->_fmt_datetime($header['T_ItemOutIsConfirmDate']) : '-'),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// -- Definisi kolom: [label, lebar, align] -----------------------------
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 72 + 20 + 20 + 30 + 30 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama Item', 72, 'L'),
array('Unit', 20, 'C'),
array('Qty', 20, 'R'),
array('Batch No.', 30, 'C'),
array('Qty Request', 30, 'R'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$total_qty = 0;
$total_req = 0;
foreach ($details as $d) {
$qty = floatval($d['T_ItemOutDetailQty'] ?? 0);
$qtyReq = floatval($d['T_ItemOutDetailRequestItemOutDetailID'] > 0 ? $qty : 0);
$batchNo = $d['T_ItemOutDetailItemBatchNo'] ?? '';
// Jika batch no berupa JSON array, decode untuk ditampilkan
$batchDisplay = $batchNo;
if (strpos($batchNo, '[') === 0) {
$decoded = json_decode($batchNo, true);
if (is_array($decoded) && count($decoded) > 0) {
$parts = array();
foreach ($decoded as $b) {
$parts[] = isset($b['batchNo']) ? $b['batchNo'] : (isset($b['BatchNo']) ? $b['BatchNo'] : (is_string($b) ? $b : '-'));
}
$batchDisplay = implode(', ', $parts);
}
}
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[4][1], 6, $batchDisplay, 1, 0, 'C');
$reqQty = isset($d['RequestQty']) ? floatval($d['RequestQty']) : 0;
$pdf->Cell($cols[5][1], 6, $reqQty > 0 ? $this->_fmt_qty($reqQty) : '-', 1, 0, 'R');
$pdf->Ln();
$total_qty += $qty;
if ($reqQty > 0) {
$total_req += $reqQty;
}
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 4), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[4][1], 7, '', 1, 0, 'C', true);
$pdf->Cell($cols[5][1], 7, $this->_fmt_qty($total_qty), 1, 0, 'R', true);
$pdf->Ln();
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Barang yang dikeluarkan harus sesuai dengan jumlah dan spesifikasi yang tercantum dalam dokumen ini.',
'Penerima barang wajib memeriksa dan memverifikasi kesesuaian barang sebelum menerima.',
'Dokumen ini merupakan bukti sah pengeluaran barang dari gudang.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,429 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class MutasiHandoverFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// ── Posisi: 20mm dari bawah halaman ──────────────────────────────────
$this->SetY(-20);
// ── Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ───────────────
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// ── Baris 2: Tgl Print (kiri) ──────────────────────────────────────────
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_penyerahan_mutasi extends MY_Controller
{
// ── Properti bersama antar fungsi PDF ─────────────────────────────────────
/** @var MutasiHandoverFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Mutasi Handover (Penyerahan Mutasi) Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (MutasiHandoverID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// ── Ambil data header & detail dari DB ────────────────────────────
$header = $this->_get_header($id);
$details = $this->_get_detail($id);
// ── Inisialisasi FPDF custom ──────────────────────────────────────
$this->_pdf = new MutasiHandoverFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// ── Susun isi halaman ─────────────────────────────────────────────
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_terms();
// ── Output ────────────────────────────────────────────────────────
$filename = 'PH_' . str_replace('/', '-', $header['MutasiHandoverNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
mh.*,
mr.MutasiRequestNumber,
ic.itemCategoryID,
ic.itemCategoryName,
a.M_BranchID AS branch_asal_id,
a.M_BranchCode AS branch_asal_code,
a.M_BranchName AS branch_asal_name,
b.M_BranchID AS branch_tujuan_id,
b.M_BranchCode AS branch_tujuan_code,
b.M_BranchName AS branch_tujuan_name,
IFNULL(u.M_UserUsername, '') AS CreatedByName,
IFNULL(ur.M_UserUsername, '') AS ReceiveByName,
CASE
WHEN wh.WarehouseType = 'B' THEN CONCAT(wh.WarehouseCode, ' ', wh.WarehouseName, ' - ', b.M_BranchName)
ELSE wh.WarehouseName
END AS WarehouseName,
r.M_RuanganName
FROM mutasi_handover mh
LEFT JOIN mutasi_request mr ON mr.MutasiRequestID = mh.MutasiHandoverMutasiRequestID
JOIN item_category ic ON ic.itemCategoryID = mh.MutasiHandoverItemCategoryID
AND ic.itemCategoryIsActive = 'Y'
JOIN m_branch a ON a.M_BranchID = mh.MutasiHandoverFromBranchID
AND a.M_BranchIsActive = 'Y'
JOIN m_branch b ON b.M_BranchID = mh.MutasiHandoverToBranchID
AND b.M_BranchIsActive = 'Y'
LEFT JOIN m_user u ON u.M_UserID = mh.MutasiHandoverUserID
LEFT JOIN m_user ur ON ur.M_UserID = mh.MutasiHandoverReceiveUserID
LEFT JOIN warehouse wh ON wh.WarehouseID = mh.MutasiHandoverReceiveWarehouseID
AND wh.WarehouseIsActive = 'Y'
LEFT JOIN m_ruangan r ON r.M_RuanganID = mh.MutasiHandoverReceiveM_RuanganID
AND r.M_RuanganIsActive = 'Y'
WHERE mh.MutasiHandoverID = ?
AND mh.MutasiHandoverIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Penyerahan Mutasi tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
mhd.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode
FROM mutasi_handover_detail mhd
LEFT JOIN m_item i ON i.M_ItemID = mhd.MutasiHandoverDetailM_ItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = mhd.MutasiHandoverDetailItemUnitID
WHERE mhd.MutasiHandoverDetailMutasiHandoverID = ?
AND mhd.MutasiHandoverDetailIsActive = 'Y'
ORDER BY mhd.MutasiHandoverDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Judul utama ───────────────────────────────────────────────────────
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'PENYERAHAN MUTASI (HANDOVER)', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 30;
$valW = $halfW - $lblW - 4;
// ── Kolom Kiri ────────────────────────────────────────────────────────
$pdf->SetY($startY);
$leftItems = array(
array('Nomor Handover', $header['MutasiHandoverNumber'], true),
array('No. Request', $header['MutasiRequestNumber'] ?: '-', false),
array('Tanggal', $this->_fmt_date($header['MutasiHandoverDate']), false),
array('Status', $header['MutasiHandoverStatus'], false),
array('Kategori', $header['itemCategoryName'], false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Keterangan (Note) langsung di bawah Status
if (!empty($header['MutasiHandoverNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->MultiCell($valW, 5, $header['MutasiHandoverNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// ── Kolom Kanan ───────────────────────────────────────────────────────
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Cabang Asal', $header['branch_asal_name']),
array('Cabang Tujuan', $header['branch_tujuan_name']),
array('Warehouse Tujuan', $header['WarehouseName'] ?: '-'),
array('Ruangan Tujuan', $header['M_RuanganName'] ?: '-'),
array('Dibuat Oleh', $header['CreatedByName']),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Tanggal terima di kolom kanan
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Tgl Terima', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $this->_fmt_date($header['MutasiHandoverReceiveDate']), 0, 1, 'L');
// Penerima di kolom kanan
if ($header['MutasiHandoverStatus'] === 'Received') {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Diterima Oleh', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $header['ReceiveByName'] ?: '-', 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item + ringkasan nilai
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Definisi kolom: [label, lebar, align] ─────────────────────────────
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 80 + 20 + 20 + 25 + 27 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama Item', 80, 'L'),
array('Unit', 20, 'C'),
array('Qty', 20, 'R'),
array('Nilai Buku', 25, 'R'),
array('Total', 27, 'R'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$total_val = 0;
foreach ($details as $d) {
$bookValue = floatval($d['MutasiHandoverDetailBookValue'] ?? 0);
$qty = floatval($d['MutasiHandoverDetailQty'] ?? 0);
$lineTotal = $qty * $bookValue;
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $this->_fmt_rp($lineTotal), 1, 0, 'R');
$pdf->Ln();
$total_val += $lineTotal;
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 5), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[5][1], 7, $this->_fmt_rp($total_val), 1, 0, 'R', true);
$pdf->Ln();
// ── Ringkasan nilai (rata kanan) ──────────────────────────────────────
$pdf->Ln(3);
$summary = array(
array('Total Nilai Penyerahan', $this->_fmt_rp($header['MutasiHandoverTotalValue'])),
);
$cW1 = 50;
$cW2 = 40;
$offsetX = 15 + $this->_pageW - $cW1 - $cW2;
foreach ($summary as $s) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW1, 5, $s[0], 0, 0, 'L');
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->Cell($cW2, 5, $s[1], 0, 1, 'R');
}
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Penyerahan mutasi ini dilakukan berdasarkan dokumen mutasi yang telah disetujui sebelumnya.',
'Barang yang diserahkan harus sesuai dengan jumlah dan spesifikasi yang tercantum dalam dokumen ini.',
'Penerima barang wajib memeriksa dan memverifikasi kesesuaian barang sebelum menerima.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,391 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class RequestMutasiFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// ── Posisi: 20mm dari bawah halaman ──────────────────────────────────
$this->SetY(-20);
// ── Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ───────────────
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// ── Baris 2: Tgl Print (kiri) ──────────────────────────────────────────
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_request_mutasi extends MY_Controller
{
// ── Properti bersama antar fungsi PDF ─────────────────────────────────────
/** @var RequestMutasiFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Request Mutasi Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (MutasiRequestID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// ── Ambil data header & detail dari DB ────────────────────────────
$header = $this->_get_header($id);
$details = $this->_get_detail($id);
// ── Inisialisasi FPDF custom ──────────────────────────────────────
$this->_pdf = new RequestMutasiFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// ── Susun isi halaman ─────────────────────────────────────────────
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_terms();
// ── Output ────────────────────────────────────────────────────────
$filename = 'RM_' . str_replace('/', '-', $header['MutasiRequestNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
mr.*,
ic.itemCategoryID,
ic.itemCategoryName,
a.M_BranchID AS branch_asal_id,
a.M_BranchCode AS branch_asal_code,
a.M_BranchName AS branch_asal_name,
b.M_BranchID AS branch_tujuan_id,
b.M_BranchCode AS branch_tujuan_code,
b.M_BranchName AS branch_tujuan_name,
IFNULL(u.M_UserUsername, '') AS CreatedByName,
IFNULL(uv.M_UserUsername, '') AS VerifByName,
IFNULL(ua.M_UserUsername, '') AS ApprovedByName
FROM mutasi_request mr
JOIN item_category ic ON ic.itemCategoryID = mr.MutasiRequestItemCategoryID
JOIN m_branch a ON a.M_BranchID = mr.MutasiRequestFromBranchID
JOIN m_branch b ON b.M_BranchID = mr.MutasiRequestToBranchID
LEFT JOIN m_user u ON u.M_UserID = mr.MutasiRequestUserID
LEFT JOIN m_user uv ON uv.M_UserID = mr.MutasiRequestVerifUserID
LEFT JOIN m_user ua ON ua.M_UserID = mr.MutasiRequestApprovedUserID
WHERE mr.MutasiRequestID = ?
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Request Mutasi tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
mrd.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode
FROM mutasi_request_detail mrd
LEFT JOIN m_item i ON i.M_ItemID = mrd.MutasiRequestDetailM_ItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = mrd.MutasiRequestDetailItemUnitID
WHERE mrd.MutasiRequestDetailMutasiRequestID = ?
AND mrd.MutasiRequestDetailIsActive = 'Y'
ORDER BY mrd.MutasiRequestDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Judul utama ───────────────────────────────────────────────────────
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'REQUEST MUTASI REPORT (RM)', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 30;
$valW = $halfW - $lblW - 4;
// ── Kolom Kiri ────────────────────────────────────────────────────────
$pdf->SetY($startY);
$leftItems = array(
array('Nomor', $header['MutasiRequestNumber'], true),
array('Tanggal', $this->_fmt_date($header['MutasiRequestDate']), false),
array('Status', $header['MutasiRequestStatus'], false),
array('Kategori', $header['itemCategoryName'], false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Keterangan (Note) langsung di bawah Status
if (!empty($header['MutasiRequestNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->MultiCell($valW, 5, $header['MutasiRequestNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// ── Kolom Kanan ───────────────────────────────────────────────────────
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Cabang Asal', $header['branch_asal_name']),
array('Cabang Tujuan', $header['branch_tujuan_name']),
array('Dibuat Oleh', $header['CreatedByName']),
array('Diverifikasi Oleh', $header['VerifByName'] ?: '-'),
array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Tanggal approvals di kolom kanan
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Tgl Verifikasi', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $this->_fmt_datetime($header['MutasiRequestVerifDate']), 0, 1, 'L');
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Tgl Approved', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $this->_fmt_datetime($header['MutasiRequestApprovedDate']), 0, 1, 'L');
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item + ringkasan nilai
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Definisi kolom: [label, lebar, align] ─────────────────────────────
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 80 + 20 + 20 + 25 + 27 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama Item', 80, 'L'),
array('Unit', 20, 'C'),
array('Qty', 20, 'R'),
array('Nilai Buku', 25, 'R'),
array('Total', 27, 'R'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$total_val = 0;
foreach ($details as $d) {
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($d['MutasiRequestDetailQty']), 1, 0, 'R');
$bookValue = floatval($d['MutasiRequestDetailBookValue'] ?? 0);
$pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $this->_fmt_rp($d['MutasiRequestDetailQty'] * $bookValue), 1, 0, 'R');
$pdf->Ln();
$total_val += floatval($d['MutasiRequestDetailQty'] * $bookValue);
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 5), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[5][1], 7, $this->_fmt_rp($total_val), 1, 0, 'R', true);
$pdf->Ln();
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Mutasi barang ini harus mendapatkan persetujuan dari pejabat berwenang sebelum proses mutasi dilakukan.',
'Barang yang dimutasi harus sesuai dengan spesifikasi dan jumlah yang tercantum dalam dokumen ini.',
'Proses mutasi dilakukan setelah barang diverifikasi dan disetujui oleh pihak terkait.',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}

View File

@@ -1,410 +0,0 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class RequestPengeluaranBarangFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // margin 15+15
// -- Posisi: 20mm dari bawah halaman ----------------------------------
$this->SetY(-20);
// -- Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ---------------
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// -- Baris 2: Tgl Print (kiri) ------------------------------------------
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_request_pengeluaran_barang extends MY_Controller
{
// -- Properti bersama antar fungsi PDF -------------------------------------
/** @var RequestPengeluaranBarangFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Request Pengeluaran Barang Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (RequestItemOutID), username (opsional)
// =========================================================================
public function pdf()
{
try {
$id = intval($this->input->get_post('id'));
$username = trim($this->input->get_post('username') ?? '');
if ($id <= 0) {
$this->sys_error("ID tidak valid");
exit;
}
// -- Ambil data header & detail dari DB ----------------------------
$header = $this->_get_header($id);
$details = $this->_get_detail($id);
// -- Inisialisasi FPDF custom --------------------------------------
$this->_pdf = new RequestPengeluaranBarangFpdf('P', 'mm', 'A4');
$this->_pdf->printUsername = $username !== '' ? $username : '-';
$this->_pdf->printDate = date('d-m-Y H:i:s');
$this->_pageW = $this->_pdf->GetPageWidth() - 30;
$this->_header_data = $header;
$this->_username = $this->_pdf->printUsername;
$this->_pdf->AliasNbPages(); // aktifkan alias total halaman
$this->_pdf->SetMargins(15, 15, 15);
$this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah
$this->_pdf->AddPage();
// -- Susun isi halaman ---------------------------------------------
$this->_pdf_header();
$this->_pdf_data($details);
$this->_pdf_terms();
// -- Output --------------------------------------------------------
$filename = 'RIO_' . str_replace('/', '-', $header['RequestItemOutNumber']) . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $this->_pdf->Output('S');
} catch (Exception $exc) {
$this->sys_error($exc->getMessage());
}
}
// =========================================================================
// DATABASE: Ambil data header
// =========================================================================
private function _get_header($id)
{
$sql = "
SELECT
rio.*,
wh.WarehouseCode,
wh.WarehouseName,
wh.WarehouseType,
r.S_RegionalName,
d.DivisionCode,
d.DivisionName,
b.M_BranchName AS BranchName,
IFNULL(u.M_UserUsername, '') AS CreatedByName,
IFNULL(um.M_UserUsername, '') AS ManagerByName,
IFNULL(ua.M_UserUsername, '') AS ApprovedByName,
IFNULL(uv.M_UserUsername, '') AS VerifiedByName
FROM request_item_out rio
LEFT JOIN warehouse wh ON wh.WarehouseID = rio.RequestItemOutWarehouseID
AND wh.WarehouseIsActive = 'Y'
LEFT JOIN s_regional r ON r.S_RegionalID = rio.RequestItemOutS_RegionalID
AND r.S_RegionalIsActive = 'Y'
LEFT JOIN m_branch b ON b.M_BranchCode = rio.RequestItemOutM_BranchCode
AND b.M_BranchIsActive = 'Y'
LEFT JOIN division d ON d.DivisionID = rio.RequestItemOutDivisionID
AND d.DivisionIsActive = 'Y'
LEFT JOIN m_user u ON u.M_UserID = rio.RequestItemOutUserID
LEFT JOIN m_user um ON um.M_UserID = rio.RequestItemOutApprovedManagerUserID
LEFT JOIN m_user ua ON ua.M_UserID = rio.RequestItemOutApprovedUserID
LEFT JOIN m_user uv ON uv.M_UserID = rio.RequestItemOutVerifiedUserID
WHERE rio.RequestItemOutID = ?
AND rio.RequestItemOutIsActive = 'Y'
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Request Pengeluaran Barang tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
rod.*,
i.M_ItemCode,
i.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode
FROM request_item_out_detail rod
LEFT JOIN m_item i ON i.M_ItemID = rod.RequestItemOutDetailM_ItemID
LEFT JOIN itemunit iu ON iu.ItemUnitID = rod.RequestItemOutDetailItemUnitID
WHERE rod.RequestItemOutDetailRequestItemOutID = ?
AND rod.RequestItemOutDetailIsActive = 'Y'
ORDER BY rod.RequestItemOutDetailID ASC
";
$qry = $this->db->query($sql, array($id));
return $qry ? $qry->result_array() : array();
}
// =========================================================================
// PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom)
// =========================================================================
private function _pdf_header()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// -- Judul utama --------------------------------------------------------
$pdf->SetFont('Arial_Narrow', 'B', 14);
$pdf->Cell($pageW, 8, 'REQUEST PENGELUARAN BARANG', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 32;
$valW = $halfW - $lblW - 4;
// -- Kolom Kiri -----------------------------------------------------------
$pdf->SetY($startY);
$leftItems = array(
array('Nomor Request', $header['RequestItemOutNumber'], true),
array('Tanggal', $this->_fmt_date($header['RequestItemOutDate']), false),
array('Status', $header['RequestItemOutStatus'], false),
array('Divisi', $header['DivisionName'] ?: '-', false),
array('Gudang', $header['WarehouseName'] ?: '-', false),
array('Regional', $header['S_RegionalName'] ?: '-', false),
);
foreach ($leftItems as $item) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
if ($item[2]) {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$pdf->SetFont('Arial_Narrow', '', 9);
}
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Keterangan (Note)
if (!empty($header['RequestItemOutNote'])) {
$pdf->SetX(15);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->MultiCell($valW, 5, $header['RequestItemOutNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// -- Kolom Kanan ----------------------------------------------------------
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Cabang', $header['BranchName'] ?: '-'),
array('Dibuat Oleh', $header['CreatedByName'] ?: '-'),
array('Dibuat Tgl', $this->_fmt_datetime($header['RequestItemOutCreated'])),
array('Approve Manager', $header['ManagerByName'] ?: '-'),
array('Tgl Appr. Manager',$header['RequestItemOutApprovedManagerDate'] ? $this->_fmt_datetime($header['RequestItemOutApprovedManagerDate']) : '-'),
array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'),
array('Tgl Disetujui', $header['RequestItemOutApprovedDate'] ? $this->_fmt_datetime($header['RequestItemOutApprovedDate']) : '-'),
);
foreach ($rightItems as $item) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, $item[0], 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
// Verifikasi (hanya tampil jika ada)
if (!empty($header['RequestItemOutVerifiedDate'])) {
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Diverifikasi Oleh', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $header['VerifiedByName'] ?: '-', 0, 1, 'L');
$pdf->SetX($rightX);
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($lblW, 5, 'Tgl Verifikasi', 0, 0, 'L');
$pdf->Cell(4, 5, ':', 0, 0, 'C');
$pdf->SetFont('Arial_Narrow', '', 9);
$pdf->Cell($valW, 5, $this->_fmt_datetime($header['RequestItemOutVerifiedDate']), 0, 1, 'L');
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
// -- Definisi kolom: [label, lebar, align] -------------------------------
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 72 + 20 + 25 + 25 + 30 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama Item', 72, 'L'),
array('Unit', 20, 'C'),
array('Qty Request', 25, 'R'),
array('Qty Diterima', 25, 'R'),
array('Status', 30, 'C'),
);
// Header kolom
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$total_qty = 0;
$total_recv = 0;
foreach ($details as $d) {
$qty = floatval($d['RequestItemOutDetailQty'] ?? 0);
$recvQty = floatval($d['RequestItemOutDetailReceiveQty'] ?? 0);
$status = $d['RequestItemOutDetailStatus'] ?? '-';
$pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C');
$pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L');
$pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R');
$pdf->Cell($cols[4][1], 6, $this->_fmt_qty($recvQty), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $status, 1, 0, 'C');
$pdf->Ln();
$total_qty += $qty;
$total_recv += $recvQty;
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 3), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[3][1], 7, $this->_fmt_qty($total_qty), 1, 0, 'R', true);
$pdf->Cell($cols[4][1], 7, $this->_fmt_qty($total_recv), 1, 0, 'R', true);
$pdf->Cell($cols[5][1], 7, '', 1, 0, 'C', true);
$pdf->Ln();
$pdf->Ln(4);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Request ini harus disetujui oleh atasan (manager) sebelum diproses lebih lanjut.',
'Barang yang diminta akan dikeluarkan sesuai dengan jumlah yang telah disetujui.',
'Apabila jumlah barang tidak tersedia, maka akan dilakukan pengeluaran sebagian (partial).',
'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.',
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}