diff --git a/src/components/ui/VnToSummary.vue b/src/components/ui/VnToSummary.vue new file mode 100644 index 000000000..1c7d6cf01 --- /dev/null +++ b/src/components/ui/VnToSummary.vue @@ -0,0 +1,33 @@ +<script setup> +import { useRoute } from 'vue-router'; +import { defineProps } from 'vue'; + +const props = defineProps({ + routeName: { + type: String, + required: true, + }, + entityId: { + type: [String, Number], + required: true, + }, + url: { + type: String, + default: null, + }, +}); + +const route = useRoute(); +const id = props.entityId; +</script> + +<template> + <router-link + v-if="route?.name !== routeName" + :to="{ name: routeName, params: { id: id } }" + class="header link" + :href="url" + > + <QIcon name="open_in_new" color="white" size="sm" /> + </router-link> +</template> diff --git a/src/components/ui/VnUsesMana.vue b/src/components/ui/VnUsesMana.vue new file mode 100644 index 000000000..891de5f63 --- /dev/null +++ b/src/components/ui/VnUsesMana.vue @@ -0,0 +1,55 @@ +<script setup> +import { defineProps, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; + +const { t } = useI18n(); +const props = defineProps({ + usesMana: { + type: Boolean, + required: true, + }, + manaCode: { + type: String, + required: true, + }, + manaVal: { + type: String, + default: 'mana', + }, + manaLabel: { + type: String, + default: 'Promotion mana', + }, + manaClaimVal: { + type: String, + default: 'manaClaim', + }, + claimLabel: { + type: String, + default: 'Claim mana', + }, +}); + +const manaCode = ref(props.manaCode); +</script> + +<template> + <div class="column q-gutter-y-sm q-mt-sm"> + <QRadio + v-model="manaCode" + dense + :val="manaVal" + :label="t(manaLabel)" + :dark="true" + class="q-mb-sm" + /> + <QRadio + v-model="manaCode" + dense + :val="manaClaimVal" + :label="t(claimLabel)" + :dark="true" + class="q-mb-sm" + /> + </div> +</template> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index 58a5c2e1b..c2f1e6b57 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -11,6 +11,7 @@ import { toDate, toCurrency } from 'src/filters'; import { getUrl } from 'src/composables/getUrl'; import axios from 'axios'; import FetchedTags from 'src/components/ui/FetchedTags.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; const route = useRoute(); const { t } = useI18n(); @@ -163,14 +164,12 @@ const fetchEntryBuys = async () => { data-key="EntrySummary" > <template #header-left> - <router-link + <VnToSummary v-if="route?.name !== 'EntrySummary'" - :to="{ name: 'EntrySummary', params: { id: entityId } }" - class="header link" - :href="entryUrl" - > - <QIcon name="open_in_new" color="white" size="sm" /> - </router-link> + :route-name="'EntrySummary'" + :entity-id="entityId" + :url="entryUrl" + /> </template> <template #header> <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> diff --git a/src/pages/ItemType/Card/ItemTypeSummary.vue b/src/pages/ItemType/Card/ItemTypeSummary.vue index 62d1c74ab..c51d59e13 100644 --- a/src/pages/ItemType/Card/ItemTypeSummary.vue +++ b/src/pages/ItemType/Card/ItemTypeSummary.vue @@ -6,6 +6,7 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; onUpdated(() => summaryRef.value.fetch()); @@ -55,6 +56,11 @@ async function setItemTypeData(data) { > <QIcon name="open_in_new" color="white" size="sm" /> </router-link> + <VnToSummary + v-if="route?.name !== 'ItemTypeSummary'" + :route-name="'ItemTypeSummary'" + :entity-id="entityId" + /> </template> <template #header> <span> diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 73104fe27..834fced87 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -3,7 +3,7 @@ import axios from 'axios'; import { ref, toRefs } from 'vue'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; +import { useRoute, useRouter } from 'vue-router'; import { usePrintService } from 'composables/usePrintService'; import SendEmailDialog from 'components/common/SendEmailDialog.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; @@ -23,6 +23,7 @@ const props = defineProps({ required: true, }, }); +const route = useRoute(); const { push, currentRoute } = useRouter(); const { dialog, notify } = useQuasar(); @@ -40,6 +41,8 @@ const isEditable = ref(); const hasInvoicing = useAcl('invoicing'); const hasPdf = ref(); const weight = ref(); +const hasDocuwareFile = ref(); +const quasar = useQuasar(); const actions = { clone: async () => { const opts = { message: t('Ticket cloned'), type: 'positive' }; @@ -331,10 +334,49 @@ async function handleInvoiceOutData() { }); hasPdf.value = data[0]?.hasPdf; } + +async function docuwareDownload() { + await axios.get(`Tickets/${ticketId}/docuwareDownload`); +} + +async function hasDocuware() { + const { data } = await axios.post(`Docuwares/${ticketId}/checkFile`, { + fileCabinet: 'deliveryNote', + signed: true, + }); + hasDocuwareFile.value = data; +} + +async function uploadDocuware(force) { + console.log('force: ', force); + if (!force) + return quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Send PDF to tablet'), + message: t('Are you sure you want to replace this delivery note?'), + }, + }) + .onOk(async () => { + uploadDocuware(true); + }); + + const { data } = await axios.post(`Docuwares/upload`, { + fileCabinet: 'deliveryNote', + ticketIds: [parseInt(ticketId)], + }); + + if (data) notify({ message: t('PDF sent!'), type: 'positive' }); +} </script> <template> <FetchData - :url="`Tickets/${ticketId}/isEditable`" + :url=" + route.path.startsWith('/ticket') + ? `Tickets/${ticketId}/isEditable` + : `Tickets/${ticket}/isEditable` + " auto-load @on-fetch="handleFetchData" /> @@ -452,7 +494,13 @@ async function handleInvoiceOutData() { <QItemSection side> <QIcon name="keyboard_arrow_right" /> </QItemSection> - <QMenu anchor="top end" self="top start" auto-close bordered> + <QMenu + anchor="top end" + self="top start" + auto-close + bordered + @click="hasDocuware()" + > <QList> <QItem @click="openDeliveryNote('deliveryNote')" v-ripple clickable> <QItemSection>{{ t('as PDF') }}</QItemSection> @@ -460,6 +508,14 @@ async function handleInvoiceOutData() { <QItem @click="openDeliveryNote('withoutPrices')" v-ripple clickable> <QItemSection>{{ t('as PDF without prices') }}</QItemSection> </QItem> + <QItem + v-if="hasDocuwareFile" + @click="docuwareDownload()" + v-ripple + clickable + > + <QItemSection>{{ t('as PDF signed') }}</QItemSection> + </QItem> <QItem @click="openDeliveryNote('deliveryNote', 'csv')" v-ripple @@ -478,7 +534,7 @@ async function handleInvoiceOutData() { <QItemSection side> <QIcon name="keyboard_arrow_right" /> </QItemSection> - <QMenu anchor="top end" self="top start" auto-close> + <QMenu anchor="top end" self="top start" auto-close @click="hasDocuware()"> <QList> <QItem @click="sendDeliveryNoteConfirmation('deliveryNote')" @@ -487,11 +543,7 @@ async function handleInvoiceOutData() { > <QItemSection>{{ t('Send PDF') }}</QItemSection> </QItem> - <QItem - @click="sendDeliveryNoteConfirmation('withoutPrices')" - v-ripple - clickable - > + <QItem @click="uploadDocuware(!hasDocuwareFile)" v-ripple clickable> <QItemSection>{{ t('Send PDF to tablet') }}</QItemSection> </QItem> <QItem @@ -695,4 +747,6 @@ es: invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}" This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas? You are going to delete this ticket: Vas a eliminar este ticket + as PDF signed: como PDF firmado + Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán? </i18n> diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index 428e5e8c2..3d5b04a41 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -1,8 +1,8 @@ <script setup> import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; - import { toCurrency } from 'src/filters'; +import VnUsesMana from 'components/ui/VnUsesMana.vue'; const $props = defineProps({ mana: { @@ -13,12 +13,21 @@ const $props = defineProps({ type: Number, default: 0, }, + usesMana: { + type: Boolean, + default: false, + }, + manaCode: { + type: String, + default: 'mana', + }, }); const emit = defineEmits(['save', 'cancel']); const { t } = useI18n(); const QPopupProxyRef = ref(null); +const manaCode = ref($props.manaCode); const save = () => { emit('save'); @@ -47,6 +56,9 @@ const cancel = () => { </div> </div> </div> + <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> + <VnUsesMana :mana-code="manaCode" /> + </div> <div class="row"> <QBtn color="primary" diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 43af8d528..c786c67e3 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -22,6 +22,7 @@ import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; import VnTable from 'src/components/VnTable/VnTable.vue'; +import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; const route = useRoute(); const router = useRouter(); @@ -768,6 +769,8 @@ watch( <TicketEditManaProxy :mana="mana" :new-price="getNewPrice" + :uses-mana="usesMana" + :mana-code="manaCode" @save="changeDiscount(row)" > <VnInput @@ -775,6 +778,9 @@ watch( :label="t('ticketSale.discount')" type="number" /> + <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> + <VnUsesMana :mana-code="manaCode" /> + </div> </TicketEditManaProxy> </template> <span v-else>{{ toPercentage(row.discount / 100) }}</span> diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index af96c2724..1e14b0402 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -19,6 +19,8 @@ import VnTitle from 'src/components/common/VnTitle.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; const route = useRoute(); const { notify } = useNotify(); @@ -68,7 +70,7 @@ function isEditable() { async function changeState(value) { try { - stateBtnDropdownRef.value.hide(); + stateBtnDropdownRef.value?.hide(); const formData = { ticketFk: entityId.value, code: value, @@ -85,6 +87,10 @@ async function changeState(value) { function toTicketUrl(section) { return '#/ticket/' + entityId.value + '/' + section; } +function isOnTicketCard() { + const currentPath = route.path; + return currentPath.startsWith('/ticket'); +} </script> <template> @@ -99,6 +105,14 @@ function toTicketUrl(section) { :url="`Tickets/${entityId}/summary`" data-key="TicketSummary" > + <template #header-left> + <VnToSummary + v-if="route?.name !== 'TicketSummary'" + :route-name="'TicketSummary'" + :entity-id="entityId" + :url="ticketUrl" + /> + </template> <template #header="{ entity }"> <div> Ticket #{{ entity.id }} - {{ entity.client?.name }} ({{ @@ -108,23 +122,37 @@ function toTicketUrl(section) { </div> </template> <template #header-right> - <QBtnDropdown - ref="stateBtnDropdownRef" - color="black" - text-color="white" - :label="t('ticket.summary.changeState')" - :disable="!isEditable()" - > - <VnSelect - :options="editableStates" - hide-selected - option-label="name" - option-value="code" - hide-dropdown-icon - focus-on-mount - @update:model-value="changeState" - /> - </QBtnDropdown> + <div class="flex items-end"> + <QBtnDropdown + ref="stateBtnDropdownRef" + color="black" + text-color="white" + :label="t('ticket.summary.changeState')" + :disable="!isEditable()" + > + <VnSelect + :options="editableStates" + hide-selected + option-label="name" + option-value="code" + hide-dropdown-icon + focus-on-mount + @update:model-value="changeState" + /> + </QBtnDropdown> + <QBtn + v-if="!isOnTicketCard()" + icon="more_vert" + round + size="md" + flat + color="white" + > + <QMenu> + <TicketDescriptorMenu :ticket="entityId" /> + </QMenu> + </QBtn> + </div> </template> <template #body="{ entity }"> <QCard class="vn-one"> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 6d72de764..7f72f9862 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -20,11 +20,31 @@ const provinces = ref([]); const states = ref([]); const agencies = ref([]); const warehouses = ref([]); +const groupedStates = ref([]); + +const getGroupedStates = (data) => { + for (const state of data) { + groupedStates.value.push({ + id: state.id, + name: t(`${state.code}`), + code: state.code, + }); + } +}; </script> <template> <FetchData url="Provinces" @on-fetch="(data) => (provinces = data)" auto-load /> <FetchData url="States" @on-fetch="(data) => (states = data)" auto-load /> + <FetchData + url="AlertLevels" + @on-fetch=" + (data) => { + getGroupedStates(data); + } + " + auto-load + /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table"> @@ -90,12 +110,35 @@ const warehouses = ref([]); option-label="name" emit-value map-options + use-input dense outlined rounded /> </QItemSection> </QItem> + <QItem> + <QItemSection v-if="!groupedStates"> + <QSkeleton type="QInput" class="full-width" /> + </QItemSection> + <QItemSection v-if="groupedStates"> + <QSelect + :label="t('Grouped state')" + v-model="params.groupedStates" + @update:model-value="searchFn()" + :options="groupedStates" + option-value="id" + option-label="name" + emit-value + map-options + use-input + dense + outlined + rounded + sort-by="name ASC" + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <VnInput @@ -114,6 +157,15 @@ const warehouses = ref([]); /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.nickname" + :label="t('Nickname')" + is-outlined + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <QCheckbox @@ -176,6 +228,7 @@ const warehouses = ref([]); option-label="name" emit-value map-options + use-input dense outlined rounded @@ -196,6 +249,7 @@ const warehouses = ref([]); option-label="name" emit-value map-options + use-input dense outlined rounded @@ -216,12 +270,22 @@ const warehouses = ref([]); option-label="name" emit-value map-options + use-input dense outlined rounded /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnInput + v-model="params.collectionFk" + :label="t('Collection')" + is-outlined + /> + </QItemSection> + </QItem> </QExpansionItem> </template> </VnFilterPanel> @@ -245,6 +309,11 @@ en: provinceFk: Province agencyModeFk: Agency warehouseFk: Warehouse + FREE: Free + ON_PREPARATION: On preparation + PACKED: Packed + DELIVERED: Delivered + ON_PREVIOUS: ON_PREVIOUS es: params: search: Contiene @@ -278,4 +347,12 @@ es: Yes: Si No: No Days onward: Días adelante + Grouped state: Estado agrupado + FREE: Libre + ON_PREPARATION: En preparación + PACKED: Encajado + DELIVERED: Servido + ON_PREVIOUS: ON_PREVIOUS + Collection: Colección + Nickname: Nombre mostrado </i18n> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index fe9667bea..c8687ed5f 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -550,7 +550,7 @@ function setReference(data) { </template> <template #column-salesPersonFk="{ row }"> <span class="link" @click.stop> - {{ dashIfEmpty(row.salesPerson) }} + {{ dashIfEmpty(row.userName) }} <CustomerDescriptorProxy :id="row.salesPersonFk" /> </span> </template>