AddEditContractDialog.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. <template>
  2. <q-dialog ref="dialogRef" @hide="onDialogHide">
  3. <q-card
  4. class="q-dialog-plugin overflow-hidden"
  5. style="width: 100%; max-width: 1350px"
  6. >
  7. <DefaultDialogHeader
  8. :title="props.contract ? 'Editar Contrato' : 'Novo Contrato'"
  9. @close="onDialogCancel"
  10. />
  11. <template v-if="props.contract">
  12. <q-tabs
  13. v-model="activeTab"
  14. dense
  15. align="left"
  16. class="q-px-md text-grey-7"
  17. active-color="primary"
  18. indicator-color="primary"
  19. >
  20. <q-tab name="dados" label="Dados do Contrato" />
  21. <q-tab name="midias" label="Mídias do Contrato" />
  22. </q-tabs>
  23. <q-separator />
  24. </template>
  25. <q-card-section class="q-pt-sm" style="height: 65vh; overflow-y: auto">
  26. <div v-show="activeTab === 'dados'">
  27. <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
  28. <div class="row q-col-gutter-sm">
  29. <div class="col-12">
  30. <DefaultInput
  31. :model-value="props.student.name"
  32. label="Aluno"
  33. disable
  34. />
  35. </div>
  36. <div class="col-6">
  37. <DefaultInput
  38. :model-value="props.student.document_number"
  39. label="CPF"
  40. disable
  41. />
  42. </div>
  43. <div class="col-6">
  44. <DefaultInput
  45. :model-value="formattedBirthDate"
  46. label="Data de Nascimento"
  47. disable
  48. />
  49. </div>
  50. </div>
  51. <div class="text-subtitle1 q-mt-lg q-mb-md">Dados do Contrato</div>
  52. <div class="row q-col-gutter-sm">
  53. <div class="col-4">
  54. <DefaultInput v-model="form.protocol" label="Protocolo" />
  55. </div>
  56. <div class="col-4">
  57. <DefaultInputDatePicker
  58. v-model="form.signature_date"
  59. label="Data Assinatura"
  60. />
  61. </div>
  62. <div class="col-4">
  63. <DefaultInputDatePicker
  64. v-model="form.end_date"
  65. label="Data Encerramento"
  66. />
  67. </div>
  68. <div class="col-5">
  69. <DefaultSelect
  70. v-model="form.package_id"
  71. label="Pacote de Aulas"
  72. :options="packages"
  73. option-value="id"
  74. option-label="name"
  75. emit-value
  76. map-options
  77. />
  78. </div>
  79. <div class="col-7">
  80. <DefaultInput
  81. v-model="form.class_quantity"
  82. label="Qtd. Aulas"
  83. type="number"
  84. disable
  85. />
  86. </div>
  87. <div class="col-4">
  88. <DefaultSelect
  89. v-model="form.weekday"
  90. label="Dia da Semana"
  91. :options="weekdays"
  92. option-value="value"
  93. option-label="label"
  94. emit-value
  95. map-options
  96. />
  97. </div>
  98. <div class="col-4">
  99. <DefaultInput
  100. v-model="form.start_time"
  101. label="Hora de Início"
  102. mask="##:##"
  103. >
  104. <template #append>
  105. <q-icon name="mdi-clock-outline" />
  106. </template>
  107. </DefaultInput>
  108. </div>
  109. <div class="col-4">
  110. <DefaultInput
  111. v-model="form.end_time"
  112. label="Hora de Término"
  113. mask="##:##"
  114. >
  115. <template #append>
  116. <q-icon name="mdi-clock-outline" />
  117. </template>
  118. </DefaultInput>
  119. </div>
  120. <div class="col-4">
  121. <DefaultSelect
  122. v-model="form.second_weekday"
  123. label="2° Dia da Semana"
  124. :options="weekdays"
  125. option-value="value"
  126. option-label="label"
  127. emit-value
  128. map-options
  129. />
  130. </div>
  131. <div class="col-4">
  132. <DefaultInput
  133. v-model="form.second_start_time"
  134. label="Hora de Início"
  135. mask="##:##"
  136. >
  137. <template #append>
  138. <q-icon name="mdi-clock-outline" />
  139. </template>
  140. </DefaultInput>
  141. </div>
  142. <div class="col-4">
  143. <DefaultInput
  144. v-model="form.second_end_time"
  145. label="Hora de Término"
  146. mask="##:##"
  147. >
  148. <template #append>
  149. <q-icon name="mdi-clock-outline" />
  150. </template>
  151. </DefaultInput>
  152. </div>
  153. </div>
  154. <div class="text-subtitle1 q-mt-lg q-mb-md">Dados Financeiros</div>
  155. <div class="row q-col-gutter-sm">
  156. <div class="col-4">
  157. <DefaultInput
  158. v-model="form.due_day"
  159. label="Dia de Vencimento"
  160. type="number"
  161. />
  162. </div>
  163. </div>
  164. <div class="text-subtitle2 q-mt-md q-mb-sm text-grey-8">Matrícula</div>
  165. <div class="row q-col-gutter-sm">
  166. <div class="col-3">
  167. <DefaultCurrencyInput
  168. v-model="form.enrollment_fee"
  169. label="Valor da Matrícula"
  170. disable
  171. />
  172. </div>
  173. <div class="col-3">
  174. <DefaultSelect
  175. v-model="form.installments"
  176. label="Qtde Parcelas"
  177. :options="enrollmentInstallmentOptions"
  178. option-value="value"
  179. option-label="label"
  180. emit-value
  181. map-options
  182. />
  183. </div>
  184. <div class="col-3">
  185. <DefaultCurrencyInput
  186. :model-value="enrollmentInstallmentValue"
  187. label="Valor da Parcela"
  188. disable
  189. />
  190. </div>
  191. <div class="col-3">
  192. <DefaultInputDatePicker
  193. v-model="form.enrollment_due_date"
  194. label="Data Vencimento"
  195. />
  196. </div>
  197. </div>
  198. <div class="text-subtitle2 q-mt-md q-mb-sm text-grey-8">Pacote</div>
  199. <div class="row q-col-gutter-sm">
  200. <div class="col-3">
  201. <DefaultCurrencyInput
  202. v-model="form.package_value"
  203. label="Valor do Pacote"
  204. disable
  205. />
  206. </div>
  207. <div class="col-3">
  208. <DefaultSelect
  209. v-model="form.package_installments"
  210. label="Qtde Parcelas"
  211. :options="packageInstallmentOptions"
  212. option-value="value"
  213. option-label="label"
  214. emit-value
  215. map-options
  216. />
  217. </div>
  218. <div class="col-3">
  219. <DefaultCurrencyInput
  220. :model-value="packageInstallmentValue"
  221. label="Valor da Parcela"
  222. disable
  223. />
  224. </div>
  225. <div class="col-3">
  226. <DefaultInputDatePicker
  227. v-model="form.package_due_date"
  228. label="Data 1ª Parcela"
  229. />
  230. </div>
  231. </div>
  232. <div class="row q-col-gutter-sm q-mt-xs">
  233. <div class="col-6">
  234. <DefaultInput
  235. v-model="form.early_payment_discount"
  236. label="Desconto até o vencimento (%)"
  237. type="number"
  238. />
  239. </div>
  240. <div class="col-6">
  241. <DefaultSelect
  242. v-model="form.payment_method"
  243. label="Forma de Pagamento"
  244. :options="paymentMethods"
  245. option-value="value"
  246. option-label="label"
  247. emit-value
  248. map-options
  249. />
  250. </div>
  251. <div class="col-6">
  252. <DefaultInput
  253. v-model="form.interest_rate"
  254. label="Juros (%) a.m"
  255. type="number"
  256. />
  257. </div>
  258. <div class="col-6">
  259. <DefaultInput
  260. v-model="form.late_fee"
  261. label="Multa (%)"
  262. type="number"
  263. />
  264. </div>
  265. </div>
  266. </div>
  267. <div v-if="props.contract" v-show="activeTab === 'midias'">
  268. <DefaultTable
  269. v-model:rows="medias"
  270. title="Mídias"
  271. :columns="mediaColumns"
  272. descricao="mídias"
  273. :feminino="true"
  274. no-api-call
  275. :show-search-field="false"
  276. :loading="loadingMedias"
  277. >
  278. <template #body-cell-actions="{ row }">
  279. <q-td align="center">
  280. <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
  281. <q-btn
  282. outline
  283. icon="mdi-eye-outline"
  284. style="width: 36px"
  285. @click.prevent.stop="openFile(row.file_url)"
  286. />
  287. <q-btn
  288. outline
  289. icon="mdi-trash-can-outline"
  290. style="width: 36px"
  291. color="negative"
  292. @click.prevent.stop="handleDeleteMedia(row)"
  293. />
  294. </q-item-section>
  295. </q-td>
  296. </template>
  297. </DefaultTable>
  298. </div>
  299. </q-card-section>
  300. <q-separator />
  301. <q-card-actions align="right">
  302. <q-btn
  303. outline
  304. color="primary"
  305. label="CANCELAR"
  306. @click="onDialogCancel"
  307. />
  308. <q-btn
  309. v-if="activeTab === 'dados'"
  310. color="primary"
  311. label="SALVAR"
  312. :loading="saving"
  313. @click="handleSave"
  314. />
  315. </q-card-actions>
  316. </q-card>
  317. </q-dialog>
  318. </template>
  319. <script setup>
  320. import { computed, ref, watch, onMounted, nextTick } from "vue";
  321. import { useDialogPluginComponent, useQuasar } from "quasar";
  322. import { addYears, format } from "date-fns";
  323. import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
  324. import DefaultInput from "src/components/defaults/DefaultInput.vue";
  325. import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
  326. import DefaultTable from "src/components/defaults/DefaultTable.vue";
  327. import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
  328. import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
  329. import { useSubmitHandler } from "src/composables/useSubmitHandler";
  330. import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
  331. import { formatDateYMDtoDMY, formatDateDMYtoYMD } from "src/helpers/utils";
  332. import { getUnitPackagesForSelect } from "src/api/package";
  333. import { getFinancialMe } from "src/api/unit_financial";
  334. import { permissionStore } from "src/stores/permission";
  335. import {
  336. createStudentContract,
  337. updateStudentContract,
  338. } from "src/api/studentContract";
  339. import { getContractMedias, deleteContractMedia } from "src/api/student_media";
  340. const props = defineProps({
  341. student: {
  342. type: Object,
  343. required: true,
  344. },
  345. contract: {
  346. type: Object,
  347. default: null,
  348. },
  349. });
  350. defineEmits([...useDialogPluginComponent.emits]);
  351. const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
  352. useDialogPluginComponent();
  353. const $q = useQuasar();
  354. const permissions = permissionStore();
  355. const canViewFinancial = computed(() =>
  356. permissions.getAccess("franchisee_financial", "view"),
  357. );
  358. const activeTab = ref("dados");
  359. const medias = ref([]);
  360. const loadingMedias = ref(false);
  361. const trimTime = (t) => (t ? t.slice(0, 5) : null);
  362. function calculateEndTime(startTime, durationMinutes = 120) {
  363. if (!/^\d{2}:\d{2}$/.test(startTime ?? "")) return null;
  364. const [hours, minutes] = startTime.split(":").map(Number);
  365. if (hours > 23 || minutes > 59) return null;
  366. const endMinutes = (hours * 60 + minutes + durationMinutes) % (24 * 60);
  367. return `${String(Math.floor(endMinutes / 60)).padStart(2, "0")}:${String(
  368. endMinutes % 60,
  369. ).padStart(2, "0")}`;
  370. }
  371. const defaultContractDates = (() => {
  372. if (props.contract) return null;
  373. const start = new Date();
  374. return {
  375. signature_date: format(start, "dd/MM/yyyy"),
  376. end_date: format(addYears(start, 1), "dd/MM/yyyy"),
  377. };
  378. })();
  379. const { form } = useFormUpdateTracker({
  380. protocol: props.contract?.protocol ?? null,
  381. signature_date:
  382. props.contract?.signature_date ?? defaultContractDates?.signature_date ?? null,
  383. end_date: props.contract?.end_date ?? defaultContractDates?.end_date ?? null,
  384. package_id: props.contract?.class_package_unit_id ?? null,
  385. class_quantity: props.contract?.class_quantity ?? null,
  386. weekday: props.contract?.weekday ?? null,
  387. start_time: trimTime(props.contract?.start_time),
  388. end_time: trimTime(props.contract?.end_time),
  389. second_weekday: props.contract?.second_weekday ?? null,
  390. second_start_time: trimTime(props.contract?.second_start_time),
  391. second_end_time: trimTime(props.contract?.second_end_time),
  392. due_day: props.contract?.recurring_day ?? null,
  393. enrollment_fee: props.contract?.tax_register ?? null,
  394. total_classes: props.contract?.class_quantity ?? null,
  395. installments: props.contract?.installments ?? null,
  396. enrollment_due_date: props.contract?.enrollment_due_date ?? null,
  397. package_value: props.contract?.package_value ?? null,
  398. package_installments: props.contract?.package_installments ?? null,
  399. package_due_date: props.contract?.package_due_date ?? null,
  400. early_payment_discount: props.contract?.early_payment_discount ?? null,
  401. interest_rate: props.contract?.interest_rate ?? null,
  402. payment_method: props.contract?.payment_method ?? null,
  403. late_fee: props.contract?.fine_cancelled ?? null,
  404. });
  405. const dateOverrideConfirmed = ref(false);
  406. const dateConfirmationOpen = ref(false);
  407. const restoringDefaultDates = ref(false);
  408. const completeDatePattern = /^\d{2}\/\d{2}\/\d{4}$/;
  409. function restoreDefaultContractDates() {
  410. restoringDefaultDates.value = true;
  411. form.signature_date = defaultContractDates.signature_date;
  412. form.end_date = defaultContractDates.end_date;
  413. nextTick(() => {
  414. restoringDefaultDates.value = false;
  415. dateConfirmationOpen.value = false;
  416. });
  417. }
  418. watch(
  419. [() => form.signature_date, () => form.end_date],
  420. ([signatureDate, endDate]) => {
  421. if (
  422. props.contract ||
  423. dateOverrideConfirmed.value ||
  424. dateConfirmationOpen.value ||
  425. restoringDefaultDates.value
  426. ) {
  427. return;
  428. }
  429. const datesAreStillDefault =
  430. signatureDate === defaultContractDates.signature_date &&
  431. endDate === defaultContractDates.end_date;
  432. if (datesAreStillDefault) return;
  433. // Evita abrir a confirmação enquanto o usuário ainda está digitando a data.
  434. const datesAreComplete =
  435. (!signatureDate || completeDatePattern.test(signatureDate)) &&
  436. (!endDate || completeDatePattern.test(endDate));
  437. if (!datesAreComplete) return;
  438. dateConfirmationOpen.value = true;
  439. $q.dialog({
  440. title: "Alterar duração do contrato?",
  441. message:
  442. "As datas foram preenchidas automaticamente para a duração de um ano. Tem certeza de que deseja alterá-las?",
  443. ok: { color: "primary", label: "Sim" },
  444. cancel: { color: "primary", outline: true, label: "Não" },
  445. persistent: true,
  446. })
  447. .onOk(() => {
  448. dateOverrideConfirmed.value = true;
  449. dateConfirmationOpen.value = false;
  450. })
  451. .onCancel(restoreDefaultContractDates);
  452. },
  453. );
  454. const enrollmentInstallmentOptions = Array.from({ length: 12 }, (_, i) => ({
  455. value: i + 1,
  456. label: `${i + 1}x`,
  457. }));
  458. const packageInstallmentOptions = Array.from({ length: 13 }, (_, i) => ({
  459. value: i + 1,
  460. label: `${i + 1}x`,
  461. }));
  462. const enrollmentInstallmentValue = computed(() => {
  463. if (!form.enrollment_fee || !form.installments) return null;
  464. return parseFloat((form.enrollment_fee / form.installments).toFixed(2));
  465. });
  466. const packageInstallmentValue = computed(() => {
  467. if (!form.package_value || !form.package_installments) return null;
  468. return parseFloat((form.package_value / form.package_installments).toFixed(2));
  469. });
  470. // Two-way binding: due_day <-> package_due_date
  471. const _syncingFromDay = ref(false);
  472. const _syncingFromDate = ref(false);
  473. function buildDateFromDay(day) {
  474. const d = parseInt(day);
  475. if (!d || d < 1 || d > 31) return null;
  476. const now = new Date();
  477. const nextMonthIndex = (now.getMonth() + 1) % 12;
  478. const nextYear = now.getMonth() === 11 ? now.getFullYear() + 1 : now.getFullYear();
  479. const lastDayOfNextMonth = new Date(nextYear, nextMonthIndex + 1, 0).getDate();
  480. const safeDay = Math.min(d, lastDayOfNextMonth);
  481. return `${String(safeDay).padStart(2, '0')}/${String(nextMonthIndex + 1).padStart(2, '0')}/${nextYear}`;
  482. }
  483. watch(
  484. () => form.due_day,
  485. (day) => {
  486. if (_syncingFromDate.value) return;
  487. const dateStr = buildDateFromDay(day);
  488. if (!dateStr) return;
  489. _syncingFromDay.value = true;
  490. form.package_due_date = dateStr;
  491. nextTick(() => { _syncingFromDay.value = false; });
  492. },
  493. );
  494. watch(
  495. () => form.package_due_date,
  496. (dateStr) => {
  497. if (_syncingFromDay.value) return;
  498. if (!dateStr || dateStr.length < 8) return;
  499. const day = parseInt(dateStr.split('/')[0]);
  500. if (!isNaN(day) && day !== parseInt(form.due_day)) {
  501. _syncingFromDate.value = true;
  502. form.due_day = day;
  503. nextTick(() => { _syncingFromDate.value = false; });
  504. }
  505. },
  506. );
  507. const paymentMethods = [
  508. { value: "pix", label: "Pix" },
  509. { value: "credit_card", label: "Cartão de Crédito" },
  510. { value: "debit_card", label: "Cartão de Débito" },
  511. ];
  512. const weekdays = [
  513. { value: 1, label: "Segunda" },
  514. { value: 2, label: "Terça" },
  515. { value: 3, label: "Quarta" },
  516. { value: 4, label: "Quinta" },
  517. { value: 5, label: "Sexta" },
  518. { value: 6, label: "Sábado" },
  519. { value: 0, label: "Domingo" },
  520. ];
  521. const packages = ref([]);
  522. const mediaColumns = [
  523. { name: "created_at", label: "Data de Anexo", field: "created_at", align: "left" },
  524. { name: "actions", label: "Ações", field: null, align: "center" },
  525. ];
  526. async function fetchMedias() {
  527. if (!props.contract) return;
  528. loadingMedias.value = true;
  529. try {
  530. medias.value = await getContractMedias(props.contract.id);
  531. } finally {
  532. loadingMedias.value = false;
  533. }
  534. }
  535. watch(activeTab, (tab) => {
  536. if (tab === "midias") fetchMedias();
  537. });
  538. onMounted(async () => {
  539. packages.value = await getUnitPackagesForSelect();
  540. // Para novo contrato, pré-preenche desconto/juros/multa com os defaults da unidade
  541. if (!props.contract && canViewFinancial.value) {
  542. try {
  543. const financial = await getFinancialMe();
  544. if (financial) {
  545. if (financial.default_discount != null) form.early_payment_discount = financial.default_discount;
  546. if (financial.default_interest != null) form.interest_rate = financial.default_interest;
  547. if (financial.default_fine != null) form.late_fee = financial.default_fine;
  548. }
  549. } catch (e) {
  550. console.error(e);
  551. }
  552. }
  553. });
  554. function openFile(url) {
  555. window.open(url, "_blank");
  556. }
  557. function handleDeleteMedia(media) {
  558. $q.dialog({
  559. title: "Excluir mídia",
  560. message: "Deseja excluir esta mídia permanentemente?",
  561. ok: { color: "negative", label: "Excluir" },
  562. cancel: { color: "primary", outline: true, label: "Cancelar" },
  563. }).onOk(async () => {
  564. try {
  565. await deleteContractMedia(media.id);
  566. medias.value = medias.value.filter((m) => m.id !== media.id);
  567. } catch (e) {
  568. console.error(e);
  569. $q.notify({ type: "negative", message: "Erro ao excluir mídia." });
  570. }
  571. });
  572. }
  573. watch(
  574. () => form.package_id,
  575. (id) => {
  576. const pkg = packages.value.find((p) => p.id === id);
  577. if (!pkg) return;
  578. form.class_quantity = pkg.quantity_classes;
  579. form.total_classes = pkg.quantity_classes;
  580. form.enrollment_fee = pkg.contract_register_value;
  581. form.package_value = pkg.contract_value;
  582. form.weekday = pkg.weekday ?? null;
  583. form.start_time = trimTime(pkg.start_time);
  584. form.end_time = calculateEndTime(
  585. form.start_time,
  586. pkg.class_duration_minutes ?? 120,
  587. );
  588. form.second_weekday = pkg.second_weekday ?? null;
  589. form.second_start_time = trimTime(pkg.second_start_time);
  590. form.second_end_time = calculateEndTime(
  591. form.second_start_time,
  592. pkg.class_duration_minutes ?? 120,
  593. );
  594. },
  595. );
  596. watch(
  597. () => form.start_time,
  598. (startTime) => {
  599. const pkg = packages.value.find((p) => p.id === form.package_id);
  600. const calculated = calculateEndTime(
  601. startTime,
  602. pkg?.class_duration_minutes ?? 120,
  603. );
  604. if (calculated) form.end_time = calculated;
  605. },
  606. );
  607. watch(
  608. () => form.second_start_time,
  609. (startTime) => {
  610. const pkg = packages.value.find((p) => p.id === form.package_id);
  611. const calculated = calculateEndTime(
  612. startTime,
  613. pkg?.class_duration_minutes ?? 120,
  614. );
  615. if (calculated) form.second_end_time = calculated;
  616. },
  617. );
  618. const formattedBirthDate = computed(() =>
  619. props.student.birth_date ? formatDateYMDtoDMY(props.student.birth_date) : "",
  620. );
  621. const { loading: saving, execute } = useSubmitHandler({
  622. onSuccess: () => onDialogOK(true),
  623. });
  624. function buildPayload() {
  625. return {
  626. student_id: props.student.id,
  627. protocol: form.protocol,
  628. signature_date: form.signature_date
  629. ? formatDateDMYtoYMD(form.signature_date)
  630. : null,
  631. end_date: form.end_date ? formatDateDMYtoYMD(form.end_date) : null,
  632. class_package_unit_id: form.package_id,
  633. class_quantity: form.class_quantity,
  634. weekday: form.weekday,
  635. start_time: form.start_time,
  636. end_time: form.end_time,
  637. second_weekday: form.second_weekday,
  638. second_start_time: form.second_start_time,
  639. second_end_time: form.second_end_time,
  640. due_day: form.due_day ? parseInt(form.due_day) : null,
  641. tax_register: form.enrollment_fee,
  642. installments: form.installments,
  643. enrollment_due_date: form.enrollment_due_date
  644. ? formatDateDMYtoYMD(form.enrollment_due_date)
  645. : null,
  646. package_value: form.package_value,
  647. package_installments: form.package_installments,
  648. package_due_date: form.package_due_date
  649. ? formatDateDMYtoYMD(form.package_due_date)
  650. : null,
  651. early_payment_discount: form.early_payment_discount,
  652. interest_rate: form.interest_rate,
  653. payment_method: form.payment_method,
  654. fine_cancelled: form.late_fee,
  655. };
  656. }
  657. async function handleSave() {
  658. const payload = buildPayload();
  659. await execute(() =>
  660. props.contract
  661. ? updateStudentContract(props.contract.id, payload)
  662. : createStudentContract(payload),
  663. );
  664. }
  665. </script>