Applying and Releasing Sales Order Holds Based on Customer Item Cross-Reference Commodity Code

Applying and Releasing Sales Order Holds Based on Customer Item Cross-Reference Commodity Code

In Oracle Order Management, business rules often require dynamic control over Sales Order processing. In this case, the customer wanted to automatically apply and release Sales Order holds based on the Commodity Code maintained in the Customer Item Cross-Reference.

This blog explains how we implemented a fully automated hold management solution using standard Oracle APIs, without manual intervention.

 

Business Requirement

The customer’s requirement was straightforward but critical:

  • Apply a Sales Order hold when an item is associated with a specific commodity code (for example: CUSTOMER CHOICE) in the Customer Item Cross-Reference
  • Release the hold automatically when the commodity condition no longer exists
  • Ensure the logic works for booked and open orders
  • Avoid manual hold application or release by users
  • Maintain auditability and Oracle support compliance

 

Key Design Considerations

  • Use standard Oracle Order Management APIs
  • Apply hold at the Order Header level
  • Prevent duplicate holds
  • Release holds only when no qualifying lines remain
  • Maintain a tracking table for reporting and audit

 

Solution Overview

The solution was implemented as a concurrent program that performs two main actions:

  1. Apply Hold – for orders that meet the commodity condition
  2. Release Hold – for orders where the condition no longer applies

The logic is driven by the Customer Item Cross-Reference commodity code.

 

 

 

High-Level Process Flow

Apply Hold Flow

  1. Identify open Sales Orders
  2. Validate Customer Item Cross-Reference commodity code
  3. Check whether the hold already exists
  4. Apply the hold using OE_HOLDS_PUB.APPLY_HOLDS
  5. Update tracking table and flag processed order lines

Release Hold Flow

  1. Identify orders with the specific hold applied
  2. Re-validate Customer Item Cross-Reference
  3. If no qualifying items exist, release the hold
  4. Release hold using OE_HOLDS_PUB.RELEASE_HOLDS
  5. Update tracking table and clear processing flags

 

Technical Implementation Details

Application Context Initialization

The program initializes the Oracle EBS context using profile values:

  • User ID
  • Responsibility ID
  • Application ID

This ensures all API calls run with proper authorization.

 

Applying Sales Order Hold

Selection Logic

Orders are selected when:

  • Order lines are in ENTERED / BOOKED / AWAITING statuses
  • Customer Item Cross-Reference exists
  • Commodity Code = CUSTOMER CHOICE
  • Order creation date ≥ Customer Item Cross-Reference update date
  • Hold is not already applied

 

Hold Application API

OE_HOLDS_PUB.APPLY_HOLDS

Key Inputs

  • Order Header ID
  • Hold Definition ID
  • Validation Level = FULL
  • Hold Comment for audit clarity

 

Post-Processing

  • Insert audit records into a custom hold tracking table
  • Update OE_ORDER_LINES_ALL.INDUSTRY_ATTRIBUTE29 as a processing flag
  • Commit transaction only on success

 

Releasing Sales Order Hold

Release Validation Logic

Before releasing a hold, the program checks:

  • Whether any order lines still meet the commodity condition
  • Only releases the hold when no qualifying items exist

 

Hold Release API

OE_HOLDS_PUB.RELEASE_HOLDS

Key Inputs

  • Order Header ID
  • Hold ID
  • Release Reason Code
  • Release Comment

 

Post-Release Actions

  • Insert release record into audit table
  • Clear processing flags on order lines
  • Commit transaction on success

 

Sample PL/SQL Code

Below is the complete PL/SQL program used to apply and release Sales Order holds based on Customer Item Cross-Reference commodity code:

/* Formatted on 2026/01/30 19:22 (Formatter Plus v4.8.8) */

