isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $regID = $prm["S_RegionalID"]; $sql = "SELECT M_BranchID, M_BranchCode, M_BranchName FROM m_branch WHERE M_BranchIsActive = 'Y' AND M_BranchS_RegionalID = ?"; $qry = $this->db->query($sql, [$regID]); if (!$qry) { $this->sys_error_db("Failed to get Branches from RegionalID: {$regID}", $this->db); exit; } $rst = $qry->result_array(); $this->sys_ok(["records" => $rst]); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function getStatus() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); } $rst = [ ["value" => 0, "title" => "All"], ["value" => 1, "title" => "Draft"], ["value" => 2, "title" => "Pending"], ["value" => 3, "title" => "Approved"], ["value" => 4, "title" => "Rejected"], ["value" => 5, "title" => "Paid"], ["value" => 6, "title" => "Completed"] ]; $this->sys_ok(["records" => $rst]); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); exit; } } public function search() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $payload = $this->sys_input; $regionalID = $this->sys_user["S_RegionalID"]; $startDate = $payload["startDate"]; $endDate = $payload["endDate"]; $search = $payload['search']; if ($search == "") { $search = "%"; } $status = $payload['StatusName']; if (strtolower($status) == "all") { $status = "%"; } $cabangcode = $payload["M_BranchCode"]; if (strtolower($cabangcode) == "all") { $cabangcode = "%"; } $offset = 0; $limit = 10; if ($payload['currentPage'] > 0) { $offset = ($payload['currentPage'] - 1) * $limit; } $sql = "SELECT t_goods_transfer.*, M_BranchID, M_BranchCode, M_BranchName FROM t_goods_transfer JOIN m_branch ON T_GoodsTransferToM_BranchCode = M_BranchCode AND M_BranchIsActive = 'Y' WHERE T_GoodsTransferIsActive = 'Y' AND T_GoodsTransferFromS_RegionalID = ? AND T_GoodsTransferNum LIKE ? AND T_GoodsTransferToM_BranchCode LIKE ? AND T_GoodsTransferType = 'P' AND T_GoodsTransferStatus LIKE ? AND T_GoodsTransferDate BETWEEN DATE(?) AND DATE(?) ORDER BY T_GoodsTransferDate DESC"; $sqldata = $sql . " LIMIT ? OFFSET ?"; $quedata = $this->db->query($sqldata, [ $regionalID, $search, $cabangcode, $status, $startDate, $endDate, $limit, $offset ]); if (!$quedata) { $this->sys_error_db("[Error] get data"); exit; } $data = $quedata->result_array(); $sqltotal = "SELECT COUNT(*) AS total FROM ($sql) AS x"; $quetotal = $this->db->query($sqltotal, [ $regionalID, $search, $cabangcode, $status, $startDate, $endDate ]); if (!$quetotal) { $this->sys_error_db("[Error] get total data"); exit; } $total = $quetotal->result_array()[0]['total']; $result = array( "records" => $data, "total" => $total ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function getUserLevel() { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $userId = $this->sys_user["M_UserID"]; $sqlevel = "SELECT * FROM m_user WHERE M_UserIsActive = 'Y' AND M_UserID = ?"; $qulevel = $this->db->query($sqlevel, [$userId]); if (!$qulevel) { $this->sys_error_db("[Error] get approval level user"); exit; } $data = $qulevel->row_array(); $this->sys_ok($data); } public function createTfRequest() { try { if (!$this->isLogin) { throw new Exception("invalid token"); } $user = $this->sys_user; $userID = $this->sys_user["M_UserID"]; $data = $this->sys_input; // Check for debug mode $isDebug = isset($data['isDebug']) && $data['isDebug'] === true; if ($isDebug) { $this->db->trans_start(true); // Auto rollback mode } else { $this->db->trans_start(); } // $sqlnum = "SELECT `fn_numbering`('TR') AS GoodsTfNum"; // $quenum = $this->db->query($sqlnum, []); // if (!$quenum) { // throw new Exception("[Error] Failed to generate transfer request number"); // } // $goodsTfNum = $quenum->row()->GoodsTfNum; $areaid = $user['M_BranchID']; $areatype = 'B'; if ($user['loginLevel'] == 'regional') { $areaid = $user['S_RegionalID']; $areatype = 'R'; } $sqlusrdivisi = "SELECT M_UserDivisionDivisionID FROM m_userdivision WHERE M_UserDivisionM_UserID = ? AND M_UserDivisionIsActive = 'Y'"; $queusrdivisi = $this->db->query($sqlusrdivisi, [$user['M_UserID']]); if (!$queusrdivisi) { $this->db->trans_rollback(); $this->sys_error_db("[Error] get user divisi", $this->db); exit; } $userDivID = $queusrdivisi->row_array()['M_UserDivisionDivisionID']; $sqlnum = "SELECT `fn_penomoran`(?, ?, ?, ?, ?, ?) AS numpd;"; $quenum = $this->db->query($sqlnum, ['ST', $userDivID, $areatype, $areaid, 'SM', 'Y']); if (!$quenum) { throw new Exception("[Error] Failed generate number for TF request. " . json_encode($this->db->error())); } $goodsTfNum = $quenum->row_array()['numpd']; $sqlTG = "INSERT INTO t_goods_transfer ( T_GoodsTransferFromS_RegionalID, T_GoodsTransferToM_BranchCode, T_GoodsTransferNum, T_GoodsTransferNotes, T_GoodsTransferDate, T_GoodsTransferType, T_GoodsTransferStatus, T_GoodsTransferIsActive, T_GoodsTransferCreatedAt, T_GoodsTransferM_UserID ) VALUES (?,?,?,?,?,?,?,'Y',NOW(),?)"; $queTG = $this->db->query($sqlTG, [ $data["S_RegionalID"], $data["M_BranchCode"], $goodsTfNum, $data["GoodsTfNotes"], $data["GoodsTfDate"], 'P', $data["GoodsTfStatus"], $userID ]); if (!$queTG) { throw new Exception("[Error] Failed to insert into table t_goods_transfer. " . json_encode($this->db->error())); } $tGoodsTFID = $this->db->insert_id(); $datadetail = $data['detail']; foreach ($datadetail as $key => $value) { $flagID = $value['PurchaseRequestFlagID']; $itemID = $value['PurchaseRequestDetailM_ItemID']; $unitID = $value['PurchaseRequestDetailItemUnitID']; $QtyStart = intval($value['PurchaseRequestFlagQty']); $QtyTfRes = intval($value['PurchaseRequestFlagQtyRest']); // encode json list batch number $jsonBatchNo = "[]"; if (isset($value['ItemRequest']) && is_array($value['ItemRequest'])) { $batches = $value['ItemRequest']; if (sizeof($batches) > 0) { $jsonResult = json_encode($batches); if ($jsonResult === false) { throw new Exception("[Error] Failed to encode json batch number"); } else { $jsonBatchNo = $jsonResult; } } } $sqlTGD = "INSERT INTO t_goods_transfer_detail ( T_GoodsTransferDetailIDT_GoodsTransferID, T_GoodsTransferDetailT_PurchaseRequestFlagID, T_GoodsTransferDetailM_ItemID, T_GoodsTransferDetailItemUnitID, T_GoodsTransferDetailItemBatchNo, T_GoodsTransferDetailQty, T_GoodsTransferDetailQtyTF, T_GoodsTransferDetailCreatedAt, T_GoodsTransferDetailM_UserID ) VALUES (?,?,?,?,?,?,?,NOW(),?)"; $queTGD = $this->db->query($sqlTGD, [ $tGoodsTFID, $flagID, $itemID, $unitID, $jsonBatchNo, $QtyStart, $QtyTfRes, $userID ]); if (!$queTGD) { throw new Exception("[Error] Failed to insert TF goods detail. " . json_encode($this->db->error())); } $TGoodsTFDetailID = $this->db->insert_id(); $datadetail[$key]['TGoodsTFDetailID'] = $TGoodsTFDetailID; // jika status verified if ($data["GoodsTfStatus"] == 'Verified') { $this->moveStockToTransit($value, $data["S_RegionalID"], $userID, $TGoodsTFDetailID); $sqlUpdFlag = "UPDATE purchase_request_flag SET PurchaseRequestFlagQtyRest = ?, PurchaseRequestFlagQtyProses = PurchaseRequestFlagQtyProses + ? WHERE PurchaseRequestFlagID = ? AND PurchaseRequestFlagIsActive = 'Y'"; $queUpdFlag = $this->db->query($sqlUpdFlag, [$QtyTfRes, $QtyTfRes, $flagID]); if (!$queUpdFlag) { throw new Exception("[Error] Failed to update quantity in table purchase_request_flag. " . json_encode($this->db->error())); } } } // get last inserted data $sqltfgood = "SELECT * FROM t_goods_transfer WHERE T_GoodsTransferIsActive = 'Y' AND T_GoodsTransferID = ?"; $quetfgood = $this->db->query($sqltfgood, [$tGoodsTFID]); if (!$quetfgood) { throw new Exception("[Error] Failed to get last inserted transfer data. " . json_encode($this->db->error())); } $lastTFData = $quetfgood->row_array(); $sqltfdetail = "SELECT * FROM t_goods_transfer_detail WHERE T_GoodsTransferDetailIsActive = 'Y' AND T_GoodsTransferDetailIDT_GoodsTransferID = ?"; $quetfdetail = $this->db->query($sqltfdetail, [$tGoodsTFID]); if (!$quetfdetail) { throw new Exception("[Error] Failed to get last inserted transfer detail data. " . json_encode($this->db->error())); } $lastTFDetail = $quetfdetail->result_array(); $lastTFData['detail'] = $lastTFDetail; $msg = "Transfer Request nomor: {$lastTFData['T_GoodsTransferNum']}, tanggal: {$lastTFData['T_GoodsTransferDate']}, type: {$lastTFData['T_GoodsTransferType']}"; $this->insertUserActivty($tGoodsTFID, "CREATE", $userID, $msg, json_encode($lastTFData)); $this->db->trans_complete(); $result = array( "total" => 1, "msg" => $msg, "records" => $lastTFData ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->db->trans_rollback(); $this->sys_error($message); } } public function updateTfRequest() { try { if (!$this->isLogin) { throw new Exception("Invalid Token"); } $user = $this->sys_user; $userID = $this->sys_user["M_UserID"]; $data = $this->sys_input; $this->db->trans_begin(); $sqlTG = "UPDATE t_goods_transfer SET T_GoodsTransferFromS_RegionalID = ?, T_GoodsTransferToM_BranchCode = ?, T_GoodsTransferNum = ?, T_GoodsTransferNotes = ?, T_GoodsTransferDate = ?, T_GoodsTransferStatus = ?, T_GoodsTransferLastUpdated = NOW(), T_GoodsTransferM_UserID = ? WHERE T_GoodsTransferID = ? AND T_GoodsTransferIsActive = 'Y'"; $queTG = $this->db->query($sqlTG, [ $data["S_RegionalID"], $data["M_BranchCode"], $data["GoodsTfNum"], $data["GoodsTfNotes"], $data["GoodsTfDate"], $data["GoodsTfStatus"], $userID, $data["GoodsTfID"] ]); if (!$queTG) { throw new Exception("[Error] Failed to update table t_goods_transfer. " . json_encode($this->db->error())); } // approve or verif tf request if ($data["GoodsTfStatus"] == 'Verified' || $data["GoodsTfStatus"] == 'Approved') { $this->toVerifOrApprove($data["GoodsTfID"], $user); } $sqlUpdToN = "UPDATE t_goods_transfer_detail SET T_GoodsTransferDetailIsActive = 'N' WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ?"; $queUpdToN = $this->db->query($sqlUpdToN, [$data["GoodsTfID"]]); if (!$queUpdToN) { throw new Exception("[Error] Failed to update all detail records to inactive. " . json_encode($this->db->error())); } $datadetail = $data['detail']; foreach ($datadetail as $key => $value) { $flagID = $value['PurchaseRequestFlagID']; $itemID = $value['PurchaseRequestDetailM_ItemID']; $unitID = $value['PurchaseRequestDetailItemUnitID']; $QtyStart = intval($value['PurchaseRequestFlagQty']); $QtyTfRes = intval($value['PurchaseRequestFlagQtyRest']); // encode json list batch number $jsonBatchNo = "[]"; if (isset($value['ItemRequest']) && is_array($value['ItemRequest'])) { $batches = $value['ItemRequest']; if (sizeof($batches) > 0) { $jsonResult = json_encode($batches); if ($jsonResult === false) { throw new Exception("[Error] Failed to encode json batch number"); } else { $jsonBatchNo = $jsonResult; } } } $sqlTGD = "INSERT INTO t_goods_transfer_detail ( T_GoodsTransferDetailIDT_GoodsTransferID, T_GoodsTransferDetailT_PurchaseRequestFlagID, T_GoodsTransferDetailM_ItemID, T_GoodsTransferDetailItemUnitID, T_GoodsTransferDetailItemBatchNo, T_GoodsTransferDetailQty, T_GoodsTransferDetailQtyTF, T_GoodsTransferDetailCreatedAt, T_GoodsTransferDetailM_UserID ) VALUES (?,?,?,?,?,?,?,NOW(),?)"; $queTGD = $this->db->query($sqlTGD, [ $data["GoodsTfID"], $flagID, $itemID, $unitID, $jsonBatchNo, $QtyStart, $QtyTfRes, $userID ]); if (!$queTGD) { throw new Exception("[Error] Failed to insert TF goods detail. " . json_encode($this->db->error())); } $TGoodsTFDetailID = $this->db->insert_id(); $datadetail[$key]['TGoodsTFDetailID'] = $TGoodsTFDetailID; // jika status verified if ($data["GoodsTfStatus"] == 'Verified') { $this->moveStockToTransit($value, $data["S_RegionalID"], $userID, $TGoodsTFDetailID); $sqlUpdFlag = "UPDATE purchase_request_flag SET PurchaseRequestFlagQtyRest = ?, PurchaseRequestFlagQtyProses = PurchaseRequestFlagQtyProses + ? WHERE PurchaseRequestFlagID = ? AND PurchaseRequestFlagIsActive = 'Y'"; $queUpdFlag = $this->db->query($sqlUpdFlag, [$QtyTfRes, $QtyTfRes, $flagID]); if (!$queUpdFlag) { throw new Exception("[Error] Failed to update quantity in table purchase_request_flag. " . json_encode($this->db->error())); } } } // get last inserted data $sqltfgood = "SELECT * FROM t_goods_transfer WHERE T_GoodsTransferIsActive = 'Y' AND T_GoodsTransferID = ?"; $quetfgood = $this->db->query($sqltfgood, [$data["GoodsTfID"]]); if (!$quetfgood) { throw new Exception("[Error] Failed to get updated transfer data. " . json_encode($this->db->error())); } $lastTFData = $quetfgood->row_array(); $sqltfdetail = "SELECT * FROM t_goods_transfer_detail WHERE T_GoodsTransferDetailIsActive = 'Y' AND T_GoodsTransferDetailIDT_GoodsTransferID = ?"; $quetfdetail = $this->db->query($sqltfdetail, [$data["GoodsTfID"]]); if (!$quetfdetail) { throw new Exception("[Error] Failed to get updated transfer detail data. " . json_encode($this->db->error())); } $lastTFDetail = $quetfdetail->result_array(); $lastTFData['detail'] = $lastTFDetail; $msg = "Transfer Request nomor: {$lastTFData['T_GoodsTransferNum']}, tanggal: {$lastTFData['T_GoodsTransferDate']}, type: {$lastTFData['T_GoodsTransferType']}"; $this->insertUserActivty($data["GoodsTfID"], "UPDATE", $userID, $msg, json_encode($lastTFData)); $this->db->trans_commit(); $result = array( "total" => 1, "msg" => $msg, "records" => $lastTFData ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->db->trans_rollback(); $this->sys_error($message); } } /** * * Pindah Stock ke WH Transit jika Verified * @param array $detailTf - The detail of the transfer with ItemRequest info * @param int $regionalID - The regional ID to find the appropriate transit warehouse * @param int $userID - The user ID performing the operation * @param int $TGoodsTFDetailID - The ID of the transfer detail * @return void * @throws Exception if any validation or database operation fails */ private function moveStockToTransit($detailTf, $regionalID, $userID,$TGoodsTFDetailID){ //* Validasi StockQty bisa memenuhi foreach ($detailTf['ItemRequest'] as $itemRequest) { $stockID = $itemRequest['StockID']; $qtyReq = intval($itemRequest['QtyReq']); $sqlCheck = "SELECT StockQty FROM stock WHERE StockID = ?"; $qryCheck = $this->db->query($sqlCheck, [$stockID]); if (!$qryCheck || $qryCheck->num_rows() === 0) { $err = json_encode($this->db->error()); throw new Exception($err); } if($qryCheck->row()->StockQty < $qtyReq){ $sqlItem = "SELECT M_ItemDesc, ItemUnitName FROM stock JOIN m_item ON M_ItemID = StockItemID JOIN itemunit ON ItemUnitID = StockItemUnitID WHERE StockID = ?"; $qryItem = $this->db->query($sqlItem, [$stockID]); $itemInfo = $qryItem->row_array(); throw new Exception("Err: Perhatikan Qty Stock dan Request. Tidak bisa verifikasi karena Stock Item {$itemInfo['M_ItemDesc']} tersisa " . ($qryCheck ? $qryCheck->row()->StockQty : 0) . " {$itemInfo['ItemUnitName']}, sedangkan request {$qtyReq} {$itemInfo['ItemUnitName']}"); } } //* Query Warehouse Transit where WHRegionalID == $data[S_RegionalID] $sqlWh = "SELECT WarehouseID FROM warehouse WHERE WarehouseS_RegionalID = ? AND WarehouseIsTransit = 'Y' AND WarehouseIsActive = 'Y' AND WarehouseType = 'R' ORDER BY WarehouseID DESC LIMIT 1"; $qryWh = $this->db->query($sqlWh, [$regionalID]); if (!$qryWh || $qryWh->num_rows() === 0) { throw new Exception("Transit warehouse not found for Regional ID: {$regionalID}"); } $transitWhId = $qryWh->row()->WarehouseID; foreach($detailTf['ItemRequest'] as $itemReq){ $stockID = $itemReq['StockID']; $qtyReq = intval($itemReq['QtyReq']); $stockNumber = $itemReq['StockStockNumber']; $batchNo = $itemReq['StockBatchNo']; $itemID = $itemReq['StockItemID']; $unitID = $itemReq['StockItemUnitID']; $itemPrice = $itemReq['StockItemPrice']; $expDate = $itemReq['StockED']; $warehouseID = $itemReq['WarehouseID']; $lastSourceStockQty = $itemReq['StockQty']; //* Update Kurangi Existing Stock Qty $sqlUpdt = "UPDATE stock SET StockQty = StockQty - ?, StockLastUpdated = NOW(), StockUserID = ? WHERE StockID = ?"; $qryUpdt = $this->db->query($sqlUpdt, [$qtyReq, $userID, $stockID]); if (!$qryUpdt) { throw new Exception("Failed to update source stock quantity (ID: {$stockID})"); } //* Create / Update Transit Stock $sqlCekTransit = "SELECT StockID FROM stock WHERE StockWarehouseID = ? AND StockItemID = ? AND StockItemUnitID = ? AND StockBatchNo = ? ORDER BY StockID DESC LIMIT 1"; $qryCekTransit = $this->db->query($sqlCekTransit, [ $transitWhId, $itemID, $unitID, $batchNo ]); if (!$qryCekTransit) { $err = json_encode($this->db->error()); throw new Exception("Failed to cek stock on warehouseId (ID: {$transitWhId}). DB Error: $err"); } // *Insert or Update Row Stock Warehouse Transit if ($qryCekTransit->num_rows() > 0) { $transitStockID = $qryCekTransit->row()->StockID; $sqlUpdTransit = "UPDATE stock SET StockQty = StockQty + ?, StockLastUpdated = NOW(), StockUserID = ? WHERE StockID = ?"; $qryUpdTransit = $this->db->query($sqlUpdTransit, [ $qtyReq, $userID, $transitStockID ]); if (!$qryUpdTransit) { throw new Exception("Failed to update transit stock quantity (ID: {$transitStockID})"); } } else { $sqlInsTransit = "INSERT INTO stock ( StockWarehouseID, StockItemID, StockItemUnitID, StockBatchNo, StockQty, StockStockNumber, StockItemPrice, StockED, StockLastUpdated, StockUserID ) VALUES (?, ?, ?, ?, ?, fn_numbering('SN'), ?, ?, NOW(), ?)"; $qryInsTransit = $this->db->query($sqlInsTransit, [ $transitWhId, $itemID, $unitID, $batchNo, $qtyReq, $itemPrice, $expDate, $userID ]); if (!$qryInsTransit) { throw new Exception("Failed to insert new transit stock for Item ID: {$itemID}"); } $transitStockID = $this->db->insert_id(); } //* Log Stock Movement (WTT - Warehouse To Transit) // Log hanya dari sumber ke transit $sqlLog = "INSERT INTO stocklog ( StockLogWarehouseID, StockLogDateTime, StockLogItemID, StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo, StockLogED, StockLogReffID, StockLogStatus, StockLogQty, StockLogUserID) VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, 'WTT', ?, ?)"; $qryLog = $this->db->query($sqlLog, [ $warehouseID, $itemID, $unitID, $stockNumber, $batchNo, $expDate, $TGoodsTFDetailID, $qtyReq, $userID ]); if (!$qryLog) { throw new Exception("Failed to log source warehouse movement"); } // * Query Stockcard where StockCardReffID == $stockID ORDER BY StockCardID DESC LIMIT 1 // * Insert Stockcard WH Sumber ke Transit (OUT) // StockCardBefore = Previous StockCardAfter, StockCardIn=0, StockCardOut= qtyReg, StockCardAfter = PreviousCardAfter - qtyReq $sqlLastSourceCard = "SELECT StockCardAfter FROM stockcard WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1"; $qryLastSourceCard = $this->db->query($sqlLastSourceCard, [$stockID]); if (!$qryLastSourceCard) { throw new Exception("Failed to query last stockcard entry for source stock ID: {$stockID}"); } $beforeQty = 0; if ($qryLastSourceCard->num_rows() > 0) { $beforeQty = $qryLastSourceCard->row()->StockCardAfter; } else { $beforeQty = $lastSourceStockQty; } $afterQty = $beforeQty - $qtyReq; // Insert Stockcard for source warehouse (OUT) $sqlInsSourceCard = "INSERT INTO stockcard ( StockCardWarehouseID, StockCardDatetime, StockCardItemID, StockCardItemUnitID, StockCardBatchNo, StockCardED, StockCardReffID, StockCardStatus, StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, StockCardUserID ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'MWT', ?, 0, ?, ?, ?)"; $qryInsSourceCard = $this->db->query($sqlInsSourceCard, [ $warehouseID, $itemID, $unitID, $batchNo, $expDate, $stockID, $beforeQty, $qtyReq, $afterQty, $userID ]); if (!$qryInsSourceCard) { throw new Exception("Failed to insert stockcard for source warehouse ID: {$warehouseID}"); } // * Query Stockcard where StockCardReffID == $transitStockID ORDER BY StockCardID DESC LIMIT 1 // * Insert Stockcard WH Transit (IN) // New row with StockReffID = $transitStockID, StockCardIn = qtyReq, StockCardOut = 0, StockCardAfter = PreviousCardAfter + qtyReq $sqlLastTransitCard = "SELECT StockCardAfter FROM stockcard WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1"; $qryLastTransitCard = $this->db->query($sqlLastTransitCard, [$transitStockID]); if (!$qryLastTransitCard) { throw new Exception("Failed to query last stockcard entry for transit stock ID: {$transitStockID}"); } $transitBeforeQty = 0; if ($qryLastTransitCard->num_rows() > 0) { $transitBeforeQty = $qryLastTransitCard->row()->StockCardAfter; } else { $sqlTransitQty = "SELECT StockQty FROM stock WHERE StockID = ?"; $qryTransitQty = $this->db->query($sqlTransitQty, [$transitStockID]); if (!$qryTransitQty || $qryTransitQty->num_rows() === 0) { throw new Exception("Failed to get current stock quantity for transit stock ID: {$transitStockID}"); } $transitBeforeQty = $qryTransitQty->row()->StockQty - $qtyReq; // Subtract qtyReq since we've already added it } $transitAfterQty = $transitBeforeQty + $qtyReq; // Insert Stockcard for transit warehouse (IN) $sqlInsTransitCard = "INSERT INTO stockcard ( StockCardWarehouseID, StockCardDatetime, StockCardItemID, StockCardItemUnitID, StockCardBatchNo, StockCardED, StockCardReffID, StockCardStatus, StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, StockCardUserID ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'MWF', ?, ?, 0, ?, ?)"; $qryInsTransitCard = $this->db->query($sqlInsTransitCard, [ $transitWhId, $itemID, $unitID, $batchNo, $expDate, $transitStockID, $transitBeforeQty, $qtyReq, $transitAfterQty, $userID ]); if (!$qryInsTransitCard) { throw new Exception("Failed to insert stockcard for transit warehouse ID: {$transitWhId}"); } } // Return true to indicate success return true; } public function deleteTfRequest() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); } $userID = $this->sys_user["M_UserID"]; $goodsTfID = $this->sys_input["GoodsTfID"]; $this->db->trans_begin(); $sql = "UPDATE t_goods_transfer SET T_GoodsTransferIsActive = 'N', T_GoodsTransferDeleted = NOW(), T_GoodsTransferM_UserID = ? WHERE T_GoodsTransferID = ? AND T_GoodsTransferIsActive = 'Y'"; $qry = $this->db->query($sql, [$userID, $goodsTfID]); if (!$qry) { $this->db->trans_rollback(); $this->sys_error_db("Gagal menghapus Transfer Request (ID: {$goodsTfID}). Error:", $this->db); exit; } $sqldet = "UPDATE t_goods_transfer_detail SET T_GoodsTransferDetailIsActive = 'N', T_GoodsTransferDetailDeletedAt = NOW(), T_GoodsTransferDetailM_UserID = ? WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ? AND T_GoodsTransferDetailIsActive = 'Y' "; $qdet = $this->db->query($sqldet, [$userID, $goodsTfID]); if (!$qdet) { $this->db->trans_rollback(); $this->sys_error_db("failed delete detail transfer request item. Error:", $this->db); exit; } $sql_json = "SELECT t_goods_transfer.*, '' as details FROM t_goods_transfer WHERE T_GoodsTransferID = ?"; $qry_json = $this->db->query($sql_json, [$goodsTfID]); if (!$qry_json) { $this->db->trans_rollback(); $this->sys_error_db("select json tf error"); exit; } $row_json = $qry_json->row_array(); $descActivity = "Hapus transfer request {$row_json['T_GoodsTransferNum']}"; $sql_json_detail = "SELECT t_goods_transfer_detail.* FROM t_goods_transfer_detail WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ?"; $qry_json_detail = $this->db->query($sql_json_detail, [$goodsTfID]); if ($qry_json_detail) { $row_json["details"] = $qry_json_detail->result_array(); } else { $this->sys_error_db("select detail tf error", $this->db); exit; } $data_json = $row_json; $this->insertUserActivty($goodsTfID, 'DELETE', $userID, $descActivity, json_encode($data_json)); $this->db->trans_commit(); $this->sys_ok("Berhasil menghapus Transfer Request (ID: {$goodsTfID})"); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function getListDetail() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $regionalid = $prm['regionalid']; $branchIDOrigin = $prm['branchIDorigin']; $branchCodeDestin = $prm['branchcodedestin']; $currpage = $prm['currpage']; $page = 0; $limit = 5; if ($currpage > 0) { $page = ($currpage - 1) * $limit; } // Get total count (unchanged) $sqltotal = "SELECT COUNT(DISTINCT PurchaseRequestDetailID) as total FROM purchase_request_flag JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID AND PurchaseRequestDetailIsActive = 'Y' JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID AND PurchaseRequestIsActive = 'Y' JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID AND M_ItemIsActive = 'Y' AND M_ItemItem_CategoryID = 1 WHERE PurchaseRequestFlagIsActive = 'Y' AND FIND_IN_SET('TF', PurchaseRequestFlagStatus) AND PurchaseRequestDetailStatus = 'Read' AND PurchaseRequestFlagIsClosed = 'N' AND PurchaseRequestS_RegionalID = ? AND PurchaseRequestM_BranchCode = ? AND NOT EXISTS ( SELECT 1 FROM t_goods_transfer JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID AND T_GoodsTransferDetailIsActive = 'Y' WHERE T_GoodsTransferDetailT_PurchaseRequestFlagID = PurchaseRequestFlagID AND T_GoodsTransferFromS_RegionalID = ? AND T_GoodsTransferToM_BranchCode = ? )"; $quetotal = $this->db->query($sqltotal, [ $regionalid, $branchCodeDestin, $regionalid, $branchCodeDestin ]); if (!$quetotal) { $this->sys_error_db("error get total listing tf request"); exit; } $total = $quetotal->result_array()[0]['total']; if (intval($total) === 0) { $this->sys_ok(["records" => [], "total" => $total]); exit; } // Simplified main query - removing stock-related JOINs $sqlist = "SELECT PurchaseRequestDate, PurchaseRequestNumber, PurchaseRequestDetailM_ItemID, PurchaseRequestDetailItemUnitID, M_ItemDesc, unitreq.ItemUnitID as RequestItemUnitID, unitreq.ItemUnitName as RequestItemUnitName, PurchaseRequestFlagID, PurchaseRequestFlagQty, PurchaseRequestFlagQtyRest, PurchaseRequestFlagStatus FROM purchase_request_flag JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID AND PurchaseRequestDetailIsActive = 'Y' JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID AND PurchaseRequestIsActive = 'Y' JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID AND M_ItemIsActive = 'Y' AND M_ItemItem_CategoryID = 1 JOIN itemunit unitreq ON unitreq.ItemUnitID = PurchaseRequestDetailItemUnitID AND unitreq.ItemUnitIsActive = 'Y' WHERE PurchaseRequestFlagIsActive = 'Y' AND FIND_IN_SET('TF', PurchaseRequestFlagStatus) AND PurchaseRequestDetailStatus = 'Read' AND PurchaseRequestFlagIsClosed = 'N' AND PurchaseRequestS_RegionalID = ? AND PurchaseRequestM_BranchCode = ? AND NOT EXISTS ( SELECT 1 FROM t_goods_transfer JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID AND T_GoodsTransferDetailIsActive = 'Y' WHERE T_GoodsTransferDetailT_PurchaseRequestFlagID = PurchaseRequestFlagID AND T_GoodsTransferFromS_RegionalID = ? AND T_GoodsTransferToM_BranchCode = ? ) ORDER BY PurchaseRequestDate ASC, PurchaseRequestNumber ASC LIMIT ? OFFSET ?"; $quelist = $this->db->query($sqlist, [ $regionalid, $branchCodeDestin, $regionalid, $branchCodeDestin, $limit, $page ]); if (!$quelist) { $this->sys_error_db("error get listing detail request transfer"); exit; } $rows = $quelist->result_array(); foreach ($rows as $key => $value) { $itemID = $value['PurchaseRequestDetailM_ItemID']; $reqUnitID = $value['RequestItemUnitID']; $reqUnitName = $value['RequestItemUnitName']; // Query semua stock yang tersedia di Gudang $stockGudang = $this->getStockForItemRequested($itemID, $regionalid); $rows[$key]['StockGudang'] = $stockGudang; // Cek dulu apakah salah satu UnitStock ada yang == UnitReq $hasDirectMatch = $this->checkStockReqUnitMatch($stockGudang, $reqUnitID); $unpackData = $this->checkConversionPossibility($stockGudang, $reqUnitID, $reqUnitName, $itemID); $hasUnpackOption = $unpackData['UnpackData']['CanUnpack'] ?? false; $rows[$key]['CanDirectUse'] = $hasDirectMatch; $rows[$key]['CanUnpack'] = $hasUnpackOption; $rows[$key]['UnpackData'] = $unpackData['UnpackData']; $rows[$key]['ItemRequest'] = []; } $result = array( "records" => $rows, "total" => $total ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } /** * Ambil semua stock untuk ItemID di gudang regionalId meskipun beda batch dan UnitItem * @param : int ItemID, int RegionalID * @return : array of objects */ private function getStockForItemRequested($itemID, $regionalID) { $sqlStock = "SELECT stock.StockID, stock.StockItemUnitID, itemunit.ItemUnitName as StockItemUnitName, stock.StockQty, stock.StockBatchNo, stock.StockED, stock.StockStockNumber, stock.StockItemPrice FROM warehouse JOIN stock ON StockWarehouseID = WarehouseID AND stock.StockQty > 0 JOIN itemunit ON ItemUnitID = stock.StockItemUnitID AND itemunit.ItemUnitIsActive = 'Y' WHERE WarehouseS_RegionalID = ? AND WarehouseIsTransit = 'N' AND WarehouseType = 'R' AND WarehouseIsActive = 'Y' AND stock.StockItemID = ? ORDER BY stock.StockED ASC, stock.StockQty DESC"; $qryStock = $this->db->query($sqlStock, [$regionalID, $itemID]); if (!$qryStock) { return []; } return $qryStock->result_array(); } /** * Cek apakah ada Stock memiliki ItemUnit yang sama dengan Unit Request * @param : array($stockGudang), int(reqUnitID) * @return : boolean, true jika ada */ private function checkStockReqUnitMatch($stockGudang, $reqUnitID){ foreach ($stockGudang as $stock) { if ($stock['StockItemUnitID'] == $reqUnitID) { return true; } } return false; } /** * Cek semua kemungkinan Konversi Unit * Misal: UnitReq = BUAH, tapi di stock ada LUSIN, dan KODI. Maka akan ada beberapa ConversionOptions * @param : array($stockGudang), int($reqUnitID), string($reqUnitName), int($itemID) * @return : object */ private function checkConversionPossibility($stockGudang, $reqUnitID, $reqUnitName, $itemID) { $conversionOptions = []; $uniqueCombinations = []; foreach ($stockGudang as $stock) { $stockUnitID = $stock['StockItemUnitID']; $stockUnitName = $stock['StockItemUnitName']; if ($stockUnitID != $reqUnitID) { $sqlConv = "SELECT * FROM unitconvert WHERE UnitConvertFromItemUnitID = ? AND UnitConvertToItemUnitID = ? AND UnitConvertIsActive = 'Y' LIMIT 1"; $qryConv = $this->db->query($sqlConv, [$stockUnitID, $reqUnitID]); $conversionKey = $stockUnitID . '_' . $reqUnitID; // Hanya tambah ke conversionOptions jika FromItemUnitID dan ToItemUnitID berbeda dengan existing if ($qryConv && $qryConv->num_rows() > 0 && !in_array($conversionKey, $uniqueCombinations)) { $conversionData = $qryConv->row(); $conversionOptions[] = [ "FromItemUnitID" => $stockUnitID, "FromItemUnitName" => $stockUnitName, "ToItemUnitID" => $reqUnitID, "ToItemUnitName" => $reqUnitName, "UnitConvertAmount" => $conversionData->UnitConvertAmount ]; $uniqueCombinations[] = $conversionKey; } } } if (empty($conversionOptions)) { return [ 'MustUnpack' => true, 'UnpackData' => [ "M_ItemID" => $itemID, "CanUnpack" => false, "ReasonCannotUnpack"=> "Tidak ditemukan konversi dari unit stock yang tersedia ke unit request {$reqUnitName}" ] ]; } else { return [ 'MustUnpack' => true, 'UnpackData' => [ "M_ItemID" => $itemID, "CanUnpack" => true, "ConversionOptions" => $conversionOptions ] ]; } } public function getDraftDetail() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $regID = $this->sys_user["S_RegionalID"]; $goodTfID = $prm['goodTfID']; $sql = "SELECT PurchaseRequestDate, PurchaseRequestNumber, PurchaseRequestDetailM_ItemID, PurchaseRequestDetailItemUnitID, M_ItemDesc, ItemUnitName, COALESCE(SUM(CASE WHEN WarehouseID IS NOT NULL AND StockItemId IS NOT NULL THEN StockQty ELSE 0 END), 0) as StockGudang, PurchaseRequestFlagID, T_GoodsTransferDetailQty as PurchaseRequestFlagQty, T_GoodsTransferDetailQtyTF as PurchaseRequestFlagQtyRest, T_GoodsTransferDetailItemBatchNo, PurchaseRequestFlagStatus, T_GoodsTransferVerifiedBy, T_GoodsTransferVerifiedDate FROM t_goods_transfer LEFT JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID AND T_GoodsTransferDetailIsActive = 'Y' JOIN purchase_request_flag ON PurchaseRequestFlagID = T_GoodsTransferDetailT_PurchaseRequestFlagID AND PurchaseRequestFlagIsActive = 'Y' JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID AND PurchaseRequestDetailIsActive = 'Y' JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID AND PurchaseRequestIsActive = 'Y' JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID AND M_ItemItem_CategoryID = 1 AND M_ItemIsActive = 'Y' JOIN itemunit ON ItemUnitID = PurchaseRequestDetailItemUnitID AND ItemUnitIsActive = 'Y' JOIN m_branch ON M_BranchCode = PurchaseRequestM_BranchCode AND M_BranchIsActive = 'Y' JOIN warehouse ON WarehouseS_RegionalID = PurchaseRequestS_RegionalID AND WarehouseIsTransit = 'N' AND WarehouseM_BranchID = 0 AND WarehouseIsActive = 'Y' LEFT JOIN stock ON StockItemId = M_ItemID AND StockWarehouseID = WarehouseID WHERE PurchaseRequestFlagIsActive = 'Y' AND T_GoodsTransferID = ? AND T_GoodsTransferIsActive = 'Y' GROUP BY PurchaseRequestNumber, PurchaseRequestFlagID"; $qry = $this->db->query($sql, [$goodTfID]); if (!$qry) { $this->sys_error_db("error get data draft detail transfer"); exit; } $detail = $qry->result_array(); foreach ($detail as $key => $value) { $jsonStr = $value['T_GoodsTransferDetailItemBatchNo']; $batches = []; if (!empty($jsonStr)) { $decode = json_decode($jsonStr); if ($decode !== null && json_last_error() === JSON_ERROR_NONE) { $batches = $decode; } else { $this->sys_error_db("[Error] decode json for batches number"); exit; } } $detail[$key]['StockGudang'] = []; // Foreach batches update StockQty dengan Query Baru foreach($batches as &$batch){ $sqlCekStock = "SELECT stock.StockID, stock.StockItemUnitID, itemunit.ItemUnitName as StockItemUnitName, stock.StockQty, stock.StockBatchNo, stock.StockED, stock.StockStockNumber, stock.StockItemPrice FROM stock JOIN itemunit ON ItemUnitID = stock.StockItemUnitID AND itemunit.ItemUnitIsActive = 'Y' WHERE StockID = ?"; $qryCekStock = $this->db->query($sqlCekStock, [$batch->StockID]); if(!$qryCekStock){ $this->sys_error_db("[Error] Query StockQty yang terbaru untuk ItemRequest"); exit; } $batch->StockQty = $qryCekStock->row()->StockQty; $detail[$key]['StockGudang'][] = $qryCekStock->row(); } unset($batch); $detail[$key]['ItemRequest'] = $batches; unset($detail[$key]['T_GoodsTransferDetailItemBatchNo']); } $result = array( "records" => $detail, "total" => sizeof($detail) ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function processTransfer() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $userID = $this->sys_user["M_UserID"]; $data = $this->sys_input; $tfdetail = $data['detail']; foreach ($tfdetail as $key => $value) { $reqFlagID = $data['PurchaseRequestFlagID']; $reqTFQty = $data['PurchaseRequestFlagQtyRest']; $sqlgetproc = "SELECT SELECT PurchaseRequestFlagQtyProses FROM purchase_request_flag WHERE PurchaseRequestFlagID = ? AND PurchaseRequestFlagIsActive = 'Y'"; $qrygetproc = $this->db->query($sqlgetproc, [$reqFlagID]); if (!$qrygetproc) { $this->sys_error_db("error get current purchase request flag process quantity"); exit; } $qtyProc = $qrygetproc->result_array()[0]['PurchaseRequestFlagQtyProses']; $this->db->trans_begin(); $procQtyUpdt = intval($qtyProc) + intval($reqTFQty); $sqlupdtproc = "UPDATE purchase_request_flag SET PurchaseRequestFlagQtyProses = ?, PurchaseRequestFlagLastUpdated = NOW(), PurchaseRequestFlagUserID = ? WHERE PurchaseRequestFlagID = ? AND PurchaseRequestFlagIsActive = 'Y'"; $qryupdtproc = $this->db->query($sqlupdtproc, [$procQtyUpdt, $userID, $reqFlagID]); if (!$qryupdtproc) { $this->db->trans_rollback(); $this->sys_error_db('error update flag update process qty'); exit; } } $this->db->trans_commit(); $this->sys_ok("Berhasil mengupdate Qty process (ID: {$data['GoodsTfID']})"); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function getItemBatchListing() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $batchNo = "%"; if ($prm['batchno'] != '') { $batchNo = $prm['batchno'] . "%"; } $sqlbatch = "SELECT WarehouseID, WarehouseName, StockID, StockStockNumber, StockItemID, StockItemUnitID, ItemUnitName, StockItemPrice, StockBatchNo, StockED, StockQty FROM warehouse JOIN stock ON StockWarehouseID = WarehouseID AND WarehouseIsActive = 'Y' AND WarehouseIsTransit = 'N' JOIN itemunit ON ItemUnitID = StockItemUnitID AND ItemUnitIsActive= 'Y' WHERE WarehouseS_RegionalID = ? AND WarehouseM_BranchID = 0 AND StockItemID = ? AND StockItemUnitID = ? AND StockBatchNo LIKE ?"; $quebatch = $this->db->query($sqlbatch, [ $prm['regionalid'], $prm['itemid'], $prm['unitid'], $batchNo ]); if (!$quebatch) { $this->sys_error_db("error get data draft detail transfer"); exit; } $batch = $quebatch->result_array(); $result = array( 'records' => $batch, 'total' => sizeof($batch) ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function getUnpackItemBatchListing() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $batchNo = "%"; if ($prm['batchno'] != '') { $batchNo = $prm['batchno'] . "%"; } $sqlbatch = "SELECT WarehouseID, WarehouseName, StockID, StockStockNumber, StockItemID, StockItemUnitID, ItemUnitName, StockItemPrice, StockBatchNo, StockED, StockQty FROM warehouse JOIN stock ON StockWarehouseID = WarehouseID AND WarehouseIsActive = 'Y' AND WarehouseIsTransit = 'N' JOIN itemunit ON ItemUnitID = StockItemUnitID AND ItemUnitIsActive= 'Y' WHERE WarehouseS_RegionalID = ? AND WarehouseM_BranchID = 0 AND StockItemID = ? AND StockItemUnitID IN ? -- Bedanya dengan yang getItemBatchListing AND StockBatchNo LIKE ?"; $quebatch = $this->db->query($sqlbatch, [ $prm['regionalid'], $prm['itemid'], $prm['unitid'], $batchNo ]); if (!$quebatch) { $this->sys_error_db("error get data draft detail transfer"); exit; } $batch = $quebatch->result_array(); $result = array( 'records' => $batch, 'total' => sizeof($batch) ); $this->sys_ok($result); } catch (Exception $exc) { $message = $exc->getMessage(); $this->sys_error($message); } } public function unpackingStock() { try { if (!$this->isLogin) { $this->sys_error("Invalid Token"); exit; } $prm = $this->sys_input; $stockNo = $prm['StockStockNumber']; $stockBatchNo = $prm['StockBatchNo']; $unpackQty = intval($prm['UnpackQty']); // Qty yang mau di unpack dalam Unit Stock $toItemUnitID = $prm['ToItemUnitID']; $convertAmount = $prm['UnitConvertAmount']; if($prm['isDebug'] == true){ $this->db->trans_start(true); } else { $this->db->trans_start(); } /* *== Input Validation ==* */ // UnitConvertAmount can't 0 if($convertAmount <= 0){ throw new Exception("UnitConvertAmount tidak boleh <= 0"); } // Query Stock by StockNumber and BatchNo $sqlCekStock = "SELECT stock.*, ItemUnitName as StockUnitName FROM stock JOIN itemunit ON ItemUnitID = StockItemUnitID WHERE StockStockNumber = ? AND StockBatchNo = ? "; $qryCekStock = $this->db->query($sqlCekStock, [$stockNo, $stockBatchNo]); if(!$qryCekStock || $qryCekStock->num_rows() == 0){ $error = json_encode($this->db->error(), JSON_PRETTY_PRINT); throw new Exception("Error Query DB $error atau Tidak Ditemukan Stock dengan StockNumber {$stockNo} dan Batch $stockBatchNo"); } // Take the StockID $rstCek = $qryCekStock->row(); $stockID = $rstCek->StockID; $stockQty = $rstCek->StockQty; $stockUnitName = $rstCek->StockUnitName; // Validate StockQty >= UnpackQty if ($stockQty < $unpackQty){ throw new Exception("Unpack Qty tidak boleh melebih Stock Qty"); } $qryLogs = []; // Update StockQty = StockQty - UnpackQty $newStockQty = $stockQty - $unpackQty; $sqlUp = "UPDATE stock SET StockQty = ? WHERE StockID = ?"; $qryUp = $this->db->query($sqlUp, [$newStockQty, $stockID]); if(!$qryUp){ throw new Exception("Failed to update StockQty"); }else{ $qryLogs["update_qty_unpack"]="Berhasil existing stock dari $stockQty $stockUnitName to {$newStockQty} {$stockUnitName}"; $sql = "SELECT stock.*, ItemUnitName as StockUnitName FROM stock JOIN itemunit ON ItemUnitID = StockItemUnitID WHERE StockID = ?"; $qry = $this->db->query($sql, [$stockID]); $updatedStock = $qry->row(); } // Insert Log, with StockLogStatus = UP_OUT $sqlUpLog = "INSERT INTO stocklog ( StockLogWarehouseID, StockLogDateTime, StockLogItemID, StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo, StockLogED, StockLogStatus, StockLogQty, StockLogUserID ) VALUES (?,NOW(),?,?,?,?,?,?,?,?)"; $qryUpLog = $this->db->query($sqlUpLog, [ $rstCek->StockWarehouseID, $rstCek->StockItemID, $rstCek->StockItemUnitID, $rstCek->StockStockNumber, $rstCek->StockBatchNo, $rstCek->StockED, 'UP_OUT', $unpackQty, // OUT sesuai UnpackQty $this->sys_user['M_UserID'] ]); if(!$qryUpLog){ throw new Exception("Failed to insert stocklog for UP_OUT"); }else{ $qryLogs["insert_log_up_out"] = "Berhasil stocklog UP_OUT {$rstCek->StockQty} {$stockUnitName} for StockNumber {$rstCek->StockStockNumber}"; } $sqlLastSourceCard = "SELECT StockCardAfter FROM stockcard WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1"; $qryLastSourceCard = $this->db->query($sqlLastSourceCard, [$stockID]); if (!$qryLastSourceCard) { throw new Exception("Failed to query last stockcard entry for source StockID: {$stockID}"); } $beforeQty = 0; if ($qryLastSourceCard->num_rows() > 0) { $beforeQty = $qryLastSourceCard->row()->StockCardAfter; } else { $beforeQty = $stockQty; } $afterQty = $beforeQty - $unpackQty; // Insert Stockcard for source stock (OUT) $sqlInsSourceCard = "INSERT INTO stockcard ( StockCardWarehouseID, StockCardDatetime, StockCardItemID, StockCardItemUnitID, StockCardBatchNo, StockCardED, StockCardReffID, StockCardStatus, StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, StockCardUserID ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'UP_OUT', ?, 0, ?, ?, ?)"; $qryInsSourceCard = $this->db->query($sqlInsSourceCard, [ $rstCek->StockWarehouseID, $rstCek->StockItemID, $rstCek->StockItemUnitID, $rstCek->StockBatchNo, $rstCek->StockED, $stockID, $beforeQty, $unpackQty, $afterQty, $this->sys_user['M_UserID'] ]); if (!$qryInsSourceCard) { throw new Exception("Failed to insert stockcard for source stock (UP_OUT)"); }else{ $qryLogs["insert_card_up_out"] = `Berhasil stockcard UP_OUT pada StockCardID: {$this->db->insert_id()}dengan StockCardReffID: {$stockID}`; } // Insert new row stock hasil unpack $sqlIn = "INSERT INTO stock ( StockWarehouseAlmariID, StockWarehouseRackID, StockWarehouseID,StockStockNumber, StockBatchNo,StockItemID,StockItemUnitID, StockED,StockItemPrice, StockQty,StockLastUpdated,StockUserID ) VALUES (0, 0, ?, fn_numbering('SN'), ?, ?, ?, ?, ?, ?, NOW(), ?) ON DUPLICATE KEY UPDATE StockItemPrice = VALUES(StockItemPrice), StockQty = StockQty + VALUES(StockQty), StockLastUpdated = NOW(), StockUserID = VALUES(StockUserID);"; $qtyConverted = $unpackQty * $convertAmount; $priceConverted = $rstCek->StockItemPrice / $convertAmount; $qryIn = $this->db->query($sqlIn, [ $rstCek->StockWarehouseID, $rstCek->StockBatchNo, $rstCek->StockItemID, $toItemUnitID, $rstCek->StockED, $priceConverted, $qtyConverted, $this->sys_user['M_UserID'] ]); if(!$qryIn){ $error = json_encode($this->db->error(), JSON_PRETTY_PRINT); throw new Exception("Failed to insert new stock for unpacking. DB Error: $error"); } // Query Stock Hasil Unpack yang Baru untuk data Stocklog dan Stockcard $sql = "SELECT stock.*, ItemUnitName as StockUnitName FROM stock JOIN itemunit ON ItemUnitID = StockItemUnitID WHERE StockWarehouseID = ? AND StockItemID = ? AND StockItemUnitID = ? AND StockBatchNo = ? AND StockED = ?"; $qry = $this->db->query($sql, [ $rstCek->StockWarehouseID, $rstCek->StockItemID, $toItemUnitID, $rstCek->StockBatchNo, $rstCek->StockED ]); if(!$qry){ $err = json_encode($this->db->error(), JSON_PRETTY_PRINT); throw new Exception("Error select row stock hasil unpacking"); } $unpackResultStock = $qry->row(); $qryLogs["insert_new_stock"] = "Berhasil insert Stock No. {$unpackResultStock->StockStockNumber} after unpacking Stock No. {$rstCek->StockStockNumber} dari {$unpackQty} {$stockUnitName} ke {$qtyConverted} {$unpackResultStock->StockUnitName}"; // Log hasil konversi with StockLogStatus = UP_IN $sqlUpLog = "INSERT INTO stocklog ( StockLogWarehouseID, StockLogDateTime, StockLogItemID, StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo, StockLogED, StockLogStatus, StockLogQty, StockLogUserID ) VALUES (?,NOW(),?,?,?,?,?,?,?,?)"; $qryUpLog = $this->db->query($sqlUpLog, [ $unpackResultStock->StockWarehouseID, $unpackResultStock->StockItemID, $unpackResultStock->StockItemUnitID, $unpackResultStock->StockStockNumber, $unpackResultStock->StockBatchNo, $unpackResultStock->StockED, 'UP_IN', $qtyConverted, // IN sesuai stock hasil konversi jangan ambil StockQty jaga2 kalau StockQty=StcokQty+convertedQty $this->sys_user['M_UserID'] ]); if(!$qryUpLog){ throw new Exception("Failed to insert stocklog for UP_IN"); } else{ $qryLogs["insert_log_up_in"] = "Berhasil stocklog UP_IN {$qtyConverted} {$unpackResultStock->StockUnitName}"; } $sqlLastRstCard = "SELECT StockCardAfter FROM stockcard WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1"; $qryLastRstCard = $this->db->query($sqlLastRstCard, [$unpackResultStock->StockID]); if (!$qryLastRstCard) { throw new Exception("Failed to query last stockcard entry for destination stock ID: {$unpackResultStock->StockID}"); } $destBeforeQty = 0; if ($qryLastRstCard->num_rows() > 0) { $destBeforeQty = $qryLastRstCard->row()->StockCardAfter; } else { // If no previous stockcard for unpack result stock. Ambil StockQty $destBeforeQty = $unpackResultStock->StockQty - $qtyConverted; } $destAfterQty = $destBeforeQty + $qtyConverted; // Insert Stockcard for destination stock (IN) $sqlInsDestCard = "INSERT INTO stockcard ( StockCardWarehouseID, StockCardDatetime, StockCardItemID, StockCardItemUnitID, StockCardBatchNo, StockCardED, StockCardReffID, StockCardStatus, StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, StockCardUserID ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'UP_IN', ?, ?, 0, ?, ?)"; $qryInsDestCard = $this->db->query($sqlInsDestCard, [ $unpackResultStock->StockWarehouseID, $unpackResultStock->StockItemID, $unpackResultStock->StockItemUnitID, $unpackResultStock->StockBatchNo, $unpackResultStock->StockED, $unpackResultStock->StockID, $destBeforeQty, $qtyConverted, $destAfterQty, $this->sys_user['M_UserID'] ]); if (!$qryInsDestCard) { throw new Exception("Failed to insert stockcard for destination stock (UP_IN)"); } else{ $qryLogs["insert_card_up_in"] = `Berhasil stockcard UP_OUT pada StockCardID: {$this->db->insert_id()}dengan StockCardReffID: {$stockID}`; } $this->db->trans_complete(); $this->sys_ok([ "OldStock" => $rstCek, "UpdatedStock" => $updatedStock, "UnpackResultStock" => $unpackResultStock, "TranssactionLogs" => $qryLogs ]); } catch (Exception $e) { $this->db->trans_rollback(); $message = $e->getMessage(); $this->sys_error($message); } } function insertUserActivty($id, $type, $userID, $desc = '', $data = '') { $sql = "INSERT INTO user_activity ( UserActivityCode, UserActivityStatus, UserActivityDescription, UserActivityRefID, UserActivityData, UserActivityUserID, UserActivityCreated ) VALUES ( ?, ?, ?, ?, ?, ?, NOW() )"; // Eksekusi query dengan parameter yang diberikan $exec = $this->db->query($sql, [ 'TR', $type, $desc, $id, $data, $userID ]); // Jika query gagal, lempar error if (!$exec) { $this->sys_error_db("Error inserting user activity", $this->db); $this->db->trans_rollback(); exit; } } function toVerifOrApprove($id, $user) { $sqlevl = "SELECT * FROM m_user WHERE M_UserIsActive = 'Y' AND M_UserID = ?"; $quelvl = $this->db->query($sqlevl, [$user['M_UserID']]); if (!$quelvl) { $this->db->trans_rollback(); $this->sys_error_db("[Error] get user level"); exit; } $level = $quelvl->row_array()['M_UserM_ApproveLevelID']; if ($level == '1') { $sql = "UPDATE t_goods_transfer SET T_GoodsTransferVerifiedBy = ?, T_GoodsTransferVerifiedDate = NOW(), T_GoodsTransferLastUpdated = NOW() WHERE T_GoodsTransferID = ? AND T_GoodsTransferIsActive = 'Y'"; $que = $this->db->query($sql, [$user['M_UserID'], $id]); if (!$que) { $this->db->trans_rollback(); $this->sys_error_db("[Error] update verified by manager"); exit; } } elseif ($level == '2') { $sql = "UPDATE t_goods_transfer SET T_GoodsTransferApprovedBy = ?, T_GoodsTransferApprovedDate = NOW(), T_GoodsTransferLastUpdated = NOW() WHERE T_GoodsTransferID = ? AND T_GoodsTransferIsActive = 'Y'"; $que = $this->db->query($sql, [$user['M_UserID'], $id]); if (!$que) { $this->db->trans_rollback(); $this->sys_error_db("[Error] update approve kareg"); exit; } } } }