View | Details | Raw Unified | Return to bug 30144
Collapse All | Expand All

(-)a/acqui/addorder.pl (-1 / +23 lines)
Lines 118-124 if it is an order from an existing suggestion : the id of this suggestion. Link Here
118
118
119
use Modern::Perl;
119
use Modern::Perl;
120
use CGI  qw ( -utf8 );
120
use CGI  qw ( -utf8 );
121
use JSON qw ( to_json encode_json );
121
use JSON qw ( to_json encode_json decode_json );
122
use Carp qw( carp );
122
123
123
use C4::Acquisition qw( FillWithDefaultValues ModOrderUsers );
124
use C4::Acquisition qw( FillWithDefaultValues ModOrderUsers );
124
use C4::Auth        qw( get_template_and_user );
125
use C4::Auth        qw( get_template_and_user );
Lines 266-271 if ( $op eq 'cud-order' ) { Link Here
266
        unitprice               => scalar $input->param('unitprice'),
267
        unitprice               => scalar $input->param('unitprice'),
267
        order_internalnote      => scalar $input->param('order_internalnote'),
268
        order_internalnote      => scalar $input->param('order_internalnote'),
268
        order_vendornote        => scalar $input->param('order_vendornote'),
269
        order_vendornote        => scalar $input->param('order_vendornote'),
270
        servicing_instruction   => build_servicing_instruction_json($input),
269
        sort1                   => scalar $input->param('sort1'),
271
        sort1                   => scalar $input->param('sort1'),
270
        sort2                   => scalar $input->param('sort2'),
272
        sort2                   => scalar $input->param('sort2'),
271
        subscriptionid          => scalar $input->param('subscriptionid'),
273
        subscriptionid          => scalar $input->param('subscriptionid'),
Lines 510-512 if ( $op eq 'cud-order' ) { Link Here
510
    print $input->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
512
    print $input->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
511
    exit;
513
    exit;
512
}
514
}
515
516
# Helper function to build servicing instruction JSON from form parameters
517
# Expects form parameters in the format:
518
#   servicing_instruction_groups_json (JSON string)
519
# Returns JSON string or undef
520
sub build_servicing_instruction_json {
521
    my ($input) = @_;
522
523
    my $json_string = $input->param('servicing_instruction_groups_json');
524
    return unless $json_string;
525
526
    # Validate JSON
527
    eval { decode_json($json_string); };
528
    if ($@) {
529
        carp "Invalid servicing instruction JSON: $@";
530
        return;
531
    }
532
533
    return $json_string;
534
}
(-)a/acqui/basket.pl (-5 / +64 lines)
Lines 24-29 use Modern::Perl; Link Here
24
use C4::Auth   qw( get_template_and_user haspermission );
24
use C4::Auth   qw( get_template_and_user haspermission );
25
use C4::Output qw( output_html_with_http_headers output_and_exit );
25
use C4::Output qw( output_html_with_http_headers output_and_exit );
26
use CGI        qw ( -utf8 );
26
use CGI        qw ( -utf8 );
27
use JSON       qw( decode_json );
27
use C4::Acquisition
28
use C4::Acquisition
28
    qw( GetBasket CanUserManageBasket GetBasketAsCSV NewBasket NewBasketgroup ModBasket ReopenBasket ModBasketUsers GetBasketgroup GetBasketgroups GetBasketUsers GetOrders GetOrder get_rounded_price );
29
    qw( GetBasket CanUserManageBasket GetBasketAsCSV NewBasket NewBasketgroup ModBasket ReopenBasket ModBasketUsers GetBasketgroup GetBasketgroups GetBasketUsers GetOrders GetOrder get_rounded_price );
