{"sha":"07293ded0fab6db7db6d2b8b8ef1287c50708081","node_id":"C_kwDOLgQlp9oAKDA3MjkzZGVkMGZhYjZkYjdkYjZkMmI4YjhlZjEyODdjNTA3MDgwODE","commit":{"author":{"name":"Henning Berge","email":"henning.ber@gmail.com","date":"2026-05-13T11:54:28Z"},"committer":{"name":"GitHub","email":"noreply@github.com","date":"2026-05-13T11:54:28Z"},"message":"Merge pull request #1034 from PorticoEstate/fix/user-creation-duplication-1032\n\nFix/user creation duplication 1032","tree":{"sha":"863e4984024abc90dc82e419ff77891e6ee31f67","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/git/trees/863e4984024abc90dc82e419ff77891e6ee31f67"},"url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/git/commits/07293ded0fab6db7db6d2b8b8ef1287c50708081","comment_count":0,"verification":{"verified":true,"reason":"valid","signature":"-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJqBGZ0CRC1aQ7uu5UhlAAAXtUQAIWW2QTcZ0Eadnb3gIZg4plE\nd9muH6dmYhfJTIFYkoWj59GkF3AOq4UtiNZU/nfbQwkOYLigFgQh8vQmaSG2A0ge\n1s+Kg4HNIA9N6Z6xmQnzfdJJAmX9JmLuajhVATEIXo2E59G8XUudtc172ZVuVqSt\nQgl0+K4nIbnm0m9kmZxUba/Ipufe0oYVygPuo3uePxPx+Gr5up0Rq3e58C1dITNa\nRkFG6QV0oNMFoBuezx/B65r/4ar509y8PkmMsTC9HYqw8b8xyXuQCzBln2VPqlZS\n/OtxQhOynGr/kPqbwR8eF+r3Zc4AOnRdV1yswPqIOgKxyBNhRwHhlTYvoc6xzkgV\nvyT+aqvxu7TstSUEQBECJQDBCPH+V5zRZ3JpmLrl4pktDqdYcTFmpg/Fho7uoSLJ\nvb6xBfx/CBDcs3qDyidOH5oQw65ou+jeFlgwKWI1sUniDwMidk8lnNnwqKo07pbs\nCB2Nr1hvdBUvhAOyL9+rg0dYufoU0qkT6D+8Jbqpi/Jk1HdhvdyvUUzxOsSnVdNn\naHzJSmZIcwjIS61Z34bvYMFwCJbsOVkRF8NvXbu5FjnCE1QZvEsxpfnx1cZadp2o\nGottNNy1PR+nzwI8882g0UOVsHKRjn+sFZETK/NNwxjIJUKuDwrFXM2AqyCe9xE8\n1x8nmehoXBnlWl11XxfT\n=QCM9\n-----END PGP SIGNATURE-----\n","payload":"tree 863e4984024abc90dc82e419ff77891e6ee31f67\nparent 6d1120f5273f7cd3940eb3fdf343720d6411592c\nparent be18e5d51eb0d6433baca63c68dbefe668eb44e5\nauthor Henning Berge <henning.ber@gmail.com> 1778673268 +0200\ncommitter GitHub <noreply@github.com> 1778673268 +0200\n\nMerge pull request #1034 from PorticoEstate/fix/user-creation-duplication-1032\n\nFix/user creation duplication 1032","verified_at":"2026-05-13T11:54:29Z"}},"url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/07293ded0fab6db7db6d2b8b8ef1287c50708081","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/07293ded0fab6db7db6d2b8b8ef1287c50708081","comments_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/07293ded0fab6db7db6d2b8b8ef1287c50708081/comments","author":{"login":"Promises","id":4681207,"node_id":"MDQ6VXNlcjQ2ODEyMDc=","avatar_url":"https://avatars.githubusercontent.com/u/4681207?v=4","gravatar_id":"","url":"https://api.github.com/users/Promises","html_url":"https://github.com/Promises","followers_url":"https://api.github.com/users/Promises/followers","following_url":"https://api.github.com/users/Promises/following{/other_user}","gists_url":"https://api.github.com/users/Promises/gists{/gist_id}","starred_url":"https://api.github.com/users/Promises/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Promises/subscriptions","organizations_url":"https://api.github.com/users/Promises/orgs","repos_url":"https://api.github.com/users/Promises/repos","events_url":"https://api.github.com/users/Promises/events{/privacy}","received_events_url":"https://api.github.com/users/Promises/received_events","type":"User","user_view_type":"public","site_admin":false},"committer":{"login":"web-flow","id":19864447,"node_id":"MDQ6VXNlcjE5ODY0NDQ3","avatar_url":"https://avatars.githubusercontent.com/u/19864447?v=4","gravatar_id":"","url":"https://api.github.com/users/web-flow","html_url":"https://github.com/web-flow","followers_url":"https://api.github.com/users/web-flow/followers","following_url":"https://api.github.com/users/web-flow/following{/other_user}","gists_url":"https://api.github.com/users/web-flow/gists{/gist_id}","starred_url":"https://api.github.com/users/web-flow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/web-flow/subscriptions","organizations_url":"https://api.github.com/users/web-flow/orgs","repos_url":"https://api.github.com/users/web-flow/repos","events_url":"https://api.github.com/users/web-flow/events{/privacy}","received_events_url":"https://api.github.com/users/web-flow/received_events","type":"User","user_view_type":"public","site_admin":false},"parents":[{"sha":"6d1120f5273f7cd3940eb3fdf343720d6411592c","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/6d1120f5273f7cd3940eb3fdf343720d6411592c","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/6d1120f5273f7cd3940eb3fdf343720d6411592c"},{"sha":"be18e5d51eb0d6433baca63c68dbefe668eb44e5","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/be18e5d51eb0d6433baca63c68dbefe668eb44e5","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/be18e5d51eb0d6433baca63c68dbefe668eb44e5"}],"stats":{"total":109,"additions":73,"deletions":36},"files":[{"sha":"884a49d86c6d2139916a61363fe8e6d5c3db0f32","filename":"src/modules/bookingfrontend/client/src/components/user/creation-modal/user-creation-modal.tsx","status":"modified","additions":11,"deletions":3,"changes":14,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fcomponents%2Fuser%2Fcreation-modal%2Fuser-creation-modal.tsx","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fcomponents%2Fuser%2Fcreation-modal%2Fuser-creation-modal.tsx","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fcomponents%2Fuser%2Fcreation-modal%2Fuser-creation-modal.tsx?ref=07293ded0fab6db7db6d2b8b8ef1287c50708081","patch":"@@ -2,7 +2,7 @@ import React, {useEffect, useMemo} from 'react';\n import {useForm, Controller} from 'react-hook-form';\n import {z} from 'zod';\n import {zodResolver} from \"@hookform/resolvers/zod\";\n-import {Button, Textfield} from \"@digdir/designsystemet-react\";\n+import {Alert, Button, Textfield} from \"@digdir/designsystemet-react\";\n import {useTrans} from \"@/app/i18n/ClientTranslationProvider\";\n import MobileDialog from \"@/components/dialog/mobile-dialog\";\n import {useCreateBookingUser, useExternalUserData} from \"@/service/hooks/api-hooks\";\n@@ -70,6 +70,7 @@ const UserCreationModal: React.FC<UserCreationModalProps> = ({\n     const { data: externalData, isLoading: isLoadingExternal } = useExternalUserData();\n     const { mutateAsync: createUser } = useCreateBookingUser();\n     const [isSubmitting, setIsSubmitting] = React.useState(false);\n+    const [submitError, setSubmitError] = React.useState<string | null>(null);\n \n     const userCreationSchema = useMemo(() => createUserCreationSchema(t), [t]);\n \n@@ -109,6 +110,7 @@ const UserCreationModal: React.FC<UserCreationModalProps> = ({\n     const onSubmit = async (formData: UserCreationFormData) => {\n         try {\n             setIsSubmitting(true);\n+            setSubmitError(null);\n \n             // Convert empty strings to null for nullable fields\n             const userData = {\n@@ -125,10 +127,10 @@ const UserCreationModal: React.FC<UserCreationModalProps> = ({\n \n             // Call success callback\n             onUserCreated?.();\n-            // onClose();\n \n         } catch (error) {\n-            console.error('Failed to create user:', error);\n+            const message = error instanceof Error ? error.message : t('bookingfrontend.unknown_error');\n+            setSubmitError(message);\n         } finally {\n             setIsSubmitting(false);\n         }\n@@ -185,6 +187,12 @@ const UserCreationModal: React.FC<UserCreationModalProps> = ({\n                     <p className={styles.loadingText}>{t('common.loading_external_data')}</p>\n                 )}\n \n+                {submitError && (\n+                    <Alert data-color=\"danger\" role=\"alert\">\n+                        {submitError}\n+                    </Alert>\n+                )}\n+\n                 <form\n                     id=\"user-creation-form\"\n                     onSubmit={handleSubmit(onSubmit)}"},{"sha":"d97e62d9d9d53e8abd929d2d4bd602d11e93d590","filename":"src/modules/bookingfrontend/controllers/BookingUserController.php","status":"modified","additions":55,"deletions":33,"changes":88,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2FBookingUserController.php","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2FBookingUserController.php","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2FBookingUserController.php?ref=07293ded0fab6db7db6d2b8b8ef1287c50708081","patch":"@@ -590,13 +590,6 @@ public function create(Request $request, Response $response): Response\n \t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'No SSN found for user'], 400);\n \t\t\t}\n \n-\t\t\t// Check if user already exists\n-\t\t\t$existingUserId = $bouser->get_user_id($userSsn);\n-\t\t\tif ($existingUserId)\n-\t\t\t{\n-\t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'User already exists'], 400);\n-\t\t\t}\n-\n \t\t\t// Get create data from request body\n \t\t\t$data = json_decode($request->getBody()->getContents(), true);\n \t\t\tif (json_last_error() !== JSON_ERROR_NONE)\n@@ -621,39 +614,68 @@ public function create(Request $request, Response $response): Response\n \t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'Invalid homepage URL format'], 400);\n \t\t\t}\n \n-\t\t\t// Prepare user data for creation (only use known valid columns)\n-\t\t\t$createData = [\n-\t\t\t\t'customer_ssn' => $userSsn,\n-\t\t\t\t'name' => $data['name']\n-\t\t\t];\n+\t\t\t// Check if user already exists — if so, update their profile instead of failing\n+\t\t\t$existingUserId = $bouser->get_user_id($userSsn);\n+\t\t\tif ($existingUserId)\n+\t\t\t{\n+\t\t\t\t$updateFields = [];\n+\t\t\t\t$updateValues = [];\n \n-\t\t\t// Add optional fields only if they have values\n-\t\t\tif (!empty($data['email'])) $createData['email'] = $data['email'];\n-\t\t\tif (!empty($data['phone'])) $createData['phone'] = $data['phone'];\n-\t\t\tif (!empty($data['street'])) $createData['street'] = $data['street'];\n-\t\t\tif (!empty($data['zip_code'])) $createData['zip_code'] = $data['zip_code'];\n-\t\t\tif (!empty($data['city'])) $createData['city'] = $data['city'];\n-\t\t\tif (!empty($data['homepage'])) $createData['homepage'] = $data['homepage'];\n+\t\t\t\t$allowedFields = ['name', 'email', 'phone', 'street', 'zip_code', 'city', 'homepage'];\n+\t\t\t\tforeach ($allowedFields as $field)\n+\t\t\t\t{\n+\t\t\t\t\tif (isset($data[$field]) && $data[$field] !== '')\n+\t\t\t\t\t{\n+\t\t\t\t\t\t$updateFields[] = \"{$field} = ?\";\n+\t\t\t\t\t\t$updateValues[] = $data[$field];\n+\t\t\t\t\t}\n+\t\t\t\t}\n \n-\t\t\t// Create the user directly in the database\n-\t\t\t$placeholders = implode(',', array_fill(0, count($createData), '?'));\n-\t\t\t$columns = implode(',', array_keys($createData));\n+\t\t\t\tif (!empty($updateFields))\n+\t\t\t\t{\n+\t\t\t\t\t$updateValues[] = $existingUserId;\n+\t\t\t\t\t$sql = \"UPDATE bb_user SET \" . implode(', ', $updateFields) . \" WHERE id = ?\";\n+\t\t\t\t\t$stmt = $this->db->prepare($sql);\n+\t\t\t\t\t$stmt->execute($updateValues);\n+\t\t\t\t}\n \n-\t\t\t$sql = \"INSERT INTO bb_user ({$columns}) VALUES ({$placeholders})\";\n+\t\t\t\t$userId = $existingUserId;\n+\t\t\t}\n+\t\t\telse\n+\t\t\t{\n+\t\t\t\t// Prepare user data for creation (only use known valid columns)\n+\t\t\t\t$createData = [\n+\t\t\t\t\t'customer_ssn' => $userSsn,\n+\t\t\t\t\t'name' => $data['name']\n+\t\t\t\t];\n \n-\t\t\t$stmt = $this->db->prepare($sql);\n-\t\t\t$result = $stmt->execute(array_values($createData));\n+\t\t\t\t// Add optional fields only if they have values\n+\t\t\t\tif (!empty($data['email'])) $createData['email'] = $data['email'];\n+\t\t\t\tif (!empty($data['phone'])) $createData['phone'] = $data['phone'];\n+\t\t\t\tif (!empty($data['street'])) $createData['street'] = $data['street'];\n+\t\t\t\tif (!empty($data['zip_code'])) $createData['zip_code'] = $data['zip_code'];\n+\t\t\t\tif (!empty($data['city'])) $createData['city'] = $data['city'];\n+\t\t\t\tif (!empty($data['homepage'])) $createData['homepage'] = $data['homepage'];\n \n-\t\t\tif (!$result)\n-\t\t\t{\n-\t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'Failed to create user'], 500);\n-\t\t\t}\n+\t\t\t\t$placeholders = implode(',', array_fill(0, count($createData), '?'));\n+\t\t\t\t$columns = implode(',', array_keys($createData));\n \n-\t\t\t$userId = $this->db->lastInsertId();\n+\t\t\t\t$sql = \"INSERT INTO bb_user ({$columns}) VALUES ({$placeholders})\";\n \n-\t\t\tif (!$userId)\n-\t\t\t{\n-\t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'Failed to create user'], 500);\n+\t\t\t\t$stmt = $this->db->prepare($sql);\n+\t\t\t\t$result = $stmt->execute(array_values($createData));\n+\n+\t\t\t\tif (!$result)\n+\t\t\t\t{\n+\t\t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'Failed to create user'], 500);\n+\t\t\t\t}\n+\n+\t\t\t\t$userId = $this->db->lastInsertId();\n+\n+\t\t\t\tif (!$userId)\n+\t\t\t\t{\n+\t\t\t\t\treturn ResponseHelper::sendErrorResponse(['error' => 'Failed to create user'], 500);\n+\t\t\t\t}\n \t\t\t}\n \n \t\t\t// Directly update the session data to include the newly created user"},{"sha":"7b381de9b580ddeb7ae8f8b6d4afa0667a3e20ba","filename":"src/modules/phpgwapi/inc/class.common.inc.php","status":"modified","additions":7,"deletions":0,"changes":7,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fphpgwapi%2Finc%2Fclass.common.inc.php","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/07293ded0fab6db7db6d2b8b8ef1287c50708081/src%2Fmodules%2Fphpgwapi%2Finc%2Fclass.common.inc.php","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fphpgwapi%2Finc%2Fclass.common.inc.php?ref=07293ded0fab6db7db6d2b8b8ef1287c50708081","patch":"@@ -724,6 +724,13 @@ public static function get_tpl_dir($appname = '', $layout = '')\n \t\t\t$serverSettings['template_set'] = 'base';\n \t\t}\n \n+\t\t// Fallback to bootstrap if the selected template doesn't exist\n+\t\t$coreTplDir = PHPGW_SERVER_ROOT . \"/phpgwapi/templates/{$serverSettings['template_set']}\";\n+\t\tif (!is_dir($coreTplDir) && $serverSettings['template_set'] !== 'base')\n+\t\t{\n+\t\t\t$serverSettings['template_set'] = 'bootstrap';\n+\t\t}\n+\n \t\tSettings::getInstance()->set('server', $serverSettings);\n \n \t\t$tpldir         = PHPGW_SERVER_ROOT . \"/{$appname}/templates/{$serverSettings['template_set']}\";"}]}