Broadleaf Microservices
  • v1.0.0-latest-prod

Release Notes for 2.0.0-GA

As of 2.0.0-GA of PromotionServices, CampaignServices functionality was moved into OfferServices and CampaignServices is now deprecated. Since all Campaign endpoints have been merged into Offer Services, see the following list for the new endpoint mapping details.

Data migration for moving the data from the campaign schema to the offer schema is documented below. See Release Notes 2.0.0-GA Data Migration.

Requirements

  • JDK 11 is now required for Broadleaf release trains 1.7.0-GA, and beyond.

New Features & Notable Changes

Feature/Notable Change Related Services Links

Refactored Campaign Services to fold it into Offer Services

OfferServices, CampaignServices

Refactored OfferAudit & Campaign Audit Admin Page

OfferServices, OfferClient

Introduced segment support to offers and offer codes

OfferServices, OfferClient

Added returning messages for reasons Offer was not applied

OfferServices, OfferClient

See Offer Code Responses

Introduced advanced Offer caching

OfferServices

See Offer Cache docs

Introduce offer rounding properties for order and order item offers

OfferServices

See Offer Discount Rounding

Add property to control if rounded remainders should be distributed

OfferServices

See Offer Discount Rounding

Bug Fixes

Issue Related Services

Updated to use the correct quantity when only part of a line item quantity should be used in offer proration

OfferServices

Fix bundle items not applying offers

OfferServices

Add filtering logic to only build qualifier details for used qualifier items and not all candidates

OfferServices

Fix detection of dirty state for user restriction form on deletion

OfferServices

Fix metadata query filter mapping for offer target type and discount amount.

OfferServices

Upgrade Guide

API Changes

New Endpoints

Path Description

GET /{id}/offer-codes/{codeId}

Get offer code for offer

GET /{id}/offer-codes

Get all offer codes for offer

PATCH /{id}/offer-codes/{codeId}

Update an offer code

PUT /{id}/offer-codes/{codeId}

Replace an offer code

POST /{id}/offer-codes

Create an offer code

DELETE /{id}/offer-codes/{codeId}

Remove an offer code for offer

POST /offer-audit-details/record-offer-usages

Record offer and offer code usages

POST /offer-engine/evaluate-offers

Evaluates offers against an order

GET /shared-code-audit-summaries

Read all shared code audit summaries

POST /shared-code-audit-summaries

Create a shared code audit summary

GET /shared-code-audit-summaries/{sharedCodeAuditSummaryId}

Get a shared code audit summary by ID

PATCH /shared-code-audit-summaries/{sharedCodeAuditSummaryId}

Update a shared code audit summary

PUT /shared-code-audit-summaries/{sharedCodeAuditSummaryId}

Replace a shared code audit summary

DELETE /shared-code-audit-summaries/{sharedCodeAuditSummaryId}

Delete a shared code audit summary

GET /shared-code-audit-summaries/{offerCodeId}/uses-left

Count the uses remaining for a shared offer code

GET /shared-code-audit-summaries/{code}/usability-info

Find usability info for shared offer code

GET /shared-code-audit-summaries/validate-uses

Validate shared offer code usage

Data Migration

The tables from the campaign schema were moved to the offer schema, therefore a data migration is required using the following SQL scripts:

Important
Before the data migration, be sure to backup the data in the campaign and offer schemas in case of a migration rollback.

Campaigns

INSERT INTO offer.blc_campaign(id, active_end_date, active_start_date, code_prefix, name, trk_application_context_id,
                               trk_application_default_locale, trk_application_name, trk_archived, trk_change_details,
                               trk_creating_application_id, trk_current_message, trk_level, trk_max_field_versions,
                               trk_sandbox_archived, trk_sub_container_catalog, trk_change_container_id,
                               trk_change_container_name, trk_change_container_route_key, trk_sub_container_name,
                               trk_sandbox_change_type, trk_sandbox_context_id, trk_sandbox_name, trk_sandbox_owner,
                               trk_sandbox_stage, trk_tenant_id, audit_creation_time, audit_creator, audit_update_time,
                               audit_updater)
