|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
@ -162,6 +163,7 @@ type DecisionModel struct {
|
|
|
|
|
|
|
|
|
|
// Create a new decision.
|
|
|
|
|
func (m *DecisionModel) Create(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
proponent *Voter,
|
|
|
|
|
voteType VoteType,
|
|
|
|
|
title, content string,
|
|
|
|
@ -176,14 +178,18 @@ func (m *DecisionModel) Create(
|
|
|
|
|
VoteType: voteType,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := m.DB.NamedExec(`INSERT INTO decisions
|
|
|
|
|
result, err := m.DB.NamedExecContext(
|
|
|
|
|
ctx,
|
|
|
|
|
`INSERT INTO decisions
|
|
|
|
|
(proposed, proponent, title, content, votetype, status, due, modified, tag)
|
|
|
|
|
VALUES (:proposed, :proponent, :title, :content, :votetype, :status, :due, :proposed,
|
|
|
|
|
'm' || strftime('%Y%m%d', :proposed) || '.' || (
|
|
|
|
|
SELECT COUNT(*)+1 AS num
|
|
|
|
|
FROM decisions
|
|
|
|
|
WHERE proposed BETWEEN date(:proposed) AND date(:proposed, '1 day')
|
|
|
|
|
))`, d)
|
|
|
|
|
))`,
|
|
|
|
|
d,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, fmt.Errorf("creating motion failed: %w", err)
|
|
|
|
|
}
|
|
|
|
@ -196,8 +202,8 @@ VALUES (:proposed, :proponent, :title, :content, :votetype, :status, :due, :prop
|
|
|
|
|
return id, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *DecisionModel) CloseDecisions() ([]*ClosedDecision, error) {
|
|
|
|
|
tx, err := m.DB.Beginx()
|
|
|
|
|
func (m *DecisionModel) CloseDecisions(ctx context.Context) ([]*ClosedDecision, error) {
|
|
|
|
|
tx, err := m.DB.BeginTxx(ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("could not start transaction: %w", err)
|
|
|
|
|
}
|
|
|
|
@ -241,7 +247,7 @@ WHERE decisions.status=0 AND :now > due`, struct{ Now time.Time }{Now: time.Now
|
|
|
|
|
for _, decision := range decisions {
|
|
|
|
|
m.InfoLog.Printf("found closable decision %s", decision.Tag)
|
|
|
|
|
|
|
|
|
|
if decisionResult, err = m.CloseDecision(tx, decision); err != nil {
|
|
|
|
|
if decisionResult, err = m.CloseDecision(ctx, tx, decision); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("closing decision %s failed: %w", decision.Tag, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -255,7 +261,7 @@ WHERE decisions.status=0 AND :now > due`, struct{ Now time.Time }{Now: time.Now
|
|
|
|
|
return results, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision, error) {
|
|
|
|
|
func (m *DecisionModel) CloseDecision(ctx context.Context, tx *sqlx.Tx, d *Decision) (*ClosedDecision, error) {
|
|
|
|
|
quorum, majority := d.VoteType.QuorumAndMajority()
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
@ -264,13 +270,14 @@ func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision
|
|
|
|
|
reasoning string
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if voteSums, err = m.GetVoteSums(tx, d); err != nil {
|
|
|
|
|
if voteSums, err = m.SumsForDecision(ctx, tx, d); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("getting vote sums failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.Status, reasoning = voteSums.CalculateResult(quorum, majority)
|
|
|
|
|
|
|
|
|
|
result, err := m.DB.NamedExec(
|
|
|
|
|
result, err := m.DB.NamedExecContext(
|
|
|
|
|
ctx,
|
|
|
|
|
`UPDATE decisions SET status=:status, modified=CURRENT_TIMESTAMP WHERE id=:id`,
|
|
|
|
|
d,
|
|
|
|
|
)
|
|
|
|
@ -293,12 +300,13 @@ func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision
|
|
|
|
|
return &ClosedDecision{d, voteSums, reasoning}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *DecisionModel) FindUnVotedDecisionsForVoter(_ *Voter) ([]Decision, error) {
|
|
|
|
|
func (m *DecisionModel) UnVotedDecisionsForVoter(_ context.Context, _ *Voter) ([]Decision, error) {
|
|
|
|
|
panic("not implemented")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *DecisionModel) GetVoteSums(tx *sqlx.Tx, d *Decision) (*VoteSums, error) {
|
|
|
|
|
voteRows, err := tx.NamedQuery(
|
|
|
|
|
func (m *DecisionModel) SumsForDecision(ctx context.Context, tx *sqlx.Tx, d *Decision) (*VoteSums, error) {
|
|
|
|
|
voteRows, err := tx.QueryxContext(
|
|
|
|
|
ctx,
|
|
|
|
|
`SELECT vote, COUNT(vote) FROM votes WHERE decision=$1 GROUP BY vote`,
|
|
|
|
|
d.ID,
|
|
|
|
|
)
|
|
|
|
@ -339,8 +347,12 @@ func (m *DecisionModel) GetVoteSums(tx *sqlx.Tx, d *Decision) (*VoteSums, error)
|
|
|
|
|
return sums, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *DecisionModel) GetNextPendingDecisionDue() (*time.Time, error) {
|
|
|
|
|
row := m.DB.QueryRow(`SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`, nil)
|
|
|
|
|
func (m *DecisionModel) NextPendingDecisionDue(ctx context.Context) (*time.Time, error) {
|
|
|
|
|
row := m.DB.QueryRowContext(
|
|
|
|
|
ctx,
|
|
|
|
|
`SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`,
|
|
|
|
|
nil,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if row == nil {
|
|
|
|
|
return nil, errors.New("no row returned")
|
|
|
|
|