|
|
@@ -2,54 +2,96 @@
|
|
|
<q-page class="bg-page q-pb-xl">
|
|
|
<div class="payments-header row items-center bg-white">
|
|
|
<q-space />
|
|
|
- <span class="gradient-diarista font16 fontbold">{{ $t('provider.payments.title') }}</span>
|
|
|
+
|
|
|
+ <span class="gradient-diarista font16 fontbold">
|
|
|
+ {{ $t("provider.payments.title") }}
|
|
|
+ </span>
|
|
|
+
|
|
|
<q-space />
|
|
|
</div>
|
|
|
|
|
|
<div class="q-mx-md q-mt-md">
|
|
|
- <q-card class="earnings-card bg-primary" :flat="false" style="cursor: pointer;" @click="earningsExpanded = !earningsExpanded">
|
|
|
+ <q-card
|
|
|
+ class="earnings-card bg-primary"
|
|
|
+ style="cursor: pointer"
|
|
|
+ :flat="false"
|
|
|
+ @click="earningsExpanded = !earningsExpanded"
|
|
|
+ >
|
|
|
<q-card-section class="q-pa-md">
|
|
|
<div class="row items-center no-wrap">
|
|
|
- <q-icon name="mdi-currency-usd" size="22px" color="white" class="q-mr-sm" />
|
|
|
- <span class="text-white font16 fontbold">{{ $t('provider.payments.total_earnings_title') }}</span>
|
|
|
- <q-space />
|
|
|
<q-icon
|
|
|
- :name="earningsExpanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
|
|
+ class="q-mr-sm"
|
|
|
+ color="white"
|
|
|
+ name="mdi-currency-usd"
|
|
|
size="22px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <span class="text-white font16 fontbold">
|
|
|
+ {{ $t("provider.payments.total_earnings_title") }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <q-space />
|
|
|
+
|
|
|
+ <q-icon
|
|
|
color="white"
|
|
|
+ size="22px"
|
|
|
+ :name="
|
|
|
+ earningsExpanded
|
|
|
+ ? 'mdi-chevron-up'
|
|
|
+ : 'mdi-chevron-down'
|
|
|
+ "
|
|
|
/>
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
|
|
|
<q-slide-transition>
|
|
|
- <div v-show="earningsExpanded" @click.stop>
|
|
|
- <q-separator color="white" style="opacity: 0.2;" />
|
|
|
+ <div
|
|
|
+ v-show="earningsExpanded"
|
|
|
+ @click.stop
|
|
|
+ >
|
|
|
+ <q-separator
|
|
|
+ color="white"
|
|
|
+ style="opacity: 0.2"
|
|
|
+ />
|
|
|
+
|
|
|
<q-card-section class="q-pa-md">
|
|
|
<div class="earnings-value font28 fontbold text-white">
|
|
|
{{ formatCurrency(earningsByPeriod[selectedPeriod].value) }}
|
|
|
</div>
|
|
|
+
|
|
|
<div class="earnings-count font12 text-white q-mb-md">
|
|
|
- {{ $t('provider.payments.total_earnings_count', { count: earningsByPeriod[selectedPeriod].count }) }}
|
|
|
+ {{ $t("provider.payments.total_earnings_count", { count: earningsByPeriod[selectedPeriod].count }) }}
|
|
|
</div>
|
|
|
|
|
|
<div class="row q-gutter-x-xs q-mb-md">
|
|
|
<q-btn
|
|
|
v-for="period in periods"
|
|
|
:key="period.key"
|
|
|
- dense
|
|
|
- no-caps
|
|
|
- :unelevated="selectedPeriod === period.key"
|
|
|
+ :color="
|
|
|
+ selectedPeriod === period.key
|
|
|
+ ? 'white'
|
|
|
+ : undefined
|
|
|
+ "
|
|
|
:flat="selectedPeriod !== period.key"
|
|
|
- :rounded="true"
|
|
|
- :color="selectedPeriod === period.key ? 'white' : undefined"
|
|
|
- :text-color="selectedPeriod === period.key ? 'primary' : 'white'"
|
|
|
:label="$t(period.labelKey)"
|
|
|
+ :rounded="true"
|
|
|
+ :text-color="
|
|
|
+ selectedPeriod === period.key
|
|
|
+ ? 'primary'
|
|
|
+ : 'white'
|
|
|
+ "
|
|
|
+ :unelevated="selectedPeriod === period.key"
|
|
|
+ dense
|
|
|
+ no-caps
|
|
|
@click.stop="selectedPeriod = period.key"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
- <div class="text-white font12" style="opacity: 0.8;">
|
|
|
- {{ $t('provider.payments.total_earnings_days', { days: periodDays[selectedPeriod] }) }}
|
|
|
+ <div
|
|
|
+ class="text-white font12"
|
|
|
+ style="opacity: 0.8"
|
|
|
+ >
|
|
|
+ {{ $t("provider.payments.total_earnings_days", { days: periodDays[selectedPeriod] }) }}
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
</div>
|
|
|
@@ -58,44 +100,75 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="q-mx-md q-mt-sm">
|
|
|
- <q-card class="balance-card bg-surface shadow-card" :flat="false">
|
|
|
+ <q-card
|
|
|
+ class="balance-card bg-surface shadow-card"
|
|
|
+ :flat="false"
|
|
|
+ >
|
|
|
<q-card-section class="q-pa-md">
|
|
|
<div class="row items-center no-wrap q-mb-xs">
|
|
|
<div class="col">
|
|
|
- <div class="text-grey-6 font12 fontbold">{{ $t('provider.payments.available_balance_label') }}</div>
|
|
|
- <div class="balance-value fontbold text-secondary">{{ formatCurrency(saldoDisponivel) }}</div>
|
|
|
+ <div class="text-grey-6 font12 fontbold">
|
|
|
+ {{ $t("provider.payments.available_balance_label") }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="balance-value fontbold text-secondary">
|
|
|
+ {{ formatCurrency(saldoDisponivel) }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+
|
|
|
<q-btn
|
|
|
- unelevated
|
|
|
- no-caps
|
|
|
- color="secondary"
|
|
|
class="btn-withdraw font13 fontbold"
|
|
|
+ color="secondary"
|
|
|
+ no-caps
|
|
|
+ unelevated
|
|
|
+ :disable="!canWithdraw"
|
|
|
:label="$t('provider.payments.btn_withdraw')"
|
|
|
:loading="withdrawLoading"
|
|
|
- :disable="!canWithdraw"
|
|
|
@click="onSacar"
|
|
|
/>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="text-grey-6 font12">
|
|
|
- {{ $t('provider.payments.available_balance_prefix') }}
|
|
|
- <strong>{{ $t('provider.payments.available_balance_highlight') }}</strong>
|
|
|
+ {{ $t("provider.payments.available_balance_prefix") }}
|
|
|
+
|
|
|
+ <strong>
|
|
|
+ {{ $t("provider.payments.available_balance_highlight") }}
|
|
|
+ </strong>
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
</q-card>
|
|
|
</div>
|
|
|
|
|
|
<div class="q-mx-md q-mt-sm">
|
|
|
- <q-card class="balance-card bg-secondary shadow-card" :flat="false">
|
|
|
+ <q-card
|
|
|
+ class="balance-card bg-secondary shadow-card"
|
|
|
+ :flat="false"
|
|
|
+ >
|
|
|
<q-card-section class="q-pa-md">
|
|
|
<div class="row items-center no-wrap">
|
|
|
<div class="col">
|
|
|
- <div class="text-white font12">{{ $t('provider.payments.pending_balance_label') }}</div>
|
|
|
- <div class="balance-value fontbold text-white">{{ formatCurrency(saldoALiberar) }}</div>
|
|
|
- <div class="text-white font12 q-mt-xs" style="opacity: 0.85;">
|
|
|
- {{ $t('provider.payments.pending_balance_desc') }}
|
|
|
+ <div class="text-white font12">
|
|
|
+ {{ $t("provider.payments.pending_balance_label") }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="balance-value fontbold text-white">
|
|
|
+ {{ formatCurrency(saldoALiberar) }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="text-white font12 q-mt-xs"
|
|
|
+ style="opacity: 0.85"
|
|
|
+ >
|
|
|
+ {{ $t("provider.payments.pending_balance_desc") }}
|
|
|
</div>
|
|
|
</div>
|
|
|
- <q-icon name="mdi-clock-outline" color="white" size="32px" style="opacity: 0.7;" />
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ color="white"
|
|
|
+ name="mdi-clock-outline"
|
|
|
+ size="32px"
|
|
|
+ style="opacity: 0.7"
|
|
|
+ />
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
</q-card>
|
|
|
@@ -103,24 +176,37 @@
|
|
|
|
|
|
<div class="q-mx-md q-mt-md">
|
|
|
<div class="row items-center no-wrap q-mb-sm">
|
|
|
- <span class="section-title font16 fontbold gradient-diarista">{{ $t('provider.payments.services_title') }}</span>
|
|
|
+ <span class="section-title font16 fontbold gradient-diarista">
|
|
|
+ {{ $t("provider.payments.services_title") }}
|
|
|
+ </span>
|
|
|
+
|
|
|
<q-space />
|
|
|
+
|
|
|
<q-btn
|
|
|
+ color="primary"
|
|
|
flat
|
|
|
+ icon-right="mdi-chevron-right"
|
|
|
no-caps
|
|
|
- color="primary"
|
|
|
size="sm"
|
|
|
:label="$t('provider.payments.btn_view_movements')"
|
|
|
- icon-right="mdi-chevron-right"
|
|
|
@click="openMovimentacoes"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="servicesLoading" class="flex flex-center q-py-xl">
|
|
|
- <q-spinner color="primary" size="40px" />
|
|
|
+ <div
|
|
|
+ v-if="servicesLoading"
|
|
|
+ class="flex flex-center q-py-xl"
|
|
|
+ >
|
|
|
+ <q-spinner
|
|
|
+ color="primary"
|
|
|
+ size="40px"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
- <template v-for="item in services" :key="item.id">
|
|
|
+ <template
|
|
|
+ v-for="item in services"
|
|
|
+ :key="item.id"
|
|
|
+ >
|
|
|
<q-card
|
|
|
class="service-card bg-surface shadow-card q-mb-sm"
|
|
|
:flat="false"
|
|
|
@@ -128,33 +214,63 @@
|
|
|
<q-card-section class="q-pa-sm">
|
|
|
<div class="row no-wrap items-start q-gutter-x-sm">
|
|
|
<q-avatar size="44px">
|
|
|
- <img v-if="item.client_photo" :src="item.client_photo" style="object-fit:cover" />
|
|
|
- <span v-else class="full-width full-height flex flex-center" :style="avatarColors[item.id % avatarColors.length]" style="border-radius:50%">{{ item.client_name?.slice(0,2).toUpperCase() ?? '??' }}</span>
|
|
|
+ <img
|
|
|
+ v-if="item.client_photo"
|
|
|
+ :src="item.client_photo"
|
|
|
+ style="object-fit: cover"
|
|
|
+ />
|
|
|
+
|
|
|
+ <span
|
|
|
+ v-else
|
|
|
+ class="full-width full-height flex flex-center"
|
|
|
+ style="border-radius: 50%"
|
|
|
+ :style="avatarColors[item.id % avatarColors.length]"
|
|
|
+ >
|
|
|
+ {{ item.client_name?.slice(0, 2).toUpperCase() ?? "??" }}
|
|
|
+ </span>
|
|
|
</q-avatar>
|
|
|
|
|
|
<div class="col column">
|
|
|
- <span class="text-name font13 fontbold ellipsis">{{ item.client_name }}</span>
|
|
|
- <div class="">
|
|
|
- <span class="">{{ $t('provider.payments.services_date_service') }}</span>
|
|
|
- {{ ' ' + item.date }}
|
|
|
+ <span class="text-name font13 fontbold ellipsis">
|
|
|
+ {{ item.client_name }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ {{ $t("provider.payments.services_date_service") }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ {{ " " + item.date }}
|
|
|
</div>
|
|
|
- <div class="">
|
|
|
- <span class="">{{ $t('provider.payments.services_date_payment') }}</span>
|
|
|
- {{ ' ' + item.payment_date }}
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ {{ $t("provider.payments.services_date_payment") }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ {{ " " + item.payment_date }}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto column items-end">
|
|
|
<q-chip
|
|
|
+ class="status-chip font11 fontbold"
|
|
|
dense
|
|
|
square
|
|
|
:color="payStatusBgColor(item.payment_status)"
|
|
|
- :text-color="payStatusTextColor(item.payment_status)"
|
|
|
:label="payStatusLabel(item.payment_status)"
|
|
|
- class="status-chip font11 fontbold"
|
|
|
+ :text-color="
|
|
|
+ payStatusTextColor(item.payment_status)
|
|
|
+ "
|
|
|
/>
|
|
|
- <span class="text-price font13 fontbold">{{ formatCurrency(item.total_amount) }}</span>
|
|
|
- <span class="text-period font10">{{ item.period_label }}</span>
|
|
|
+
|
|
|
+ <span class="text-price font13 fontbold">
|
|
|
+ {{ formatCurrency(item.total_amount) }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <span class="text-period font10">
|
|
|
+ {{ item.period_label }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
@@ -165,153 +281,162 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { computed, ref, onMounted } from 'vue';
|
|
|
-import { useQuasar } from 'quasar';
|
|
|
-import { useI18n } from 'vue-i18n';
|
|
|
-import { formatCurrency } from 'src/helpers/utils';
|
|
|
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
|
|
|
-import { getBalance, getWithdrawalFees, requestWithdrawal } from 'src/api/providerWithdrawal';
|
|
|
-import { getPaymentSplits } from 'src/api/paymentSplit';
|
|
|
-import MovimentacoesDialog from 'src/components/payments/MovimentacoesDialog.vue';
|
|
|
-import WithdrawConfirmDialog from 'src/components/payments/WithdrawConfirmDialog.vue';
|
|
|
-
|
|
|
-const $q = useQuasar();
|
|
|
-const { t } = useI18n();
|
|
|
-
|
|
|
-const avatarColors = [
|
|
|
- { background: '#ffd5df', color: '#932e57' },
|
|
|
- { background: '#d7e8ff', color: '#2158a8' },
|
|
|
- { background: '#dfd', color: '#2a7a3b' },
|
|
|
- { background: '#ffe5cc', color: '#8a4500' },
|
|
|
-];
|
|
|
-const earningsExpanded = ref(false);
|
|
|
-const selectedPeriod = ref('week');
|
|
|
-const servicesLoading = ref(true);
|
|
|
-const withdrawLoading = ref(false);
|
|
|
-
|
|
|
-const periods = [
|
|
|
- { key: 'week', labelKey: 'provider.payments.period_week' },
|
|
|
- { key: 'month', labelKey: 'provider.payments.period_month' },
|
|
|
- { key: 'year', labelKey: 'provider.payments.period_year' },
|
|
|
-];
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
+import { formatCurrency } from "src/helpers/utils";
|
|
|
|
|
|
-const periodDays = { week: 7, month: 30, year: 365 };
|
|
|
+import {
|
|
|
+ getBalance,
|
|
|
+ getWithdrawalFees,
|
|
|
+ requestWithdrawal,
|
|
|
+} from "src/api/providerWithdrawal";
|
|
|
|
|
|
-const earningsByPeriod = ref({
|
|
|
- week: { value: 0, count: 0 },
|
|
|
- month: { value: 0, count: 0 },
|
|
|
- year: { value: 0, count: 0 },
|
|
|
-});
|
|
|
+import { getPaymentSplits } from "src/api/paymentSplit";
|
|
|
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
|
|
|
-const saldoDisponivel = ref(0);
|
|
|
-const saldoALiberar = ref(0);
|
|
|
-const paymentTransferFeeAmount = ref(3.67);
|
|
|
-const services = ref([]);
|
|
|
+import MovimentacoesDialog from "src/components/payments/MovimentacoesDialog.vue";
|
|
|
+import WithdrawConfirmDialog from "src/components/payments/WithdrawConfirmDialog.vue";
|
|
|
|
|
|
-const parseAmount = (value) => {
|
|
|
- const amount = Number.parseFloat(value);
|
|
|
- return Number.isFinite(amount) ? amount : 0;
|
|
|
-};
|
|
|
-
|
|
|
-const roundMoney = (value) => Math.round((parseAmount(value) + Number.EPSILON) * 100) / 100;
|
|
|
-
|
|
|
-const withdrawalNetAmount = computed(() => (
|
|
|
- Math.max(0, roundMoney(saldoDisponivel.value - paymentTransferFeeAmount.value))
|
|
|
-));
|
|
|
+const $q = useQuasar();
|
|
|
|
|
|
-const canWithdraw = computed(() => (
|
|
|
- saldoDisponivel.value > 0
|
|
|
- && withdrawalNetAmount.value > 0
|
|
|
- && !withdrawLoading.value
|
|
|
-));
|
|
|
+const { t } = useI18n();
|
|
|
|
|
|
-const splitAmount = (split) => (
|
|
|
- parseAmount(split.provider_amount ?? split.net_amount ?? split.gross_amount)
|
|
|
-);
|
|
|
+const earningsExpanded = ref(false);
|
|
|
+const selectedPeriod = ref("week");
|
|
|
+const servicesLoading = ref(true);
|
|
|
+const withdrawLoading = ref(false);
|
|
|
|
|
|
-const splitPaymentStatus = (split) => (
|
|
|
- split.payment_status ?? split.payment?.status ?? 'pending'
|
|
|
-);
|
|
|
+const saldoDisponivel = ref(0);
|
|
|
+const saldoALiberar = ref(0);
|
|
|
|
|
|
-const splitScheduleStatus = (split) => (
|
|
|
- split.schedule_status ?? split.schedule?.status ?? split.scheduleStatus ?? ''
|
|
|
-);
|
|
|
+const paymentTransferFeeAmount = ref(3.67);
|
|
|
|
|
|
-const splitCodeVerified = (split) => (
|
|
|
- split.code_verified ?? split.schedule?.code_verified ?? false
|
|
|
-);
|
|
|
+const services = ref([]);
|
|
|
|
|
|
-const isTruthyFlag = (value) => (
|
|
|
- value === true || value === 1 || value === '1' || value === 'true'
|
|
|
-);
|
|
|
+const earningsByPeriod = ref({
|
|
|
+ week: {
|
|
|
+ value: 0,
|
|
|
+ count: 0,
|
|
|
+ },
|
|
|
+ month: {
|
|
|
+ value: 0,
|
|
|
+ count: 0,
|
|
|
+ },
|
|
|
+ year: {
|
|
|
+ value: 0,
|
|
|
+ count: 0,
|
|
|
+ },
|
|
|
+});
|
|
|
|
|
|
-const isSplitPaidAndFinished = (split) => (
|
|
|
- splitPaymentStatus(split) === 'paid'
|
|
|
- && (!splitScheduleStatus(split) || splitScheduleStatus(split) === 'finished')
|
|
|
- && isTruthyFlag(splitCodeVerified(split))
|
|
|
+const withdrawalNetAmount = computed(() =>
|
|
|
+ Math.max(
|
|
|
+ 0,
|
|
|
+ roundMoney(
|
|
|
+ saldoDisponivel.value - paymentTransferFeeAmount.value,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
);
|
|
|
|
|
|
-const splitReleaseDate = (split) => (
|
|
|
- split.available_at
|
|
|
- ?? split.release_at
|
|
|
- ?? split.released_at
|
|
|
- ?? split.withdraw_available_at
|
|
|
- ?? null
|
|
|
+const canWithdraw = computed(
|
|
|
+ () =>
|
|
|
+ saldoDisponivel.value > 0 &&
|
|
|
+ withdrawalNetAmount.value > 0 &&
|
|
|
+ !withdrawLoading.value,
|
|
|
);
|
|
|
|
|
|
-const isSplitAvailable = (split) => {
|
|
|
- if (!isSplitPaidAndFinished(split)) return false;
|
|
|
+const avatarColors = [
|
|
|
+ { background: "#ffd5df", color: "#932e57" },
|
|
|
+ { background: "#d7e8ff", color: "#2158a8" },
|
|
|
+ { background: "#dfd", color: "#2a7a3b" },
|
|
|
+ { background: "#ffe5cc", color: "#8a4500" },
|
|
|
+];
|
|
|
|
|
|
- const releaseAt = splitReleaseDate(split);
|
|
|
- if (!releaseAt) return true;
|
|
|
+const periods = [
|
|
|
+ {
|
|
|
+ key: "week",
|
|
|
+ labelKey: "provider.payments.period_week",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "month",
|
|
|
+ labelKey: "provider.payments.period_month",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "year",
|
|
|
+ labelKey: "provider.payments.period_year",
|
|
|
+ },
|
|
|
+];
|
|
|
|
|
|
- const releaseDate = parseDateSafe(releaseAt);
|
|
|
- return !releaseDate || releaseDate <= new Date();
|
|
|
+const periodDays = {
|
|
|
+ week: 7,
|
|
|
+ month: 30,
|
|
|
+ year: 365,
|
|
|
};
|
|
|
|
|
|
-const sumSplits = (splits, predicate) => (
|
|
|
- splits.reduce((total, split) => total + (predicate(split) ? splitAmount(split) : 0), 0)
|
|
|
-);
|
|
|
-
|
|
|
const loadData = async () => {
|
|
|
servicesLoading.value = true;
|
|
|
+
|
|
|
try {
|
|
|
const [balance, splits, fees] = await Promise.all([
|
|
|
getBalance(),
|
|
|
getPaymentSplits(),
|
|
|
loadWithdrawalFees(),
|
|
|
]);
|
|
|
+
|
|
|
const paymentSplits = splits || [];
|
|
|
- const availableFromSplits = sumSplits(paymentSplits, isSplitAvailable);
|
|
|
+
|
|
|
+ const availableFromSplits = sumSplits(
|
|
|
+ paymentSplits,
|
|
|
+ isSplitAvailable,
|
|
|
+ );
|
|
|
+
|
|
|
const pendingFromSplits = sumSplits(
|
|
|
paymentSplits,
|
|
|
- (split) => isSplitPaidAndFinished(split) && !isSplitAvailable(split)
|
|
|
+ (split) =>
|
|
|
+ isSplitPaidAndFinished(split) &&
|
|
|
+ !isSplitAvailable(split),
|
|
|
);
|
|
|
|
|
|
- saldoDisponivel.value = parseAmount(balance?.available) || availableFromSplits;
|
|
|
- saldoALiberar.value = parseAmount(balance?.pending) || pendingFromSplits;
|
|
|
- paymentTransferFeeAmount.value = parseAmount(
|
|
|
- fees?.payment_transfer_fee_amount
|
|
|
- ?? fees?.gateway_fee_amount
|
|
|
- ?? fees?.fee_amount
|
|
|
- ?? fees?.amount
|
|
|
- ) || paymentTransferFeeAmount.value;
|
|
|
-
|
|
|
- services.value = paymentSplits.map((s) => ({
|
|
|
- id: s.id,
|
|
|
- client_name: s.client_name ?? t('provider.payments.default_client_name'),
|
|
|
+ saldoDisponivel.value =
|
|
|
+ parseAmount(balance?.available) || availableFromSplits;
|
|
|
+
|
|
|
+ saldoALiberar.value =
|
|
|
+ parseAmount(balance?.pending) || pendingFromSplits;
|
|
|
+
|
|
|
+ paymentTransferFeeAmount.value =
|
|
|
+ parseAmount(
|
|
|
+ fees?.payment_transfer_fee_amount ??
|
|
|
+ fees?.gateway_fee_amount ??
|
|
|
+ fees?.fee_amount ??
|
|
|
+ fees?.amount,
|
|
|
+ ) || paymentTransferFeeAmount.value;
|
|
|
+
|
|
|
+ services.value = paymentSplits.map((split) => ({
|
|
|
+ id: split.id,
|
|
|
+ client_name:
|
|
|
+ split.client_name ??
|
|
|
+ t("provider.payments.default_client_name"),
|
|
|
client_photo: null,
|
|
|
- date: formatServiceDate(s.schedule_date),
|
|
|
- payment_date: formatIsoDate(s.payment_paid_at),
|
|
|
- total_amount: splitAmount(s),
|
|
|
- period_label: s.schedule_period_type
|
|
|
- ? t(labelsPeriodTypes.find(l => l.value == s.schedule_period_type)?.label)
|
|
|
- : '',
|
|
|
- schedule_type: 'default',
|
|
|
- payment_status: splitPaymentStatus(s),
|
|
|
+ date: formatServiceDate(split.schedule_date),
|
|
|
+ payment_date: formatIsoDate(split.payment_paid_at),
|
|
|
+ total_amount: splitAmount(split),
|
|
|
+ period_label: split.schedule_period_type
|
|
|
+ ? t(
|
|
|
+ labelsPeriodTypes.find(
|
|
|
+ (label) =>
|
|
|
+ label.value == split.schedule_period_type,
|
|
|
+ )?.label,
|
|
|
+ )
|
|
|
+ : "",
|
|
|
+ schedule_type: "default",
|
|
|
+ payment_status: splitPaymentStatus(split),
|
|
|
}));
|
|
|
} catch (error) {
|
|
|
- $q.notify({ type: 'negative', message: error?.response?.data?.message || error.message });
|
|
|
+ $q.notify({
|
|
|
+ type: "negative",
|
|
|
+ message:
|
|
|
+ error?.response?.data?.message || error.message,
|
|
|
+ });
|
|
|
} finally {
|
|
|
servicesLoading.value = false;
|
|
|
}
|
|
|
@@ -321,13 +446,20 @@ const loadWithdrawalFees = async () => {
|
|
|
try {
|
|
|
return await getWithdrawalFees();
|
|
|
} catch {
|
|
|
- return { payment_transfer_fee_amount: paymentTransferFeeAmount.value };
|
|
|
+ return {
|
|
|
+ payment_transfer_fee_amount:
|
|
|
+ paymentTransferFeeAmount.value,
|
|
|
+ };
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const onSacar = () => {
|
|
|
const grossAmount = roundMoney(saldoDisponivel.value);
|
|
|
- const gatewayFeeAmount = roundMoney(paymentTransferFeeAmount.value);
|
|
|
+
|
|
|
+ const gatewayFeeAmount = roundMoney(
|
|
|
+ paymentTransferFeeAmount.value,
|
|
|
+ );
|
|
|
+
|
|
|
const netAmount = roundMoney(withdrawalNetAmount.value);
|
|
|
|
|
|
$q.dialog({
|
|
|
@@ -339,113 +471,226 @@ const onSacar = () => {
|
|
|
},
|
|
|
}).onOk(async () => {
|
|
|
withdrawLoading.value = true;
|
|
|
+
|
|
|
try {
|
|
|
await requestWithdrawal({
|
|
|
gross_amount: grossAmount,
|
|
|
gateway_fee_amount: gatewayFeeAmount,
|
|
|
net_amount: netAmount,
|
|
|
});
|
|
|
+
|
|
|
saldoDisponivel.value = 0;
|
|
|
- // notification handled by axios interceptor
|
|
|
} catch (error) {
|
|
|
- $q.notify({ type: 'negative', message: error?.response?.data?.message || error.message });
|
|
|
+ $q.notify({
|
|
|
+ type: "negative",
|
|
|
+ message:
|
|
|
+ error?.response?.data?.message || error.message,
|
|
|
+ });
|
|
|
} finally {
|
|
|
withdrawLoading.value = false;
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+const openMovimentacoes = () => {
|
|
|
+ $q.dialog({
|
|
|
+ component: MovimentacoesDialog,
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const parseAmount = (value) => {
|
|
|
+ const amount = Number.parseFloat(value);
|
|
|
+
|
|
|
+ return Number.isFinite(amount) ? amount : 0;
|
|
|
+};
|
|
|
+
|
|
|
+const roundMoney = (value) =>
|
|
|
+ Math.round(
|
|
|
+ (parseAmount(value) + Number.EPSILON) * 100,
|
|
|
+ ) / 100;
|
|
|
+
|
|
|
+const splitAmount = (split) =>
|
|
|
+ parseAmount(
|
|
|
+ split.provider_amount ??
|
|
|
+ split.net_amount ??
|
|
|
+ split.gross_amount,
|
|
|
+ );
|
|
|
+
|
|
|
+const splitPaymentStatus = (split) =>
|
|
|
+ split.payment_status ??
|
|
|
+ split.payment?.status ??
|
|
|
+ "pending";
|
|
|
+
|
|
|
+const splitScheduleStatus = (split) =>
|
|
|
+ split.schedule_status ??
|
|
|
+ split.schedule?.status ??
|
|
|
+ split.scheduleStatus ??
|
|
|
+ "";
|
|
|
+
|
|
|
+const splitCodeVerified = (split) =>
|
|
|
+ split.code_verified ??
|
|
|
+ split.schedule?.code_verified ??
|
|
|
+ false;
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
+const isSplitAvailable = (split) => {
|
|
|
+ if (!isSplitPaidAndFinished(split)) return false;
|
|
|
+
|
|
|
+ const releaseAt = splitReleaseDate(split);
|
|
|
+
|
|
|
+ if (!releaseAt) return true;
|
|
|
+
|
|
|
+ const releaseDate = parseDateSafe(releaseAt);
|
|
|
+
|
|
|
+ return !releaseDate || releaseDate <= new Date();
|
|
|
+};
|
|
|
+
|
|
|
+const isTruthyFlag = (value) =>
|
|
|
+ value === true ||
|
|
|
+ value === 1 ||
|
|
|
+ value === "1" ||
|
|
|
+ value === "true";
|
|
|
+
|
|
|
+const isSplitPaidAndFinished = (split) =>
|
|
|
+ splitPaymentStatus(split) === "paid" &&
|
|
|
+ (!splitScheduleStatus(split) ||
|
|
|
+ splitScheduleStatus(split) === "finished") &&
|
|
|
+ isTruthyFlag(splitCodeVerified(split));
|
|
|
+
|
|
|
+const splitReleaseDate = (split) =>
|
|
|
+ split.available_at ??
|
|
|
+ split.release_at ??
|
|
|
+ split.released_at ??
|
|
|
+ split.withdraw_available_at ??
|
|
|
+ null;
|
|
|
+
|
|
|
+const sumSplits = (splits, predicate) =>
|
|
|
+ splits.reduce(
|
|
|
+ (total, split) =>
|
|
|
+ total +
|
|
|
+ (predicate(split) ? splitAmount(split) : 0),
|
|
|
+ 0,
|
|
|
+ );
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
const parseDateSafe = (value) => {
|
|
|
if (!value) return null;
|
|
|
+
|
|
|
const raw = String(value).trim();
|
|
|
|
|
|
- // yyyy-mm-dd (from API date fields)
|
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
|
|
const date = new Date(`${raw}T12:00:00`);
|
|
|
+
|
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
|
}
|
|
|
|
|
|
- // dd/mm/yyyy
|
|
|
- const brMatch = raw.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
|
- if (brMatch) {
|
|
|
- const [, day, month, year] = brMatch;
|
|
|
- const date = new Date(`${year}-${month}-${day}T12:00:00`);
|
|
|
+ const brDate = raw.match(
|
|
|
+ /^(\d{2})\/(\d{2})\/(\d{4})$/,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (brDate) {
|
|
|
+ const [, day, month, year] = brDate;
|
|
|
+ const date = new Date(
|
|
|
+ `${year}-${month}-${day}T12:00:00`,
|
|
|
+ );
|
|
|
+
|
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
|
}
|
|
|
|
|
|
const date = new Date(raw);
|
|
|
+
|
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
|
};
|
|
|
|
|
|
-const formatShortDate = (dateStr) => {
|
|
|
- const date = parseDateSafe(dateStr);
|
|
|
- if (!date) return '';
|
|
|
- return date.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
|
|
|
+
|
|
|
+const formatShortDate = (dateString) => {
|
|
|
+ const date = parseDateSafe(dateString);
|
|
|
+
|
|
|
+ if (!date) return "";
|
|
|
+
|
|
|
+ return date.toLocaleDateString("pt-BR", {
|
|
|
+ day: "2-digit",
|
|
|
+ month: "2-digit",
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
-const formatServiceDate = (dateStr) => formatShortDate(dateStr);
|
|
|
-const formatIsoDate = (dateStr) => formatShortDate(dateStr);
|
|
|
+const formatIsoDate = (dateString) =>
|
|
|
+ formatShortDate(dateString);
|
|
|
|
|
|
-const payStatusLabel = (status) => {
|
|
|
- const map = {
|
|
|
- pending: t('provider.payments.pay_status_pending'),
|
|
|
- paid: t('provider.payments.pay_status_paid'),
|
|
|
- authorized: t('provider.payments.pay_status_authorized'),
|
|
|
- processing: t('provider.payments.pay_status_processing'),
|
|
|
- failed: t('provider.payments.pay_status_failed'),
|
|
|
- cancelled: t('provider.payments.pay_status_cancelled'),
|
|
|
+const formatServiceDate = (dateString) =>
|
|
|
+ formatShortDate(dateString);
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
+
|
|
|
+const payStatusBgColor = (status) => {
|
|
|
+ const colors = {
|
|
|
+ pending: "warning-bg",
|
|
|
+ paid: "success-bg",
|
|
|
+ authorized: "teal-bg",
|
|
|
+ processing: "info-bg",
|
|
|
+ failed: "error-bg",
|
|
|
+ cancelled: "neutral-bg",
|
|
|
};
|
|
|
- return map[status] ?? status;
|
|
|
+
|
|
|
+ return colors[status] ?? "neutral-bg";
|
|
|
};
|
|
|
|
|
|
-const payStatusBgColor = (status) => {
|
|
|
- const map = {
|
|
|
- pending: 'warning-bg',
|
|
|
- paid: 'success-bg',
|
|
|
- authorized: 'teal-bg',
|
|
|
- processing: 'info-bg',
|
|
|
- failed: 'error-bg',
|
|
|
- cancelled: 'neutral-bg',
|
|
|
+const payStatusLabel = (status) => {
|
|
|
+ const labels = {
|
|
|
+ pending: t("provider.payments.pay_status_pending"),
|
|
|
+ paid: t("provider.payments.pay_status_paid"),
|
|
|
+ authorized: t(
|
|
|
+ "provider.payments.pay_status_authorized",
|
|
|
+ ),
|
|
|
+ processing: t(
|
|
|
+ "provider.payments.pay_status_processing",
|
|
|
+ ),
|
|
|
+ failed: t("provider.payments.pay_status_failed"),
|
|
|
+ cancelled: t(
|
|
|
+ "provider.payments.pay_status_cancelled",
|
|
|
+ ),
|
|
|
};
|
|
|
- return map[status] ?? 'neutral-bg';
|
|
|
+
|
|
|
+ return labels[status] ?? status;
|
|
|
};
|
|
|
|
|
|
const payStatusTextColor = (status) => {
|
|
|
- const map = {
|
|
|
- pending: 'warning',
|
|
|
- paid: 'success',
|
|
|
- authorized: 'teal',
|
|
|
- processing: 'info',
|
|
|
- failed: 'error-dark',
|
|
|
- cancelled: 'status-finished',
|
|
|
+ const colors = {
|
|
|
+ pending: "warning",
|
|
|
+ paid: "success",
|
|
|
+ authorized: "teal",
|
|
|
+ processing: "info",
|
|
|
+ failed: "error-dark",
|
|
|
+ cancelled: "status-finished",
|
|
|
};
|
|
|
- return map[status] ?? 'text';
|
|
|
-};
|
|
|
|
|
|
-const openMovimentacoes = () => {
|
|
|
- $q.dialog({ component: MovimentacoesDialog });
|
|
|
+ return colors[status] ?? "text";
|
|
|
};
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- loadData();
|
|
|
-});
|
|
|
+//
|
|
|
+
|
|
|
+onMounted(loadData);
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.payments-header {
|
|
|
padding-top: 12px;
|
|
|
padding-bottom: 12px;
|
|
|
- box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08);
|
|
|
+ box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.earnings-card,
|
|
|
.balance-card,
|
|
|
.service-card {
|
|
|
- border-radius: 12px;
|
|
|
width: 100%;
|
|
|
+ border-radius: 12px;
|
|
|
}
|
|
|
|
|
|
-.earnings-value {
|
|
|
+.earnings-value,
|
|
|
+.balance-value {
|
|
|
line-height: 1.2;
|
|
|
}
|
|
|
|
|
|
@@ -453,19 +698,15 @@ onMounted(() => {
|
|
|
opacity: 0.9;
|
|
|
}
|
|
|
|
|
|
-.balance-value {
|
|
|
- line-height: 1.2;
|
|
|
-}
|
|
|
-
|
|
|
.btn-withdraw {
|
|
|
- border-radius: 8px !important;
|
|
|
padding: 6px 18px;
|
|
|
+ border-radius: 8px !important;
|
|
|
}
|
|
|
|
|
|
.text-name {
|
|
|
- color: #3a3a4a;
|
|
|
max-width: 130px;
|
|
|
overflow: hidden;
|
|
|
+ color: #3a3a4a;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
@@ -483,6 +724,6 @@ onMounted(() => {
|
|
|
|
|
|
.status-chip {
|
|
|
height: auto;
|
|
|
- padding: 2px 2px;
|
|
|
+ padding: 2px;
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|