SELECT id,
       active_end_date,
       active_start_date,
       code_prefix,
       name,
       trk_application_context_id,
       trk_application_default_locale,
       trk_application_name,
       trk_archived,
       trk_change_details,
       trk_creating_application_id,
       trk_current_message,
       trk_level,
       trk_max_field_versions,
       trk_sandbox_archived,
       trk_sub_container_catalog,
       trk_change_container_id,
       trk_change_container_name,
       trk_change_container_route_key,
       trk_sub_container_name,
       trk_sandbox_change_type,
       trk_sandbox_context_id,
       trk_sandbox_name,
       trk_sandbox_owner,
       trk_sandbox_stage,
       trk_tenant_id,
       audit_creation_time,
       audit_creator,
       audit_update_time,
       audit_updater
FROM campaign.blc_campaign;

Code Generators

INSERT INTO offer.blc_code_generator(id, active_end_date, active_start_date, allowed_letters, allowed_numbers,
                                     campaign_id, code_format, message, status, code_length, code_prefix, create_date,
                                     email_regex, max_uses, number_generated, number_to_generate,
                                     trk_application_context_id, trk_application_default_locale, trk_application_name,
                                     trk_archived, audit_creation_time, audit_creator, audit_update_time, audit_updater,
                                     trk_change_details, trk_creating_application_id, trk_current_message, trk_level,
                                     trk_max_field_versions, trk_sandbox_archived, trk_sub_container_catalog,
                                     trk_change_container_id, trk_change_container_name, trk_change_container_route_key,
                                     trk_sub_container_name, trk_sandbox_change_type, trk_sandbox_context_id,
                                     trk_sandbox_name, trk_sandbox_owner, trk_sandbox_stage, trk_tenant_id, update_date,
                                     is_for_voucher)
SELECT id,
       active_end_date,
       active_start_date,
       allowed_letters,
       allowed_numbers,
       campaign_id,
       code_format,
       message,
       status,
       code_length,
       code_prefix,
       create_date,
       email_regex,
       max_uses,
       number_generated,
       number_to_generate,
       trk_application_context_id,
       trk_application_default_locale,
       trk_application_name,
       trk_archived,
       audit_creation_time,
       audit_creator,
       audit_update_time,
       audit_updater,
       trk_change_details,
       trk_creating_application_id,
       trk_current_message,
       trk_level,
       trk_max_field_versions,
       trk_sandbox_archived,
       trk_sub_container_catalog,
       trk_change_container_id,
       trk_change_container_name,
       trk_change_container_route_key,
       trk_sub_container_name,
       trk_sandbox_change_type,
       trk_sandbox_context_id,
       trk_sandbox_name,
       trk_sandbox_owner,
       trk_sandbox_stage,
       trk_tenant_id,
       update_date,
       is_for_voucher
FROM campaign.blc_code_generator;

Campaign Codes to Offer Codes

INSERT INTO offer.blc_offer_code (id, active_end_date, active_start_date, campaign_id, code, email_regex, generator_id,
                                  max_uses, trk_application_context_id, trk_application_default_locale,
                                  trk_application_name, trk_archived, trk_change_details, trk_creating_application_id,
                                  trk_current_message, trk_level, trk_max_field_versions, trk_sandbox_archived,
                                  trk_sub_container_catalog, trk_change_container_id, trk_change_container_name,
                                  trk_change_container_route_key, trk_sub_container_name, trk_sandbox_change_type,
                                  trk_sandbox_context_id, trk_sandbox_name, trk_sandbox_owner, trk_sandbox_stage,
                                  trk_tenant_id, audit_creation_time, audit_creator, audit_update_time, audit_updater,
                                  is_assigned)
SELECT id,
       active_end_date,
       active_start_date,
       campaign_id,
       code,
       email_regex,
       generator_id,
       max_uses,
       trk_application_context_id,
       trk_application_default_locale,
       trk_application_name,
       trk_archived,
       trk_change_details,
       trk_creating_application_id,
       trk_current_message,
       trk_level,
       trk_max_field_versions,
       trk_sandbox_archived,
       trk_sub_container_catalog,
       trk_change_container_id,
       trk_change_container_name,
       trk_change_container_route_key,
       trk_sub_container_name,
       trk_sandbox_change_type,
       trk_sandbox_context_id,
       trk_sandbox_name,
       trk_sandbox_owner,
       trk_sandbox_stage,
       trk_tenant_id,
       audit_creation_time,
       audit_creator,
       audit_update_time,
       audit_updater,
       is_assigned
FROM campaign.blc_campaign_code;

Campaign Audit to Offer Audit

