Merge pull request 'finley/110-extra-ccs' (#113) from finley/110-extra-ccs into master
Reviewed-on: cmc/cmc-sales#113 Reviewed-by: kzrl <karl@cordes.com.au>
This commit is contained in:
commit
d804a88d15
|
|
@ -389,7 +389,6 @@ ENDINSTRUCTIONS;
|
|||
|
||||
|
||||
|
||||
|
||||
if(isset($source_document_id)) {
|
||||
//This is not ideal. But nothing else is either.
|
||||
$sourceDoc = $this->Document->find('first', array('conditions' => array('Document.id' => $source_document_id)));
|
||||
|
|
@ -1114,21 +1113,18 @@ EOT;
|
|||
$this->Email->delivery = 'smtp';
|
||||
|
||||
$document = $this->Document->read(null,$id);
|
||||
|
||||
if(empty($document['Document']['pdf_filename'])) {
|
||||
$this->Session->setFlash(__('Error. Please generate the PDF before attempting to email it', true));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$pdf_dir = Configure::read('pdf_directory');
|
||||
|
||||
$attachment_files = array($pdf_dir.$document['Document']['pdf_filename']);
|
||||
foreach($document['DocumentAttachment'] as $document_attachment) {
|
||||
$attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']);
|
||||
$attachment_files[] = $attachment['Attachment']['file'];
|
||||
$attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']);
|
||||
$attachment_files[] = $attachment['Attachment']['file'];
|
||||
}
|
||||
$this->Email->attachments = $attachment_files;
|
||||
|
||||
}
|
||||
$enquiry = $this->Document->getEnquiry($document);
|
||||
|
||||
|
|
@ -1209,6 +1205,221 @@ EOT;
|
|||
|
||||
}
|
||||
|
||||
function format_email($email) {
|
||||
$email = trim($email);
|
||||
// Basic RFC 5322 email validation
|
||||
if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) {
|
||||
return '';
|
||||
}
|
||||
return "$email <$email>";
|
||||
}
|
||||
|
||||
function parse_email_to_array($input) {
|
||||
try {
|
||||
if (empty($input) || !is_string($input)) {
|
||||
return array();
|
||||
}
|
||||
$input = trim($input);
|
||||
if ($input === '') {
|
||||
return array();
|
||||
}
|
||||
if (strpos($input, ',') !== false) {
|
||||
$parts = explode(',', $input);
|
||||
$result = array();
|
||||
foreach ($parts as $email) {
|
||||
$email = $this->format_email($email);
|
||||
if ($email !== '') {
|
||||
$result[] = $email;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
$result = $this->format_email($input);
|
||||
$array[] = $result;
|
||||
return $array;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Email the PDF(document + attachments) for this Document to custom recipients.
|
||||
*
|
||||
* @param int $id - Document ID
|
||||
* @param string $to - Recipient email address (comma-separated if multiple)
|
||||
* @param string|null $cc - CC email address(es), optional (comma-separated)
|
||||
* @param string|null $bcc - BCC email address(es), optional (comma-separated)
|
||||
*/
|
||||
function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $bcc = null) {
|
||||
// Disable automatic rendering of a view
|
||||
$this->autoRender = false;
|
||||
|
||||
// Retrieve recipient emails from form data if not provided as arguments
|
||||
if (empty($to) && !empty($this->params['form']['to'])) {
|
||||
$to = $this->params['form']['to'];
|
||||
}
|
||||
if (empty($cc) && isset($this->params['form']['cc'])) {
|
||||
$cc = $this->params['form']['cc'];
|
||||
}
|
||||
if (empty($bcc) && isset($this->params['form']['bcc'])) {
|
||||
$bcc = $this->params['form']['bcc'];
|
||||
}
|
||||
|
||||
// Basic validation for required parameters
|
||||
if (empty($id) || empty($to)) {
|
||||
$msg = 'Document ID and recipient email are required.';
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure SMTP settings for email delivery
|
||||
$this->Email->smtpOptions = Configure::read('smtp_settings');
|
||||
$this->Email->delivery = 'smtp';
|
||||
|
||||
// Load the document and its attachments
|
||||
$document = $this->Document->read(null, $id);
|
||||
error_log("[email_pdf_with_custom_recipients] Document loaded: " . print_r($document['Document'], true));
|
||||
error_log("[email_pdf_with_custom_recipients] DocumentAttachments: " . print_r($document['DocumentAttachment'], true));
|
||||
if (empty($document) || empty($document['Document'])) {
|
||||
$msg = 'Document not found.';
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the PDF has been generated before emailing
|
||||
if (empty($document['Document']['pdf_filename'])) {
|
||||
$msg = 'Error. Please generate the PDF before attempting to email it';
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the list of attachments (PDF + any additional attachments)
|
||||
$pdf_dir = Configure::read('pdf_directory');
|
||||
$attachment_files = array($pdf_dir.$document['Document']['pdf_filename']);
|
||||
foreach($document['DocumentAttachment'] as $document_attachment) {
|
||||
$attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']);
|
||||
$attachment_files[] = $attachment['Attachment']['file'];
|
||||
error_log("[email_pdf_with_custom_recipients] Added attachment: " . $attachment['Attachment']['file']);
|
||||
}
|
||||
if (!empty($document['DocumentAttachment'])) {
|
||||
foreach ($document['DocumentAttachment'] as $document_attachment) {
|
||||
if (!empty($document_attachment['attachment_id'])) {
|
||||
$attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']);
|
||||
if (!empty($attachment['Attachment']['file'])) {
|
||||
$attachment_files[] = $attachment['Attachment']['file'];
|
||||
error_log("[email_pdf_with_custom_recipients] Added attachment: " . $attachment['Attachment']['file']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error_log("[email_pdf_with_custom_recipients] All attachments: " . print_r($attachment_files, true));
|
||||
$this->Email->attachments = $attachment_files;
|
||||
|
||||
// Get related enquiry for the document
|
||||
$enquiry = $this->Document->getEnquiry($document);
|
||||
|
||||
// Parse and validate recipient email addresses
|
||||
$toArray = $this->parse_email_to_array($to);
|
||||
if (empty($toArray)) {
|
||||
$msg = 'Invalid recipient email address.';
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
} else {
|
||||
$this->Email->to = implode(', ', $toArray);
|
||||
}
|
||||
$ccArray = $this->parse_email_to_array($cc);
|
||||
if (!empty($ccArray)) {
|
||||
$this->Email->cc = $ccArray;
|
||||
}
|
||||
$bccArray = $this->parse_email_to_array($bcc);
|
||||
if (!empty($bccArray)) {
|
||||
$this->Email->bcc = $bccArray;
|
||||
}
|
||||
|
||||
// Set reply-to and from addresses
|
||||
$this->Email->replyTo = 'CMC Technologies - Sales <sales@cmctechnologies.com.au>';
|
||||
$this->Email->from = 'CMC Technologies - Sales <sales@cmctechnologies.com.au>';
|
||||
|
||||
// Determine document type, email template, and subject
|
||||
$docType = $this->Document->getDocType($document);
|
||||
$template = $docType . '_email';
|
||||
$subject = !empty($enquiry['Enquiry']['title']) ? $enquiry['Enquiry']['title'] . ' ' : 'Document';
|
||||
error_log("[email_pdf_with_custom_recipients] Enquiry Title: " . empty($enquiry['Enquiry']['title']) . $enquiry['Enquiry']['title']);
|
||||
|
||||
// Customise subject and template based on document type
|
||||
switch($docType) {
|
||||
case 'quote':
|
||||
$subject = !empty($enquiry['Enquiry']['title']) ? "Quotation: " . $enquiry['Enquiry']['title'] : 'Quotation';
|
||||
break;
|
||||
case 'invoice':
|
||||
$subject = $this->invoice_email_subject($document);
|
||||
if (!empty($document['Invoice']['id'])) {
|
||||
$this->set('invoice', $this->Document->Invoice->find('first', array('conditions'=>array('Invoice.id'=>$document['Invoice']['id']))));
|
||||
}
|
||||
if (!empty($document['Invoice']['job_id'])) {
|
||||
$this->set('job', $this->Document->Invoice->Job->find('first', array('conditions'=>array('Job.id'=>$document['Invoice']['job_id']))));
|
||||
}
|
||||
break;
|
||||
case 'purchaseOrder':
|
||||
$subject .= "Purchase Order";
|
||||
$primary_contact = null;
|
||||
if (!empty($document['PurchaseOrder']['principle_id'])) {
|
||||
$primary_contact = $this->Document->User->find('first', array('conditions'=>array('User.principle_id' => $document['PurchaseOrder']['principle_id'],'User.primary_contact' => 1)));
|
||||
}
|
||||
if(empty($primary_contact)) {
|
||||
$msg = 'Unable to send. No primary Principle Contact';
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
}
|
||||
$subject = $this->po_email_subject($document['PurchaseOrder']);
|
||||
if (!empty($document['OrderAcknowledgement']['job_id'])) {
|
||||
$this->set('job', $this->Document->PurchaseOrder->Job->find('first', array('conditions'=>array('Job.id'=>$document['OrderAcknowledgement']['job_id']))));
|
||||
}
|
||||
break;
|
||||
case 'orderAck':
|
||||
$subject = $this->orderack_email_subject($document);
|
||||
if (!empty($document['OrderAcknowledgement']['job_id'])) {
|
||||
$this->set('job', $this->Document->OrderAcknowledgement->Job->find('first', array('conditions'=>array('Job.id'=>$document['OrderAcknowledgement']['job_id']))));
|
||||
}
|
||||
if (!empty($document['OrderAcknowledgement']['signature_required'])) {
|
||||
$template = 'orderAck_email_signature_required';
|
||||
}
|
||||
break;
|
||||
case 'packingList':
|
||||
$subject = $this->packing_list_email_subject($document);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set email template and other parameters
|
||||
$this->Email->template = $template;
|
||||
$this->Email->subject = $subject;
|
||||
$this->Email->sendAs = 'both';
|
||||
$this->Email->charset = 'iso-8859-1';
|
||||
$this->set('enquiry', $enquiry);
|
||||
$this->set('document', $document);
|
||||
$this->set('DocFullName', $this->Document->getDocFullName($document['Document']['type']));
|
||||
|
||||
// Attempt to send the email and handle errors
|
||||
$sent = false;
|
||||
try {
|
||||
$sent = $this->Email->send();
|
||||
} catch (Exception $e) {
|
||||
$msg = 'Exception: ' . $e->getMessage();
|
||||
echo json_encode(array('success' => false, 'message' => $msg));
|
||||
return;
|
||||
}
|
||||
if ($sent) {
|
||||
echo json_encode(array('success' => true, 'message' => 'The Email has been sent'));
|
||||
return;
|
||||
} else {
|
||||
$msg = 'The Email has NOT been sent';
|
||||
echo json_encode(array('success' => false, 'message' => $msg, 'smtp_errors' => $this->Email->smtpError));
|
||||
return;
|
||||
}
|
||||
echo json_encode(array('success' => false, 'message' => 'No response from email function'));
|
||||
}
|
||||
|
||||
// generateShippingReference builds the Shipping Instructions: with the PO number and job titles.
|
||||
function generateShippingInstructions($document_id) {
|
||||
$this->layout = 'ajax';
|
||||
|
|
@ -1331,5 +1542,4 @@ ENDINSTRUCTIONS;
|
|||
}
|
||||
return $newDoc[$model];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,147 @@
|
|||
<div class="docButtons">
|
||||
<button id="paymentReceived">Enter Payment Received</button>
|
||||
<button id="pdfDocButton" data-url="/documents/pdf/<?= $document['Document']['id']; ?>">Generate PDF</button>
|
||||
<button id="emailDocButton" data-url="/documents/email_pdf/<?= $document['Document']['id']; ?>">Email Invoice to customer</button>
|
||||
<button id="emailInvoiceButton" data-url="/documents/email_pdf_with_custom_recipients/<?= $document['Document']['id']; ?>">Email Invoice</button>
|
||||
</div>
|
||||
|
||||
<!-- Email Modal -->
|
||||
<div id="emailModal" class="modal-pop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background:#fff; border:2px solid #1976d2; padding:14px 28px 18px 28px; z-index:1000; min-width:370px; font-size:14px; box-shadow: 0 8px 32px rgba(0,0,0,0.25), 0 1.5px 8px rgba(0,0,0,0.10); border-radius:12px;">
|
||||
<h3 style="font-size:1.5em; margin-top:0; padding-bottom:10px;">Email Invoice</h3>
|
||||
<form id="emailInvoiceForm">
|
||||
<div class="input">
|
||||
<label for="emailTo">To:
|
||||
<span class="info-icon" title="Use comma separated values for multiple addresses">ⓘ</span>
|
||||
</label>
|
||||
<input type="text" id="emailTo" name="to" class="emailInputSmall" required value="<?= isset($accountsEmail) ? h($accountsEmail) : '' ?>" />
|
||||
</div>
|
||||
<div class="input">
|
||||
<label for="emailCc">Cc:
|
||||
<span class="info-icon" title="Use comma separated values for multiple addresses">ⓘ</span>
|
||||
</label>
|
||||
<input type="text" id="emailCc" name="cc" class="emailInputSmall" value="<?= isset($enquiry['Contact']['email']) ? h($enquiry['Contact']['email']) : '' ?>" />
|
||||
</div>
|
||||
<div class="input">
|
||||
<label for="emailBcc">Bcc:
|
||||
<span class="info-icon" title="Use comma separated values for multiple addresses">ⓘ</span>
|
||||
</label>
|
||||
<input type="text" id="emailBcc" name="bcc" class="emailInputSmall" value="sales@cmctechnologies.com.au" />
|
||||
</div>
|
||||
<div id="emailError" style="color:red; display:none; margin-top:8px;"></div>
|
||||
<div class="modal-btn-row">
|
||||
<button type="button" id="cancelEmailInvoice" class="modal-btn">Cancel</button>
|
||||
<button type="submit" id="sendEmailInvoice" class="modal-btn" style="min-width:80px;">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modalOverlay" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.3); z-index:999;"></div>
|
||||
|
||||
<script>
|
||||
document.getElementById('emailInvoiceButton').addEventListener('click', function() {
|
||||
document.getElementById('emailModal').style.display = 'block';
|
||||
document.getElementById('modalOverlay').style.display = 'block';
|
||||
document.getElementById('emailError').style.display = 'none';
|
||||
document.getElementById('emailError').textContent = '';
|
||||
// Reset to default values each time modal opens
|
||||
document.getElementById('emailTo').value = "<?= isset($accountsEmail) ? h($accountsEmail) : '' ?>";
|
||||
document.getElementById('emailCc').value = "<?= isset($enquiry['Contact']['email']) ? h($enquiry['Contact']['email']) : '' ?>";
|
||||
document.getElementById('emailBcc').value = 'sales@cmctechnologies.com.au';
|
||||
// Disable send button if 'to' field is empty
|
||||
var sendBtn = document.getElementById('sendEmailInvoice');
|
||||
sendBtn.disabled = !document.getElementById('emailTo').value.trim();
|
||||
if (sendBtn.disabled) {
|
||||
sendBtn.setAttribute('aria-disabled-msg', 'An invoice recipient must be specified');
|
||||
} else {
|
||||
sendBtn.removeAttribute('aria-disabled-msg');
|
||||
}
|
||||
});
|
||||
document.getElementById('emailTo').addEventListener('input', function() {
|
||||
var sendBtn = document.getElementById('sendEmailInvoice');
|
||||
sendBtn.disabled = !this.value.trim();
|
||||
if (sendBtn.disabled) {
|
||||
sendBtn.setAttribute('aria-disabled-msg', 'An invoice recipient must be specified');
|
||||
} else {
|
||||
sendBtn.removeAttribute('aria-disabled-msg');
|
||||
}
|
||||
});
|
||||
document.getElementById('cancelEmailInvoice').addEventListener('click', function() {
|
||||
document.getElementById('emailModal').style.display = 'none';
|
||||
document.getElementById('modalOverlay').style.display = 'none';
|
||||
});
|
||||
document.getElementById('modalOverlay').addEventListener('click', function() {
|
||||
document.getElementById('emailModal').style.display = 'none';
|
||||
document.getElementById('modalOverlay').style.display = 'none';
|
||||
});
|
||||
document.getElementById('emailInvoiceForm').addEventListener('submit', function(e) {
|
||||
if (!confirm('Are you sure you want to send this invoice?')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var to = document.getElementById('emailTo').value;
|
||||
var cc = document.getElementById('emailCc').value;
|
||||
var bcc = document.getElementById('emailBcc').value;
|
||||
var url = document.getElementById('emailInvoiceButton').getAttribute('data-url');
|
||||
var formData = new FormData();
|
||||
formData.append('to', to);
|
||||
formData.append('cc', cc);
|
||||
formData.append('bcc', bcc);
|
||||
var errorDiv = document.getElementById('emailError');
|
||||
errorDiv.style.display = 'none';
|
||||
errorDiv.textContent = '';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(async response => {
|
||||
// Always read the response as text first, then try to parse as JSON
|
||||
let text = await response.text();
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (err) {
|
||||
data = { error: 'Unexpected server response.', raw: text };
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.then(data => {
|
||||
// Show SMTP errors if present, otherwise show any error or failure message
|
||||
if (data && (data.error || data.smtp_errors || data.success === false)) {
|
||||
var msgArr = [];
|
||||
if (data.smtp_errors) {
|
||||
msgArr.push('SMTP Error: ' + data.smtp_errors);
|
||||
}
|
||||
if (data.error) {
|
||||
msgArr.push(data.error);
|
||||
}
|
||||
if (data.success === false && data.message) {
|
||||
msgArr.push(data.message);
|
||||
}
|
||||
if (data.raw) {
|
||||
msgArr.push(data.raw);
|
||||
} else if (data.error) {
|
||||
msgArr.push('<em>No response receieved.</em>');
|
||||
}
|
||||
if (msgArr.length === 0) {
|
||||
msgArr.push('Failed to send email. No error details provided.');
|
||||
}
|
||||
errorDiv.innerHTML = msgArr.join('<br>');
|
||||
errorDiv.style.display = 'block';
|
||||
} else {
|
||||
alert(data.message || 'Email sent!');
|
||||
document.getElementById('emailModal').style.display = 'none';
|
||||
document.getElementById('modalOverlay').style.display = 'none';
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
errorDiv.innerHTML = 'Failed to send email: ' + (err && err.message ? err.message : err);
|
||||
errorDiv.style.display = 'block';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="docOperations">
|
||||
<h3>Create new Documents based on this</h3>
|
||||
<ul class="document-buttons">
|
||||
|
|
@ -64,3 +202,86 @@
|
|||
<?php //debug($docType);?>
|
||||
|
||||
<? //debug($invoice); ?>
|
||||
|
||||
<style>
|
||||
.emailInputSmall {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.modal-pop {
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.25), 0 1.5px 8px rgba(0,0,0,0.10);
|
||||
border-radius: 12px;
|
||||
border: 2px solid #1976d2;
|
||||
background: linear-gradient(135deg, #f8fafc 80%, #e3f2fd 100%);
|
||||
padding: 28px 28px 18px 28px !important;
|
||||
min-width: 370px;
|
||||
font-size: 14px;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.modal-btn-row {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
/* align-items: center; */
|
||||
}
|
||||
.modal-btn {
|
||||
font-size: 13px !important;
|
||||
padding: 4px 16px !important;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #1976d2;
|
||||
background: #1976d2;
|
||||
color: #fff;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
box-shadow: 0 1px 4px rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
.modal-btn:disabled, #sendEmailInvoice:disabled {
|
||||
background: #eee !important;
|
||||
color: #aaa !important;
|
||||
border: 1px solid #ccc !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
.info-icon {
|
||||
display: inline-block;
|
||||
margin-left: 3px !important;
|
||||
margin-bottom: 2px !important;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
.info-icon:hover::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: -18px;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 1001;
|
||||
}
|
||||
#sendEmailInvoice[aria-disabled-msg]:hover::after {
|
||||
content: attr(aria-disabled-msg);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 110%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
z-index: 1002;
|
||||
pointer-events: none;
|
||||
}
|
||||
#sendEmailInvoice {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue