|
|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
|
<q-dialog ref="dialogRef" @hide="onDialogHide">
|
|
|
- <div style="width: 100%; max-width: 700px">
|
|
|
+ <div style="width: 100%; max-width: 960px">
|
|
|
<q-card class="overflow-hidden" style="width: 100%">
|
|
|
<DefaultDialogHeader
|
|
|
:title="props.package ? 'Editar Proposta Comercial' : 'Nova Proposta Comercial'"
|
|
|
@@ -65,130 +65,6 @@
|
|
|
A duração padrão das aulas é de 2 horas e pode ser alterada
|
|
|
se necessário. É possível configurar no máximo dois dias.
|
|
|
</div>
|
|
|
-
|
|
|
- <DefaultCurrencyInput
|
|
|
- v-model="form.contract_register_value"
|
|
|
- v-model:error="validationErrors.contract_register_value"
|
|
|
- class="col-4"
|
|
|
- label="R$ Matrícula"
|
|
|
- :error-message="validationErrors.contract_register_value"
|
|
|
- />
|
|
|
-
|
|
|
- <DefaultCurrencyInput
|
|
|
- v-model="form.contract_value"
|
|
|
- v-model:error="validationErrors.contract_value"
|
|
|
- class="col-4"
|
|
|
- label="R$ Total do Contrato"
|
|
|
- :error-message="validationErrors.contract_value"
|
|
|
- />
|
|
|
-
|
|
|
- <DefaultInput
|
|
|
- v-model="form.contrat_discount_value"
|
|
|
- v-model:error="validationErrors.contrat_discount_value"
|
|
|
- class="col-4"
|
|
|
- label="Desconto em %"
|
|
|
- min="0"
|
|
|
- type="number"
|
|
|
- :error-message="validationErrors.contrat_discount_value"
|
|
|
- :rules="[inputRules.minValue(0)]"
|
|
|
- />
|
|
|
-
|
|
|
- <div
|
|
|
- v-for="(material, index) in form.materials"
|
|
|
- :key="index"
|
|
|
- class="col-12"
|
|
|
- >
|
|
|
- <div class="row q-col-gutter-sm items-center">
|
|
|
- <DefaultSelect
|
|
|
- v-model="material.product_id"
|
|
|
- v-model:error="
|
|
|
- validationErrors[`materials.${index}.product_id`]
|
|
|
- "
|
|
|
- class="col"
|
|
|
- emit-value
|
|
|
- label="Material"
|
|
|
- map-options
|
|
|
- :error-message="
|
|
|
- validationErrors[`materials.${index}.product_id`]
|
|
|
- "
|
|
|
- :options="productOptions"
|
|
|
- :rules="[
|
|
|
- inputRules.whenValue(
|
|
|
- () => material.price,
|
|
|
- inputRules.required,
|
|
|
- ),
|
|
|
- ]"
|
|
|
- @update:model-value="onProductSelected(material)"
|
|
|
- />
|
|
|
-
|
|
|
- <DefaultInput
|
|
|
- v-model="material.quantity"
|
|
|
- v-model:error="
|
|
|
- validationErrors[`materials.${index}.quantity`]
|
|
|
- "
|
|
|
- class="col-2"
|
|
|
- label="Qtd"
|
|
|
- min="1"
|
|
|
- type="number"
|
|
|
- :error-message="
|
|
|
- validationErrors[`materials.${index}.quantity`]
|
|
|
- "
|
|
|
- :rules="[
|
|
|
- inputRules.whenValue(
|
|
|
- () => material.product_id,
|
|
|
- inputRules.requiredNumber,
|
|
|
- ),
|
|
|
- inputRules.whenValue(
|
|
|
- () => material.product_id,
|
|
|
- inputRules.integer,
|
|
|
- ),
|
|
|
- inputRules.whenValue(
|
|
|
- () => material.product_id,
|
|
|
- inputRules.minValue(1),
|
|
|
- ),
|
|
|
- ]"
|
|
|
- />
|
|
|
-
|
|
|
- <DefaultCurrencyInput
|
|
|
- v-model="material.price"
|
|
|
- v-model:error="
|
|
|
- validationErrors[`materials.${index}.price`]
|
|
|
- "
|
|
|
- class="col-3"
|
|
|
- label="R$ Unitário"
|
|
|
- :error-message="
|
|
|
- validationErrors[`materials.${index}.price`]
|
|
|
- "
|
|
|
- :rules="[
|
|
|
- inputRules.whenValue(
|
|
|
- () => material.product_id,
|
|
|
- inputRules.requiredNumber,
|
|
|
- ),
|
|
|
- ]"
|
|
|
- />
|
|
|
-
|
|
|
- <div class="col-auto">
|
|
|
- <q-btn
|
|
|
- v-if="index === form.materials.length - 1"
|
|
|
- color="primary"
|
|
|
- icon="mdi-plus"
|
|
|
- style="border-radius: 8px; height: 40px; width: 40px"
|
|
|
- unelevated
|
|
|
- @click="addMaterial"
|
|
|
- />
|
|
|
-
|
|
|
- <q-btn
|
|
|
- v-else
|
|
|
- color="negative"
|
|
|
- dense
|
|
|
- flat
|
|
|
- icon="mdi-delete-outline"
|
|
|
- round
|
|
|
- @click="removeMaterial(index)"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -198,7 +74,9 @@
|
|
|
>
|
|
|
<ProposalPricingTab
|
|
|
v-model="form.pavao"
|
|
|
+ v-model:enabled="form.pavao_enabled"
|
|
|
:product-options="productOptions"
|
|
|
+ section-label="Pavão"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
@@ -208,7 +86,9 @@
|
|
|
>
|
|
|
<ProposalPricingTab
|
|
|
v-model="form.irrecusavel"
|
|
|
+ v-model:enabled="form.irrecusavel_enabled"
|
|
|
:product-options="productOptions"
|
|
|
+ section-label="Irrecusável"
|
|
|
show-condition
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -258,11 +138,9 @@ import { useScroll } from "src/composables/useScroll";
|
|
|
import { useSubmitHandler } from "src/composables/useSubmitHandler";
|
|
|
|
|
|
import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
|
|
|
-import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
|
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
import DefaultForm from "src/components/defaults/DefaultForm.vue";
|
|
|
import DefaultInput from "src/components/defaults/DefaultInput.vue";
|
|
|
-import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
|
|
|
import ProposalPricingTab from "./ProposalPricingTab.vue";
|
|
|
|
|
|
defineEmits([...useDialogPluginComponent.emits]);
|
|
|
@@ -310,19 +188,11 @@ const defaultPricingBlock = (withCondition = false) => ({
|
|
|
const { form, getUpdatedFields, setUpdateFormAsOriginal } = useForm(
|
|
|
{
|
|
|
class_duration_hours: (props.package?.class_duration_minutes ?? 120) / 60,
|
|
|
- contract_register_value: props.package?.contract_register_value ?? null,
|
|
|
- contract_value: props.package?.contract_value ?? null,
|
|
|
- contrat_discount_value: props.package?.contrat_discount_value ?? null,
|
|
|
irrecusavel: defaultPricingBlock(true),
|
|
|
- materials: [
|
|
|
- {
|
|
|
- price: null,
|
|
|
- product_id: null,
|
|
|
- quantity: 1,
|
|
|
- },
|
|
|
- ],
|
|
|
+ irrecusavel_enabled: false,
|
|
|
name: props.package?.name ?? null,
|
|
|
pavao: defaultPricingBlock(),
|
|
|
+ pavao_enabled: false,
|
|
|
quantity_classes: props.package?.quantity_classes ?? null,
|
|
|
},
|
|
|
{ containerRef: scrollAreaRef },
|
|
|
@@ -356,28 +226,6 @@ const tabs = [
|
|
|
{ label: "Irrecusável", name: "irrecusavel" },
|
|
|
];
|
|
|
|
|
|
-const addMaterial = () => {
|
|
|
- form.materials.push({
|
|
|
- price: null,
|
|
|
- product_id: null,
|
|
|
- quantity: 1,
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-const onProductSelected = (material) => {
|
|
|
- const option = productOptions.value.find(
|
|
|
- (item) => item.value === material.product_id,
|
|
|
- );
|
|
|
-
|
|
|
- if (option) {
|
|
|
- material.price = option.price_sale;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const removeMaterial = (index) => {
|
|
|
- form.materials.splice(index, 1);
|
|
|
-};
|
|
|
-
|
|
|
const mapPricingBlockFromApi = (data, existingMaterials) => ({
|
|
|
classes_discount_percentage: data.classes_discount_percentage,
|
|
|
classes_included_in_course: data.classes_included_in_course,
|
|
|
@@ -451,13 +299,8 @@ const loadData = async () => {
|
|
|
if (props.package?.id) {
|
|
|
const packageData = await getPackage(props.package.id);
|
|
|
|
|
|
- if (packageData.materials?.length) {
|
|
|
- form.materials = packageData.materials.map((material) => ({
|
|
|
- price: material.price,
|
|
|
- product_id: material.product_id,
|
|
|
- quantity: material.quantity,
|
|
|
- }));
|
|
|
- }
|
|
|
+ form.pavao_enabled = !!packageData.pavao;
|
|
|
+ form.irrecusavel_enabled = !!packageData.irrecusavel;
|
|
|
|
|
|
if (packageData.pavao) {
|
|
|
form.pavao = mapPricingBlockFromApi(packageData.pavao, form.pavao.materials);
|
|
|
@@ -472,29 +315,17 @@ const loadData = async () => {
|
|
|
};
|
|
|
|
|
|
const onOKClick = async () => {
|
|
|
- const validMaterials = form.materials.filter(
|
|
|
- (material) => material.product_id,
|
|
|
- );
|
|
|
-
|
|
|
const payload = {
|
|
|
class_duration_minutes: Math.round(
|
|
|
Number(form.class_duration_hours) * 60,
|
|
|
),
|
|
|
|
|
|
- contract_register_value: form.contract_register_value,
|
|
|
- contract_value: form.contract_value,
|
|
|
- contrat_discount_value: form.contrat_discount_value,
|
|
|
-
|
|
|
- irrecusavel: toPricingPayload(form.irrecusavel, { withCondition: true }),
|
|
|
-
|
|
|
- materials: validMaterials.map((material) => ({
|
|
|
- price: Number(material.price),
|
|
|
- product_id: material.product_id,
|
|
|
- quantity: Number(material.quantity),
|
|
|
- })),
|
|
|
+ irrecusavel: form.irrecusavel_enabled
|
|
|
+ ? toPricingPayload(form.irrecusavel, { withCondition: true })
|
|
|
+ : null,
|
|
|
|
|
|
name: form.name,
|
|
|
- pavao: toPricingPayload(form.pavao),
|
|
|
+ pavao: form.pavao_enabled ? toPricingPayload(form.pavao) : null,
|
|
|
quantity_classes: form.quantity_classes,
|
|
|
};
|
|
|
|
|
|
@@ -511,10 +342,14 @@ const onOKClick = async () => {
|
|
|
}
|
|
|
|
|
|
// Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo
|
|
|
- // de checkbox) que não é seguro fatiar num diff parcial — vão inteiros.
|
|
|
+ // de checkbox) e um sinal explícito de "removido" (null) — não é
|
|
|
+ // seguro fatiar isso num diff parcial, vão sempre inteiros.
|
|
|
updated.pavao = payload.pavao;
|
|
|
updated.irrecusavel = payload.irrecusavel;
|
|
|
|
|
|
+ delete updated.pavao_enabled;
|
|
|
+ delete updated.irrecusavel_enabled;
|
|
|
+
|
|
|
return updatePackage(props.package.id, updated);
|
|
|
}
|
|
|
|