0
0
Fork 0

Merge branch 'feature/TicketExpedition' of https://gitea.verdnatura.es/hyervoni/salix-front-mindshore into feature/TicketExpedition

This commit is contained in:
William Buezas 2024-07-01 11:05:28 -03:00
commit fef3dfa605
17 changed files with 907 additions and 104 deletions

View File

@ -187,15 +187,10 @@ function existSummary(routes) {
color: lighten($primary, 20%); color: lighten($primary, 20%);
} }
.q-checkbox { .q-checkbox {
display: flex;
margin-bottom: 9px;
& .q-checkbox__label { & .q-checkbox__label {
margin-left: 31px;
color: var(--vn-text-color); color: var(--vn-text-color);
} }
& .q-checkbox__inner { & .q-checkbox__inner {
position: absolute;
left: 0;
color: var(--vn-label-color); color: var(--vn-label-color);
} }
} }

View File

@ -2,6 +2,7 @@
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import { computed } from 'vue';
const $props = defineProps({ const $props = defineProps({
label: { type: String, default: null }, label: { type: String, default: null },
@ -24,52 +25,67 @@ function copyValueText() {
}, },
}); });
} }
const val = computed(() => $props.value);
</script> </script>
<style scoped>
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
</style>
<template> <template>
<div class="vn-label-value"> <div class="vn-label-value">
<div v-if="$props.label || $slots.label" class="label"> <QCheckbox
<slot name="label"> v-if="typeof value === 'boolean'"
<span>{{ $props.label }}</span> v-model="val"
</slot> :label="label"
</div> disable
<div class="value"> dense
<slot name="value"> />
<span :title="$props.value"> <template v-else>
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }} <div v-if="label || $slots.label" class="label">
</span> <slot name="label">
</slot> <span>{{ label }}</span>
</div> </slot>
<div class="info" v-if="$props.info"> </div>
<QIcon name="info" class="cursor-pointer" size="xs" color="grey"> <div class="value">
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> <slot name="value">
{{ $props.info }} <span :title="value">
</QTooltip> {{ dash ? dashIfEmpty(value) : value }}
</QIcon> </span>
</div> </slot>
<div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()"> </div>
<QIcon name="Content_Copy" color="primary"> <div class="info" v-if="info">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip> <QIcon name="info" class="cursor-pointer" size="xs" color="grey">
</QIcon> <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
</div> {{ info }}
</QTooltip>
</QIcon>
</div>
<div class="copy" v-if="copy && value" @click="copyValueText()">
<QIcon name="Content_Copy" color="primary">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
</QIcon>
</div>
</template>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.vn-label-value:hover .copy { .vn-label-value {
visibility: visible; &:hover .copy {
cursor: pointer; visibility: visible;
cursor: pointer;
}
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
.copy {
visibility: hidden;
}
.info {
margin-left: 5px;
}
} }
.copy {
visibility: hidden; :deep(.q-checkbox.disabled) {
} opacity: 1 !important;
.info {
margin-left: 5px;
} }
</style> </style>

View File

@ -444,11 +444,15 @@ ticket:
sms: Sms sms: Sms
notes: Notes notes: Notes
sale: Sale sale: Sale
volume: Volume
observation: Notes
ticketAdvance: Advance tickets ticketAdvance: Advance tickets
futureTickets: Future tickets futureTickets: Future tickets
expedition: Expedition expedition: Expedition
purchaseRequest: Purchase request purchaseRequest: Purchase request
weeklyTickets: Weekly tickets weeklyTickets: Weekly tickets
services: Service
tracking: Tracking
list: list:
nickname: Nickname nickname: Nickname
state: State state: State

View File

@ -443,11 +443,15 @@ ticket:
sms: Sms sms: Sms
notes: Notas notes: Notas
sale: Lineas del pedido sale: Lineas del pedido
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro futureTickets: Tickets a futuro
expedition: Expedición expedition: Expedición
purchaseRequest: Petición de compra purchaseRequest: Petición de compra
weeklyTickets: Tickets programados weeklyTickets: Tickets programados
services: Servicios
tracking: Estados
list: list:
nickname: Alias nickname: Alias
state: Estado state: Estado

