github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/datamodel/search.go (about) 1 package datamodel 2 3 import ( 4 "regexp" 5 "sort" 6 "strings" 7 8 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 9 "github.com/benoitkugler/goACVE/server/core/rawdata/matching" 10 ) 11 12 // Fonctions de recherche rapide (par string) 13 14 func matchSearch(regexps []*regexp.Regexp, dataString string) bool { 15 dataString = matching.RemoveAccents(dataString) 16 for _, r := range regexps { 17 if !r.MatchString(dataString) { 18 return false 19 } 20 } 21 return true 22 } 23 24 func prepareSearch(pattern string) (out []*regexp.Regexp, err error) { 25 if pattern == "*" { 26 return out, nil 27 } 28 29 for _, s := range strings.Split(pattern, " ") { 30 if strings.TrimSpace(s) == "" { 31 continue 32 } 33 r, err := regexp.Compile("(?i)" + matching.RemoveAccents(s)) 34 if err != nil { 35 return out, err 36 } 37 out = append(out, r) 38 } 39 return out, nil 40 } 41 42 func personneToString(raw rd.Personne) string { 43 return raw.NomPrenom().String() 44 } 45 46 func organismeToString(raw rd.Organisme) string { 47 return raw.Nom.String() 48 } 49 50 func aideToString(raw rd.Aide, base *BaseLocale) string { 51 part := base.Participants[raw.IdParticipant] 52 struc := base.Structureaides[raw.IdStructureaide] 53 return participantToString(part, base) + " " + structureaideToString(struc) 54 } 55 56 func campToString(raw rd.Camp) string { 57 return string(raw.Nom) + " " + raw.DateDebut.String() + " " + raw.DateFin.String() 58 } 59 60 func groupeToString(raw rd.Groupe, base *BaseLocale) string { 61 return string(raw.Nom) + " " + campToString(base.Camps[raw.IdCamp]) 62 } 63 64 func participantToString(raw rd.Participant, base *BaseLocale) string { 65 pers := base.Personnes[raw.IdPersonne] 66 camp := base.Camps[raw.IdCamp] 67 return personneToString(pers) + " " + string(camp.Nom) + " " + camp.DateDebut.String() 68 } 69 70 func structureaideToString(raw rd.Structureaide) string { 71 return string(raw.Nom + " " + raw.Immatriculation) 72 } 73 74 func factureToString(raw rd.Facture, base *BaseLocale) string { 75 pers := base.Personnes[raw.IdPersonne] 76 return personneToString(pers) 77 } 78 79 func (b *BaseLocale) RechercheRapidePersonnes(withOrganismes bool, pattern string) (out rd.Table) { 80 rs, err := prepareSearch(pattern) 81 if err != nil { 82 return 83 } 84 85 for i, v := range b.Personnes { 86 if !bool(v.IsTemporaire) && matchSearch(rs, personneToString(v)) { 87 out = append(out, b.NewPersonne(i).AsItem(0)) 88 } 89 } 90 91 if !withOrganismes { 92 return out 93 } 94 95 for i, o := range b.Organismes { 96 if matchSearch(rs, organismeToString(o)) { 97 out = append(out, b.NewOrganisme(i).AsItem()) 98 } 99 } 100 101 return 102 } 103 104 func (b *BaseLocale) RechercheRapideAides(pattern string) (out rd.Table) { 105 rs, err := prepareSearch(pattern) 106 if err != nil { 107 return 108 } 109 110 for i, v := range b.Aides { 111 if matchSearch(rs, aideToString(v, b)) { 112 out = append(out, b.NewAide(i).AsItem()) 113 } 114 } 115 return 116 } 117 118 func (b *BaseLocale) RechercheRapideCamps(pattern string) (out rd.Table) { 119 rs, err := prepareSearch(pattern) 120 if err != nil { 121 return 122 } 123 124 _, cache1 := b.ResoudParticipants() 125 cache2 := b.ResoudParticipantsimples() 126 for i, v := range b.Camps { 127 if matchSearch(rs, campToString(v)) { 128 out = append(out, b.NewCamp(i).AsItem(cache1, cache2)) 129 } 130 } 131 return 132 } 133 134 func (b *BaseLocale) RechercheRapideGroupes(pattern string) (out rd.Table) { 135 rs, err := prepareSearch(pattern) 136 if err != nil { 137 return 138 } 139 140 for i, v := range b.Groupes { 141 if matchSearch(rs, groupeToString(v, b)) { 142 out = append(out, b.NewGroupe(i).AsItem()) 143 } 144 } 145 return 146 } 147 148 // RechercheRapideParticipants ignore les équipiers et les participants simples 149 func (b *BaseLocale) RechercheRapideParticipants(pattern string) (out rd.Table) { 150 rs, err := prepareSearch(pattern) 151 if err != nil { 152 return 153 } 154 155 var tmp []rd.Participant 156 for _, v := range b.Participants { 157 if matchSearch(rs, participantToString(v, b)) { 158 tmp = append(tmp, v) 159 } 160 } 161 _, cache := b.ResoudParticipants() 162 getDate := func(i int) int { 163 return b.Camps[tmp[i].IdCamp].DateDebut.Time().Year() 164 } 165 hasDoss := func(i int) bool { 166 return tmp[i].IdFacture.Valid 167 } 168 // Tri par date de participation 169 sort.Slice(tmp, func(i, j int) bool { return getDate(i) > getDate(j) }) 170 // Participant libre d'abord 171 sort.SliceStable(tmp, func(i, j int) bool { return !hasDoss(i) && hasDoss(j) }) 172 out = make(rd.Table, len(tmp)) 173 for index, rawPart := range tmp { 174 out[index] = b.NewParticipant(rawPart.Id).AsItem(cache) 175 } 176 return out 177 } 178 179 func (b *BaseLocale) RechercheRapideStructureaides(pattern string) rd.Table { 180 rs, err := prepareSearch(pattern) 181 if err != nil { 182 return nil 183 } 184 185 var tmp []int64 186 for i, v := range b.Structureaides { 187 if matchSearch(rs, structureaideToString(v)) { 188 tmp = append(tmp, i) 189 } 190 } 191 192 // Tri par utilisations des structures 193 frequences := make(map[int64]int, len(b.Structureaides)) 194 for _, aide := range b.Aides { 195 frequences[aide.IdStructureaide] += 1 196 } 197 sort.Slice(tmp, func(i, j int) bool { return frequences[tmp[i]] > frequences[tmp[j]] }) 198 199 out := make(rd.Table, len(tmp)) 200 for index, idStructure := range tmp { 201 out[index] = b.NewStructureaide(idStructure).AsItem() 202 } 203 return out 204 } 205 206 func (b *BaseLocale) RechercheRapideFactures(pattern string) (out rd.Table) { 207 rs, err := prepareSearch(pattern) 208 if err != nil { 209 return 210 } 211 cache1, _ := b.ResoudParticipants() 212 cache2 := b.ResoudMessages() 213 cache3 := b.ResoudPaiements() 214 for i, v := range b.Factures { 215 if matchSearch(rs, factureToString(v, b)) { 216 ac := b.NewFacture(i) 217 out = append(out, ac.AsItem(cache1, cache2, cache3)) 218 } 219 } 220 return 221 } 222 223 // RechercheDetailleeChilds renvoie la liste filtrée, avec les champs Bold mis à jour 224 // `in` peut aussi être modifiée et devrait être ignorée après l'appel 225 func RechercheDetailleeChilds(in []rd.ItemChilds, pattern string, entete []rd.Header) (out []rd.ItemChilds) { 226 rs, err := prepareSearch(pattern) 227 if err != nil || len(rs) == 0 { 228 return 229 } 230 // alloue un cache une fois pour toute 231 cacheStrings := make([]string, len(entete)) 232 233 var groupeMatch, rowMatch bool 234 for _, row := range in { 235 groupeMatch = false // il suffit d'une seule ligne (parent ou enfant) qui match 236 237 // on analyse le parent ... 238 row.Item, rowMatch = checkItem(entete, row.Item, cacheStrings, rs) 239 if rowMatch { 240 groupeMatch = true 241 } 242 243 // ... puis les enfants 244 for indexChild, itemChild := range row.Childs { 245 row.Childs[indexChild], rowMatch = checkItem(entete, itemChild, cacheStrings, rs) 246 if rowMatch { 247 groupeMatch = true 248 } 249 } 250 251 if groupeMatch { 252 out = append(out, row) 253 } 254 } 255 return out 256 } 257 258 func RechercheDetaillee(in rd.Table, pattern string, entete []rd.Header) rd.Table { 259 // on transforme temporairement les Items en ItemChilds ... 260 tmp := make([]rd.ItemChilds, len(in)) 261 for i, v := range in { 262 tmp[i] = rd.ItemChilds{Item: v} 263 } 264 tmp = RechercheDetailleeChilds(tmp, pattern, entete) 265 // ... et on reconvertit dans l'autre sens 266 out := make(rd.Table, len(tmp)) 267 for i, v := range tmp { 268 out[i] = v.Item 269 } 270 return out 271 } 272 273 // renvoie l'item avec les champs Bolds mis à jour, et `true` si l'item passe la recherche 274 func checkItem(entete []rd.Header, item rd.Item, cacheStrings []string, rs []*regexp.Regexp) (rd.Item, bool) { 275 rowMatch := true 276 277 // processe Data une seule fois 278 for indexE, fieldE := range entete { 279 cacheStrings[indexE] = matching.RemoveAccents(item.Fields.Data(fieldE.Field).String()) 280 } 281 282 // init & reset 283 item.Bolds = make(rd.B) 284 285 for _, re := range rs { 286 reFound := false 287 for index, field := range entete { 288 if re.MatchString(cacheStrings[index]) { 289 reFound = true 290 item.Bolds[field.Field] = true 291 } 292 } 293 294 // toutes les regexps doivent matcher (au moins une case) 295 if !reFound { 296 rowMatch = false 297 break 298 } 299 } 300 301 return item, rowMatch 302 } 303 304 // ChercheSimilaires applique la recherche de similarité sur toutes 305 // la base 306 func (b *BaseLocale) ChercheSimilaires(in matching.PatternsSimilarite) rd.Table { 307 pers := b.GetRawPersonnes() 308 sm, res := matching.ChercheSimilaires(pers, in) 309 out := make(rd.Table, len(res)) 310 for i, r := range res { 311 ac := b.NewPersonne(r.Personne.Id) 312 pertinence := float64(r.Score) / float64(sm) 313 out[i] = ac.AsItem(pertinence) 314 } 315 return out 316 }