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

(-)a/Koha/EDI.pm (-3 / +54 lines)
Lines 95-100 sub create_edi_order { Link Here
95
        }
95
        }
96
    )->single;
96
    )->single;
97
97
98
    # Check for duplicate PO numbers when po_is_basketname is enabled
99
    if ( $vendor && $vendor->po_is_basketname ) {
100
        my $basket = $orderlines[0]->basketno;
101
        if ( $basket->basketname ) {
102
            my $existing_basket = $schema->resultset('Aqbasket')->search(
103
                {
104
                    basketname   => $basket->basketname,
105
                    booksellerid => $basket->booksellerid,
106
                    basketno     => { '!=' => $basketno },    # Exclude current basket
107
                }
108
            )->first;
109
110
            if ($existing_basket) {
111
                $logger->error( "EDI order blocked for basket $basketno due to duplicate PO number '"
112
                        . $basket->basketname
113
                        . "' (existing basket: "
114
                        . $existing_basket->basketno
115
                        . ")" );
116
                return {
117
                    error     => "duplicate_po_number", existing_basket => $existing_basket->basketno,
118
                    po_number => $basket->basketname
119
                };
120
            }
121
        }
122
    }
123
98
    my $ean_search_keys = { ean => $ean, };
124
    my $ean_search_keys = { ean => $ean, };
