View | Details | Raw Unified | Return to bug 31028
Collapse All | Expand All

(-)a/C4/Letters.pm (+7 lines)
Lines 739-744 sub _parseletter_sth { Link Here
739
    ($table eq 'biblio'       )    ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
739
    ($table eq 'biblio'       )    ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
740
    ($table eq 'biblioitems'  )    ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
740
    ($table eq 'biblioitems'  )    ? "SELECT * FROM $table WHERE   biblionumber = ?"                                  :
741
    ($table eq 'tickets'      )    ? "SELECT * FROM $table WHERE   id = ?"                                            :
741
    ($table eq 'tickets'      )    ? "SELECT * FROM $table WHERE   id = ?"                                            :
742
    ($table eq 'ticket_updates' )  ? "SELECT * FROM $table WHERE   id = ?"                                            :
742
    ($table eq 'credits'      )    ? "SELECT * FROM accountlines WHERE   accountlines_id = ?"                         :
743
    ($table eq 'credits'      )    ? "SELECT * FROM accountlines WHERE   accountlines_id = ?"                         :
743
    ($table eq 'debits'       )    ? "SELECT * FROM accountlines WHERE   accountlines_id = ?"                         :
744
    ($table eq 'debits'       )    ? "SELECT * FROM accountlines WHERE   accountlines_id = ?"                         :
744
    ($table eq 'items'        )    ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
745
    ($table eq 'items'        )    ? "SELECT * FROM $table WHERE     itemnumber = ?"                                  :
Lines 1736-1741 sub _get_tt_params { Link Here
1736
            plural   => 'tickets',
1737
            plural   => 'tickets',
1737
            pk       => 'id',
1738
            pk       => 'id',
1738
        },
1739
        },
1740
        ticket_updates => {
1741
            module   => 'Koha::Ticket::Updates',
1742
            singular => 'ticket_update',
1743
            plural   => 'ticket_updates',
1744
            pk       => 'id',
1745
        },
