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>.
23 use C4::Log qw(logaction);
24 use C4::Overdues qw(GetFine);
26 use Koha::Account::CreditType;
27 use Koha::Account::DebitType;
28 use Koha::Account::Offsets;
31 use Koha::Exceptions::Account;
34 use base qw(Koha::Object);
40 Koha::Account::Line - Koha accountline Object class
50 Return the patron linked to this account line
56 my $rs = $self->_result->borrowernumber;
58 return Koha::Patron->_new_from_dbic( $rs );
63 Return the item linked to this account line if exists
69 my $rs = $self->_result->itemnumber;
71 return Koha::Item->_new_from_dbic( $rs );
76 Return the checkout linked to this account line if exists
82 return unless $self->issue_id ;
84 $self->{_checkout} ||= Koha::Checkouts->find( $self->issue_id );
85 $self->{_checkout} ||= Koha::Old::Checkouts->find( $self->issue_id );
86 return $self->{_checkout};
91 Returns a Koha::Library object representing where the accountline was recorded
97 my $rs = $self->_result->library;
99 return Koha::Library->_new_from_dbic($rs);
104 Return the credit_type linked to this account line
110 my $rs = $self->_result->credit_type_code;
112 return Koha::Account::CreditType->_new_from_dbic( $rs );
117 Return the debit_type linked to this account line
123 my $rs = $self->_result->debit_type_code;
125 return Koha::Account::DebitType->_new_from_dbic( $rs );
128 =head3 credit_offsets
130 Return the credit_offsets linked to this account line if some exist
136 my $rs = $self->_result->account_offsets_credits;
138 return Koha::Account::Offsets->_new_from_dbic($rs);
143 Return the debit_offsets linked to this account line if some exist
149 my $rs = $self->_result->account_offsets_debits;
151 return Koha::Account::Offsets->_new_from_dbic($rs);
157 my $credits = $accountline->credits;
158 my $credits = $accountline->credits( $cond, $attr );
160 Return the credits linked to this account line if some exist.
161 Search conditions and attributes may be passed if you wish to filter
162 the resultant resultant resultset.
167 my ( $self, $cond, $attr ) = @_;
169 unless ( $self->is_debit ) {
170 Koha::Exceptions::Account::IsNotDebit->throw(
171 error => 'Account line ' . $self->id . ' is not a debit'
175 my $cond_m = { map { "credit.".$_ => $cond->{$_} } keys %{$cond}};
177 $self->_result->search_related('account_offsets_debits')
178 ->search_related( 'credit', $cond_m, $attr );
180 return Koha::Account::Lines->_new_from_dbic($rs);
185 my $debits = $accountline->debits;
186 my $debits = $accountline->debits( $cond, $attr );
188 Return the debits linked to this account line if some exist.
189 Search conditions and attributes may be passed if you wish to filter
190 the resultant resultant resultset.
195 my ( $self, $cond, $attr ) = @_;
197 unless ( $self->is_credit ) {
198 Koha::Exceptions::Account::IsNotCredit->throw(
199 error => 'Account line ' . $self->id . ' is not a credit'
203 my $cond_m = { map { "debit.".$_ => $cond->{$_} } keys %{$cond}};
205 $self->_result->search_related('account_offsets_credits')
206 ->search_related( 'debit', $cond_m, $attr );
208 return Koha::Account::Lines->_new_from_dbic($rs);
213 $payment_accountline->void({
214 interface => $interface,
215 [ staff_id => $staff_id, branch => $branchcode ]
218 Used to 'void' (or reverse) a payment/credit. It will roll back any offsets
219 created by the application of this credit upon any debits and mark the credit
220 as 'void' by updating it's status to "VOID".
225 my ($self, $params) = @_;
227 # Make sure it is a credit we are voiding
228 unless ( $self->is_credit ) {
229 Koha::Exceptions::Account::IsNotCredit->throw(
230 error => 'Account line ' . $self->id . 'is not a credit' );
233 # Make sure it is not already voided
234 if ( $self->status && $self->status eq 'VOID' ) {
235 Koha::Exceptions::Account->throw(
236 error => 'Account line ' . $self->id . 'is already void' );
239 # Check for mandatory parameters
240 my @mandatory = ( 'interface' );
241 for my $param (@mandatory) {
242 unless ( defined( $params->{$param} ) ) {
243 Koha::Exceptions::MissingParameter->throw(
244 error => "The $param parameter is mandatory" );
248 # More mandatory parameters
249 if ( $params->{interface} eq 'intranet' ) {
250 my @optional = ( 'staff_id', 'branch' );
251 for my $param (@optional) {
252 unless ( defined( $params->{$param} ) ) {
253 Koha::Exceptions::MissingParameter->throw( error =>
254 "The $param parameter is mandatory when interface is set to 'intranet'"
260 # Find any applied offsets for the credit so we may reverse them
261 my @account_offsets =
262 Koha::Account::Offsets->search(
263 { credit_id => $self->id, amount => { '<' => 0 } } );
266 $self->_result->result_source->schema->txn_do(
269 # A 'void' is a 'debit'
270 $void = Koha::Account::Line->new(
272 borrowernumber => $self->borrowernumber,
274 debit_type_code => 'VOID',
275 amount => $self->amount * -1,
276 amountoutstanding => $self->amount * -1,
277 manager_id => $params->{staff_id},
278 interface => $params->{interface},
279 branchcode => $params->{branch},
283 # Record the creation offset
284 Koha::Account::Offset->new(
286 debit_id => $void->id,
288 amount => $self->amount * -1
292 # Reverse any applied payments
293 foreach my $account_offset (@account_offsets) {
295 Koha::Account::Lines->find( $account_offset->debit_id );
297 next unless $fee_paid;
299 my $amount_paid = $account_offset->amount * -1; # amount paid is stored as a negative amount
300 my $new_amount = $fee_paid->amountoutstanding + $amount_paid;
301 $fee_paid->amountoutstanding($new_amount);
304 Koha::Account::Offset->new(
306 credit_id => $self->id,
307 debit_id => $fee_paid->id,
308 amount => $amount_paid,
314 # Link void to payment
316 amountoutstanding => $self->amount,
319 $self->apply({ debits => [$void]});
321 if ( C4::Context->preference("FinesLog") ) {
324 $self->borrowernumber,
327 action => 'void_payment',
328 borrowernumber => $self->borrowernumber,
329 amount => $self->amount,
330 amountoutstanding => $self->amountoutstanding,
331 description => $self->description,
332 credit_type_code => $self->credit_type_code,
333 payment_type => $self->payment_type,
335 itemnumber => $self->itemnumber,
336 manager_id => $self->manager_id,
338 [ map { $_->unblessed } @account_offsets ],
346 $void->discard_changes;
352 $debit_accountline->cancel();
354 Cancel a charge. It will mark the debit as 'cancelled' by updating its
355 status to 'CANCELLED'.
357 Charges that have been fully or partially paid cannot be cancelled.
359 Returns the cancellation accountline.
364 my ( $self, $params ) = @_;
366 # Make sure it is a charge we are reducing
367 unless ( $self->is_debit ) {
368 Koha::Exceptions::Account::IsNotDebit->throw(
369 error => 'Account line ' . $self->id . 'is not a debit' );
371 if ( $self->debit_type_code eq 'PAYOUT' ) {
372 Koha::Exceptions::Account::IsNotDebit->throw(
373 error => 'Account line ' . $self->id . 'is a payout' );
376 # Make sure it is not already cancelled
377 if ( $self->status && $self->status eq 'CANCELLED' ) {
378 Koha::Exceptions::Account->throw(
379 error => 'Account line ' . $self->id . 'is already cancelled' );
382 # Make sure it has not be paid yet
383 if ( $self->amount != $self->amountoutstanding ) {
384 Koha::Exceptions::Account->throw(
385 error => 'Account line ' . $self->id . 'is already offset' );
388 # Check for mandatory parameters
389 my @mandatory = ( 'staff_id', 'branch' );
390 for my $param (@mandatory) {
391 unless ( defined( $params->{$param} ) ) {
392 Koha::Exceptions::MissingParameter->throw(
393 error => "The $param parameter is mandatory" );
398 $self->_result->result_source->schema->txn_do(
401 # A 'cancellation' is a 'credit'
402 $cancellation = Koha::Account::Line->new(
405 amount => 0 - $self->amount,
406 credit_type_code => 'CANCELLATION',
408 amountoutstanding => 0 - $self->amount,
409 manager_id => $params->{staff_id},
410 borrowernumber => $self->borrowernumber,
411 interface => 'intranet',
412 branchcode => $params->{branch},
416 my $cancellation_offset = Koha::Account::Offset->new(
418 credit_id => $cancellation->accountlines_id,
419 type => 'CANCELLATION',
420 amount => $self->amount
424 # Link cancellation to charge
425 $cancellation->apply(
428 offset_type => 'CANCELLATION'
431 $cancellation->status('APPLIED')->store();
433 # Update status of original debit
434 $self->status('CANCELLED')->store;
438 $cancellation->discard_changes;
439 return $cancellation;
444 $charge_accountline->reduce({
445 reduction_type => $reduction_type
448 Used to 'reduce' a charge/debit by adding a credit to offset against the amount
451 May be used to apply a discount whilst retaining the original debit amounts or
452 to apply a full or partial refund for example when a lost item is found and
455 It will immediately be applied to the given debit unless the debit has already
456 been paid, in which case a 'zero' offset will be added to maintain a link to
457 the debit but the outstanding credit will be left so it may be applied to other
460 Reduction type may be one of:
465 Returns the reduction accountline (which will be a credit)
470 my ( $self, $params ) = @_;
472 # Make sure it is a charge we are reducing
473 unless ( $self->is_debit ) {
474 Koha::Exceptions::Account::IsNotDebit->throw(
475 error => 'Account line ' . $self->id . 'is not a debit' );
477 if ( $self->debit_type_code eq 'PAYOUT' ) {
478 Koha::Exceptions::Account::IsNotDebit->throw(
479 error => 'Account line ' . $self->id . 'is a payout' );
482 # Check for mandatory parameters
483 my @mandatory = ( 'interface', 'reduction_type', 'amount' );
484 for my $param (@mandatory) {
485 unless ( defined( $params->{$param} ) ) {
486 Koha::Exceptions::MissingParameter->throw(
487 error => "The $param parameter is mandatory" );
491 # More mandatory parameters
492 if ( $params->{interface} eq 'intranet' ) {
493 my @optional = ( 'staff_id', 'branch' );
494 for my $param (@optional) {
495 unless ( defined( $params->{$param} ) ) {
496 Koha::Exceptions::MissingParameter->throw( error =>
497 "The $param parameter is mandatory when interface is set to 'intranet'"
503 # Make sure the reduction isn't more than the original
504 my $original = $self->amount;
505 Koha::Exceptions::Account::AmountNotPositive->throw(
506 error => 'Reduce amount passed is not positive' )
507 unless ( $params->{amount} > 0 );
508 Koha::Exceptions::ParameterTooHigh->throw( error =>
509 "Amount to reduce ($params->{amount}) is higher than original amount ($original)"
510 ) unless ( $original >= $params->{amount} );
512 $self->credits( { credit_type_code => [ 'DISCOUNT', 'REFUND' ] } )->total;
513 Koha::Exceptions::ParameterTooHigh->throw( error =>
514 "Combined reduction ($params->{amount} + $reduced) is higher than original amount ("
517 unless ( $original >= ( $params->{amount} + abs($reduced) ) );
519 my $status = { 'REFUND' => 'REFUNDED', 'DISCOUNT' => 'DISCOUNTED' };
522 $self->_result->result_source->schema->txn_do(
525 # A 'reduction' is a 'credit'
526 $reduction = Koha::Account::Line->new(
529 amount => 0 - $params->{amount},
530 credit_type_code => $params->{reduction_type},
532 amountoutstanding => 0 - $params->{amount},
533 manager_id => $params->{staff_id},
534 borrowernumber => $self->borrowernumber,
535 interface => $params->{interface},
536 branchcode => $params->{branch},
540 my $reduction_offset = Koha::Account::Offset->new(
542 credit_id => $reduction->accountlines_id,
543 type => uc( $params->{reduction_type} ),
544 amount => $params->{amount}
548 # Link reduction to charge (and apply as required)
549 my $debit_outstanding = $self->amountoutstanding;
550 if ( $debit_outstanding >= $params->{amount} ) {
555 offset_type => uc( $params->{reduction_type} )
558 $reduction->status('APPLIED')->store();
562 # Zero amount offset used to link original 'debit' to
564 my $link_reduction_offset = Koha::Account::Offset->new(
566 credit_id => $reduction->accountlines_id,
567 debit_id => $self->accountlines_id,
568 type => uc( $params->{reduction_type} ),
574 # Update status of original debit
575 $self->status( $status->{ $params->{reduction_type} } )->store;
579 $reduction->discard_changes;
585 my $debits = $account->outstanding_debits;
586 my $credit = $credit->apply( { debits => $debits, [ offset_type => $offset_type ] } );
588 Applies the credit to a given debits array reference.
590 =head4 arguments hashref
594 =item debits - Koha::Account::Lines object set of debits
596 =item offset_type (optional) - a string indicating the offset type (valid values are those from
597 the 'account_offset_types' table)
604 my ( $self, $params ) = @_;
606 my $debits = $params->{debits};
607 my $offset_type = $params->{offset_type} // 'Credit Applied';
609 unless ( $self->is_credit ) {
610 Koha::Exceptions::Account::IsNotCredit->throw(
611 error => 'Account line ' . $self->id . ' is not a credit'
615 my $available_credit = $self->amountoutstanding * -1;
617 unless ( $available_credit > 0 ) {
618 Koha::Exceptions::Account::NoAvailableCredit->throw(
619 error => 'Outstanding credit is ' . $available_credit . ' and cannot be applied'
623 my $schema = Koha::Database->new->schema;
625 $schema->txn_do( sub {
626 for my $debit ( @{$debits} ) {
628 unless ( $debit->is_debit ) {
629 Koha::Exceptions::Account::IsNotDebit->throw(
630 error => 'Account line ' . $debit->id . 'is not a debit'
633 my $amount_to_cancel;
634 my $owed = $debit->amountoutstanding;
636 if ( $available_credit >= $owed ) {
637 $amount_to_cancel = $owed;
639 else { # $available_credit < $debit->amountoutstanding
640 $amount_to_cancel = $available_credit;
643 # record the account offset
644 Koha::Account::Offset->new(
645 { credit_id => $self->id,
646 debit_id => $debit->id,
647 amount => $amount_to_cancel * -1,
648 type => $offset_type,
652 $available_credit -= $amount_to_cancel;
654 $self->amountoutstanding( $available_credit * -1 )->store;
655 $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
657 # Attempt to renew the item associated with this debit if
659 if ( $self->credit_type_code ne 'FORGIVEN' && $debit->is_renewable ) {
660 my $outcome = $debit->renew_item( { interface => $params->{interface} } );
664 message => 'renewal',
669 $debit->discard_changes; # Refresh values from DB to clear floating point remainders
671 # Same logic exists in Koha::Account::pay
673 C4::Context->preference('MarkLostItemsAsReturned') =~
675 && $debit->debit_type_code
676 && $debit->debit_type_code eq 'LOST'
677 && $debit->amountoutstanding == 0
678 && $debit->itemnumber
680 $self->credit_type_code eq 'LOST_FOUND'
681 && $self->itemnumber == $debit->itemnumber
685 C4::Circulation::ReturnLostItem( $self->borrowernumber,
686 $debit->itemnumber );
689 last if $available_credit == 0;
698 $credit_accountline->payout(
700 payout_type => $payout_type,
701 register_id => $register_id,
702 staff_id => $staff_id,
703 interface => 'intranet',
708 Used to 'pay out' a credit to a user.
710 Payout type may be one of any existing payment types
712 Returns the payout debit line that is created via this transaction.
717 my ( $self, $params ) = @_;
719 # Make sure it is a credit we are paying out
720 unless ( $self->is_credit ) {
721 Koha::Exceptions::Account::IsNotCredit->throw(
722 error => 'Account line ' . $self->id . ' is not a credit' );
725 # Check for mandatory parameters
727 ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
728 for my $param (@mandatory) {
729 unless ( defined( $params->{$param} ) ) {
730 Koha::Exceptions::MissingParameter->throw(
731 error => "The $param parameter is mandatory" );
735 # Make sure there is outstanding credit to pay out
736 my $outstanding = -1 * $self->amountoutstanding;
738 $params->{amount} ? $params->{amount} : $outstanding;
739 Koha::Exceptions::Account::AmountNotPositive->throw(
740 error => 'Payout amount passed is not positive' )
741 unless ( $amount > 0 );
742 Koha::Exceptions::ParameterTooHigh->throw(
743 error => "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)" )
744 unless ($outstanding >= $amount );
746 # Make sure we record the cash register for cash transactions
747 Koha::Exceptions::Account::RegisterRequired->throw()
748 if ( C4::Context->preference("UseCashRegisters")
749 && defined( $params->{payout_type} )
750 && ( $params->{payout_type} eq 'CASH' )
751 && !defined( $params->{cash_register} ) );
754 $self->_result->result_source->schema->txn_do(
757 # A 'payout' is a 'debit'
758 $payout = Koha::Account::Line->new(
762 debit_type_code => 'PAYOUT',
763 payment_type => $params->{payout_type},
764 amountoutstanding => $amount,
765 manager_id => $params->{staff_id},
766 borrowernumber => $self->borrowernumber,
767 interface => $params->{interface},
768 branchcode => $params->{branch},
769 register_id => $params->{cash_register}
773 my $payout_offset = Koha::Account::Offset->new(
775 debit_id => $payout->accountlines_id,
781 $self->apply( { debits => [$payout], offset_type => 'PAYOUT' } );
782 $self->status('PAID')->store;
786 $payout->discard_changes;
792 This method allows updating a debit or credit on a patron's account
794 $account_line->adjust(
797 type => $update_type,
798 interface => $interface
802 $update_type can be any of:
805 Authors Note: The intention here is that this method is only used
806 to adjust accountlines where the final amount is not yet known/fixed.
807 Incrementing fines are the only existing case at the time of writing,
808 all other forms of 'adjustment' should be recorded as distinct credits
809 or debits and applied, via an offset, to the corresponding debit or credit.
814 my ( $self, $params ) = @_;
816 my $amount = $params->{amount};
817 my $update_type = $params->{type};
818 my $interface = $params->{interface};
820 unless ( exists($Koha::Account::Line::allowed_update->{$update_type}) ) {
821 Koha::Exceptions::Account::UnrecognisedType->throw(
822 error => 'Update type not recognised'
826 my $debit_type_code = $self->debit_type_code;
827 my $account_status = $self->status;
831 $Koha::Account::Line::allowed_update->{$update_type}
834 && ( $Koha::Account::Line::allowed_update->{$update_type}
835 ->{$debit_type_code} eq $account_status )
839 Koha::Exceptions::Account::UnrecognisedType->throw(
840 error => 'Update type not allowed on this debit_type' );
843 my $schema = Koha::Database->new->schema;
848 my $amount_before = $self->amount;
849 my $amount_outstanding_before = $self->amountoutstanding;
850 my $difference = $amount - $amount_before;
851 my $new_outstanding = $amount_outstanding_before + $difference;
853 my $offset_type = $debit_type_code;
854 $offset_type .= ( $difference > 0 ) ? "_INCREASE" : "_DECREASE";
856 # Catch cases that require patron refunds
857 if ( $new_outstanding < 0 ) {
859 Koha::Patrons->find( $self->borrowernumber )->account;
860 my $credit = $account->add_credit(
862 amount => $new_outstanding * -1,
863 type => 'OVERPAYMENT',
864 interface => $interface,
865 ( $update_type eq 'overdue_update' ? ( item_id => $self->itemnumber ) : ()),
868 $new_outstanding = 0;
871 # Update the account line
876 amountoutstanding => $new_outstanding,
880 # Record the account offset
881 my $account_offset = Koha::Account::Offset->new(
883 debit_id => $self->id,
884 type => $offset_type,
885 amount => $difference
889 if ( C4::Context->preference("FinesLog") ) {
891 "FINES", 'UPDATE', #undef becomes UPDATE in UpdateFine
892 $self->borrowernumber,
894 { action => $update_type,
895 borrowernumber => $self->borrowernumber,
897 description => undef,
898 amountoutstanding => $new_outstanding,
899 debit_type_code => $self->debit_type_code,
901 itemnumber => $self->itemnumber,
905 ) if ( $update_type eq 'overdue_update' );
915 my $bool = $line->is_credit;
922 return defined $self->credit_type_code;
927 my $bool = $line->is_debit;
934 return !$self->is_credit;
937 =head3 to_api_mapping
939 This method returns the mapping for representing a Koha::Account::Line object
946 accountlines_id => 'account_line_id',
947 credit_number => undef,
948 credit_type_code => 'credit_type',
949 debit_type_code => 'debit_type',
950 amountoutstanding => 'amount_outstanding',
951 borrowernumber => 'patron_id',
952 branchcode => 'library_id',
953 issue_id => 'checkout_id',
954 itemnumber => 'item_id',
955 manager_id => 'user_id',
956 note => 'internal_note',
957 register_id => 'cash_register_id',
964 my $bool = $line->is_renewable;
972 $self->amountoutstanding == 0 &&
973 $self->debit_type_code &&
974 $self->debit_type_code eq 'OVERDUE' &&
976 $self->status eq 'UNRETURNED' &&
984 my $renew_result = $line->renew_item;
986 Conditionally attempt to renew an item and return the outcome. This is
987 as a consequence of the fine on an item being fully paid off.
988 Caller must call is_renewable before.
993 my ($self, $params) = @_;
997 # We want to reject the call to renew if:
998 # - The RenewAccruingItemWhenPaid syspref is off
1000 # - The RenewAccruingItemInOpac syspref is off
1001 # - There is an interface param passed and it's value is 'opac'
1004 !C4::Context->preference('RenewAccruingItemWhenPaid') ||
1006 !C4::Context->preference('RenewAccruingItemInOpac') &&
1007 $params->{interface} &&
1008 $params->{interface} eq 'opac'
1014 my $itemnumber = $self->item->itemnumber;
1015 my $borrowernumber = $self->patron->borrowernumber;
1016 my ( $can_renew, $error ) = C4::Circulation::CanBookBeRenewed(
1021 my $due_date = C4::Circulation::AddRenewal(
1024 $self->{branchcode},
1030 itemnumber => $itemnumber,
1031 due_date => $due_date,
1036 itemnumber => $itemnumber,
1046 Specific store method to generate credit number before saving
1053 my $AutoCreditNumber = C4::Context->preference('AutoCreditNumber');
1054 my $credit_number_enabled = $self->is_credit && $self->credit_type->credit_number_enabled;
1056 if ($AutoCreditNumber && $credit_number_enabled && !$self->in_storage) {
1057 if (defined $self->credit_number) {
1058 Koha::Exceptions::Account->throw('AutoCreditNumber is enabled but credit_number is already defined');
1061 my $rs = Koha::Database->new->schema->resultset($self->_type);
1063 if ($AutoCreditNumber eq 'incremental') {
1064 my $max = $rs->search({
1065 credit_number => { -regexp => '^[0-9]+$' }
1067 select => \'CAST(credit_number AS UNSIGNED)',
1068 as => ['credit_number'],
1069 })->get_column('credit_number')->max;
1071 $self->credit_number($max + 1);
1072 } elsif ($AutoCreditNumber eq 'annual') {
1073 my $now = dt_from_string;
1074 my $prefix = sprintf('%d-', $now->year);
1075 my $max = $rs->search({
1077 credit_number => { -regexp => '[0-9]{4}$' },
1078 credit_number => { -like => "$prefix%" },
1080 })->get_column('credit_number')->max;
1081 $max //= $prefix . '0000';
1082 my $incr = substr($max, length $prefix);
1083 $self->credit_number(sprintf('%s%04d', $prefix, $incr + 1));
1084 } elsif ($AutoCreditNumber eq 'branchyyyymmincr') {
1085 my $userenv = C4::Context->userenv;
1087 my $branch = $userenv->{branch};
1088 my $now = dt_from_string;
1089 my $prefix = sprintf('%s%d%02d', $branch, $now->year, $now->month);
1090 my $pattern = $prefix;
1091 $pattern =~ s/([\?%_])/\\$1/g;
1092 my $max = $rs->search({
1094 credit_number => { -regexp => '[0-9]{4}$' },
1095 credit_number => { -like => "$pattern%" },
1097 })->get_column('credit_number')->max;
1098 $max //= $prefix . '0000';
1099 my $incr = substr($max, length $prefix);
1100 $self->credit_number(sprintf('%s%04d', $prefix, $incr + 1));
1105 return $self->SUPER::store();
1108 =head2 Internal methods
1117 return 'Accountline';
1122 =head2 Name mappings
1124 =head3 $allowed_update
1128 our $allowed_update = { 'overdue_update' => { 'OVERDUE' => 'UNRETURNED' } };
1132 Kyle M Hall <kyle@bywatersolutions.com >
1133 Tomás Cohen Arazi <tomascohen@theke.io>
1134 Martin Renvoize <martin.renvoize@ptfs-europe.com>