Bug 37893: SIP accounts patron attributes

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Pedro Amorim 2025-01-30 12:36:38 -01:00 committed by Lucas Gass
parent a0680c94f0
commit 48b845f0eb
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
13 changed files with 426 additions and 34 deletions

View File

@ -38,8 +38,7 @@ sub list {
return try {
my $insitutions = $c->objects->search( Koha::SIP2::Accounts->new );
return $c->render( status => 200, openapi => $insitutions );
}
catch {
} catch {
$c->unhandled_exception($_);
};
@ -64,8 +63,7 @@ sub get {
status => 200,
openapi => $account
);
}
catch {
} catch {
$c->unhandled_exception($_);
};
}
@ -85,17 +83,19 @@ sub add {
my $body = $c->req->json;
my $account = Koha::SIP2::Account->new_from_api($body)->store;
my $patron_attributes = delete $body->{patron_attributes} // [];
$c->res->headers->location($c->req->url->to_string . '/' . $account->sip_account_id);
my $account = Koha::SIP2::Account->new_from_api($body)->store;
$account->patron_attributes($patron_attributes);
$c->res->headers->location( $c->req->url->to_string . '/' . $account->sip_account_id );
return $c->render(
status => 201,
openapi => $c->objects->to_api($account),
);
}
);
}
catch {
} catch {
my $to_api_mapping = Koha::SIP2::Account->new->to_api_mapping;
@ -105,15 +105,10 @@ sub add {
status => 409,
openapi => { error => $_->error, conflict => $_->duplicate_id }
);
}
elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
} elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
return $c->render(
status => 400,
openapi => {
error => "Given "
. $to_api_mapping->{ $_->parameter }
. " does not exist"
}
openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
);
}
}
@ -142,35 +137,33 @@ sub update {
my $body = $c->req->json;
$account->set_from_api($body)->store;
my $patron_attributes = delete $body->{patron_attributes} // [];
$c->res->headers->location($c->req->url->to_string . '/' . $account->sip_account_id);
$account->set_from_api($body)->store;
$account->patron_attributes($patron_attributes);
$c->res->headers->location( $c->req->url->to_string . '/' . $account->sip_account_id );
return $c->render(
status => 200,
openapi => $c->objects->to_api($account),
);
}
);
}
catch {
} catch {
my $to_api_mapping = Koha::SIP2::Account->new->to_api_mapping;
if ( blessed $_ ) {
if ( $_->isa('Koha::Exceptions::BadParameter') ) {
return $c->render(
status => 400,
openapi => {
error => "Given "
. $to_api_mapping->{ $_->parameter }
. " does not exist"
}
openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
);
}
}
$c->unhandled_exception($_);
};
};
}
=head3 delete

View File

@ -19,6 +19,7 @@ use Modern::Perl;
use base qw(Koha::SIP2::Object Koha::Object);
use Koha::SIP2::Account::PatronAttributes;
use Koha::SIP2::Institution;
=head1 NAME
@ -68,6 +69,14 @@ sub get_for_config {
delete $unblessed->{error_detect};
}
# Add relations
# If a single element, return that, otherwise return a list
my @patron_attributes = map { $_->get_for_config } @{ $self->patron_attributes->as_list };
if (@patron_attributes) {
$unblessed->{patron_attribute} = scalar(@patron_attributes) == 1 ? $patron_attributes[0] : \@patron_attributes;
}
return $unblessed;
}
@ -84,6 +93,32 @@ sub institution {
return Koha::SIP2::Institution->_new_from_dbic($institution_rs);
}
=head3 patron_attributes
Returns the patron attributes for this SIP account
=cut
sub patron_attributes {
my ( $self, $patron_attributes ) = @_;
if ($patron_attributes) {
my $schema = $self->_result->result_source->schema;
$schema->txn_do(
sub {
$self->patron_attributes->delete;
for my $patron_attribute (@$patron_attributes) {
$self->_result->add_to_sip_account_patron_attributes($patron_attribute);
}
}
);
}
my $patron_attributes_rs = $self->_result->sip_account_patron_attributes;
return Koha::SIP2::Account::PatronAttributes->_new_from_dbic($patron_attributes_rs);
}
=head3 type
=cut

View File

@ -0,0 +1,59 @@
package Koha::SIP2::Account::PatronAttribute;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Database;
use base qw(Koha::Object);
=head1 NAME
Koha::SIP2::Account::PatronAttribute - Koha SIP2 Account Patron Attribute Object class
=head1 API
=head2 Class Methods
=cut
=head3 get_for_config
Returns the patron attribute hashref as expected by C4/SIP/Sip/Configuration->new;
=cut
sub get_for_config {
my ($self) = @_;
return {
'field' => $self->field,
'code' => $self->code,
};
}
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'SipAccountPatronAttribute';
}
1;