View File

@ -0,0 +1,49 @@
<script setup>
import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const nameInputRef = ref(null);
const serviceFormData = reactive({});
const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
};
onMounted(async () => {
await nextTick();
nameInputRef.value.focus();
});
</script>
<template>
<FormModelPopup
url-create="TicketServiceTypes"
model="TicketServiceType"
:title="t('New service type')"
:form-initial-data="serviceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
ref="nameInputRef"
:label="t('service.description')"
v-model="data.name"
:required="true"
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New service type: Nuevo tipo de servicio
</i18n>

View File

@ -0,0 +1,86 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState';
const emit = defineEmits(['onRequestCreated']);
const route = useRoute();
const { t } = useI18n();
const state = useState();
const user = state.getUser();
const stateFetchDataRef = ref(null);
const statesOptions = ref([]);
const workersOptions = ref([]);
const onStateFkChange = (formData) => (formData.userFk = user.value.id);
</script>
<template>
<FetchData
ref="stateFetchDataRef"
url="States"
auto-load
@on-fetch="(data) => (statesOptions = data)"
/>
<FetchData
url="Workers/search"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
auto-load
@on-fetch="(data) => (workersOptions = data)"
/>
<FormModelPopup
:title="t('Create tracking')"
url-create="Tickets/state"
model="CreateTicketTracking"
:form-initial-data="{ ticketFk: route.params.id }"
@on-data-saved="() => emit('onRequestCreated')"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.stateFk"
:label="t('tracking.state')"
:options="statesOptions"
@update:model-value="onStateFkChange(data)"
hide-selected
option-label="name"
option-value="id"
/>
<VnSelect
:label="t('tracking.worker')"
v-model="data.userFk"
:options="workersOptions"
hide-selected
option-label="name"
option-value="id"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>
{{ opt.name }}
</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}, {{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template></VnSelect
>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Create tracking: Crear estado
</i18n>

View File

@ -71,7 +71,7 @@ const filter = {
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => const setData = (entity) =>
(data.value = useCardDescription(entity.client.name, entity.id)); (data.value = useCardDescription(entity.client?.name, entity.id));
</script> </script>
<template> <template>
@ -92,7 +92,7 @@ const setData = (entity) =>
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.clientFk }} {{ entity.clientFk }}
<CustomerDescriptorProxy :id="entity.client.id" /> <CustomerDescriptorProxy :id="entity.client?.id" />
</span> </span>
</template> </template>
</VnLv> </VnLv>
@ -109,8 +109,8 @@ const setData = (entity) =>
<VnLv :label="t('ticket.summary.salesPerson')"> <VnLv :label="t('ticket.summary.salesPerson')">
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="entity.client.salesPersonUser?.name" :name="entity.client?.salesPersonUser?.name"
:worker-id="entity.client.salesPersonFk" :worker-id="entity.client?.salesPersonFk"
/> />
</template> </template>
</VnLv> </VnLv>

View File

@ -0,0 +1,106 @@
<script setup>
import { ref, watch, computed, reactive } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
const ticketNotesCrudRef = ref(null);
const observationTypes = ref([]);
const arrayData = useArrayData('TicketNotes');
const { store } = arrayData;
const crudModelFilter = reactive({
where: { ticketFk: route.params.id },
fields: ['id', 'ticketFk', 'observationTypeFk', 'description'],
});
const crudModelRequiredData = computed(() => ({ ticketFk: route.params.id }));
watch(
() => route.params.id,
async () => {
crudModelFilter.where.ticketFk = route.params.id;
store.filter = crudModelFilter;
await ticketNotesCrudRef.value.reload();
}
);
</script>
<template>
<FetchData
@on-fetch="(data) => (observationTypes = data)"
auto-load
url="ObservationTypes"
/>
<div class="flex justify-center">
<CrudModel
ref="ticketNotesCrudRef"
data-key="TicketNotes"
url="TicketObservations"
model="TicketNotes"
:filter="crudModelFilter"
:data-required="crudModelRequiredData"
:default-remove="false"
auto-load
style="max-width: 800px"
>
<template #body="{ rows }">
<QCard class="q-px-lg q-py-md">
<div
v-for="(row, index) in rows"
:key="index"
class="q-mb-md row items-center q-gutter-x-md"
>
<VnSelect
:label="t('ticketNotes.observationType')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="row.observationTypeFk"
:disable="!!row.id"
/>
<VnInput
:label="t('ticketNotes.description')"
v-model="row.description"
class="col"
/>
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="ticketNotesCrudRef.remove([row])"
>
<QTooltip>
{{ t('ticketNotes.removeNote') }}
</QTooltip>
</QIcon>
</div>
<VnRow v-if="observationTypes.length > rows.length">
<QIcon
name="add_circle"
class="fill-icon-on-hover q-ml-md"
size="sm"
color="primary"
@click="ticketNotesCrudRef.insert()"
>
<QTooltip>
{{ t('ticketNotes.addNote') }}
</QTooltip>
</QIcon>
</VnRow>
</QCard>
</template>
</CrudModel>
</div>
</template>

View File

@ -0,0 +1,190 @@
<script setup>
import { ref, watch, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CrudModel from 'components/CrudModel.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
import TicketCreateServiceType from './TicketCreateServiceType.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const ticketServiceTypeFetchRef = ref(null);
const ticketServiceCrudRef = ref(null);
const ticketServiceOptions = ref([]);
const arrayData = useArrayData('TicketNotes');
const { store } = arrayData;
const { notify } = useNotify();
const selected = ref([]);
const defaultTaxClass = ref(null);
const crudModelFilter = computed(() => ({
where: { ticketFk: route.params.id },
}));
const crudModelRequiredData = computed(() => ({
ticketFk: route.params.id,
taxClassFk: defaultTaxClass.value?.id,
}));
watch(
() => route.params.id,
async () => {
store.filter = crudModelFilter.value;
await ticketServiceCrudRef.value.reload();
}
);
onMounted(async () => await getDefaultTaxClass());
const createRefund = async () => {
try {
if (!selected.value.length) return;
const params = {
servicesIds: selected.value.map((s) => +s.ticketFk),
withWarehouse: false,
negative: true,
};
const { data } = await axios.post('Sales/clone', params);
const [refundTicket] = data;
notify(
t('service.createRefundSuccess', {
ticketId: refundTicket.id,
}),
'positive'
);
router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
} catch (error) {
console.error(error);
}
};
const getDefaultTaxClass = async () => {
try {
let filter = {
where: { code: 'G' },
};
const { data } = await axios.get('TaxClasses/findOne', {
params: { filter: JSON.stringify(filter) },
});
defaultTaxClass.value = data;
console.log('defaultTaxClass', defaultTaxClass.value);
} catch (error) {
console.error(error);
}
};
const columns = computed(() => [
{
name: 'description',
label: t('service.description'),
field: (row) => row.ticketServiceTypeFk,
sortable: true,
align: 'left',
},
{
name: 'quantity',
label: t('service.quantity'),
field: (row) => row.quantity,
sortable: true,
align: 'left',
},
{
name: 'price',
label: t('service.price'),
field: (row) => row.price,
sortable: true,
align: 'left',
},
]);
</script>
<template>
<FetchData
ref="ticketServiceTypeFetchRef"
@on-fetch="(data) => (ticketServiceOptions = data)"
auto-load
url="TicketServiceTypes"
/>
<CrudModel
ref="ticketServiceCrudRef"
data-key="TicketService"
url="TicketServices"
model="TicketService"
:filter="crudModelFilter"
:data-required="crudModelRequiredData"
auto-load
v-model:selected="selected"
>
<template #moreBeforeActions>
<QBtn
color="primary"
:label="t('service.pay')"
:disabled="!selected.length"
@click.stop="createRefund()"
/>
</template>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
row-key="$index"
selection="multiple"
v-model:selected="selected"
table-header-class="text-left"
>
<template #body-cell-description="{ row, col }">
<QTd auto-width>
<VnSelectDialog
:label="col.label"
v-model="row.ticketServiceTypeFk"
:options="ticketServiceOptions"
option-label="name"
option-value="id"
hide-selected
>
<template #form>
<TicketCreateServiceType
@on-data-saved="ticketServiceTypeFetchRef.fetch()"
/>
</template>
</VnSelectDialog>
</QTd>
</template>
<template #body-cell-quantity="{ row, col }">
<QTd auto-width>
<VnInput
:label="col.label"
v-model.number="row.quantity"
type="number"
min="0"
:info="t('service.quantityInfo')"
/>
</QTd>
</template>
<template #body-cell-price="{ row, col }">
<QTd auto-width>
<VnInput
:label="col.label"
v-model.number="row.price"
type="number"
min="0"
/>
</QTd>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="ticketServiceCrudRef.insert()" />
</QPageSticky>
</template>

View File

@ -0,0 +1,121 @@
<script setup>
import { ref, computed, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import TicketCreateTracking from './TicketCreateTracking.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import { toDateTimeFormat } from 'src/filters/date.js';
const route = useRoute();
const { t } = useI18n();
const createTrackingDialogRef = ref(null);
const paginateRef = ref(null);
watch(
() => route.params.id,
async (val) => {
paginateFilter.where.ticketFk = val;
paginateRef.value.fetch();
}
);
const paginateFilter = reactive({
include: [
{
relation: 'user',
scope: {
fields: ['id', 'name'],
include: {
relation: 'worker',
scope: {
fields: ['id'],
},
},
},
},
{
relation: 'state',
scope: {
fields: ['name'],
},
},
],
order: ['created DESC'],
where: {
ticketFk: route.params.id,
},
});
const columns = computed(() => [
{
label: t('tracking.state'),
name: 'state',
field: 'state',
align: 'left',
format: (val) => val.name,
},
{
label: t('tracking.worker'),
name: 'worker',
align: 'left',
},
{
label: t('tracking.created'),
name: 'created',
field: 'created',
align: 'left',
format: (val) => toDateTimeFormat(val),
},
]);
const openCreateModal = () => createTrackingDialogRef.value.show();
</script>
<template>
<QPage class="column items-center q-pa-md">
<VnPaginate
ref="paginateRef"
data-key="TicketTracking"
:filter="paginateFilter"
url="TicketTrackings"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-worker="{ row }">
<QTd @click.stop>
<QBtn flat color="primary">
{{ row.user?.name }}
<WorkerDescriptorProxy :id="row.user?.worker?.id" />
</QBtn>
</QTd>
</template>
</QTable>
</template>
</VnPaginate>
<QDialog
ref="createTrackingDialogRef"
transition-show="scale"
transition-hide="scale"
>
<TicketCreateTracking @on-request-created="paginateRef.fetch()" />
</QDialog>
<QPageSticky :offset="[20, 20]">
<QBtn @click="openCreateModal()" color="primary" fab icon="add" />
<QTooltip class="text-no-wrap">
{{ t('tracking.addState') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>

View File

@ -0,0 +1,153 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import FetchData from 'components/FetchData.vue';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty } from 'src/filters';
import axios from 'axios';
const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n();
const salesRef = ref(null);
watch(
() => route.params.id,
async () => {
await nextTick();
salesRef.value?.fetch();
}
);
const salesFilter = computed(() => ({
include: { relation: 'item' },
order: 'concept',
where: { ticketFk: route.params.id },
limit: 20,
}));
const sales = ref([]);
const packingTypeVolume = ref([]);
const rows = computed(() => sales.value);
const columns = computed(() => [
{
label: t('volume.item'),
name: 'item',
align: 'left',
},
{
label: t('volume.description'),
name: 'description',
align: 'left',
},
{
label: t('volume.packingType'),
name: 'quantity',
field: (row) => row.item.itemPackingTypeFk,
align: 'left',
format: (val) => dashIfEmpty(val),
},
{
label: t('volume.quantity'),
name: 'quantity',
field: 'quantity',
align: 'left',
},
{
label: t('volume.volumeQuantity'),
name: 'quantity',
field: (row) => row.saleVolume?.volume,
align: 'left',
},
]);
const applyVolumes = async (salesData) => {
try {
if (!salesData.length) return;
sales.value = salesData;
const ticket = sales.value[0].ticketFk;
const { data } = await axios.get(`Tickets/${ticket}/getVolume`);
const volumes = new Map(data.saleVolume.map((volume) => [volume.saleFk, volume]));
sales.value.forEach((sale) => {
sale.saleVolume = volumes.get(sale.id);
});
packingTypeVolume.value = data.packingTypeVolume;
} catch (error) {
console.error(error);
}
};
onMounted(() => {
stateStore.rightDrawer = true;
});
onUnmounted(() => (stateStore.rightDrawer = false));
</script>
<template>
<FetchData
ref="salesRef"
url="sales"
:filter="salesFilter"
@on-fetch="(data) => applyVolumes(data)"
auto-load
/>
<RightMenu v-if="packingTypeVolume.length">
<template #right-panel>
<QCard
v-for="(packingType, index) in packingTypeVolume"
:key="index"
class="q-pa-md q-mb-md q-ma-md color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection class="column items-center" horizontal>
<span>
{{ t('volume.type') }}:
{{ dashIfEmpty(packingType.description) }}
</span>
</QCardSection>
<QCardSection class="column items-center" horizontal>
<span> {{ t('volume.volume') }}: {{ packingType.volume }} </span>
</QCardSection>
</QCard>
</template>
</RightMenu>
<QTable
:rows="rows"
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-item="{ row }">
<QTd>
<QBtn flat color="primary">
{{ row.itemFk }}
<ItemDescriptorProxy :id="row.itemFk" />
</QBtn>
</QTd>
</template>
<template #body-cell-description="{ row }">
<QTd>
<div class="column">
<span>{{ row.item.name }}</span>
<span class="color-vn-label">{{ row.item.subName }}</span>
<FetchedTags :item="row.item" :max-length="6" />
</div>
</QTd>
</template>
</QTable>
</template>

View File

@ -1,3 +1,19 @@
card:
search: Search tickets
searchInfo: You can search by ticket id or alias
volume:
item: Item
description: Description
packingType: Packing Type
quantity: Quantity
volumeQuantity: m³ per quantity
type: Type
volume: Volume
ticketNotes:
observationType: Observation type
description: Description
removeNote: Remove note
addNote: Add note
ticketSale: ticketSale:
id: Id id: Id
visible: Visible visible: Visible
@ -132,9 +148,6 @@ basicData:
negativesConfirmMessage: Negatives are going to be generated, are you sure you want to advance all the lines? negativesConfirmMessage: Negatives are going to be generated, are you sure you want to advance all the lines?
chooseAnOption: Choose an option chooseAnOption: Choose an option
unroutedTicket: The ticket has been unrouted unroutedTicket: The ticket has been unrouted
card:
search: Search tickets
searchInfo: You can search by ticket id or alias
purchaseRequest: purchaseRequest:
id: Id id: Id
description: Description description: Description
@ -155,3 +168,18 @@ weeklyTickets:
salesperson: Salesperson salesperson: Salesperson
search: Search weekly tickets search: Search weekly tickets
searchInfo: Search weekly tickets by id or client id searchInfo: Search weekly tickets by id or client id
service:
pay: Pay
description: Description
quantity: Quantity
price: Price
removeService: Remove service
newService: New service type
addService: Add service
quantityInfo: To create services with negative amounts mark the service on the source ticket and press the pay button.
createRefundSuccess: 'The following refund ticket have been created: { ticketId }'
tracking:
state: State
worker: Worker
created: Created
addState: Add state

View File

@ -1,6 +1,34 @@
service:
pay: Abonar
description: Descripción
quantity: Cantidad
price: Precio
removeService: Quitar servicio
newService: Nuevo tipo de servicio
addService: Añadir servicio
quantityInfo: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar.
createRefundSuccess: 'Se ha creado siguiente ticket de abono: { ticketId }'
tracking:
state: Estado
worker: Trabajador
created: Fecha creación
addState: Añadir estado
card: card:
search: Buscar tickets search: Buscar tickets
searchInfo: Buscar tickets por identificador o alias searchInfo: Buscar tickets por identificador o alias
volume:
item: Artículo
description: Descripción
packingType: Encajado
quantity: Cantidad
volumeQuantity: m³ por cantidad
type: Tipo
volume: Volumen
ticketNotes:
observationType: Tipo de observación
description: Descripción
removeNote: Quitar nota
addNote: Añadir nota
purchaseRequest: purchaseRequest:
Id: Id Id: Id
description: Descripción description: Descripción
@ -112,8 +140,6 @@ futureTickets:
moveTicketSuccess: Tickets movidos correctamente moveTicketSuccess: Tickets movidos correctamente
searchInfo: Buscar tickets por fecha searchInfo: Buscar tickets por fecha
futureTicket: Tickets a futuro futureTicket: Tickets a futuro
Search ticket: Buscar tickets
You can search by ticket id or alias: Puedes buscar por id o alias del ticket
ticketSale: ticketSale:
id: Id id: Id
visible: Visible visible: Visible
@ -157,3 +183,5 @@ expedition:
removeExpeditionSubtitle: ¿Está seguro de eliminar esta expedición? removeExpeditionSubtitle: ¿Está seguro de eliminar esta expedición?
worker: Trabajador worker: Trabajador
move: Mover move: Mover
Search ticket: Buscar tickets
You can search by ticket id or alias: Puedes buscar por id o alias del ticket

View File

@ -14,12 +14,16 @@ export default {
main: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], main: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'],
card: [ card: [
'TicketBasicData', 'TicketBasicData',
'TicketBoxing', 'TicketPurchaseRequest',
'TicketSms',
'TicketSale', 'TicketSale',
'TicketLog', 'TicketLog',
'TicketExpedition', 'TicketExpedition',
'TicketPurchaseRequest', 'TicketService',
'TicketVolume',
'TicketNotes',
'TicketTracking',
'TicketBoxing',
'TicketSms',
], ],
}, },
children: [ children: [
@ -30,8 +34,8 @@ export default {
redirect: { name: 'TicketList' }, redirect: { name: 'TicketList' },
children: [ children: [
{ {
name: 'TicketList',
path: 'list', path: 'list',
name: 'TicketList',
meta: { meta: {
title: 'list', title: 'list',
icon: 'view_list', icon: 'view_list',
@ -39,8 +43,8 @@ export default {
component: () => import('src/pages/Ticket/TicketList.vue'), component: () => import('src/pages/Ticket/TicketList.vue'),
}, },
{ {
name: 'TicketCreate',
path: 'create', path: 'create',
name: 'TicketCreate',
meta: { meta: {
title: 'createTicket', title: 'createTicket',
icon: 'vn:ticketAdd', icon: 'vn:ticketAdd',
@ -58,8 +62,17 @@ export default {
component: () => import('src/pages/Ticket/TicketAdvance.vue'), component: () => import('src/pages/Ticket/TicketAdvance.vue'),
}, },
{ {
name: 'TicketWeekly', name: 'TicketAdvance',
path: 'advance',
meta: {
title: 'ticketAdvance',
icon: 'keyboard_double_arrow_left',
},
component: () => import('src/pages/Ticket/TicketAdvance.vue'),
},
{
path: 'weekly', path: 'weekly',
name: 'TicketWeekly',
meta: { meta: {
title: 'weeklyTickets', title: 'weeklyTickets',
icon: 'access_time', icon: 'access_time',
@ -67,14 +80,23 @@ export default {
component: () => import('src/pages/Ticket/TicketWeekly.vue'), component: () => import('src/pages/Ticket/TicketWeekly.vue'),
}, },
{ {
name: 'TicketFuture',
path: 'future', path: 'future',
name: 'TicketFuture',
meta: { meta: {
title: 'futureTickets', title: 'futureTickets',
icon: 'keyboard_double_arrow_right', icon: 'keyboard_double_arrow_right',
}, },
component: () => import('src/pages/Ticket/TicketFuture.vue'), component: () => import('src/pages/Ticket/TicketFuture.vue'),
}, },
{
path: 'advance',
name: 'TicketAdvance',
meta: {
title: 'ticketAdvance',
icon: 'keyboard_double_arrow_left',
},
component: () => import('src/pages/Ticket/TicketAdvance.vue'),
},
], ],
}, },
{ {
@ -84,8 +106,8 @@ export default {
redirect: { name: 'TicketSummary' }, redirect: { name: 'TicketSummary' },
children: [ children: [
{ {
name: 'TicketSummary',
path: 'summary', path: 'summary',
name: 'TicketSummary',
meta: { meta: {
title: 'summary', title: 'summary',
icon: 'launch', icon: 'launch',
@ -93,8 +115,8 @@ export default {
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
}, },
{ {
name: 'TicketBasicData',
path: 'basic-data', path: 'basic-data',
name: 'TicketBasicData',
meta: { meta: {
title: 'basicData', title: 'basicData',
icon: 'vn:settings', icon: 'vn:settings',
@ -103,8 +125,8 @@ export default {
import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'),
}, },
{ {
name: 'TicketSale',
path: 'sale', path: 'sale',
name: 'TicketSale',
meta: { meta: {
title: 'sale', title: 'sale',
icon: 'vn:lines', icon: 'vn:lines',
@ -122,23 +144,13 @@ export default {
import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'),
}, },
{ {
path: 'log', path: 'tracking',
name: 'TicketLog', name: 'TicketTracking',
meta: { meta: {
title: 'log', title: 'tracking',
icon: 'history', icon: 'vn:eye',
}, },
component: () => import('src/pages/Ticket/Card/TicketLog.vue'), component: () => import('src/pages/Ticket/Card/TicketTracking.vue'),
},
{
path: 'request',
name: 'TicketPurchaseRequest',
meta: {
title: 'purchaseRequest',
icon: 'vn:buyrequest',
},
component: () =>
import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'),
}, },
{ {
path: 'log', path: 'log',
@ -149,6 +161,24 @@ export default {
}, },
component: () => import('src/pages/Ticket/Card/TicketLog.vue'), component: () => import('src/pages/Ticket/Card/TicketLog.vue'),
}, },
{
path: 'observation',
name: 'TicketNotes',
meta: {
title: 'notes',
icon: 'vn:notes',
},
component: () => import('src/pages/Ticket/Card/TicketNotes.vue'),
},
{
path: 'volume',
name: 'TicketVolume',
meta: {
title: 'volume',
icon: 'vn:volume',
},
component: () => import('src/pages/Ticket/Card/TicketVolume.vue'),
},
{ {
path: 'expedition', path: 'expedition',
name: 'TicketExpedition', name: 'TicketExpedition',
@ -158,7 +188,15 @@ export default {
}, },
component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'), component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'),
}, },
{
path: 'service',
name: 'TicketService',
meta: {
title: 'services',
icon: 'vn:services',
},
component: () => import('src/pages/Ticket/Card/TicketService.vue'),
},
{ {
path: 'boxing', path: 'boxing',
name: 'TicketBoxing', name: 'TicketBoxing',

View File

@ -2,13 +2,10 @@ describe('AgencyWorkCenter', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/agency`); cy.visit(`/#/agency/11/workCenter`);
}); });
it('assign workCenter', () => { it('assign workCenter', () => {
cy.visit(`/#/agency`);
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get( cy.get(
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'
@ -17,8 +14,6 @@ describe('AgencyWorkCenter', () => {
}); });
it('delete workCenter', () => { it('delete workCenter', () => {
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click();
cy.get('.q-notification__message').should( cy.get('.q-notification__message').should(
'have.text', 'have.text',
@ -27,9 +22,6 @@ describe('AgencyWorkCenter', () => {
}); });
it('error on duplicate workCenter', () => { it('error on duplicate workCenter', () => {
cy.visit(`/#/agency`);
cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click();
cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click();
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get( cy.get(
'.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'

View File

@ -1,8 +1,7 @@
describe('InvoiceInDescriptor', () => { describe('InvoiceInDescriptor', () => {
const dialogBtns = '.q-card__actions button'; const dialogBtns = '.q-card__actions button';
const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section'; const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section';
const isBookedField = const isBookedField = '.q-card:nth-child(3) .vn-label-value:nth-child(5) .q-checkbox';
'.q-card:nth-child(3) .vn-label-value:nth-child(5) > .value > span';
it('should booking and unbooking the invoice properly', () => { it('should booking and unbooking the invoice properly', () => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
@ -12,10 +11,10 @@ describe('InvoiceInDescriptor', () => {
cy.openActionsDescriptor(); cy.openActionsDescriptor();
cy.get(firstDescritorOpt).click(); cy.get(firstDescritorOpt).click();
cy.get(dialogBtns).eq(1).click(); cy.get(dialogBtns).eq(1).click();
cy.get(isBookedField).should('have.attr', 'title', 'true'); cy.get(isBookedField).should('have.attr', 'aria-checked', 'true');
cy.get(firstDescritorOpt).click(); cy.get(firstDescritorOpt).click();
cy.get(dialogBtns).eq(1).click(); cy.get(dialogBtns).eq(1).click();
cy.get(isBookedField).should('have.attr', 'title', 'false'); cy.get(isBookedField).should('have.attr', 'aria-checked', 'false');
}); });
}); });

View File

@ -3,13 +3,10 @@ describe('VnSearchBar', () => {
const employeeId = ' #1'; const employeeId = ' #1';
const salesPersonId = ' #18'; const salesPersonId = ' #18';
const idGap = '.q-item > .q-item__label'; const idGap = '.q-item > .q-item__label';
const cardList = '.vn-card-list'; const vnTableRow = '.q-virtual-scroll__content';
let url;
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/list'); cy.visit('#/customer/list');
cy.url().then((currentUrl) => (url = currentUrl));
}); });
it('should redirect to customer summary page', () => { it('should redirect to customer summary page', () => {
@ -19,12 +16,12 @@ describe('VnSearchBar', () => {
it('should stay on the list page if there are several results or none', () => { it('should stay on the list page if there are several results or none', () => {
cy.writeSearchbar('salesA{enter}'); cy.writeSearchbar('salesA{enter}');
checkCardListAndUrl(2); checkTableLength(2);
cy.clearSearchbar(); cy.clearSearchbar();
cy.writeSearchbar('0{enter}'); cy.writeSearchbar('0{enter}');
checkCardListAndUrl(0); checkTableLength(0);
}); });
const searchAndCheck = (searchTerm, expectedText) => { const searchAndCheck = (searchTerm, expectedText) => {
@ -33,10 +30,7 @@ describe('VnSearchBar', () => {
cy.get(idGap).should('have.text', expectedText); cy.get(idGap).should('have.text', expectedText);
}; };
const checkCardListAndUrl = (expectedLength) => { const checkTableLength = (expectedLength) => {
cy.get(cardList).then(($cardList) => { cy.get(vnTableRow).find('tr').should('have.length', expectedLength);
expect($cardList.find('.q-card').length).to.equal(expectedLength);
cy.url().then((currentUrl) => expect(currentUrl).to.contain(url));
});
}; };
}); });