link-stack/apps/bridge-worker/tasks/formstack/create-ticket-from-form.ts
2025-10-26 15:39:55 +01:00

181 lines
5.7 KiB
TypeScript

import { createLogger } from "@link-stack/logger";
import { Zammad, getUser } from "../../lib/zammad.js";
const logger = createLogger('create-ticket-from-form');
export interface CreateTicketFromFormOptions {
formData: any;
receivedAt: string;
}
const createTicketFromFormTask = async (
options: CreateTicketFromFormOptions,
): Promise<void> => {
const { formData, receivedAt } = options;
logger.info({
formData,
receivedAt,
formDataKeys: Object.keys(formData),
}, 'Processing Formstack form submission');
// Extract data from Formstack payload
const {
FormID,
UniqueID,
name,
signal_number,
type_of_help_requested,
type_of_organization,
urgency_level,
email_address,
phone_number,
address,
description_of_issue,
preferred_contact_method,
available_times_for_contact,
how_did_you_hear_about_us,
preferred_language,
} = formData;
// Build full name
const fullName = name
? `${name.first || ''} ${name.last || ''}`.trim()
: 'Unknown';
// Get organization name from form data
const organizationName = type_of_organization || '';
// Build ticket title - matching ngo-isac-uploader pattern
let title = fullName;
if (organizationName) {
title += ` - ${organizationName}`;
}
if (type_of_help_requested) {
title += ` - ${type_of_help_requested}`;
}
// Build article body - only description and metadata
// All other fields go into custom Zammad ticket fields
const body = description_of_issue
? `<p>${description_of_issue}</p>
<hr>
<p><em>Submitted via Formstack | Form ID: ${FormID} | Submission ID: ${UniqueID} | Received: ${receivedAt}</em></p>`
: `<p><em>No description provided</em></p>
<hr>
<p><em>Submitted via Formstack | Form ID: ${FormID} | Submission ID: ${UniqueID} | Received: ${receivedAt}</em></p>`;
// Get Zammad configuration from environment
const zammadUrl = process.env.ZAMMAD_URL || 'http://zammad-nginx:8080';
const zammadToken = process.env.ZAMMAD_API_TOKEN;
if (!zammadToken) {
logger.error('ZAMMAD_API_TOKEN environment variable is not configured');
throw new Error('ZAMMAD_API_TOKEN is required');
}
const zammad = Zammad({ token: zammadToken }, zammadUrl);
try {
// Get or create user based on contact info
// Priority: signal_number > phone_number > email_address
let customer;
// Try to find existing user by phone or email
if (signal_number || phone_number) {
const phoneToSearch = signal_number || phone_number;
customer = await getUser(zammad, phoneToSearch);
if (customer) {
logger.info({ customerId: customer.id, method: 'phone' }, 'Found existing user by phone');
}
}
if (!customer && email_address) {
// Search by email if phone search didn't work
const emailResults = await zammad.user.search(`email:${email_address}`);
if (emailResults.length > 0) {
customer = emailResults[0];
logger.info({ customerId: customer.id, method: 'email' }, 'Found existing user by email');
}
}
if (!customer) {
// Create new user with all available contact information
logger.info('Creating new user from form submission');
customer = await zammad.user.create({
firstname: name?.first || '',
lastname: name?.last || '',
email: email_address || `${UniqueID}@formstack.local`,
phone: signal_number || phone_number || '',
note: `User created from Formstack submission ${UniqueID}`,
});
}
logger.info({
customerId: customer.id,
customerEmail: customer.email,
customerPhone: customer.phone,
}, 'Customer identified/created');
// Build address parts
const streetAddress = address?.address || '';
const cityValue = address?.city || '';
const stateValue = address?.state || '';
const zipValue = address?.zip || '';
// Create the ticket with custom fields mapped to Zammad attributes
// Following the pattern from ngo-isac-uploader where all form data
// goes into structured fields rather than HTML body
const ticket = await zammad.ticket.create({
title,
group: "Users", // Default group - you may want to make this configurable
customer_id: customer.id,
// Custom fields - these will be populated in Zammad's ticket attributes
// NOTE: 'organization', 'formstack_form_id', 'formstack_submission_id'
// fields could not be created due to naming conflicts, so metadata
// is included in the ticket body instead
signal_number: signal_number || undefined,
type_of_help_requested: type_of_help_requested || undefined,
type_of_organization: type_of_organization || undefined,
urgency_level: urgency_level || undefined,
city: cityValue || undefined,
us_state: stateValue || undefined,
zip_code: zipValue || undefined,
street_address: streetAddress || undefined,
preferred_contact_method: preferred_contact_method || undefined,
available_times: available_times_for_contact || undefined,
where_heard: how_did_you_hear_about_us || undefined,
preferred_language: preferred_language || undefined,
// Article with just the description
article: {
body,
subject: title,
content_type: "text/html",
type: "note",
},
});
logger.info({
ticketId: ticket.id,
customerId: customer.id,
formId: FormID,
submissionId: UniqueID,
}, 'Zammad ticket created successfully');
} catch (error: any) {
logger.error({
error: error.message,
stack: error.stack,
output: error.output,
formId: FormID,
submissionId: UniqueID,
}, 'Failed to create Zammad ticket');
throw error;
}
};
export default createTicketFromFormTask;