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

     1  package onglets
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/benoitkugler/goACVE/client/GUI/onglets/notifications"
     8  
     9  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    10  
    11  	"github.com/benoitkugler/goACVE/client/GUI/fields"
    12  
    13  	"github.com/benoitkugler/goACVE/client/GUI/lists"
    14  
    15  	"github.com/therecipe/qt/core"
    16  
    17  	"github.com/benoitkugler/goACVE/client/GUI/basic"
    18  	"github.com/benoitkugler/goACVE/client/GUI/common"
    19  	"github.com/benoitkugler/goACVE/client/controllers"
    20  	"github.com/therecipe/qt/widgets"
    21  
    22  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    23  )
    24  
    25  func (c *Camps) Render() {
    26  	c.camps.table.Model().SetData(c.controller.ListeCamps)
    27  	if c.controller.Etat.CampCurrent != nil {
    28  		c.camps.table.SelectRow(c.controller.Etat.CampCurrent)
    29  	}
    30  	c.inscrits.Model().SetData(c.controller.ListeInscrits)
    31  	c.attente.Model().SetData(c.controller.ListeAttente)
    32  	if idParticipant := c.controller.Etat.IdParticipant; idParticipant != nil {
    33  		c.inscrits.SelectRow(idParticipant) // pas très subtil
    34  		c.attente.SelectRow(idParticipant)  // mais pourquoi pas
    35  	}
    36  	c.stats.SetData(c.controller.GetStats())
    37  	c.updateToInscrits()
    38  
    39  	// on affiche une fois au démarrage les notifications
    40  	if c.IsActif() && !c.hasShownNotifications {
    41  		inscrits, attentes := c.controller.CheckListeAttentes()
    42  		if len(attentes) > 0 { // les notifs inscrits sont moins importantes
    43  			c.hasShownNotifications = true
    44  			c.notifieAttentesExt(inscrits, attentes)
    45  		}
    46  	}
    47  }
    48  
    49  func (c *Camps) updateToInscrits() {
    50  	var enabledToInscrit bool
    51  	if idPart, ok := c.controller.Etat.IdParticipant.(rd.IdParticipant); ok {
    52  		isInscrit := c.controller.Base.NewParticipant(idPart.Int64()).RawData().ListeAttente.IsInscrit()
    53  		enabledToInscrit = !isInscrit
    54  	}
    55  	c.toInscrits.SetEnabled(enabledToInscrit)
    56  }
    57  
    58  func (c *Camps) ConfirmeNotifPlaceLiberee(info string) (keep bool, notifie bool) {
    59  	d := basic.Dialog("Passage en liste principale")
    60  	d.Layout().AddWidget(basic.Label(info))
    61  	btnEnvoi := basic.Button("Envoyer un message")
    62  	btnEnvoi.ConnectClicked(func(_ bool) {
    63  		notifie = true
    64  		d.Accept()
    65  	})
    66  	btnNoEnvoi := basic.Button("Ne pas notifier")
    67  	btnNoEnvoi.ConnectClicked(func(_ bool) {
    68  		notifie = false
    69  		d.Accept()
    70  	})
    71  
    72  	btns := basic.Frame()
    73  	btnsL := widgets.NewQHBoxLayout2(btns)
    74  	btnsL.AddStretch(2)
    75  	btnsL.AddWidget(btnNoEnvoi, 1, 0)
    76  	btnsL.AddWidget(btnEnvoi, 1, 0)
    77  	d.Layout().AddWidget(btns)
    78  	keep = d.Exec() > 0
    79  	return keep, notifie
    80  }
    81  
    82  func (c *Camps) ConfirmeAjoutParticipant(camp dm.AccesCamp, personne rd.Personne, causes []string) bool {
    83  	msg := fmt.Sprintf("Confirmez-vous l'ajout de %s au %s ?", personne.NomPrenom(), camp.RawData().Label())
    84  	if len(causes) > 0 {
    85  		for i, cause := range causes {
    86  			causes[i] = fmt.Sprintf("<b>%s</b>", cause)
    87  		}
    88  		msg += fmt.Sprintf(`<br/>Certaines conditions ne sont pas remplies : <br/> 
    89  		<b>%s</b> <br/><br/>`, strings.Join(causes, "<br/>"))
    90  	}
    91  	return basic.Confirmation(msg, "Ajouter", basic.ONAdd)
    92  }
    93  
    94  func (c *Camps) ConfirmeSupprimeParticipant(msg string) bool {
    95  	return basic.Confirmation(msg, "Supprimer", basic.ONDelete)
    96  }
    97  
    98  type Camps struct {
    99  	*widgets.QFrame
   100  	controller *controllers.Camps
   101  	parent     *widgets.QTabWidget
   102  	toolbar    *widgets.QToolBar
   103  
   104  	camps             listeCamps
   105  	inscrits, attente lists.Table
   106  	stats             common.Statistics
   107  	toInscrits        *widgets.QPushButton
   108  
   109  	hasShownNotifications bool
   110  }
   111  
   112  func newCamps(ct *controllers.Camps) *Camps {
   113  	p := Camps{QFrame: basic.Frame(), controller: ct}
   114  	p.camps = newListeCamps(ct)
   115  	p.inscrits = lists.Table{
   116  		Liste: lists.Liste{
   117  			Headers: ct.HeaderInscrits,
   118  			OnClick: func(acces rd.Item, _ int) {
   119  				p.onClick(acces, true)
   120  			},
   121  			OnDoubleClick: func(acces rd.Item, _ int) {
   122  				p.onDoubleClick(acces)
   123  			},
   124  			OnRightClick: func(acces rd.Item, pos *core.QPoint) {
   125  				p.onRightClick(acces, p.inscrits.MapToGlobal(pos))
   126  			},
   127  			OnSort: func(field rd.Field, reverse bool) {
   128  				lists.DefaultSort(p.inscrits, field, reverse)
   129  			},
   130  		},
   131  	}
   132  	p.inscrits.Init()
   133  
   134  	p.toInscrits = basic.Button("Passer en liste principale")
   135  	p.toInscrits.SetObjectName(basic.ONAction)
   136  	p.toInscrits.ConnectClicked(func(_ bool) {
   137  		part := ct.Etat.IdParticipant
   138  		if part == nil {
   139  			return
   140  		}
   141  		ct.MoveToInscrits(part)
   142  	})
   143  	p.toInscrits.SetEnabled(false)
   144  
   145  	rH := widgets.NewQHBoxLayout()
   146  	rH.AddWidget(p.toInscrits, 1, 0)
   147  	p.attente = lists.Table{
   148  		Liste: lists.Liste{
   149  			Headers: ct.HeaderAttente,
   150  			OnClick: func(acces rd.Item, _ int) {
   151  				p.onClick(acces, false)
   152  			},
   153  			OnDoubleClick: func(acces rd.Item, _ int) {
   154  				p.onDoubleClick(acces)
   155  			},
   156  			Title:       "Liste d'attente",
   157  			RightHeader: rH,
   158  			OnRightClick: func(acces rd.Item, pos *core.QPoint) {
   159  				p.onRightClick(acces, p.attente.MapToGlobal(pos))
   160  			},
   161  			OnSort: func(field rd.Field, reverse bool) {
   162  				lists.DefaultSort(p.attente, field, reverse)
   163  			},
   164  		},
   165  	}
   166  	p.attente.Init()
   167  
   168  	p.stats = common.NewStatistics(true)
   169  	lay := widgets.NewQGridLayout(p)
   170  	lay.AddLayout2(p.camps, 0, 0, 4, 1, 0)
   171  	lay.AddWidget2(p.stats, 0, 1, 0)
   172  	lay.AddWidget2(p.inscrits, 1, 1, 0)
   173  	lay.AddWidget2(p.attente, 3, 1, 0)
   174  	lay.SetColumnStretch(1, 4)
   175  	lay.SetRowStretch(1, 3)
   176  	lay.SetRowStretch(3, 2)
   177  	return &p
   178  }
   179  
   180  func (c *Camps) onClick(acces rd.Item, fromInscrits bool) {
   181  	c.controller.Etat.IdParticipant = acces.Id
   182  	c.UpdateToolbar()
   183  	c.updateToInscrits()
   184  	if fromInscrits {
   185  		c.attente.View.ClearSelection()
   186  	} else {
   187  		c.inscrits.View.ClearSelection()
   188  	}
   189  }
   190  
   191  func (c *Camps) onDoubleClick(acces rd.Item) {
   192  	if _, isSimple := acces.Id.(rd.IdParticipantsimple); isSimple {
   193  		ac := c.controller.Base.NewParticipantsimple(acces.Id.Int64())
   194  		modif := EditParticipantsimple(c.controller.Base, c.controller.ParticipantsRight, ac.RawData(),
   195  			rd.NewOptionnalId(ac.GetPersonne().Id),
   196  			rd.NewOptionnalId(ac.GetCamp().Id))
   197  		if modif != nil {
   198  			c.controller.UpdateParticipantsimple(*modif)
   199  		}
   200  	} else {
   201  		ac := c.controller.Base.NewParticipant(acces.Id.Int64())
   202  		modif := EditParticipant(c.controller.Base, c.controller.ParticipantsRight, ac.RawData(),
   203  			rd.NewOptionnalId(ac.GetPersonne().Id), ac.GetCamp().Id)
   204  		if modif != nil {
   205  			c.controller.UpdateParticipant(*modif)
   206  		}
   207  	}
   208  }
   209  
   210  func (c *Camps) onRightClick(acces rd.Item, pos *core.QPoint) {
   211  	var part dm.Inscrit
   212  	switch id := acces.Id.(type) {
   213  	case rd.IdParticipantsimple:
   214  		part = c.controller.Base.NewParticipantsimple(id.Int64())
   215  	case rd.IdParticipant:
   216  		part = c.controller.Base.NewParticipant(id.Int64())
   217  	default:
   218  		return
   219  	}
   220  	menu := widgets.NewQMenu(c)
   221  	ac := menu.AddAction("Aller à la personne")
   222  	ac.ConnectTriggered(func(_ bool) {
   223  		c.controller.Main().Controllers.Personnes.SelectPersonne(part.GetPersonne())
   224  	})
   225  
   226  	fac, has := part.GetFacture()
   227  	if has {
   228  		ac := menu.AddAction("Aller au dossier")
   229  		ac.ConnectTriggered(func(_ bool) {
   230  			c.controller.Main().Controllers.SuiviDossiers.SelectFacture(fac)
   231  		})
   232  	}
   233  	menu.Exec2(pos, nil)
   234  }
   235  
   236  func (c *Camps) creerParticipant() {
   237  	idCamp := c.controller.Etat.CampCurrent
   238  	if idCamp == nil {
   239  		return
   240  	}
   241  	camp := c.controller.Base.NewCamp(idCamp.Int64())
   242  	if camp.RawData().InscriptionSimple {
   243  		part := CreeParticipantsimple(c.controller.Base, rd.NewOptionnalId(camp.Id))
   244  		if part != nil {
   245  			c.controller.CreeParticipantsimple(*part)
   246  		}
   247  	} else {
   248  		part := CreeParticipant(c.controller.Base, idCamp.Int64())
   249  		if part != nil {
   250  			c.controller.CreeParticipant(*part)
   251  		}
   252  	}
   253  }
   254  
   255  func (c *Camps) supprimerParticipant() {
   256  	c.controller.SupprimeParticipantCourant()
   257  }
   258  
   259  func (c *Camps) changerParticipant() {
   260  	dial := lists.NewRechercheCamp(c.controller.Base)
   261  	dial.Exec()
   262  	if dial.Out == nil {
   263  		return
   264  	}
   265  	c.controller.ChangeCamp(dial.Out.Int64())
   266  }
   267  func (c *Camps) exporterParticipants() {
   268  	dial := basic.Dialog2("Exporter les participants")
   269  	lay := widgets.NewQFormLayout(dial)
   270  	bus := fields.NewBus(true)
   271  
   272  	triGroupe := widgets.NewQCheckBox(nil)
   273  	withAttente := widgets.NewQCheckBox(nil)
   274  	valid := basic.Button("Exporter")
   275  	valid.SetObjectName(basic.ONAction)
   276  	valid.ConnectClicked(func(_ bool) {
   277  		dial.Accept()
   278  	})
   279  
   280  	lay.AddRow3("Filtrer les trajets en bus", bus)
   281  	lay.AddRow3("Trier par groupe", triGroupe)
   282  	lay.AddRow3("Inclure la liste d'attente", withAttente)
   283  	lay.AddRow5(valid)
   284  	if dial.Exec() == 0 {
   285  		return
   286  	}
   287  	path := c.controller.ExportParticipants(bus.GetData(), triGroupe.IsChecked(), withAttente.IsChecked())
   288  	basic.ShowFile(path)
   289  }
   290  
   291  func (c *Camps) notifieAttentes() {
   292  	inscrits, attentes := c.controller.CheckListeAttentes()
   293  	c.notifieAttentesExt(inscrits, attentes)
   294  }
   295  
   296  func (c *Camps) notifieAttentesExt(inscrits, attentes []controllers.DiagnosticListeAttente) {
   297  	notifAttentes, notifInscrits := notifications.NewList(), notifications.NewList()
   298  
   299  	setupList := func(liste []controllers.DiagnosticListeAttente, target notifications.List) {
   300  		for _, row := range liste {
   301  			row := row // closure in loop
   302  			part := row.Participant.RawData()
   303  			text := fmt.Sprintf("<i>%s</i> - <a href>%s</b>: %s --> <b>%s</b>",
   304  				row.Participant.GetCamp().RawData().Label(), row.Participant.GetPersonne().RawData().NomPrenom(),
   305  				part.ListeAttente.Statut, row.Hint)
   306  			target.AddNotification(text, func() {
   307  				c.controller.SelectParticipant(row.Participant)
   308  			})
   309  		}
   310  	}
   311  
   312  	// le setup peut être visuellement bloquant
   313  	basic.Delay(func() { setupList(inscrits, notifInscrits) })
   314  	basic.Delay(func() { setupList(attentes, notifAttentes) })
   315  
   316  	d := basic.Dialog("Notifications sur les listes d'attentes")
   317  	d.Layout().AddWidget(basic.Label(`Les listes suivantes indiquent les participants pour lesquels
   318  le statut actuel n'est pas celui conseillé (<i>actuel</i> --> <i>conseillé</i>).`))
   319  
   320  	d.Layout().AddWidget(basic.Label("Participants sur <b>liste d'attente</b> :"))
   321  	if len(attentes) == 0 {
   322  		d.Layout().AddWidget(basic.Label("Aucune notification pour les listes d'attentes. <br/>"))
   323  	} else {
   324  		d.Layout().AddWidget(notifAttentes)
   325  	}
   326  
   327  	d.Layout().AddWidget(basic.Label("Participants sur <b>liste principale</b> :"))
   328  	if len(inscrits) == 0 {
   329  		d.Layout().AddWidget(basic.Label("Aucune notification pour les listes d'inscrits."))
   330  	} else {
   331  		d.Layout().AddWidget(notifInscrits)
   332  	}
   333  
   334  	d.Show() // non modal
   335  }
   336  
   337  // CreeParticipant propose la création d'un participant.
   338  // En cas d'annulation, out vaut `nil`
   339  func CreeParticipant(base *dm.BaseLocale, idCamp int64) *rd.Participant {
   340  	return EditParticipant(base, true, rd.Participant{}, rd.OptionnalId{}, idCamp)
   341  }
   342  
   343  // CreeParticipantsimple propose la création d'un participant à un séjour simple.
   344  // En cas d'annulation, out vaut `nil`
   345  func CreeParticipantsimple(base *dm.BaseLocale, idCamp rd.OptionnalId) *rd.Participantsimple {
   346  	return EditParticipantsimple(base, true, rd.Participantsimple{}, rd.OptionnalId{}, idCamp)
   347  }
   348  
   349  func EditParticipant(base *dm.BaseLocale, editable bool, participant rd.Participant,
   350  	idPersonne rd.OptionnalId, idCamp int64) *rd.Participant {
   351  	d := common.NewFicheParticipant(base, editable, idCamp)
   352  	d.SetData(participant, idPersonne)
   353  	dialog := common.NewDetails(d, editable)
   354  	if dialog.Exec() == 0 {
   355  		return nil
   356  	}
   357  	part := d.GetData()
   358  	return &part
   359  }
   360  
   361  func EditParticipantsimple(base *dm.BaseLocale, editable bool, participant rd.Participantsimple,
   362  	idPersonne rd.OptionnalId, idCamp rd.OptionnalId) *rd.Participantsimple {
   363  	d := common.NewFicheParticipantsimple(base, editable)
   364  	d.ActiveCamp(idCamp.IsNil())
   365  	d.SetData(participant, idPersonne, idCamp)
   366  	dialog := common.NewDetails(&d, editable)
   367  	if dialog.Exec() == 0 {
   368  		return nil
   369  	}
   370  	part := d.GetData()
   371  	return &part
   372  }
   373  
   374  // ---------------------------- Liste détaillée ----------------------------
   375  type listeCamps struct {
   376  	*widgets.QVBoxLayout
   377  	table lists.Table
   378  
   379  	controller *controllers.Camps
   380  }
   381  
   382  func newListeCamps(ct *controllers.Camps) listeCamps {
   383  	l := listeCamps{QVBoxLayout: widgets.NewQVBoxLayout(), controller: ct}
   384  	l.table.Headers = []rd.Header{
   385  		{Field: dm.CampLabel, Label: ""},
   386  	}
   387  	l.table.HideHeaders = true
   388  	l.table.MaxWidth = 200
   389  	l.table.OnRightClick = func(acces rd.Item, pos *core.QPoint) {
   390  		menu := widgets.NewQMenu(l.table)
   391  		acCamp := l.controller.Base.NewCamp(acces.Id.Int64())
   392  		if directeur, has := acCamp.GetDirecteur(); has {
   393  			ac := menu.AddAction("Aller au directeur")
   394  			ac.ConnectTriggered(func(_ bool) {
   395  				l.controller.Main().Controllers.Personnes.SelectPersonne(directeur)
   396  			})
   397  			ac = menu.AddAction("Envoyer un mail au directeur...")
   398  			ac.ConnectTriggered(func(_ bool) {
   399  				to := string(directeur.RawData().Mail)
   400  				m := common.NewMailLibre(to, "")
   401  				if m.Exec() == 0 {
   402  					return
   403  				}
   404  				ct.Main().NotifieDirecteur(to, m.Text.ToHtml())
   405  			})
   406  			menu.AddSeparator()
   407  		}
   408  		ac := menu.AddAction("Détails...")
   409  		ac.ConnectTriggered(func(_ bool) {
   410  			l.editCamp(acces.Id.Int64())
   411  		})
   412  		ac = menu.AddAction("Documents du camp...")
   413  		ac.ConnectTriggered(func(_ bool) {
   414  			l.showDocuments(acces.Id.Int64())
   415  		})
   416  		ac = menu.AddAction("Suivi financier")
   417  		ac.ConnectTriggered(func(_ bool) {
   418  			path := l.controller.ExportSuiviFinancier(acces.Id.Int64())
   419  			basic.ShowFile(path)
   420  		})
   421  		l.table.ShowContextMenu(menu, pos)
   422  	}
   423  	l.table.OnClick = func(acces rd.Item, _ int) {
   424  		l.controller.Etat.CampCurrent = acces.Id
   425  		l.controller.Reset()
   426  	}
   427  	l.table.Init()
   428  	l.table.View.(*widgets.QTableView).VerticalHeader().SetSectionResizeMode(widgets.QHeaderView__ResizeToContents)
   429  
   430  	// camps terminés
   431  	cb := widgets.NewQCheckBox2("Afficher les camps terminés", nil)
   432  	cb.SetChecked(l.controller.Etat.ShowCampsTerminated)
   433  	cb.ConnectClicked(func(checked bool) {
   434  		l.controller.Etat.ShowCampsTerminated = checked
   435  		l.controller.Reset()
   436  	})
   437  
   438  	l.AddWidget(l.table, 4, 0)
   439  	l.AddWidget(cb, 0, 0)
   440  	return l
   441  }
   442  
   443  func (l *listeCamps) editCamp(id int64) {
   444  	up := EditCamp(l.controller.CampsRight, true, false, l.controller.Base.Camps[id])
   445  	if up == nil {
   446  		return
   447  	}
   448  	l.controller.UpdateCamp(*up)
   449  }
   450  
   451  func EditCamp(editable, minimalEdit, enableInscriptionSimple bool, camp rd.Camp) *rd.Camp {
   452  	fiche := common.NewFicheCamp(editable, minimalEdit, enableInscriptionSimple)
   453  	fiche.SetData(camp)
   454  	det := common.NewDetails(&fiche, editable)
   455  	if det.Exec() == 0 {
   456  		return nil
   457  	}
   458  	camp = fiche.GetData()
   459  	return &camp
   460  }
   461  
   462  func (l *listeCamps) showDocuments(id int64) {
   463  	dial := basic.Dialog("Documents")
   464  	ac := l.controller.Base.NewCamp(id)
   465  	if ac.RawData().Envois.Locked {
   466  		dial.Layout().AddWidget(basic.Label("Attention, l'envoi des documents est encore <b>verrouillé</b>. <br/>" +
   467  			"<i>Le directeur lèvera ce verrou une fois les documents prêts.</i>"))
   468  	}
   469  	list := common.NewListePiecesJointes(l.controller.Main(),
   470  		fmt.Sprintf("Document du %s", ac.RawData().Label()),
   471  		controllers.GetDocuments(ac))
   472  	dial.Layout().AddWidget(list)
   473  	dial.Exec()
   474  }