|
@@ -4,6 +4,14 @@
|
|
|
class="q-dialog-plugin overflow-hidden"
|
|
class="q-dialog-plugin overflow-hidden"
|
|
|
style="width: 100%; max-width: 1100px"
|
|
style="width: 100%; max-width: 1100px"
|
|
|
>
|
|
>
|
|
|
|
|
+ <input
|
|
|
|
|
+ ref="signedFileInputRef"
|
|
|
|
|
+ accept="image/jpeg,image/png,.pdf"
|
|
|
|
|
+ style="display: none"
|
|
|
|
|
+ type="file"
|
|
|
|
|
+ @change="onSignedFileSelected"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
<DefaultDialogHeader
|
|
<DefaultDialogHeader
|
|
|
title="Histórico do Contrato"
|
|
title="Histórico do Contrato"
|
|
|
@close="onDialogCancel"
|
|
@close="onDialogCancel"
|
|
@@ -69,6 +77,53 @@
|
|
|
{{ formatDateTime(row.updated_at) }}
|
|
{{ formatDateTime(row.updated_at) }}
|
|
|
</q-td>
|
|
</q-td>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #body-cell-actions="{ row }">
|
|
|
|
|
+ <q-td align="center">
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="row.file_url || canEdit"
|
|
|
|
|
+ :icon="
|
|
|
|
|
+ row.file_url
|
|
|
|
|
+ ? 'mdi-file-eye-outline'
|
|
|
|
|
+ : 'mdi-file-plus-outline'
|
|
|
|
|
+ "
|
|
|
|
|
+ :loading="generatingId === row.id"
|
|
|
|
|
+ outline
|
|
|
|
|
+ style="width: 36px"
|
|
|
|
|
+ @click="handleContractFile(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-tooltip>
|
|
|
|
|
+ {{
|
|
|
|
|
+ row.file_url
|
|
|
|
|
+ ? "Visualizar arquivo desta versão"
|
|
|
|
|
+ : "Gerar arquivo desta versão"
|
|
|
|
|
+ }}
|
|
|
|
|
+ </q-tooltip>
|
|
|
|
|
+ </q-btn>
|
|
|
|
|
+
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="row.signed_file_url || canEdit"
|
|
|
|
|
+ class="q-ml-xs"
|
|
|
|
|
+ outline
|
|
|
|
|
+ style="width: 36px"
|
|
|
|
|
+ :icon="
|
|
|
|
|
+ row.signed_file_url
|
|
|
|
|
+ ? 'mdi-file-check-outline'
|
|
|
|
|
+ : 'mdi-file-upload-outline'
|
|
|
|
|
+ "
|
|
|
|
|
+ :loading="uploadingSignedId === row.id"
|
|
|
|
|
+ @click="handleSignedContractFile(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-tooltip>
|
|
|
|
|
+ {{
|
|
|
|
|
+ row.signed_file_url
|
|
|
|
|
+ ? "Visualizar contrato assinado desta versão"
|
|
|
|
|
+ : "Enviar contrato assinado desta versão"
|
|
|
|
|
+ }}
|
|
|
|
|
+ </q-tooltip>
|
|
|
|
|
+ </q-btn>
|
|
|
|
|
+ </q-td>
|
|
|
|
|
+ </template>
|
|
|
</q-table>
|
|
</q-table>
|
|
|
</q-card-section>
|
|
</q-card-section>
|
|
|
|
|
|
|
@@ -86,9 +141,15 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
|
|
+import {
|
|
|
|
|
+ attachSignedContractFile,
|
|
|
|
|
+ generateStudentContractFile,
|
|
|
|
|
+ getStudentContractById,
|
|
|
|
|
+} from "src/api/studentContract";
|
|
|
|
|
+
|
|
|
import { computed, onMounted, ref } from "vue";
|
|
import { computed, onMounted, ref } from "vue";
|
|
|
import { getUnitPackagesForSelect } from "src/api/package";
|
|
import { getUnitPackagesForSelect } from "src/api/package";
|
|
|
-import { getStudentContractById } from "src/api/studentContract";
|
|
|
|
|
|
|
+import { permissionStore } from "src/stores/permission";
|
|
|
import { useDialogPluginComponent, useQuasar } from "quasar";
|
|
import { useDialogPluginComponent, useQuasar } from "quasar";
|
|
|
|
|
|
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
@@ -110,11 +171,21 @@ const props = defineProps({
|
|
|
const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
|
|
const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
|
|
|
|
|
|
|
|
const $q = useQuasar();
|
|
const $q = useQuasar();
|
|
|
|
|
+const permissions = permissionStore();
|
|
|
|
|
|
|
|
const contract = ref(null);
|
|
const contract = ref(null);
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
|
|
+const generatingId = ref(null);
|
|
|
|
|
+const signedFileInputRef = ref(null);
|
|
|
|
|
+const uploadingSignedId = ref(null);
|
|
|
const packages = ref([]);
|
|
const packages = ref([]);
|
|
|
|
|
|
|
|
|
|
+let pendingSignedContractId = null;
|
|
|
|
|
+
|
|
|
|
|
+const canEdit = computed(() =>
|
|
|
|
|
+ permissions.getAccess("franchisee_contracts", "edit"),
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
const columns = [
|
|
const columns = [
|
|
|
{
|
|
{
|
|
|
align: "center",
|
|
align: "center",
|
|
@@ -164,15 +235,19 @@ const columns = [
|
|
|
label: "Alterado em",
|
|
label: "Alterado em",
|
|
|
name: "updated_at",
|
|
name: "updated_at",
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ align: "center",
|
|
|
|
|
+ field: null,
|
|
|
|
|
+ label: "Arquivo",
|
|
|
|
|
+ name: "actions",
|
|
|
|
|
+ },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
const normalizeId = (value) => (value == null ? null : String(value));
|
|
const normalizeId = (value) => (value == null ? null : String(value));
|
|
|
|
|
|
|
|
const packageNames = computed(
|
|
const packageNames = computed(
|
|
|
() =>
|
|
() =>
|
|
|
- new Map(
|
|
|
|
|
- packages.value.map((item) => [normalizeId(item.id), item.name]),
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ new Map(packages.value.map((item) => [normalizeId(item.id), item.name])),
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
const historyRows = computed(() =>
|
|
const historyRows = computed(() =>
|
|
@@ -187,11 +262,11 @@ const historyRows = computed(() =>
|
|
|
? props.contractData?.package_name
|
|
? props.contractData?.package_name
|
|
|
: null),
|
|
: null),
|
|
|
}))
|
|
}))
|
|
|
- .sort(
|
|
|
|
|
- (first, second) => Number(second.version) - Number(first.version),
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ .sort((first, second) => Number(second.version) - Number(first.version)),
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
const formatCurrency = (value) =>
|
|
const formatCurrency = (value) =>
|
|
|
new Intl.NumberFormat("pt-BR", {
|
|
new Intl.NumberFormat("pt-BR", {
|
|
|
currency: "BRL",
|
|
currency: "BRL",
|
|
@@ -209,6 +284,110 @@ const formatDateTime = (value) => {
|
|
|
return `${day}/${month}/${year}${time ? ` ${time.slice(0, 5)}` : ""}`;
|
|
return `${day}/${month}/${year}${time ? ` ${time.slice(0, 5)}` : ""}`;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const handleContractFile = async (row) => {
|
|
|
|
|
+ if (row.file_url) {
|
|
|
|
|
+ window.open(row.file_url, "_blank", "noopener,noreferrer");
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fileWindow = window.open("", "_blank");
|
|
|
|
|
+
|
|
|
|
|
+ generatingId.value = row.id;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const updated = await generateStudentContractFile(row.id);
|
|
|
|
|
+
|
|
|
|
|
+ const version = contract.value?.version_history?.find(
|
|
|
|
|
+ (item) => item.id === row.id,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (version) {
|
|
|
|
|
+ version.file_url = updated.file_url;
|
|
|
|
|
+ version.file_type = updated.file_type;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (updated.file_url && fileWindow) {
|
|
|
|
|
+ fileWindow.location.href = updated.file_url;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fileWindow?.close();
|
|
|
|
|
+
|
|
|
|
|
+ throw new Error("A API não retornou o arquivo gerado.");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ fileWindow?.close();
|
|
|
|
|
+
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+
|
|
|
|
|
+ $q.notify({
|
|
|
|
|
+ message: "Erro ao gerar o arquivo desta versão do contrato.",
|
|
|
|
|
+ type: "negative",
|
|
|
|
|
+ });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ generatingId.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+const handleSignedContractFile = (row) => {
|
|
|
|
|
+ if (row.signed_file_url) {
|
|
|
|
|
+ window.open(row.signed_file_url, "_blank", "noopener,noreferrer");
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pendingSignedContractId = row.id;
|
|
|
|
|
+
|
|
|
|
|
+ signedFileInputRef.value.value = "";
|
|
|
|
|
+ signedFileInputRef.value.click();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onSignedFileSelected = async (event) => {
|
|
|
|
|
+ const file = event.target.files?.[0];
|
|
|
|
|
+
|
|
|
|
|
+ const contractId = pendingSignedContractId;
|
|
|
|
|
+
|
|
|
|
|
+ if (!file || !contractId) return;
|
|
|
|
|
+
|
|
|
|
|
+ uploadingSignedId.value = contractId;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+
|
|
|
|
|
+ formData.append("file", file);
|
|
|
|
|
+
|
|
|
|
|
+ const updated = await attachSignedContractFile(contractId, formData);
|
|
|
|
|
+
|
|
|
|
|
+ const version = contract.value?.version_history?.find(
|
|
|
|
|
+ (item) => item.id === contractId,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (version) {
|
|
|
|
|
+ version.signed_file_url = updated.signed_file_url;
|
|
|
|
|
+ version.signed_file_type = updated.signed_file_type;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $q.notify({
|
|
|
|
|
+ message: "Contrato assinado enviado com sucesso.",
|
|
|
|
|
+ type: "positive",
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+
|
|
|
|
|
+ $q.notify({
|
|
|
|
|
+ message: "Erro ao enviar o contrato assinado.",
|
|
|
|
|
+ type: "negative",
|
|
|
|
|
+ });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ pendingSignedContractId = null;
|
|
|
|
|
+
|
|
|
|
|
+ uploadingSignedId.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
const statusColor = (status) => {
|
|
const statusColor = (status) => {
|
|
|
if (status === "active") return "positive";
|
|
if (status === "active") return "positive";
|
|
|
if (status === "frozen") return "info";
|
|
if (status === "frozen") return "info";
|
|
@@ -225,6 +404,8 @@ const statusLabel = (status) => {
|
|
|
return "Inativo";
|
|
return "Inativo";
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
|
|
|