V1 Working

This commit is contained in:
Mark Pinkster 2025-07-10 15:17:59 +02:00
parent cd8806bbf6
commit 1e7037428a
6 changed files with 352 additions and 1 deletions

8
.env-sample Normal file
View File

@ -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=

1
.gitignore vendored
View File

@ -12,3 +12,4 @@
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
.env

View File

@ -1,2 +1,2 @@
# PHP-Bridge_Monday-WeFact # PHP-Bridge Monday-WeFact

67
lib/wefact_api.php Normal file
View File

@ -0,0 +1,67 @@
<?php
class WeFactAPI
{
private $url;
private $apiKey;
/**
* Constructor die de API sleutel en URL ontvangt.
* @param string $apiKey Jouw WeFact API sleutel.
* @param string $apiUrl De basis-URL voor de WeFact API.
*/
public function __construct(string $apiKey, string $apiUrl = 'https://api.mijnwefact.nl/v2/')
{
$this->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;
}
}

208
webhook.php Normal file
View File

@ -0,0 +1,208 @@
<?php
ini_set('error_log', __DIR__ . '/debug.log');
// Laad de class en de .env variabelen
require_once 'lib/wefact_api.php';
// Helper functie om .env bestand te laden ZONDER Composer
function loadEnv($path) {
if (!file_exists($path)) return;
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) continue;
list($name, $value) = explode('=', $line, 2);
$_ENV[trim($name)] = trim($value);
}
}
loadEnv(__DIR__ . '/.env');
// --- Configuratie ---
$wefactApiKey = $_ENV['WEFACT_API_KEY'];
$mondayApiToken = $_ENV['MONDAY_API_TOKEN'];
$bedrijvenBoardId = (int)$_ENV['BEDRIJVEN_BOARD_ID'];
$vakkrachtenBoardId = (int)$_ENV['VAKKRACHTEN_BOARD_ID'];
// --- Data Mapping & Helper Functies (ongewijzigd) ---
$countryCodeMap = [
'nederland' => '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.']);
?>

67
wefact_api.php Normal file
View File

@ -0,0 +1,67 @@
<?php
class WeFactAPI
{
private $url;
private $apiKey;
/**
* Constructor die de API sleutel en URL ontvangt.
* @param string $apiKey Jouw WeFact API sleutel.
* @param string $apiUrl De basis-URL voor de WeFact API.
*/
public function __construct(string $apiKey, string $apiUrl = 'https://api.mijnwefact.nl/v2/')
{
$this->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;
}
}