feat(TicketSale): refs #7984 add currency
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2025-01-29 15:27:08 +01:00
parent 2e085c6b15
commit 1b2573bfc6
7 changed files with 133 additions and 104 deletions

View File

@ -17,6 +17,10 @@ const $props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
userFilter: {
type: Object,
default: null,
},
title: { title: {
type: String, type: String,
default: '', default: '',
@ -58,6 +62,7 @@ onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, { arrayData = useArrayData($props.dataKey, {
url: $props.url, url: $props.url,
filter: $props.filter, filter: $props.filter,
userFilter: $props.userFilter,
skip: 0, skip: 0,
}); });
store = arrayData.store; store = arrayData.store;
@ -73,7 +78,7 @@ onBeforeMount(async () => {
() => [$props.url, $props.filter], () => [$props.url, $props.filter],
async () => { async () => {
if (!isSameDataKey.value) await getData(); if (!isSameDataKey.value) await getData();
} },
); );
}); });
@ -108,7 +113,7 @@ const iconModule = computed(() => route.matched[1].meta.icon);
const toModule = computed(() => const toModule = computed(() =>
route.matched[1].path.split('/').length > 2 route.matched[1].path.split('/').length > 2
? route.matched[1].redirect ? route.matched[1].redirect
: route.matched[1].children[0].redirect : route.matched[1].children[0].redirect,
); );
</script> </script>

View File

@ -6,6 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
import TicketBasicData from './TicketBasicData.vue'; import TicketBasicData from './TicketBasicData.vue';
import TicketBasicDataForm from './TicketBasicDataForm.vue'; import TicketBasicDataForm from './TicketBasicDataForm.vue';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import { useArrayData } from 'composables/useArrayData';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -81,7 +82,7 @@ const getPriceDifference = async () => {
}; };
const { data } = await axios.post( const { data } = await axios.post(
`tickets/${formData.value.id}/priceDifference`, `tickets/${formData.value.id}/priceDifference`,
params params,
); );
formData.value.ticket = data; formData.value.ticket = data;
}; };
@ -109,12 +110,14 @@ const submit = async () => {
const { data } = await axios.post( const { data } = await axios.post(
`tickets/${formData.value.id}/componentUpdate`, `tickets/${formData.value.id}/componentUpdate`,
params params,
); );
if (!data) return; if (!data) return;
const ticketToMove = data.id; const ticketToMove = data.id;
const arrayData = useArrayData('ticketData');
arrayData.fetch({});
notify(t('basicData.unroutedTicket'), 'positive'); notify(t('basicData.unroutedTicket'), 'positive');
router.push({ name: 'TicketSummary', params: { id: ticketToMove } }); router.push({ name: 'TicketSummary', params: { id: ticketToMove } });
}; };
@ -136,7 +139,7 @@ const onNextStep = async () => {
openConfirmationModal( openConfirmationModal(
t('basicData.negativesConfirmTitle'), t('basicData.negativesConfirmTitle'),
t('basicData.negativesConfirmMessage'), t('basicData.negativesConfirmMessage'),
submitWithNegatives submitWithNegatives,
); );
else await submit(); else await submit();
} }

View File

