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

(-)a/Koha/EDI.pm (-1 / +196 lines)
Lines 331-336 sub process_invoice { Link Here
331
                next;
331
                next;
332
            }
332
            }
333
            $invoice_message->edi_acct( $vendor_acct->id );
333
            $invoice_message->edi_acct( $vendor_acct->id );
334
335
            # Check for duplicate invoices if preference enabled
336
            if ( C4::Context->preference('EdiBlockDuplicateInvoice') ) {
337
                my $duplicate_invoice = $schema->resultset('Aqinvoice')->search(
338
                    {
339
                        invoicenumber => $invoicenumber,
340
                        booksellerid  => $invoice_message->vendor_id,
341
                    }
342
                )->first;
343
344
                if ($duplicate_invoice) {
345
                    $logger->error( "Duplicate invoice $invoicenumber for vendor "
346
                            . $invoice_message->vendor_id . " in "
347
                            . $invoice_message->filename );
348
349
                    # Log to edifact_errors table
350
                    $invoice_message->add_to_edifact_errors(
351
                        {
352
                            section => "BGM+" . $invoicenumber,
353
                            details => "Duplicate invoice number '$invoicenumber'. "
354
                                . "Original invoice ID: "
355
                                . $duplicate_invoice->invoiceid . ". "
356
                                . "Processing blocked."
357
                        }
358
                    );
359
360
                    # Send email notification if enabled
361
                    _send_duplicate_invoice_email_notice(
362
                        $invoice_message,
363
                        $invoicenumber,
364
                        $vendor_acct,
365
                        $duplicate_invoice
366
                    );
367
368
                    # Mark message as error and stop processing this invoice
369
                    $invoice_message->status('error');
370
                    $invoice_message->update;
371
372
                    next;    # Skip to next message in transmission
373
                }
374
            }
375
334
            $logger->trace("Adding invoice: $invoicenumber");
376
            $logger->trace("Adding invoice: $invoicenumber");
335
            my $new_invoice = $schema->resultset('Aqinvoice')->create(
377
            my $new_invoice = $schema->resultset('Aqinvoice')->create(
336
                {
378
                {
Lines 470-476 sub process_invoice { Link Here
470
        }
512
        }
471
    }
513
    }
472
514
473
    $invoice_message->status('received');
515
    # Only set status to 'received' if not already set to 'error'
516
    $invoice_message->discard_changes;
517
    if ( $invoice_message->status ne 'error' ) {
518
        $invoice_message->status('received');
519
    }
474
    $invoice_message->update;    # status and basketno link
520
    $invoice_message->update;    # status and basketno link
475
    return;
521
    return;
476
}
522
}
Lines 1400-1405 sub _handle_008_field { Link Here
1400
    return $bib_record;
1446
    return $bib_record;
1401
}
1447
}
1402
1448
1449
sub _send_duplicate_invoice_email_notice {
1450
    my ( $invoice_message, $invoicenumber, $vendor_acct, $duplicate_invoice ) = @_;
1451
1452
    my $logger = Koha::Logger->get( { interface => 'edi' } );
1453
1454
    # Check if email notifications enabled
1455
    return unless C4::Context->preference('EdiBlockDuplicateInvoiceEmailNotice');
1456
1457
    # Get vendor information
1458
    my $vendor_id = $invoice_message->vendor_id;
1459
1460
    # Prepare template substitution values
1461
    my $substitute = {
1462
        invoicenumber         => $invoicenumber,
1463
        vendor_id             => $vendor_id,
1464
        vendor_san            => $vendor_acct ? $vendor_acct->san : '',
1465
        filename              => $invoice_message->filename,
1466
        message_id            => $invoice_message->id,
1467
        original_invoiceid    => $duplicate_invoice->invoiceid,
1468
        original_shipmentdate => $duplicate_invoice->shipmentdate || 'N/A',
1469
        received_date         => DateTime->now->ymd,
1470
    };
1471
1472
    # 1. Send notification to library staff
1473
    my $library_email_addresses = C4::Context->preference('EdiBlockDuplicateInvoiceEmailAddresses');
1474
    if ($library_email_addresses) {
1475
        my @library_addresses = split /\s*,\s*/, $library_email_addresses;
1476
1477
        foreach my $to_address (@library_addresses) {
1478
            $to_address =~ s/^\s+|\s+$//g;    # trim whitespace
1479
            next unless $to_address;
1480
            next unless Koha::Email->is_valid($to_address);
1481
1482
            my $letter = C4::Letters::GetPreparedLetter(
1483
                module                 => 'acquisition',
1484
                letter_code            => 'EDI_DUP_INV_LIBRARY',
1485
                message_transport_type => 'email',
1486
                tables                 => {
1487
                    aqbooksellers => $vendor_id,
1488
                },
1489
                substitute => $substitute,
1490
            );
1491
1492
            if ($letter) {
1493
                my $message_id = C4::Letters::EnqueueLetter(
1494
                    {
1495
                        letter                 => $letter,
1496
                        to_address             => $to_address,
1497
                        message_transport_type => 'email',
1498
                    }
1499
                );
1500
1501
                if ($message_id) {
1502
                    $logger->info(
1503
                        "Library duplicate invoice notification queued (message_id: $message_id) for $to_address, invoice $invoicenumber. Message will be sent by message_queue cronjob."
1504
                    );
1505
                } else {
1506
                    $logger->warn("Failed to enqueue library notification to $to_address for invoice $invoicenumber");
1507
                }
1508
            } else {
1509
                $logger->warn("Could not generate library notification letter for invoice $invoicenumber");
1510
            }
1511
        }
1512
    }
1513
1514
    # 2. Send notification to vendor contacts
1515
    if ($vendor_acct) {
1516
        my $schema = Koha::Database->new()->schema();
1517
        my $vendor = $schema->resultset('Aqbookseller')->find($vendor_id);
1518
        if ($vendor) {
1519
            my @edi_contacts = $vendor->aqcontacts->search(
1520
                {
1521
                    edi_error_notification => 1,
1522
                    email                  => { '!=' => undef },
1523
                }
1524
            )->all;
1525
1526
            foreach my $contact (@edi_contacts) {
1527
                my $vendor_email = $contact->email;
1528
1529
                if ( Koha::Email->is_valid($vendor_email) ) {
1530
                    my $letter = C4::Letters::GetPreparedLetter(
1531
                        module                 => 'acquisition',
1532
                        letter_code            => 'EDI_DUP_INV_VENDOR',
1533
                        message_transport_type => 'email',
1534
                        tables                 => {
1535
                            aqbooksellers => $vendor_id,
1536
                        },
1537
                        substitute => $substitute,
1538
                    );
1539
1540
                    if ($letter) {
1541
                        my $message_id = C4::Letters::EnqueueLetter(
1542
                            {
1543
                                letter                 => $letter,
1544
                                to_address             => $vendor_email,
1545
                                message_transport_type => 'email',
1546
                            }
1547
                        );
1548
1549
                        if ($message_id) {
1550
                            $logger->info(
1551
                                "Vendor duplicate invoice notification queued (message_id: $message_id) for $vendor_email (contact: "
1552
                                    . $contact->name
1553
                                    . "), invoice $invoicenumber. Message will be sent by message_queue cronjob." );
1554
                        } else {
1555
                            $logger->warn(
1556
                                "Failed to enqueue vendor notification to $vendor_email for invoice $invoicenumber");
1557
                        }
1558
                    } else {
1559
                        $logger->warn("Could not generate vendor notification letter for invoice $invoicenumber");
1560
                    }
1561
                } else {
1562
                    $logger->warn(
1563
                        "Invalid vendor contact email address: $vendor_email for contact: " . $contact->name );
1564
                }
1565
            }
1566
        }
1567
    }
1568
}
1569
1403
1;
1570
1;
1404
__END__
1571
__END__
1405
1572
Lines 1515-1520 Koha::EDI Link Here
1515
1682
1516
      If all else fails returns empty string
