181 lines
5.7 KiB
TypeScript
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;
|