@ -117,7 +117,7 @@ const setData = (entity) => {
<CardDescriptor <CardDescriptor
module="Ticket" module="Ticket"
:url="`Tickets/${entityId}`" :url="`Tickets/${entityId}`"
:filter="filter" :userFilter="filter"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
@on-fetch="setData" @on-fetch="setData"

View File

@ -3,6 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import VnUsesMana from 'components/ui/VnUsesMana.vue'; import VnUsesMana from 'components/ui/VnUsesMana.vue';
import VnCurrency from 'src/components/ui/VnCurrency.vue';
const $props = defineProps({ const $props = defineProps({
mana: { mana: {
@ -45,13 +46,21 @@ const cancel = () => {
<div class="container"> <div class="container">
<QSpinner v-if="!mana && mana !== 0" color="primary" size="md" /> <QSpinner v-if="!mana && mana !== 0" color="primary" size="md" />
<div v-else> <div v-else>
<div class="header">Mana: {{ toCurrency(mana) }}</div> <div class="header">
Mana:
<VnCurrency
:model="{ mana }"
local-field="mana"
foreign-field="mana"
arrayDataModel="ticketData"
/>
</div>
<div class="q-pa-md"> <div class="q-pa-md">
<slot /> <slot />
<div v-if="newPrice" class="column items-center q-mt-lg"> <div v-if="newPrice" class="column items-center q-mt-lg">
<span class="text-primary">{{ t('New price') }}</span> <span class="text-primary">{{ t('New price') }}</span>
<span class="text-subtitle1"> <span class="text-subtitle1">
{{ toCurrency($props.newPrice) }} {{ toCurrency($props.newPrice) }}A
</span> </span>
</div> </div>
</div> </div>

View File

@ -0,0 +1,59 @@
<script setup>
import RightMenu from 'src/components/common/RightMenu.vue';
import { useArrayData } from 'composables/useArrayData';
import VnCurrency from 'src/components/ui/VnCurrency.vue';
const arrayData = useArrayData('ticketData');
const store = arrayData.store;
</script>
<template>
<RightMenu>
<template #right-panel>
<div
class="q-pa-md q-mb-md q-ma-md color-vn-text"
style="border: 2px solid black"
>
<QCardSection class="justify-end text-subtitle1" horizontal>
<span class="q-mr-xs color-vn-label"
>{{ $t('ticketSale.subtotal') }}:
</span>
<VnCurrency
:model="store.data"
:currency-code="store.data?.currency?.code"
local-field="totalWithoutVat"
foreign-field="foreignTotalWithoutVat"
/>
</QCardSection>
<QCardSection class="justify-end text-subtitle1" horizontal>
<span class="q-mr-xs color-vn-label">
{{ $t('ticketSale.tax') }}:
</span>
<VnCurrency
:model="{
vat: store.data?.totalWithVat - store.data?.totalWithoutVat,
foreignVat:
store.data?.foreignTotalWithVat -
store.data?.foreignTotalWithoutVat,
}"
:currency-code="store.data?.currency?.code"
local-field="vat"
foreign-field="foreignVat"
/>
</QCardSection>
<QCardSection
class="justify-end text-weight-bold text-subtitle1"
horizontal
>
<span class="q-mr-xs color-vn-label">
{{ $t('basicData.total') }}:
</span>
<VnCurrency
:model="store.data"
:currency-code="store.data?.currency?.code"
local-field="totalWithVat"
foreign-field="foreignTotalWithVat"
/>
</QCardSection>
</div>
</template>
</RightMenu>
</template>

View File

@ -3,6 +3,7 @@ import { onMounted, ref, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
@ -25,11 +26,15 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; import VnUsesMana from 'src/components/ui/VnUsesMana.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnCurrency from 'src/components/ui/VnCurrency.vue'; import VnCurrency from 'src/components/ui/VnCurrency.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import TicketRightTotals from './TicketRightTotals.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const state = useState();
const user = state.getUser();
const { notify } = useNotify(); const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const editPriceProxyRef = ref(null); const editPriceProxyRef = ref(null);
@ -132,8 +137,7 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
label: t('globals.amount'), label: t('globals.amount'),
name: 'amount', name: 'total',
format: (row) => parseInt(row.amount * row.quantity),
}, },
{ {
align: 'left', align: 'left',
@ -163,20 +167,6 @@ const getConfig = async () => {
ticketConfig.value = data; ticketConfig.value = data;
}; };
const onSalesFetched = (salesData) => {
sales.value = salesData;
for (let sale of salesData) sale.amount = getSaleTotal(sale);
};
const getSaleTotal = (sale) => {
if (sale.quantity == null || sale.price == null) return null;
const price = sale.quantity * sale.price;
const discount = (sale.discount * price) / 100;
return price - discount;
};
const resetChanges = async () => { const resetChanges = async () => {
arrayData.fetch({ append: false }); arrayData.fetch({ append: false });
tableRef.value.reload(); tableRef.value.reload();
@ -262,7 +252,9 @@ const getUsesMana = async () => {
}; };
const getMana = async () => { const getMana = async () => {
const { data } = await axios.get(`Tickets/${route.params.id}/getSalesPersonMana`); const { data } = await axios.get(`Tickets/${route.params.id}/getSalesPersonMana`, {
params: { isForeign: user.value.foreignCurrency },
});
mana.value = data; mana.value = data;
await getUsesMana(); await getUsesMana();
}; };
@ -274,9 +266,12 @@ const selectedValidSales = computed(() => {
const onOpenEditPricePopover = async (sale) => { const onOpenEditPricePopover = async (sale) => {
await getMana(); await getMana();
console.log('sale: ', sale, user.value.foreignCurrency);
edit.value = { edit.value = {
sale: JSON.parse(JSON.stringify(sale)), sale: JSON.parse(JSON.stringify(sale)),
price: sale.price, price: user.value.foreignCurrency
? (sale.foreignPrice ?? sale.price)
: sale.price,
}; };
}; };
@ -301,13 +296,18 @@ const updatePrice = async (sale) => {
if (!canProceed.value) return; if (!canProceed.value) return;
const newPrice = edit.value.price; const newPrice = edit.value.price;
if (newPrice != null && newPrice != sale.price) { if (newPrice != null && newPrice != sale.price) {
await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); const { data } = await axios.post(`Sales/${sale.id}/updatePrice`, {
sale.price = newPrice; newPrice,
isForeign: user.value.foreignCurrency,
});
sale.price = data.price;
sale.total = data.total;
sale.foreignPrice = data.foreignPrice;
sale.foreignTotal = data.foreignTotal;
arrayData.fetch({ append: false });
edit.value = { ...DEFAULT_EDIT }; edit.value = { ...DEFAULT_EDIT };
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
} }
await getMana();
}; };
const changeDiscount = async (sale) => { const changeDiscount = async (sale) => {
@ -331,7 +331,8 @@ const updateDiscount = async (sales, newDiscount = null) => {
}; };
await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); await axios.post(`Tickets/${route.params.id}/updateDiscount`, params);
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
for (let sale of sales) sale.discount = _newDiscount; tableRef.value.reload();
arrayData.fetch({ append: false });
edit.value = { ...DEFAULT_EDIT }; edit.value = { ...DEFAULT_EDIT };
}; };
@ -339,7 +340,10 @@ const getNewPrice = computed(() => {
if (edit.value?.sale) { if (edit.value?.sale) {
const sale = edit.value.sale; const sale = edit.value.sale;
let newDiscount = sale.discount; let newDiscount = sale.discount;
let newPrice = edit.value.price || sale.price; let newPrice = user.value.foreignCurrency
? (sale.foreignPrice ?? sale.price)
: sale.price;
// let newPrice = edit.value.price || sale.price;
if (edit.value.discount != null) newDiscount = edit.value.discount; if (edit.value.discount != null) newDiscount = edit.value.discount;
@ -435,7 +439,7 @@ const updateItem = async (row) => {
row.price = selectedItem.price; row.price = selectedItem.price;
row.discount = 0; row.discount = 0;
row.quantity = 0; row.quantity = 0;
row.amount = row.price * row.quantity; row.total = 0;
} }
endNewRow(selectedItem); endNewRow(selectedItem);
}; };
@ -456,7 +460,7 @@ const addRow = (original = null) => {
quantity: original.quantity, quantity: original.quantity,
price: original.price, price: original.price,
discount: original.discount, discount: original.discount,
amount: original.amount, total: original.total,
isNew: true, isNew: true,
}; };
} }
@ -619,56 +623,7 @@ watch(
</QBtnGroup> </QBtnGroup>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<RightMenu> <TicketRightTotals />
<template #right-panel>
<div
class="q-pa-md q-mb-md q-ma-md color-vn-text"
style="border: 2px solid black"
>
<QCardSection class="justify-end text-subtitle1" horizontal>
<span class="q-mr-xs color-vn-label"
>{{ t('ticketSale.subtotal') }}:
</span>
<VnCurrency
:model="store.data"
:currency-code="store.data?.currency?.code"
local-field="totalWithoutVat"
foreign-field="foreignTotalWithoutVat"
/>
</QCardSection>
<QCardSection class="justify-end text-subtitle1" horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketSale.tax') }}:
</span>
<VnCurrency
:model="{
vat: store.data?.totalWithVat - store.data?.totalWithoutVat,
foreignVat:
store.data?.foreignTotalWithVat -
store.data?.foreignTotalWithoutVat,
}"
:currency-code="store.data?.currency?.code"
local-field="vat"
foreign-field="foreignVat"
/>
</QCardSection>
<QCardSection
class="justify-end text-weight-bold text-subtitle1"
horizontal
>
<span class="q-mr-xs color-vn-label">
{{ t('basicData.total') }}:
</span>
<VnCurrency
:model="store.data"
:currency-code="store.data?.currency?.code"
local-field="totalWithVat"
foreign-field="foreignTotalWithVat"
/>
</QCardSection>
</div>
</template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TicketSales" data-key="TicketSales"
@ -684,7 +639,7 @@ watch(
:column-search="false" :column-search="false"
:disable-option="{ card: true }" :disable-option="{ card: true }"
auto-load auto-load
@on-fetch="onSalesFetched" @on-fetch="(data) => (sales = data)"
:create="{ :create="{
onDataSaved: handleOnDataSave, onDataSaved: handleOnDataSave,
}" }"
@ -834,11 +789,7 @@ watch(
:new-price="getNewPrice" :new-price="getNewPrice"
@save="updatePrice(row)" @save="updatePrice(row)"
> >
<VnInput <VnInputNumber v-model="edit.price" :label="t('basicData.price')" />
v-model.number="edit.price"
:label="t('basicData.price')"
type="number"
/>
</TicketEditManaProxy> </TicketEditManaProxy>
</template> </template>
<span v-else> <span v-else>
@ -857,10 +808,9 @@ watch(
:mana-code="manaCode" :mana-code="manaCode"
@save="changeDiscount(row)" @save="changeDiscount(row)"
> >
<VnInput <VnInputNumber
v-model.number="edit.discount" v-model="edit.discount"
:label="t('ticketSale.discount')" :label="t('ticketSale.discount')"
type="number"
/> />
<div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm">
<VnUsesMana :mana-code="manaCode" /> <VnUsesMana :mana-code="manaCode" />
@ -869,15 +819,11 @@ watch(
</template> </template>
<span v-else>{{ toPercentage(row.discount / 100) }}</span> <span v-else>{{ toPercentage(row.discount / 100) }}</span>
</template> </template>
<template #column-amount="{ row }"> <template #column-total="{ row }">
<VnCurrency <VnCurrency
:model="{ :model="row"
amount: row.quantity * row.price,
foreignAmount: row.quantity * row.foreignPrice,
}"
:currency-code="store.data?.currency?.code" :currency-code="store.data?.currency?.code"
local-field="amount" local-field="total"
foreign-field="foreignAmount"
/> />
</template> </template>
</VnTable> </VnTable>

View File

@ -12,10 +12,14 @@ import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
import TicketRightTotals from './TicketRightTotals.vue';
import { useState } from 'src/composables/useState';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const state = useState();
const user = state.getUser();
const ticketServiceTypeFetchRef = ref(null); const ticketServiceTypeFetchRef = ref(null);
const ticketServiceCrudRef = ref(null); const ticketServiceCrudRef = ref(null);
const ticketServiceOptions = ref([]); const ticketServiceOptions = ref([]);
@ -40,7 +44,7 @@ watch(
async () => { async () => {
store.filter = crudModelFilter.value; store.filter = crudModelFilter.value;
await ticketServiceCrudRef.value.reload(); await ticketServiceCrudRef.value.reload();
} },
); );
onMounted(async () => await getDefaultTaxClass()); onMounted(async () => await getDefaultTaxClass());
@ -59,7 +63,7 @@ const createRefund = async () => {
t('service.createRefundSuccess', { t('service.createRefundSuccess', {
ticketId: refundTicket.id, ticketId: refundTicket.id,
}), }),
'positive' 'positive',
); );
router.push({ name: 'TicketSale', params: { id: refundTicket.id } }); router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
}; };
@ -130,6 +134,7 @@ async function handleSave() {
auto-load auto-load
url="TicketServiceTypes" url="TicketServiceTypes"
/> />
<TicketRightTotals />
<CrudModel <CrudModel
ref="ticketServiceCrudRef" ref="ticketServiceCrudRef"
data-key="TicketService" data-key="TicketService"
@ -194,7 +199,9 @@ async function handleSave() {
<QTd auto-width> <QTd auto-width>
<VnInput <VnInput
:label="col.label" :label="col.label"
v-model.number="row.price" v-model.number="
row[user.foreignCurrency ? 'foreignPrice' : 'price']
"
type="number" type="number"
min="0" min="0"
@keyup.enter="handleSave" @keyup.enter="handleSave"