<eg-link-table-link i18n-label label="Statistical Popularity Badges"
routerLink="/staff/admin/local/rating/badge"></eg-link-table-link>
<eg-link-table-link i18n-label label="Surveys"
- url="/eg/staff/admin/local/action/survey"></eg-link-table-link>
+ routerLink="/staff/admin/local/action/survey"></eg-link-table-link>
<eg-link-table-link i18n-label label="Transit List"
url="/eg/staff/circ/transits/list"></eg-link-table-link>
<eg-link-table-link i18n-label label="Work Log"
url="/eg/staff/admin/workstation/log"></eg-link-table-link>
-
</eg-link-table>
</div>
path: 'config/standing_penalty',
component: StandingPenaltyComponent
}, {
+ path: 'action/survey',
+ loadChildren: '@eg/staff/admin/local/survey/survey.module#SurveyModule'
+}, {
path: ':schema/:table',
component: BasicAdminPageComponent
}];
--- /dev/null
+<eg-staff-banner bannerText="Survey ID # {{surveyId}}" i18n-bannerText
+ class="mb-3"></eg-staff-banner>
+<ngb-tabset #surveyTabs [activeId]="surveyTab" (tabChange)="onTabChange($event)" class="mb-3">
+ <ngb-tab title="Edit Survey" i18n-title id="edit">
+ <ng-template ngbTabContent>
+ <div class="col-lg-6 offset-lg-3 mt-3">
+ <div style="text-align: center;">
+ <button class="p-2 mb-3 btn btn-danger btn-lg"
+ (click)="endSurvey()" i18n>
+ End Survey Now
+ </button>
+ </div>
+ <eg-fm-record-editor displayMode="inline"
+ hiddenFieldsList="id"
+ datetimeFieldsList="start_date,end_date"
+ idlClass="asv"
+ mode="update"
+ [record]="surveyObj">
+ </eg-fm-record-editor>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Questions and Answers" i18n-title id="qanda">
+ <ng-template ngbTabContent>
+ <div class="col-lg-8 offset-lg-2 mt-3">
+ <eg-staff-banner bannerText="Questions & Answers" i18n-bannerText>
+ </eg-staff-banner>
+ <div *ngFor="let question of localArray; let questionIndex = index;">
+ <div class="mb-3 mt-3 p-2 bg-light input-group">
+ <label class="input-group-text">
+ <b>Question</b>
+ </label>
+ <input type="text" [(ngModel)]="question.words" class="form-control"
+ name="question-{{questionIndex}}">
+ <span class="input-group-append">
+ <button class="ml-2 btn btn-info"
+ (click)="updateQuestion(question)" i18n>
+ Save
+ </button>
+ <button class="ml-1 btn btn-danger"
+ (click)="deleteQuestion(question)" i18n>
+ Delete Question & Answers
+ </button>
+ </span>
+ </div>
+ <div *ngFor="let answer of question.answers; let answerIndex = index;"
+ class="mb-2 input-group">
+ <input class="form-control" type="text"
+ [(ngModel)]="answer.words"
+ name="answer-{{questionIndex}}-{{answerIndex}}">
+ <span class="input-group-append">
+ <button class="ml-2 btn btn-info"
+ (click)="updateAnswer(answer, question, questionIndex, answerIndex)"
+ i18n>
+ Save
+ </button>
+ <button class="ml-1 btn btn-danger" (click)="deleteAnswer(answer)"
+ i18n>
+ Delete
+ </button>
+ </span>
+ </div>
+ <div class="mb-2 input-group">
+ <input class="form-control" type="text"
+ [(ngModel)]="newAnswerArray[questionIndex].inputText"
+ value="">
+ <span class="input-group-append">
+ <button class="ml-2 btn btn-info"
+ (click)="createAnswer(newAnswerArray[questionIndex].inputText, question)"
+ i18n>
+ Add Answer
+ </button>
+ </span>
+ </div>
+ </div>
+ <div class="mb-3 mt-3 p-2 bg-light input-group">
+ <label class="input-group-text">
+ <b>New Question</b>
+ </label>
+ <input #newQuestionInput
+ class="form-control"
+ type="text"
+ [(ngModel)]="newQuestionText"
+ name="question-new" value="">
+ <span class="input-group-append">
+ <button class="ml-2 btn btn-info"
+ (click)="createQuestion(newQuestionText)" i18n>
+ Save Question & Add Answer
+ </button>
+ </span>
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+</ngb-tabset>
+
+<eg-string #createAnswerString i18n-text text="New Answer Added"></eg-string>
+<eg-string #createAnswerErrString i18n-text text="Failed to Create New Answer">
+ </eg-string>
+<eg-string #createQuestionString i18n-text text="New Question Added"></eg-string>
+<eg-string #createQuestionErrString i18n-text text="Failed to Create New Question">
+ </eg-string>
+<eg-string #delAnswerSuccessStr i18n-text text="Survey Answer deleted">
+ </eg-string>
+<eg-string #delAnswerFailStr i18n-text text="Survey Answer deletion failed">
+ </eg-string>
+<eg-string #delQuestionSuccessStr i18n-text text="Survey Question deleted">
+ </eg-string>
+<eg-string #delQuestionFailStr i18n-text text="Survey Question deletion failed">
+ </eg-string>
+<eg-string #updateAnswerSuccessStr i18n-text text="Survey Answer updated">
+ </eg-string>
+<eg-string #updateAnswerFailStr i18n-text text="Survey Answer update failed">
+ </eg-string>
+<eg-string #updateQuestionSuccessStr i18n-text text="Survey Question updated">
+ </eg-string>
+<eg-string #updateQuestionFailStr i18n-text text="Survey Question update failed">
+ </eg-string>
+<eg-string #endSurveyFailedString i18n-text
+ text="Ending Survey failed or was not allowed"></eg-string>
+<eg-string #endSurveySuccessString i18n-text text="Survey ended"></eg-string>
+<eg-string #questionAlreadyStartedErrString i18n-text
+ text="The survey Start Date must be set for the future to add new questions or modify existing questions.">
+ </eg-string>
\ No newline at end of file
--- /dev/null
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {IdlObject, IdlService } from '@eg/core/idl.service';
+import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ templateUrl: './survey-edit.component.html'
+})
+
+export class SurveyEditComponent implements OnInit {
+ surveyId: number;
+ surveyObj: IdlObject;
+ localArray: any;
+ newAnswerArray: object[];
+ newQuestionText: string;
+ surveyTab: string;
+
+ @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+
+ @ViewChild('createAnswerString', { static: true })
+ createAnswerString: StringComponent;
+ @ViewChild('createAnswerErrString', { static: true })
+ createAnswerErrString: StringComponent;
+ @ViewChild('createQuestionString', { static: true })
+ createQuestionString: StringComponent;
+ @ViewChild('createQuestionErrString', { static: true })
+ createQuestionErrString: StringComponent;
+
+ @ViewChild('updateQuestionSuccessStr', { static: true })
+ updateQuestionSuccessStr: StringComponent;
+ @ViewChild('updateQuestionFailStr', { static: true })
+ updateQuestionFailStr: StringComponent;
+ @ViewChild('updateAnswerSuccessStr', { static: true })
+ updateAnswerSuccessStr: StringComponent;
+ @ViewChild('updateAnswerFailStr', { static: true })
+ updateAnswerFailStr: StringComponent;
+
+ @ViewChild('delAnswerSuccessStr', { static: true })
+ delAnswerSuccessStr: StringComponent;
+ @ViewChild('delAnswerFailStr', { static: true })
+ delAnswerFailStr: StringComponent;
+ @ViewChild('delQuestionSuccessStr', { static: true })
+ delQuestionSuccessStr: StringComponent;
+ @ViewChild('delQuestionFailStr', { static: true })
+ delQuestionFailStr: StringComponent;
+
+ @ViewChild('endSurveyFailedString', { static: true })
+ endSurveyFailedString: StringComponent;
+ @ViewChild('endSurveySuccessString', { static: true })
+ endSurveySuccessString: StringComponent;
+ @ViewChild('questionAlreadyStartedErrString', { static: true })
+ questionAlreadyStartedErrString: StringComponent;
+
+ constructor(
+ private auth: AuthService,
+ private net: NetService,
+ private route: ActivatedRoute,
+ private toast: ToastService,
+ private idl: IdlService,
+ ) {
+ }
+
+ ngOnInit() {
+ this.surveyId = parseInt(this.route.snapshot.paramMap.get('id'), 10);
+ this.updateData();
+ }
+
+ updateData() {
+ this.newQuestionText = '';
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.fleshed.retrieve',
+ this.surveyId
+ ).subscribe(res => {
+ this.surveyObj = res;
+ this.buildLocalArray(res);
+ return res;
+ });
+ }
+
+ onTabChange(event: NgbTabChangeEvent) {
+ this.surveyTab = event.nextId;
+ }
+
+ buildLocalArray(res) {
+ this.localArray = [];
+ this.newAnswerArray = [];
+ const allQuestions = res.questions();
+ allQuestions.forEach((question, index) => {
+ this.newAnswerArray.push({inputText: ''});
+ question.words = question.question();
+ question.answers = question.answers();
+ this.localArray.push(question);
+ question.answers.forEach(answer => {
+ answer.words = answer.answer();
+ });
+ this.sortAnswers(index);
+ });
+ this.sortQuestions();
+ }
+
+ sortQuestions() {
+ this.localArray.sort(function(a, b) {
+ const q1 = a.question().toUpperCase();
+ const q2 = b.question().toUpperCase();
+ return (q1 < q2) ? -1 : (q1 > q2) ? 1 : 0;
+ });
+ }
+
+ sortAnswers(questionIndex) {
+ this.localArray[questionIndex].answers.sort(function(a, b) {
+ const a1 = a.answer().toUpperCase();
+ const a2 = b.answer().toUpperCase();
+ return (a1 < a2) ? -1 : (a1 > a2) ? 1 : 0;
+ });
+ }
+
+ updateQuestion(questionToChange) {
+ if (this.surveyHasBegun()) {
+ return;
+ }
+ questionToChange.question(questionToChange.words);
+ questionToChange.ischanged(true);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.updateQuestionFailStr.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.updateQuestionSuccessStr.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+ });
+ }
+
+ deleteQuestion(questionToDelete) {
+ if (this.surveyHasBegun()) {
+ return;
+ }
+ questionToDelete.isdeleted(true);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.delQuestionFailStr.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.delQuestionSuccessStr.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+
+ });
+ }
+
+ createQuestion(newQuestionText) {
+ if (this.surveyHasBegun()) {
+ return;
+ }
+ const newQuestion = this.idl.create('asvq');
+ newQuestion.question(newQuestionText);
+ newQuestion.isnew(true);
+ let questionObjects = [];
+ questionObjects = this.surveyObj.questions();
+ questionObjects.push(newQuestion);
+ this.surveyObj.questions(questionObjects);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.newQuestionText = '';
+ this.createQuestionErrString.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.newQuestionText = '';
+ this.createQuestionString.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+
+ });
+ }
+
+ deleteAnswer(answerObj) {
+ if (this.surveyHasBegun()) {
+ return;
+ }
+ answerObj.isdeleted(true);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.delAnswerFailStr.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.delAnswerSuccessStr.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+ });
+ }
+
+ updateAnswer(answerObj) {
+ if (this.surveyHasBegun()) {
+ return;
+ }
+ answerObj.answer(answerObj.words);
+ answerObj.ischanged(true);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.updateAnswerFailStr.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.updateAnswerSuccessStr.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+ });
+ }
+
+ createAnswer(newAnswerText, questionObj) {
+ // Create answer *is* allowed if survey has already begun
+ const questionId = questionObj.id();
+ const newAnswer = this.idl.create('asva');
+ newAnswer.answer(newAnswerText);
+ newAnswer.question(questionId);
+ newAnswer.isnew(true);
+ questionObj.answers.push(newAnswer);
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), this.surveyObj
+ ).subscribe(res => {
+ if (res.debug) {
+ this.createAnswerErrString.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.buildLocalArray(this.surveyObj);
+ this.createAnswerString.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+ });
+ }
+
+ endSurvey() {
+ const today = new Date().toISOString();
+ this.surveyObj.end_date(today);
+ this.surveyObj.ischanged(true);
+ // to get fm-editor to display changed date we need to set
+ // this.surveyObj to null temporarily
+ const surveyClone = this.idl.clone(this.surveyObj);
+ this.surveyObj = null;
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.update',
+ this.auth.token(), surveyClone
+ ).subscribe(res => {
+ if (res.debug) {
+ this.endSurveyFailedString.current().then(msg => this.toast.warning(msg));
+ return res;
+ } else {
+ this.surveyObj = res;
+ this.surveyObj.ischanged(false);
+ this.buildLocalArray(this.surveyObj);
+ this.endSurveySuccessString.current().then(msg => this.toast.success(msg));
+ return res;
+ }
+ });
+ }
+
+ surveyHasBegun() {
+ const surveyStartDate = new Date(this.surveyObj.start_date());
+ const now = new Date();
+ if (surveyStartDate <= now) {
+ this.questionAlreadyStartedErrString.current().then(msg =>
+ this.toast.warning(msg));
+ return true;
+ }
+ return false;
+ }
+}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {SurveyComponent} from './survey.component';
+import {SurveyEditComponent} from './survey-edit.component';
+
+const routes: Routes = [{
+ path: '',
+ component: SurveyComponent
+}, {
+ path: ':id',
+ component: SurveyEditComponent
+}];
+
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class SurveyRoutingModule {}
+
+
--- /dev/null
+<eg-staff-banner bannerText="Survey Configuration" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-grid #grid idlClass="asv" [dataSource]="gridDataSource"
+[sortable]="true">
+ <eg-grid-toolbar-button label="New Survey" i18n-label [action]="createNew">
+ </eg-grid-toolbar-button>
+ <eg-grid-toolbar-action label="Edit Selected" i18n-label [action]="editSelected">
+ </eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label
+ (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="End Survey Now" i18n-label
+ (onClick)="endSurvey($event)"></eg-grid-toolbar-action>
+</eg-grid>
+
+<eg-fm-record-editor
+ datetimeFieldsList="start_date,end_date"
+ hiddenFieldsList="id"
+ #editDialog
+ idlClass="asv">
+</eg-fm-record-editor>
+
+<eg-string #createString i18n-text text="New Survey Added"></eg-string>
+<eg-string #createErrString i18n-text text="Failed to Create New Survey">
+ </eg-string>
+<eg-string #endSurveyFailedString i18n-text
+ text="Ending Survey failed or was not allowed"></eg-string>
+<eg-string #endSurveySuccessString i18n-text text="Survey ended">
+ </eg-string>
+<eg-string #deleteFailedString i18n-text
+ text="Delete of Survey failed or was not allowed"></eg-string>
+<eg-string #deleteSuccessString i18n-text text="Delete of Survey succeeded">
+ </eg-string>
+<eg-string #successString i18n-text text="Update of Survey succeeded">
+ </eg-string>
+<eg-string #updateFailedString i18n-text text="Update of Survey succeeded">
+ </eg-string>
--- /dev/null
+import {Pager} from '@eg/share/util/pager';
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {Router} from '@angular/router';
+import {IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+
+@Component({
+ templateUrl: './survey.component.html'
+})
+
+export class SurveyComponent implements OnInit {
+
+ gridDataSource: GridDataSource;
+
+ @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+ @ViewChild('grid', { static: true }) grid: GridComponent;
+ @ViewChild('successString', { static: true }) successString: StringComponent;
+ @ViewChild('createString', { static: true }) createString: StringComponent;
+ @ViewChild('createErrString', { static: true }) createErrString: StringComponent;
+ @ViewChild('updateFailedString', { static: true }) updateFailedString: StringComponent;
+ @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent;
+ @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent;
+ @ViewChild('endSurveyFailedString', { static: true }) endSurveyFailedString: StringComponent;
+ @ViewChild('endSurveySuccessString', { static: true }) endSurveySuccessString: StringComponent;
+
+ @Input() dialogSize: 'sm' | 'lg' = 'lg';
+
+ constructor(
+ private auth: AuthService,
+ private net: NetService,
+ private pcrud: PcrudService,
+ private toast: ToastService,
+ private router: Router
+ ) {
+ this.gridDataSource = new GridDataSource();
+ }
+
+ ngOnInit() {
+ this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return this.pcrud.retrieveAll('asv', {});
+ };
+
+ this.grid.onRowActivate.subscribe(
+ (idlThing: IdlObject) => {
+ const idToEdit = idlThing.id();
+ this.navigateToEditPage(idToEdit);
+ }
+ );
+ }
+
+ showEditDialog(idlThing: IdlObject): Promise<any> {
+ return;
+ }
+
+ editSelected = (surveys: IdlObject[]) => {
+ const idToEdit = surveys[0].id();
+ this.navigateToEditPage(idToEdit);
+ }
+
+ endSurvey = (surveys: IdlObject[]) => {
+ const today = new Date().toISOString();
+ for (let i = 0; i < surveys.length; i++) {
+ surveys[i].end_date(today);
+ this.pcrud.update(surveys[i]).toPromise().then(
+ async (ok) => {
+ this.toast.success(await this.endSurveySuccessString.current());
+ },
+ async (err) => {
+ this.toast.warning(await this.endSurveyFailedString.current());
+ }
+ );
+ }
+ }
+
+ deleteSelected = (surveys: IdlObject[]) => {
+ for (let i = 0; i < surveys.length; i++) {
+ const idToDelete = surveys[i].id();
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.survey.delete.cascade.override',
+ this.auth.token(), idToDelete
+ ).subscribe(res => {
+ this.deleteSuccessString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ return res;
+ }, (err) => {
+ this.deleteFailedString.current()
+ .then(str => this.toast.success(str));
+ });
+ }
+ }
+
+ navigateToEditPage(id: any) {
+ this.router.navigate(['/staff/admin/local/action/survey/' + id]);
+ }
+
+ createNew = () => {
+ this.editDialog.mode = 'create';
+ this.editDialog.datetimeFields = 'start_date,end_date';
+ this.editDialog.open({size: this.dialogSize}).subscribe(
+ ok => {
+ this.createString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ },
+ rejection => {
+ if (!rejection.dismissed) {
+ this.createErrString.current()
+ .then(str => this.toast.danger(str));
+ }
+ }
+ );
+ }
+}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {SurveyComponent} from './survey.component';
+import {FormsModule} from '@angular/forms';
+import {SurveyEditComponent} from './survey-edit.component';
+import {SurveyRoutingModule} from './survey-routing.module';
+
+@NgModule({
+ declarations: [
+ SurveyComponent,
+ SurveyEditComponent
+ ],
+ imports: [
+ AdminCommonModule,
+ SurveyRoutingModule,
+ FormsModule,
+ ],
+ exports: [
+ ],
+ providers: [
+ ]
+})
+
+export class SurveyModule {
+}
use OpenILS::Event;
use Time::HiRes qw(time);
use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenSRF::Utils::Logger qw/$logger/;
my $apputils = "OpenILS::Application::AppUtils";
+__PACKAGE__->register_method(
+ method => "update_survey",
+ api_name => "open-ils.circ.survey.update",
+ signature => {
+ desc => q/Create, update, delete surveys, survey questions, and
+ survey answers. Relies on isnew ; isnchanged ; isdeleted
+ attributes of provided objects to determine outcome.
+ /,
+ params => [
+ {desc => 'Authtoken', type => 'string'},
+ {desc => 'Fleshed survey (asv) object', type => 'object'}
+ ],
+ return => '1 on success, event on error'
+ }
+);
+
+
+sub update_survey {
+ my ($self, $client, $auth, $survey) = @_;
+
+ my $e = new_editor(authtoken => $auth, xact => 1);
+ return $e->die_event unless $e->checkauth;
+ return $e->die_event unless $e->allowed('ADMIN_SURVEY', $survey->owner);
+
+ my $questions = $survey->questions || [];
+
+ if ($survey->isdeleted) {
+
+ $questions = $e->search_action_survey_question({survey => $survey->id});
+ $_->isdeleted(1) for @$questions;
+ $survey->questions($questions);
+
+ # Remove dependent data first.
+ return $e->die_event if update_questions($e, $survey);
+ return $e->die_event unless $e->delete_action_survey($survey);
+
+ } else {
+
+ if ($survey->isnew) {
+
+ $survey->clear_id;
+ return $e->die_event unless $e->create_action_survey($survey);
+
+ $_->isnew(1) for @$questions;
+
+ } elsif ($survey->ischanged) {
+
+ return $e->die_event unless $e->update_action_survey($survey);
+ }
+
+ return $e->die_event if update_questions($e, $survey);
+ }
+
+ $e->commit;
+
+ $e->xact_begin;
+ $survey = $e->retrieve_action_survey([
+ $survey->id,
+ { flesh => 2,
+ flesh_fields => {asv => ['questions'], 'asvq' => ['answers']}
+ }
+ ]);
+ $e->rollback;
+
+ return $survey;
+}
+
+# returns undef on success, event on error
+sub update_questions {
+ my ($e, $survey) = @_;
+
+ for my $question (@{$survey->questions}) {
+
+ if ($question->isdeleted) {
+
+ # Get the full set
+ my $answers =
+ $e->search_action_survey_answer({question => $question->id});
+ $_->isdeleted(1) for @$answers;
+ $question->answers($answers);
+
+ # Delete linked objects first.
+ return 1 if update_answers($e, $question);
+ return $e->die_event
+ unless $e->delete_action_survey_question($question);
+
+ } else {
+
+ if ($question->ischanged) {
+
+ return $e->die_event
+ unless $e->update_action_survey_question($question);
+
+ } elsif ($question->isnew) {
+
+ $question->survey($survey->id);
+ $question->clear_id;
+
+ return $e->die_event unless $e->create_action_survey_question($question);
+ }
+
+ return 1 if update_answers($e, $question);
+ }
+ }
+
+ return undef;
+}
+
+sub update_answers {
+ my ($e, $question) = @_;
+
+ return undef unless $question->answers;
+
+ for my $answer (@{$question->answers}) {
+
+ if ($answer->isdeleted) {
+ my $responses =
+ $e->search_action_survey_response({answer => $answer->id});
+
+ for my $response (@$responses) {
+ return $e->die_event unless
+ $e->delete_action_survey_response($response);
+ }
+
+ return $e->die_event unless $e->delete_action_survey_answer($answer);
+
+ } elsif ($answer->isnew) {
+
+ $answer->clear_id;
+ $answer->question($question->id);
+
+ return $e->die_event
+ unless $e->create_action_survey_answer($answer);
+
+ } elsif ($answer->ischanged) {
+ return $e->die_event
+ unless $e->update_action_survey_answer($answer);
+ }
+ }
+
+ return undef;
+}
+
+
+
# - creates a new survey
# expects a survey complete with questions and answers
__PACKAGE__->register_method(
return $survey;
}
-sub _update_survey {
- my($session, $survey) = @_;
-}
-
sub _add_questions {
my($session, $survey) = @_;
-1;
+1;
\ No newline at end of file