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

     1  package onglets
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/benoitkugler/goACVE/client/GUI/onglets/notifications"
    10  
    11  	"github.com/benoitkugler/goACVE/client/GUI/messages"
    12  	"github.com/benoitkugler/goACVE/server/core/apiserver"
    13  
    14  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    15  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    16  
    17  	"github.com/benoitkugler/goACVE/client/GUI/fields"
    18  	"github.com/benoitkugler/goACVE/client/GUI/lists"
    19  
    20  	"github.com/benoitkugler/goACVE/client/GUI/common"
    21  
    22  	"github.com/benoitkugler/goACVE/client/GUI/basic"
    23  
    24  	"github.com/therecipe/qt/core"
    25  
    26  	"github.com/therecipe/qt/widgets"
    27  
    28  	"github.com/benoitkugler/goACVE/client/controllers"
    29  )
    30  
    31  func (t *SuiviDossiers) Render() {
    32  	t.dossiers.Model().SetData(t.controller.Liste)
    33  	t.dossiers.SetSearch(t.controller.Etat.Recherche)
    34  	t.updateSelection()
    35  
    36  	t.acquite.SetData(t.controller.Etat.CritereAcquite)
    37  	t.attente.SetData(string(t.controller.Etat.CritereAttente))
    38  	t.camp.Update()
    39  	t.camp.SetData(t.controller.Etat.CritereCamp)
    40  
    41  	// on affiche une fois au démarrage les notifications
    42  	if t.IsActif() && !t.hasShownNotifications {
    43  		if t.controller.HasNotifications() {
    44  			t.hasShownNotifications = true
    45  			t.showNotifications()
    46  		}
    47  	}
    48  }
    49  
    50  func (t *SuiviDossiers) updateSelection() {
    51  	if current := t.controller.Etat.FactureCurrent; current != nil {
    52  		t.dossiers.SelectRow(current)
    53  		t.fil.setMessages(t.controller.Base.NewFacture(current.Int64()).GetEtat(nil, nil).PseudoMessages(dm.POVCentre))
    54  		t.fil.enableActions(true)
    55  	} else {
    56  		t.fil.setMessages(nil)
    57  		t.fil.enableActions(false)
    58  	}
    59  }
    60  
    61  func (t *SuiviDossiers) ConfirmeSupprimeFacture(html string) bool {
    62  	return basic.Confirmation(html, "Supprimer", basic.ONDelete)
    63  }
    64  
    65  func (t *SuiviDossiers) ConfirmeMail(fac dm.AccesFacture, messageKind rd.MessageKind) bool {
    66  	adresses := []string{fac.GetPersonne().RawData().Mail.String()}
    67  	for _, adresse := range fac.RawData().CopiesMails {
    68  		adresses = append(adresses, adresse)
    69  	}
    70  	message := fmt.Sprintf("Confirmez-vous l'envoi d'un mail <i>%s</i> à <br/> <b>%s</b> ? ", messageKind,
    71  		strings.Join(adresses, ", "))
    72  	return basic.Confirmation(message, "Envoyer", basic.ONAction)
    73  }
    74  
    75  func (t *SuiviDossiers) ConfirmeSupprimeMessages(nb int) bool {
    76  	message := "Confirmez-vous la suppression du message ?"
    77  	if nb > 1 {
    78  		message = fmt.Sprintf("Confirmez-vous la suppression de %d messages ?", nb)
    79  	}
    80  	message += "<br/> Attention, cette opération est <b>irréversible</b>."
    81  	return basic.Confirmation(message, "Supprimer", basic.ONDelete)
    82  }
    83  
    84  func (t *SuiviDossiers) ShowPreviewFacture(fac dm.AccesFacture) {
    85  	d := basic.Dialog("Bilan financier")
    86  	facHtml := basic.Label(fac.Description())
    87  	facHtml.ConnectLinkActivated(func(link string) {
    88  		id, err := strconv.Atoi(link)
    89  		if err != nil {
    90  			return
    91  		}
    92  		d.Reject()
    93  		t.controller.Main().Controllers.Paiements.SelectPaiement(int64(id))
    94  	})
    95  	d.Layout().AddWidget(facHtml)
    96  	d.Exec()
    97  }
    98  
    99  type SuiviDossiers struct {
   100  	*widgets.QFrame
   101  	controller *controllers.SuiviDossiers
   102  	parent     *widgets.QTabWidget
   103  	toolbar    *widgets.QToolBar
   104  
   105  	dossiers lists.Table
   106  	acquite  fields.Completion
   107  	attente  fields.Enum
   108  	camp     fields.Camp
   109  	fil      fil
   110  
   111  	hasShownNotifications bool
   112  }
   113  
   114  func newSuiviDossiers(ct *controllers.SuiviDossiers) *SuiviDossiers {
   115  	p := SuiviDossiers{QFrame: basic.Frame(), controller: ct}
   116  
   117  	p.dossiers = lists.Table{
   118  		Liste: lists.Liste{
   119  			SingleSelection: true,
   120  			Headers:         ct.Header,
   121  			Title:           "Dossiers",
   122  			OnClick: func(acces rd.Item, _ int) {
   123  				ct.Etat.FactureCurrent = acces.Id
   124  				// on ne reset pas complètement pour éviter de perdre le click
   125  				// ce qui empèche le double clic
   126  				p.updateSelection()
   127  				p.UpdateToolbar()
   128  			},
   129  			OnDoubleClick: func(acces rd.Item, _ int) {
   130  				// dossier courant
   131  				p.editDossier()
   132  			},
   133  			OnSearch: func(pattern string) {
   134  				ct.Etat.Recherche = pattern
   135  				ct.Reset()
   136  			},
   137  			OnSort: func(field rd.Field, reverse bool) {
   138  				ct.Etat.CriteresTri = ct.Etat.CriteresTri.AddCritere(field, reverse)
   139  				ct.Reset()
   140  			},
   141  			OnRightClick: func(acces rd.Item, pos *core.QPoint) {
   142  				fac := ct.Base.NewFacture(acces.Id.Int64())
   143  				menu := widgets.NewQMenu(p.dossiers)
   144  				menu.AddAction("Afficher les paiements...").ConnectTriggered(func(_ bool) {
   145  					p.ShowPreviewFacture(fac)
   146  				})
   147  				menu.AddSeparator()
   148  				menu.AddAction("Copier le lien vers l'espace de suivi").ConnectTriggered(func(_ bool) {
   149  					url := fac.RawData().UrlEspacePerso("/espace_perso")
   150  					url = controllers.Server.EndpointURL(url)
   151  					basic.Copy(url)
   152  					ct.Main().ShowStandard("Lien : "+url, false)
   153  				})
   154  				menu.AddAction("Copier le label de virement").ConnectTriggered(func(_ bool) {
   155  					label := fac.LabelVirement()
   156  					basic.Copy(label)
   157  					ct.Main().ShowStandard("Label du virement : "+label, false)
   158  				})
   159  
   160  				menu.AddSeparator()
   161  				menu.AddAction("Aller au responsable").ConnectTriggered(func(_ bool) {
   162  					ct.Main().Controllers.Personnes.SelectPersonne(fac.GetPersonne())
   163  				})
   164  				menu.AddSeparator()
   165  				for _, doss := range fac.GetDossiers(nil) {
   166  					resp, doss := doss.GetPersonne(), doss
   167  					menu.AddAction("Aller au participant " + resp.RawData().NomPrenom().String()).ConnectTriggered(func(_ bool) {
   168  						ct.Main().Controllers.Camps.SelectParticipant(doss)
   169  					})
   170  				}
   171  				menu.AddSeparator()
   172  				menu.AddAction("Fusionner vers ...").ConnectTriggered(func(bool) {
   173  					p.fusionneDossier(fac)
   174  				})
   175  				p.dossiers.ShowContextMenu(menu, pos)
   176  			},
   177  		},
   178  	}
   179  	p.dossiers.Init()
   180  	p.dossiers.HorizontalHeader().SetMinimumSectionSize(300)
   181  
   182  	p.acquite = fields.NewCompletion(true)
   183  	p.acquite.ConnectCurrentIndexChanged(func(_ int) {
   184  		ct.Etat.CritereAcquite = p.acquite.GetData()
   185  		ct.Reset()
   186  	})
   187  	p.attente = fields.NewEnum([]fields.Choice{
   188  		{Field: string(controllers.Indifferent), Label: "Tous"},
   189  		{Field: string(controllers.MasquerNoAttente), Label: "Avec participants en attente"},
   190  		{Field: string(controllers.MasquerOnlyAttente), Label: "Avec participants inscrits"},
   191  	}, true)
   192  	p.attente.ConnectCurrentIndexChanged(func(_ int) {
   193  		ct.Etat.CritereAttente = controllers.CritereAttente(p.attente.GetData())
   194  		ct.Reset()
   195  	})
   196  	p.camp = fields.NewCamp(true, ct.Base, true)
   197  	p.camp.ShowTerminated(false)
   198  	p.camp.ConnectActivated(func(_ int) {
   199  		var optId rd.OptionnalId
   200  		if p.camp.GetData() != nil {
   201  			optId = rd.NewOptionnalId(p.camp.GetData().Id)
   202  		}
   203  		ct.Etat.CritereCamp = optId
   204  		ct.Reset()
   205  	})
   206  	showTerminated := widgets.NewQCheckBox2("Afficher les camps terminés", nil)
   207  	showTerminated.ConnectClicked(func(checked bool) {
   208  		p.camp.ShowTerminated(checked)
   209  	})
   210  
   211  	// options
   212  	options := widgets.NewQFormLayout(nil)
   213  	options.AddRow3("Choix du camp :", p.camp)
   214  	options.AddRow5(showTerminated)
   215  	options.AddRow3("Liste d'attente :", p.attente)
   216  	options.AddRow3("Etat du règlement :", p.acquite)
   217  
   218  	// bandeau du haut
   219  	haut := widgets.NewQHBoxLayout()
   220  	haut.AddWidget(p.dossiers, 3, 0)
   221  	haut.AddLayout(options, 1)
   222  
   223  	// fil de discussion
   224  	p.fil = newFil(ct)
   225  	p.fil.msgList.OnMessageGroupe = p.onMessageGroupe
   226  
   227  	// final
   228  	lay := widgets.NewQVBoxLayout2(p)
   229  	lay.AddLayout(haut, 2)
   230  	lay.AddWidget(p.fil, 3, 0)
   231  	return &p
   232  }
   233  
   234  func (t *SuiviDossiers) creer() {
   235  	dial := basic.Dialog2("Créer un dossier")
   236  
   237  	choixRespo := fields.NewBoutonIdPersonne(true, t.controller.Base, false)
   238  
   239  	listeP := lists.Table{
   240  		Liste: lists.Liste{
   241  			Headers:   lists.HeadersRechercheParticipants,
   242  			Title:     "Participants à lier au dossier",
   243  			MinHeight: 200,
   244  		},
   245  		HideHeaders: true,
   246  	}
   247  	listeP.OnAdd = func() {
   248  		dial := lists.NewRechercheParticipant(t.controller.Base)
   249  		dial.Exec()
   250  		if dial.Out != nil {
   251  			participant := t.controller.Base.NewParticipant(dial.Out.Int64())
   252  			listeP.Model().SetData(append(listeP.Model().GetData(), participant.AsItem(nil)))
   253  		}
   254  	}
   255  	listeP.OnDelete = func(_ rd.Item, index int) {
   256  		listeP.Model().SetData(controllers.RemoveItem(listeP.Model().GetData(), index))
   257  	}
   258  	listeP.Init()
   259  
   260  	valid := basic.Button("Créer le dossier")
   261  	valid.SetObjectName(basic.ONAdd)
   262  	valid.ConnectClicked(func(_ bool) {
   263  		if choixRespo.GetData() == nil {
   264  			basic.ShowError(errors.New("Veuillez choisir un <b>responsable</b> pour le dossier !"))
   265  			return
   266  		}
   267  		dial.Accept()
   268  	})
   269  	lay := widgets.NewQFormLayout(dial)
   270  
   271  	lay.AddRow(basic.Label("<b>Choix du responsable :</b>"), choixRespo)
   272  	lay.AddRow5(listeP)
   273  	lay.AddRow5(valid)
   274  
   275  	if dial.Exec() == 0 {
   276  		return
   277  	}
   278  	t.SetEnabled(false)
   279  	t.controller.CreeFacture(choixRespo.GetData().Int64(), listeP.Model().GetData())
   280  	t.SetEnabled(true)
   281  }
   282  
   283  func (t *SuiviDossiers) supprimer() {
   284  	t.controller.SupprimeFacture()
   285  }
   286  
   287  func (t *SuiviDossiers) onMessageGroupe(idMessages rd.Ids) {
   288  	idFactures := rd.NewSet()
   289  	for _, idMessage := range idMessages {
   290  		idFactures.Add(t.controller.Base.Messages[idMessage].IdFacture)
   291  	}
   292  	t.writeStreamMessages(idFactures.Keys())
   293  }
   294  
   295  func (t *SuiviDossiers) writeStreamMessages(idFactures rd.Ids) {
   296  	contenu, keep := messages.EditMessage("", true, len(idFactures))
   297  	if !keep {
   298  		t.controller.Main().ShowStandard("Envoi annulé", false)
   299  		return
   300  	}
   301  
   302  	dialog := messages.NewMonitor(len(idFactures))
   303  	sender := common.NewMailsSender(nil)
   304  	// mise en place des callbacks
   305  	sender.Monitor.OnError = dialog.OnError
   306  	sender.Monitor.OnSuccess = func(index int, out apiserver.NotifieManyOut) {
   307  		t.controller.OnSuccesNotifieMessage(out)
   308  		dialog.OnSuccess(index)
   309  	}
   310  	sender.OnFinished = dialog.ShowBilan
   311  
   312  	job := func(monitor controllers.SendMessagesSignals) ([]controllers.DossierError, error) {
   313  		return t.controller.EnvoiManyMessages(contenu, idFactures, monitor)
   314  	}
   315  	sender.SetupLaunch(job)
   316  }
   317  
   318  func (t *SuiviDossiers) envoiManyMessages() {
   319  	ids := messages.SelectFactures(t.controller)
   320  	if len(ids) == 0 {
   321  		t.controller.Main().ShowStandard("Envoi annulé", false)
   322  		return
   323  	}
   324  	t.writeStreamMessages(ids)
   325  }
   326  
   327  func (t *SuiviDossiers) envoiDocumentsCamp() {
   328  	idCamp, resend, keep := messages.SelectCamp(t.controller.Base)
   329  	if !keep {
   330  		t.controller.Main().ShowStandard("Envoi annulé", false)
   331  		return
   332  	}
   333  
   334  	ids := t.controller.SelectionneFacturesDocuments(idCamp, resend)
   335  	if len(ids) == 0 {
   336  		basic.ShowError(errors.New("Il n'y a aucun message à envoyer."))
   337  		return
   338  	}
   339  	streamEnvoiDocuments(t.controller, idCamp, ids)
   340  }
   341  
   342  func streamEnvoiDocuments(ct *controllers.SuiviDossiers, idCamp int64, idsFactures rd.Ids) {
   343  	// on vérifie que les envois ne sont pas verrouillés
   344  	if err := ct.Base.NewCamp(idCamp).RawData().CheckEnvoisLock(); err != nil {
   345  		basic.ShowError(err)
   346  		return
   347  	}
   348  
   349  	dialog := messages.NewMonitor(len(idsFactures))
   350  	sender := common.NewDocumentsSender(nil)
   351  	// mise en place des callbacks
   352  	sender.Monitor.OnError = dialog.OnError
   353  	sender.Monitor.OnSuccess = func(index int, out apiserver.NotifieDocumentsOut) {
   354  		ct.OnSuccesNotifieDocuments(out)
   355  		dialog.OnSuccess(index)
   356  	}
   357  	sender.OnFinished = dialog.ShowBilan
   358  
   359  	job := func(monitor controllers.SendDocumentsSignals) ([]controllers.DossierError, error) {
   360  		return ct.EnvoiDocumentsCamp(idCamp, idsFactures, monitor)
   361  	}
   362  	sender.SetupLaunch(job)
   363  }
   364  
   365  func (t *SuiviDossiers) editDossier() {
   366  	det := NewDetailsDossier(t.controller, common.MainWindow.StatusBar())
   367  	det.Exec()
   368  }
   369  
   370  func (t *SuiviDossiers) fusionneDossier(fac dm.AccesFacture) {
   371  	dial := basic.Dialog2("Fusionner deux dossiers")
   372  
   373  	idTarget := fields.NewBoutonIdFacture(true, t.controller.Base)
   374  
   375  	notifie := fields.NewBool(true)
   376  	notifie.SetData(true) // cochée par défaut
   377  
   378  	valid := basic.Button("Fusionner")
   379  	valid.SetObjectName(basic.ONAction)
   380  	valid.ConnectClicked(func(bool) { dial.Accept() })
   381  
   382  	valid.SetEnabled(false)
   383  	idTarget.DataChanged = func() {
   384  		valid.SetEnabled(idTarget.GetData() != nil)
   385  	}
   386  
   387  	lay := widgets.NewQFormLayout(dial)
   388  	lay.AddRow3("Dossier vers lequel fusionner:", idTarget)
   389  	lay.AddRow3("Notifier par mail", notifie)
   390  	lay.AddRow5(valid)
   391  
   392  	dial.SetMinimumWidth(500)
   393  	if dial.Exec() == 0 {
   394  		t.controller.Main().ShowStandard("Fusion annulée", false)
   395  		return
   396  	}
   397  	params := apiserver.FusionneFacturesIn{
   398  		From:    fac.Id,
   399  		To:      idTarget.GetData().Int64(),
   400  		Notifie: bool(notifie.GetData()),
   401  	}
   402  	t.controller.FusionneDossiers(params)
   403  }
   404  
   405  // ----------------------------- Alertes nouveaux messages / aides -----------------------------
   406  
   407  func (t *SuiviDossiers) showNotifications() {
   408  	aides, factures := t.controller.Notifications()
   409  
   410  	d := basic.Dialog("Notifications")
   411  	listAides := notifications.NewList()
   412  	listFactures := notifications.NewList()
   413  	for _, aide := range aides {
   414  		aide := aide // variable in closure
   415  		listAides.AddNotification(aide.Label(), func() {
   416  			t.controller.Main().Controllers.Aides.SelectAide(aide)
   417  		})
   418  	}
   419  
   420  	for _, facture := range factures {
   421  		facture := facture // variable in closure
   422  		isInscription := !facture.RawData().IsValidated
   423  		label := facture.GetPersonne().RawData().NomPrenom().String()
   424  		onLink := func() {
   425  			t.controller.SelectFacture(facture)
   426  		}
   427  		if isInscription {
   428  			label = "<i>(Nouvelle inscription)</i> " + label
   429  			onLink = nil // on désactive le goTo
   430  		}
   431  		listFactures.AddNotification(label, onLink)
   432  	}
   433  
   434  	d.SetMinimumWidth(600)
   435  
   436  	d.Layout().AddWidget(basic.Label(fmt.Sprintf("<b>Nouveaux messages (%d) :</b>", len(factures))))
   437  	if len(factures) == 0 {
   438  		d.Layout().AddWidget(basic.Label("<i>Aucun message en attente.</i>"))
   439  	} else {
   440  		d.Layout().AddWidget(listFactures)
   441  	}
   442  
   443  	d.Layout().AddWidget(basic.Label(fmt.Sprintf("<b>Aides déclarées et non vérifiées (%d) :</b>", len(aides))))
   444  	if len(aides) == 0 {
   445  		d.Layout().AddWidget(basic.Label("<i>Aucune aide en attente.</i>"))
   446  	} else {
   447  		d.Layout().AddWidget(listAides)
   448  	}
   449  
   450  	d.Show()
   451  }
   452  
   453  // ---------------------------------------------------------------------------------------------
   454  // --------------------- Fiche de détails : facture + participants + aides ---------------------
   455  // ---------------------------------------------------------------------------------------------
   456  
   457  type widgetParticipant struct {
   458  	*widgets.QFrame
   459  
   460  	deleteButton *widgets.QPushButton
   461  	label        *widgets.QLabel
   462  	boutonOption *fields.OptionsFinanceParticipant
   463  	aides        lists.Table
   464  	editable     bool
   465  
   466  	onDeleteAide      func(a rd.Item) bool
   467  	onAjouteAide      func()
   468  	onChangeOptions   func(rd.Remises, rd.OptionPrixParticipant)
   469  	onGoToParticipant func(id int64)
   470  }
   471  
   472  func newWidgetParticipant(editable bool) *widgetParticipant {
   473  	w := widgetParticipant{QFrame: basic.Frame(), editable: editable}
   474  	w.SetObjectName("participant")
   475  	lay := widgets.NewQGridLayout(w)
   476  	w.label = basic.Label("")
   477  	w.label.SetTextFormat(core.Qt__RichText)
   478  	w.label.ConnectLinkActivated(func(link string) {
   479  		if id, err := strconv.Atoi(link); err == nil {
   480  			w.onGoToParticipant(int64(id))
   481  		}
   482  	})
   483  
   484  	w.boutonOption = fields.NewOptionsFinanceParticipant(editable, "", nil, rd.Plage{})
   485  	w.boutonOption.DataChanged = func() {
   486  		w.onChangeOptions(w.boutonOption.GetData())
   487  	}
   488  
   489  	w.deleteButton = basic.Button("Supprimer")
   490  	w.deleteButton.SetIcon(basic.Icons.Delete)
   491  	w.deleteButton.SetObjectName(basic.ONDelete)
   492  	w.deleteButton.SetEnabled(editable)
   493  
   494  	w.aides = lists.Table{
   495  		Liste: lists.Liste{
   496  			Headers: []rd.Header{
   497  				{Field: dm.AideStructure, Label: "Organisme"},
   498  				{Field: dm.AideMontant, Label: "Montant"},
   499  				{Field: dm.AideValide, Label: "Valide"},
   500  			},
   501  			MaxWidth:    600,
   502  			MinHeight:   110,
   503  			Placeholder: "Aucune aide n'est déclarée.",
   504  		},
   505  		ResizeToContents: true,
   506  	}
   507  	if editable {
   508  		w.aides.OnAdd = func() {
   509  			w.onAjouteAide() // variable a setup
   510  		}
   511  		w.aides.OnDelete = func(acces rd.Item, index int) {
   512  			mustDelete := w.onDeleteAide(acces)
   513  			if mustDelete {
   514  				w.aides.Model().SetData(controllers.RemoveItem(w.aides.Model().GetData(), index))
   515  			}
   516  		}
   517  	}
   518  	w.aides.Init()
   519  
   520  	w.label.SetWordWrap(true)
   521  	w.label.SetMaximumWidth(400)
   522  	lay.AddWidget2(w.label, 0, 0, 0)
   523  	lay.AddWidget2(w.boutonOption, 1, 0, 0)
   524  	lay.AddWidget2(w.deleteButton, 2, 0, 0)
   525  	lay.AddWidget3(w.aides, 0, 1, 3, 2, 0)
   526  	return &w
   527  }
   528  
   529  // met à jour les widgets
   530  func (w *widgetParticipant) render(part dm.AccesParticipant) {
   531  	camp := part.GetCamp().RawData()
   532  
   533  	// label
   534  	w.label.SetText(fmt.Sprintf("<i><a href='%d'>%s au %s</a></i>", part.Id, part.GetPersonne().RawData().NomPrenom(),
   535  		camp.Label()))
   536  	style := "padding: 2px;"
   537  	if !part.RawData().ListeAttente.IsInscrit() {
   538  		style += "background-color : " + dm.CouleurListeAttente.AHex()
   539  	}
   540  	w.label.SetStyleSheet(style)
   541  
   542  	// options financières
   543  	w.boutonOption.OptionPrixParticipant = fields.NewOptionPrixParticipant(w.editable, camp.OptionPrix.Active, camp.OptionPrix.Statut,
   544  		rd.Plage{From: camp.DateDebut, To: camp.DateFin})
   545  	w.boutonOption.SetData(part.RawData().Remises, part.RawData().OptionPrix)
   546  
   547  	// aides
   548  	partAides := part.GetAides(nil)
   549  	data := make([]rd.Item, len(partAides))
   550  	for index, aide := range partAides {
   551  		data[index] = aide.AsItem()
   552  	}
   553  	w.aides.Model().SetData(data)
   554  }
   555  
   556  type DetailsDossier struct {
   557  	*widgets.QDialog
   558  	acces dm.AccesFacture
   559  
   560  	ct           *controllers.SuiviDossiers
   561  	statusBar    *widgets.QStatusBar
   562  	scroll       *widgets.QScrollArea
   563  	ficheFacture *common.FicheFacture
   564  }
   565  
   566  func NewDetailsDossier(ct *controllers.SuiviDossiers, mainStatusBar *widgets.QStatusBar) DetailsDossier {
   567  	d := DetailsDossier{QDialog: basic.Dialog2("Détails du dossier"), ct: ct}
   568  	lay := widgets.NewQGridLayout(d)
   569  	idFac := ct.Etat.FactureCurrent
   570  	if idFac == nil {
   571  		lay.AddWidget2(basic.Label("Aucun dossier courant !"), 0, 0, 0)
   572  		return d
   573  	}
   574  	d.acces = ct.Base.NewFacture(idFac.Int64())
   575  
   576  	fac := common.NewFicheFacture(ct.Base, ct.EditRight)
   577  	fac.SetLayout()
   578  	fac.SetData(d.acces.RawData(), rd.NewOptionnalId(d.acces.GetPersonne().Id))
   579  	d.ficheFacture = &fac
   580  
   581  	valid := basic.ButtonEnregistrer()
   582  	valid.ConnectClicked(func(_ bool) {
   583  		if err := fac.Valid(); err != nil {
   584  			basic.ShowError(err)
   585  			return
   586  		}
   587  		d.UpdateFacture(fac.GetData())
   588  	})
   589  	d.scroll = widgets.NewQScrollArea(nil)
   590  	d.scroll.SetMinimumHeight(500)
   591  
   592  	ajoutPart := basic.Button("Ajouter un participant")
   593  	ajoutPart.SetObjectName(basic.ONAction)
   594  	ajoutPart.ConnectClicked(func(_ bool) { d.AjouteParticipant() })
   595  
   596  	d.drawParticipants()
   597  
   598  	valid.SetEnabled(ct.EditRight)
   599  	ajoutPart.SetEnabled(ct.EditRight)
   600  
   601  	d.SetMinimumWidth(800)
   602  	lay.AddWidget3(fac, 0, 0, 2, 1, 0)
   603  	lay.AddWidget2(valid, 2, 0, 0)
   604  	lay.AddWidget2(basic.Label("Participants :"), 0, 1, 0)
   605  	lay.AddWidget2(ajoutPart, 0, 3, 0)
   606  	lay.AddWidget3(d.scroll, 1, 1, 2, 3, 0)
   607  
   608  	lay.SetRowStretch(0, 1)
   609  	lay.SetRowStretch(1, 4)
   610  	lay.SetRowStretch(2, 4)
   611  	lay.SetColumnStretch(0, 2)
   612  	lay.SetColumnStretch(1, 3)
   613  
   614  	d.statusBar = widgets.NewQStatusBar(nil)
   615  	mainStatusBar.ConnectMessageChanged(func(message string) {
   616  		d.statusBar.ShowMessage(message, 4000)
   617  		d.statusBar.Repaint()
   618  	})
   619  	lay.AddWidget3(d.statusBar, 3, 0, 1, 4, 0)
   620  	return d
   621  }
   622  
   623  func (d *DetailsDossier) drawParticipants() {
   624  	parts := d.acces.GetDossiers(nil)
   625  	f := basic.Frame()
   626  	pLay := widgets.NewQFormLayout(f)
   627  	for _, part := range parts {
   628  		part := part // attention aux closures dans les boucles
   629  		wPart := newWidgetParticipant(d.ct.EditRight)
   630  		wPart.onDeleteAide = d.DeleteAide
   631  		wPart.onAjouteAide = func() { d.AjouteAide(part) }
   632  		wPart.onChangeOptions = func(remises rd.Remises, options rd.OptionPrixParticipant) {
   633  			d.UpdateOptionsParticipant(part.RawData(), remises, options)
   634  		}
   635  		wPart.onGoToParticipant = func(id int64) {
   636  			d.Close()
   637  			d.ct.Main().ShowStandard("Modifications sur le dossier annulées.", false)
   638  			d.ct.Main().Controllers.Camps.SelectParticipant(d.ct.Base.NewParticipant(id))
   639  		}
   640  
   641  		wPart.render(part)
   642  		pLay.AddRow5(wPart)
   643  		wPart.deleteButton.ConnectClicked(func(_ bool) { d.DeleteParticipant(part) })
   644  	}
   645  	d.scroll.SetWidget(f)
   646  }
   647  
   648  func (d *DetailsDossier) AjouteParticipant() {
   649  	dial := lists.NewRechercheParticipant(d.ct.Base)
   650  	dial.Exec()
   651  	if dial.Out == nil {
   652  		d.statusBar.ShowMessage("Ajout d'un participant annulé.", 3000)
   653  		return
   654  	}
   655  	d.SetEnabled(false)
   656  	defer d.SetEnabled(true)
   657  	d.ct.AjouteParticipant(d.ct.Base.NewParticipant(dial.Out.Int64()))
   658  	d.drawParticipants()
   659  }
   660  
   661  func (d *DetailsDossier) UpdateOptionsParticipant(part rd.Participant, remises rd.Remises, options rd.OptionPrixParticipant) {
   662  	d.SetEnabled(false)
   663  	defer d.SetEnabled(true)
   664  	part.Remises = remises
   665  	part.OptionPrix = options
   666  	d.ct.UpdateParticipant(part)
   667  	d.drawParticipants()
   668  }
   669  
   670  func (d *DetailsDossier) DeleteParticipant(part dm.AccesParticipant) {
   671  	conf := basic.Dialog2("Confirmation")
   672  	label := basic.Label(fmt.Sprintf(`Confirmez-vous la <b>suppression</b> de <b>%s</b> (%s) ?
   673  		<br><br> 
   674  		<i>Vous pouvez supprimer la participation ou seulement enlever le participant du dossier.</i>
   675  		`, part.GetPersonne().RawData().NomPrenom(), part.GetCamp().RawData().Label()))
   676  
   677  	buttonEnleve := basic.Button("Retirer du dossier")
   678  	buttonEnleve.SetObjectName(basic.ONAction)
   679  	buttonSupprime := basic.Button("Supprimer")
   680  	buttonSupprime.SetObjectName(basic.ONDelete)
   681  
   682  	var fullSupp bool
   683  	buttonEnleve.ConnectClicked(func(_ bool) {
   684  		conf.Accept()
   685  	})
   686  	buttonSupprime.ConnectClicked(func(_ bool) {
   687  		fullSupp = true
   688  		conf.Accept()
   689  	})
   690  
   691  	lay := widgets.NewQGridLayout(conf)
   692  	lay.AddWidget3(label, 0, 0, 1, 3, 0)
   693  	lay.AddWidget2(buttonSupprime, 1, 0, 0)
   694  	lay.AddWidget2(buttonEnleve, 1, 2, 0)
   695  
   696  	if conf.Exec() == 0 {
   697  		d.statusBar.ShowMessage("Suppression annulée.", 3000)
   698  		return
   699  	}
   700  	d.SetEnabled(false)
   701  	defer d.SetEnabled(true)
   702  	d.ct.RetireParticipant(part, fullSupp)
   703  	d.drawParticipants()
   704  }
   705  
   706  func (d *DetailsDossier) DeleteAide(aide rd.Item) bool {
   707  	dial := basic.Dialog("Confirmation")
   708  	valid := basic.Button("Confirmer la suppression")
   709  	valid.SetObjectName(basic.ONDelete)
   710  	valid.ConnectClicked(func(_ bool) {
   711  		dial.Accept()
   712  	})
   713  	dial.Layout().AddWidget(basic.Label("Etes-vous sur de vouloir supprimer l'aide ?"))
   714  	dial.Layout().AddWidget(valid)
   715  	if dial.Exec() == 0 {
   716  		return false
   717  	}
   718  	d.ct.SupprimeAide(aide.Id.Int64())
   719  	return true
   720  }
   721  
   722  func (d *DetailsDossier) AjouteAide(part dm.AccesParticipant) {
   723  	aide := CreeAide(d.ct.Base, rd.NewOptionnalId(part.Id))
   724  	if aide == nil {
   725  		d.statusBar.ShowMessage("Ajout d'une aide annulé.", 3000)
   726  		return
   727  	}
   728  	d.SetEnabled(false)
   729  	defer d.SetEnabled(true)
   730  	d.statusBar.ShowMessage("Ajout de l'aide...", 0)
   731  	created := d.ct.AjouteAide(*aide)
   732  	if created != nil {
   733  		d.statusBar.ShowMessage("Aide ajoutée avec succès.", 5000)
   734  		d.drawParticipants()
   735  	}
   736  }
   737  
   738  func (d *DetailsDossier) UpdateFacture(rf rd.Facture) {
   739  	d.SetEnabled(false)
   740  	defer d.SetEnabled(true)
   741  	d.Repaint()
   742  	out := d.ct.UpdateFacture(rf)
   743  	if out != nil {
   744  		d.ficheFacture.SetData(rf, rd.NewOptionnalId(rf.IdPersonne))
   745  	}
   746  }