diff --git a/admin/api/claim.go b/admin/api/claim.go new file mode 100644 index 00000000..a5e815fc --- /dev/null +++ b/admin/api/claim.go @@ -0,0 +1,189 @@ +package api + +import ( + "encoding/json" + "time" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + // Tasks + router.GET("/api/claims/", apiHandler(getClaims)) + router.POST("/api/claims/", apiHandler(newClaim)) + router.DELETE("/api/claims/", apiHandler(clearClaims)) + + router.GET("/api/claims/:cid", apiHandler(claimHandler(showClaim))) + router.PUT("/api/claims/:cid", apiHandler(claimHandler(updateClaim))) + router.POST("/api/claims/:cid", apiHandler(claimHandler(addClaimDescription))) + router.DELETE("/api/claims/:cid", apiHandler(claimHandler(deleteClaim))) + + // Assignees + router.GET("/api/claims-assignees/", apiHandler(getAssignees)) + router.POST("/api/claims-assignees/", apiHandler(newAssignee)) + + router.GET("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(showClaimAssignee))) + router.PUT("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(updateClaimAssignee))) + router.DELETE("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(deleteClaimAssignee))) +} + +func getClaims(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.GetClaims() +} + +type ClaimExported struct { + Id int64 `json:"id"` + Subject string `json:"subject"` + IdTeam *int64 `json:"id_team"` + Team *fic.Team `json:"team"` + IdAssignee *int64 `json:"id_assignee"` + Assignee *fic.ClaimAssignee `json:"assignee"` + Creation time.Time `json:"creation"` + LastUpdate time.Time `json:"last_update"` + State string `json:"state"` + Priority string `json:"priority"` + Descriptions []fic.ClaimDescription `json:"descriptions"` +} + +func showClaim(claim fic.Claim, _ []byte) (interface{}, error) { + var e ClaimExported + var err error + if e.Team, err = claim.GetTeam(); err != nil { + return nil, err + } + if e.Assignee, err = claim.GetAssignee(); err != nil { + return nil, err + } + if e.Descriptions, err = claim.GetDescriptions(); err != nil { + return nil, err + } + + e.LastUpdate = e.Creation + for _, d := range e.Descriptions { + if d.Date.After(e.LastUpdate) { + e.LastUpdate = d.Date + } + } + + e.Id = claim.Id + e.IdAssignee = claim.IdAssignee + e.IdTeam = claim.IdTeam + e.Subject = claim.Subject + e.Creation = claim.Creation + e.State = claim.State + e.Priority = claim.Priority + return e, nil +} + +type ClaimUploaded struct { + Subject string `json:"subject"` + Team *int `json:"id_team"` + Assignee *int64 `json:"id_assignee"` + Priority string `json:"priority"` +} + +func newClaim(_ httprouter.Params, body []byte) (interface{}, error) { + var uc ClaimUploaded + if err := json.Unmarshal(body, &uc); err != nil { + return nil, err + } + + var t *fic.Team + if uc.Team != nil { + if team, err := fic.GetTeam(*uc.Team); err != nil { + return nil, err + } else { + t = &team + } + } else { + t = nil + } + + var a *fic.ClaimAssignee + if uc.Assignee != nil { + if assignee, err := fic.GetAssignee(*uc.Assignee); err != nil { + return nil, err + } else { + a = &assignee + } + } else { + a = nil + } + + return fic.NewClaim(uc.Subject, t, a, uc.Priority) +} + +func clearClaims(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.ClearClaims() +} + + +func addClaimDescription(claim fic.Claim, body []byte) (interface{}, error) { + var ud fic.ClaimDescription + if err := json.Unmarshal(body, &ud); err != nil { + return nil, err + } + + if assignee, err := fic.GetAssignee(ud.IdAssignee); err != nil { + return nil, err + } else { + return claim.AddDescription(ud.Content, assignee) + } +} + +func updateClaim(claim fic.Claim, body []byte) (interface{}, error) { + var uc fic.Claim + if err := json.Unmarshal(body, &uc); err != nil { + return nil, err + } + + uc.Id = claim.Id + + if _, err := uc.Update(); err != nil { + return nil, err + } else { + return uc, nil + } +} + +func deleteClaim(claim fic.Claim, _ []byte) (interface{}, error) { + return claim.Delete() +} + + +func getAssignees(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.GetAssignees() +} + +func showClaimAssignee(assignee fic.ClaimAssignee, _ []byte) (interface{}, error) { + return assignee, nil +} +func newAssignee(_ httprouter.Params, body []byte) (interface{}, error) { + var ua fic.ClaimAssignee + if err := json.Unmarshal(body, &ua); err != nil { + return nil, err + } + + return fic.NewClaimAssignee(ua.Name) +} + +func updateClaimAssignee(assignee fic.ClaimAssignee, body []byte) (interface{}, error) { + var ua fic.ClaimAssignee + if err := json.Unmarshal(body, &ua); err != nil { + return nil, err + } + + ua.Id = assignee.Id + + if _, err := ua.Update(); err != nil { + return nil, err + } else { + return ua, nil + } +} + +func deleteClaimAssignee(assignee fic.ClaimAssignee, _ []byte) (interface{}, error) { + return assignee.Delete() +} diff --git a/admin/api/handlers.go b/admin/api/handlers.go index 49a2ccd8..854cbf21 100644 --- a/admin/api/handlers.go +++ b/admin/api/handlers.go @@ -248,6 +248,30 @@ func eventHandler(f func(fic.Event,[]byte) (interface{}, error)) func (httproute } } +func claimHandler(f func(fic.Claim,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if cid, err := strconv.Atoi(string(ps.ByName("cid"))); err != nil { + return nil, err + } else if claim, err := fic.GetClaim(cid); err != nil { + return nil, err + } else { + return f(claim, body) + } + } +} + +func claimAssigneeHandler(f func(fic.ClaimAssignee,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if aid, err := strconv.Atoi(string(ps.ByName("aid"))); err != nil { + return nil, err + } else if assignee, err := fic.GetAssignee(int64(aid)); err != nil { + return nil, err + } else { + return f(assignee, body) + } + } +} + func fileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { return func (ps httprouter.Params, body []byte) (interface{}, error) { if fileid, err := strconv.Atoi(string(ps.ByName("fileid"))); err != nil { diff --git a/admin/index.go b/admin/index.go index d1efd3fb..939b9250 100644 --- a/admin/index.go +++ b/admin/index.go @@ -36,6 +36,7 @@ const indextpl = ` + diff --git a/admin/static.go b/admin/static.go index 75127168..6a62d747 100644 --- a/admin/static.go +++ b/admin/static.go @@ -13,6 +13,9 @@ func init() { api.Router().GET("/", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { http.ServeFile(w, r, path.Join(StaticDir, "index.html")) }) + api.Router().GET("/claims/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + http.ServeFile(w, r, path.Join(StaticDir, "index.html")) + }) api.Router().GET("/exercices/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { http.ServeFile(w, r, path.Join(StaticDir, "index.html")) }) diff --git a/admin/static/index.html b/admin/static/index.html index 391af203..85900057 100644 --- a/admin/static/index.html +++ b/admin/static/index.html @@ -34,6 +34,7 @@ + diff --git a/admin/static/js/app.js b/admin/static/js/app.js index ca12158d..9c54537f 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -61,12 +61,46 @@ angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"]) controller: "EventController", templateUrl: "views/event.html" }) + .when("/claims", { + controller: "ClaimsListController", + templateUrl: "views/claim-list.html" + }) + .when("/claims/:claimId", { + controller: "ClaimController", + templateUrl: "views/claim.html" + }) .when("/", { templateUrl: "views/home.html" }); $locationProvider.html5Mode(true); }); +function setCookie(name, value, days) { + var expires; + + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toGMTString(); + } else { + expires = ""; + } + document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; +} + +function getCookie(name) { + var nameEQ = encodeURIComponent(name) + "="; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) === ' ') + c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + return null; +} + angular.module("FICApp") .directive('autofocus', ['$timeout', function($timeout) { return { @@ -88,6 +122,16 @@ angular.module("FICApp") 'update': {method: 'PUT'}, }) }) + .factory("Claim", function($resource) { + return $resource("/api/claims/:claimId", { claimId: '@id' }, { + 'update': {method: 'PUT'}, + }) + }) + .factory("ClaimAssignee", function($resource) { + return $resource("/api/claims-assignees/:assigneeId", { assigneeId: '@id' }, { + 'update': {method: 'PUT'}, + }) + }) .factory("File", function($resource) { return $resource("/api/files/:fileId", { fileId: '@id' }) }) @@ -616,6 +660,134 @@ angular.module("FICApp") } }) + .controller("AssigneesListController", function($scope, ClaimAssignee, $location) { + $scope.assignees = ClaimAssignee.query(); + + $scope.setMyAId = function(aid) { + setCookie("myassignee", aid, 5); + $location.url("/claims/"); + } + $scope.whoami = getCookie("myassignee"); + $scope.newAssignee = function() { + $scope.assignees.push(new ClaimAssignee()); + } + $scope.edit = function(a) { + a.edit = true; + } + $scope.updateAssignee = function(a) { + if (a.id) { + a.$update(function() { $location.url("/claims/");}); + } else { + a.$save() + } + } + $scope.removeAssignee = function(a) { + a.$remove(function() { $location.url("/claims/");}); + } + }) + .controller("ClaimsListController", function($scope, Claim, ClaimAssignee, Teams, $location) { + $scope.claims = Claim.query(); + $scope.assignees = ClaimAssignee.query(); + $scope.whoami = getCookie("myassignee"); + $scope.teams = Teams.get(); + $scope.fields = ["id", "state", "subject", "creation", "id_team", "id_assignee"]; + + $scope.order = "priority"; + $scope.chOrder = function(no) { + $scope.order = no; + }; + + $scope.clearClaims = function(id) { + Claim.delete(function() { + $scope.claims = []; + }); + }; + $scope.show = function(id) { + $location.url("/claims/" + id); + }; + }) + .controller("ClaimController", function($scope, Claim, ClaimAssignee, Teams, $routeParams, $location, $http) { + $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function(v) { + v.id_team = "" + v.id_team; + if (!v.priority) + v.priority = "medium"; + }); + if ($routeParams.claimId == "new") + $scope.fields = ["id_team", "subject", "priority", "id_assignee"]; + else + $scope.fields = ["state", "subject", "priority", "id_assignee", "id_team", "creation"]; + $scope.assignees = ClaimAssignee.query(); + $scope.whoami = Math.floor(getCookie("myassignee")); + $scope.teams = Teams.get(); + $scope.namedFields = { + "subject": "Objet", + "id_assignee": "Assigné a", + "state": "État", + "id_team": "Équipe", + "creation": "Création", + "priority": "Priorité", + "description": "Description", + }; + $scope.states = { + "new": "Nouveau", + "need-info": "Besoin d'infos", + "confirmed": "Confirmé", + "in-progress": "En cours", + "need-review": "Fait", + "closed": "Clos", + "invalid": "Invalide", + }; + $scope.priorities = { + "low": "Basse", + "medium": "Moyenne", + "high": "Haute", + "critical": "Critique", + }; + + $scope.assignToMe = function() { + this.claim.id_assignee = $scope.whoami; + if (this.claim.id) + this.saveClaim(); + } + $scope.saveDescription = function() { + $http({ + url: "/api/claims/" + $scope.claim.id, + method: "POST", + data: { + "id_assignee": $scope.whoami, + "content": $scope.ndescription + } + }).then(function() { + $location.url("/claims/" + $scope.claim.id); + }); + } + $scope.saveClaim = function() { + if (this.claim.id_team) { + this.claim.id_team = Math.floor(this.claim.id_team); + } else { + this.claim.id_team = null; + } + if (this.claim.id) { + this.claim.$update(function(v) { + v.id_team = "" + v.id_team; + if ($scope.ndescription) + $scope.saveDescription(); + else + $location.url("/claims/"); + }); + } else { + this.claim.$save(function() { + if (!this.ndescription) + this.ndescription = "Création de la tâche"; + $scope.saveDescription(); + }); + } + } + $scope.deleteClaim = function() { + this.claim.$remove(function() { $location.url("/claims/");}); + } + }) + .controller("ThemesListController", function($scope, Theme, $location, $rootScope, $http) { $scope.themes = Theme.query(); $scope.fields = ["id", "name"]; diff --git a/admin/static/views/claim-list.html b/admin/static/views/claim-list.html new file mode 100644 index 00000000..85a17bfa --- /dev/null +++ b/admin/static/views/claim-list.html @@ -0,0 +1,61 @@ +

+ Tâches et réclammations ({{ claims.length }}) + + +
+
+
+
+

+ +

+ + + + + + + + + + + +
+ {{ field }} +
+ {{ claim[field] }} +
+ +
+ +
+

+ Assignables à + +

+ + + + + + + + + + + + + + + +
IDNom
+ {{ a.id }} + + {{ a.name }} + + + + + +
+
diff --git a/admin/static/views/claim.html b/admin/static/views/claim.html new file mode 100644 index 00000000..69e4521a --- /dev/null +++ b/admin/static/views/claim.html @@ -0,0 +1,36 @@ +

Tâche

+ +
+
+ +
+ + + + + + + +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+
+ +
+ +
+ Par anonymous {{ assignee.name }} le {{ description.date | date:"mediumTime" }} : + {{ description.content }} +
diff --git a/libfic/db.go b/libfic/db.go index 9ff54422..79b209a0 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -229,6 +229,42 @@ CREATE TABLE IF NOT EXISTS team_hints( FOREIGN KEY(id_hint) REFERENCES exercice_hints(id_hint), FOREIGN KEY(id_team) REFERENCES teams(id_team) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS claim_assignees( + id_assignee INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL UNIQUE +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS claims( + id_claim INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + subject VARCHAR(255) NOT NULL, + id_team INTEGER, + id_assignee INTEGER, + creation TIMESTAMP NOT NULL, + state ENUM('new', 'need-info', 'confirmed', 'in-progress', 'need-review', 'closed', 'invalid') NOT NULL, + priority ENUM('low', 'medium', 'high', 'critical') NOT NULL, + FOREIGN KEY(id_assignee) REFERENCES claim_assignees(id_assignee), + FOREIGN KEY(id_team) REFERENCES teams(id_team) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS claim_descriptions( + id_description INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_claim INTEGER NOT NULL, + id_assignee INTEGER NOT NULL, + date TIMESTAMP NOT NULL, + content TEXT NOT NULL, + FOREIGN KEY(id_assignee) REFERENCES claim_assignees(id_assignee), + FOREIGN KEY(id_claim) REFERENCES claims(id_claim) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return err } diff --git a/libfic/todo.go b/libfic/todo.go new file mode 100644 index 00000000..1d2a64bd --- /dev/null +++ b/libfic/todo.go @@ -0,0 +1,268 @@ +package fic + +import ( + "database/sql" + "time" +) + +type Claim struct { + Id int64 `json:"id"` + Subject string `json:"subject"` + IdTeam *int64 `json:"id_team"` + IdAssignee *int64 `json:"id_assignee"` + Creation time.Time `json:"creation"` + State string `json:"state"` + Priority string `json:"priority"` +} + +func GetClaim(id int) (c Claim, err error) { + err = DBQueryRow("SELECT id_claim, subject, id_team, id_assignee, creation, state, priority FROM claims WHERE id_claim = ?", id).Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdAssignee, &c.Creation, &c.State, &c.Priority) + return +} + +func GetClaims() (res []Claim, err error) { + var rows *sql.Rows + if rows, err = DBQuery("SELECT id_claim, subject, id_team, id_assignee, creation, state, priority FROM claims"); err != nil { + return + } + defer rows.Close() + + for rows.Next() { + var c Claim + if err = rows.Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdAssignee, &c.Creation, &c.State, &c.Priority); err != nil { + return + } + res = append(res, c) + } + err = rows.Err() + + return +} + +func (t Team) GetClaims() (res []Claim, err error) { + var rows *sql.Rows + if rows, err = DBQuery("SELECT id_claim, subject, IdTeam, id_assignee, creation, state, priority FROM claims WHERE IdTeam = ?", t.Id); err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var c Claim + if err = rows.Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdAssignee, &c.Creation, &c.State, &c.Priority); err != nil { + return + } + res = append(res, c) + } + err = rows.Err() + + return +} + +func NewClaim(subject string, team *Team, assignee *ClaimAssignee, priority string) (Claim, error) { + var tid *int64 + if team == nil { + tid = nil + } else { + tid = &team.Id + } + + var aid *int64 + if assignee == nil { + aid = nil + } else { + aid = &assignee.Id + } + + if res, err := DBExec("INSERT INTO claims (subject, id_team, id_assignee, creation, state, priority) VALUES (?, ?, ?, ?, ?, ?)", subject, tid, aid, time.Now(), "new", priority); err != nil { + return Claim{}, err + } else if cid, err := res.LastInsertId(); err != nil { + return Claim{}, err + } else { + return Claim{cid, subject, tid, aid, time.Now(), "new", priority}, nil + } +} + +func (c Claim) GetTeam() (*Team, error) { + if c.IdTeam == nil { + return nil, nil + } else if t, err := GetTeam(int(*c.IdTeam)); err != nil { + return nil, err + } else { + return &t, nil + } +} + +func (c Claim) SetTeam(t Team) { + c.IdTeam = &t.Id +} + +func (c Claim) Update() (int64, error) { + if res, err := DBExec("UPDATE claims SET subject = ?, id_team = ?, id_assignee = ?, creation = ?, state = ?, priority = ? WHERE id_claim = ?", c.Subject, c.IdTeam, c.IdAssignee, c.Creation, c.State, c.Priority, c.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (c Claim) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM claims WHERE id_claim = ?", c.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearClaims() (int64, error) { + if res, err := DBExec("DELETE FROM claims"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + + +type ClaimDescription struct { + Id int64 `json:"id"` + IdAssignee int64 `json:"id_assignee"` + Content string `json:"content"` + Date time.Time `json:"date"` +} + +func (c Claim) GetDescriptions() (res []ClaimDescription, err error) { + var rows *sql.Rows + if rows, err = DBQuery("SELECT id_description, id_assignee, content, date FROM claim_descriptions WHERE id_claim = ?", c.Id); err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var d ClaimDescription + if err = rows.Scan(&d.Id, &d.IdAssignee, &d.Content, &d.Date); err != nil { + return + } + res = append(res, d) + } + err = rows.Err() + + return +} + +func (c Claim) AddDescription(content string, assignee ClaimAssignee) (ClaimDescription, error) { + if res, err := DBExec("INSERT INTO claim_descriptions (id_claim, id_assignee, content, date) VALUES (?, ?, ?, ?)", c.Id, assignee.Id, content, time.Now()); err != nil { + return ClaimDescription{}, err + } else if did, err := res.LastInsertId(); err != nil { + return ClaimDescription{}, err + } else { + return ClaimDescription{did, assignee.Id, content, time.Now()}, nil + } +} + +func (d ClaimDescription) Update() (int64, error) { + if res, err := DBExec("UPDATE claim_descriptions SET id_assignee = ?, content = ?, date = ? WHERE id_description = ?", d.IdAssignee, d.Content, d.Date, d.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (d ClaimDescription) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM claim_descriptions WHERE id_description = ?", d.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + + +type ClaimAssignee struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +func GetAssignee(id int64) (a ClaimAssignee, err error) { + err = DBQueryRow("SELECT id_assignee, name FROM claim_assignees WHERE id_assignee = ?", id).Scan(&a.Id, &a.Name) + return +} + +func GetAssignees() (res []ClaimAssignee, err error) { + var rows *sql.Rows + if rows, err = DBQuery("SELECT id_assignee, name FROM claim_assignees"); err != nil { + return + } + defer rows.Close() + + for rows.Next() { + var a ClaimAssignee + if err = rows.Scan(&a.Id, &a.Name); err != nil { + return + } + res = append(res, a) + } + err = rows.Err() + + return +} + +func NewClaimAssignee(name string) (ClaimAssignee, error) { + if res, err := DBExec("INSERT INTO claim_assignees (name) VALUES (?)", name); err != nil { + return ClaimAssignee{}, err + } else if aid, err := res.LastInsertId(); err != nil { + return ClaimAssignee{}, err + } else { + return ClaimAssignee{aid, name}, nil + } +} + +func (a ClaimAssignee) Update() (int64, error) { + if res, err := DBExec("UPDATE claim_assignees SET name = ? WHERE id_assignee = ?", a.Name, a.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (a ClaimAssignee) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM claim_assignees WHERE id_assignee = ?", a.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearAssignees() (int64, error) { + if res, err := DBExec("DELETE FROM claim_assignees"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (c Claim) GetAssignee() (*ClaimAssignee, error) { + if c.IdAssignee == nil { + return nil, nil + } else if a, err := GetAssignee(*c.IdAssignee); err != nil { + return nil, err + } else { + return &a, nil + } +} + +func (c Claim) SetAssignee(a ClaimAssignee) { + c.IdAssignee = &a.Id +}