1739
        issues => {
1746
        issues => {
1740
            module   => 'Koha::Checkouts',
1747
            module   => 'Koha::Checkouts',
1741
            singular => 'checkout',
1748
            singular => 'checkout',
(-)a/Koha/REST/V1/Tickets.pm (+98 lines)
Lines 147-150 sub delete { Link Here
147
    };
147
    };
148
}
148
}
149
149
150
=head3 list_updates
151
152
=cut
153
154
sub list_updates {
155
    my $c = shift->openapi->valid_input or return;
156
157
    return try {
158
        my $ticket = Koha::Tickets->find( $c->validation->param('ticket_id') );
159
        unless ($ticket) {
160
            return $c->render(
161
                status  => 404,
162
                openapi => { error => "Ticket not found" }
163
            );
164
        }
165
166
        my $updates_set = $ticket->updates;
167
        my $updates     = $c->objects->search($updates_set);
168
        return $c->render( status => 200, openapi => $updates );
169
    }
170
    catch {
171
        $c->unhandled_exception($_);
172
    };
173
}
174
175
=head3 add_update
176
177
=cut
178
179
sub add_update {
180
    my $c = shift->openapi->valid_input or return;
181
182
    my $ticket_id_param = $c->validation->param('ticket_id');
183
    my $ticket_update   = $c->validation->param('body');
184
    $ticket_update->{ticket_id} //= $ticket_id_param;
185
186
    if ( $ticket_update->{ticket_id} != $ticket_id_param ) {
187
        return $c->render(
188
            status  => 400,
189
            openapi => { error => "Ticket Mismatch" }
190
        );
191
    }
192
193
    return try {
194
        my $state = delete $ticket_update->{state};
195
196
        # Store update
197
        my $update = Koha::Ticket::Update->new_from_api($ticket_update)->store;
198
        $update->discard_changes;
199
200
        # Update ticket state if needed
201
        if ( defined($state) && $state eq 'resolved' ) {
202
            my $ticket = $update->ticket;
203
            $ticket->set(
204
                {
205
                    resolver_id   => $update->user_id,
206
                    resolved_date => $update->date
207
                }
208
            )->store;
209
        }
210
211
        # Optionally add to message_queue here to notify reporter
212
        if ( $update->public ) {
213
            my $notice =
214
              ( defined($state) && $state eq 'resolved' )
215
              ? 'TICKET_RESOLVE'
216
              : 'TICKET_UPDATE';
217
            my $letter = C4::Letters::GetPreparedLetter(
218
                module      => 'catalog',
219
                letter_code => $notice,
220
                branchcode  => $update->user->branchcode,
221
                tables      => { ticket_updates => $update->id }
222
            );
223
224
            if ($letter) {
225
                my $message_id = C4::Letters::EnqueueLetter(
226
                    {
227
                        letter                 => $letter,
228
                        borrowernumber         => $update->ticket->reporter_id,
229
                        message_transport_type => 'email',
230
                    }
231
                );
232
            }
233
        }
234
235
        # Return
236
        $c->res->headers->location(
237
            $c->req->url->to_string . '/' . $update->id );
238
        return $c->render(
239
            status  => 201,
240
            openapi => $update->to_api
241
        );
242
    }
243
    catch {
244
        $c->unhandled_exception($_);
245
    };
246
}
247
150
1;
248
1;
(-)a/Koha/Ticket/Update.pm (+11 lines)
Lines 57-62 sub user { Link Here
57
57
58
=head2 Internal methods
58
=head2 Internal methods
59
59
60
=head3 to_api_mapping
61
62
This method returns the mapping for representing a Koha::Ticket::Update object
63
on the API.
64
65
=cut
66
67
sub to_api_mapping {
68
    return { id => 'update_id', };
69
}
70
60
=head3 _type
71
=head3 _type
61
72
62
=cut
73
=cut
(-)a/admin/columns_settings.yml (+17 lines)
Lines 633-638 modules: Link Here
633
            -
633
            -
634
              columnname: stocknumber
634
              columnname: stocknumber
635
635
636
    concerns:
637
      table_concerns:
638
        default_sort_order: 0
639
        columns:
640
            -
641
              columnname: reported
642
            -
643
              columnname: details
644
            -
645
              columnname: title
646
            -
647
              columnname: status
648
            -
649
              columnname: actions
650
              cannot_be_toggled: 1
651
              cannot_be_modified: 1
652
636
    z3950_search:
653
    z3950_search:
637
      resultst:
654
      resultst:
638
        default_sort_order: 1
655
        default_sort_order: 1
(-)a/api/v1/swagger/definitions/ticket.yaml (+5 lines)
Lines 48-51 properties: Link Here
48
      - "null"
48
      - "null"
49
    format: date-time
49
    format: date-time
50
    description: Date the ticket was resolved_date
50
    description: Date the ticket was resolved_date
51
  updates_count:
52
    type:
53
      - integer
54
      - "null"
55
    description: Number of updates
51
additionalProperties: false
56
additionalProperties: false
(-)a/api/v1/swagger/definitions/ticket_update.yaml (+30 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  update_id:
5
    type: integer
6
    description: Internal ticket update identifier
7
  ticket_id:
8
    type: integer
9
    description: Internal ticket identifier
10
  user:
11
    type:
12
      - object
13
      - "null"
14
    description: The object representing the patron who added the update
15
  user_id:
16
    type: integer
17
    description: Internal identifier for the patron who added the update
18
  date:
19
    type:
20
      - string
21
      - "null"
22
    format: date-time
23
    description: Date the ticket update was reported
24
  message:
25
    type: string
26
    description: Ticket update details
27
  public:
28
    type: boolean
29
    description: Is this update intended to be sent to the patron
30
additionalProperties: true
(-)a/api/v1/swagger/paths/tickets.yaml (+103 lines)
Lines 28-33 Link Here
28
            - reporter
28
            - reporter
29
            - resolver
29
            - resolver
30
            - biblio
30
            - biblio
31
            - updates+count
31
        collectionFormat: csv
32
        collectionFormat: csv
32
    responses:
33
    responses:
33
      "200":
34
      "200":
Lines 210-215 Link Here
210
    x-koha-authorization:
211
    x-koha-authorization:
211
      permissions:
212
      permissions:
212
        editcatalogue: edit_catalogue
213
        editcatalogue: edit_catalogue
214
"/tickets/{ticket_id}/updates":
215
  get:
216
    x-mojo-to: Tickets#list_updates
217
    operationId: listTicketUpdates
218
    tags:
219
      - tickets
220
    summary: List ticket updates
221
    produces:
222
      - application/json
223
    parameters:
224
      - $ref: "../swagger.yaml#/parameters/ticket_id_pp"
225
      - $ref: "../swagger.yaml#/parameters/match"
226
      - $ref: "../swagger.yaml#/parameters/order_by"
227
      - $ref: "../swagger.yaml#/parameters/page"
228
      - $ref: "../swagger.yaml#/parameters/per_page"
229
      - $ref: "../swagger.yaml#/parameters/q_param"
230
      - $ref: "../swagger.yaml#/parameters/q_body"
231
      - $ref: "../swagger.yaml#/parameters/q_header"
232
      - $ref: "../swagger.yaml#/parameters/request_id_header"
233
      - name: x-koha-embed
234
        in: header
235
        required: false
236
        description: Embed list sent as a request header
237
        type: array
238
        items:
239
          type: string
240
          enum:
241
            - user
242
        collectionFormat: csv
243
    responses:
244
      "200":
245
        description: A list of ticket updates
246
        schema:
247
          type: array
248
          items:
249
            $ref: "../swagger.yaml#/definitions/ticket_update"
250
      "403":
251
        description: Access forbidden
252
        schema:
253
          $ref: "../swagger.yaml#/definitions/error"
254
      "404":
255
        description: Ticket not found
256
        schema:
257
          $ref: "../swagger.yaml#/definitions/error"
258
      "500":
259
        description: |
260
          Internal server error. Possible `error_code` attribute values:
261
262
          * `internal_server_error`
263
        schema:
264
          $ref: "../swagger.yaml#/definitions/error"
265
      "503":
266
        description: Under maintenance
267
        schema:
268
          $ref: "../swagger.yaml#/definitions/error"
269
    x-koha-authorization:
270
      permissions:
271
        catalogue: "1"
272
  post:
273
    x-mojo-to: Tickets#add_update
274
    operationId: addTicketUpdate
275
    tags:
276
      - tickets
277
    summary: Add an update to the ticket
278
    parameters:
279
      - $ref: "../swagger.yaml#/parameters/ticket_id_pp"
280
      - name: body
281
        in: body
282
        description: A ticket update object
283
        required: true
284
        schema:
285
          $ref: "../swagger.yaml#/definitions/ticket_update"
286
    produces:
287
      - application/json
288
    responses:
289
      "201":
290
        description: Ticket added
291
        schema:
292
          $ref: "../swagger.yaml#/definitions/ticket_update"
293
      "401":
294
        description: Authentication required
295
        schema:
296
          $ref: "../swagger.yaml#/definitions/error"
297
      "403":
298
        description: Access forbidden
299
        schema:
300
          $ref: "../swagger.yaml#/definitions/error"
301
      "404":
302
        description: Ticket not found
303
        schema:
304
          $ref: "../swagger.yaml#/definitions/error"
305
      "500":
306
        description: Internal error
307
        schema:
308
          $ref: "../swagger.yaml#/definitions/error"
309
      "503":
310
        description: Under maintenance
311
        schema:
312
          $ref: "../swagger.yaml#/definitions/error"
313
    x-koha-authorization:
314
      permissions:
315
        editcatalogue: edit_catalogue
213
/public/tickets:
316
/public/tickets:
214
  post:
317
  post:
215
    x-mojo-to: Tickets#add
318
    x-mojo-to: Tickets#add
(-)a/api/v1/swagger/swagger.yaml (+4 lines)
Lines 90-95 definitions: Link Here
90
    $ref: ./definitions/suggestion.yaml
90
    $ref: ./definitions/suggestion.yaml
91
  ticket:
91
  ticket:
92
    $ref: ./definitions/ticket.yaml
92
    $ref: ./definitions/ticket.yaml
93
  ticket_update:
94
    $ref: ./definitions/ticket_update.yaml
93
  transfer_limit:
95
  transfer_limit:
94
    $ref: ./definitions/transfer_limit.yaml
96
    $ref: ./definitions/transfer_limit.yaml
95
  vendor:
97
  vendor:
Lines 319-324 paths: Link Here
319
    $ref: "./paths/tickets.yaml#/~1tickets"
321
    $ref: "./paths/tickets.yaml#/~1tickets"
320
  "/tickets/{ticket_id}":
322
  "/tickets/{ticket_id}":
321
    $ref: "./paths/tickets.yaml#/~1tickets~1{ticket_id}"
323
    $ref: "./paths/tickets.yaml#/~1tickets~1{ticket_id}"
324
  "/tickets/{ticket_id}/updates":
325
    $ref: "./paths/tickets.yaml#/~1tickets~1{ticket_id}~1updates"
322
  /transfer_limits:
326
  /transfer_limits:
323
    $ref: ./paths/transfer_limits.yaml#/~1transfer_limits
327
    $ref: ./paths/transfer_limits.yaml#/~1transfer_limits
324
  /transfer_limits/batch:
328
  /transfer_limits/batch:
(-)a/cataloguing/concerns.pl (+37 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use CGI qw ( -utf8 );
21
use C4::Context;
22
use C4::Auth qw( get_template_and_user );
23
use C4::Output qw( output_html_with_http_headers );
24
25
my $query = CGI->new;
26
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
27
    {
28
        template_name   => "cataloguing/concerns.tt",
29
        query           => $query,
30
        type            => "intranet",
31
        flagsrequired   => { cataloguing => '*' },
32
    }
33
);
34
35
output_html_with_http_headers $query, $cookie, $template->output;
36
37
1;
(-)a/installer/data/mysql/atomicupdate/bug_31028.pl (+16 lines)
Lines 93-97 return { Link Here
93
            }
93
            }
94
        );