View File

@ -0,0 +1,52 @@
package Koha::SIP2::Account::PatronAttributes;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Database;
use Koha::SIP2::Account::PatronAttribute;
use base qw(Koha::Objects);
=head1 NAME
Koha::SIP2::Account::PatronAttributes - Koha SIP2 Account Patron Attributes Object set class
=head1 API
=head2 Class Methods
=cut
=head3 type
=cut
sub _type {
return 'SipAccountPatronAttribute';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::SIP2::Account::PatronAttribute';
}
1;

View File

@ -21,7 +21,7 @@ use base qw(Koha::Object);
use DateTime;
use Koha::SIP2::ServerParams;
# use Koha::SIP2::ServerParams;
=head1 NAME
@ -57,13 +57,13 @@ sub delete {
return $self->SUPER::delete;
}
sub _update_config_timestamp {
my $timestamp = DateTime->now;
# sub _update_config_timestamp {
# my $timestamp = DateTime->now;
my $config_timestamp = Koha::SIP2::ServerParams->find( { key => 'config_timestamp' } )
|| Koha::SIP2::ServerParam->new( { key => 'config_timestamp' } );
$config_timestamp->value( DateTime->now->epoch )->store;
}
# my $config_timestamp = Koha::SIP2::ServerParams->find( { key => 'config_timestamp' } )
# || Koha::SIP2::ServerParam->new( { key => 'config_timestamp' } );
# $config_timestamp->value( DateTime->now->epoch )->store;
# }
1;

View File

