+ Lock and optionally expire a patron account.
+ Remove holds and article requests if remove flag set.
+ In order to distinguish from locking by entering a wrong password, let's
+ call this an administrative lockout.
+
+=cut
+
+sub lock {
+ my ( $self, $params ) = @_;
+ $self->login_attempts( ADMINISTRATIVE_LOCKOUT );
+ if( $params->{expire} ) {
+ $self->dateexpiry( dt_from_string->subtract(days => 1) );
+ }
+ $self->store;
+ if( $params->{remove} ) {
+ $self->holds->delete;
+ $self->article_requests->delete;
+ }
+ return $self;
+}
+
+=head3 anonymize
+
+ Koha::Patrons->find($id)->anonymize;
+
+ Anonymize or clear borrower fields. Fields in BorrowerMandatoryField
+ are randomized, other personal data is cleared too.
+ Patrons with issues are skipped.
+
+=cut
+
+sub anonymize {
+ my ( $self ) = @_;
+ if( $self->_result->issues->count ) {
+ warn "Exiting anonymize: patron ".$self->borrowernumber." still has issues";
+ return;
+ }
+ # Mandatory fields come from the corresponding pref, but email fields
+ # are removed since scrambled email addresses only generate errors
+ my $mandatory = { map { (lc $_, 1); } grep { !/email/ }
+ split /\s*\|\s*/, C4::Context->preference('BorrowerMandatoryField') };
+ $mandatory->{userid} = 1; # needed since sub store does not clear field
+ my @columns = $self->_result->result_source->columns;
+ @columns = grep { !/borrowernumber|branchcode|categorycode|^date|password|flags|updated_on|lastseen|lang|login_attempts|anonymized/ } @columns;
+ push @columns, 'dateofbirth'; # add this date back in
+ foreach my $col (@columns) {
+ $self->_anonymize_column($col, $mandatory->{lc $col} );
+ }
+ $self->anonymized(1)->store;
+}
+
+sub _anonymize_column {
+ my ( $self, $col, $mandatory ) = @_;
+ my $col_info = $self->_result->result_source->column_info($col);
+ my $type = $col_info->{data_type};
+ my $nullable = $col_info->{is_nullable};
+ my $val;
+ if( $type =~ /char|text/ ) {
+ $val = $mandatory
+ ? Koha::Token->new->generate({ pattern => '\w{10}' })
+ : $nullable
+ ? undef
+ : q{};
+ } elsif( $type =~ /integer|int$|float|dec|double/ ) {
+ $val = $nullable ? undef : 0;
+ } elsif( $type =~ /date|time/ ) {
+ $val = $nullable ? undef : dt_from_string;
+ }
+ $self->$col($val);
+}
+
+=head3 add_guarantor
+
+ my @relationships = $patron->add_guarantor(
+ {
+ borrowernumber => $borrowernumber,
+ relationships => $relationship,
+ }
+ );
+
+ Adds a new guarantor to a patron.
+
+=cut
+
+sub add_guarantor {
+ my ( $self, $params ) = @_;
+
+ my $guarantor_id = $params->{guarantor_id};
+ my $relationship = $params->{relationship};
+
+ return Koha::Patron::Relationship->new(
+ {
+ guarantee_id => $self->id,
+ guarantor_id => $guarantor_id,
+ relationship => $relationship
+ }
+ )->store();
+}
+
+=head3 to_api
+
+ my $json = $patron->to_api;
+
+Overloaded method that returns a JSON representation of the Koha::Patron object,
+suitable for API output.
+
+=cut
+
+sub to_api {
+ my ( $self, $params ) = @_;
+
+ my $json_patron = $self->SUPER::to_api( $params );
+
+ $json_patron->{restricted} = ( $self->is_debarred )
+ ? Mojo::JSON->true
+ : Mojo::JSON->false;
+
+ return $json_patron;
+}
+
+=head3 to_api_mapping
+
+This method returns the mapping for representing a Koha::Patron object
+on the API.
+
+=cut
+
+sub to_api_mapping {
+ return {
+ borrowernotes => 'staff_notes',
+ borrowernumber => 'patron_id',
+ branchcode => 'library_id',
+ categorycode => 'category_id',
+ checkprevcheckout => 'check_previous_checkout',
+ contactfirstname => undef, # Unused
+ contactname => undef, # Unused
+ contactnote => 'altaddress_notes',
+ contacttitle => undef, # Unused
+ dateenrolled => 'date_enrolled',
+ dateexpiry => 'expiry_date',
+ dateofbirth => 'date_of_birth',
+ debarred => undef, # replaced by 'restricted'
+ debarredcomment => undef, # calculated, API consumers will use /restrictions instead
+ emailpro => 'secondary_email',
+ flags => undef, # permissions manipulation handled in /permissions
+ gonenoaddress => 'incorrect_address',
+ guarantorid => 'guarantor_id',
+ lastseen => 'last_seen',
+ lost => 'patron_card_lost',
+ opacnote => 'opac_notes',
+ othernames => 'other_name',
+ password => undef, # password manipulation handled in /password
+ phonepro => 'secondary_phone',
+ relationship => 'relationship_type',
+ sex => 'gender',
+ smsalertnumber => 'sms_number',
+ sort1 => 'statistics_1',
+ sort2 => 'statistics_2',
+ streetnumber => 'street_number',
+ streettype => 'street_type',
+ zipcode => 'postal_code',
+ B_address => 'altaddress_address',
+ B_address2 => 'altaddress_address2',
+ B_city => 'altaddress_city',
+ B_country => 'altaddress_country',
+ B_email => 'altaddress_email',
+ B_phone => 'altaddress_phone',
+ B_state => 'altaddress_state',
+ B_streetnumber => 'altaddress_street_number',
+ B_streettype => 'altaddress_street_type',
+ B_zipcode => 'altaddress_postal_code',
+ altcontactaddress1 => 'altcontact_address',
+ altcontactaddress2 => 'altcontact_address2',
+ altcontactaddress3 => 'altcontact_city',
+ altcontactcountry => 'altcontact_country',
+ altcontactfirstname => 'altcontact_firstname',
+ altcontactphone => 'altcontact_phone',
+ altcontactsurname => 'altcontact_surname',
+ altcontactstate => 'altcontact_state',
+ altcontactzipcode => 'altcontact_postal_code'
+ };