94
        );
95
        say $out "Added new notice 'TICKET_ACKNOWLEDGEMENT'";
95
        say $out "Added new notice 'TICKET_ACKNOWLEDGEMENT'";
96
97
        $dbh->do(
98
            q{
99
                INSERT IGNORE INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
100
                VALUES ( 'catalog', 'TICKET_UPDATE', '', 'Concern updated', '1', 'Catalog concern updated', "Dear [% INCLUDE 'patron-title.inc' patron => ticket_update.ticket.reporter %],\r\n\r\nThe library has added an update to the concern you reported against [% INCLUDE 'biblio-title.inc' biblio=ticket_update.ticket.biblio link = 0 %].\r\n\r\nThe following comment was left: \r\n[% ticket_update.message %]\r\n\r\nThankyou", 'email' );
101
            }
102
        );
103
        say $out "Added new notice 'TICKET_UPDATE'";
104
105
        $dbh->do(
106
            q{
107
                INSERT IGNORE INTO letter(module,code,branchcode,name,is_html,title,content,message_transport_type)
108
                VALUES ( 'catalog', 'TICKET_RESOLVE', '', 'Concern resolved', '1', 'Catalog concern resolved', "Dear [% INCLUDE 'patron-title.inc' patron => ticket_update.ticket.reporter %],\r\n\r\nThe library has now marked your concern with [% INCLUDE 'biblio-title.inc' biblio=ticket_update.ticket.biblio link = 0 %] as resolved.\r\n\r\nThe following comment was left:   \r\n[% ticket_update.message %]\r\n\r\nThankyou", 'email' );
109
            }
110
        );
