Add the ability to merge two contacts, the same way third parties and members
can already be merged. The merged contact is absorbed by the current one, all
its satellite data is moved, then it is deleted.
Contact::mergeContact() orchestrates everything in a single transaction, using
the new CommonObject::commonReplaceContact() helper and the replaceContact()
static methods of Contact, ActionComm and User. A replaceContact hook lets the
external modules move their own data. No schema change, no migration.
Three specific traps of this table are handled:
- llx_element_contact.fk_socpeople references llx_socpeople only when
c_type_contact.source is 'external', and llx_user when it is 'internal', so
both queries filter on it. Without that filter the internal assignments of
the users would silently be moved to the merged contact.
- Contact::updateRoles(), called by update(), deletes then reinserts every
societe_contacts row of the contact from $this->roles. It is neutralised by
setting roles to null, and update() is called before the links are moved.
- Every table having a unique index on the contact id is deduplicated before
its update, so the update cannot violate it. $ignoreerrors is never used:
the FIXME of commonReplaceThirdparty() documents the links it loses.
The files are moved after the commit and not before, unlike mergeCompany() and
mergeMembers(): a failure after the move leaves them with the database rolled
back but the files already gone. A name collision renames the file instead of
losing it, and a failure is reported to the user instead of being ignored.
Access guards live in the class and not in the page, so the REST API, the
scheduled jobs and the modules benefit from them too. They cover both contacts,
fetch() by rowid applying neither an entity nor a permission filter: entity,
private contact of another user, external user, sales representative perimeter,
absorbing a shared contact into a private one, and a contact linked to a user
account (moving llx_user.fk_socpeople would let the email of that user be
rewritten from the contact card, so it requires the permission to create users).
Form::select_contact() gets a one line fix on the way: it accepts a $filter
parameter, documents it, forwards it to the ajax branch, but dropped it when
building the combo, so the merge popup could not exclude the current contact.
Known limitations, out of the scope of this pull request: merging contacts of
different entities is refused, Contact::update() neither writes url, no_email,
fk_parent nor import_key while its $nosyncuser parameter is declared but never
read, and with CONTACT_USE_SEARCH_TO_SELECT set the current contact is still
offered in the popup, contact/ajax/contact.php overwriting the filter it gets.
When a user lacks 'societe / client / voir', the agenda list / index /
pertype / peruser / API endpoints and ActionComm::load_board_userid_v2 SQL
add an EXISTS sub-query to filter on societe_commerciaux. The clause was
written without a guard on a.fk_soc IS NULL, so any event not attached
to a thirdparty (the common case for personal reminders or internal-only
events) was silently excluded - even when the event was assigned to the
user himself.
Wrap the EXISTS / NOT EXISTS branches with 'a.fk_soc IS NULL OR ...' so
events without a thirdparty stay visible regardless of the sale-rep
filter. Matches eldy's stated semantics on the issue: event permission
depends on the assignee, not on the linked thirdparty.
Touches the five list paths plus the API agendaevents endpoint.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
ActionComm::create()'s INSERT and ActionComm::update()'s UPDATE both
omitted the fk_task column, so the value posted via /agendaevents or
the manual create-event form was silently dropped. The fetch() SELECT
was also missing the column, so even if it had been written it would
not have been loaded back.
Add fk_task to all three SQL statements and assign it from the row in
fetch(), matching the existing pattern for fk_user_action / fk_contact.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
* Improve user card performance by avoid mysql OR
* Negate first condition to avoid duplicates in UNION
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>