DECLARE

   g_conc_program_id   NUMBER        := fnd_profile.VALUE (‘CONC_REQUEST_ID’);

   g_resp_id           NUMBER                := fnd_profile.VALUE (‘RESP_ID’);

   g_appl_id           NUMBER           := fnd_profile.VALUE (‘RESP_APPL_ID’);

   g_user_id           NUMBER                := fnd_profile.VALUE (‘USER_ID’);

   l_upp_hold_id       NUMBER;

   l_hold_found        NUMBER;

   l_order_tbl         oe_holds_pvt.order_tbl_type;

   l_return_status     VARCHAR2 (5);

   l_msg_count         NUMBER;

   l_msg_data          VARCHAR2 (2000);

   l_msg_index         NUMBER;

   l_upp_count         NUMBER;

 

   CURSOR c1

   IS

      SELECT DISTINCT hca.account_number customer_number,

                      hp.party_name customer_name, ood.organization_code,

                      ooh.order_number, ooh.header_id

                 FROM oe_order_headers_all ooh,

                      oe_order_lines_all ool,

                      mtl_customer_item_xrefs_v mcix,

                      mtl_customer_items_all_v mciav,

                      hz_cust_accounts hca,

                      hz_parties hp,

                      org_organization_definitions ood,

                      mtl_system_items_b msib

                WHERE ooh.header_id = ool.header_id

                  AND ool.flow_status_code IN

                         (‘AWAITING_SHIPPING’, ‘AWAITING_RECEIPT’, ‘BOOKED’,

                          ‘ENTERED’)

                  AND mcix.customer_id = ool.sold_to_org_id

                  AND mcix.inventory_item_id = ool.inventory_item_id

                  AND industry_attribute29 IS NULL

                  AND mcix.customer_item_id = mciav.customer_item_id

                  AND mcix.customer_id = mciav.customer_id

                  AND mciav.commodity_code = ‘CUSTOMER CHOICE’

                  AND hca.cust_account_id = ool.sold_to_org_id

                  AND hca.party_id = hp.party_id

                  AND ooh.ship_from_org_id = ood.organization_id

                  AND ool.inventory_item_id = msib.inventory_item_id

                  AND ool.ship_from_org_id = msib.organization_id

                  AND TRUNC (ooh.creation_date) >=

                                                TRUNC (mciav.last_update_date)

             GROUP BY hca.account_number,

                      hp.party_name,

                      ood.organization_code,

                      ooh.order_number,

                      ooh.header_id;

 

   CURSOR c2

   IS

      SELECT DISTINCT hca.account_number customer_number,

                      hp.party_name customer_name, ood.organization_code,

                      ooh.order_number, ooh.header_id, hs.hold_id

                 FROM oe_order_headers_all ooh,

                      hz_cust_accounts hca,

                      hz_parties hp,

                      org_organization_definitions ood,

                      oe_order_holds_all oh,

                      oe_hold_sources_all hs,

                      oe_hold_definitions h

                WHERE 1 = 1

                  AND hca.cust_account_id = ooh.sold_to_org_id

                  AND hca.party_id = hp.party_id

                  AND ooh.ship_from_org_id = ood.organization_id

                  AND ooh.header_id = oh.header_id

                  AND oh.hold_release_id IS NULL

                  AND oh.hold_source_id = hs.hold_source_id

                  AND hs.hold_id = h.hold_id

                  AND h.type_code = ‘HOLD’

                  AND h.NAME = ‘Hold Name’

                  AND oh.released_flag = ‘N’

                  AND EXISTS (SELECT 1

                                FROM xxxx_sales_order_hold_tbl temp

                               WHERE temp.header_id = ooh.header_id)

                  AND EXISTS (

                         SELECT 1

                           FROM oe_order_lines_all ool

                          WHERE ool.header_id = ooh.header_id

                            AND ool.flow_status_code IN

                                   (‘AWAITING_SHIPPING’, ‘AWAITING_RECEIPT’,

                                    ‘BOOKED’, ‘ENTERED’));