@ -346,6 +346,21 @@ __PACKAGE__->add_unique_constraint("account_login_id", ["login_id"]);
=head1 RELATIONS
=head2 sip_account_patron_attributes
Type: has_many
Related object: L<Koha::Schema::Result::SipAccountPatronAttribute>
=cut
__PACKAGE__->has_many(
"sip_account_patron_attributes",
"Koha::Schema::Result::SipAccountPatronAttribute",
{ "foreign.sip_account_id" => "self.sip_account_id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 sip_institution
Type: belongs_to

View File

@ -0,0 +1,123 @@
use utf8;
package Koha::Schema::Result::SipAccountPatronAttribute;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Koha::Schema::Result::SipAccountPatronAttribute
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<sip_account_patron_attributes>
=cut
__PACKAGE__->table("sip_account_patron_attributes");
=head1 ACCESSORS
=head2 sip_account_patron_attribute_id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
=head2 sip_account_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
Foreign key to sip_accounts.sip_account_id
=head2 field
data_type: 'varchar'
is_nullable: 0
size: 80
SIP field name e.g. XY
=head2 code
data_type: 'varchar'
is_nullable: 0
size: 80
Patron attribute code as in borrower_attribute_types.code
=cut
__PACKAGE__->add_columns(
"sip_account_patron_attribute_id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"sip_account_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"field",
{ data_type => "varchar", is_nullable => 0, size => 80 },
"code",
{ data_type => "varchar", is_nullable => 0, size => 80 },
);
=head1 PRIMARY KEY
=over 4
=item * L</sip_account_patron_attribute_id>
=back
=cut
__PACKAGE__->set_primary_key("sip_account_patron_attribute_id");
=head1 UNIQUE CONSTRAINTS
=head2 C<sip_account>
=over 4
=item * L</sip_account_patron_attribute_id>
=item * L</sip_account_id>
=back
=cut
__PACKAGE__->add_unique_constraint(
"sip_account",
["sip_account_patron_attribute_id", "sip_account_id"],
);
=head1 RELATIONS
=head2 sip_account
Type: belongs_to
Related object: L<Koha::Schema::Result::SipAccount>
=cut
__PACKAGE__->belongs_to(
"sip_account",
"Koha::Schema::Result::SipAccount",
{ sip_account_id => "sip_account_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-01-30 12:33:46
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:aKRw1tnA6e32AxFjm4g7YQ
# You can replace this text with custom code or comments, and it will be preserved on regeneration
1;

View File

@ -189,6 +189,11 @@ properties:
type:
- object
- 'null'
patron_attributes:
description: Related patron_attributes embed
type:
- array
- 'null'
terminator:
description: terminator
type:

View File

@ -23,6 +23,7 @@
type: string
enum:
- institution
- patron_attributes
collectionFormat: csv
- $ref: "../swagger.yaml#/parameters/match"
- $ref: "../swagger.yaml#/parameters/order_by"
@ -132,6 +133,16 @@
- application/json
parameters:
- $ref: "../swagger.yaml#/parameters/sip_account_id_pp"
- name: x-koha-embed
in: header
required: false
description: Embed list sent as a request header
type: array
items:
type: string
enum:
- patron_attributes
collectionFormat: csv
responses:
200:
description: A SIP2 account

View File

@ -90,8 +90,8 @@ return {
my $encoding = $SIPconfig->{accounts}->{$account_key}->{encoding};
my $error_detect;
if ( defined $SIPconfig->{accounts}->{$account_key}->{'error-detect'} ){
$error_detect = $SIPconfig->{accounts}->{$account_key}->{'error-detect'} eq 'enabled' ? 1 : 0
if ( defined $SIPconfig->{accounts}->{$account_key}->{'error-detect'} ) {
$error_detect = $SIPconfig->{accounts}->{$account_key}->{'error-detect'} eq 'enabled' ? 1 : 0;
}
my $format_due_date = $SIPconfig->{accounts}->{$account_key}->{format_due_date};
@ -156,6 +156,25 @@ return {
$show_outstanding_amount,
$terminator
);
my $new_account_id = $dbh->last_insert_id( undef, undef, "sip_accounts", "sip_account_id" );
# Accounts patron attributes
my @patron_attributes =
ref $SIPconfig->{accounts}->{$account_key}->{patron_attribute} eq "ARRAY"
? @{ $SIPconfig->{accounts}->{$account_key}->{patron_attribute} }
: ( $SIPconfig->{accounts}->{$account_key}->{patron_attribute} );
my $insert_patron_attributes = $dbh->prepare(
q{INSERT IGNORE INTO sip_account_patron_attributes (sip_account_id, field, code) VALUES (?, ?, ?)});
foreach my $patron_attribute (@patron_attributes) {
$insert_patron_attributes->execute(
$new_account_id,
$patron_attribute->{field},
$patron_attribute->{code}
) if $patron_attribute;
}
}
},
};

View File

@ -6130,6 +6130,25 @@ CREATE TABLE `sip_accounts` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sip_account_patron_attributes`
--
DROP TABLE IF EXISTS `sip_account_patron_attributes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `sip_account_patron_attributes` (
`sip_account_patron_attribute_id` int(11) NOT NULL AUTO_INCREMENT,
`sip_account_id` int(11) NOT NULL COMMENT 'Foreign key to sip_accounts.sip_account_id',
`field` varchar(80) NOT NULL COMMENT 'SIP field name e.g. XY',
`code` varchar(80) NOT NULL COMMENT 'Patron attribute code as in borrower_attribute_types.code',
PRIMARY KEY (`sip_account_patron_attribute_id`),
UNIQUE KEY `sip_account` (`sip_account_patron_attribute_id`,`sip_account_id`),
KEY `sip_account_patron_attributes_ibfk_1` (`sip_account_id`),
CONSTRAINT `sip_account_patron_attributes_ibfk_1` FOREIGN KEY (`sip_account_id`) REFERENCES `sip_accounts` (`sip_account_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sip_institutions`
--

View File

@ -47,6 +47,9 @@ export class SIP2APIClient {
get: id =>
this.get({
endpoint: "accounts/" + id,
headers: {
"x-koha-embed": "patron_attributes",
},
}),
getAll: params =>
this.getAll({

View File

@ -392,6 +392,59 @@ export default {
placeholder: "CRLF",
group: "Details",
},
{
name: "patron_attributes",
type: "relationshipWidget",
showElement: {
type: "table",
columnData: "patron_attributes",
hidden: account => !!account.patron_attributes?.length,
columns: [
{
name: __("Field"),
value: "field",
},
{
name: __("Code"),
value: "code",
},
],
},
group: __("SIP response mappings"),
componentProps: {
resourceRelationships: {
resourceProperty: "patron_attributes",
},
relationshipStrings: {
nameLowerCase: __("patron attribute"),
nameUpperCase: __("Patron attribute"),
namePlural: __("patron attributes"),
},
newRelationshipDefaultAttrs: {
type: "object",
value: {
field: null,
code: null,
},
},
},
relationshipFields: [
{
name: "field",
required: true,
type: "text",
placeholder: "XY",
label: __("Field"),
},
{
name: "code",
required: true,
type: "text",
placeholder: "CODE",
label: __("Code"),
},
],
},
{
name: "ae_field_template",
type: "textarea",
@ -607,6 +660,11 @@ export default {
account.terminator = null;
}
account.patron_attributes = account.patron_attributes.map(
({ account_id, account_patron_attribute_id, ...keepAttrs }) =>
keepAttrs
);
const client = APIClient.sip2;
if (sip_account_id) {
client.accounts.update(account, sip_account_id).then(