fix(projects): enforce single-customer projects (guard event attach + re-label)
A project must stay tied to one customer. The quote/contract/hours attach paths already rejected a foreign customer (equality on project.customer_account_id); the two remaining holes are closed here: - assignEvent: an event may only join a project that shares its customer. The event's customer(s) come from event_customer_assignments; a customer-assigned project rejects an event for a different customer (PROJECT_CUSTOMER_MISMATCH), and an empty project ADOPTS a single-customer event's customer. This is why a foreign-customer event could previously be attached. - updateProject: re-labelling a project to a customer that conflicts with the events/quotes/contracts it already holds is rejected (clearing to null is still allowed), so the customer can't be swapped out from under existing content. Frontend: the cockpit attach-event action surfaces the translated mismatch message; projects.error.customerMismatch reworded to read for both documents and events (de + en).
This commit is contained in:
@@ -3453,7 +3453,7 @@
|
||||
"previewFailed": "Vorschau konnte nicht geladen werden"
|
||||
},
|
||||
"error": {
|
||||
"customerMismatch": "Dieses Projekt gehört zu einem anderen Kunden als dieser Eintrag."
|
||||
"customerMismatch": "Das gehört zu einem anderen Kunden als dieses Projekt."
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
|
||||
@@ -3453,7 +3453,7 @@
|
||||
"previewFailed": "Could not load preview"
|
||||
},
|
||||
"error": {
|
||||
"customerMismatch": "That project belongs to a different customer than this entry."
|
||||
"customerMismatch": "That belongs to a different customer than this project."
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
|
||||
@@ -162,7 +162,13 @@ export const ProjectCockpitPage: React.FC = () => {
|
||||
setEventSearch('');
|
||||
toast.success(t('projects.events.attached', 'Event attached') as string);
|
||||
},
|
||||
onError: (err: any) => toast.error(err?.response?.data?.error || (t('projects.events.attachFailed', 'Could not attach event') as string)),
|
||||
onError: (err: any) => {
|
||||
if (err?.response?.data?.code === 'PROJECT_CUSTOMER_MISMATCH') {
|
||||
toast.error(t('projects.error.customerMismatch', 'That belongs to a different customer than this project.') as string);
|
||||
return;
|
||||
}
|
||||
toast.error(err?.response?.data?.error || (t('projects.events.attachFailed', 'Could not attach event') as string));
|
||||
},
|
||||
});
|
||||
|
||||
const openPreview = async (emailId: number) => {
|
||||
|
||||
Reference in New Issue
Block a user