github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/GUI/common/similaires.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/benoitkugler/goACVE/client/GUI/fields"
     7  
     8  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
     9  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    10  	"github.com/benoitkugler/goACVE/server/core/rawdata/matching"
    11  	"github.com/therecipe/qt/core"
    12  
    13  	"github.com/benoitkugler/goACVE/client/GUI/basic"
    14  	"github.com/benoitkugler/goACVE/client/GUI/lists"
    15  	"github.com/therecipe/qt/widgets"
    16  )
    17  
    18  type Similaires struct {
    19  	lists.Table
    20  
    21  	base       *dm.BaseLocale
    22  	candidat   matching.PatternsSimilarite
    23  	inPersonne rd.BasePersonne
    24  
    25  	btnNewPersonne *widgets.QPushButton
    26  }
    27  
    28  type OnIdentifie = func(target matching.IdentifieTarget)
    29  
    30  func NewSimilaires(base *dm.BaseLocale, onIdentifie OnIdentifie) *Similaires {
    31  	s := Similaires{Table: lists.Table{}, base: base}
    32  	s.Headers = []rd.Header{
    33  		{Field: dm.PersonnePertinence, Label: "Pertinence"},
    34  		{Field: dm.PersonneNom, Label: "Nom"},
    35  		{Field: dm.PersonnePrenom, Label: "Prénom"},
    36  		{Field: dm.PersonneSexe, Label: "Sexe"},
    37  		{Field: dm.PersonneDateNaissance, Label: "Date de naissance"},
    38  		{Field: dm.PersonneNomJeuneFille, Label: "Nom de jeune fille"},
    39  		{Field: dm.PersonneMail, Label: "Mail"},
    40  	}
    41  	s.OnSearch = s.onSearch
    42  
    43  	s.RightHeader = widgets.NewQHBoxLayout()
    44  	s.btnNewPersonne = basic.Button("Vers un nouveau profil")
    45  	s.btnNewPersonne.SetObjectName(basic.ONAdd)
    46  	s.btnNewPersonne.ConnectClicked(func(_ bool) { onIdentifie(matching.NouveauProfil{}) })
    47  	s.RightHeader.AddWidget(s.btnNewPersonne, 1, 0)
    48  
    49  	s.OnClick = func(acces rd.Item, _ int) {
    50  		existant := s.base.NewPersonne(acces.Id.Int64())
    51  		merged, conflicts := matching.Merge(s.inPersonne, existant.RawData().BasePersonne)
    52  		if conflicts != (matching.Conflicts{}) { // sinon, aucun conflits, on fusionne directement
    53  			dial := setupComparaison(s.inPersonne, existant.RawData().BasePersonne, &merged, conflicts)
    54  			if dial.Exec() == 0 {
    55  				return // on annule
    56  			}
    57  		}
    58  		onIdentifie(matching.Rattache{IdTarget: existant.Id, Modifications: merged})
    59  	}
    60  	s.Init()
    61  	return &s
    62  }
    63  
    64  func (s *Similaires) onSearch(pattern string) {
    65  	var liste rd.Table
    66  	if pattern == "" {
    67  		liste = s.base.ChercheSimilaires(s.candidat)
    68  	} else {
    69  		liste = s.base.RechercheRapidePersonnes(false, pattern)
    70  	}
    71  	s.Table.Model().SetData(liste)
    72  }
    73  
    74  // SetData affiche les personnes similaires au profil `inPersonne`,
    75  // ou rien pour une valeur nulle.
    76  // Passer `isTemporaire == false` signifie que la personne est déjà
    77  // référencée, et le bouton Nouveau profil est alors désactivé.
    78  func (s *Similaires) SetData(inPersonne rd.BasePersonne, isTemporaire bool) {
    79  	s.inPersonne = inPersonne
    80  	s.candidat = matching.PatternsSimilarite{
    81  		Nom:           inPersonne.Nom,
    82  		Prenom:        inPersonne.Prenom,
    83  		NomJeuneFille: inPersonne.NomJeuneFille,
    84  		DateNaissance: inPersonne.DateNaissance,
    85  		Sexe:          inPersonne.Sexe,
    86  		Mail:          inPersonne.Mail,
    87  	}
    88  	var data rd.Table
    89  	if s.candidat != (matching.PatternsSimilarite{}) {
    90  		data = s.base.ChercheSimilaires(s.candidat)
    91  	}
    92  	s.Table.Model().SetData(data)
    93  
    94  	// si la personne est déjà référencée, on désactive l'action
    95  	// Nouveau profil, qui serait alors une no-op
    96  	s.btnNewPersonne.SetEnabled(isTemporaire)
    97  }
    98  
    99  type comparateur struct {
   100  	*widgets.QGridLayout
   101  	WidgetsBasePersonne // fusion, modifiable
   102  
   103  	av WidgetsBasePersonne // lecture seule
   104  	ex WidgetsBasePersonne // lecture seule
   105  }
   106  
   107  type transferable interface {
   108  	transfer()
   109  	w1() widgets.QWidget_ITF
   110  	w2() widgets.QWidget_ITF
   111  }
   112  
   113  type tfString struct {
   114  	in, merged fields.String
   115  }
   116  
   117  func (t tfString) transfer() {
   118  	t.merged.SetData(t.in.GetData())
   119  }
   120  func (t tfString) w1() widgets.QWidget_ITF { return t.in }
   121  func (t tfString) w2() widgets.QWidget_ITF { return t.merged }
   122  
   123  type tfBool struct {
   124  	in, merged fields.Bool
   125  }
   126  
   127  func (t tfBool) transfer() {
   128  	t.merged.SetData(t.in.GetData())
   129  }
   130  func (t tfBool) w1() widgets.QWidget_ITF { return t.in }
   131  func (t tfBool) w2() widgets.QWidget_ITF { return t.merged }
   132  
   133  type tfTels struct {
   134  	in, merged fields.Tels
   135  }
   136  
   137  func (t tfTels) transfer() {
   138  	t.merged.SetData(t.in.GetData())
   139  }
   140  func (t tfTels) w1() widgets.QWidget_ITF { return t.in }
   141  func (t tfTels) w2() widgets.QWidget_ITF { return t.merged }
   142  
   143  type tfDepartement struct {
   144  	in, merged fields.Departement
   145  }
   146  
   147  func (t tfDepartement) transfer() {
   148  	t.merged.SetData(t.in.GetData())
   149  }
   150  func (t tfDepartement) w1() widgets.QWidget_ITF { return t.in }
   151  func (t tfDepartement) w2() widgets.QWidget_ITF { return t.merged }
   152  
   153  type tfSexe struct {
   154  	in, merged fields.Sexe
   155  }
   156  
   157  func (t tfSexe) transfer() {
   158  	t.merged.SetData(t.in.GetData())
   159  }
   160  func (t tfSexe) w1() widgets.QWidget_ITF { return t.in }
   161  func (t tfSexe) w2() widgets.QWidget_ITF { return t.merged }
   162  
   163  type tfDate struct {
   164  	in, merged fields.Date
   165  }
   166  
   167  func (t tfDate) transfer() {
   168  	t.merged.SetData(t.in.GetData())
   169  }
   170  func (t tfDate) w1() widgets.QWidget_ITF { return t.in }
   171  func (t tfDate) w2() widgets.QWidget_ITF { return t.merged }
   172  
   173  type tfMultiLineString struct {
   174  	in, merged fields.MultiLineString
   175  }
   176  
   177  func (t tfMultiLineString) transfer() {
   178  	t.merged.SetData(t.in.GetData())
   179  }
   180  func (t tfMultiLineString) w1() widgets.QWidget_ITF { return t.in }
   181  func (t tfMultiLineString) w2() widgets.QWidget_ITF { return t.merged }
   182  
   183  func NewComparateur(arrivant, existant, merged rd.BasePersonne, conflicts matching.Conflicts, isRefEditable bool) *comparateur {
   184  	f := comparateur{QGridLayout: widgets.NewQGridLayout2(), WidgetsBasePersonne: NewWidgetsBasePersonne(isRefEditable)}
   185  
   186  	f.av = NewWidgetsBasePersonne(false)
   187  	f.av.SetData(arrivant)
   188  	f.ex = NewWidgetsBasePersonne(false)
   189  	f.ex.SetData(existant)
   190  	f.SetData(merged)
   191  
   192  	row := 1
   193  	lab := basic.Label("<b>Profil entrant</b>")
   194  	lab.SetAlignment(core.Qt__AlignCenter)
   195  	f.AddWidget2(lab, 0, 1, 0)
   196  	lab2 := basic.Label("<b>Personne référencée</b>")
   197  	lab2.SetAlignment(core.Qt__AlignCenter)
   198  	f.AddWidget2(lab2, 0, 3, 0)
   199  	lab3 := basic.Label("<b>Résultat de la fusion</b>")
   200  	lab3.SetAlignment(core.Qt__AlignCenter)
   201  	f.AddWidget2(lab3, 0, 5, 0)
   202  
   203  	// on affiche que les champs avec des conflits
   204  	setup := func(tfArrivant, tfExistant transferable, show bool, label string) {
   205  		if !show {
   206  			return
   207  		}
   208  		f.AddWidget2(basic.Label(label), row, 0, 0)
   209  
   210  		f.AddWidget2(tfArrivant.w1(), row, 1, 0)
   211  		f.AddWidget2(tfExistant.w1(), row, 3, 0)
   212  		f.AddWidget2(tfArrivant.w2(), row, 5, 0) // w2() est le même pour arrivant ou existant
   213  
   214  		if isRefEditable {
   215  			btnArrivant := widgets.NewQToolButton(nil)
   216  			btnArrivant.SetArrowType(core.Qt__RightArrow)
   217  			btnArrivant.SetToolTip("Utiliser la valeur entrante")
   218  			btnExistant := widgets.NewQToolButton(nil)
   219  			btnExistant.SetArrowType(core.Qt__RightArrow)
   220  			btnExistant.SetToolTip("Utiliser la valeur existante")
   221  
   222  			btnArrivant.ConnectClicked(func(_ bool) { tfArrivant.transfer() })
   223  			btnExistant.ConnectClicked(func(_ bool) { tfExistant.transfer() })
   224  
   225  			f.AddWidget2(btnArrivant, row, 2, 0)
   226  			f.AddWidget2(btnExistant, row, 4, 0)
   227  		}
   228  		row += 1
   229  	}
   230  
   231  	setup(tfString{in: f.av.nom, merged: f.nom}, tfString{in: f.ex.nom, merged: f.nom}, conflicts.Nom, "Nom")
   232  	setup(tfString{in: f.av.prenom, merged: f.prenom}, tfString{in: f.ex.prenom, merged: f.prenom}, conflicts.Prenom, "Prénom")
   233  	setup(tfSexe{in: f.av.sexe, merged: f.sexe}, tfSexe{in: f.ex.sexe, merged: f.sexe}, conflicts.Sexe, "Sexe")
   234  	setup(tfDate{in: f.av.dateNaissance, merged: f.dateNaissance}, tfDate{in: f.ex.dateNaissance, merged: f.dateNaissance}, conflicts.DateNaissance, "Date de naissance")
   235  	setup(tfTels{in: f.av.tels, merged: f.tels}, tfTels{in: f.ex.tels, merged: f.tels}, conflicts.Tels, "Tels.")
   236  	setup(tfString{in: f.av.mail, merged: f.mail}, tfString{in: f.ex.mail, merged: f.mail}, conflicts.Mail, "Mail")
   237  	setup(tfMultiLineString{in: f.av.adresse, merged: f.adresse}, tfMultiLineString{in: f.ex.adresse, merged: f.adresse}, conflicts.Adresse, "Adresse")
   238  	setup(tfString{in: f.av.codePostal, merged: f.codePostal}, tfString{in: f.ex.codePostal, merged: f.codePostal}, conflicts.CodePostal, "Code postal")
   239  	setup(tfString{in: f.av.ville, merged: f.ville}, tfString{in: f.ex.ville, merged: f.ville}, conflicts.Ville, "Ville")
   240  	setup(tfString{in: f.av.pays, merged: f.pays}, tfString{in: f.ex.pays, merged: f.pays}, conflicts.Pays, "Pays")
   241  	setup(tfString{in: f.av.nomJeuneFille, merged: f.nomJeuneFille}, tfString{in: f.ex.nomJeuneFille, merged: f.nomJeuneFille}, conflicts.NomJeuneFille, "Nom de jeune fille")
   242  	setup(tfString{in: f.av.villeNaissance, merged: f.villeNaissance}, tfString{in: f.ex.villeNaissance, merged: f.villeNaissance}, conflicts.VilleNaissance, "Ville de naissance")
   243  	setup(tfDepartement{in: f.av.departementNaissance, merged: f.departementNaissance}, tfDepartement{in: f.ex.departementNaissance, merged: f.departementNaissance}, conflicts.DepartementNaissance, "Département de naissance")
   244  	setup(tfString{in: f.av.securiteSociale, merged: f.securiteSociale}, tfString{in: f.ex.securiteSociale, merged: f.securiteSociale}, conflicts.SecuriteSociale, "Sécurité sociale")
   245  	setup(tfString{in: f.av.profession, merged: f.profession}, tfString{in: f.ex.profession, merged: f.profession}, conflicts.Profession, "Profession")
   246  	setup(tfBool{in: f.av.etudiant, merged: f.etudiant}, tfBool{in: f.ex.etudiant, merged: f.etudiant}, conflicts.Etudiant, "Etudiant")
   247  	setup(tfBool{in: f.av.fonctionnaire, merged: f.fonctionnaire}, tfBool{in: f.ex.fonctionnaire, merged: f.fonctionnaire}, conflicts.Fonctionnaire, "Fonctionnaire")
   248  
   249  	return &f
   250  }
   251  
   252  func setupComparaison(arrivant, existant rd.BasePersonne, merged *rd.BasePersonne, conflicts matching.Conflicts) *widgets.QDialog {
   253  	cp := basic.Dialog("Identification")
   254  
   255  	comparateur := NewComparateur(arrivant, existant, *merged, conflicts, true)
   256  
   257  	valid := basic.Button("Identifier")
   258  	valid.SetObjectName(basic.ONAction)
   259  	valid.ConnectClicked(func(_ bool) {
   260  		comparateur.GetData(merged)
   261  		cp.Accept()
   262  	})
   263  
   264  	frame := basic.Frame()
   265  	frame.SetLayout(comparateur)
   266  	sc := widgets.NewQScrollArea(nil)
   267  	sc.SetMaximumWidth(800)
   268  	sc.SetMaximumHeight(850)
   269  	sc.SetWidget(frame)
   270  	sc.SetWidgetResizable(true)
   271  	cp.SetMinimumWidth(750)
   272  	label := basic.Label(`Les champs suivants présentent un <b>conflit</b> entre les deux personnes à rapprocher.<br/>
   273  	Si vous le souhaitez, vous pouvez trancher en éditant la dernière colonne. Vous pouvez copier directement une valeur en utilisant les flêches.`)
   274  	label.SetMaximumWidth(700)
   275  	label.SetWordWrap(true)
   276  	cp.Layout().AddWidget(label)
   277  	cp.Layout().AddWidget(sc)
   278  	cp.Layout().AddWidget(valid)
   279  	return cp
   280  }
   281  
   282  func ChoixEviteDoublonsDocuments(docs []dm.AccesDocumentPersonne) (toSupp rd.Ids) {
   283  	dialog := basic.Dialog2("Supprimer les doublons")
   284  	label := basic.Label("Les documents de <i>la personne cible</i> concernent des catégories <b>déjà présentes</b>. <br/>" +
   285  		"Les documents de la personne cible seront ajoutés à ceux déjà liés à la personne temporaires. <br/>" +
   286  		"Pour <i>éviter les doublons</i>, vous pouvez <b>supprimer</b> un ou plusieurs documents parmi la liste suivante :")
   287  	lay := widgets.NewQGridLayout2()
   288  	var cbs []*widgets.QCheckBox
   289  	valid := basic.Button("Supprimer")
   290  	valid.ConnectClicked(func(_ bool) {
   291  		dialog.Accept()
   292  	})
   293  	getToSupp := func() []int64 {
   294  		var out []int64
   295  		for index, cb := range cbs {
   296  			if cb.IsChecked() {
   297  				out = append(out, docs[index].Id)
   298  			}
   299  		}
   300  		return out
   301  	}
   302  	updateButton := func(_ bool) {
   303  		l := len(getToSupp())
   304  		label := "Continuer sans supprimer de documents"
   305  		name := basic.ONAction
   306  		if l > 0 {
   307  			label = fmt.Sprintf("Supprimer %d document(s)", l)
   308  			name = basic.ONDelete
   309  		}
   310  		valid.SetText(label)
   311  		valid.SetObjectName(name)
   312  	}
   313  
   314  	for index, doc := range docs {
   315  		cb := widgets.NewQCheckBox(nil)
   316  		cb.ConnectClicked(updateButton)
   317  		cbs = append(cbs, cb)
   318  		lay.AddWidget2(cb, index, 0, 0)
   319  		lay.AddWidget2(basic.Label(fmt.Sprintf("%s (%s)", doc.RawData().NomClient, doc.GetContrainte().Nom)),
   320  			index, 1, 0)
   321  	}
   322  
   323  	mainL := widgets.NewQVBoxLayout2(dialog)
   324  	mainL.AddWidget(label, 1, 0)
   325  	mainL.AddLayout(lay, 3)
   326  	mainL.AddWidget(valid, 0, 0)
   327  	if dialog.Exec() == 0 {
   328  		return nil // annulation
   329  	}
   330  	return getToSupp()
   331  }