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 }