updates
This commit is contained in:
parent
e5b8affa84
commit
a49322f852
|
|
@ -31,7 +31,7 @@
|
||||||
<!-- Generate EOI Button - Only show if no documents uploaded -->
|
<!-- Generate EOI Button - Only show if no documents uploaded -->
|
||||||
<div v-if="!hasEOI && !hasEOIDocuments" class="d-flex flex-wrap gap-2">
|
<div v-if="!hasEOI && !hasEOIDocuments" class="d-flex flex-wrap gap-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
@click="generateEOI"
|
@click="generateEOI(0, false)"
|
||||||
:loading="isGenerating"
|
:loading="isGenerating"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
|
|
@ -84,66 +84,112 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Signature Links - Only show if EOI is generated but not uploaded/signed -->
|
<!-- Signature Links - Only show if EOI is generated but not uploaded/signed -->
|
||||||
<v-list v-if="hasEOI && !isEOISigned" :density="mobile ? 'compact' : 'comfortable'">
|
<div v-if="hasEOI && !isEOISigned">
|
||||||
<v-list-item class="mb-2">
|
<div class="d-flex align-center mb-3">
|
||||||
<template v-slot:prepend>
|
<v-chip color="info" variant="tonal" size="small" prepend-icon="mdi-account-multiple">
|
||||||
<v-avatar color="primary" :size="mobile ? 32 : 40">
|
Signature Status
|
||||||
<v-icon :size="mobile ? 'small' : 'default'">mdi-account</v-icon>
|
</v-chip>
|
||||||
</v-avatar>
|
<v-btn
|
||||||
</template>
|
icon="mdi-refresh"
|
||||||
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Client Signature Link</v-list-item-title>
|
variant="text"
|
||||||
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle>
|
size="small"
|
||||||
<template v-slot:append>
|
:loading="isCheckingStatus"
|
||||||
<v-btn
|
@click="checkSignatureStatus"
|
||||||
icon="mdi-content-copy"
|
class="ml-2"
|
||||||
variant="text"
|
></v-btn>
|
||||||
:size="mobile ? 'small' : 'default'"
|
</div>
|
||||||
@click="copyLink(interest['Signature Link Client'])"
|
|
||||||
></v-btn>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item class="mb-2">
|
<v-list :density="mobile ? 'compact' : 'comfortable'">
|
||||||
<template v-slot:prepend>
|
<v-list-item class="mb-2">
|
||||||
<v-avatar color="success" :size="mobile ? 32 : 40">
|
<template v-slot:prepend>
|
||||||
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-check</v-icon>
|
<v-avatar color="primary" :size="mobile ? 32 : 40">
|
||||||
</v-avatar>
|
<v-icon :size="mobile ? 'small' : 'default'">mdi-account</v-icon>
|
||||||
</template>
|
</v-avatar>
|
||||||
<v-list-item-title :class="mobile ? 'text-body-2' : ''">CC Signature Link</v-list-item-title>
|
</template>
|
||||||
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle>
|
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Client Signature Link</v-list-item-title>
|
||||||
<template v-slot:append>
|
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle>
|
||||||
<v-btn
|
<template v-slot:append>
|
||||||
icon="mdi-content-copy"
|
<div class="d-flex align-center">
|
||||||
variant="text"
|
<v-icon
|
||||||
:size="mobile ? 'small' : 'default'"
|
v-if="getSignatureStatusIcon('client')"
|
||||||
@click="copyLink(interest['Signature Link CC'])"
|
:color="getSignatureStatusColor('client')"
|
||||||
></v-btn>
|
:size="mobile ? 'small' : 'default'"
|
||||||
</template>
|
class="mr-2"
|
||||||
</v-list-item>
|
>
|
||||||
|
{{ getSignatureStatusIcon('client') }}
|
||||||
|
</v-icon>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-copy"
|
||||||
|
variant="text"
|
||||||
|
:size="mobile ? 'small' : 'default'"
|
||||||
|
@click="copyLink(interest['Signature Link Client'])"
|
||||||
|
></v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
<v-list-item class="mb-2">
|
<v-list-item class="mb-2">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-avatar color="secondary" :size="mobile ? 32 : 40">
|
<v-avatar color="success" :size="mobile ? 32 : 40">
|
||||||
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-tie</v-icon>
|
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-check</v-icon>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</template>
|
</template>
|
||||||
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Developer Signature Link</v-list-item-title>
|
<v-list-item-title :class="mobile ? 'text-body-2' : ''">CC Signature Link</v-list-item-title>
|
||||||
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle>
|
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-btn
|
<div class="d-flex align-center">
|
||||||
icon="mdi-content-copy"
|
<v-icon
|
||||||
variant="text"
|
v-if="getSignatureStatusIcon('cc')"
|
||||||
:size="mobile ? 'small' : 'default'"
|
:color="getSignatureStatusColor('cc')"
|
||||||
@click="copyLink(interest['Signature Link Developer'])"
|
:size="mobile ? 'small' : 'default'"
|
||||||
></v-btn>
|
class="mr-2"
|
||||||
</template>
|
>
|
||||||
</v-list-item>
|
{{ getSignatureStatusIcon('cc') }}
|
||||||
</v-list>
|
</v-icon>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-copy"
|
||||||
|
variant="text"
|
||||||
|
:size="mobile ? 'small' : 'default'"
|
||||||
|
@click="copyLink(interest['Signature Link CC'])"
|
||||||
|
></v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
<!-- Regenerate Button - Only show if EOI is generated but not uploaded/signed -->
|
<v-list-item class="mb-2">
|
||||||
<div v-if="hasEOI && !isEOISigned" class="mt-4">
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="secondary" :size="mobile ? 32 : 40">
|
||||||
|
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-tie</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Developer Signature Link</v-list-item-title>
|
||||||
|
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle>
|
||||||
|
<template v-slot:append>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon
|
||||||
|
v-if="getSignatureStatusIcon('developer')"
|
||||||
|
:color="getSignatureStatusColor('developer')"
|
||||||
|
:size="mobile ? 'small' : 'default'"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
{{ getSignatureStatusIcon('developer') }}
|
||||||
|
</v-icon>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-copy"
|
||||||
|
variant="text"
|
||||||
|
:size="mobile ? 'small' : 'default'"
|
||||||
|
@click="copyLink(interest['Signature Link Developer'])"
|
||||||
|
></v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Regenerate and Delete Generated EOI Buttons - Only show if EOI is generated but not uploaded/signed -->
|
||||||
|
<div v-if="hasEOI && !isEOISigned" class="mt-4 d-flex flex-wrap gap-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
@click="generateEOI"
|
@click="generateEOI(0, true)"
|
||||||
:loading="isGenerating"
|
:loading="isGenerating"
|
||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -151,6 +197,17 @@
|
||||||
>
|
>
|
||||||
Regenerate EOI
|
Regenerate EOI
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="canDeleteGenerated"
|
||||||
|
@click="showDeleteGeneratedDialog = true"
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
prepend-icon="mdi-delete"
|
||||||
|
>
|
||||||
|
Delete Generated EOI
|
||||||
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Delete EOI Button - Only show if EOI is uploaded/signed -->
|
<!-- Delete EOI Button - Only show if EOI is uploaded/signed -->
|
||||||
|
|
@ -274,6 +331,103 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Delete Generated EOI Dialog with Slider Confirmation -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showDeleteGeneratedDialog"
|
||||||
|
:max-width="mobile ? '100%' : '500'"
|
||||||
|
:transition="mobile ? 'dialog-bottom-transition' : 'dialog-transition'"
|
||||||
|
persistent
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon class="mr-2" color="error">mdi-delete-alert</v-icon>
|
||||||
|
Delete Generated EOI Document
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<div class="mb-4">
|
||||||
|
<v-alert type="warning" variant="tonal" class="mb-4">
|
||||||
|
<v-icon>mdi-alert-triangle</v-icon>
|
||||||
|
This will permanently delete the generated EOI document from Documenso.
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
This action will:
|
||||||
|
<ul class="mt-2 mb-4">
|
||||||
|
<li>Delete the document from Documenso platform</li>
|
||||||
|
<li>Remove all signature links</li>
|
||||||
|
<li>Reset the Sales Process Level to "Specific Qualified Interest"</li>
|
||||||
|
<li>Reset the EOI Status to "Awaiting Further Details"</li>
|
||||||
|
<li>Reset the EOI Time Sent field</li>
|
||||||
|
<li>Allow a new EOI to be generated</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div v-if="signatureStatus?.allSigned" class="text-error mb-3">
|
||||||
|
<v-icon class="mr-1">mdi-block-helper</v-icon>
|
||||||
|
Cannot delete: All parties have already signed this document.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="signatureStatus && (signatureStatus.signedRecipients?.length > 0)" class="text-warning mb-3">
|
||||||
|
<v-icon class="mr-1">mdi-alert</v-icon>
|
||||||
|
Warning: Some parties have already signed this document.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!signatureStatus?.allSigned">
|
||||||
|
<v-divider class="mb-4" />
|
||||||
|
|
||||||
|
<div class="text-subtitle-2 mb-3">
|
||||||
|
To confirm deletion, slide the handle all the way to the right:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="position-relative">
|
||||||
|
<v-slider
|
||||||
|
v-model="deleteSliderValue"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
color="error"
|
||||||
|
track-color="grey-lighten-2"
|
||||||
|
thumb-label="always"
|
||||||
|
class="mt-2"
|
||||||
|
:disabled="isDeletingGenerated"
|
||||||
|
>
|
||||||
|
<template v-slot:thumb-label="{ modelValue }">
|
||||||
|
{{ Math.round(modelValue) }}%
|
||||||
|
</template>
|
||||||
|
</v-slider>
|
||||||
|
|
||||||
|
<div class="d-flex justify-space-between text-caption mt-1">
|
||||||
|
<span>Cancel</span>
|
||||||
|
<span :class="deleteSliderValue >= 100 ? 'text-error font-weight-bold' : 'text-grey'">
|
||||||
|
DELETE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
@click="closeDeleteGeneratedDialog"
|
||||||
|
variant="text"
|
||||||
|
:disabled="isDeletingGenerated"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="flat"
|
||||||
|
@click="deleteGeneratedEOI"
|
||||||
|
:loading="isDeletingGenerated"
|
||||||
|
:disabled="deleteSliderValue < 100 || signatureStatus?.allSigned"
|
||||||
|
>
|
||||||
|
<v-icon class="mr-1">mdi-delete</v-icon>
|
||||||
|
Delete Generated EOI
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -297,6 +451,11 @@ const isUploading = ref(false);
|
||||||
const selectedFile = ref<File | null>(null);
|
const selectedFile = ref<File | null>(null);
|
||||||
const showDeleteConfirmDialog = ref(false);
|
const showDeleteConfirmDialog = ref(false);
|
||||||
const isDeleting = ref(false);
|
const isDeleting = ref(false);
|
||||||
|
const signatureStatus = ref<any>(null);
|
||||||
|
const isCheckingStatus = ref(false);
|
||||||
|
const showDeleteGeneratedDialog = ref(false);
|
||||||
|
const isDeletingGenerated = ref(false);
|
||||||
|
const deleteSliderValue = ref(0);
|
||||||
|
|
||||||
const hasEOI = computed(() => {
|
const hasEOI = computed(() => {
|
||||||
return !!(props.interest['Signature Link Client'] ||
|
return !!(props.interest['Signature Link Client'] ||
|
||||||
|
|
@ -316,7 +475,42 @@ const isEOISigned = computed(() => {
|
||||||
return props.interest['EOI Status'] === 'Signed';
|
return props.interest['EOI Status'] === 'Signed';
|
||||||
});
|
});
|
||||||
|
|
||||||
const generateEOI = async (retryCount = 0) => {
|
const canDeleteGenerated = computed(() => {
|
||||||
|
return hasEOI.value && !isEOISigned.value && (!signatureStatus.value?.allSigned);
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkSignatureStatus = async () => {
|
||||||
|
if (!hasEOI.value || isEOISigned.value) return;
|
||||||
|
|
||||||
|
isCheckingStatus.value = true;
|
||||||
|
try {
|
||||||
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
documentStatus: string;
|
||||||
|
unsignedRecipients: any[];
|
||||||
|
signedRecipients: any[];
|
||||||
|
clientSigned: boolean;
|
||||||
|
allSigned: boolean;
|
||||||
|
}>('/api/eoi/check-signature-status', {
|
||||||
|
headers: {
|
||||||
|
'x-tag': '094ut234'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
interestId: props.interest.Id.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
signatureStatus.value = response;
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to check signature status:', error);
|
||||||
|
} finally {
|
||||||
|
isCheckingStatus.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateEOI = async (retryCount = 0, regenerate = false) => {
|
||||||
isGenerating.value = true;
|
isGenerating.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -331,14 +525,17 @@ const generateEOI = async (retryCount = 0) => {
|
||||||
'x-tag': '094ut234'
|
'x-tag': '094ut234'
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
interestId: props.interest.Id.toString()
|
interestId: props.interest.Id.toString(),
|
||||||
|
regenerate: regenerate
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
toast.success(response.documentId === 'existing'
|
toast.success(response.documentId === 'existing'
|
||||||
? 'EOI already exists - signature links retrieved'
|
? 'EOI already exists - signature links retrieved'
|
||||||
: 'EOI generated successfully');
|
: regenerate
|
||||||
|
? 'EOI regenerated successfully'
|
||||||
|
: 'EOI generated successfully');
|
||||||
|
|
||||||
emit('eoi-generated', { signingLinks: response.signingLinks });
|
emit('eoi-generated', { signingLinks: response.signingLinks });
|
||||||
emit('update'); // Trigger parent to refresh data
|
emit('update'); // Trigger parent to refresh data
|
||||||
|
|
@ -352,7 +549,7 @@ const generateEOI = async (retryCount = 0) => {
|
||||||
if (retryCount < 3) {
|
if (retryCount < 3) {
|
||||||
console.log(`Retrying EOI generation... Attempt ${retryCount + 2}/4`);
|
console.log(`Retrying EOI generation... Attempt ${retryCount + 2}/4`);
|
||||||
await new Promise(resolve => setTimeout(resolve, (retryCount + 1) * 1000));
|
await new Promise(resolve => setTimeout(resolve, (retryCount + 1) * 1000));
|
||||||
return generateEOI(retryCount + 1);
|
return generateEOI(retryCount + 1, regenerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show error message after all retries failed
|
// Show error message after all retries failed
|
||||||
|
|
@ -473,7 +670,6 @@ const handleUpload = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const closeUploadDialog = () => {
|
const closeUploadDialog = () => {
|
||||||
showUploadDialog.value = false;
|
showUploadDialog.value = false;
|
||||||
selectedFile.value = null;
|
selectedFile.value = null;
|
||||||
|
|
@ -505,4 +701,94 @@ const deleteEOI = async () => {
|
||||||
isDeleting.value = false;
|
isDeleting.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSignatureStatusIcon = (recipientType: 'client' | 'cc' | 'developer') => {
|
||||||
|
if (!signatureStatus.value) return null;
|
||||||
|
|
||||||
|
const signedRecipients = signatureStatus.value.signedRecipients || [];
|
||||||
|
const unsignedRecipients = signatureStatus.value.unsignedRecipients || [];
|
||||||
|
|
||||||
|
// Check if this recipient type has signed
|
||||||
|
let isSigned = false;
|
||||||
|
|
||||||
|
if (recipientType === 'client') {
|
||||||
|
isSigned = signatureStatus.value.clientSigned || false;
|
||||||
|
} else {
|
||||||
|
// For CC and Developer, check by email
|
||||||
|
const emailMap = {
|
||||||
|
cc: 'sales@portnimara.com',
|
||||||
|
developer: 'dm@portnimara.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = emailMap[recipientType];
|
||||||
|
isSigned = signedRecipients.some((r: any) => r.email === email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSigned ? 'mdi-check-circle' : 'mdi-clock-outline';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSignatureStatusColor = (recipientType: 'client' | 'cc' | 'developer') => {
|
||||||
|
if (!signatureStatus.value) return 'grey';
|
||||||
|
|
||||||
|
const signedRecipients = signatureStatus.value.signedRecipients || [];
|
||||||
|
|
||||||
|
// Check if this recipient type has signed
|
||||||
|
let isSigned = false;
|
||||||
|
|
||||||
|
if (recipientType === 'client') {
|
||||||
|
isSigned = signatureStatus.value.clientSigned || false;
|
||||||
|
} else {
|
||||||
|
// For CC and Developer, check by email
|
||||||
|
const emailMap = {
|
||||||
|
cc: 'sales@portnimara.com',
|
||||||
|
developer: 'dm@portnimara.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = emailMap[recipientType];
|
||||||
|
isSigned = signedRecipients.some((r: any) => r.email === email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSigned ? 'success' : 'warning';
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDeleteGeneratedDialog = () => {
|
||||||
|
showDeleteGeneratedDialog.value = false;
|
||||||
|
deleteSliderValue.value = 0; // Reset slider
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGeneratedEOI = async () => {
|
||||||
|
if (deleteSliderValue.value < 100) return;
|
||||||
|
|
||||||
|
isDeletingGenerated.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await $fetch<{ success: boolean; message: string }>('/api/eoi/delete-generated-document', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'x-tag': '094ut234'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
interestId: props.interest.Id.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
toast.success('Generated EOI deleted successfully');
|
||||||
|
closeDeleteGeneratedDialog();
|
||||||
|
emit('update'); // Refresh parent data
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to delete generated EOI:', error);
|
||||||
|
toast.error(error.data?.statusMessage || 'Failed to delete generated EOI');
|
||||||
|
} finally {
|
||||||
|
isDeletingGenerated.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check signature status when component mounts and when interest changes
|
||||||
|
watchEffect(() => {
|
||||||
|
if (hasEOI.value && !isEOISigned.value) {
|
||||||
|
checkSignatureStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -43,19 +43,83 @@ export default defineEventHandler(async (event) => {
|
||||||
throw createError({ statusCode: 404, statusMessage: "Interest not found" });
|
throw createError({ statusCode: 404, statusMessage: "Interest not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Documenso API configuration (declare early for use in regeneration)
|
||||||
|
const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
||||||
|
const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
||||||
|
|
||||||
// Check if EOI already exists (has signature links)
|
// Check if EOI already exists (has signature links)
|
||||||
if (interest['Signature Link Client'] && interest['Signature Link CC'] && interest['Signature Link Developer']) {
|
const hasExistingEOI = !!(interest['Signature Link Client'] && interest['Signature Link CC'] && interest['Signature Link Developer']);
|
||||||
console.log('EOI already exists, returning existing links');
|
|
||||||
return {
|
if (hasExistingEOI) {
|
||||||
success: true,
|
console.log('EOI already exists, checking if regeneration is needed');
|
||||||
documentId: 'existing',
|
|
||||||
clientSigningUrl: interest['Signature Link Client'],
|
// For regeneration, we need to delete the old document first
|
||||||
signingLinks: {
|
const regenerateRequested = body.regenerate === true;
|
||||||
'Client': interest['Signature Link Client'],
|
|
||||||
'CC': interest['Signature Link CC'],
|
if (!regenerateRequested) {
|
||||||
'Developer': interest['Signature Link Developer']
|
console.log('Returning existing EOI links');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
documentId: 'existing',
|
||||||
|
clientSigningUrl: interest['Signature Link Client'],
|
||||||
|
signingLinks: {
|
||||||
|
'Client': interest['Signature Link Client'],
|
||||||
|
'CC': interest['Signature Link CC'],
|
||||||
|
'Developer': interest['Signature Link Developer']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.log('Regeneration requested, deleting old document first');
|
||||||
|
|
||||||
|
// Try to delete the old document from Documenso
|
||||||
|
try {
|
||||||
|
const externalId = `loi-${interestId}`;
|
||||||
|
|
||||||
|
// Import the delete utility functions
|
||||||
|
const { getDocumesoDocumentByExternalId, checkDocumentSignatureStatus } = await import('~/server/utils/documeso');
|
||||||
|
|
||||||
|
const document = await getDocumesoDocumentByExternalId(externalId);
|
||||||
|
if (document) {
|
||||||
|
// Check if all parties have signed
|
||||||
|
const signatureStatus = await checkDocumentSignatureStatus(document.id);
|
||||||
|
|
||||||
|
if (signatureStatus.allSigned) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Cannot regenerate: All parties have already signed this document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Deleting old document from Documenso:', document.id);
|
||||||
|
|
||||||
|
const deleteResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${document.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${documensoApiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!deleteResponse.ok) {
|
||||||
|
console.warn('Failed to delete old document from Documenso, continuing with new generation');
|
||||||
|
} else {
|
||||||
|
console.log('Successfully deleted old document from Documenso');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn('Error during old document cleanup:', error.message);
|
||||||
|
// Continue with new document generation even if cleanup fails
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// Reset signature links so new ones will be generated
|
||||||
|
await updateInterest(interestId, {
|
||||||
|
'Signature Link Client': undefined,
|
||||||
|
'Signature Link CC': undefined,
|
||||||
|
'Signature Link Developer': undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Old document cleanup completed, proceeding with new generation');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
|
|
@ -105,8 +169,6 @@ export default defineEventHandler(async (event) => {
|
||||||
const berthNumbers = berths.map(b => b['Mooring Number']).join(', ');
|
const berthNumbers = berths.map(b => b['Mooring Number']).join(', ');
|
||||||
|
|
||||||
// Documenso API configuration
|
// Documenso API configuration
|
||||||
const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
|
||||||
const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
|
||||||
const templateId = '9';
|
const templateId = '9';
|
||||||
|
|
||||||
if (!documensoApiKey || !documensoBaseUrl) {
|
if (!documensoApiKey || !documensoBaseUrl) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { getInterestById, updateInterest } from '~/server/utils/nocodb';
|
||||||
|
import { getDocumesoDocumentByExternalId, checkDocumentSignatureStatus } from '~/server/utils/documeso';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const xTagHeader = getRequestHeader(event, "x-tag");
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Request received with x-tag:', xTagHeader);
|
||||||
|
|
||||||
|
if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
|
||||||
|
console.error('[EOI Delete Generated] Authentication failed - invalid x-tag');
|
||||||
|
throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { interestId } = body;
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Interest ID:', interestId);
|
||||||
|
|
||||||
|
if (!interestId) {
|
||||||
|
console.error('[EOI Delete Generated] No interest ID provided');
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Interest ID is required',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current interest data
|
||||||
|
const interest = await getInterestById(interestId);
|
||||||
|
if (!interest) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: 'Interest not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are signature links (indicating a generated document)
|
||||||
|
const hasSignatureLinks = !!(interest['Signature Link Client'] ||
|
||||||
|
interest['Signature Link CC'] ||
|
||||||
|
interest['Signature Link Developer']);
|
||||||
|
|
||||||
|
if (!hasSignatureLinks) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'No generated EOI document found for this interest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check signature status to prevent deletion of fully signed documents
|
||||||
|
const externalId = `loi-${interestId}`;
|
||||||
|
let documentId: number | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const document = await getDocumesoDocumentByExternalId(externalId);
|
||||||
|
if (document) {
|
||||||
|
documentId = document.id;
|
||||||
|
|
||||||
|
// Check if all parties have signed
|
||||||
|
const signatureStatus = await checkDocumentSignatureStatus(documentId);
|
||||||
|
|
||||||
|
if (signatureStatus.allSigned) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Cannot delete: All parties have already signed this document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Document found in Documenso:', documentId);
|
||||||
|
console.log('[EOI Delete Generated] Signature status:', {
|
||||||
|
allSigned: signatureStatus.allSigned,
|
||||||
|
clientSigned: signatureStatus.clientSigned,
|
||||||
|
signedCount: signatureStatus.signedRecipients.length,
|
||||||
|
unsignedCount: signatureStatus.unsignedRecipients.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn('[EOI Delete Generated] Could not find or check Documenso document:', error.message);
|
||||||
|
// Continue with deletion even if Documenso document not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete document from Documenso if it exists
|
||||||
|
if (documentId) {
|
||||||
|
try {
|
||||||
|
const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
||||||
|
const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
||||||
|
|
||||||
|
if (!documensoApiKey || !documensoBaseUrl) {
|
||||||
|
console.error('[EOI Delete Generated] Documenso configuration missing');
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Documenso configuration missing',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Deleting document from Documenso:', documentId);
|
||||||
|
|
||||||
|
const deleteResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${documentId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${documensoApiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!deleteResponse.ok) {
|
||||||
|
const errorText = await deleteResponse.text();
|
||||||
|
console.error('[EOI Delete Generated] Failed to delete from Documenso:', errorText);
|
||||||
|
throw new Error(`Failed to delete document from Documenso: ${deleteResponse.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Successfully deleted document from Documenso');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[EOI Delete Generated] Error deleting from Documenso:', error);
|
||||||
|
// Don't throw here - continue with database cleanup even if Documenso deletion fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset interest fields
|
||||||
|
const updateData = {
|
||||||
|
'EOI Status': 'Awaiting Further Details' as const,
|
||||||
|
'Sales Process Level': 'Specific Qualified Interest' as const,
|
||||||
|
'EOI Time Sent': undefined,
|
||||||
|
'Signature Link Client': undefined,
|
||||||
|
'Signature Link CC': undefined,
|
||||||
|
'Signature Link Developer': undefined,
|
||||||
|
'Documeso ID': undefined,
|
||||||
|
'reminder_enabled': false
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Resetting interest fields');
|
||||||
|
|
||||||
|
// Update the interest
|
||||||
|
await updateInterest(interestId, updateData);
|
||||||
|
|
||||||
|
console.log('[EOI Delete Generated] Delete completed successfully');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Generated EOI document deleted successfully',
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[EOI Delete Generated] Failed to delete generated EOI document:', error);
|
||||||
|
console.error('[EOI Delete Generated] Error stack:', error.stack);
|
||||||
|
throw createError({
|
||||||
|
statusCode: error.statusCode || 500,
|
||||||
|
statusMessage: error.statusMessage || error.message || 'Failed to delete generated EOI document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue