1 package Koha::Account::Line;
3 # This file is part of Koha.
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.
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.
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>.
20 use Data::Dumper qw( Dumper );
22 use C4::Log qw( logaction );
23 use C4::Overdues qw( UpdateFine );
25 use Koha::Account::CreditType;
26 use Koha::Account::DebitType;
27 use Koha::Account::Offsets;
29 use Koha::DateUtils qw( dt_from_string );
30 use Koha::Exceptions::Account;
33 use base qw(Koha::Object);
39 Koha::Account::Line - Koha accountline Object class
49 Return the patron linked to this account line
55 my $rs = $self->_result->borrowernumber;
57 return Koha::Patron->_new_from_dbic( $rs );
62 Return the manager linked to this account line
68 my $rs = $self->_result->manager;
70 return Koha::Patron->_new_from_dbic( $rs );
75 Return the item linked to this account line if exists
81 my $rs = $self->_result->itemnumber;
83 return Koha::Item->_new_from_dbic( $rs );
88 Return the checkout linked to this account line if exists
94 return unless $self->issue_id ;
96 $self->{_checkout} ||= Koha::Checkouts->find( $self->issue_id );
97 $self->{_checkout} ||= Koha::Old::Checkouts->find( $self->issue_id );
98 return $self->{_checkout};
103 Returns a Koha::Library object representing where the accountline was recorded
109 my $rs = $self->_result->library;
111 return Koha::Library->_new_from_dbic($rs);
116 Return the credit_type linked to this account line
122 my $rs = $self->_result->credit_type_code;
124 return Koha::Account::CreditType->_new_from_dbic( $rs );
129 Return the debit_type linked to this account line
135 my $rs = $self->_result->debit_type_code;
137 return Koha::Account::DebitType->_new_from_dbic( $rs );
140 =head3 credit_offsets
142 Return the credit_offsets linked to this account line if some exist
147 my ( $self, $cond, $attr ) = @_;
148 my $rs = $self->_result->search_related( 'account_offsets_credits', $cond, $attr);
150 return Koha::Account::Offsets->_new_from_dbic($rs);
155 Return the debit_offsets linked to this account line if some exist
160 my ( $self, $cond, $attr ) = @_;
161 my $rs = $self->_result->search_related( 'account_offsets_debits', $cond, $attr);
163 return Koha::Account::Offsets->_new_from_dbic($rs);
169 my $credits = $accountline->credits;
170 my $credits = $accountline->credits( $cond, $attr );
172 Return the credits linked to this account line if some exist.
173 Search conditions and attributes may be passed if you wish to filter
174 the resultant resultant resultset.
179 my ( $self, $cond, $attr ) = @_;
181 unless ( $self->is_debit ) {
182 Koha::Exceptions::Account::IsNotDebit->throw(
183 error => 'Account line ' . $self->id . ' is not a debit'
187 my $cond_m = { map { "credit.".$_ => $cond->{$_} } keys %{$cond}};
189 $self->_result->search_related('account_offsets_debits')
190 ->search_related( 'credit', $cond_m, $attr );
192 return Koha::Account::Lines->_new_from_dbic($rs);
197 my $debits = $accountline->debits;
198 my $debits = $accountline->debits( $cond, $attr );
200 Return the debits linked to this account line if some exist.
201 Search conditions and attributes may be passed if you wish to filter
202 the resultant resultant resultset.
207 my ( $self, $cond, $attr ) = @_;
209 unless ( $self->is_credit ) {
210 Koha::Exceptions::Account::IsNotCredit->throw(
211 error => 'Account line ' . $self->id . ' is not a credit'
215 my $cond_m = { map { "debit.".$_ => $cond->{$_} } keys %{$cond}};
217 $self->_result->search_related('account_offsets_credits')
218 ->search_related( 'debit', $cond_m, $attr );
220 return Koha::Account::Lines->_new_from_dbic($rs);
225 $payment_accountline->void({
226 interface => $interface,
227 [ staff_id => $staff_id, branch => $branchcode ]
230 Used to 'void' (or reverse) a payment/credit. It will roll back any offsets
231 created by the application of this credit upon any debits and mark the credit
232 as 'void' by updating it's status to "VOID".
237 my ($self, $params) = @_;
239 # Make sure it is a credit we are voiding
240 unless ( $self->is_credit ) {
241 Koha::Exceptions::Account::IsNotCredit->throw(
242 error => 'Account line ' . $self->id . 'is not a credit' );
245 # Make sure it is not already voided
246 if ( $self->status && $self->status eq 'VOID' ) {
247 Koha::Exceptions::Account->throw(
248 error => 'Account line ' . $self->id . 'is already void' );
251 # Check for mandatory parameters
252 my @mandatory = ( 'interface' );
253 for my $param (@mandatory) {
254 unless ( defined( $params->{$param} ) ) {
255 Koha::Exceptions::MissingParameter->throw(
256 error => "The $param parameter is mandatory" );
260 # More mandatory parameters
261 if ( $params->{interface} eq 'intranet' ) {
262 my @optional = ( 'staff_id', 'branch' );
263 for my $param (@optional) {
264 unless ( defined( $params->{$param} ) ) {
265 Koha::Exceptions::MissingParameter->throw( error =>
266 "The $param parameter is mandatory when interface is set to 'intranet'"
272 # Find any applied offsets for the credit so we may reverse them
273 my @account_offsets =
274 Koha::Account::Offsets->search(
275 { credit_id => $self->id, amount => { '<' => 0 } } );
278 $self->_result->result_source->schema->txn_do(
281 # A 'void' is a 'debit'
282 $void = Koha::Account::Line->new(
284 borrowernumber => $self->borrowernumber,
286 debit_type_code => 'VOID',
287 amount => $self->amount * -1,
288 amountoutstanding => $self->amount * -1,
289 manager_id => $params->{staff_id},
290 interface => $params->{interface},
291 branchcode => $params->{branch},
295 # Record the creation offset
296 Koha::Account::Offset->new(
298 debit_id => $void->id,
300 amount => $self->amount * -1
304 # Link void to payment
306 amountoutstanding => $self->amount,
309 $self->apply( { debits => [$void] } );
311 # Reverse any applied payments
312 foreach my $account_offset (@account_offsets) {
314 Koha::Account::Lines->find( $account_offset->debit_id );
316 next unless $fee_paid;
318 my $amount_paid = $account_offset->amount * -1; # amount paid is stored as a negative amount
319 my $new_amount = $fee_paid->amountoutstanding + $amount_paid;
320 $fee_paid->amountoutstanding($new_amount);
323 Koha::Account::Offset->new(
325 credit_id => $self->id,
326 debit_id => $fee_paid->id,
327 amount => $amount_paid,
333 if ( C4::Context->preference("FinesLog") ) {
336 $self->borrowernumber,
339 action => 'void_payment',
340 borrowernumber => $self->borrowernumber,
341 amount => $self->amount,
342 amountoutstanding => $self->amountoutstanding,
343 description => $self->description,
344 credit_type_code => $self->credit_type_code,
345 payment_type => $self->payment_type,
347 itemnumber => $self->itemnumber,
348 manager_id => $self->manager_id,
350 [ map { $_->unblessed } @account_offsets ],
358 $void->discard_changes;
364 $debit_accountline->cancel();
366 Cancel a charge. It will mark the debit as 'cancelled' by updating its
367 status to 'CANCELLED'.
369 Charges that have been fully or partially paid cannot be cancelled.
371 Returns the cancellation accountline.
376 my ( $self, $params ) = @_;
378 # Make sure it is a charge we are reducing
379 unless ( $self->is_debit ) {
380 Koha::Exceptions::Account::IsNotDebit->throw(
381 error => 'Account line ' . $self->id . 'is not a debit' );
383 if ( $self->debit_type_code eq 'PAYOUT' ) {
384 Koha::Exceptions::Account::IsNotDebit->throw(
385 error => 'Account line ' . $self->id . 'is a payout' );
388 # Make sure it is not already cancelled
389 if ( $self->status && $self->status eq 'CANCELLED' ) {
390 Koha::Exceptions::Account->throw(
391 error => 'Account line ' . $self->id . 'is already cancelled' );
394 # Make sure it has not be paid yet
395 if ( $self->amount != $self->amountoutstanding ) {
396 Koha::Exceptions::Account->throw(
397 error => 'Account line ' . $self->id . 'is already offset' );
400 # Check for mandatory parameters
401 my @mandatory = ( 'staff_id', 'branch' );
402 for my $param (@mandatory) {
403 unless ( defined( $params->{$param} ) ) {
404 Koha::Exceptions::MissingParameter->throw(
405 error => "The $param parameter is mandatory" );
410 $self->_result->result_source->schema->txn_do(
413 # A 'cancellation' is a 'credit'
414 $cancellation = Koha::Account::Line->new(
417 amount => 0 - $self->amount,
418 credit_type_code => 'CANCELLATION',
420 amountoutstanding => 0 - $self->amount,
421 manager_id => $params->{staff_id},
422 borrowernumber => $self->borrowernumber,
423 interface => 'intranet',
424 branchcode => $params->{branch},
428 my $cancellation_offset = Koha::Account::Offset->new(
430 credit_id => $cancellation->accountlines_id,
432 amount => 0 - $self->amount
436 # Link cancellation to charge
437 $cancellation->apply( { debits => [$self] } );
438 $cancellation->status('APPLIED')->store();
440 # Update status of original debit
441 $self->status('CANCELLED')->store;
445 $cancellation->discard_changes;
446 return $cancellation;
451 $charge_accountline->reduce({
452 reduction_type => $reduction_type
455 Used to 'reduce' a charge/debit by adding a credit to offset against the amount
458 May be used to apply a discount whilst retaining the original debit amounts or
459 to apply a full or partial refund for example when a lost item is found and
462 It will immediately be applied to the given debit unless the debit has already
463 been paid, in which case a 'zero' offset will be added to maintain a link to
464 the debit but the outstanding credit will be left so it may be applied to other
467 Reduction type may be one of:
472 Returns the reduction accountline (which will be a credit)
477 my ( $self, $params ) = @_;
479 # Make sure it is a charge we are reducing
480 unless ( $self->is_debit ) {
481 Koha::Exceptions::Account::IsNotDebit->throw(
482 error => 'Account line ' . $self->id . 'is not a debit' );
484 if ( $self->debit_type_code eq 'PAYOUT' ) {
485 Koha::Exceptions::Account::IsNotDebit->throw(
486 error => 'Account line ' . $self->id . 'is a payout' );
489 # Check for mandatory parameters
490 my @mandatory = ( 'interface', 'reduction_type', 'amount' );
491 for my $param (@mandatory) {
492 unless ( defined( $params->{$param} ) ) {
493 Koha::Exceptions::MissingParameter->throw(
494 error => "The $param parameter is mandatory" );
498 # More mandatory parameters
499 if ( $params->{interface} eq 'intranet' ) {
500 my @optional = ( 'staff_id', 'branch' );
501 for my $param (@optional) {
502 unless ( defined( $params->{$param} ) ) {
503 Koha::Exceptions::MissingParameter->throw( error =>
504 "The $param parameter is mandatory when interface is set to 'intranet'"
510 # Make sure the reduction isn't more than the original
511 my $original = $self->amount;
512 Koha::Exceptions::Account::AmountNotPositive->throw(
513 error => 'Reduce amount passed is not positive' )
514 unless ( $params->{amount} > 0 );
515 Koha::Exceptions::ParameterTooHigh->throw( error =>
516 "Amount to reduce ($params->{amount}) is higher than original amount ($original)"
517 ) unless ( $original >= $params->{amount} );
519 $self->credits( { credit_type_code => [ 'DISCOUNT', 'REFUND' ] } )->total;
520 Koha::Exceptions::ParameterTooHigh->throw( error =>
521 "Combined reduction ($params->{amount} + $reduced) is higher than original amount ("
524 unless ( $original >= ( $params->{amount} + abs($reduced) ) );
526 my $status = { 'REFUND' => 'REFUNDED', 'DISCOUNT' => 'DISCOUNTED' };
529 $self->_result->result_source->schema->txn_do(
532 # A 'reduction' is a 'credit'
533 $reduction = Koha::Account::Line->new(
536 amount => 0 - $params->{amount},
537 credit_type_code => $params->{reduction_type},
539 amountoutstanding => 0 - $params->{amount},
540 manager_id => $params->{staff_id},
541 borrowernumber => $self->borrowernumber,
542 interface => $params->{interface},
543 branchcode => $params->{branch},
547 my $reduction_offset = Koha::Account::Offset->new(
549 credit_id => $reduction->accountlines_id,
551 amount => 0 - $params->{amount}
555 # Link reduction to charge (and apply as required)
556 my $debit_outstanding = $self->amountoutstanding;
557 if ( $debit_outstanding >= $params->{amount} ) {
559 $reduction->apply( { debits => [$self] } );
560 $reduction->status('APPLIED')->store();
564 # Zero amount offset used to link original 'debit' to
566 my $link_reduction_offset = Koha::Account::Offset->new(
568 credit_id => $reduction->accountlines_id,
569 debit_id => $self->accountlines_id,
576 # Update status of original debit
577 $self->status( $status->{ $params->{reduction_type} } )->store;
581 $reduction->discard_changes;
587 my $debits = $account->outstanding_debits;
588 my $credit = $credit->apply( { debits => $debits } );
590 Applies the credit to a given debits array reference.
592 =head4 arguments hashref
596 =item debits - Koha::Account::Lines object set of debits
603 my ( $self, $params ) = @_;
605 my $debits = $params->{debits};
607 unless ( $self->is_credit ) {
608 Koha::Exceptions::Account::IsNotCredit->throw(
609 error => 'Account line ' . $self->id . ' is not a credit'
613 my $available_credit = $self->amountoutstanding * -1;
615 unless ( $available_credit > 0 ) {
616 Koha::Exceptions::Account::NoAvailableCredit->throw(
617 error => 'Outstanding credit is ' . $available_credit . ' and cannot be applied'
621 my $schema = Koha::Database->new->schema;
623 $schema->txn_do( sub {
624 for my $debit ( @{$debits} ) {
626 unless ( $debit->is_debit ) {
627 Koha::Exceptions::Account::IsNotDebit->throw(
628 error => 'Account line ' . $debit->id . 'is not a debit'
631 my $amount_to_cancel;
632 my $owed = $debit->amountoutstanding;
634 if ( $available_credit >= $owed ) {
635 $amount_to_cancel = $owed;
637 else { # $available_credit < $debit->amountoutstanding
638 $amount_to_cancel = $available_credit;
641 # record the account offset
642 Koha::Account::Offset->new(
643 { credit_id => $self->id,
644 debit_id => $debit->id,
645 amount => $amount_to_cancel * -1,
650 $available_credit -= $amount_to_cancel;
652 $self->amountoutstanding( $available_credit * -1 )->store;
653 $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
655 # Attempt to renew the item associated with this debit if
657 if ( $self->credit_type_code ne 'FORGIVEN' && $debit->is_renewable ) {
658 my $outcome = $debit->renew_item( { interface => $params->{interface} } );
662 message => 'renewal',
667 $debit->discard_changes; # Refresh values from DB to clear floating point remainders
669 # Same logic exists in Koha::Account::pay
671 C4::Context->preference('MarkLostItemsAsReturned') =~
673 && $debit->debit_type_code
674 && $debit->debit_type_code eq 'LOST'
675 && $debit->amountoutstanding == 0
676 && $debit->itemnumber
678 $self->credit_type_code eq 'LOST_FOUND'
679 && $self->itemnumber == $debit->itemnumber
683 C4::Circulation::ReturnLostItem( $self->borrowernumber,
684 $debit->itemnumber );
687 last if $available_credit == 0;
696 $credit_accountline->payout(
698 payout_type => $payout_type,
699 register_id => $register_id,
700 staff_id => $staff_id,
701 interface => 'intranet',
706 Used to 'pay out' a credit to a user.
708 Payout type may be one of any existing payment types
710 Returns the payout debit line that is created via this transaction.
715 my ( $self, $params ) = @_;
717 # Make sure it is a credit we are paying out
718 unless ( $self->is_credit ) {
719 Koha::Exceptions::Account::IsNotCredit->throw(
720 error => 'Account line ' . $self->id . ' is not a credit' );
723 # Check for mandatory parameters
725 ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
726 for my $param (@mandatory) {
727 unless ( defined( $params->{$param} ) ) {
728 Koha::Exceptions::MissingParameter->throw(
729 error => "The $param parameter is mandatory" );
733 # Make sure there is outstanding credit to pay out
734 my $outstanding = -1 * $self->amountoutstanding;
736 $params->{amount} ? $params->{amount} : $outstanding;
737 Koha::Exceptions::Account::AmountNotPositive->throw(
738 error => 'Payout amount passed is not positive' )
739 unless ( $amount > 0 );
740 Koha::Exceptions::ParameterTooHigh->throw(
741 error => "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)" )
742 unless ($outstanding >= $amount );
744 # Make sure we record the cash register for cash transactions
745 Koha::Exceptions::Account::RegisterRequired->throw()
746 if ( C4::Context->preference("UseCashRegisters")
747 && defined( $params->{payout_type} )
748 && ( $params->{payout_type} eq 'CASH' )
749 && !defined( $params->{cash_register} ) );
752 $self->_result->result_source->schema->txn_do(
755 # A 'payout' is a 'debit'
756 $payout = Koha::Account::Line->new(
760 debit_type_code => 'PAYOUT',
761 payment_type => $params->{payout_type},
762 amountoutstanding => $amount,
763 manager_id => $params->{staff_id},
764 borrowernumber => $self->borrowernumber,
765 interface => $params->{interface},
766 branchcode => $params->{branch},
767 register_id => $params->{cash_register}
771 my $payout_offset = Koha::Account::Offset->new(
773 debit_id => $payout->accountlines_id,
779 $self->apply( { debits => [$payout] } );
780 $self->status('PAID')->store;
784 $payout->discard_changes;
790 This method allows updating a debit or credit on a patron's account
792 $account_line->adjust(
795 type => $update_type,
796 interface => $interface
800 $update_type can be any of:
803 Authors Note: The intention here is that this method is only used
804 to adjust accountlines where the final amount is not yet known/fixed.
805 Incrementing fines are the only existing case at the time of writing,
806 all other forms of 'adjustment' should be recorded as distinct credits
807 or debits and applied, via an offset, to the corresponding debit or credit.
812 my ( $self, $params ) = @_;
814 my $amount = $params->{amount};
815 my $update_type = $params->{type};
816 my $interface = $params->{interface};
818 unless ( exists($Koha::Account::Line::allowed_update->{$update_type}) ) {
819 Koha::Exceptions::Account::UnrecognisedType->throw(
820 error => 'Update type not recognised'
824 my $debit_type_code = $self->debit_type_code;
825 my $account_status = $self->status;
829 $Koha::Account::Line::allowed_update->{$update_type}
832 && ( $Koha::Account::Line::allowed_update->{$update_type}
833 ->{$debit_type_code} eq $account_status )
837 Koha::Exceptions::Account::UnrecognisedType->throw(
838 error => 'Update type not allowed on this debit_type' );
841 my $schema = Koha::Database->new->schema;
846 my $amount_before = $self->amount;
847 my $amount_outstanding_before = $self->amountoutstanding;
848 my $difference = $amount - $amount_before;
849 my $new_outstanding = $amount_outstanding_before + $difference;
851 my $offset_type = $debit_type_code;
852 $offset_type .= ( $difference > 0 ) ? "_INCREASE" : "_DECREASE";
854 # Catch cases that require patron refunds
855 if ( $new_outstanding < 0 ) {
857 Koha::Patrons->find( $self->borrowernumber )->account;
858 my $credit = $account->add_credit(
860 amount => $new_outstanding * -1,
861 type => 'OVERPAYMENT',
862 interface => $interface,
863 ( $update_type eq 'overdue_update' ? ( item_id => $self->itemnumber ) : ()),
866 $new_outstanding = 0;
869 # Update the account line
874 amountoutstanding => $new_outstanding,
878 # Record the account offset
879 my $account_offset = Koha::Account::Offset->new(
881 debit_id => $self->id,
882 type => $offset_type,
883 amount => $difference
887 if ( C4::Context->preference("FinesLog") ) {
889 "FINES", 'UPDATE', #undef becomes UPDATE in UpdateFine
890 $self->borrowernumber,
892 { action => $update_type,
893 borrowernumber => $self->borrowernumber,
895 description => undef,
896 amountoutstanding => $new_outstanding,
897 debit_type_code => $self->debit_type_code,
899 itemnumber => $self->itemnumber,
903 ) if ( $update_type eq 'overdue_update' );
913 my $bool = $line->is_credit;
920 return defined $self->credit_type_code;
925 my $bool = $line->is_debit;
932 return !$self->is_credit;
935 =head3 to_api_mapping
937 This method returns the mapping for representing a Koha::Account::Line object
944 accountlines_id => 'account_line_id',
945 credit_number => undef,
946 credit_type_code => 'credit_type',
947 debit_type_code => 'debit_type',
948 amountoutstanding => 'amount_outstanding',
949 borrowernumber => 'patron_id',
950 branchcode => 'library_id',
951 issue_id => 'checkout_id',
952 itemnumber => 'item_id',
953 manager_id => 'user_id',
954 note => 'internal_note',
955 register_id => 'cash_register_id',
962 my $bool = $line->is_renewable;
970 $self->amountoutstanding == 0 &&
971 $self->debit_type_code &&
972 $self->debit_type_code eq 'OVERDUE' &&
974 $self->status eq 'UNRETURNED' &&
982 my $renew_result = $line->renew_item;
984 Conditionally attempt to renew an item and return the outcome. This is
985 as a consequence of the fine on an item being fully paid off.
986 Caller must call is_renewable before.
991 my ($self, $params) = @_;
995 # We want to reject the call to renew if:
996 # - The RenewAccruingItemWhenPaid syspref is off
998 # - The RenewAccruingItemInOpac syspref is off
999 # - There is an interface param passed and it's value is 'opac'
1002 !C4::Context->preference('RenewAccruingItemWhenPaid') ||
1004 !C4::Context->preference('RenewAccruingItemInOpac') &&
1005 $params->{interface} &&
1006 $params->{interface} eq 'opac'
1012 my $itemnumber = $self->item->itemnumber;
1013 my $borrowernumber = $self->patron->borrowernumber;
1014 my ( $can_renew, $error ) = C4::Circulation::CanBookBeRenewed(
1019 my $due_date = C4::Circulation::AddRenewal(
1022 $self->{branchcode},
1028 itemnumber => $itemnumber,
1029 due_date => $due_date,
1034 itemnumber => $itemnumber,
1044 Specific store method to generate credit number before saving
1051 my $AutoCreditNumber = C4::Context->preference('AutoCreditNumber');
1052 my $credit_number_enabled = $self->is_credit && $self->credit_type->credit_number_enabled;
1054 if ($AutoCreditNumber && $credit_number_enabled && !$self->in_storage) {
1055 if (defined $self->credit_number) {
1056 Koha::Exceptions::Account->throw('AutoCreditNumber is enabled but credit_number is already defined');
1059 my $rs = Koha::Database->new->schema->resultset($self->_type);
1061 if ($AutoCreditNumber eq 'incremental') {
1062 my $max = $rs->search({
1063 credit_number => { -regexp => '^[0-9]+$' }
1065 select => \'CAST(credit_number AS UNSIGNED)',
1066 as => ['credit_number'],
1067 })->get_column('credit_number')->max;
1069 $self->credit_number($max + 1);
1070 } elsif ($AutoCreditNumber eq 'annual') {
1071 my $now = dt_from_string;
1072 my $prefix = sprintf('%d-', $now->year);
1073 my $max = $rs->search({
1075 credit_number => { -regexp => '[0-9]{4}$' },
1076 credit_number => { -like => "$prefix%" },
1078 })->get_column('credit_number')->max;
1079 $max //= $prefix . '0000';
1080 my $incr = substr($max, length $prefix);
1081 $self->credit_number(sprintf('%s%04d', $prefix, $incr + 1));
1082 } elsif ($AutoCreditNumber eq 'branchyyyymmincr') {
1083 my $userenv = C4::Context->userenv;
1085 my $branch = $userenv->{branch};
1086 my $now = dt_from_string;
1087 my $prefix = sprintf('%s%d%02d', $branch, $now->year, $now->month);
1088 my $pattern = $prefix;
1089 $pattern =~ s/([\?%_])/\\$1/g;
1090 my $max = $rs->search({
1092 credit_number => { -regexp => '[0-9]{4}$' },
1093 credit_number => { -like => "$pattern%" },
1095 })->get_column('credit_number')->max;
1096 $max //= $prefix . '0000';
1097 my $incr = substr($max, length $prefix);
1098 $self->credit_number(sprintf('%s%04d', $prefix, $incr + 1));
1103 return $self->SUPER::store();
1106 =head2 Internal methods
1115 return 'Accountline';
1120 =head2 Name mappings
1122 =head3 $allowed_update
1126 our $allowed_update = { 'overdue_update' => { 'OVERDUE' => 'UNRETURNED' } };
1130 Kyle M Hall <kyle@bywatersolutions.com >
1131 Tomás Cohen Arazi <tomascohen@theke.io>
1132 Martin Renvoize <martin.renvoize@ptfs-europe.com>