BEGIN

   fnd_file.put_line

      (fnd_file.LOG,

       ‘******************************************************************************************************************************************************’

      );

   fnd_file.put_line (fnd_file.LOG,

                         ‘Program Apply Started ‘

                      || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                     );

 

   BEGIN

      SELECT hold_id

        INTO l_cust_hold_id

        FROM oe_hold_definitions

       WHERE NAME = ‘Hold Name’

         AND (end_date_active IS NULL OR end_date_active >= TRUNC (SYSDATE));

   EXCEPTION

      WHEN OTHERS

      THEN

         l_cust_hold_id := NULL;

   END;

 

   fnd_file.put_line (fnd_file.LOG,

                         ‘Hold ID-‘

                      || l_cust_hold_id

                      || ‘-‘

                      || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                     );

 

   BEGIN

      fnd_global.apps_initialize (g_user_id, g_resp_id, g_appl_id);

   END;

 

   FOR rec_apply_hold IN c1

   LOOP

      fnd_file.put_line (fnd_file.LOG,

                         ‘Customer Number#’ || rec_apply_hold.customer_number

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Customer Name#’ || rec_apply_hold.customer_name

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Order Number#’ || rec_apply_hold.order_number

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Header ID#’ || rec_apply_hold.header_id

                        );

 

      –Check if hold exists on the order line.

      BEGIN

         SELECT COUNT (*)

           INTO l_hold_found

           FROM oe_order_holds_all oh,

                oe_hold_sources_all hs,

                oe_hold_definitions h

          WHERE oh.header_id = rec_apply_hold.header_id

            AND oh.hold_release_id IS NULL

            AND oh.hold_source_id = hs.hold_source_id

            AND hs.hold_id = h.hold_id

            AND h.type_code = ‘HOLD’

            AND h.NAME = ‘Hold Name’

            AND oh.released_flag = ‘N’;

      END;

 

      fnd_file.put_line (fnd_file.LOG,

                         ‘Hold Already Exists Count’ || l_hold_found

                        );

 

      IF l_hold_found = 0

      THEN

         fnd_file.put_line (fnd_file.LOG,

                               ‘Program GoinG to Apply Hold  API Part’

                            || l_hold_found

                           );

         l_order_tbl (1).header_id := rec_apply_hold.header_id;

         oe_holds_pub.apply_holds

                        (p_api_version           => 1.0,

                         p_init_msg_list         => fnd_api.g_false,

                         p_commit                => fnd_api.g_false,

                         p_validation_level      => fnd_api.g_valid_level_full,

                         p_order_tbl             => l_order_tbl,

                         p_hold_id               => l_cust_hold_id,

                         p_hold_until_date       => NULL,

                         p_hold_comment          => ‘Program Automation Applied -Hold’,

                         x_return_status         => l_return_status,

                         x_msg_count             => l_msg_count,

                         x_msg_data              => l_msg_data

                        );

         fnd_file.put_line (fnd_file.LOG,

                            ‘API Return Status’ || l_return_status

                           );

 

         IF l_return_status = fnd_api.g_ret_sts_success

         THEN

            fnd_file.put_line (fnd_file.LOG,

                               ‘Hold Applied Sucessfully -‘ || l_return_status

                              );

 

            BEGIN

               INSERT INTO xxxx_sales_order_hold_tbl

                           (customer_number,

                            customer_name,

                            organization_code,

                            order_number,

                            header_id, request_id,

                            created_by, created_date, last_updated_by,

                            last_upadted_date, hold_name, hold_id,

                            rec_type

                           )

                    VALUES (rec_apply_hold.customer_number,

                            rec_apply_hold.customer_name,

                            rec_apply_hold.organization_code,

                            rec_apply_hold.order_number,

                            rec_apply_hold.header_id, g_conc_program_id,

                            g_user_id, SYSDATE, g_user_id,

                            SYSDATE, ‘Hold Name’, l_cust_hold_id,

                            ‘Apply Hold’

                           );

            EXCEPTION

               WHEN OTHERS

               THEN

                  fnd_file.put_line

                           (fnd_file.LOG,

                               ‘Error In Insert – XXXX_SALES_ORDER_HOLD_TBL-‘

                            || SQLERRM

                           );

            END;

 

            BEGIN

               UPDATE oe_order_lines_all

                  SET industry_attribute29 = ‘Y’

                WHERE 1 = 1 AND header_id = rec_apply_hold.header_id;

            EXCEPTION

               WHEN OTHERS

               THEN

                  fnd_file.put_line

                     (fnd_file.LOG,

                      ‘Error In Update -OE_ORDER_LINES_ALL- INDUSTRY_ATTRIBUTE29’

                     );

            END;

 

            COMMIT;

         ELSE

            IF l_msg_count > 0

            THEN

               FOR x_index IN 1 .. l_msg_count

               LOOP

                  oe_msg_pub.get (p_msg_index          => x_index,

                                  p_encoded            => fnd_api.g_false,

                                  p_data               => l_msg_data,

                                  p_msg_index_out      => l_msg_index

                                 );

                  fnd_file.put_line

                          (fnd_file.LOG,

                              ‘Error in applying holds for UPP Policy Hold…’

                           || l_msg_data

                          );

               END LOOP;

            END IF;

         END IF;

      END IF;

   END LOOP;

 

   fnd_file.put_line (fnd_file.LOG,

                         ‘Program Apply Ended ‘

                      || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                     );

   fnd_file.put_line

      (fnd_file.LOG,

       ‘******************************************************************************************************************************************************’

      );

   fnd_file.put_line

      (fnd_file.LOG,

       ‘~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~’

      );

   fnd_file.put_line (fnd_file.LOG,

                         ‘Program Release Started ‘

                      || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                     );

 

   FOR rec_release_hold IN c2

   LOOP

      fnd_file.put_line (fnd_file.LOG,

                         ‘Customer Number#’

                         || rec_release_hold.customer_number

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Customer Name#’ || rec_release_hold.customer_name

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Order Number#’ || rec_release_hold.order_number

                        );

      fnd_file.put_line (fnd_file.LOG,

                         ‘Header ID#’ || rec_release_hold.header_id

                        );

      fnd_file.put_line (fnd_file.LOG, ‘Hold ID#’ || rec_release_hold.hold_id);

 

      BEGIN

         SELECT COUNT (*)

           INTO l_upp_count

           FROM oe_order_headers_all ooh,

                oe_order_lines_all ool,

                mtl_customer_item_xrefs_v mcix,

                mtl_customer_items_all_v mciav,

                hz_cust_accounts hca,

                hz_parties hp,

                org_organization_definitions ood,

                mtl_system_items_b msib

          WHERE ooh.header_id = ool.header_id

            AND ool.flow_status_code IN

                   (‘AWAITING_SHIPPING’, ‘AWAITING_RECEIPT’, ‘BOOKED’,

                    ‘ENTERED’)

            AND mcix.customer_id = ool.sold_to_org_id

            AND mcix.inventory_item_id = ool.inventory_item_id

            AND mcix.customer_item_id = mciav.customer_item_id

            AND mcix.customer_id = mciav.customer_id

            AND mciav.commodity_code = ‘CUSTOMER CHOICE’

            AND hca.cust_account_id = ool.sold_to_org_id

            AND hca.party_id = hp.party_id

            AND ooh.ship_from_org_id = ood.organization_id

            AND ool.inventory_item_id = msib.inventory_item_id

            AND ool.ship_from_org_id = msib.organization_id

            AND ooh.header_id = rec_release_hold.header_id;

      END;

 

      fnd_file.put_line (fnd_file.LOG,

                            ‘Customer Item Cross Ref Validation Count ‘

                         || l_upp_count

                        );

 

      IF l_upp_count = 0

      THEN

         fnd_file.put_line (fnd_file.LOG,

                            ‘Program Going to Relase Hold API Part ‘

                           );

 

         BEGIN

            l_order_tbl (1).header_id := rec_release_hold.header_id;

            oe_holds_pub.release_holds

               (p_api_version              => 1.0,

                p_order_tbl                => l_order_tbl,

                p_hold_id                  => rec_release_hold.hold_id,

                p_release_reason_code      => ‘HOLD RELEASE AUTOMATION’,

                p_release_comment          => ‘Released Through Release Hold API Call’,

                x_return_status            => l_return_status,

                x_msg_count                => l_msg_count,

                x_msg_data                 => l_msg_data

               );

            fnd_file.put_line (fnd_file.LOG,

                                  ‘Hold Release API Return Status’

                               || l_return_status

                              );

 

            IF l_return_status = fnd_api.g_ret_sts_success

            THEN

               fnd_file.put_line (fnd_file.LOG,

                                     ‘Hold Released Sucessfully -‘

                                  || l_return_status

                                 );

 

               BEGIN

                  INSERT INTO xxxx_sales_order_hold_tbl

                              (customer_number,

                               customer_name,

                               organization_code,

                               order_number,

                               header_id,

                               request_id, created_by, created_date,

                               last_updated_by, last_upadted_date,

                               hold_name, hold_id,

                               rec_type

                              )

                       VALUES (rec_release_hold.customer_number,

                               rec_release_hold.customer_name,

                               rec_release_hold.organization_code,

                               rec_release_hold.order_number,

                               rec_release_hold.header_id,

                               g_conc_program_id, g_user_id, SYSDATE,

                               g_user_id, SYSDATE,

                               ‘Hold Name’, rec_release_hold.hold_id,

                               ‘Release Hold’

                              );

               EXCEPTION

                  WHEN OTHERS

                  THEN

                     fnd_file.put_line

                               (fnd_file.LOG,

                                ‘Error In Insert – XXXX_SALES_ORDER_HOLD_TBL’

                               );

               END;

 

               BEGIN

                  UPDATE oe_order_lines_all

                     SET industry_attribute29 = NULL

                   WHERE 1 = 1 AND header_id = rec_release_hold.header_id;

               EXCEPTION

                  WHEN OTHERS

                  THEN

                     fnd_file.put_line

                        (fnd_file.LOG,

                         ‘Error In Update -OE_ORDER_LINES_ALL- INDUSTRY_ATTRIBUTE29’

                        );

               END;

 

               COMMIT;

            ELSE

               IF l_msg_count > 0

               THEN

                  FOR x_index IN 1 .. l_msg_count

                  LOOP

                     oe_msg_pub.get (p_msg_index          => x_index,

                                     p_encoded            => fnd_api.g_false,

                                     p_data               => l_msg_data,

                                     p_msg_index_out      => l_msg_index

                                    );

                     fnd_file.put_line

                          (fnd_file.LOG,

                              ‘Error in applying holds for UPP Policy Hold…’

                           || l_msg_data

                          );

                  END LOOP;

               END IF;

            END IF;

         END;

      ELSE

         fnd_file.put_line (fnd_file.LOG,

                               ‘No Hold Release Found ‘

                            || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                           );

      END IF;

   END LOOP;

 

   fnd_file.put_line (fnd_file.LOG,

                         ‘Program Release Ended ‘

                      || TO_CHAR (SYSDATE, ‘DD-MM-YYYY HH24:MI:SS’)

                     );

   fnd_file.put_line

      (fnd_file.LOG,

       ‘~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~’

      );

END;

 

Benefits of This Approach

  • Fully automated hold management
  • No manual user intervention
  • Uses Oracle-supported APIs
  •  Prevents duplicate holds
  •  Clear audit trail
  •  Easy to extend for new commodity codes

 

Common Use Cases

  • Regulatory or compliance-driven order control
  • Customer-specific product restrictions
  • Conditional order fulfillment logic

 

Conclusion

By leveraging Customer Item Cross-Reference data and standard Oracle Order Management APIs, we successfully implemented a robust and scalable solution to apply and release Sales Order holds automatically.

This approach ensures:

  • Data integrity
  • Business rule enforcement
  • Minimal operational overhead

If your organization manages complex customer-specific rules, this model provides a clean, supportable, and reusable framework.

 

Recent Posts