29
use C4::Budgets     qw( GetBudgetHierarchy GetBudget CanUserUseBudget );
30
use C4::Budgets     qw( GetBudgetHierarchy GetBudget CanUserUseBudget );
Lines 33-38 use Koha::Biblios; Link Here
33
use Koha::Acquisition::Baskets;
34
use Koha::Acquisition::Baskets;
34
use Koha::Acquisition::Booksellers;
35
use Koha::Acquisition::Booksellers;
35
use Koha::Acquisition::Orders;
36
use Koha::Acquisition::Orders;
37
use Koha::AuthorisedValues;
36
use Koha::Libraries;
38
use Koha::Libraries;
37
use C4::Letters qw( SendAlerts );
39
use C4::Letters qw( SendAlerts );
38
use Date::Calc  qw( Add_Delta_Days );
40
use Date::Calc  qw( Add_Delta_Days );
Lines 473-478 if ( $op eq 'list' ) { Link Here
473
475
474
    my $active_currency = Koha::Acquisition::Currencies->get_active;
476
    my $active_currency = Koha::Acquisition::Currencies->get_active;
475
477
478
    # Fetch EDIFACT servicing instruction authorized values for display
479
    my @edifact_si_avs = Koha::AuthorisedValues->search(
480
        { category => 'EDIFACT_SI' },
481
        { order_by => 'lib' }
482
    )->as_list;
483
    my %edifact_si_map = map { $_->authorised_value => $_->lib } @edifact_si_avs;
484
476
    my @orders = GetOrders($basketno);
485
    my @orders = GetOrders($basketno);
477
    my @books_loop;
486
    my @books_loop;
478
487
Lines 483-489 if ( $op eq 'list' ) { Link Here
483
    my $total_tax_included = 0;
492
    my $total_tax_included = 0;
484
    my $total_tax_value    = 0;
493
    my $total_tax_value    = 0;
485
    for my $order (@orders) {
494
    for my $order (@orders) {
486
        my $line = get_order_infos( $order, $bookseller );
495
        my $line = get_order_infos( $order, $bookseller, \%edifact_si_map );
487
        if ( $line->{uncertainprice} ) {
496
        if ( $line->{uncertainprice} ) {
488
            $template->param( uncertainprices => 1 );
497
            $template->param( uncertainprices => 1 );
489
        }
498
        }
Lines 510-516 if ( $op eq 'list' ) { Link Here
510
    my @cancelledorders = GetOrders( $basketno, { cancelled => 1 } );
519
    my @cancelledorders = GetOrders( $basketno, { cancelled => 1 } );
511
    my @cancelledorders_loop;
520
    my @cancelledorders_loop;
512
    for my $order (@cancelledorders) {
521
    for my $order (@cancelledorders) {
513
        my $line = get_order_infos( $order, $bookseller );
522
        my $line = get_order_infos( $order, $bookseller, \%edifact_si_map );
514
        push @cancelledorders_loop, $line;
523
        push @cancelledorders_loop, $line;
515
    }
524
    }
516
525
Lines 587-595 $template->param( messages => \@messages ); Link Here
587
output_html_with_http_headers $query, $cookie, $template->output;
596
output_html_with_http_headers $query, $cookie, $template->output;
588
597
589
sub get_order_infos {
598
sub get_order_infos {
590
    my $order      = shift;
599
    my $order          = shift;
591
    my $bookseller = shift;
600
    my $bookseller     = shift;
592
    my $qty        = $order->{'quantity'} || 0;
601
    my $edifact_si_map = shift;
602
    my $qty            = $order->{'quantity'} || 0;
593
    if ( !defined $order->{quantityreceived} ) {
603
    if ( !defined $order->{quantityreceived} ) {
594
        $order->{quantityreceived} = 0;
604
        $order->{quantityreceived} = 0;
595
    }
605
    }
Lines 679-684 sub get_order_infos { Link Here
679
        }
689
        }
680
    }
690
    }
681
691
692
    # Format servicing instruction for display
693
    if ( $line{servicing_instruction} && $edifact_si_map ) {
694
        my $si_display = '';
695
        eval {
696
            my $groups = decode_json( $line{servicing_instruction} );
697
            if ( ref $groups eq 'ARRAY' && @$groups ) {
698
                my @display_parts;
699
                foreach my $group (@$groups) {
700
                    if ( ref $group eq 'ARRAY' && @$group && @$group <= 2 ) {
701
                        my $lvc;
702
                        my $lvt;
703
704
                        # Extract LVC and LVT from group
705
                        foreach my $instruction (@$group) {
706
                            if (   ref $instruction eq 'HASH'
707
                                && $instruction->{type}
708
                                && $instruction->{value} )
709
                            {
710
                                if ( $instruction->{type} eq 'LVC' ) {
711
                                    $lvc = $instruction->{value};
712
                                } elsif ( $instruction->{type} eq 'LVT' ) {
713
                                    $lvt = $instruction->{value};
714
                                }
715
                            }
716
                        }
717
718
                        # Format based on what's present
719
                        if ( $lvc && $lvt ) {
720
721
                            # Both: "LVC description: LVT text"
722
                            my $lvc_desc = $edifact_si_map->{$lvc} || $lvc;
723
                            push @display_parts, $lvc_desc . ': ' . $lvt;
724
                        } elsif ($lvc) {
725
726
                            # LVC only: show description
727
                            push @display_parts, $edifact_si_map->{$lvc} || $lvc;
728
                        } elsif ($lvt) {
729
730
                            # LVT only: show text
731
                            push @display_parts, $lvt;
732
                        }
733
                    }
734
                }
735
                $si_display = join( '<br>', @display_parts ) if @display_parts;
736
            }
737
        };
738
        $line{servicing_instruction_display} = $si_display;
739
    }
740
682
    return \%line;
741
    return \%line;
683
}
742
}
684
743
(-)a/acqui/neworderempty.pl (-47 / +74 lines)
Lines 64-70 the item's id in the breeding reservoir Link Here
64
=cut
64
=cut
65
65
66
use Modern::Perl;
66
use Modern::Perl;
67
use CGI qw ( -utf8 );
67
use CGI  qw ( -utf8 );
68
use JSON qw( decode_json encode_json );
69
use Carp qw( carp );
68
use C4::Context;
70
use C4::Context;
69
71
70
use C4::Auth    qw( get_template_and_user );
72
use C4::Auth    qw( get_template_and_user );
Lines 100-105 use Koha::RecordProcessor; Link Here
100
use Koha::Subscriptions;
102
use Koha::Subscriptions;
101
use Koha::UI::Form::Builder::Biblio;
103
use Koha::UI::Form::Builder::Biblio;
102
use Koha::AdditionalFields;
104
use Koha::AdditionalFields;
105
use Koha::AuthorisedValues;
103
106
104
our $input        = CGI->new;
107
our $input        = CGI->new;
105
our $biblionumber = $input->param('biblionumber');
108
our $biblionumber = $input->param('biblionumber');
Lines 381-397 if ( defined $from_subscriptionid ) { Link Here
381
        $budget_id              = $lastOrderReceived->{budgetid};
384
        $budget_id              = $lastOrderReceived->{budgetid};
382
        $data->{listprice}      = $lastOrderReceived->{listprice};
385
        $data->{listprice}      = $lastOrderReceived->{listprice};
383
        $data->{uncertainprice} = $lastOrderReceived->{uncertainprice};
386
        $data->{uncertainprice} = $lastOrderReceived->{uncertainprice};
384
        $data->{tax_rate}           = $lastOrderReceived->{tax_rate_on_ordering};
387
        $data->{tax_rate}              = $lastOrderReceived->{tax_rate_on_ordering};
385
        $data->{discount}           = $lastOrderReceived->{discount};
388
        $data->{discount}              = $lastOrderReceived->{discount};
386
        $data->{rrp}                = $lastOrderReceived->{rrp};
389
        $data->{rrp}                   = $lastOrderReceived->{rrp};
387
        $data->{replacementprice}   = $lastOrderReceived->{replacementprice};
390
        $data->{replacementprice}      = $lastOrderReceived->{replacementprice};
388
        $data->{ecost}              = $lastOrderReceived->{ecost};
391
        $data->{ecost}                 = $lastOrderReceived->{ecost};
389
        $data->{quantity}           = $lastOrderReceived->{quantity};
392
        $data->{quantity}              = $lastOrderReceived->{quantity};
390
        $data->{unitprice}          = $lastOrderReceived->{unitprice};
393
        $data->{unitprice}             = $lastOrderReceived->{unitprice};
391
        $data->{order_internalnote} = $lastOrderReceived->{order_internalnote};
394
        $data->{order_internalnote}    = $lastOrderReceived->{order_internalnote};
392
        $data->{order_vendornote}   = $lastOrderReceived->{order_vendornote};
395
        $data->{order_vendornote}      = $lastOrderReceived->{order_vendornote};
393
        $data->{sort1}              = $lastOrderReceived->{sort1};
396
        $data->{servicing_instruction} = $lastOrderReceived->{servicing_instruction};
394
        $data->{sort2}              = $lastOrderReceived->{sort2};
397
        $data->{sort1}                 = $lastOrderReceived->{sort1};
398
        $data->{sort2}                 = $lastOrderReceived->{sort2};
395
399
396
        $basket = GetBasket( $input->param('basketno') );
400
        $basket = GetBasket( $input->param('basketno') );
397
    }
401
    }
Lines 444-449 $template->param( Link Here
444
    items                   => $items,
448
    items                   => $items,
445
);
449
);
446
450
451
# Get servicing instruction authorized values for EDIFACT_SI category
452
my @servicing_instruction_authorised_values = Koha::AuthorisedValues->search(
453
    { category => 'EDIFACT_SI' },
454
    { order_by => 'lib' }
455
)->as_list;
456
457
# Parse servicing instruction JSON into array of groups
458
my $servicing_instruction_groups      = [];
459
my $servicing_instruction_groups_json = '[]';
460
if ( $data->{'servicing_instruction'} ) {
461
    eval { $servicing_instruction_groups = decode_json( $data->{'servicing_instruction'} ); };
462
    if ($@) {
463
        carp "Failed to parse servicing_instruction JSON: $@";
464
    } else {
465
466
        # Re-encode for JavaScript (already validated JSON)
467
        $servicing_instruction_groups_json = $data->{'servicing_instruction'};
468
    }
469
}
470
447
# fill template
471
# fill template
448
$template->param(
472
$template->param(
449
    existing => $biblionumber,
473
    existing => $biblionumber,
Lines 461-501 $template->param( Link Here
461
    closedate            => $basket->{'closedate'},
485
    closedate            => $basket->{'closedate'},
462
486
463
    # order details
487
    # order details
464
    suggestionid         => $suggestion->{suggestionid},
488
    suggestionid                            => $suggestion->{suggestionid},
465
    surnamesuggestedby   => $suggestion->{surnamesuggestedby},
489
    surnamesuggestedby                      => $suggestion->{surnamesuggestedby},
466
    firstnamesuggestedby => $suggestion->{firstnamesuggestedby},
490
    firstnamesuggestedby                    => $suggestion->{firstnamesuggestedby},
467
    biblionumber         => $biblionumber,
491
    biblionumber                            => $biblionumber,
468
    uncertainprice       => $data->{'uncertainprice'},
492
    uncertainprice                          => $data->{'uncertainprice'},
469
    discount_2dp         => sprintf( "%.2f", $bookseller->discount ),      # for display
493
    discount_2dp                            => sprintf( "%.2f", $bookseller->discount ),      # for display
470
    discount             => $bookseller->discount,
494
    discount                                => $bookseller->discount,
471
    orderdiscount_2dp    => sprintf( "%.2f", $data->{'discount'} || 0 ),
495
    orderdiscount_2dp                       => sprintf( "%.2f", $data->{'discount'} || 0 ),
472
    orderdiscount        => $data->{'discount'},
496
    orderdiscount                           => $data->{'discount'},
473
    order_internalnote   => $data->{'order_internalnote'},
497
    order_internalnote                      => $data->{'order_internalnote'},
474
    order_vendornote     => $data->{'order_vendornote'},
498
    order_vendornote                        => $data->{'order_vendornote'},
475
    listincgst           => $bookseller->listincgst,
499
    servicing_instruction_groups            => $servicing_instruction_groups,
476
    invoiceincgst        => $bookseller->invoiceincgst,
500
    servicing_instruction_groups_json       => $servicing_instruction_groups_json,
477
    cur_active_sym       => $active_currency->symbol,
501
    servicing_instruction_authorised_values => \@servicing_instruction_authorised_values,
478
    cur_active           => $active_currency->currency,
502
    listincgst                              => $bookseller->listincgst,
479
    currencies           => Koha::Acquisition::Currencies->search,
503
    invoiceincgst                           => $bookseller->invoiceincgst,
480
    currency             => $data->{currency},
504
    cur_active_sym                          => $active_currency->symbol,
481
    vendor_currency      => $bookseller->listprice,
505
    cur_active                              => $active_currency->currency,
482
    orderexists          => ( $new eq 'yes' ) ? 0 : 1,
506
    currencies                              => Koha::Acquisition::Currencies->search,
483
    title                => $data->{'title'},
507
    currency                                => $data->{currency},
484
    author               => $data->{'author'},
508
    vendor_currency                         => $bookseller->listprice,
485
    publicationyear      => $data->{'publicationyear'} ? $data->{'publicationyear'} : $data->{'copyrightdate'},
509
    orderexists                             => ( $new eq 'yes' ) ? 0 : 1,
486
    editionstatement     => $data->{'editionstatement'},
510
    title                                   => $data->{'title'},
487
    budget_loop          => $budget_loop,
511
    author                                  => $data->{'author'},
488
    isbn                 => $data->{'isbn'},
512
    publicationyear  => $data->{'publicationyear'} ? $data->{'publicationyear'} : $data->{'copyrightdate'},
489
    ean                  => $data->{'ean'},
513
    editionstatement => $data->{'editionstatement'},
490
    seriestitle          => $data->{'seriestitle'},
514
    budget_loop      => $budget_loop,
491
    itemtypeloop         => \@itemtypes,
515
    isbn             => $data->{'isbn'},
492
    quantity             => $quantity,
516
    ean              => $data->{'ean'},
493
    quantityrec          => $quantity,
517
    seriestitle      => $data->{'seriestitle'},
494
    quantitysugg         => $data->{quantitysugg},
518
    itemtypeloop     => \@itemtypes,
495
    rrp                  => $data->{'rrp'},
519
    quantity         => $quantity,
496
    replacementprice     => $data->{'replacementprice'},
520
    quantityrec      => $quantity,
497
    gst_values           => \@gst_values,
521
    quantitysugg     => $data->{quantitysugg},
498
    tax_rate             => $data->{tax_rate_on_ordering} ? $data->{tax_rate_on_ordering} + 0.0
522
    rrp              => $data->{'rrp'},
523
    replacementprice => $data->{'replacementprice'},
524
    gst_values       => \@gst_values,
525
    tax_rate         => $data->{tax_rate_on_ordering} ? $data->{tax_rate_on_ordering} + 0.0
499
    : $bookseller->tax_rate ? $bookseller->tax_rate + 0.0
526
    : $bookseller->tax_rate ? $bookseller->tax_rate + 0.0
500
    : 0,
527
    : 0,
501
    listprice => sprintf( "%.2f", $data->{listprice} || $data->{price} || $listprice ),
528
    listprice => sprintf( "%.2f", $data->{listprice} || $data->{price} || $listprice ),
(-)a/admin/columns_settings.yml (+2 lines)
Lines 201-206 modules: Link Here
201
              is_hidden: 1
201
              is_hidden: 1
202
            -
202
            -
203
              columnname: invoice
203
              columnname: invoice
204
            -
205
              columnname: servicing_instruction
204
            -
206
            -
205
              columnname: supplier_report
207
              columnname: supplier_report
206
            -
208
            -
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/basket.tt (-1 / +13 lines)
Lines 745-750 Link Here
745
                                        <th data-colname="statistic1">Statistic 1</th>
745
                                        <th data-colname="statistic1">Statistic 1</th>
746
                                        <th data-colname="statistic2">Statistic 2</th>
746
                                        <th data-colname="statistic2">Statistic 2</th>
747
                                        <th data-colname="invoice">Invoice</th>
747
                                        <th data-colname="invoice">Invoice</th>
748
                                        <th data-colname="servicing_instruction">Servicing instruction</th>
748
                                        <th data-colname="supplier_report">Supplier report</th>
749
                                        <th data-colname="supplier_report">Supplier report</th>
749
                                        <th data-colname="place_hold" class="no-sort no-export">Place hold</th>
750
                                        <th data-colname="place_hold" class="no-sort no-export">Place hold</th>
750
                                        <th data-colname="modify" class="no-sort no-export">Modify</th>
751
                                        <th data-colname="modify" class="no-sort no-export">Modify</th>
Lines 777-782 Link Here
777
                                            <th>&nbsp;</th>
778
                                            <th>&nbsp;</th>
778
                                            <th>&nbsp;</th>
779
                                            <th>&nbsp;</th>
779
                                            <th>&nbsp;</th>
780
                                            <th>&nbsp;</th>
781
                                            <th>&nbsp;</th>
780
                                        </tr>
782
                                        </tr>
781
                                    [% END %]
783
                                    [% END %]
782
                                    <tr>
784
                                    <tr>
Lines 803-808 Link Here
803
                                        <th>&nbsp;</th>
805
                                        <th>&nbsp;</th>
804
                                        <th>&nbsp;</th>
806
                                        <th>&nbsp;</th>
805
                                        <th>&nbsp;</th>
807
                                        <th>&nbsp;</th>
808
                                        <th>&nbsp;</th>
806
                                    </tr>
809
                                    </tr>
807
                                </tfoot>
810
                                </tfoot>
808
                                <tbody>
811
                                <tbody>
Lines 943-949 Link Here
943
                                                    [% books_loo.invoice_object.invoicenumber | html %]
946
                                                    [% books_loo.invoice_object.invoicenumber | html %]
944
                                                [% END %]
947
                                                [% END %]
945
                                            </td>
948
                                            </td>
946
949
                                            <td> [% books_loo.servicing_instruction_display | $raw %] </td>
947
                                            [% IF Koha.Preference('EDIFACT') && ediaccount %]
950
                                            [% IF Koha.Preference('EDIFACT') && ediaccount %]
948
                                                <td>[% books_loo.suppliers_report | html %]</td>
951
                                                <td>[% books_loo.suppliers_report | html %]</td>
949
                                            [% ELSE %]
952
                                            [% ELSE %]
Lines 1416-1421 Link Here
1416
        [% END %]
1419
        [% END %]
1417
        supplier_report_column.force_visibility = 1;
1420
        supplier_report_column.force_visibility = 1;
1418
1421
1422
        let servicing_instruction_column = orders_table_settings.columns.find(c => c.columnname == 'servicing_instruction');
1423
        [% IF !(Koha.Preference('EDIFACT') && ediaccount) %]
1424
            servicing_instruction_column.is_hidden = 1;
1425
            servicing_instruction_column.cannot_be_toggled = 1;
1426
        [% ELSE %]
1427
            servicing_instruction_column.is_hidden = 0;
1428
        [% END %]
1429
        servicing_instruction_column.force_visibility = 1;
1430
1419
        let modify_column = orders_table_settings.columns.find(c => c.columnname == 'modify');
1431
        let modify_column = orders_table_settings.columns.find(c => c.columnname == 'modify');
1420
        [% IF ( active && !closedate ) %]
1432
        [% IF ( active && !closedate ) %]
1421
            modify_column.is_hidden = 0;
1433
            modify_column.is_hidden = 0;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/neworderempty.tt (-1 / +234 lines)
Lines 198-203 Link Here
198
            getAuthValueDropbox( 'sort2', sort2_authcat, destination_sort2, sort2 );
198
            getAuthValueDropbox( 'sort2', sort2_authcat, destination_sort2, sort2 );
199
        });
199
        });