1683
      If all else fails returns empty string
1517
1684
1685
=head2 _send_duplicate_invoice_email_notice
1686
1687
    _send_duplicate_invoice_email_notice($invoice_message, $invoicenumber, $vendor_acct, $duplicate_invoice)
1688
1689
    Internal function to queue email notifications when duplicate EDIFACT invoices are detected.
1690
1691
    Uses the standard Koha messaging pattern:
1692
    - GetPreparedLetter: Generates the notice content from templates
1693
    - EnqueueLetter: Adds the message to the message_queue table for delivery by the cronjob
1694
1695
    Queues two types of notifications using letter templates:
1696
    1. EDI_DUP_INV_LIBRARY - to library staff (addresses from EdiBlockDuplicateInvoiceEmailAddresses)
1697
    2. EDI_DUP_INV_VENDOR - to vendor (address from vendor_edi_accounts.vendor_email)
1698
1699
    All messages are recorded in the message_queue table for auditing and can be reviewed
1700
    in the Koha notices interface. Messages will be sent by the message_queue cronjob
1701
    (misc/cronjobs/process_message_queue.pl).
1702
1703
    Only runs if EdiBlockDuplicateInvoiceEmailNotice preference is enabled.
1704
1705
    Parameters:
1706
    - $invoice_message: The EdifactMessage object being processed
1707
    - $invoicenumber: The duplicate invoice number found
1708
    - $vendor_acct: The VendorEdiAccount object
1709
    - $duplicate_invoice: The existing Aqinvoice object with the same invoice number
1710
1711
    Returns: nothing
1712
1518
=head2 _create_bib_from_quote
1713
=head2 _create_bib_from_quote
1519
1714
1520
       marc_record_obj = _create_bib_from_quote(lineitem, quote)
1715
       marc_record_obj = _create_bib_from_quote(lineitem, quote)
(-)a/Koha/Schema/Result/Aqcontact.pm (-5 / +16 lines)
Lines 126-131 is this the primary contact for acquisitions messages Link Here
126
126
127
is this the primary contact for serials messages
127
is this the primary contact for serials messages
128
128
129
=head2 edi_error_notification
130
131
  data_type: 'tinyint'
132
  default_value: 0
133
  is_nullable: 0
134
135
should this contact receive EDI error notifications (e.g. duplicate invoices)
136
129
=head2 booksellerid
137
=head2 booksellerid
130
138
131
  data_type: 'integer'
139
  data_type: 'integer'