111
        say $out "Added new notice 'TICKET_RESOLVE'";
96
    }
112
    }
97
}
113
}
(-)a/installer/data/mysql/en/mandatory/sample_notices.yml (+36 lines)
Lines 61-66 tables: Link Here
61
            - ""
61
            - ""
62
            - "Thankyou"
62
            - "Thankyou"
63
63
64
        - module: catalog
65
          code: TICKET_RESOLVE
66
          branchcode: ""
67
          name: "Concern resolved"
68
          is_html: 1
69
          title: "Catalog concern resolved"
70
          message_transport_type: email
71
          lang: default
72
          content:
73
            - "Dear [% INCLUDE 'patron-title.inc' patron => ticket_update.ticket.reporter %],"
74
            - ""
75
            - "The library has now marked your concern with [% INCLUDE 'biblio-title.inc' biblio=ticket_update.ticket.biblio link = 0 %] as resolved."
76
            - ""
77
            - "The following comment was left: "
78
            - "[% ticket_update.message %]"
79
            - ""
80
            - "Thankyou"
81
82
        - module: catalog
83
          code: TICKET_UPDATE
84
          branchcode: ""
85
          name: "Concern updated"
86
          is_html: 1
87
          title: "Catalog concern updated"
88
          message_transport_type: email
89
          lang: default
90
          content:
91
            - "Dear [% INCLUDE 'patron-title.inc' patron => ticket_update.ticket.reporter %],"
92
            - ""
93
            - "The library has added an update to the concern you reported against [% INCLUDE 'biblio-title.inc' biblio=ticket_update.ticket.biblio link = 0 %]."
94
            - ""
95
            - "The following comment was left: "
96
            - "[% ticket_update.message %]"
97
            - ""
98
            - "Thankyou"
99
64
        - module: circulation
100
        - module: circulation
65
          code: ACCOUNT_CREDIT
101
          code: ACCOUNT_CREDIT
66
          branchcode: ""