200
        $("#budget_id").change();
200
        $("#budget_id").change();
201
202
        // Servicing Instructions Management
203
        initServicingInstructions();
201
    });
204
    });
202
205
203
    function add_user(borrowernumber, borrowername) {
206
    function add_user(borrowernumber, borrowername) {
Lines 225-230 Link Here
225
        ids.splice(ids.indexOf(borrowernumber.toString()), 1);
228
        ids.splice(ids.indexOf(borrowernumber.toString()), 1);
226
        $("#users_ids").val(ids.join(':'));
229
        $("#users_ids").val(ids.join(':'));
227
    }
230
    }
231
232
    // Servicing Instructions Management
233
    var servicingInstructionGroups = [];
234
    var servicingInstructionAVs = [
235
        [% FOREACH av IN servicing_instruction_authorised_values %]
236
            { value: '[% av.authorised_value | html %]', label: '[% av.lib | html %]' },
237
        [% END %]
238
    ];
239
240
    function initServicingInstructions() {
241
        // Load existing data from template
242
        try {
243
            var existingData = JSON.parse('[% servicing_instruction_groups_json | $raw %]');
244
            servicingInstructionGroups = Array.isArray(existingData) ? existingData : [];
245
        } catch (e) {
246
            console.error('Failed to parse servicing instruction groups:', e);
247
            servicingInstructionGroups = [];
248
        }
249
250
        renderServicingInstructions();
251
        updateJSON();
252
253
        // Add group button handler
254
        $('#add_servicing_group').on('click', function() {
255
            addServicingGroup();
256
        });
257
258
        // Form submission handler - update JSON before submit
259
        $('form').on('submit', function() {
260
            updateJSON();
261
        });
262
    }