Lines 161-166 __PACKAGE__->add_columns( Link Here
161
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
169
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
162
  "serialsprimary",
170
  "serialsprimary",
163
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
171
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
172
  "edi_error_notification",
173
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
164
  "booksellerid",
174
  "booksellerid",
165
  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
175
  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
166
);
176
);
Lines 199-209 __PACKAGE__->belongs_to( Link Here
199
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:IefjqDsoXPLWKSfhYGne1A
209
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:IefjqDsoXPLWKSfhYGne1A
200
210
201
__PACKAGE__->add_columns(
211
__PACKAGE__->add_columns(
202
    '+orderacquisition' => { is_boolean => 1 },
212
    '+orderacquisition'       => { is_boolean => 1 },
203
    '+claimacquisition' => { is_boolean => 1 },
213
    '+claimacquisition'       => { is_boolean => 1 },
204
    '+claimissues'      => { is_boolean => 1 },
214
    '+claimissues'            => { is_boolean => 1 },
205
    '+acqprimary'       => { is_boolean => 1 },
215
    '+acqprimary'             => { is_boolean => 1 },
206
    '+serialsprimary'   => { is_boolean => 1 },
216
    '+serialsprimary'         => { is_boolean => 1 },
217
    '+edi_error_notification' => { is_boolean => 1 },
207
);
218
);
208
219
209
=head2 koha_object_class
220
=head2 koha_object_class
(-)a/Koha/Schema/Result/VendorEdiAccount.pm (-2 / +2 lines)
Lines 248-255 __PACKAGE__->belongs_to( Link Here
248
);
248
);
249
249
250
250
251
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-11-03 20:27:30
251
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-11-26 00:00:00
252
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1sHAak6V/HC2E1AUHzDapg
252
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:original_schema_bug_41297
253
253
254
__PACKAGE__->add_columns(
254
__PACKAGE__->add_columns(
255
    '+auto_orders'       => { is_boolean => 1 },
255
    '+auto_orders'       => { is_boolean => 1 },
(-)a/installer/data/mysql/atomicupdate/bug_40383.pl (+163 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number  => "41297",
5
    description => "Add system preferences for blocking duplicate EDI invoices",
6
    up          => sub {
7
        my ($args) = @_;
8
        my ( $dbh, $out ) = @$args{qw(dbh out)};
9
10
        # Master preference to enable duplicate blocking
11
        $dbh->do(
12
            q{
13
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
14
            VALUES (
15
                'EdiBlockDuplicateInvoice',
16
                '0',
17
                NULL,
18
                'Block processing of EDIFACT invoices when a duplicate invoice number is detected for the same supplier. When enabled, duplicate invoices will be rejected and logged as errors.',
19
                'YesNo'
20
            )
21
        }
22
        );
23
        say $out "Added system preference 'EdiBlockDuplicateInvoice'";
24
25
        # Email notification toggle
26
        $dbh->do(
27
            q{
28
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
29
            VALUES (
30
                'EdiBlockDuplicateInvoiceEmailNotice',
31
                '0',
32
                NULL,
33
                'Send email notification when duplicate EDIFACT invoices are detected. Requires EdiBlockDuplicateInvoice to be enabled.',
34
                'YesNo'
35
            )
36
        }
37
        );
38
        say $out "Added system preference 'EdiBlockDuplicateInvoiceEmailNotice'";
39
40
        # Email recipient list
41
        $dbh->do(
42
            q{
43
            INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
44
            VALUES (
45
                'EdiBlockDuplicateInvoiceEmailAddresses',
46
                '',
47
                NULL,
48
                'Comma-separated list of email addresses to notify when duplicate EDIFACT invoices are detected (e.g., "purchasing@library.org,edi_support@library.org"). Requires EdiBlockDuplicateInvoiceEmailNotice to be enabled.',
49
                'Textarea'
50
            )
51
        }
52
        );
53
        say $out "Added system preference 'EdiBlockDuplicateInvoiceEmailAddresses'";
54
55
        # Add database index for performance
56
        my $index_exists = $dbh->selectrow_array(
57
            q{
58
            SELECT COUNT(*)
59
            FROM information_schema.statistics
60
            WHERE table_schema = DATABASE()
61
            AND table_name = 'aqinvoices'
62
            AND index_name = 'idx_invoicenumber_booksellerid'
63
        }
64
        );
65
66
        unless ($index_exists) {
67
            $dbh->do(
68
                q{
69
                CREATE INDEX idx_invoicenumber_booksellerid
70
                ON aqinvoices (invoicenumber(100), booksellerid)
71
            }
72
            );
73
            say $out "Added index idx_invoicenumber_booksellerid to aqinvoices table";
74
        }
75
76
        # Add edi_error_notification column to aqcontacts
77
        unless ( column_exists( 'aqcontacts', 'edi_error_notification' ) ) {
78
            $dbh->do(
79
                q{
80
                ALTER TABLE aqcontacts
81
                ADD COLUMN edi_error_notification TINYINT(1) NOT NULL DEFAULT 0
82
                AFTER serialsprimary
83
            }
84
            );
85
            say $out "Added edi_error_notification column to aqcontacts table";
86
        }
87
88
        # Delete any truncated templates first
89
        $dbh->do(q{DELETE FROM letter WHERE code = 'EDI_DUPLICATE_INVOIC' AND module = 'acquisition'});
90
91
        # Add notice templates for duplicate invoice notifications
92
        $dbh->do(
93
            q{
94
            INSERT IGNORE INTO letter (module, code, branchcode, name, is_html, title, content, message_transport_type, lang)
95
            VALUES (
96
                'acquisition',
97
                'EDI_DUP_INV_LIBRARY',
98
                '',
99
                'EDIFACT duplicate invoice detected - library notification',
100
                0,
101
                'EDIFACT Duplicate Invoice Blocked - [% invoicenumber | html %]',
102
                'Duplicate EDIFACT Invoice Detected and Blocked
103
104
Invoice Number: [% invoicenumber | html %]
105
Vendor: [% aqbooksellers.name | html %] (ID: [% vendor_id | html %])
106
EDI Message File: [% filename | html %]
107
Original Invoice ID: [% original_invoiceid | html %]
108
Original Invoice Date: [% original_shipmentdate | html %]
109
110
Status: Processing has been blocked. The invoice was NOT created in Koha.
111
112
Action Required:
113
The supplier must resend this invoice with a unique invoice number.
114
115
View EDI Message: [% OPACBaseURL | uri %]/cgi-bin/koha/acqui/edimsg.pl?id=[% message_id | uri %]
116
View Original Invoice: [% OPACBaseURL | uri %]/cgi-bin/koha/acqui/invoice.pl?invoiceid=[% original_invoiceid | uri %]
117
118
This is an automated notification from your Koha system.',
119
                'email',
120
                'default'
121
            )
122
        }
123
        );
124
        say $out "Added letter template 'EDI_DUP_INV_LIBRARY'";
125
126
        $dbh->do(
127
            q{
128
            INSERT IGNORE INTO letter (module, code, branchcode, name, is_html, title, content, message_transport_type, lang)
129
            VALUES (
130
                'acquisition',
131
                'EDI_DUP_INV_VENDOR',
132
                '',
133
                'EDIFACT duplicate invoice detected - vendor notification',
134
                0,
135
                'Duplicate Invoice Number - Action Required - [% invoicenumber | html %]',
136
                'Dear Supplier,
137
138
We have received an EDIFACT invoice message from your system with a duplicate invoice number.
139
140
Invoice Number: [% invoicenumber | html %]
141
Your Reference (SAN): [% vendor_san | html %]
142
EDI Message File: [% filename | html %]
143
Received Date: [% received_date | html %]
144
145
Issue:
146
This invoice number has already been processed in our system (original invoice ID: [% original_invoiceid | html %], date: [% original_shipmentdate | html %]).
147
148
Action Required:
149
Please resend this invoice using a UNIQUE invoice number. Duplicate invoice numbers cannot be processed by our system.
150
151
If you believe this is in error, please contact our acquisitions department.
152
153
Library: [% aqbooksellers.name | html %]
154
155
This is an automated notification. Please do not reply to this email.',
156
                'email',
157
                'default'
158
            )
159
        }
160
        );
161
        say $out "Added letter template 'EDI_DUP_INV_VENDOR'";
162
    },
163
};
(-)a/installer/data/mysql/en/mandatory/sample_notices.yml (+57 lines)
Lines 43-48 tables: Link Here
43
            - ""
43
            - ""
44
            - "Your library."
44
            - "Your library."
45
45
46
        - module: acquisition
47
          code: EDI_DUP_INV_LIBRARY
48
          branchcode: ""
49
          name: "EDIFACT duplicate invoice detected - library notification"
50
          is_html: 0
51
          title: "EDIFACT Duplicate Invoice Blocked - [% invoicenumber %]"
52
          message_transport_type: email
53
          lang: default
54
          content:
55
            - "Duplicate EDIFACT Invoice Detected and Blocked"
56
            - ""
57
            - "Invoice Number: [% invoicenumber %]"
58
            - "Vendor: [% aqbooksellers.name %] (ID: [% vendor_id %])"
59
            - "EDI Message File: [% filename %]"
60
            - "Original Invoice ID: [% original_invoiceid %]"
61
            - "Original Invoice Date: [% original_shipmentdate %]"
62
            - ""
63
            - "Status: Processing has been blocked. The invoice was NOT created in Koha."
64
            - ""
65
            - "Action Required:"
66
            - "The supplier must resend this invoice with a unique invoice number."
67
            - ""
68
            - "View EDI Message: [% OPACBaseURL %]/cgi-bin/koha/acqui/edimsg.pl?id=[% message_id %]"
69
            - "View Original Invoice: [% OPACBaseURL %]/cgi-bin/koha/acqui/invoice.pl?invoiceid=[% original_invoiceid %]"
70
            - ""
71
            - "This is an automated notification from your Koha system."
72
73
        - module: acquisition
74
          code: EDI_DUP_INV_VENDOR
75
          branchcode: ""
76
          name: "EDIFACT duplicate invoice detected - vendor notification"
77
          is_html: 0
78
          title: "Duplicate Invoice Number - Action Required - [% invoicenumber %]"
79
          message_transport_type: email
80
          lang: default
81
          content:
82
            - "Dear Supplier,"
83
            - ""
84
            - "We have received an EDIFACT invoice message from your system with a duplicate invoice number."
85
            - ""
86
            - "Invoice Number: [% invoicenumber %]"
87
            - "Your Reference (SAN): [% vendor_san %]"
88
            - "EDI Message File: [% filename %]"
89
            - "Received Date: [% received_date %]"
90
            - ""
91
            - "Issue:"
92
            - "This invoice number has already been processed in our system (original invoice ID: [% original_invoiceid %], date: [% original_shipmentdate %])."
93
            - ""
94
            - "Action Required:"
95
            - "Please resend this invoice using a UNIQUE invoice number. Duplicate invoice numbers cannot be processed by our system."
96
            - ""
97
            - "If you believe this is in error, please contact our acquisitions department."
98
            - ""
99
            - "Library: [% aqbooksellers.name %]"
100
            - ""
101
            - "This is an automated notification. Please do not reply to this email."
102
46
        - module: bookings
103
        - module: bookings
47
          code: BOOKING_CANCELLATION
104
          code: BOOKING_CANCELLATION
48
          branchcode: ""
105
          branchcode: ""
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref (+17 lines)
Lines 186-188 Acquisitions: Link Here
186
                location: "location"
186
                location: "location"
187
                ccode: "collection"
187
                ccode: "collection"
188
            - " in items."
188
            - " in items."
189
        -
190
            - pref: EdiBlockDuplicateInvoice
191
              choices:
192
                  1: Block
193
                  0: "Don't block"
194
            - processing of EDIFACT invoices when a duplicate invoice number is detected for the same supplier.
195
        -
196
            - pref: EdiBlockDuplicateInvoiceEmailNotice
197
              choices:
198
                  1: Send
199
                  0: "Don't send"
200
            - email notifications when duplicate EDIFACT invoices are detected.
201
        -
202
            - "Send duplicate invoice notifications to these email addresses (comma-separated):"
203
            - pref: EdiBlockDuplicateInvoiceEmailAddresses
204
              type: textarea
205
              class: code
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Vendors/VendorContacts.vue (+11 lines)
Lines 44-49 Link Here
44
                                {{ $__("Contact about late orders") }}
44
                                {{ $__("Contact about late orders") }}
45
                            </label>
45
                            </label>
46
                        </li>
46
                        </li>
47
                        <li>
48
                            <label>
49
                                <input
50
                                    type="checkbox"
51
                                    :id="`contact_edi_error_notification_${index}`"
52
                                    class="contact_edi_error_notification"
53
                                    v-model="contact.edi_error_notification"
54
                                />
55
                                {{ $__("Contact about EDI errors") }}
56
                            </label>
57
                        </li>
47
                    </ol>
58
                    </ol>
48
                </fieldset>
59
                </fieldset>
49
            </div>
60
            </div>
(-)a/t/db_dependent/Koha/EDI.t (-2 / +1010 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 => 6;
24
use Test::More tests => 7;
25
use Test::MockModule;
25
use Test::MockModule;
26
26
27
use t::lib::Mocks;
27
use t::lib::Mocks;
Lines 1616-1618 subtest 'create_edi_order_logging' => sub { Link Here
1616
1616
1617
    $schema->storage->txn_rollback;
1617
    $schema->storage->txn_rollback;
1618
};
1618
};
1619
- 
1619
1620
subtest 'duplicate_invoice_blocking' => sub {
1621
    plan tests => 7;
1622
1623
    $schema->storage->txn_begin;
1624
1625
    # Get dirname for transport
1626
    my $dirname = ( $Bin =~ /^(.*\/t\/)/ ? $1 . 'edi_testfiles/' : q{} );
1627
1628
    # Test 1: Backward compatibility - duplicate detection disabled
1629
    subtest 'duplicate_detection_disabled' => sub {
1630
        plan tests => 4;
1631
1632
        $schema->storage->txn_begin;
1633
1634
        # Disable duplicate blocking preference
1635
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice', 0 );
1636
1637
        # Create file transport for local testing
1638
        my $file_transport = $builder->build(
1639
            {
1640
                source => 'FileTransport',
1641
                value  => {
1642
                    name               => 'Test Invoice Transport',
1643
                    transport          => 'local',
1644
                    download_directory => $dirname,
1645
                    upload_directory   => $dirname,
1646
                }
1647
            }
1648
        );
1649
1650
        # Create vendor EDI account
1651
        my $account = $builder->build(
1652
            {
1653
                source => 'VendorEdiAccount',
1654
                value  => {
1655
                    description       => 'test vendor',
1656
                    file_transport_id => $file_transport->{file_transport_id},
1657
                    plugin            => '',
1658
                    san               => '5013546027173',
1659
                }
1660
            }
1661
        );
1662
1663
        # Create test invoice that already exists
1664
        my $existing_invoice = $builder->build(
1665
            {
1666
                source => 'Aqinvoice',
1667
                value  => {
1668
                    invoicenumber => 'INV00003',
1669
                    booksellerid  => $account->{vendor_id},
1670
                    shipmentdate  => '2020-01-01',
1671
                }
1672
            }
1673
        );
1674
1675
        # Create test basket and order
1676
        my $basket = $builder->build_object(
1677
            {
1678
                class => 'Koha::Acquisition::Baskets',
1679
                value => {
1680
                    booksellerid => $account->{vendor_id},
1681
                    basketname   => 'Test Basket',
1682
                }
1683
            }
1684
        );
1685
        my $order = $builder->build_object(
1686
            {
1687
                class => 'Koha::Acquisition::Orders',
1688
                value => {
1689
                    basketno     => $basket->id,
1690
                    orderstatus  => 'new',
1691
                    biblionumber => undef,
1692
                }
1693
            }
1694
        );
1695
        my $ordernumber = $order->ordernumber;
1696
1697
        # Prepare invoice message
1698
        my $filename = 'INVOICE.CEI';
1699
        ok( -e $dirname . $filename, 'File INVOICE.CEI found' );
1700
1701
        my $trans = Koha::Edifact::Transport->new( $account->{id} );
1702
        $trans->working_directory($dirname);
1703
1704
        my $mhash = $trans->message_hash();
1705
        $mhash->{message_type} = 'INVOICE';
1706
        $trans->ingest( $mhash, $filename );
1707
1708
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
1709
        my $raw_msg         = $invoice_message->raw_msg;
1710
        $raw_msg =~ s/ORDERNUMBER1/$ordernumber/g;
1711
        $raw_msg =~ s/ORDERNUMBER2/$ordernumber/g;
1712
        $invoice_message->update( { raw_msg => $raw_msg } );
1713
1714
        # Clear logger
1715
        $logger->clear();
1716
1717
        # Process the invoice - should succeed despite duplicate
1718
        my $error;
1719
        eval {
1720
            process_invoice($invoice_message);
1721
            1;
1722
        } or do {
1723
            $error = $@;
1724
        };
1725
        ok( !$error, 'Invoice processing completed without dying when preference disabled' );
1726
1727
        # Verify duplicate was NOT blocked (second invoice created)
1728
        my $duplicate_invoices = $schema->resultset('Aqinvoice')->search(
1729
            {
1730
                invoicenumber => 'INV00003',
1731
                booksellerid  => $account->{vendor_id},
1732
            }
1733
        );
1734
        is( $duplicate_invoices->count, 2, 'Duplicate invoice was allowed when preference disabled' );
1735
1736
        # Verify no duplicate error was logged
1737
        my $errors          = $invoice_message->edifact_errors;
1738
        my $duplicate_error = $errors->search( { details => { 'like', '%Duplicate invoice%' } } )->count;
1739
        is( $duplicate_error, 0, 'No duplicate error logged when preference disabled' );
1740
1741
        $logger->clear();
1742
        $schema->storage->txn_rollback;
1743
    };
1744
1745
    # Test 2: Duplicate blocking enabled
1746
    subtest 'duplicate_blocking_enabled' => sub {
1747
        plan tests => 6;
1748
1749
        $schema->storage->txn_begin;
1750
1751
        # Enable duplicate blocking preference
1752
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice',            1 );
1753
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailNotice', 0 );
1754
1755
        # Create file transport for local testing
1756
        my $file_transport = $builder->build(
1757
            {
1758
                source => 'FileTransport',
1759
                value  => {
1760
                    name               => 'Test Invoice Transport',
1761
                    transport          => 'local',
1762
                    download_directory => $dirname,
1763
                    upload_directory   => $dirname,
1764
                }
1765
            }
1766
        );
1767
1768
        # Create vendor EDI account
1769
        my $account = $builder->build(
1770
            {
1771
                source => 'VendorEdiAccount',
1772
                value  => {
1773
                    description       => 'test vendor',
1774
                    file_transport_id => $file_transport->{file_transport_id},
1775
                    plugin            => '',
1776
                    san               => '5013546027173',
1777
                }
1778
            }
1779
        );
1780
1781
        # Create test invoice that already exists
1782
        my $existing_invoice = $builder->build(
1783
            {
1784
                source => 'Aqinvoice',
1785
                value  => {
1786
                    invoicenumber => 'INV00003',
1787
                    booksellerid  => $account->{vendor_id},
1788
                    shipmentdate  => '2020-01-01',
1789
                }
1790
            }
1791
        );
1792
1793
        # Create test basket and order
1794
        my $basket = $builder->build_object(
1795
            {
1796
                class => 'Koha::Acquisition::Baskets',
1797
                value => {
1798
                    booksellerid => $account->{vendor_id},
1799
                    basketname   => 'Test Basket',
1800
                }
1801
            }
1802
        );
1803
        my $order = $builder->build_object(
1804
            {
1805
                class => 'Koha::Acquisition::Orders',
1806
                value => {
1807
                    basketno     => $basket->id,
1808
                    orderstatus  => 'new',
1809
                    biblionumber => undef,
1810
                }
1811
            }
1812
        );
1813
        my $ordernumber = $order->ordernumber;
1814
1815
        # Prepare invoice message
1816
        my $filename = 'INVOICE.CEI';
1817
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
1818
        $trans->working_directory($dirname);
1819
1820
        my $mhash = $trans->message_hash();
1821
        $mhash->{message_type} = 'INVOICE';
1822
        $trans->ingest( $mhash, $filename );
1823
1824
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
1825
        my $raw_msg         = $invoice_message->raw_msg;
1826
        $raw_msg =~ s/ORDERNUMBER1/$ordernumber/g;
1827
        $raw_msg =~ s/ORDERNUMBER2/$ordernumber/g;
1828
        $invoice_message->update( { raw_msg => $raw_msg } );
1829
1830
        # Clear logger
1831
        $logger->clear();
1832
1833
        # Process the invoice - should block duplicate
1834
        my $error;
1835
        eval {
1836
            process_invoice($invoice_message);
1837
            1;
1838
        } or do {
1839
            $error = $@;
1840
        };
1841
        ok( !$error, 'Invoice processing completed without dying' );
1842
1843
        # Verify duplicate was blocked (only original invoice exists)
1844
        my $duplicate_invoices = $schema->resultset('Aqinvoice')->search(
1845
            {
1846
                invoicenumber => 'INV00003',
1847
                booksellerid  => $account->{vendor_id},
1848
            }
1849
        );
1850
        is( $duplicate_invoices->count, 1, 'Duplicate invoice was blocked' );
1851
1852
        # Verify error was logged
1853
        $logger->error_like(
1854
            qr/Duplicate invoice INV00003 for vendor.*/,
1855
            'Error logged for duplicate invoice'
1856
        );
1857
1858
        # Verify error recorded in edifact_errors table
1859
        my $errors          = $invoice_message->edifact_errors;
1860
        my $duplicate_error = $errors->search( { details => { 'like', '%Duplicate invoice%' } } )->first;
1861
        ok( $duplicate_error, 'Duplicate error recorded in edifact_errors table' );
1862
        like(
1863
            $duplicate_error->details, qr/Duplicate invoice number 'INV00003'/,
1864
            'Error details contain invoice number'
1865
        );
1866
1867
        # Verify message status set to error
1868
        $invoice_message->discard_changes;
1869
        is( $invoice_message->status, 'error', 'Message status set to error' );
1870
1871
        $logger->clear();
1872
        $schema->storage->txn_rollback;
1873
    };
1874
1875
    # Test 3: Same invoice number with different vendor should be allowed
1876
    subtest 'different_vendor_allowed' => sub {
1877
        plan tests => 3;
1878
1879
        $schema->storage->txn_begin;
1880
1881
        # Enable duplicate blocking preference
1882
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice', 1 );
1883
1884
        # Create file transport for local testing
1885
        my $file_transport = $builder->build(
1886
            {
1887
                source => 'FileTransport',
1888
                value  => {
1889
                    name               => 'Test Invoice Transport',
1890
                    transport          => 'local',
1891
                    download_directory => $dirname,
1892
                    upload_directory   => $dirname,
1893
                }
1894
            }
1895
        );
1896
1897
        # Create two different vendors
1898
        my $account1 = $builder->build(
1899
            {
1900
                source => 'VendorEdiAccount',
1901
                value  => {
1902
                    description       => 'test vendor 1',
1903
                    file_transport_id => $file_transport->{file_transport_id},
1904
                    plugin            => '',
1905
                    san               => '5013546027173',
1906
                }
1907
            }
1908
        );
1909
1910
        my $account2 = $builder->build(
1911
            {
1912
                source => 'VendorEdiAccount',
1913
                value  => {
1914
                    description       => 'test vendor 2',
1915
                    file_transport_id => $file_transport->{file_transport_id},
1916
                    plugin            => '',
1917
                    san               => '5013546027999',
1918
                }
1919
            }
1920
        );
1921
1922
        # Create invoice for vendor 1
1923
        my $invoice1 = $builder->build(
1924
            {
1925
                source => 'Aqinvoice',
1926
                value  => {
1927
                    invoicenumber => 'INV00003',
1928
                    booksellerid  => $account1->{vendor_id},
1929
                }
1930
            }
1931
        );
1932
1933
        # Create test basket and order for vendor 2
1934
        my $basket = $builder->build_object(
1935
            {
1936
                class => 'Koha::Acquisition::Baskets',
1937
                value => {
1938
                    booksellerid => $account2->{vendor_id},
1939
                    basketname   => 'Test Basket',
1940
                }
1941
            }
1942
        );
1943
        my $order = $builder->build_object(
1944
            {
1945
                class => 'Koha::Acquisition::Orders',
1946
                value => {
1947
                    basketno     => $basket->id,
1948
                    orderstatus  => 'new',
1949
                    biblionumber => undef,
1950
                }
1951
            }
1952
        );
1953
1954
        # Prepare invoice message for vendor 2 using same file
1955
        my $filename = 'INVOICE.CEI';
1956
        my $trans    = Koha::Edifact::Transport->new( $account2->{id} );
1957
        $trans->working_directory($dirname);
1958
1959
        my $mhash = $trans->message_hash();
1960
        $mhash->{message_type} = 'INVOICE';
1961
        $trans->ingest( $mhash, $filename );
1962
1963
        my $invoice_message = $schema->resultset('EdifactMessage')->search(
1964
            { filename => $filename },
1965
            { order_by => { -desc => 'id' }, rows => 1 }
1966
        )->single;
1967
        my $raw_msg = $invoice_message->raw_msg;
1968
        $raw_msg =~ s/ORDERNUMBER1/$order->ordernumber/g;
1969
        $raw_msg =~ s/ORDERNUMBER2/$order->ordernumber/g;
1970
        $raw_msg =~ s/5013546027173/$account2->{san}/g;     # Replace vendor SAN
1971
        $invoice_message->update(
1972
            {
1973
                raw_msg   => $raw_msg,
1974
                vendor_id => $account2->{vendor_id},
1975
                edi_acct  => $account2->{id}
1976
            }
1977
        );
1978
1979
        # Clear logger
1980
        $logger->clear();
1981
1982
        # Process the invoice - should succeed (different vendor)
1983
        my $error;
1984
        eval {
1985
            process_invoice($invoice_message);
1986
            1;
1987
        } or do {
1988
            $error = $@;
1989
        };
1990
        ok( !$error, 'Invoice processing completed without dying' );
1991
1992
        # Verify both invoices exist (one per vendor)
1993
        my $invoices_vendor1 = $schema->resultset('Aqinvoice')->search(
1994
            {
1995
                invoicenumber => 'INV00003',
1996
                booksellerid  => $account1->{vendor_id},
1997
            }
1998
        );
1999
        is( $invoices_vendor1->count, 1, 'Invoice exists for vendor 1' );
2000
2001
        my $invoices_vendor2 = $schema->resultset('Aqinvoice')->search(
2002
            {
2003
                invoicenumber => 'INV00003',
2004
                booksellerid  => $account2->{vendor_id},
2005
            }
2006
        );
2007
        is( $invoices_vendor2->count, 1, 'Invoice allowed for vendor 2 with same invoice number' );
2008
2009
        $logger->clear();
2010
        $schema->storage->txn_rollback;
2011
    };
2012
2013
    # Test 4: Library staff email notification
2014
    subtest 'library_email_notification' => sub {
2015
        plan tests => 5;
2016
2017
        $schema->storage->txn_begin;
2018
2019
        # Enable duplicate blocking and email notifications
2020
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice',               1 );
2021
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailNotice',    1 );
2022
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailAddresses', 'library@example.com' );
2023
2024
        # Create letter templates for notifications (delete first if exist)
2025
        $schema->resultset('Letter')->search(
2026
            {
2027
                module => 'acquisition',
2028
                code   => [ 'EDI_DUP_INV_LIBRARY', 'EDI_DUP_INV_VENDOR' ],
2029
            }
2030
        )->delete;
2031
2032
        $builder->build(
2033
            {
2034
                source => 'Letter',
2035
                value  => {
2036
                    module                 => 'acquisition',
2037
                    code                   => 'EDI_DUP_INV_LIBRARY',
2038
                    branchcode             => '',
2039
                    name                   => 'Test library notification',
2040
                    is_html                => 0,
2041
                    title                  => 'Duplicate Invoice - <<invoicenumber>>',
2042
                    content                => 'Duplicate invoice <<invoicenumber>>. Processing has been blocked.',
2043
                    message_transport_type => 'email',
2044
                    lang                   => 'default',
2045
                }
2046
            }
2047
        );
2048
        $builder->build(
2049
            {
2050
                source => 'Letter',
2051
                value  => {
2052
                    module                 => 'acquisition',
2053
                    code                   => 'EDI_DUP_INV_VENDOR',
2054
                    branchcode             => '',
2055
                    name                   => 'Test vendor notification',
2056
                    is_html                => 0,
2057
                    title                  => 'Duplicate Invoice - <<invoicenumber>>',
2058
                    content                => 'Duplicate invoice <<invoicenumber>>. Please use UNIQUE invoice number.',
2059
                    message_transport_type => 'email',
2060
                    lang                   => 'default',
2061
                }
2062
            }
2063
        );
2064
2065
        # Create file transport for local testing
2066
        my $file_transport = $builder->build(
2067
            {
2068
                source => 'FileTransport',
2069
                value  => {
2070
                    name               => 'Test Invoice Transport',
2071
                    transport          => 'local',
2072
                    download_directory => $dirname,
2073
                    upload_directory   => $dirname,
2074
                }
2075
            }
2076
        );
2077
2078
        # Create vendor EDI account
2079
        my $account = $builder->build(
2080
            {
2081
                source => 'VendorEdiAccount',
2082
                value  => {
2083
                    description       => 'test vendor',
2084
                    file_transport_id => $file_transport->{file_transport_id},
2085
                    plugin            => '',
2086
                    san               => '5013546027173',
2087
                }
2088
            }
2089
        );
2090
2091
        # Create test invoice that already exists
2092
        my $existing_invoice = $builder->build(
2093
            {
2094
                source => 'Aqinvoice',
2095
                value  => {
2096
                    invoicenumber => 'INV00003',
2097
                    booksellerid  => $account->{vendor_id},
2098
                    shipmentdate  => '2020-01-01',
2099
                }
2100
            }
2101
        );
2102
2103
        # Create test basket and order
2104
        my $basket = $builder->build_object(
2105
            {
2106
                class => 'Koha::Acquisition::Baskets',
2107
                value => {
2108
                    booksellerid => $account->{vendor_id},
2109
                    basketname   => 'Test Basket',
2110
                }
2111
            }
2112
        );
2113
        my $order = $builder->build_object(
2114
            {
2115
                class => 'Koha::Acquisition::Orders',
2116
                value => {
2117
                    basketno     => $basket->id,
2118
                    orderstatus  => 'new',
2119
                    biblionumber => undef,
2120
                }
2121
            }
2122
        );
2123
2124
        # Prepare invoice message
2125
        my $filename = 'INVOICE.CEI';
2126
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
2127
        $trans->working_directory($dirname);
2128
2129
        my $mhash = $trans->message_hash();
2130
        $mhash->{message_type} = 'INVOICE';
2131
        $trans->ingest( $mhash, $filename );
2132
2133
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
2134
        my $raw_msg         = $invoice_message->raw_msg;
2135
        $raw_msg =~ s/ORDERNUMBER1/$order->ordernumber/g;
2136
        $raw_msg =~ s/ORDERNUMBER2/$order->ordernumber/g;
2137
        $invoice_message->update( { raw_msg => $raw_msg } );
2138
2139
        # Process the invoice
2140
        process_invoice($invoice_message);
2141
2142
        # Verify library email was queued in message_queue
2143
        my $library_messages = $schema->resultset('MessageQueue')->search(
2144
            {
2145
                to_address => 'library@example.com',
2146
                status     => [ 'sent', 'pending', 'failed' ],    # Accept any status - queuing is what matters
2147
            }
2148
        );
2149
        is( $library_messages->count, 1, 'Library notification was queued' );
2150
2151
        my $library_message = $library_messages->next;
2152
        like( $library_message->subject, qr/Duplicate Invoice/,           'Library email has correct subject' );
2153
        like( $library_message->content, qr/INV00003/,                    'Library email contains invoice number' );
2154
        like( $library_message->content, qr/Processing has been blocked/, 'Library email contains blocking message' );
2155
2156
        # Verify message was recorded in message_queue for audit trail
2157
        ok( $library_message->message_id, 'Message has ID for audit trail' );
2158
2159
        $schema->storage->txn_rollback;
2160
    };
2161
2162
    # Test 5: Vendor email notification
2163
    subtest 'vendor_email_notification' => sub {
2164
        plan tests => 5;
2165
2166
        $schema->storage->txn_begin;
2167
2168
        # Enable duplicate blocking and email notifications
2169
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice',               1 );
2170
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailNotice',    1 );
2171
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailAddresses', 'library@example.com' );
2172
2173
        # Create letter templates for notifications (delete first if exist)
2174
        $schema->resultset('Letter')->search(
2175
            {
2176
                module => 'acquisition',
2177
                code   => [ 'EDI_DUP_INV_LIBRARY', 'EDI_DUP_INV_VENDOR' ],
2178
            }
2179
        )->delete;
2180
2181
        $builder->build(
2182
            {
2183
                source => 'Letter',
2184
                value  => {
2185
                    module                 => 'acquisition',
2186
                    code                   => 'EDI_DUP_INV_LIBRARY',
2187
                    branchcode             => '',
2188
                    name                   => 'Test library notification',
2189
                    is_html                => 0,
2190
                    title                  => 'Duplicate Invoice - <<invoicenumber>>',
2191
                    content                => 'Duplicate invoice <<invoicenumber>>. Processing has been blocked.',
2192
                    message_transport_type => 'email',
2193
                    lang                   => 'default',
2194
                }
2195
            }
2196
        );
2197
        $builder->build(
2198
            {
2199
                source => 'Letter',
2200
                value  => {
2201
                    module                 => 'acquisition',
2202
                    code                   => 'EDI_DUP_INV_VENDOR',
2203
                    branchcode             => '',
2204
                    name                   => 'Test vendor notification',
2205
                    is_html                => 0,
2206
                    title                  => 'Duplicate Invoice - <<invoicenumber>>',
2207
                    content                => 'Duplicate invoice <<invoicenumber>>. Please use UNIQUE invoice number.',
2208
                    message_transport_type => 'email',
2209
                    lang                   => 'default',
2210
                }
2211
            }
2212
        );
2213
2214
        # Create file transport for local testing
2215
        my $file_transport = $builder->build(
2216
            {
2217
                source => 'FileTransport',
2218
                value  => {
2219
                    name               => 'Test Invoice Transport',
2220
                    transport          => 'local',
2221
                    download_directory => $dirname,
2222
                    upload_directory   => $dirname,
2223
                }
2224
            }
2225
        );
2226
2227
        # Create vendor EDI account
2228
        my $account = $builder->build(
2229
            {
2230
                source => 'VendorEdiAccount',
2231
                value  => {
2232
                    description       => 'test vendor',
2233
                    file_transport_id => $file_transport->{file_transport_id},
2234
                    plugin            => '',
2235
                    san               => '5013546027173',
2236
                }
2237
            }
2238
        );
2239
2240
        # Create vendor contact with EDI error notification enabled
2241
        my $vendor_contact = $builder->build(
2242
            {
2243
                source => 'Aqcontact',
2244
                value  => {
2245
                    name                   => 'Test Vendor Contact',
2246
                    email                  => 'vendor@supplier.com',
2247
                    booksellerid           => $account->{vendor_id},
2248
                    edi_error_notification => 1,
2249
                }
2250
            }
2251
        );
2252
2253
        # Create test invoice that already exists
2254
        my $existing_invoice = $builder->build(
2255
            {
2256
                source => 'Aqinvoice',
2257
                value  => {
2258
                    invoicenumber => 'INV00003',
2259
                    booksellerid  => $account->{vendor_id},
2260
                    shipmentdate  => '2020-01-01',
2261
                }
2262
            }
2263
        );
2264
2265
        # Create test basket and order
2266
        my $basket = $builder->build_object(
2267
            {
2268
                class => 'Koha::Acquisition::Baskets',
2269
                value => {
2270
                    booksellerid => $account->{vendor_id},
2271
                    basketname   => 'Test Basket',
2272
                }
2273
            }
2274
        );
2275
        my $order = $builder->build_object(
2276
            {
2277
                class => 'Koha::Acquisition::Orders',
2278
                value => {
2279
                    basketno     => $basket->id,
2280
                    orderstatus  => 'new',
2281
                    biblionumber => undef,
2282
                }
2283
            }
2284
        );
2285
2286
        # Prepare invoice message
2287
        my $filename = 'INVOICE.CEI';
2288
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
2289
        $trans->working_directory($dirname);
2290
2291
        my $mhash = $trans->message_hash();
2292
        $mhash->{message_type} = 'INVOICE';
2293
        $trans->ingest( $mhash, $filename );
2294
2295
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
2296
        my $raw_msg         = $invoice_message->raw_msg;
2297
        $raw_msg =~ s/ORDERNUMBER1/$order->ordernumber/g;
2298
        $raw_msg =~ s/ORDERNUMBER2/$order->ordernumber/g;
2299
        $invoice_message->update( { raw_msg => $raw_msg } );
2300
2301
        # Process the invoice
2302
        process_invoice($invoice_message);
2303
2304
        # Verify vendor email was queued in message_queue
2305
        my $vendor_messages = $schema->resultset('MessageQueue')->search(
2306
            {
2307
                to_address => 'vendor@supplier.com',
2308
                status     => [ 'sent', 'pending', 'failed' ],    # Accept any status - queuing is what matters
2309
            }
2310
        );
2311
        is( $vendor_messages->count, 1, 'Vendor notification was queued' );
2312
2313
        my $vendor_message = $vendor_messages->next;
2314
        like( $vendor_message->subject, qr/Duplicate Invoice/,     'Vendor email has correct subject' );
2315
        like( $vendor_message->content, qr/INV00003/,              'Vendor email contains invoice number' );
2316
        like( $vendor_message->content, qr/UNIQUE invoice number/, 'Vendor email contains action required message' );
2317
2318
        # Verify message was recorded for audit trail
2319
        ok( $vendor_message->message_id, 'Message has ID for audit trail' );
2320
2321
        $schema->storage->txn_rollback;
2322
    };
2323
2324
    # Test 6: Multiple email recipients
2325
    subtest 'multiple_email_recipients' => sub {
2326
        plan tests => 3;
2327
2328
        $schema->storage->txn_begin;
2329
2330
        # Enable duplicate blocking and email notifications with multiple addresses
2331
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice',            1 );
2332
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailNotice', 1 );
2333
        t::lib::Mocks::mock_preference(
2334
            'EdiBlockDuplicateInvoiceEmailAddresses',
2335
            'library1@example.com, library2@example.com, library3@example.com'
2336
        );
2337
2338
        # Create letter templates (delete first if exist)
2339
        $schema->resultset('Letter')->search(
2340
            {
2341
                module => 'acquisition',
2342
                code   => 'EDI_DUP_INV_LIBRARY',
2343
            }
2344
        )->delete;
2345
2346
        $builder->build(
2347
            {
2348
                source => 'Letter',
2349
                value  => {
2350
                    module                 => 'acquisition',
2351
                    code                   => 'EDI_DUP_INV_LIBRARY',
2352
                    branchcode             => '',
2353
                    name                   => 'Test library notification',
2354
                    is_html                => 0,
2355
                    title                  => 'Duplicate Invoice',
2356
                    content                => 'Duplicate invoice.',
2357
                    message_transport_type => 'email',
2358
                    lang                   => 'default',
2359
                }
2360
            }
2361
        );
2362
2363
        # Create file transport for local testing
2364
        my $file_transport = $builder->build(
2365
            {
2366
                source => 'FileTransport',
2367
                value  => {
2368
                    name               => 'Test Invoice Transport',
2369
                    transport          => 'local',
2370
                    download_directory => $dirname,
2371
                    upload_directory   => $dirname,
2372
                }
2373
            }
2374
        );
2375
2376
        # Create vendor EDI account
2377
        my $account = $builder->build(
2378
            {
2379
                source => 'VendorEdiAccount',
2380
                value  => {
2381
                    description       => 'test vendor',
2382
                    file_transport_id => $file_transport->{file_transport_id},
2383
                    plugin            => '',
2384
                    san               => '5013546027173',
2385
                }
2386
            }
2387
        );
2388
2389
        # Create test invoice that already exists
2390
        my $existing_invoice = $builder->build(
2391
            {
2392
                source => 'Aqinvoice',
2393
                value  => {
2394
                    invoicenumber => 'INV00003',
2395
                    booksellerid  => $account->{vendor_id},
2396
                }
2397
            }
2398
        );
2399
2400
        # Create test basket and order
2401
        my $basket = $builder->build_object(
2402
            {
2403
                class => 'Koha::Acquisition::Baskets',
2404
                value => {
2405
                    booksellerid => $account->{vendor_id},
2406
                    basketname   => 'Test Basket',
2407
                }
2408
            }
2409
        );
2410
        my $order = $builder->build_object(
2411
            {
2412
                class => 'Koha::Acquisition::Orders',
2413
                value => {
2414
                    basketno     => $basket->id,
2415
                    orderstatus  => 'new',
2416
                    biblionumber => undef,
2417
                }
2418
            }
2419
        );
2420
2421
        # Prepare invoice message
2422
        my $filename = 'INVOICE.CEI';
2423
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
2424
        $trans->working_directory($dirname);
2425
2426
        my $mhash = $trans->message_hash();
2427
        $mhash->{message_type} = 'INVOICE';
2428
        $trans->ingest( $mhash, $filename );
2429
2430
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
2431
        my $raw_msg         = $invoice_message->raw_msg;
2432
        $raw_msg =~ s/ORDERNUMBER1/$order->ordernumber/g;
2433
        $raw_msg =~ s/ORDERNUMBER2/$order->ordernumber/g;
2434
        $invoice_message->update( { raw_msg => $raw_msg } );
2435
2436
        # Process the invoice
2437
        process_invoice($invoice_message);
2438
2439
        # Verify all three library emails were queued
2440
        my $library_messages = $schema->resultset('MessageQueue')->search(
2441
            {
2442
                to_address => [ 'library1@example.com', 'library2@example.com', 'library3@example.com' ],
2443
                status     => [ 'sent', 'pending', 'failed' ],    # Accept any status - queuing is what matters
2444
            }
2445
        );
2446
        is( $library_messages->count, 3, 'Three library notification emails queued' );
2447
2448
        my @to_addresses = map { $_->to_address } $library_messages->all;
2449
        ok( ( grep { $_ eq 'library1@example.com' } @to_addresses ), 'Email sent to library1' );
2450
        ok( ( grep { $_ eq 'library2@example.com' } @to_addresses ), 'Email sent to library2' );
2451
2452
        $schema->storage->txn_rollback;
2453
    };
2454
2455
    # Test 7: Invalid email handling
2456
    subtest 'invalid_email_handling' => sub {
2457
        plan tests => 3;
2458
2459
        $schema->storage->txn_begin;
2460
2461
        # Enable duplicate blocking and email notifications with invalid addresses
2462
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoice',               1 );
2463
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailNotice',    1 );
2464
        t::lib::Mocks::mock_preference( 'EdiBlockDuplicateInvoiceEmailAddresses', 'invalid-email, valid@example.com' );
2465
2466
        # Create letter templates (delete first if exist)
2467
        $schema->resultset('Letter')->search(
2468
            {
2469
                module => 'acquisition',
2470
                code   => [ 'EDI_DUP_INV_LIBRARY', 'EDI_DUP_INV_VENDOR' ],
2471
            }
2472
        )->delete;
2473
2474
        $builder->build(
2475
            {
2476
                source => 'Letter',
2477
                value  => {
2478
                    module                 => 'acquisition',
2479
                    code                   => 'EDI_DUP_INV_LIBRARY',
2480
                    branchcode             => '',
2481
                    name                   => 'Test library notification',
2482
                    is_html                => 0,
2483
                    title                  => 'Duplicate Invoice',
2484
                    content                => 'Duplicate invoice.',
2485
                    message_transport_type => 'email',
2486
                    lang                   => 'default',
2487
                }
2488
            }
2489
        );
2490
        $builder->build(
2491
            {
2492
                source => 'Letter',
2493
                value  => {
2494
                    module                 => 'acquisition',
2495
                    code                   => 'EDI_DUP_INV_VENDOR',
2496
                    branchcode             => '',
2497
                    name                   => 'Test vendor notification',
2498
                    is_html                => 0,
2499
                    title                  => 'Duplicate Invoice',
2500
                    content                => 'Duplicate invoice.',
2501
                    message_transport_type => 'email',
2502
                    lang                   => 'default',
2503
                }
2504
            }
2505
        );
2506
2507
        # Create file transport for local testing
2508
        my $file_transport = $builder->build(
2509
            {
2510
                source => 'FileTransport',
2511
                value  => {
2512
                    name               => 'Test Invoice Transport',
2513
                    transport          => 'local',
2514
                    download_directory => $dirname,
2515
                    upload_directory   => $dirname,
2516
                }
2517
            }
2518
        );
2519
2520
        # Create vendor EDI account
2521
        my $account = $builder->build(
2522
            {
2523
                source => 'VendorEdiAccount',
2524
                value  => {
2525
                    description       => 'test vendor',
2526
                    file_transport_id => $file_transport->{file_transport_id},
2527
                    plugin            => '',
2528
                    san               => '5013546027173',
2529
                }
2530
            }
2531
        );
2532
2533
        # Create vendor contact with invalid email
2534
        my $vendor_contact = $builder->build(
2535
            {
2536
                source => 'Aqcontact',
2537
                value  => {
2538
                    name                   => 'Test Vendor Contact',
2539
                    email                  => 'not-an-email',
2540
                    booksellerid           => $account->{vendor_id},
2541
                    edi_error_notification => 1,
2542
                }
2543
            }
2544
        );
2545
2546
        # Create test invoice that already exists
2547
        my $existing_invoice = $builder->build(
2548
            {
2549
                source => 'Aqinvoice',
2550
                value  => {
2551
                    invoicenumber => 'INV00003',
2552
                    booksellerid  => $account->{vendor_id},
2553
                }
2554
            }
2555
        );
2556
2557
        # Create test basket and order
2558
        my $basket = $builder->build_object(
2559
            {
2560
                class => 'Koha::Acquisition::Baskets',
2561
                value => {
2562
                    booksellerid => $account->{vendor_id},
2563
                    basketname   => 'Test Basket',
2564
                }
2565
            }
2566
        );
2567
        my $order = $builder->build_object(
2568
            {
2569
                class => 'Koha::Acquisition::Orders',
2570
                value => {
2571
                    basketno     => $basket->id,
2572
                    orderstatus  => 'new',
2573
                    biblionumber => undef,
2574
                }
2575
            }
2576
        );
2577
2578
        # Prepare invoice message
2579
        my $filename = 'INVOICE.CEI';
2580
        my $trans    = Koha::Edifact::Transport->new( $account->{id} );
2581
        $trans->working_directory($dirname);
2582
2583
        my $mhash = $trans->message_hash();
2584
        $mhash->{message_type} = 'INVOICE';
2585
        $trans->ingest( $mhash, $filename );
2586
2587
        my $invoice_message = $schema->resultset('EdifactMessage')->find( { filename => $filename } );
2588
        my $raw_msg         = $invoice_message->raw_msg;
2589
        $raw_msg =~ s/ORDERNUMBER1/$order->ordernumber/g;
2590
        $raw_msg =~ s/ORDERNUMBER2/$order->ordernumber/g;
2591
        $invoice_message->update( { raw_msg => $raw_msg } );
2592
2593
        # Clear logger
2594
        $logger->clear();
2595
2596
        # Process the invoice
2597
        process_invoice($invoice_message);
2598
2599
        # Verify only valid email was queued (invalid email skipped)
2600
        my $valid_messages = $schema->resultset('MessageQueue')->search(
2601
            {
2602
                to_address => 'valid@example.com',
2603
                status     => [ 'sent', 'pending', 'failed' ],    # Accept any status - queuing is what matters
2604
            }
2605
        );
2606
        is( $valid_messages->count, 1, 'Only valid library email queued' );
2607
2608
        # Verify no message for invalid email
2609
        my $invalid_messages = $schema->resultset('MessageQueue')->search(
2610
            {
2611
                to_address => 'invalid-email',
2612
            }
2613
        );
2614
        is( $invalid_messages->count, 0, 'No message queued for invalid email' );
2615
2616
        # Verify invalid vendor contact email was logged
2617
        $logger->warn_like(
2618
            qr/Invalid vendor contact email address/,
2619
            'Warning logged for invalid vendor contact email'
2620
        );
2621
2622
        $logger->clear();
2623
        $schema->storage->txn_rollback;
2624
    };
2625
2626
    $schema->storage->txn_rollback;
2627
};

Return to bug 41297