99
    if ($branchcode) {
125
    if ($branchcode) {
100
        $ean_search_keys->{branchcode} = $branchcode;
126
        $ean_search_keys->{branchcode} = $branchcode;
Lines 647-653 sub process_quote { Link Here
647
    my $schema         = Koha::Database->new()->schema();
673
    my $schema         = Koha::Database->new()->schema();
648
    my $message_count  = 0;
674
    my $message_count  = 0;
649
    my $split_message;
675
    my $split_message;
650
    my @added_baskets;    # if auto & multiple baskets need to order all
676
    my @added_baskets;                # if auto & multiple baskets need to order all
677
    my %baskets_with_po_conflicts;    # track baskets that have PO number conflicts
651
678
652
    # Get vendor EDI account configuration
679
    # Get vendor EDI account configuration
653
    my $v = $schema->resultset('VendorEdiAccount')->search(
680
    my $v = $schema->resultset('VendorEdiAccount')->search(
Lines 663-675 sub process_quote { Link Here
663
690
664
            # Determine basket name based on vendor configuration
691
            # Determine basket name based on vendor configuration
665
            my $basket_name = $quote_message->filename;
692
            my $basket_name = $quote_message->filename;
693
            my $purchase_order_number;
694
            my $existing_basket;
695
666
            if ( $v && $v->po_is_basketname ) {
696
            if ( $v && $v->po_is_basketname ) {
667
                my $purchase_order_number = $msg->purchase_order_number;
697
                $purchase_order_number = $msg->purchase_order_number;
668
                if ($purchase_order_number) {
698
                if ($purchase_order_number) {
669
                    $basket_name = $purchase_order_number;
699
                    $basket_name = $purchase_order_number;
670
700
671
                    # Check for duplicate purchase order numbers for this vendor (including closed baskets)
701
                    # Check for duplicate purchase order numbers for this vendor (including closed baskets)
672
                    my $existing_basket = $schema->resultset('Aqbasket')->search(
702
                    $existing_basket = $schema->resultset('Aqbasket')->search(
673
                        {
703
                        {
674
                            basketname   => $basket_name,
704
                            basketname   => $basket_name,
675
                            booksellerid => $quote_message->vendor_id,
705
                            booksellerid => $quote_message->vendor_id,
Lines 701-706 sub process_quote { Link Here
701
                q{} . q{}
731
                q{} . q{}
702
            );
732
            );
703
            push @added_baskets, $basketno;
733
            push @added_baskets, $basketno;
734
735
            # Record if this basket has a PO conflict (for auto_orders blocking)
736
            if ($existing_basket) {
737
                $baskets_with_po_conflicts{$basketno} = {
738
                    po_number       => $purchase_order_number,
739
                    existing_basket => $existing_basket->basketno
740
                };
741
            }
704
            if ( $message_count > 1 ) {
742
            if ( $message_count > 1 ) {
705
                my $m_filename = $quote_message->filename;
743
                my $m_filename = $quote_message->filename;
706
                $m_filename .= "_$message_count";
744
                $m_filename .= "_$message_count";
Lines 741-746 sub process_quote { Link Here
741
                               # Do we automatically generate orders for this vendor
779
                               # Do we automatically generate orders for this vendor
742
    if ( $v && $v->auto_orders ) {
780
    if ( $v && $v->auto_orders ) {
743
        for my $b (@added_baskets) {
781
        for my $b (@added_baskets) {
782
783
            # Check if this basket has a PO number conflict
784
            if ( exists $baskets_with_po_conflicts{$b} ) {
785
                my $conflict_info = $baskets_with_po_conflicts{$b};
786
                $logger->warn( "Auto-order blocked for basket $b due to duplicate PO number '"
787
                        . $conflict_info->{po_number}
788
                        . "' (existing basket: "
789
                        . $conflict_info->{existing_basket}
790
                        . ")" );
791
                $logger->info("Basket $b remains open due to PO number conflict");
792
                next;
793
            }
794
744
            create_edi_order(
795
            create_edi_order(
745
                {
796
                {
746
                    ean      => $messages->[0]->buyer_ean,
797
                    ean      => $messages->[0]->buyer_ean,
(-)a/acqui/basket.pl (-1 / +13 lines)
Lines 578-584 sub edi_close_and_order { Link Here
578
        if ( $basket->{branch} ) {
578
        if ( $basket->{branch} ) {
579
            $edi_params->{branchcode} = $basket->{branch};
579
            $edi_params->{branchcode} = $basket->{branch};
580
        }
580
        }
581
        if ( create_edi_order($edi_params) ) {
581
        my $edi_result = create_edi_order($edi_params);
582
        if ( ref $edi_result eq 'HASH' && $edi_result->{error} ) {
583
            if ( $edi_result->{error} eq 'duplicate_po_number' ) {
584
                push @messages, {
585
                    type            => 'error',
586
                    code            => 'edi_duplicate_po_number',
587
                    po_number       => $edi_result->{po_number},
588
                    existing_basket => $edi_result->{existing_basket}
589
                };
590
                $op = 'list';    # Stay on basket page to show error
591
                return;
592
            }
593
        } elsif ($edi_result) {
582
594
583
            #$template->param( edifile => 1 );
595
            #$template->param( edifile => 1 );
584
        }
596
        }
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/basket.tt (+5 lines)
Lines 306-311 Link Here
306
                                <span>There is no notice template with code ACQORDER defined.</span>
306
                                <span>There is no notice template with code ACQORDER defined.</span>
307
                            [% CASE 'email_sent' %]
307
                            [% CASE 'email_sent' %]
308
                                <span>Order e-mail was sent to the vendor.</span>
308
                                <span>Order e-mail was sent to the vendor.</span>
309
                            [% CASE 'edi_duplicate_po_number' %]
310
                                <span
311
                                    >EDI order blocked: Duplicate purchase order number '[% m.po_number | html %]' found. This purchase order number is already used by basket [% m.existing_basket | html %]. Please check with your vendor or
312
                                    use a different purchase order number.</span
313
                                >
309
                            [% CASE %]
314
                            [% CASE %]
310
                                <span>ERROR! - [% m.code | html %]</span>
315
                                <span>ERROR! - [% m.code | html %]</span>
311
                            [% END %]
316
                            [% END %]
(-)a/t/db_dependent/Koha/EDI.t (-2 / +234 lines)
Lines 21-27 use Modern::Perl; Link Here
21
use FindBin qw( $Bin );
21
use FindBin qw( $Bin );
22
22
23
use Test::NoWarnings;
23
use Test::NoWarnings;
24
use Test::More tests => 5;
24
use Test::More tests => 6;
25
use Test::MockModule;
25
use Test::MockModule;
26
26
27
use t::lib::Mocks;
27
use t::lib::Mocks;
Lines 1067-1072 subtest 'process_invoice_without_tax_rate' => sub { Link Here
1067
    $schema->storage->txn_rollback;
1067
    $schema->storage->txn_rollback;
1068
};
1068
};
1069
1069
1070
subtest 'duplicate_po_number_blocking' => sub {
1071
    plan tests => 2;
1072
1073
    $schema->storage->txn_begin;
1074
1075
    my $test_san      = '5013546098818';
1076
    my $dirname       = ( $Bin =~ /^(.*\/t\/)/ ? $1 . 'edi_testfiles/' : q{} );
1077
    my $active_period = $builder->build(
1078
        {
1079
            source => 'Aqbudgetperiod',
1080
            value  => { budget_period_active => 1 }
1081
        }
1082
    );
1083
1084
    # Test 1: Auto-orders blocking for duplicate PO numbers
1085
    subtest 'auto_orders_blocking' => sub {
1086
        plan tests => 9;
1087
1088
        $schema->storage->txn_begin;
1089
1090
        # Create vendor EDI account with auto_orders and po_is_basketname enabled
1091
        my $account = $builder->build(
1092
            {
1093
                source => 'VendorEdiAccount',
1094
                value  => {
1095
                    description      => 'auto_orders duplicate test vendor',
1096
                    transport        => 'FILE',
1097
                    plugin           => '',
1098
                    san              => $test_san,
1099
                    auto_orders      => 1,
1100
                    po_is_basketname => 1,
1101
                }
1102
            }
1103
        );
1104
1105
        my $ean = $builder->build(
1106
            {
1107
                source => 'EdifactEan',
1108
                value  => {
1109
                    description => 'test ean',
1110
                    branchcode  => undef,
1111
                    ean         => $test_san
1112
                }
1113
            }
1114
        );
1115
1116
        # Create existing basket with PO number that will conflict
1117
        my $existing_basket = $builder->build(
1118
            {
1119
                source => 'Aqbasket',
1120
                value  => {
1121
                    basketname   => 'orders 23/1',           # Same as in QUOTES_SMALL.CEQ
1122
                    booksellerid => $account->{vendor_id},
1123
                    closedate    => undef,
1124
                }
1125
            }
1126
        );
1127
1128
        # Setup the fund
1129
        $builder->build(
1130
            {
1131
                source => 'Aqbudget',
1132
                value  => {
1133
                    budget_code      => 'REF',
1134
                    budget_period_id => $active_period->{budget_period_id}
1135
                }
1136
            }
1137
        );
1138
1139
        my $filename = 'QUOTES_SMALL.CEQ';
1140
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
1141
        $trans->working_directory($dirname);
1142
1143
        my $mhash = $trans->message_hash();
1144
        $mhash->{message_type} = 'QUOTE';
1145
        $trans->ingest( $mhash, $filename );
1146
1147
        my $quote = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
1148
1149
        # Clear logger before processing
1150
        $logger->clear();
1151
1152
        # Process quote - should create basket but NOT auto-order due to duplicate PO
1153
        my $die;
1154
        eval {
1155
            process_quote($quote);
1156
            1;
1157
        } or do {
1158
            $die = $@;
1159
        };
1160
        ok( !$die, 'Quote with duplicate PO processed without dying' );
1161
1162
        # Verify duplicate PO error was logged during quote processing
1163
        my $errors = $quote->edifact_errors;
1164
        ok( $errors->count >= 1, 'Error logged for duplicate PO number' );
1165
1166
        my $duplicate_error = $errors->search( { section => 'RFF+ON:orders 23/1' } )->first;
1167
        ok( $duplicate_error, 'Duplicate PO error found in quote errors' );
1168
1169
        # Check that new basket was created
1170
        my $baskets = Koha::Acquisition::Baskets->search(
1171
            {
1172
                booksellerid => $account->{vendor_id},
1173
                basketno     => { '!=' => $existing_basket->{basketno} }
1174
            }
1175
        );
1176
        is( $baskets->count, 1, "New basket created despite duplicate PO" );
1177
1178
        my $new_basket = $baskets->next;
1179
        is( $new_basket->basketname, 'orders 23/1', "New basket uses duplicate PO number as name" );
1180
1181
        # Critical test: Verify basket was NOT closed (auto-order was blocked)
1182
        ok( !$new_basket->closedate, 'New basket with duplicate PO was NOT closed (auto-order blocked)' );
1183
1184
        # Verify no EDI order was created for the conflicting basket
1185
        my $edi_orders = $schema->resultset('EdifactMessage')->search(
1186
            {
1187
                message_type => 'ORDERS',
1188
                basketno     => $new_basket->basketno,
1189
            }
1190
        );
1191
        is( $edi_orders->count, 0, 'No EDI order created for basket with duplicate PO' );
1192
1193
        # Check that the blocking is working by verifying no EDI orders were created
1194
        # This is the most important test - the basket should NOT be auto-ordered
1195
        ok( $edi_orders->count == 0, 'Auto-order was blocked - no EDI order created for duplicate PO' );
1196
1197
        # Verify that duplicate PO error was logged (this shows detection is working)
1198
        ok( $duplicate_error, 'Duplicate PO number detection and logging is working' );
1199
1200
        $schema->storage->txn_rollback;
1201
    };
1202
1203
    # Test 2: No blocking when po_is_basketname is disabled
1204
    subtest 'no_blocking_when_feature_disabled' => sub {
1205
        plan tests => 5;
1206
1207
        $schema->storage->txn_begin;
1208
1209
        # Create vendor EDI account with po_is_basketname DISABLED
1210
        my $account = $builder->build(
1211
            {
1212
                source => 'VendorEdiAccount',
1213
                value  => {
1214
                    description      => 'feature disabled test vendor',
1215
                    transport        => 'FILE',
1216
                    plugin           => '',
1217
                    san              => $test_san,
1218
                    auto_orders      => 1,
1219
                    po_is_basketname => 0,                                # Feature disabled
1220
                }
1221
            }
1222
        );
1223
1224
        my $ean = $builder->build(
1225
            {
1226
                source => 'EdifactEan',
1227
                value  => {
1228
                    description => 'test ean',
1229
                    branchcode  => undef,
1230
                    ean         => $test_san
1231
                }
1232
            }
1233
        );
1234
1235
        # Create existing basket with any name
1236
        my $existing_basket = $builder->build(
1237
            {
1238
                source => 'Aqbasket',
1239
                value  => {
1240
                    basketname   => 'orders 23/1',
1241
                    booksellerid => $account->{vendor_id},
1242
                    closedate    => undef,
1243
                }
1244
            }
1245
        );
1246
1247
        # Setup the fund
1248
        $builder->build(
1249
            {
1250
                source => 'Aqbudget',
1251
                value  => {
1252
                    budget_code      => 'REF',
1253
                    budget_period_id => $active_period->{budget_period_id}
1254
                }
1255
            }
1256
        );
1257
1258
        my $filename = 'QUOTES_SMALL.CEQ';
1259
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
1260
        $trans->working_directory($dirname);
1261
1262
        my $mhash = $trans->message_hash();
1263
        $mhash->{message_type} = 'QUOTE';
1264
        $trans->ingest( $mhash, $filename );
1265
1266
        my $quote = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
1267
1268
        $logger->clear();
1269
        process_quote($quote);
1270
1271
        # Verify new basket was created and auto-ordered (no blocking)
1272
        my $baskets = Koha::Acquisition::Baskets->search(
1273
            {
1274
                booksellerid => $account->{vendor_id},
1275
                basketno     => { '!=' => $existing_basket->{basketno} }
1276
            }
1277
        );
1278
        is( $baskets->count, 1, "New basket created" );
1279
1280
        my $new_basket = $baskets->next;
1281
1282
        # When po_is_basketname is disabled, basket uses filename not PO number
1283
        is( $new_basket->basketname, $filename, "Basket uses filename when po_is_basketname disabled" );
1284
1285
        # The key test is that normal processing occurred without duplicate detection
1286
        # Since po_is_basketname is disabled, basket should use filename not PO number
1287
        ok( $new_basket->basketname eq $filename, 'Normal basket naming when feature disabled' );
1288
1289
        # With po_is_basketname disabled, quote processing should work normally
1290
        # The absence of duplicate errors in the database is the key indicator
1291
        my $quote_errors           = $schema->resultset('EdifactError')->search( {} );
1292
        my $has_duplicate_db_error = grep { $_->details =~ /Duplicate purchase order/ } $quote_errors->all;
1293
        ok( !$has_duplicate_db_error, 'No duplicate PO database errors when feature disabled' );
1294
1295
        pass('Feature correctly disabled - normal processing occurred');
1296
1297
        $schema->storage->txn_rollback;
1298
    };
1299
1300
    $schema->storage->txn_rollback;
1301
};
1302
1070
subtest 'create_edi_order_logging' => sub {
1303
subtest 'create_edi_order_logging' => sub {
1071
    plan tests => 8;
1304
    plan tests => 8;
1072
1305
1073
- 

Return to bug 20253