263
264
    function addServicingGroup() {
265
        servicingInstructionGroups.push([]);
266
        renderServicingInstructions();
267
        updateJSON();
268
    }
269
270
    function removeServicingGroup(groupIndex) {
271
        servicingInstructionGroups.splice(groupIndex, 1);
272
        renderServicingInstructions();
273
        updateJSON();
274
    }
275
276
    function addInstructionToGroup(groupIndex, type, value) {
277
        var group = servicingInstructionGroups[groupIndex];
278
279
        // Validate: max 2 instructions per group
280
        if (group.length >= 2) {
281
            alert('Each service group can contain at most 2 instructions (one LVC and/or one LVT).');
282
            return;
283
        }
284
285
        if (!type) type = 'LVC'; // Default to LVC (coded)
286
        if (!value) value = '';
287
288
        // Check if this type already exists in the group
289
        var hasLVC = group.some(function(inst) { return inst.type === 'LVC'; });
290
        var hasLVT = group.some(function(inst) { return inst.type === 'LVT'; });
291
292
        if (type === 'LVC' && hasLVC) {
293
            alert('This group already has an LVC instruction. Each group can have only one LVC.');
294
            return;
295
        }
296
        if (type === 'LVT' && hasLVT) {
297
            alert('This group already has an LVT instruction. Each group can have only one LVT.');
298
            return;
299
        }
300
301
        servicingInstructionGroups[groupIndex].push({ type: type, value: value });
302
        renderServicingInstructions();
303
        updateJSON();
304
    }
