Bug 34632: Add patron_import background job display template

patron_import.inc provides the report/detail/js blocks consumed by the
background job viewer: a summary of counts when finished, a live progress
bar while running, and the full feedback and error analysis in the detail
section. background_jobs.tt gains a patron_import entry in the job type
filter dropdown.

Test plan:
1) Apply patches, restart plack.
2) Navigate to the patron import tool:
  <staff_url>/cgi-bin/koha/tools/import_borrowers.pl
3) Upload the provided sample CSV file and click 'Import patrons'.
4) Confirm the page shows "The patron import job has been enqueued!" with a link to view the job.
5) Follow the link to the background job. Confirm a progress bar is shown while the job is running.
6) Once finished, confirm the report shows the correct counts (imported, overwritten, already_in_db, invalid, total).
7) Confirm the imported patrons exist under 'Patrons'.
8) Re-import the same file with 'Overwrite cardnumber' unchecked. Confirm 'already_in_db' count is 5.
9) Re-import again with 'Overwrite cardnumber' checked. Confirm 'overwritten' count is 5.
10) Visit the background jobs admin and filter by type 'Import patrons':
  <staff_url>/cgi-bin/koha/admin/background_jobs.pl
11) Confirm the jobs appear in the list.
12) Run the tests:
  prove t/db_dependent/Koha/BackgroundJob/PatronImport.t

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Pedro Amorim 2026-06-17 10:22:42 +00:00
parent 88644d26af
commit 5ca53cf2bc
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
2 changed files with 157 additions and 0 deletions

View File

