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

     1  package onglets
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/benoitkugler/goACVE/client/GUI/messages"
     8  	"github.com/benoitkugler/goACVE/server/core/apiserver"
     9  
    10  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    11  
    12  	"github.com/benoitkugler/goACVE/client/GUI/common"
    13  
    14  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    15  	"github.com/benoitkugler/goACVE/server/core/utils/joomeo"
    16  
    17  	"github.com/benoitkugler/goACVE/client/GUI/basic"
    18  	"github.com/benoitkugler/goACVE/client/GUI/fields"
    19  	"github.com/benoitkugler/goACVE/client/GUI/lists"
    20  	"github.com/benoitkugler/goACVE/client/controllers"
    21  	"github.com/therecipe/qt/core"
    22  	"github.com/therecipe/qt/widgets"
    23  )
    24  
    25  const stylePreview = `<style>
    26  td, th {
    27      padding: 2px;
    28      text-align: center;
    29  }
    30  
    31  table{
    32      border-width: 1px;
    33      border-color: #000000;
    34      border-style: solid;
    35      border-collapse: collapse;
    36      margin: 0px;
    37  }
    38  </style>`
    39  
    40  func (s *SuiviCamps) Render() {
    41  	s.camps.Model().SetData(s.controller.Liste)
    42  	if s.controller.Etat.CampCurrent != nil {
    43  		s.camps.SelectRow(s.controller.Etat.CampCurrent)
    44  	}
    45  	s.ignoreClosed.SetChecked(s.controller.Etat.CritereIgnoreClosed)
    46  	s.terminated.SetData(s.controller.Etat.CritereTerminated)
    47  }
    48  
    49  func (s *SuiviCamps) ConfirmeSetupAlbumJoomeo(alreayLinked []string) bool {
    50  	msg := "Les camps suivants sont déja liés à cet album : <br/>"
    51  	msg += strings.Join(alreayLinked, "<br/>")
    52  	msg += "Etes-vous sur de vouloir lier le camp à cet album ?"
    53  	return basic.Confirmation(msg, "Lier", basic.ONAction)
    54  }
    55  
    56  func (s *SuiviCamps) ConfirmeAjoutDirecteurJoomeo(msg string) rd.OptionnalBool {
    57  	dial := basic.Dialog2("Ajout d'un contact")
    58  	var out rd.OptionnalBool
    59  	mail := basic.Button("Ajouter et envoyer un mail")
    60  	mail.SetObjectName(basic.ONAction)
    61  	mail.ConnectClicked(func(_ bool) {
    62  		out = rd.OBOui
    63  		dial.Accept()
    64  	})
    65  	noMail := basic.Button("Ajouter")
    66  	noMail.SetObjectName(basic.ONAdd)
    67  	noMail.ConnectClicked(func(_ bool) {
    68  		out = rd.OBNon
    69  		dial.Accept()
    70  	})
    71  
    72  	lay := widgets.NewQGridLayout(dial)
    73  	lay.AddWidget3(basic.Label(msg), 0, 0, 1, 3, 0)
    74  	lay.AddWidget2(noMail, 1, 0, 0)
    75  	lay.AddWidget2(mail, 1, 2, 0)
    76  	dial.Exec()
    77  	return out
    78  }
    79  
    80  func (s *SuiviCamps) ConfirmeSupprimeCamp(camp dm.AccesCamp) bool {
    81  	dia := basic.Dialog("Suppression")
    82  	dia.Layout().AddWidget(basic.Label(fmt.Sprintf("Confirmez-vous la suppression du séjour <b>%s</b> ? <br/>"+
    83  		"Cette opération est irréversible.", camp.RawData().Label())))
    84  	valid := basic.Button("Supprimer définitivement")
    85  	valid.SetObjectName(basic.ONDelete)
    86  	valid.ConnectClicked(func(_ bool) {
    87  		dia.Accept()
    88  	})
    89  	dia.Layout().AddWidget(valid)
    90  	return dia.Exec() > 0
    91  }
    92  
    93  func (s *SuiviCamps) ProposePasswordDirecteur(camp dm.AccesCamp, password string) bool {
    94  	msg := fmt.Sprintf(`Afin de simplifier la vie des directeurs, vous pouvez attribuer au séjour %s
    95  		un <b>mot de passe propre au directeur</b>. <br/>
    96  		Il pourra ainsi n'en retenir qu'<i>un seul</i> pour tous ses séjours. <br/>
    97  		Souhaitez-vous utiliser le mot de passe %s ?
    98  	`, camp.RawData().Label(), password)
    99  	return basic.Confirmation(msg, "Utiliser ce mot de passe", basic.ONAdd)
   100  }
   101  
   102  type SuiviCamps struct {
   103  	*widgets.QFrame
   104  	controller *controllers.SuiviCamps
   105  	parent     *widgets.QTabWidget
   106  	toolbar    *widgets.QToolBar
   107  
   108  	camps        lists.Table
   109  	ignoreClosed fields.Bool
   110  	terminated   fields.OptionnalBool
   111  }
   112  
   113  func newSuiviCamps(ct *controllers.SuiviCamps) *SuiviCamps {
   114  	p := SuiviCamps{QFrame: basic.Frame(), controller: ct}
   115  
   116  	p.ignoreClosed = fields.NewBool(true)
   117  	p.ignoreClosed.ConnectClicked(func(_ bool) {
   118  		ct.Etat.CritereIgnoreClosed = p.ignoreClosed.IsChecked()
   119  		ct.Reset()
   120  	})
   121  	p.terminated = fields.NewOptionnalBool(true, "Terminés", "En cours")
   122  	p.terminated.ConnectCurrentIndexChanged(func(_ int) {
   123  		ct.Etat.CritereTerminated = p.terminated.GetData()
   124  		ct.Reset()
   125  	})
   126  	rh := widgets.NewQHBoxLayout()
   127  	rh.AddWidget(basic.Label("Masquer les camps fermés aux inscriptions"), 0, 0)
   128  	rh.AddWidget(p.ignoreClosed, 0, core.Qt__AlignLeft)
   129  	rh.AddWidget(basic.Label("Etat :"), 0, core.Qt__AlignRight)
   130  	rh.AddWidget(p.terminated, 0, 0)
   131  	p.camps = lists.Table{
   132  		Liste: lists.Liste{
   133  			Headers: ct.Header,
   134  			Title:   "Séjours",
   135  			OnDoubleClick: func(acces rd.Item, _ int) {
   136  				p.editCamp(acces)
   137  			},
   138  			OnClick: func(acces rd.Item, _ int) {
   139  				ct.Etat.CampCurrent = acces.Id
   140  				p.UpdateToolbar()
   141  			},
   142  			OnRightClick: p.showContextMenu,
   143  			OnSort: func(field rd.Field, reverse bool) {
   144  				lists.DefaultSort(p.camps, field, reverse)
   145  			},
   146  			RightHeader: rh,
   147  		},
   148  	}
   149  	p.camps.Init()
   150  	p.camps.View.SetItemDelegate(lists.NewDelegateSuviCamps())
   151  	p.camps.HorizontalHeader().SetMinimumSectionSize(150)
   152  
   153  	lay := widgets.NewQVBoxLayout2(p)
   154  	lay.AddWidget(p.camps, 2, 0)
   155  	return &p
   156  }
   157  
   158  func (s *SuiviCamps) showContextMenu(acces rd.Item, pos *core.QPoint) {
   159  	camp := s.controller.Base.NewCamp(acces.Id.Int64())
   160  	menu := widgets.NewQMenu(s.camps)
   161  	ac := menu.AddAction("Copier le lien vers le formulaire d'inscription")
   162  	ac.ConnectTriggered(func(_ bool) {
   163  		lien := s.controller.LienInscription(acces.Id.Int64())
   164  		basic.Copy(lien)
   165  	})
   166  
   167  	if directeur, has := camp.GetDirecteur(); has {
   168  		ac = menu.AddAction("Aller au directeur")
   169  		ac.ConnectTriggered(func(_ bool) {
   170  			s.controller.Main().Controllers.Personnes.SelectPersonne(directeur)
   171  		})
   172  	} else {
   173  		ac = menu.AddAction("Ajouter un directeur...")
   174  		ac.ConnectTriggered(func(_ bool) {
   175  			dial := lists.NewRecherchePersonne(s.controller.Base, false)
   176  			dial.Exec()
   177  			if dial.Out == nil {
   178  				return
   179  			}
   180  			s.controller.AjouteDirecteur(camp, dial.Out.Int64())
   181  		})
   182  	}
   183  	menu.AddSeparator()
   184  	ac = menu.AddAction("Suivi financier")
   185  	ac.ConnectTriggered(func(_ bool) {
   186  		path := s.controller.ExportSuiviFinancierParticipants(camp)
   187  		basic.ShowFile(path)
   188  	})
   189  	ac = menu.AddAction("Nombre de nuitées")
   190  	ac.ConnectTriggered(func(_ bool) {
   191  		nbEnfants, nbAdultes := s.controller.CalculeNuitees(camp)
   192  		dial := basic.Dialog("Nuitées")
   193  		dial.Layout().AddWidget(basic.Label(fmt.Sprintf("Nuitées pour le séjour <i>%s</i> : <b>%d</b> adulte(s) et <b>%d</b> enfant(s).",
   194  			camp.RawData().Label(), nbAdultes, nbEnfants)))
   195  		dial.Exec()
   196  	})
   197  	if s.controller.EditRight {
   198  		menu.AddSeparator()
   199  		ac = menu.AddAction("Envoyer un sondage")
   200  		ac.ConnectTriggered(func(_ bool) {
   201  			s.inviteSondage(camp)
   202  		})
   203  		ac.SetEnabled(bool(!camp.RawData().InscriptionSimple))
   204  	}
   205  
   206  	s.camps.ShowContextMenu(menu, pos)
   207  }
   208  
   209  func (s *SuiviCamps) creer() {
   210  	var importFrom rd.OptionnalId // zero pour non import
   211  	if s.controller.Etat.CampCurrent != nil {
   212  		dial := basic.Dialog2("Importation")
   213  		camp := s.controller.Base.NewCamp(s.controller.Etat.CampCurrent.Int64())
   214  		lab := basic.Label(fmt.Sprintf("Voulez-vous <b>importer</b> le camp courant (%s) comme nouveau camp ?", camp.RawData().Label()))
   215  		oui := basic.Button("Importer")
   216  		oui.ConnectClicked(func(_ bool) {
   217  			importFrom.Valid = true // a compléter
   218  			dial.Accept()
   219  		})
   220  		oui.SetObjectName(basic.ONAction)
   221  		non := basic.Button("Créer un camp vierge")
   222  		non.ConnectClicked(func(_ bool) {
   223  			dial.Accept()
   224  		})
   225  		non.SetObjectName(basic.ONAdd)
   226  		lay := widgets.NewQGridLayout(dial)
   227  		lay.AddWidget3(lab, 0, 0, 1, 3, 0)
   228  		lay.AddWidget2(non, 1, 0, core.Qt__AlignLeft)
   229  		lay.AddWidget2(oui, 1, 2, core.Qt__AlignRight)
   230  		if dial.Exec() == 0 {
   231  			return
   232  		}
   233  	}
   234  	var camp rd.Camp
   235  	if importFrom.Valid {
   236  		toImport := s.controller.ImportCampFrom()
   237  		camp = toImport.Camp // l'id de camp est invalide
   238  		importFrom = toImport.CopyLettreFrom
   239  	}
   240  	up := EditCamp(true, false, true, camp)
   241  	if up == nil {
   242  		return
   243  	}
   244  	s.controller.CreeCamp(apiserver.CreateCampIn{Camp: *up, CopyLettreFrom: importFrom})
   245  }
   246  
   247  func (s SuiviCamps) supprimer() {
   248  	s.controller.SupprimeCamp()
   249  }
   250  
   251  func (s *SuiviCamps) bilanFinancier() {
   252  	bilans := s.controller.BilansFinanciers(s.camps.Selection())
   253  	html := stylePreview + "<table>" + "<tr>"
   254  	for _, h := range controllers.HeadersBilanFinancier {
   255  		html += "<th>" + h.Label + "</th>"
   256  	}
   257  	html += "</tr>"
   258  	for _, bilan := range bilans {
   259  		html += rd.Html(bilan, controllers.HeadersBilanFinancier)
   260  	}
   261  	html += "</table>"
   262  	dial := basic.Dialog("Bilan des camps")
   263  	sc := widgets.NewQScrollArea(nil)
   264  	sc.SetWidget(basic.Label(html))
   265  	sc.SetMinimumSize2(1000, 250)
   266  	valid := basic.Button("Exporter")
   267  	valid.SetObjectName(basic.ONAction)
   268  	valid.ConnectClicked(func(_ bool) {
   269  		path := s.controller.ExportBilanCamps(bilans)
   270  		basic.ShowFile(path)
   271  		dial.Accept()
   272  	})
   273  	dial.Layout().AddWidget(sc)
   274  	dial.Layout().AddWidget(valid)
   275  	dial.Exec()
   276  }
   277  
   278  func (s *SuiviCamps) editCamp(acces rd.Item) {
   279  	up := EditCamp(s.controller.EditRight, false, false, s.controller.Base.Camps[acces.Id.Int64()])
   280  	if up == nil {
   281  		return
   282  	}
   283  	s.controller.UpdateCamp(*up)
   284  }
   285  
   286  func (s *SuiviCamps) afficherJoomeo() {
   287  	if s.controller.Etat.CampCurrent == nil {
   288  		return
   289  	}
   290  	dial := NewPanelJooemo(s.controller)
   291  	dial.showAndLoad()
   292  	s.controller.CloseJoomeoApi()
   293  }
   294  
   295  func (s *SuiviCamps) inviteSondage(camp dm.AccesCamp) {
   296  	idFactures := s.controller.SelectFacturesByCamp(camp.Id)
   297  	if len(idFactures) == 0 {
   298  		basic.ShowError(fmt.Errorf("Aucun dossier n'est inscrit sur le séjour %s", camp.RawData().Label()))
   299  		return
   300  	}
   301  
   302  	keep := basic.Confirmation(fmt.Sprintf("Confirmez-vous l'envoi de %d message(s) ?", len(idFactures)),
   303  		"Envoyer", basic.ONAction)
   304  	if !keep {
   305  		s.controller.Main().ShowStandard("Envoi annulé", false)
   306  		return
   307  	}
   308  
   309  	ct := s.controller.Main().Controllers.SuiviDossiers
   310  	dialog := messages.NewMonitor(len(idFactures))
   311  	sender := common.NewSondagesSender(nil)
   312  	// mise en place des callbacks
   313  	sender.Monitor.OnError = dialog.OnError
   314  	sender.Monitor.OnSuccess = func(index int, out apiserver.NotifieSondagesOut) {
   315  		ct.OnSuccesNotifieSondages(out)
   316  		dialog.OnSuccess(index)
   317  	}
   318  	sender.OnFinished = dialog.ShowBilan
   319  
   320  	job := func(monitor controllers.SendSondagesSignals) ([]controllers.DossierError, error) {
   321  		return ct.EnvoiSondages(camp.Id, idFactures, monitor)
   322  	}
   323  	sender.SetupLaunch(job)
   324  }
   325  
   326  func (s *SuiviCamps) openSondages() {
   327  	u := s.controller.UrlSondages()
   328  	s.controller.Main().ShowStandard(fmt.Sprintf("Ouverture de %s ...", u), false)
   329  	basic.OpenURL(u)
   330  }
   331  
   332  // ---------------------------------- Joomeo ----------------------------------
   333  
   334  type PanelJoomeo struct {
   335  	*widgets.QDialog
   336  
   337  	controller   *controllers.SuiviCamps
   338  	stBar        *widgets.QStatusBar
   339  	tree         lists.Tree
   340  	labelAlbum   *widgets.QLabel
   341  	albums       map[string]joomeo.Album
   342  	contacts     map[string]joomeo.Contact
   343  	addDirButton *widgets.QPushButton
   344  	removeButton *widgets.QPushButton
   345  }
   346  
   347  func NewPanelJooemo(ct *controllers.SuiviCamps) *PanelJoomeo {
   348  	p := PanelJoomeo{QDialog: basic.Dialog2("Interface Joomeo"), controller: ct}
   349  	camp := ct.Base.NewCamp(p.controller.Etat.CampCurrent.Int64()) // nil already checked
   350  	attribue := basic.Button("Lier au camp")
   351  	attribue.SetEnabled(false)
   352  	attribue.SetObjectName(basic.ONAction)
   353  	attribue.ConnectClicked(p.linkAlbum)
   354  
   355  	p.removeButton = basic.Button("Délier du camp")
   356  	p.removeButton.SetObjectName(basic.ONDelete)
   357  	p.removeButton.ConnectClicked(p.removeAlbum)
   358  
   359  	p.tree = lists.Tree{
   360  		Liste: lists.Liste{
   361  			Headers: []rd.Header{
   362  				{Field: joomeo.FLabel, Label: "Nom"},
   363  				{Field: joomeo.FDate, Label: "Dernière modification"},
   364  				{Field: joomeo.FNbFiles, Label: "Nombre de photos"},
   365  			},
   366  			Placeholder: "Aucun album Joomeo.",
   367  			MinHeight:   300,
   368  			MinWidth:    500,
   369  			OnClick: func(acces rd.Item, _ int) {
   370  				_, ok := acces.Id.(joomeo.AlbumId)
   371  				attribue.SetEnabled(ok)
   372  			},
   373  		},
   374  	}
   375  	p.tree.Init()
   376  	p.tree.FoldButton.ConnectClicked(p.tree.Fold)
   377  
   378  	p.labelAlbum = basic.Label("")
   379  	p.addDirButton = basic.Button("Ajouter le directeur aux contacts")
   380  	p.addDirButton.SetObjectName(basic.ONAdd)
   381  	p.addDirButton.SetEnabled(false)
   382  	p.addDirButton.ConnectClicked(p.ajouteDirecteur)
   383  
   384  	lay := widgets.NewQGridLayout(p)
   385  
   386  	lay.AddWidget3(basic.Label(fmt.Sprintf("<b>Camp %s</b>", camp.RawData().Label())), 0, 1, 1, 2, 0)
   387  	lay.AddWidget2(basic.Label("Album Jooemo lié :"), 1, 1, 0)
   388  	lay.AddWidget2(p.labelAlbum, 1, 2, 0)
   389  	lay.AddWidget3(p.addDirButton, 3, 1, 1, 2, 0)
   390  	lay.AddWidget3(p.removeButton, 4, 1, 1, 2, 0)
   391  	lay.AddWidget3(p.tree, 0, 0, 4, 1, 0)
   392  	lay.AddWidget2(attribue, 4, 0, 0)
   393  	p.stBar = widgets.NewQStatusBar(nil)
   394  	common.MainWindow.StatusBar().ConnectMessageChanged(func(message string) {
   395  		p.stBar.ShowMessage(message, 0)
   396  	})
   397  	lay.AddWidget3(p.stBar, 5, 0, 1, 3, 0)
   398  	return &p
   399  }
   400  
   401  func (p *PanelJoomeo) showAndLoad() {
   402  	p.SetEnabled(false)
   403  	p.stBar.ShowMessage("Chargement des informations Joomeo...", 0)
   404  	basic.Delay(func() {
   405  		var folder []rd.ItemChilds
   406  		folder, p.albums, p.contacts = p.controller.GetAlbumsContactsJoomeo()
   407  		p.tree.Model().SetData(folder)
   408  		p.update()
   409  		p.SetEnabled(true)
   410  		p.stBar.ShowMessage("Informations chargées", 3000)
   411  	})
   412  	p.Exec()
   413  }
   414  
   415  func (p *PanelJoomeo) update() {
   416  	camp := p.controller.Base.NewCamp(p.controller.Etat.CampCurrent.Int64())
   417  	alb, has := p.albums[string(camp.RawData().JoomeoAlbumId)]
   418  	lab := alb.Label
   419  	if !has {
   420  		lab = "<i>Aucun</i>"
   421  	}
   422  	p.labelAlbum.SetText(lab)
   423  	p.addDirButton.SetEnabled(has)
   424  	p.removeButton.SetEnabled(has)
   425  }
   426  
   427  func (p *PanelJoomeo) linkAlbum(_ bool) {
   428  	p.SetEnabled(false)
   429  	if item, ok := p.tree.CurrentData(); ok {
   430  		if albumId, ok := item.Id.(joomeo.AlbumId); ok {
   431  			p.controller.SetupAlbumJoomeo(albumId.JoomeoId)
   432  		}
   433  	}
   434  	p.SetEnabled(true)
   435  	p.update()
   436  }
   437  
   438  func (p *PanelJoomeo) removeAlbum(_ bool) {
   439  	p.SetEnabled(false)
   440  	p.controller.RemoveAlbumJoomeo()
   441  	p.SetEnabled(true)
   442  	p.update()
   443  }
   444  
   445  func (p *PanelJoomeo) ajouteDirecteur(_ bool) {
   446  	p.SetEnabled(false)
   447  	p.controller.AjouteDirecteurJoomeo(p.albums)
   448  	p.SetEnabled(true)
   449  }