305
306
    function removeInstructionFromGroup(groupIndex, instructionIndex) {
307
        servicingInstructionGroups[groupIndex].splice(instructionIndex, 1);
308
        renderServicingInstructions();
309
        updateJSON();
310
    }
311
312
    function updateInstruction(groupIndex, instructionIndex, type, value) {
313
        var group = servicingInstructionGroups[groupIndex];
314
315
        // Check if changing type would create a duplicate
316
        for (var i = 0; i < group.length; i++) {
317
            if (i !== instructionIndex && group[i].type === type) {
318
                alert('This group already has a ' + type + ' instruction. Each group can have only one of each type.');
319
                renderServicingInstructions(); // Re-render to reset the select
320
                return;
321
            }
322
        }
323
324
        servicingInstructionGroups[groupIndex][instructionIndex] = { type: type, value: value };
325
        updateJSON();
326
    }
327
328
    function renderServicingInstructions() {
329
        var container = $('#servicing_instructions_container');
330
        container.empty();
331
332
        if (servicingInstructionGroups.length === 0) {
333
            container.append('<div class="hint">No servicing instructions added. Click "Add servicing group" to add one.</div>');
334
            return;
335
        }
336
337
        servicingInstructionGroups.forEach(function(group, groupIndex) {
338
            var groupDiv = $('<div class="servicing_group"></div>');
339
            groupDiv.css({
340
                'border': '1px solid #ddd',
341
                'padding': '10px',
342
                'margin-bottom': '10px',
343
                'background': '#f9f9f9',
344
                'border-radius': '4px'
345
            });
346
347
            var groupHeader = $('<div style="margin-bottom: 10px;"></div>');
348
            groupHeader.append('<strong>Service Group ' + (groupIndex + 1) + '</strong> ');
349
            groupHeader.append(
350
                $('<button type="button" class="btn btn-default btn-xs" style="margin-left: 10px;">' +
351
                    '<i class="fa fa-trash"></i> Remove group</button>')
352
                    .on('click', function() { removeServicingGroup(groupIndex); })
353
            );
354
            groupDiv.append(groupHeader);
355
356
            if (group.length === 0) {
357
                groupDiv.append('<div class="hint" style="margin-left: 20px;">Empty group</div>');
358
            }
359
360
            group.forEach(function(instruction, instructionIndex) {
361
                var instructionDiv = $('<div class="servicing_instruction" style="margin-left: 20px; margin-bottom: 5px;"></div>');
362
363
                var typeSelect = $('<select style="width: 100px; margin-right: 5px;"></select>')
364
                    .append('<option value="LVC"' + (instruction.type === 'LVC' ? ' selected' : '') + '>LVC (Coded)</option>')
365
                    .append('<option value="LVT"' + (instruction.type === 'LVT' ? ' selected' : '') + '>LVT (Text)</option>')
366
                    .on('change', function() {
367
                        var newType = $(this).val();
368
                        var valueInput = $(this).next();
369
                        updateInstruction(groupIndex, instructionIndex, newType, valueInput.val());
370
                        renderServicingInstructions(); // Re-render to update input type
371
                    });
372
373
                var valueInput;
374
                if (instruction.type === 'LVC' && servicingInstructionAVs.length > 0) {
375
                    valueInput = $('<select style="width: 300px; margin-right: 5px;"></select>');
376
                    valueInput.append('<option value="">Select...</option>');
377
                    servicingInstructionAVs.forEach(function(av) {
378
                        valueInput.append(
379
                            $('<option></option>')
380
                                .attr('value', av.value)
381
                                .text(av.label)
382
                                .prop('selected', av.value === instruction.value)
383
                        );
384
                    });
385
                } else {
386
                    valueInput = $('<input type="text" style="width: 300px; margin-right: 5px;" />')
387
                        .val(instruction.value);
388
                }
389
390
                valueInput.on('change', function() {
391
                    updateInstruction(groupIndex, instructionIndex, typeSelect.val(), $(this).val());
392
                });
393
394
                var removeBtn = $('<button type="button" class="btn btn-default btn-xs">' +
395
                    '<i class="fa fa-trash"></i> Remove</button>')
396
                    .on('click', function() { removeInstructionFromGroup(groupIndex, instructionIndex); });
397
398
                instructionDiv.append(typeSelect);
399
                instructionDiv.append(valueInput);
400
                instructionDiv.append(removeBtn);
401
                groupDiv.append(instructionDiv);
402
            });
403
404
            // Determine what can be added to this group
405
            var hasLVC = group.some(function(inst) { return inst.type === 'LVC'; });
406
            var hasLVT = group.some(function(inst) { return inst.type === 'LVT'; });
407
            var canAddMore = group.length < 2;
408
409
            if (canAddMore) {
410
                var btnText = '<i class="fa fa-plus"></i> Add instruction';
411
                if (hasLVC && !hasLVT) {
412
                    btnText += ' (LVT only)';
413
                } else if (hasLVT && !hasLVC) {
414
                    btnText += ' (LVC only)';
415
                } else if (group.length === 0) {
416
                    btnText += ' to this group';
417
                }
418
419
                var addInstructionBtn = $('<button type="button" class="btn btn-default btn-xs" style="margin-left: 20px; margin-top: 5px;">' + btnText + '</button>')
420
                    .on('click', function() {
421
                        // Determine default type based on what's already in the group
422
                        var defaultType = 'LVC';
423
                        if (hasLVC && !hasLVT) defaultType = 'LVT';
424
                        else if (hasLVT && !hasLVC) defaultType = 'LVC';
425
                        addInstructionToGroup(groupIndex, defaultType);
426
                    });
427
                groupDiv.append(addInstructionBtn);
428
            } else {
429
                var maxMsg = $('<div class="hint" style="margin-left: 20px; margin-top: 5px; color: #666;">' +
430
                    'Maximum 2 instructions per group (one LVC + one LVT)</div>');
431
                groupDiv.append(maxMsg);
432
            }
433
434
            container.append(groupDiv);
435
        });
436
    }