@ -0,0 +1,153 @@
[% USE Koha %]
[% USE raw %]
[% BLOCK report %]
[% SET job_data = job.decoded_data %]
[% IF job.status == 'finished' %]
<div class="alert alert-info">
<ul>
<li>[% job_data.imported | html %] imported records</li>
[% IF job_data.patron_list_name %]
<li>Patron list with imported patrons: [% job_data.patron_list_name | html %]</li>
[% END %]
<li>[% job_data.overwritten | html %] overwritten records</li>
<li>[% job_data.already_in_db | html %] not imported because they are already in the borrowers table and overwrite is disabled</li>
<li>[% job_data.invalid | html %] not imported because they are not in the expected format</li>
<li>[% job_data.total | html %] records parsed</li>
</ul>
</div>
[% ELSIF job.status == 'started' OR job.status == 'running' %]
[% INCLUDE "job_progress.inc" job_id=job.id %]
[% ELSIF job.status == 'cancelled' %]
<div class="alert alert-warning">The job was cancelled before it finished.</div>
[% ELSIF job.status == 'failed' %]
<div class="alert alert-danger">The job failed.</div>
[% ELSE %]
[% INCLUDE "job_progress.inc" job_id=job.id %]
[% END %]
[% END %]
[% BLOCK detail %]
[% SET job_data = job.decoded_data %]
[% IF job_data.feedback %]
<h3>Feedback</h3>
<ul class="feedback">
[% FOREACH f IN job_data.feedback %]
<li>
[% IF f.filename %]
<span>Parsing upload file</span> <span class="filename">[% f.filename | html %]</span>
[% ELSIF f.backend %]
<span>Upload parsed using [% f.backend | html %]</span>
[% ELSIF f.headerrow %]
<span>These fields found: [% f.value | html %]</span>
[% ELSIF f.already_in_db %]
<span>Patron already in database: [% f.value | html %]</span>
[% ELSE %]
[% f.name | html %]:
[% f.value | html %]
[% END %]
</li>
[% END %]
</ul>
[% END %]
[% IF job_data.errors %]
<h3>Error analysis</h3>
<ul>
[% FOREACH e IN job_data.errors %]
[% IF e.badheader %]<li>Header row could not be parsed</li>[% END %]
[% FOREACH missing_critical IN e.missing_criticals %]
<li class="line_error">
Line <span class="linenumber">[% missing_critical.line | html %]</span>
[% IF missing_critical.badparse %]
<span>could not be parsed!</span>
[% ELSIF missing_critical.bad_date %]
<span>has &quot;[% missing_critical.key | html %]&quot; in unrecognized format:</span> &quot;[% missing_critical.value | html %]&quot;
[% ELSE %]
<span>Critical field &quot;[% missing_critical.key | html %]&quot;</span>
[% IF missing_critical.branch_map %]
<span>has unrecognized value &quot;[% missing_critical.value | html %]&quot;</span>
[% ELSIF missing_critical.category_map %]
<span>has unrecognized value &quot;[% missing_critical.value | html %]&quot;</span>
[% ELSE %]
<span>missing</span>
[% END %]
(<span>borrowernumber: [% missing_critical.borrowernumber | html %]</span>; <span>surname: [% missing_critical.surname | html %]</span>).
[% END %]
<br />
<code>[% missing_critical.lineraw | html %]</code>
</li>
[% END %]
[% IF e.invalid_cardnumber %]
<li class="line_error">Card number [% e.cardnumber | html %] is not a valid card number[% IF e.borrowernumber %](for patron with borrowernumber [% e.borrowernumber | html %])[% END %]</li>
[% END %]
[% IF e.duplicate_userid %]
<li class="line_error">Userid [% e.userid | html %] is already used by another patron.</li>
[% END %]
[% IF e.passwd_too_short %]
<li class="line_error">Password is too short for patron with borrowernumber [% e.borrowernumber | html %]. Minimum length is [% e.min_length | html %], length is [% e.length | html %].</li>
[% END %]
[% IF e.passwd_whitespace %]
<li class="line_error">Password contains whitespace for patron with borrowernumber [% e.borrowernumber | html %].</li>
[% END %]
[% IF e.passwd_too_weak %]
<li class="line_error">Password is too weak for patron with borrowernumber [% e.borrowernumber | html %].</li>
[% END %]
[% IF e.passwd_plugin_err %]
<li class="line_error">Password plugin error for patron with borrowernumber [% e.borrowernumber | html %].</li>
[% END %]
[% IF e.passwd_unknown_err %]
<li class="line_error">Password error for patron with borrowernumber [% e.borrowernumber | html %].</li>
[% END %]
[% IF e.patron_attribute_unique_id_constraint %]
<li class="line_error">
[% IF e.borrowernumber %]
<span>Patron attribute [% e.attribute.code | html %] must be unique for patron with borrowernumber [% e.borrowernumber | html %].</span>
[% ELSE %]
<span>Patron attribute [% e.attribute.code | html %] must be unique for patron [% e.patron_id | html %].</span>
[% END %]
</li>
[% END %]
[% IF e.patron_attribute_invalid_type %]
<li class="line_error">
[% IF e.borrowernumber %]
<span>Patron attribute [% e.attribute_type_code | html %] is invalid for patron with borrowernumber [% e.borrowernumber | html %].</span>
[% ELSE %]
<span>Patron attribute [% e.attribute_type_code | html %] is invalid for patron [% e.patron_id | html %].</span>
[% END %]
</li>
[% END %]
[% IF e.patron_attribute_non_repeatable %]
<li class="line_error">
[% IF e.borrowernumber %]
<span>Patron attribute [% e.attribute.code | html %] is non-repeatable for patron with borrowernumber [% e.borrowernumber | html %].</span>
[% ELSE %]
<span>Patron attribute [% e.attribute.code | html %] is non-repeatable for patron [% e.patron_id | html %].</span>
[% END %]
</li>
[% END %]
[% END %]
</ul>
[% END %]
[% END %]
[% BLOCK js %]
[% IF job.status != 'finished' AND job.status != 'cancelled' AND job.status != 'failed' %]
[% INCLUDE 'str/job_progress.inc' job_id=job.id %]
[% Asset.js("js/job_progress.js") | $raw %]
<script nonce="[% Koha.CSPNonce | $raw %]">
$(document).ready(function () {
updateProgress([% job.id | html %], {
finish_callback: function () {
window.location.reload();
},
});
});
</script>
[% END %]
[% END %]

View File

@ -226,6 +226,10 @@
'_id': 'import_from_kbart_file',
'_str': _("Import titles from a KBART file").escapeHtml()
},
{
'_id': 'patron_import',
'_str': _("Import patrons").escapeHtml()
},
{
'_id': 'file_transport_test',
'_str': _("File transport connection test")