INSERT INTO offer.blc_offer_audit_detail(id, date_applied, offer_id, trk_archived, trk_change_details, trk_tenant_id,
                                         user_target_type, user_target_value, audit_creation_time, audit_creator,
                                         audit_update_time, audit_updater,
                                         campaign_tracking_id, offer_code_id,
                                         trk_application_context_id, trk_application_default_locale,
                                         trk_application_name, trk_creating_application_id, trk_current_message,
                                         trk_level, trk_max_field_versions, trk_sandbox_archived,
                                         trk_sub_container_catalog, trk_change_container_id, trk_change_container_name,
                                         trk_change_container_route_key, trk_sub_container_name,
                                         trk_sandbox_change_type, trk_sandbox_context_id, trk_sandbox_name,
                                         trk_sandbox_owner, trk_sandbox_stage)
SELECT cad.id,
       cad.date_applied,
       oc.offer_id,
       cad.trk_archived,
       cad.trk_change_details,
       cad.trk_tenant_id,
       cad.target_type  as user_target_type,
       cad.target_value as user_target_value,
       cad.audit_creation_time,
       cad.audit_creator,
       cad.audit_update_time,
       cad.audit_updater,
       cad.campaign_tracking_id,
       oc.id            as offer_code_id,
       cad.trk_application_context_id,
       cad.trk_application_default_locale,
       cad.trk_application_name,
       cad.trk_creating_application_id,
       cad.trk_current_message,
       cad.trk_level,
       cad.trk_max_field_versions,
       cad.trk_sandbox_archived,
       cad.trk_sub_container_catalog,
       cad.trk_change_container_id,
       cad.trk_change_container_name,
       cad.trk_change_container_route_key,
       cad.trk_sub_container_name,
       cad.trk_sandbox_change_type,
       cad.trk_sandbox_context_id,
       cad.trk_sandbox_name,
       cad.trk_sandbox_owner,
       cad.trk_sandbox_stage
FROM campaign.blc_campaign_audit_detail cad
         JOIN offer.blc_offer_code oc ON cad.campaign_code = oc.code AND cad.campaign_tracking_id = oc.campaign_id;

Exports

INSERT INTO offer.blc_export (id, addl_parameters, application_id, author, created, error_message, exclusions,
                              exporting_application_id, exporting_catalog_id, exporting_customer_context_id,
                              exporting_sandbox_id, file_location, file_type, filter_string, inclusions, last_updated,
                              name, number_of_records_to_process, parent_id, status, target, tenant_id, time_zone_id,
                              total_records_to_process)
SELECT id,
       addl_parameters,
       application_id,
       author,
       created,
       error_message,
       exclusions,
       exporting_application_id,
       exporting_catalog_id,
       exporting_customer_context_id,
       exporting_sandbox_id,
       file_location,
       file_type,
       filter_string,
       inclusions,
       last_updated,
       name,
       number_of_records_to_process,
       parent_id,
       status,
       target,
       tenant_id,
       time_zone_id,
       total_records_to_process
FROM campaign.blc_export;

Export LOB

INSERT INTO offer.blc_export_lob (id, content, path)
SELECT id, content, path
FROM campaign.blc_export_lob;

Notification State

INSERT INTO offer.blc_notification_state (id, acked, attempts, change_timestamp, change_timestamp_ack, container,
                                          entity_type, message_type, message_value, notification_name, next_attempt,
                                          stopped)
SELECT id,
       acked,
       attempts,
       change_timestamp,
       change_timestamp_ack,
       container,
       entity_type,
       message_type,
       message_value,
       notification_name,
       next_attempt,
       stopped
FROM campaign.blc_notification_state;

Resource Lock

INSERT INTO offer.blc_resource_lock (id, concept_key, context_id, lock_expiration_timestamp, locked, sandbox_id,
                                     lock_timestamp, token, type_alias)
SELECT id,
       concept_key,
       context_id,
       lock_expiration_timestamp,
       locked,
       sandbox_id,
       lock_timestamp,
       token,
       type_alias
FROM campaign.blc_resource_lock;

Shared Codes to Offer Codes

The table blc_shared_code combines code and offer_id as the composite primary key. To migrate the data from the blc_shared_code table to the blc_offer_code table we need to generate the ULID id. To do so, the CustomTaskChange has to be implemented and added to the liquibase changeset.

The following code contains the implemented CustomTaskChange to migrate the blc_shared_code table data, as well as to update the existing data in blc_offer_audit_detail. Updating the data in blc_offer_audit_detail is necessary to ensure the max uses restriction for single-use offer code still work as expected.