102
          branchcode: ""
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+4 lines)
Lines 3374-3379 label { Link Here
3374
    white-space: pre-wrap;
3374
    white-space: pre-wrap;
3375
}
3375
}
3376
3376
3377
.wrapfix {
3378
    white-space: pre-wrap;
3379
}
3380
3377
pre {
3381
pre {
3378
    background-color: transparent;
3382
    background-color: transparent;
3379
    border: 0;
3383
    border: 0;
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-menu.inc (-1 / +6 lines)
Lines 29-35 Link Here
29
        </ul>
29
        </ul>
30
        [% END %]
30
        [% END %]
31
31
32
        [% IF ( CAN_user_tools_inventory ) %]
32
        [% IF ( CAN_user_tools_inventory || ( Koha.Preference('OpacCatalogConcerns') && CAN_user_editcatalogue_edit_catalogue ) ) %]
33
        <h5>Reports</h5>
33
        <h5>Reports</h5>
34
        <ul>
34
        <ul>
35
            [% IF ( CAN_user_tools_inventory ) %]
35
            [% IF ( CAN_user_tools_inventory ) %]
Lines 37-42 Link Here
37
                <a href="/cgi-bin/koha/tools/inventory.pl">Inventory</a>
37
                <a href="/cgi-bin/koha/tools/inventory.pl">Inventory</a>
38
            </li>
38
            </li>
39
            [% END %]
39
            [% END %]
40
            [% IF Koha.Preference('OpacCatalogConcerns') && CAN_user_editcatalogue_edit_catalogue %]
41
            <li>
42
                <a href="/cgi-bin/koha/cataloguing/concerns.pl">Catalog concerns</a>
43
            </li>
44
            [% END %]
40
        </ul>
45
        </ul>
41
        [% END %]
46
        [% END %]
42
47
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/cataloging-home.tt (-1 / +7 lines)
Lines 91-97 Link Here
91
                        </ul>
91
                        </ul>
92
                        [% END %]
92
                        [% END %]
93
93
94
                        [% IF ( CAN_user_tools_inventory ) %]
94
                        [% IF ( CAN_user_tools_inventory || ( Koha.Preference('OpacCatalogConcerns') && CAN_user_editcatalogue_edit_catalogue ) ) %]
95
                        <h3>Reports</h3>
95
                        <h3>Reports</h3>
96
                        <ul class="buttons-list">
96
                        <ul class="buttons-list">
97
                            [% IF ( CAN_user_tools_inventory ) %]
97
                            [% IF ( CAN_user_tools_inventory ) %]
Lines 99-104 Link Here
99
                                <a class="circ-button" href="/cgi-bin/koha/tools/inventory.pl"><i class="fa fa-line-chart"></i> Inventory</a>
99
                                <a class="circ-button" href="/cgi-bin/koha/tools/inventory.pl"><i class="fa fa-line-chart"></i> Inventory</a>
100
                            </li>
100
                            </li>
101
                            [% END %]
101
                            [% END %]
102
103
                            [% IF ( Koha.Preference('OpacCatalogConcerns') && CAN_user_editcatalogue_edit_catalogue ) %]
104
                            <li>
105
                                <a class="circ-button" href="/cgi-bin/koha/cataloguing/concerns.pl"><i class="fa fa-list-ul"></i> Catalog concerns</a>
106
                            </li>
107
                            [% END %]
102
                        </ul>
108
                        </ul>
103
                        [% END %]
109
                        [% END %]
104
110
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/concerns.tt (+306 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% SET footerjs = 1 %]
4
[% USE TablesSettings %]
5
[% INCLUDE 'doc-head-open.inc' %]
6
<title>
7
    Catalog concerns &rsaquo; Tools &rsaquo; Koha
8
</title>
9
[% INCLUDE 'doc-head-close.inc' %]
10
</head>
11
12
<body id="cat_concerns" class="cat">
13
    [% INCLUDE 'header.inc' %]
14
    [% INCLUDE 'cataloging-search.inc' %]
15
16
    <nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
17
        <ol>
18
            <li>
19
                <a href="/cgi-bin/koha/mainpage.pl">Home</a>
20
            </li>
21
            <li>
22
                <a href="/cgi-bin/koha/cataloguing/cataloging-home.pl">Cataloging</a>
23
            </li>
24
            <li>
25
                <a href="#" aria-current="page">
26
                    Catalog concerns
27
                </a>
28
            </li>
29
        </ol>
30
    </nav>
31
32
    <div class="main container-fluid">
33
        <div class="row">
34
            <div class="col-sm-10 col-sm-push-2">
35
                <main>
36
                    <h1>Concerns</h1>
37
38
                    <div class="page-section">
39
                        <fieldset class="action" style="cursor:pointer;">
40
                            <a id="hideResolved"><i class="fa fa-minus-square"></i> Hide resolved</a>
41
                            | <a id="showAll"><i class="fa fa-bars"></i> Show all</a>
42
                        </fieldset>
43
44
                        <table id="table_concerns">
45
                            <thead>
46
                                <tr>
47
                                    <th>Reported</th>
48
                                    <th>Details</th>
49
                                    <th>Title</th>
50
                                    <th>Status</th>
51
                                    <th data-class-name="actions noExport">Actions</th>
52
                                </tr>
53
                            </thead>
54
                        </table>
55
                    </div>
56
                </main>
57
            </div> <!-- /.col-sm-10.col-sm-push-2 -->
58
59
            <div class="col-sm-2 col-sm-pull-10">
60
                <aside>
61
                    [% INCLUDE 'cat-menu.inc' %]
62
                </aside>
63
            </div> <!-- /.col-sm-2.col-sm-pull-10 -->
64
        </div> <!-- /.row -->
65
66
        <!-- Display updates concern modal -->
67
        <div class="modal" id="ticketDetailsModal" tabindex="-1" role="dialog" aria-labelledby="ticketDetailsLabel">
68
            <div class="modal-dialog modal-lg" role="document">
69
                <div class="modal-content">
70
                    <div class="modal-header">
71
                        <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
72
                        <h4 class="modal-title" id="displayUpdateLabel">Ticket details</h4>
73
                    </div>
74
                    <div class="modal-body">
75
                        <div id="concern-details"></div>
76
                        <fieldset class="rows">
77
                            <ol>
78
                                <li>
79
                                    <label for="message">Update: </label>
80
                                    <textarea id="update_message" name="message"></textarea>
81
                                </li>
82
                                <li>
83
                                    <label for="public">Notify: </label>
84
                                    <input type="checkbox" name="public" id="public">
85
                                </li>
86
                            </ol>
87
                        </fieldset>
88
                    </div> <!-- /.modal-body -->
89
                    <div class="modal-footer">
90
                        <input type="hidden" name="ticket_id" id="ticket_id">
91
                        <button type="button" class="btn btn-default" id="resolveTicket">Resolve</button>
92
                        <button type="submit" class="btn btn-primary" id="updateTicket">Comment</button>
93
                    </div> <!-- /.modal-footer -->
94
                </div> <!-- /.modal-content -->
95
            </div> <!-- /.modal-dialog -->
96
        </div> <!-- /#displayUpdateModal -->
97
98
[% MACRO jsinclude BLOCK %]
99
    [% INCLUDE 'datatables.inc' %]
100
    [% INCLUDE 'columns_settings.inc' %]
101
    [% INCLUDE 'js-date-format.inc' %]
102
    [% INCLUDE 'js-patron-format.inc' %]
103
    [% INCLUDE 'js-biblio-format.inc' %]
104
    <script>
105
        $(document).ready(function() {
106
107
            var logged_in_user_borrowernumber = "[% logged_in_user.borrowernumber | html %]";
108
109
            var table_settings = [% TablesSettings.GetTableSettings('cataloguing', 'concerns', 'table_concerns', 'json') | $raw %];
110
111
            var tickets_url = '/api/v1/tickets';
112
            var tickets = $("#table_concerns").kohaTable({
113
                "ajax": {
114
                    "url": tickets_url
115
                },
116
                "embed": [
117
                    "reporter",
118
                    "resolver",
119
                    "biblio",
120
                    "updates+count",
121
                ],
122
                'emptyTable': '<div class="dialog message">' + _("Congradulations, there are no catalog concerns.") + '</div>',
123
                "columnDefs": [{
124
                    "targets": [0, 1, 2, 3],
125
                    "render": function(data, type, row, meta) {
126
                        if (type == 'display') {
127
                            if (data != null) {
128
                                return data.escapeHtml();
129
                            } else {
130
                                return "";
131
                            }
132
                        }
133
                        return data;
134
                    }
135
                }],
136
                "columns": [{
137
                        "data": "reported_date:reporter.firstname",
138
                        "render": function(data, type, row, meta) {
139
                            let reported = '<span class="date clearfix">' + $datetime(row.reported_date) + '</span>';
140
                            reported += '<span class="reporter clearfix">' + $patron_to_html(row.reporter, {
141
                                display_cardnumber: false,
142
                                url: true
143
                            }) + '</span>';
144
                            return reported;
145
                        },
146
                        "searchable": true,
147
                        "orderable": true
148
                    },
149
                    {
150
                        "data": "title:body",
151
                        "render": function(data, type, row, meta) {
152
                            let result = '<a role="button" href="#" data-toggle="modal" data-target="#ticketDetailsModal" data-concern="' + encodeURIComponent(row.ticket_id) + '">' + row.title + '</a>';
153
                            if (row.updates_count) {
154
                                result += '<span class="pull-right"><a role="button" href="#" data-toggle="modal" data-target="#ticketDetailsModal" data-concern="' + encodeURIComponent(row.ticket_id) + '"><i class="fa fa-comment" aria-hidden="true"></i> ' + row.updates_count + '</a></span>';
155
                            }
156
                            result += '<div id="detail_' + row.ticket_id + '" class="hidden">' + row.body + '</div>';
157
                            return result;
158
                        },
159
                        "searchable": true,
160
                        "orderable": true
161
                    },
162
                    {
163
                        "data": "biblio.title",
164
                        "render": function(data, type, row, meta) {
165
                            return $biblio_to_html(row.biblio, {
166
                                link: 1
167
                            });
168
                        },
169
                        "searchable": true,
170
                        "orderable": true
171
                    },
172
                    {
173
                        "data": "resolver.firstname:resolver.surname:resolved_date",
174
                        "render": function(data, type, row, meta) {
175
                            let result = '';
176
                            if (row.resolved_date) {
177
                                result += _("Resolved by:") + ' <span>' + $patron_to_html(row.resolver, {
178
                                    display_cardnumber: false,
179
                                    url: true
180
                                }) + '</span>';
181
                                result += '<span class="clearfix">' + $datetime(row.resolved_date) + '</span>';
182
                            } else {
183
                                result += _("Open");
184
                            }
185
                            return result;
186
                        },
187
                        "searchable": true,
188
                        "orderable": true
189
                    },
190
                    {
191
                        "data": function(row, type, val, meta) {
192
                            let result = '<a class="btn btn-default btn-xs" role="button" href="#" data-toggle="modal" data-target="#ticketDetailsModal" data-concern="' + encodeURIComponent(row.ticket_id) + '"><i class="fa fa-eye" aria-hidden="true"></i> ' + _("Details") + '</a>';
193
                            return result;
194
                        },
195
                        "searchable": false,
196
                        "orderable": false
197
                    },
198
                ]
199
            }, table_settings, 1);
200
201
            $('#hideResolved').on("click", function() {
202
                // It would be great if we could pass null here but it gets stringified
203
                tickets.DataTable().columns('3').search('special:undefined').draw();
204
            });
205
206
            $('#showAll').on("click", function() {
207
                tickets.DataTable().columns('3').search('').draw();
208
            });
209
210
            $('#ticketDetailsModal').on('show.bs.modal', function(event) {
211
                let modal = $(this);
212
                let button = $(event.relatedTarget);
213
                let ticket_id = button.data('concern');
214
                modal.find('.modal-footer input').val(ticket_id);
215
216
                let detail = $('#detail_' + ticket_id).text();
217
218
                let display = '<div class="list-group">';
219
                display += '<div class="list-group-item">';
220
                display += '<span class="wrapfix">' + detail + '</span>';
221
                display += '</div>';
222
                display += '<div id="concern-updates" class="list-group-item">';
223
                display += '<span>Loading updates . . .</span>';
224
                display += '</div>';
225
                display += '</div>';
226
227
                let details = modal.find('#concern-details');
228
                details.html(display);
229
230
                $.ajax({
231
                    url: "/api/v1/tickets/" + ticket_id + "/updates",
232
                    method: "GET",
233
                    headers: {
234
                        "x-koha-embed": "user"
235
                    },
236
                }).success(function(data) {
237
                    let updates_display = $('#concern-updates');
238
                    let updates = '';
239
                    data.forEach(function(item, index) {
240
                        updates += '<div class="list-group-item">';
241
                        updates += '<span class="wrapfix">' + item.message + '</span>';
242
                        updates += '<span class="clearfix">' + $patron_to_html(item.user, {
243
                            display_cardnumber: false,
244
                            url: true
245
                        }) + ' (' + $datetime(item.date) + ')</span>';
246
                        updates += '</div>';
247
                    });
248
                    updates_display.html(updates);
249
                }).error(function() {
250
251
                });
252
            });
253
254
            $('#ticketDetailsModal').on('click', '#updateTicket', function(e) {
255
                let ticket_id = $('#ticket_id').val();
256
                let params = {
257
                    'public': $('#public').is(":checked"),
258
                    message: $('#update_message').val(),
259
                    user_id: logged_in_user_borrowernumber
260
                };
261
262
                $.ajax({
263
                    url: "/api/v1/tickets/" + ticket_id + "/updates",
264
                    method: "POST",
265
                    data: JSON.stringify(params),
266
                    ontentType: "application/json; charset=utf-8"
267
                }).success(function() {
268
                    $("#ticketDetailsModal").modal('hide');
269
                    tickets.DataTable().ajax.reload(function(data) {
270
                        $("#concern_action_result_dialog").hide();
271
                        $("#concern_delete_success").html(_("Concern #%s updated successfully.").format(ticket_id)).show();
272
                    });
273
                }).error(function() {
274
                    $("#concern_update_error").html(_("Error resolving concern #%s. Check the logs.").format(ticket_id)).show();
275
                });
276
            });
277
278
            $('#ticketDetailsModal').on('click', '#resolveTicket', function(e) {
279
                let ticket_id = $('#ticket_id').val();
280
                let params = {
281
                    'public': $('#public').is(":checked"),
282
                    message: $('#update_message').val(),
283
                    user_id: logged_in_user_borrowernumber,
284
                    state: 'resolved'
285
                };
286
287
                $.ajax({
288
                    url: "/api/v1/tickets/" + ticket_id + "/updates",
289
                    method: "POST",
290
                    data: JSON.stringify(params),
291
                    ontentType: "application/json; charset=utf-8"
292
                }).success(function() {
293
                    $("#ticketDetailsModal").modal('hide');
294
                    tickets.DataTable().ajax.reload(function(data) {
295
                        $("#concern_action_result_dialog").hide();
296
                        $("#concern_delete_success").html(_("Concern #%s updated successfully.").format(ticket_id)).show();
297
                    });
298
                }).error(function() {
299
                    $("#concern_update_error").html(_("Error resolving concern #%s. Check the logs.").format(ticket_id)).show();
300
                });
301
            });
302
303
        });
304
    </script>
305
[% END %]
306
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt (-1 / +8 lines)
Lines 177-183 Link Here
177
                <div class="row">
177
                <div class="row">
178
                    <div class="col-sm-12">
178
                    <div class="col-sm-12">
179
                        [%# Following statement must be in one line for translatability %]
179
                        [%# Following statement must be in one line for translatability %]
180
                        [% IF ( CAN_user_tools_moderate_comments  && pendingcomments ) || ( CAN_user_tools_moderate_tags && pendingtags ) || ( CAN_user_borrowers_edit_borrowers && pending_borrower_modifications ) || ( CAN_user_suggestions_suggestions_manage && ( pendingsuggestions || all_pendingsuggestions )) || ( CAN_user_borrowers_edit_borrowers && pending_discharge_requests ) || pending_article_requests || ( Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count ) || ( Koha.Preference('OPACReportProblem') && CAN_user_problem_reports && pending_problem_reports.count ) || already_ran_jobs || new_curbside_pickups.count %]
180
                        [% IF ( CAN_user_tools_moderate_comments  && pendingcomments ) || ( CAN_user_tools_moderate_tags && pendingtags ) || ( CAN_user_borrowers_edit_borrowers && pending_borrower_modifications ) || ( CAN_user_suggestions_suggestions_manage && ( pendingsuggestions || all_pendingsuggestions )) || ( CAN_user_borrowers_edit_borrowers && pending_discharge_requests ) || pending_article_requests || ( Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count ) || ( Koha.preference('OpacCatalogConcerns') && pending_biblio_tickets && CAN_user_editcatalogue_edit_catalogue ) || ( Koha.Preference('OPACReportProblem') && CAN_user_problem_reports && pending_problem_reports.count ) || already_ran_jobs || new_curbside_pickups.count %]
181
                            <div id="area-pending" class="page-section">
181
                            <div id="area-pending" class="page-section">
182
                                [% IF pending_article_requests %]
182
                                [% IF pending_article_requests %]
183
                                <div class="pending-info" id="article_requests_pending">
183
                                <div class="pending-info" id="article_requests_pending">
Lines 232-237 Link Here
232
                                </div>
232
                                </div>
233
                                [% END %]
233
                                [% END %]
234
234
235
                                [% IF ( Koha.Preference('OpacCatalogConcerns') && CAN_user_editcatalogue_edit_catalogue ) %]
236
                                <div class="pending-info" id="catalog_concerns_pending">
237
                                    <a href="/cgi-bin/koha/cataloguing/concerns.pl">Catalog concerns pending</a>:
238
                                    <span class="pending-number-link">[% pending_biblio_tickets | html %]</span>
239
                                </div>
240
                                [% END %]
241
235
                                [% IF Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count %]
242
                                [% IF Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count %]
236
                                    <div class="pending-info" id="checkout_notes_pending">
243
                                    <div class="pending-info" id="checkout_notes_pending">
237
                                        <a href="/cgi-bin/koha/circ/checkout-notes.pl">Checkout notes pending</a>:
244
                                        <a href="/cgi-bin/koha/circ/checkout-notes.pl">Checkout notes pending</a>:
(-)a/koha-tmpl/intranet-tmpl/prog/js/datatables.js (-5 / +11 lines)
Lines 590-601 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
590
                                        var part = {};
590
                                        var part = {};
591
                                        var attr = attributes[i];
591
                                        var attr = attributes[i];
592
                                        let criteria = options.criteria;
592
                                        let criteria = options.criteria;
593
                                        if ( value.match(/^\^(.*)\$$/) ) {
593
                                        if ( value === 'special:undefined' ) {
594
                                            value = value.replace(/^\^/, '').replace(/\$$/, '');
594
                                            value = null;
595
                                            criteria = "exact";
595
                                            criteria = "exact";
596
                                        } else {
596
                                        }
597
                                           // escape SQL LIKE special characters % and _
597
                                        if ( value !== null ) {
598
                                           value = value.replace(/(\%|\\)/g, "\\$1");
598
                                            if ( value.match(/^\^(.*)\$$/) ) {
599
                                                value = value.replace(/^\^/, '').replace(/\$$/, '');
600
                                                criteria = "exact";
601
                                            } else {
602
                                                // escape SQL LIKE special characters %
603
                                                value = value.replace(/(\%|\\)/g, "\\$1");
604
                                            }
599
                                        }
605
                                        }
600
                                        part[!attr.includes('.')?'me.'+attr:attr] = criteria === 'exact'
606
                                        part[!attr.includes('.')?'me.'+attr:attr] = criteria === 'exact'
601
                                            ? value
607
                                            ? value
(-)a/mainpage.pl (-1 / +12 lines)
Lines 35-40 use Koha::Quotes; Link Here
35
use Koha::Suggestions;
35
use Koha::Suggestions;
36
use Koha::BackgroundJobs;
36
use Koha::BackgroundJobs;
37
use Koha::CurbsidePickups;
37
use Koha::CurbsidePickups;
38
use Koha::Tickets;
38
39
39
my $query = CGI->new;
40
my $query = CGI->new;
40
41
Lines 103-108 my $pending_article_requests = Koha::ArticleRequests->search_limited( Link Here
103
)->count;
104
)->count;
104
my $pending_problem_reports = Koha::ProblemReports->search({ status => 'New' });
105
my $pending_problem_reports = Koha::ProblemReports->search({ status => 'New' });
105
106
107
if ( C4::Context->preference('OpacCatalogConcerns') ) {
108
    my $pending_biblio_tickets = Koha::Tickets->search(
109
        {
110
            resolved_date => undef,
111
            biblio_id     => { '!=' => undef }
112
        }
113
    );
114
    $template->param(
115
        pending_biblio_tickets => $pending_biblio_tickets->count );
116
}
117
106
unless ( $logged_in_user->has_permission( { parameters => 'manage_background_jobs' } ) ) {
118
unless ( $logged_in_user->has_permission( { parameters => 'manage_background_jobs' } ) ) {
107
    my $already_ran_jobs = Koha::BackgroundJobs->search(
119
    my $already_ran_jobs = Koha::BackgroundJobs->search(
108
        { borrowernumber => $logged_in_user->borrowernumber } )->count ? 1 : 0;
120
        { borrowernumber => $logged_in_user->borrowernumber } )->count ? 1 : 0;
109
- 

Return to bug 31028