From 1e7037428adcc62b4183063997912524b0bf519d Mon Sep 17 00:00:00 2001 From: Mark Pinkster Date: Thu, 10 Jul 2025 15:17:59 +0200 Subject: [PATCH] V1 Working --- .env-sample | 8 ++ .gitignore | 1 + README.md | 2 +- lib/wefact_api.php | 67 +++++++++++++++ webhook.php | 208 +++++++++++++++++++++++++++++++++++++++++++++ wefact_api.php | 67 +++++++++++++++ 6 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 .env-sample create mode 100644 lib/wefact_api.php create mode 100644 webhook.php create mode 100644 wefact_api.php diff --git a/.env-sample b/.env-sample new file mode 100644 index 0000000..f286b6c --- /dev/null +++ b/.env-sample @@ -0,0 +1,8 @@ +# WeFact Secrets +WEFACT_API_KEY= +WEFACT_API_URL=https://api.mijnwefact.nl/v2 + +# Monday Secrets +MONDAY_API_TOKEN= +BEDRIJVEN_BOARD_ID= +VAKKRACHTEN_BOARD_ID= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8c2b884..e72719d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ # Built Visual Studio Code Extensions *.vsix +.env \ No newline at end of file diff --git a/README.md b/README.md index cfffd1d..dc49e03 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# PHP-Bridge_Monday-WeFact +# PHP-Bridge Monday-WeFact diff --git a/lib/wefact_api.php b/lib/wefact_api.php new file mode 100644 index 0000000..1504bc8 --- /dev/null +++ b/lib/wefact_api.php @@ -0,0 +1,67 @@ +url = $apiUrl; + $this->apiKey = $apiKey; + } + + /** + * Verstuurt een verzoek naar de WeFact API. + * @param string $controller De naam van de controller (bv. 'debtor'). + * @param string $action De actie die uitgevoerd moet worden (bv. 'add'). + * @param array $params Een array met parameters voor de call. + * @return array Het antwoord van de API. + */ + public function sendRequest(string $controller, string $action, array $params): array + { + // Voeg de verplichte parameters toe + $params['api_key'] = $this->apiKey; + $params['controller'] = $controller; + $params['action'] = $action; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, '10'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); + + // Let op: SSL-verificatie nooit uitschakelen in een productieomgeving. + // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + + $curlResp = curl_exec($ch); + $curlError = curl_error($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($curlError != '') { + $result = [ + 'controller' => 'invalid', 'action' => 'invalid', + 'status' => 'error', 'date' => date('c'), + 'errors' => [$curlError] + ]; + } elseif ($httpCode >= 400) { // Vang alle client/server fouten af + $result = [ + 'controller' => 'invalid', 'action' => 'invalid', + 'status' => 'error', 'date' => date('c'), + 'errors' => ["HTTP status code: " . $httpCode, $curlResp] + ]; + } else { + $result = json_decode($curlResp, true); + } + + return $result; + } +} \ No newline at end of file diff --git a/webhook.php b/webhook.php new file mode 100644 index 0000000..8b7d2c6 --- /dev/null +++ b/webhook.php @@ -0,0 +1,208 @@ + 'NL', 'belgiƫ' => 'BE', 'belgium' => 'BE', + 'duitsland' => 'DE', 'germany' => 'DE', +]; + +function getColumnValue(array $columnValues, string $columnId): ?string { + foreach ($columnValues as $column) { + if ($column['id'] === $columnId) return $column['text']; + } + return null; +} + +function updateMondayColumn(int $boardId, int $itemId, string $columnId, string $newValue, string $mondayToken) { + $query = 'mutation ($itemId: ID!, $boardId: ID!, $columnId: String!, $newValue: String!) { + change_simple_column_value(item_id: $itemId, board_id: $boardId, column_id: $columnId, value: $newValue) { id } + }'; + $variables = ['boardId' => $boardId, 'itemId' => $itemId, 'columnId' => $columnId, 'newValue' => $newValue]; + $payload = json_encode(['query' => $query, 'variables' => $variables]); + $ch = curl_init('https://api.monday.com/v2'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Authorization: ' . $mondayToken]); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($httpCode >= 400) { + error_log("Fout bij updaten Monday kolom {$columnId}: " . $response); + } else { + error_log("Monday kolom {$columnId} succesvol bijgewerkt voor item {$itemId}."); + } +} + +function getMondayItemData(int $itemId, string $mondayApiToken): ?array { + $graphQLQuery = 'query($itemId: [ID!]) { items (ids: $itemId) { name column_values { id text } } }'; + $mondayPayload = json_encode(['query' => $graphQLQuery, 'variables' => ['itemId' => $itemId]]); + $ch_monday = curl_init('https://api.monday.com/v2'); + curl_setopt($ch_monday, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch_monday, CURLOPT_POST, true); + curl_setopt($ch_monday, CURLOPT_POSTFIELDS, $mondayPayload); + curl_setopt($ch_monday, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Authorization: ' . $mondayApiToken]); + $mondayResponse = curl_exec($ch_monday); + curl_close($ch_monday); + $itemData = json_decode($mondayResponse, true)['data']['items'][0] ?? null; + if (!$itemData) { + error_log("Item met ID {$itemId} niet gevonden in Monday.com response."); + } + return $itemData; +} + +// --- Functies voor Bedrijven en Vakkrachten (ongewijzigd) --- +function handleBedrijven(array $itemData, int $itemId, WeFactAPI $wefactApi, string $mondayApiToken) { + global $countryCodeMap, $bedrijvenBoardId; + $columnValues = $itemData['column_values']; + $wefactDebtorIdColumn = 'text_mksq79kz'; + $wefactDebtorId = getColumnValue($columnValues, $wefactDebtorIdColumn); + $debiteurData = array_filter([ + 'CompanyName' => $itemData['name'], + 'CompanyNumber' => getColumnValue($columnValues, 'kvk_mkmphj97'), + 'TaxNumber' => getColumnValue($columnValues, 'text_mkpw2a77'), + 'PhoneNumber' => getColumnValue($columnValues, 'telefoon_mkktmz4t'), + 'EmailAddress' => getColumnValue($columnValues, 'e_mail_mkktxyr6'), + 'Address' => getColumnValue($columnValues, 'text_mkpwgxkm'), + 'ZipCode' => getColumnValue($columnValues, 'text_mkpwp7ps'), + 'City' => getColumnValue($columnValues, 'text_mkpw5ek1'), + 'Country' => getColumnValue($columnValues, 'text_mkpwb8q1') ? ($countryCodeMap[strtolower(getColumnValue($columnValues, 'text_mkpwb8q1'))] ?? getColumnValue($columnValues, 'text_mkpwb8q1')) : null, + 'InvoiceEmail' => getColumnValue($columnValues, 'text_mkpw1nxd'), + ], fn($value) => $value !== null && $value !== ''); + if ($wefactDebtorId) { + error_log("WeFact Debiteur ID {$wefactDebtorId} gevonden. Debiteur wordt bijgewerkt..."); + $debiteurData['DebtorCode'] = $wefactDebtorId; + $response = $wefactApi->sendRequest('debtor', 'edit', $debiteurData); + } else { + error_log("Geen WeFact Debiteur ID gevonden. Nieuwe debiteur wordt aangemaakt..."); + $response = $wefactApi->sendRequest('debtor', 'add', $debiteurData); + if ($response['status'] === 'success' && isset($response['debtor']['DebtorCode'])) { + $newDebtorId = $response['debtor']['DebtorCode']; + error_log("Debiteur succesvol aangemaakt met ID: {$newDebtorId}. Monday wordt bijgewerkt."); + updateMondayColumn($bedrijvenBoardId, $itemId, $wefactDebtorIdColumn, $newDebtorId, $mondayApiToken); + } + } + if ($response['status'] === 'error') { + error_log("FOUT van WeFact (Debiteur): " . json_encode($response['errors'])); + } else { + error_log("SUCCES! Reactie van WeFact (Debiteur): " . json_encode($response)); + } +} + +function handleVakkrachten(array $itemData, int $itemId, WeFactAPI $wefactApi, string $mondayApiToken) { + global $vakkrachtenBoardId; + $columnValues = $itemData['column_values']; + $wefactProductIdColumn = 'text_mksqgn4v'; + $wefactProductId = getColumnValue($columnValues, $wefactProductIdColumn); + $productData = array_filter([ + 'ProductName' => $itemData['name'], + 'ProductKeyPhrase' => getColumnValue($columnValues, 'text_mkrhy2ks'), + ], fn($value) => $value !== null && $value !== ''); + if (empty($productData['ProductName'])) { + error_log("Geen productnaam gevonden voor item {$itemId}. Actie wordt gestopt."); + return; + } + if ($wefactProductId) { + error_log("WeFact Product ID {$wefactProductId} gevonden. Product wordt bijgewerkt..."); + $productData['ProductCode'] = $wefactProductId; + $response = $wefactApi->sendRequest('product', 'edit', $productData); + } else { + error_log("Geen WeFact Product ID gevonden. Nieuw product wordt aangemaakt..."); + $response = $wefactApi->sendRequest('product', 'add', $productData); + if ($response['status'] === 'success' && isset($response['product']['ProductCode'])) { + $newProductId = $response['product']['ProductCode']; + error_log("Product succesvol aangemaakt met ID: {$newProductId}. Monday wordt bijgewerkt."); + updateMondayColumn($vakkrachtenBoardId, $itemId, $wefactProductIdColumn, $newProductId, $mondayApiToken); + } + } + if ($response['status'] === 'error') { + error_log("FOUT van WeFact (Product): " . json_encode($response['errors'])); + } else { + error_log("SUCCES! Reactie van WeFact (Product): " . json_encode($response)); + } +} + +// --- Webserver & Routing Logica --- +$requestBody = file_get_contents('php://input'); +$data = json_decode($requestBody, true); + +if (isset($data['challenge'])) { + header('Content-Type: application/json'); + echo json_encode(['challenge' => $data['challenge']]); + exit; +} + +if (isset($data['event'])) { + $event = $data['event']; + $boardId = (int)$event['boardId']; + $itemId = (int)$event['pulseId']; + + // --- NIEUW: LOCK MECHANISME OM RACE CONDITIONS TE VOORKOMEN --- + $lockFileDir = __DIR__ . '/locks'; + if (!is_dir($lockFileDir)) { + mkdir($lockFileDir, 0775, true); + } + $lockFilePath = $lockFileDir . '/' . $itemId . '.lock'; + $lockFileHandle = fopen($lockFilePath, 'c'); + + if ($lockFileHandle === false) { + error_log("Kon geen lock file handle aanmaken voor item {$itemId}."); + exit; + } + + // Probeer een exclusieve, niet-blokkerende lock te krijgen. + if (!flock($lockFileHandle, LOCK_EX | LOCK_NB)) { + // Als het niet lukt, is een ander proces al bezig met dit item. + error_log("Process voor item {$itemId} is al bezig. Deze instantie wordt gestopt om duplicaten te voorkomen."); + fclose($lockFileHandle); + exit; + } + + // Zorg ervoor dat de lock altijd wordt vrijgegeven, zelfs bij een fout. + register_shutdown_function(function() use ($lockFileHandle, $lockFilePath) { + flock($lockFileHandle, LOCK_UN); // Geef lock vrij + fclose($lockFileHandle); + unlink($lockFilePath); // Verwijder het lock-bestand + }); + // --- EINDE LOCK MECHANISME --- + + $itemData = getMondayItemData($itemId, $mondayApiToken); + + if ($itemData) { + $wefactApi = new WeFactAPI($wefactApiKey); + + if ($boardId === $bedrijvenBoardId) { + handleBedrijven($itemData, $itemId, $wefactApi, $mondayApiToken); + } elseif ($boardId === $vakkrachtenBoardId) { + handleVakkrachten($itemData, $itemId, $wefactApi, $mondayApiToken); + } else { + error_log("Webhook ontvangen van een niet-geconfigureerd bord: ID {$boardId}"); + } + } +} + +http_response_code(200); +echo json_encode(['message' => 'Event succesvol ontvangen.']); +?> \ No newline at end of file diff --git a/wefact_api.php b/wefact_api.php new file mode 100644 index 0000000..1504bc8 --- /dev/null +++ b/wefact_api.php @@ -0,0 +1,67 @@ +url = $apiUrl; + $this->apiKey = $apiKey; + } + + /** + * Verstuurt een verzoek naar de WeFact API. + * @param string $controller De naam van de controller (bv. 'debtor'). + * @param string $action De actie die uitgevoerd moet worden (bv. 'add'). + * @param array $params Een array met parameters voor de call. + * @return array Het antwoord van de API. + */ + public function sendRequest(string $controller, string $action, array $params): array + { + // Voeg de verplichte parameters toe + $params['api_key'] = $this->apiKey; + $params['controller'] = $controller; + $params['action'] = $action; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, '10'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); + + // Let op: SSL-verificatie nooit uitschakelen in een productieomgeving. + // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + + $curlResp = curl_exec($ch); + $curlError = curl_error($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($curlError != '') { + $result = [ + 'controller' => 'invalid', 'action' => 'invalid', + 'status' => 'error', 'date' => date('c'), + 'errors' => [$curlError] + ]; + } elseif ($httpCode >= 400) { // Vang alle client/server fouten af + $result = [ + 'controller' => 'invalid', 'action' => 'invalid', + 'status' => 'error', 'date' => date('c'), + 'errors' => ["HTTP status code: " . $httpCode, $curlResp] + ]; + } else { + $result = json_decode($curlResp, true); + } + + return $result; + } +} \ No newline at end of file