public class SharedCodesToOfferCodesMigration implements CustomTaskChange {

    protected static final int BATCH_SIZE = 20;

    @Override
    public void execute(Database database) throws CustomChangeException {
        int batchCounter = 0;

        try {
            JdbcConnection jdbcConnection = (JdbcConnection) database.getConnection();

            PreparedStatement queryStatement = jdbcConnection.prepareStatement(
                    "SELECT sc.code, o.context_id as offer_id, sc.max_uses, o.trk_tenant_id, o.trk_application_context_id FROM blc_shared_code sc JOIN blc_offer o ON sc.offer_id = o.id WHERE o.trk_archived != 'Y'");
            PreparedStatement insertOfferCodeStatement = jdbcConnection.prepareStatement(
                    "INSERT INTO blc_offer_code (id, code, offer_id, max_uses, trk_tenant_id, trk_application_context_id, trk_level) VALUES (?, ?, ?, ?, ?, ?, ?)");
            PreparedStatement updateOfferAuditDetailStatement =
                    jdbcConnection.prepareStatement(
                            "UPDATE blc_offer_audit_detail SET offer_code_id=?, trk_application_context_id=?, trk_level=? WHERE shared_code=? AND offer_id=?");

            ResultSet sharedCodes = queryStatement.executeQuery();
            while (sharedCodes.next()) {
                String offerCodeId = ULID.random();
                String code = sharedCodes.getString("code");
                String offerId = sharedCodes.getString("offer_id");
                int maxUses = sharedCodes.getInt("max_uses");
                String tenantId = sharedCodes.getString("trk_tenant_id");
                String applicationContextId = sharedCodes.getString("trk_application_context_id");
                int trackingLevel = sharedCodes.getInt("trk_level");

                insertOfferCodeStatement.setString(1, offerCodeId);
                insertOfferCodeStatement.setString(2, code);
                insertOfferCodeStatement.setString(3, offerId);
                insertOfferCodeStatement.setInt(4, maxUses);
                insertOfferCodeStatement.setString(5, tenantId);
                insertOfferCodeStatement.setString(6, applicationContextId);
                if (trackingLevel == 0) {
                    insertOfferCodeStatement.setNull(7, 4);
                } else {
                    insertOfferCodeStatement.setInt(7, trackingLevel);
                }
                insertOfferCodeStatement.addBatch();

                updateOfferAuditDetailStatement.setString(1, offerCodeId);
                updateOfferAuditDetailStatement.setString(2, applicationContextId);
                if (trackingLevel == 0) {
                    updateOfferAuditDetailStatement.setNull(3, 4);
                } else {
                    updateOfferAuditDetailStatement.setInt(3, trackingLevel);
                }
                updateOfferAuditDetailStatement.setString(4, code);
                updateOfferAuditDetailStatement.setString(5, offerId);
                updateOfferAuditDetailStatement.addBatch();

                if (++batchCounter % BATCH_SIZE == 0) {
                    insertOfferCodeStatement.executeBatch();
                    updateOfferAuditDetailStatement.executeBatch();
                }
            }

            insertOfferCodeStatement.executeBatch();
            updateOfferAuditDetailStatement.executeBatch();

            sharedCodes.close();
            insertOfferCodeStatement.close();
            updateOfferAuditDetailStatement.close();
            queryStatement.close();
            // don't close DB connection

        } catch (DatabaseException | SQLException e) {
            throw new CustomChangeException(e);
        }
    }

    @Override
    public String getConfirmationMessage() {
        return "The data from blc_shared_code successfully migrated to blc_offer_code, blc_offer_audit_detail also updated!";
    }

    @Override
    public void setUp() throws SetupException {
        // do nothing
    }

    @Override
    public void setFileOpener(ResourceAccessor resourceAccessor) {
        // do nothing
    }

    @Override
    public ValidationErrors validate(Database database) {
        return null;
    }
}

Add the new changelog file e.g offer.data-migration.changelog-post-process.xml with the following content

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
     <changeSet id="shared-code-migration" author="broadleaf">
        <customChange class="com.myapp.changeset.SharedCodesToOfferCodesMigration" />
     </changeSet>
</databaseChangeLog>

Add this new file to the main changelog master file. After that, the data will be migrated during the service startup.

Populating Offer Audit Summary and Shared Code Audit Summary

The tables blc_offer_audit_summary and blc_shared_code_audit_summary are responsible for keeping track of offer and offer code usages, which is used to determine if they are usable based on their max uses restriction.

