CreateContractDialog.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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: 1100px"
  6. >
  7. <DefaultDialogHeader title="Criar novo contrato" @close="onDialogCancel" />
  8. <DefaultForm ref="formRef" @submit="onSubmit">
  9. <q-scroll-area ref="scrollAreaRef" class="form-dialog-scroll-area">
  10. <q-card-section class="row q-col-gutter-md q-pt-none">
  11. <div class="col-12 text-body2">
  12. Dados da Unidade
  13. </div>
  14. <DefaultInput
  15. class="col-12 col-md-6"
  16. color="secondary"
  17. label="Nome da Unidade"
  18. label-color="secondary"
  19. readonly
  20. :model-value="unitData.name"
  21. />
  22. <DefaultInput
  23. class="col-12 col-md-3"
  24. color="secondary"
  25. label="CNPJ"
  26. label-color="secondary"
  27. readonly
  28. :mask="masks.Brasil.cnpj"
  29. :model-value="unitData.cnpj"
  30. :rules="[inputRules.cnpj]"
  31. />
  32. <DefaultInput
  33. class="col-12 col-md-3"
  34. color="secondary"
  35. disable
  36. label="ID"
  37. label-color="secondary"
  38. :model-value="unitData.id"
  39. />
  40. <DefaultInput
  41. class="col-12 col-md-4"
  42. color="secondary"
  43. disable
  44. label="Nome do Franqueado"
  45. label-color="secondary"
  46. :model-value="unitData.franchisee_name"
  47. />
  48. <DefaultInput
  49. class="col-12 col-md-4"
  50. color="secondary"
  51. disable
  52. label="CPF"
  53. label-color="secondary"
  54. :mask="masks.Brasil.cpf"
  55. :model-value="unitData.franchisee_document"
  56. :rules="[inputRules.cpf]"
  57. />
  58. <DefaultInput
  59. class="col-12 col-md-4"
  60. color="secondary"
  61. disable
  62. label="Data de Nascimento"
  63. label-color="secondary"
  64. :model-value="unitData.franchisee_birthday"
  65. />
  66. <div class="col-12 text-body2 q-mt-sm">
  67. Definir Valores e TBR
  68. </div>
  69. <DefaultInputDatePicker
  70. v-model:untreated-date="form.start_date"
  71. v-model:error="validationErrors.start_date"
  72. class="col-12 col-md-3"
  73. color="secondary"
  74. label="Data de Início"
  75. label-color="secondary"
  76. :error-message="validationErrors.start_date"
  77. :rules="[inputRules.required]"
  78. />
  79. <DefaultInputDatePicker
  80. v-model:untreated-date="form.end_date"
  81. v-model:error="validationErrors.end_date"
  82. class="col-12 col-md-3"
  83. color="secondary"
  84. label="Data de Fim"
  85. label-color="secondary"
  86. :error-message="validationErrors.end_date"
  87. :rules="[inputRules.required, endDateRule]"
  88. />
  89. <DefaultCurrencyInput
  90. v-model="form.tbr_fixed_value"
  91. v-model:error="validationErrors.tbr_fixed_value"
  92. class="col-12 col-md-3"
  93. color="secondary"
  94. label="TBR $"
  95. label-color="secondary"
  96. :error-message="validationErrors.tbr_fixed_value"
  97. :rules="[requiredNumberRule]"
  98. />
  99. <DefaultInput
  100. v-model="form.invoice_due_date"
  101. v-model:error="validationErrors.invoice_due_date"
  102. class="col-12 col-md-3"
  103. color="secondary"
  104. label="Dia de Vencimento do Boleto"
  105. label-color="secondary"
  106. max="31"
  107. min="1"
  108. type="number"
  109. :error-message="validationErrors.invoice_due_date"
  110. :rules="[invoiceDueDateRule]"
  111. />
  112. <DefaultSelect
  113. v-model="form.municipality_size_id"
  114. v-model:error="validationErrors.municipality_size_id"
  115. class="col-12 col-md-3"
  116. color="secondary"
  117. emit-value
  118. fill-input
  119. hide-selected
  120. input-debounce="0"
  121. label="Faixa de Habitante"
  122. label-color="secondary"
  123. map-options
  124. use-input
  125. :error-message="validationErrors.municipality_size_id"
  126. :options="municipalitySizeOptions"
  127. >
  128. <template #no-option>
  129. <q-item>
  130. <q-item-section class="text-grey">
  131. {{ $t("http.errors.no_records_found") }}
  132. </q-item-section>
  133. </q-item>
  134. </template>
  135. </DefaultSelect>
  136. <DefaultInput
  137. v-model="form.tax_base_royalts"
  138. v-model:error="validationErrors.tbr_fixed_value_percentage"
  139. class="col-12 col-md-3"
  140. color="secondary"
  141. label="Taxa Base Royalties"
  142. label-color="secondary"
  143. max="100"
  144. min="0"
  145. type="number"
  146. :error-message="validationErrors.tbr_fixed_value_percentage"
  147. :rules="percentageRules"
  148. >
  149. <template #append>
  150. <span class="text-secondary">%</span>
  151. </template>
  152. </DefaultInput>
  153. <DefaultInput
  154. v-model="form.tax_base_fnm"
  155. v-model:error="validationErrors.marketing_fund_percentage"
  156. class="col-12 col-md-3"
  157. color="secondary"
  158. label="Taxa Base FMN"
  159. label-color="secondary"
  160. max="100"
  161. min="0"
  162. type="number"
  163. :error-message="validationErrors.marketing_fund_percentage"
  164. :rules="percentageRules"
  165. >
  166. <template #append>
  167. <span class="text-secondary">%</span>
  168. </template>
  169. </DefaultInput>
  170. <DefaultInput
  171. v-model="form.tax_base_maintenance"
  172. v-model:error="validationErrors.maintance_tax_percentage"
  173. class="col-12 col-md-3"
  174. color="secondary"
  175. label="Taxa Base Manutenção"
  176. label-color="secondary"
  177. max="100"
  178. min="0"
  179. type="number"
  180. :error-message="validationErrors.maintance_tax_percentage"
  181. :rules="percentageRules"
  182. >
  183. <template #append>
  184. <span class="text-secondary">%</span>
  185. </template>
  186. </DefaultInput>
  187. </q-card-section>
  188. </q-scroll-area>
  189. <q-separator />
  190. <q-card-actions align="right" class="q-pa-md q-gutter-sm">
  191. <q-btn
  192. color="negative"
  193. label="Cancelar"
  194. no-caps
  195. outline
  196. @click="onDialogCancel"
  197. />
  198. <q-btn
  199. color="primary"
  200. label="Salvar"
  201. no-caps
  202. type="submit"
  203. unelevated
  204. :loading="loading"
  205. />
  206. </q-card-actions>
  207. </DefaultForm>
  208. </q-card>
  209. </q-dialog>
  210. </template>
  211. <script setup>
  212. import { createFranchiseeContract } from "src/api/franchisee_contract";
  213. import { getLatestTbr } from "src/api/tbr";
  214. import { getMunicipalitySizes } from "src/api/tbr_calculation";
  215. import { getUnit } from "src/api/unit";
  216. import { onMounted, reactive, ref } from "vue";
  217. import { useDialogPluginComponent } from "quasar";
  218. import { useForm } from "src/composables/useForm";
  219. import { useInputRules } from "src/composables/useInputRules";
  220. import { useScroll } from "src/composables/useScroll";
  221. import { useSubmitHandler } from "src/composables/useSubmitHandler";
  222. import masks from "src/helpers/masks";
  223. import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
  224. import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
  225. import DefaultForm from "src/components/defaults/DefaultForm.vue";
  226. import DefaultInput from "src/components/defaults/DefaultInput.vue";
  227. import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
  228. import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
  229. defineEmits([...useDialogPluginComponent.emits]);
  230. const props = defineProps({
  231. unitId: {
  232. required: true,
  233. type: Number,
  234. },
  235. });
  236. const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
  237. useDialogPluginComponent();
  238. const { inputRules } = useInputRules();
  239. const formRef = ref(null);
  240. const scrollAreaRef = ref(null);
  241. const { scrollToComponent } = useScroll();
  242. const { form } = useForm(
  243. {
  244. end_date: null,
  245. invoice_due_date: null,
  246. municipality_size_id: null,
  247. start_date: null,
  248. tax_base_fnm: null,
  249. tax_base_maintenance: null,
  250. tax_base_royalts: null,
  251. tbr_fixed_value: null,
  252. },
  253. { containerRef: scrollAreaRef },
  254. );
  255. const {
  256. loading,
  257. validationErrors,
  258. execute,
  259. } = useSubmitHandler({
  260. containerRef: scrollAreaRef,
  261. formRef,
  262. onSuccess: (result) => onDialogOK(result),
  263. scrollFn: scrollToComponent,
  264. });
  265. const unitData = reactive({
  266. cnpj: null,
  267. franchisee_birthday: null,
  268. franchisee_document: null,
  269. franchisee_name: null,
  270. id: null,
  271. name: null,
  272. });
  273. const municipalitySizeOptions = ref([]);
  274. const percentageRules = [
  275. inputRules.minValue(0),
  276. inputRules.maxValue(100),
  277. ];
  278. // form.start_date/form.end_date são o "untreated-date" do DefaultInputDatePicker
  279. // (formato YYYY-MM-DD), então comparam corretamente como string. O "value" que o
  280. // q-input passa pra regra é o "treated-date" (DD/MM/YYYY) do campo — formato
  281. // diferente de form.start_date, o que fazia a comparação dar falso positivo.
  282. const endDateRule = () =>
  283. !form.end_date ||
  284. !form.start_date ||
  285. form.end_date > form.start_date ||
  286. "A data de fim deve ser posterior à data de início";
  287. const invoiceDueDateRule = (value) =>
  288. value === null ||
  289. value === undefined ||
  290. value === "" ||
  291. (Number.isInteger(Number(value)) &&
  292. Number(value) >= 1 &&
  293. Number(value) <= 31) ||
  294. "O dia de vencimento deve ser um número inteiro entre 1 e 31";
  295. const loadData = async () => {
  296. const [unit, latestTbr, sizes] = await Promise.all([
  297. getUnit(props.unitId),
  298. getLatestTbr(),
  299. getMunicipalitySizes(),
  300. ]);
  301. unitData.cnpj = unit.cnpj;
  302. unitData.franchisee_name = unit.name_responsible;
  303. unitData.id = unit.id;
  304. unitData.name = unit.name;
  305. const firstPartner = unit.partners?.[0];
  306. if (firstPartner) {
  307. unitData.franchisee_birthday = firstPartner.birth_date;
  308. unitData.franchisee_document = firstPartner.cpf;
  309. }
  310. if (latestTbr) {
  311. form.tax_base_fnm = parseFloat(
  312. (latestTbr.fnm_percentage * 100).toFixed(4),
  313. );
  314. form.tax_base_maintenance = parseFloat(
  315. (latestTbr.maintenance_percentage * 100).toFixed(4),
  316. );
  317. form.tax_base_royalts = parseFloat(
  318. (latestTbr.royalties_percentage * 100).toFixed(4),
  319. );
  320. form.tbr_fixed_value = parseFloat(latestTbr.tbr_value);
  321. }
  322. municipalitySizeOptions.value = (sizes ?? []).map((size) => ({
  323. label: size.description,
  324. value: size.id,
  325. }));
  326. };
  327. const onSubmit = async () => {
  328. await execute(() =>
  329. createFranchiseeContract({
  330. end_date: form.end_date,
  331. invoice_due_date: form.invoice_due_date,
  332. maintance_tax_percentage:
  333. form.tax_base_maintenance != null
  334. ? form.tax_base_maintenance / 100
  335. : null,
  336. marketing_fund_percentage:
  337. form.tax_base_fnm != null ? form.tax_base_fnm / 100 : null,
  338. municipality_size_id: form.municipality_size_id,
  339. start_date: form.start_date,
  340. tbr_fixed_value: form.tbr_fixed_value,
  341. tbr_fixed_value_percentage:
  342. form.tax_base_royalts != null ? form.tax_base_royalts / 100 : null,
  343. unit_id: props.unitId,
  344. }),
  345. );
  346. };
  347. const requiredNumberRule = (value) =>
  348. (value !== null && value !== undefined && value !== "") ||
  349. inputRules.required(null);
  350. requiredNumberRule.$id = "required";
  351. onMounted(loadData);
  352. </script>