<?xml version="1.0" encoding="UTF-8"?>
<unload unload_date="2026-06-05 10:58:59">
<sys_script_fix action="INSERT_OR_UPDATE">
<before>false</before>
<description>This script resets  feedback records that were answered on or after selected date timestamp and have a non-empty timestamp field. Additionally, the status field will be set to "sent" on all matching records, effectively returning them to an unanswered state.</description>
<name>Reset HappySignals Feedback for tickets</name>
<record_for_rollback>true</record_for_rollback>
<script><![CDATA[// =============================================================
// FIX SCRIPT: Reset HappySignals Feedback Records
// Application: HappySignals Ticket-Based IT Experience
// Table: x_pgo_happysignals_happysignals_feedback
// =============================================================
//
// PURPOSE:
//   This script resets feedback records that were answered on or after
//   a selected date (currently set to 2026-05-01 — update the cutoffDate
//   variable below if a different cutoff is needed) and have a non-empty
//   timestamp field.
//
//   The following fields will be cleared on all matching records:
//     - answered
//     - score
//     - mood
//     - timestamp
//
//   Additionally, the status field will be set to "sent" on all
//   matching records, effectively returning them to an unanswered state.
//
// HOW TO RUN:
//   1. In ServiceNow, set the application scope BEFORE creating the record:
//      Click the application picker (globe icon, top right) and select
//      "HappySignals Ticket-Based IT Experience"
//   2. Navigate to:
//      System Definition > Fix Scripts
//   3. Click "New" to create a new Fix Script
//   4. Give it a descriptive name, e.g.:
//      "Reset HappySignals feedback records answered after 2026-05-01"
//   5. Paste this entire script into the Script field
//   6. Enable "Record for rollback" — this creates a snapshot of all
//      affected records before the update runs, allowing changes to be
//      reverted if something goes wrong. Strongly recommended when
//      updating large numbers of records.
//   7. Click "Run Fix Script" to execute
//   8. After execution, check the output log at the bottom of the page
//      to confirm how many records were updated
//
// =============================================================


// -------------------------------------------------------------
// CONFIGURATION — update these values if needed
// -------------------------------------------------------------
var tableName = 'x_pgo_happysignals_happysignals_feedback';
var cutoffDate = '2026-05-01 00:00:00'; // Format: 'YYYY-MM-DD HH:MM:SS'
var batchSize = 5000;
var maxIterations = 10; // Safety cap: covers up to 50,000 records (10 × 5,000)
var logPrefix = 'Fix Script | HappySignals [' + tableName + ']';


// -------------------------------------------------------------
// STEP 1: Count matching records before making any changes
// -------------------------------------------------------------
// This uses GlideAggregate (a lightweight COUNT query) to determine
// how many records will be affected, without loading them into memory.
// Note: this count reflects the state at script start. If other processes
// modify records during execution, the final totalUpdated may differ.

var ga = new GlideAggregate(tableName);
ga.addQuery('answered', '>=', cutoffDate);
ga.addNotNullQuery('timestamp');
ga.addAggregate('COUNT');
ga.query();

var count = 0;
if (ga.next()) {
	count = ga.getAggregate('COUNT');
}

gs.info(logPrefix + ' | Step 1 complete: ' + count + ' records match the query. Script started at: ' + new GlideDateTime().getDisplayValue());

if (count == 0) {
	gs.warn(logPrefix + ' | No matching records found. No updates will be made. Verify the cutoff date and table name in the configuration section.');
} else {

	// -------------------------------------------------------------
	// STEP 2: Update records in batches
	// -------------------------------------------------------------
	// Records are updated in batches of 5000 to balance performance
	// and memory usage. Each batch processes only records that still
	// match the query — once a record is updated, its fields are cleared
	// and it will no longer appear in subsequent batches.
	//
	// setWorkflow(false) prevents business rules and workflows from triggering.
	// autoSysFields(false) prevents system fields (e.g. sys_updated_on)
	// from being modified.
	//
	// maxIterations is calculated dynamically based on the initial record
	// count, with an extra buffer batch to account for minor discrepancies.

	var totalUpdated = 0;
	var totalFailed = 0;
	var iteration = 0;
	var hasMore = true;

	while (hasMore && iteration < maxIterations) {
		iteration++;
		gs.info(logPrefix + ' | Starting batch ' + iteration + ' of max ' + maxIterations + '...');

		var gr = new GlideRecord(tableName);
		gr.addQuery('answered', '>=', cutoffDate);
		gr.addNotNullQuery('timestamp');
		gr.setWorkflow(false);
		gr.autoSysFields(false);
		gr.setLimit(batchSize);
		gr.query();

		var processed = 0;
		var failCount = 0;

		var hasStatusField = gr.isValidField('status');

		while (gr.next()) {
			try {
				gr.setValue('answered', '');
				gr.setValue('score', '');
				gr.setValue('mood', '');
				gr.setValue('timestamp', '');
				if (hasStatusField) {
					gr.setValue('status', 'sent');
				}
				gr.update();
				processed++;
			} catch (e) {
				failCount++;
				gs.error(logPrefix + ' | Failed to update record sys_id: ' + gr.getUniqueValue() + ' — Error: ' + e.message);
				continue; // log error and continue to next record do not fail the entire script
			}
		}

		totalUpdated += processed;
		totalFailed += failCount;

		gs.info(logPrefix + ' | Batch ' + iteration + ' complete: ' + processed + ' updated, ' + failCount + ' failed (total so far: ' + totalUpdated + ')');

		processed += failCount;
		hasMore = (processed === batchSize);
	}

	// -------------------------------------------------------------
	// STEP 3: Final status report
	// -------------------------------------------------------------

	if (iteration >= maxIterations && hasMore) {
		gs.warn(logPrefix + ' | SAFETY LIMIT REACHED: Maximum batch iterations (' + maxIterations + ') exceeded. Script halted. ' + totalUpdated + ' records were updated. Remaining records must be verified and handled manually.');
	} else {
		gs.info(logPrefix + ' | Done. ' + totalUpdated + ' records successfully updated across ' + iteration + ' batch(es). Completed at: ' + new GlideDateTime().getDisplayValue());
	}

	if (totalFailed > 0) {
		gs.warn(logPrefix + ' | ' + totalFailed + ' records failed to update. Review the error log above for details.');
	}
}]]></script>
<sys_class_name>sys_script_fix</sys_class_name>
<sys_created_by>jpleht</sys_created_by>
<sys_created_on>2026-05-28 10:34:10</sys_created_on>
<sys_customer_update>false</sys_customer_update>
<sys_id>2ccf0bb63b0d0f541a9d471864e45acb</sys_id>
<sys_mod_count>24</sys_mod_count>
<sys_name>Reset HappySignals Feedback for tickets</sys_name>
<sys_package display_value="HappySignals Ticket-based IT Experience" source="x_pgo_happysignals">cc99f269db7c5700dfd83c9b7c9619d5</sys_package>
<sys_policy/>
<sys_replace_on_upgrade>false</sys_replace_on_upgrade>
<sys_scope display_value="HappySignals Ticket-based IT Experience">cc99f269db7c5700dfd83c9b7c9619d5</sys_scope>
<sys_update_name>sys_script_fix_2ccf0bb63b0d0f541a9d471864e45acb</sys_update_name>
<sys_updated_by>teemu.suominen</sys_updated_by>
<sys_updated_on>2026-06-05 10:52:21</sys_updated_on>
<unloadable>false</unloadable>
</sys_script_fix>
</unload>