In order for the max uses restriction to work as expected after the migration, data needs to be populated in blc_offer_audit_summary and blc_shared_code_audit_summary based on the existing offer audit details. To do so, the CustomTaskChange has to be implemented and added to the liquibase changeset.

The following code contains the implemented CustomTaskChange to populate the blc_offer_audit_summary and blc_shared_code_audit_summary tables data`.

public class PopulateOfferAndOfferCodeAuditSummaryMigration implements CustomTaskChange {

    protected static final int BATCH_SIZE = 20;

    @Override
    public void execute(Database database) throws CustomChangeException {
        try {
            JdbcConnection jdbcConnection = (JdbcConnection) database.getConnection();

            populateOfferAuditSummary(jdbcConnection);
            populateSharedCodeAuditSummary(jdbcConnection);

        } catch (DatabaseException | SQLException e) {
            throw new CustomChangeException(e);
        }
    }

    private void populateOfferAuditSummary(JdbcConnection jdbcConnection)
            throws DatabaseException, SQLException {
        PreparedStatement queryStatementForOfferAuditSummary =
                jdbcConnection.prepareStatement(
                        "SELECT detail.offer_id, COUNT(detail.*) AS recordCount, detail.trk_application_context_id, detail.trk_level, detail.trk_tenant_id " +
                                "FROM blc_offer_audit_detail detail " +
                                "LEFT JOIN blc_offer_audit_summary summary " +
                                "  ON detail.offer_id = summary.offer_id " +
                                "    AND COALESCE(detail.trk_application_context_id, 'null') = COALESCE(summary.trk_application_context_id, 'null') " +
                                "    AND detail.trk_tenant_id = summary.trk_tenant_id " +
                                "WHERE detail.trk_archived != 'Y' AND summary.id IS NULL " +
                                "GROUP BY detail.offer_id, detail.trk_application_context_id, detail.trk_level, detail.trk_tenant_id ");
        PreparedStatement insertOfferAuditSummaryStatement =
                jdbcConnection.prepareStatement(
                        "INSERT INTO blc_offer_audit_summary (id, offer_id, total_usages, trk_application_context_id, trk_level, trk_tenant_id) VALUES (?, ?, ?, ?, ?, ?)");

        int batchCounter = 0;
        ResultSet offerAuditDetails = queryStatementForOfferAuditSummary.executeQuery();
        while (offerAuditDetails.next()) {
            String offerId = offerAuditDetails.getString("offer_id");
            int totalUsages = offerAuditDetails.getInt("recordCount");
            String applicationContextId = offerAuditDetails.getString("trk_application_context_id");
            int trackingLevel = offerAuditDetails.getInt("trk_level");
            String tenantId = offerAuditDetails.getString("trk_tenant_id");

            // need to build primary key, see JpaAlternateLifecycleOfferAuditSummaryRepository
            ContextInfo contextInfo = new ContextInfo();
            ContextRequest contextRequest =
                    (new ContextRequest())
                            .withApplicationId(applicationContextId)
                            .withTenantId(tenantId);
            contextInfo.setContextRequest(contextRequest);
            String offerAuditSummaryId = KeyUtils.buildKey(offerId, contextInfo);

            insertOfferAuditSummaryStatement.setString(1, offerAuditSummaryId);
            insertOfferAuditSummaryStatement.setString(2, offerId);
            insertOfferAuditSummaryStatement.setInt(3, totalUsages);
            insertOfferAuditSummaryStatement.setString(4, applicationContextId);

            if (trackingLevel == 0) {
                insertOfferAuditSummaryStatement.setNull(5, 4);
            } else {
                insertOfferAuditSummaryStatement.setInt(5, trackingLevel);
            }

            insertOfferAuditSummaryStatement.setString(6, tenantId);
            insertOfferAuditSummaryStatement.addBatch();

            if (++batchCounter % BATCH_SIZE == 0) {
                insertOfferAuditSummaryStatement.executeBatch();
            }
        }

        insertOfferAuditSummaryStatement.executeBatch();

        insertOfferAuditSummaryStatement.close();
        // don't close DB connection
    }

    private void populateSharedCodeAuditSummary(JdbcConnection jdbcConnection)
            throws DatabaseException, SQLException {
        PreparedStatement queryStatementForSharedCodeAuditSummary =
                jdbcConnection.prepareStatement(
                        "SELECT detail.offer_code_id, COUNT(detail.*) AS recordCount, detail.shared_code, detail.trk_application_context_id, detail.trk_level, detail.trk_tenant_id " +
                                "FROM blc_offer_audit_detail detail " +
                                "LEFT JOIN blc_shared_code_audit_summary summary " +
                                "  ON detail.offer_code_id = summary.offer_code_id " +
                                "    AND COALESCE(detail.trk_application_context_id, 'null') = COALESCE(summary.trk_application_context_id, 'null') " +
                                "    AND detail.trk_tenant_id = summary.trk_tenant_id " +
                                "WHERE detail.trk_archived != 'Y' AND detail.shared_code IS NOT NULL AND summary.id IS NULL " +
                                "GROUP BY detail.offer_code_id, detail.shared_code, detail.trk_application_context_id, detail.trk_level, detail.trk_tenant_id ");
        PreparedStatement insertSharedCodeAuditSummaryStatement =
                jdbcConnection.prepareStatement(
                        "INSERT INTO blc_shared_code_audit_summary (id, offer_code_id, total_usages, offer_code, trk_application_context_id, trk_level, trk_tenant_id) VALUES (?, ?, ?, ?, ?, ?, ?)");

        int batchCounter = 0;
        ResultSet offerAuditDetailsForOfferCodes =
                queryStatementForSharedCodeAuditSummary.executeQuery();

        while (offerAuditDetailsForOfferCodes.next()) {
            String offerCodeId = offerAuditDetailsForOfferCodes.getString("offer_code_id");
            int totalUsages = offerAuditDetailsForOfferCodes.getInt("recordCount");
            String code = offerAuditDetailsForOfferCodes.getString("shared_code");
            String applicationContextId =
                    offerAuditDetailsForOfferCodes.getString("trk_application_context_id");
            int trackingLevel = offerAuditDetailsForOfferCodes.getInt("trk_level");
            String tenantId = offerAuditDetailsForOfferCodes.getString("trk_tenant_id");

            // need to build primary key, see JpaAlternateLifecycleSharedCodeAuditSummaryRepository
            ContextInfo contextInfo = new ContextInfo();
            ContextRequest contextRequest =
                    (new ContextRequest())
                            .withApplicationId(applicationContextId)
                            .withTenantId(tenantId);
            contextInfo.setContextRequest(contextRequest);
            String sharedCodeAuditSummaryId = KeyUtils.buildKey(offerCodeId, contextInfo);

            insertSharedCodeAuditSummaryStatement.setString(1, sharedCodeAuditSummaryId);
            insertSharedCodeAuditSummaryStatement.setString(2, offerCodeId);
            insertSharedCodeAuditSummaryStatement.setInt(3, totalUsages);
            insertSharedCodeAuditSummaryStatement.setString(4, code);
            insertSharedCodeAuditSummaryStatement.setString(5, applicationContextId);

            if (trackingLevel == 0) {
                insertSharedCodeAuditSummaryStatement.setNull(6, 4);
            } else {
                insertSharedCodeAuditSummaryStatement.setInt(6, trackingLevel);
            }

            insertSharedCodeAuditSummaryStatement.setString(7, tenantId);
            insertSharedCodeAuditSummaryStatement.addBatch();

            if (++batchCounter % BATCH_SIZE == 0) {
                insertSharedCodeAuditSummaryStatement.executeBatch();
            }
        }

        insertSharedCodeAuditSummaryStatement.executeBatch();

        insertSharedCodeAuditSummaryStatement.close();
        // don't close DB connection
    }

    @Override
    public String getConfirmationMessage() {
        return "The data for blc_offer_audit_summary and blc_shared_code_audit_summary successfully populated!";
    }

    @Override
    public void setUp() throws SetupException {
        // do nothing
    }

    @Override
    public void setFileOpener(ResourceAccessor resourceAccessor) {
        // do nothing
    }

    @Override
    public ValidationErrors validate(Database database) {
        return null;
    }
}

Add this to new changelog file that you just created, e.g offer.data-migration.changelog-post-process.xml, with the following content

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
     <changeSet id="shared-code-migration" author="broadleaf">
        <customChange class="com.myapp.changeset.SharedCodesToOfferCodesMigration" />
     </changeSet>
    <changeSet id="offer-and-offer-code-audit-summary-migration" author="broadleaf">
        <customChange class="com.myapp.changeset.PopulateOfferAndOfferCodeAuditSummaryMigration" />
    </changeSet>
</databaseChangeLog>