437
438
    function updateJSON() {
439
        // Filter out empty groups and instructions with empty values
440
        var filtered = servicingInstructionGroups
441
            .map(function(group) {
442
                return group.filter(function(instruction) {
443
                    return instruction.value && instruction.value.trim() !== '';
444
                });
445
            })
446
            .filter(function(group) {
447
                return group.length > 0;
448
            });
449
450
        var json = JSON.stringify(filtered);
451
        $('#servicing_instruction_groups_json').val(json);
452
    }
228
</script>
453
</script>
229
[% Asset.css("css/addbiblio.css") | $raw %]
454
[% Asset.css("css/addbiblio.css") | $raw %]
230
</head>
455
</head>
Lines 716-721 Link Here
716
                    <label for="order_vendornote">Vendor note: </label>
941
                    <label for="order_vendornote">Vendor note: </label>
717
                    <textarea id="order_vendornote" cols="30" rows="3" name="order_vendornote">[% IF ( order_vendornote ) %][% order_vendornote | html %][% END %]</textarea>
942
                    <textarea id="order_vendornote" cols="30" rows="3" name="order_vendornote">[% IF ( order_vendornote ) %][% order_vendornote | html %][% END %]</textarea>
718
                </li>
943
                </li>
944
                <li>
945
                    <label for="servicing_instructions">Servicing instructions: </label>
946
                    <div id="servicing_instructions_container">
947
                        <!-- Servicing instruction groups will be populated here -->
948
                    </div>
949
                    <button type="button" id="add_servicing_group" class="btn btn-default btn-xs"> <i class="fa fa-plus"></i> Add servicing group </button>
950
                    <input type="hidden" id="servicing_instruction_groups_json" name="servicing_instruction_groups_json" value="" />
951
                    <div class="hint">EDIFACT servicing instructions (GIR LVT/LVC). Each group can contain multiple coded (LVC) and/or free-text (LVT) instructions that work together.</div>
952
                </li>
719
                <li>
953
                <li>
720
                    <label for="estimated_delivery_date">Estimated delivery date: </label>
954
                    <label for="estimated_delivery_date">Estimated delivery date: </label>
721
                    <input type="text" id="estimated_delivery_date" size="10" name="estimated_delivery_date" class="flatpickr" value="[% estimated_delivery_date | html %]" />
955
                    <input type="text" id="estimated_delivery_date" size="10" name="estimated_delivery_date" class="flatpickr" value="[% estimated_delivery_date | html %]" />
722
- 

Return to bug 30144