Merge pull request '6157-actionClaim' (!106) from 6157-actionClaim into dev
gitea/salix-front/pipeline/head This commit looks good Details

Reviewed-on: #106
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Carlos Satorres 2023-11-24 07:46:29 +00:00
commit 247f935334
19 changed files with 661 additions and 71 deletions

View File

@ -269,7 +269,7 @@ watch(formUrl, async () => {
</VnPaginate> </VnPaginate>
<SkeletonTable v-if="!formData" /> <SkeletonTable v-if="!formData" />
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<QBtnGroup push class="q-gutter-x-sm"> <QBtnGroup push style="column-gap: 10px">
<slot name="moreBeforeActions" /> <slot name="moreBeforeActions" />
<QBtn <QBtn
:label="tMobile('globals.remove')" :label="tMobile('globals.remove')"

View File

@ -4,7 +4,7 @@ const emit = defineEmits(['update:modelValue', 'update:options']);
const $props = defineProps({ const $props = defineProps({
modelValue: { modelValue: {
type: [String, Number], type: [String, Number, Object],
default: null, default: null,
}, },
options: { options: {
@ -81,7 +81,6 @@ const value = computed({
map-options map-options
use-input use-input
@filter="filterHandler" @filter="filterHandler"
hide-selected
fill-input fill-input
ref="vnSelectRef" ref="vnSelectRef"
> >

View File

@ -1,8 +1,9 @@
<script setup> <script setup>
import { onMounted, useSlots, ref, watch } from 'vue'; import { onMounted, useSlots, ref, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useArrayData } from 'composables/useArrayData';
const $props = defineProps({ const $props = defineProps({
url: { url: {
@ -25,33 +26,37 @@ const $props = defineProps({
type: Number, type: Number,
default: 0, default: 0,
}, },
dataKey: {
type: String,
default: '',
},
}); });
const slots = useSlots(); const slots = useSlots();
const { t } = useI18n(); const { t } = useI18n();
const entity = ref(); const entity = computed(() => useArrayData($props.dataKey).store.data);
onMounted(async () => { onMounted(async () => {
await fetch(); await getData();
watch(
() => $props.url,
async (newUrl, lastUrl) => {
if (newUrl == lastUrl) return;
entity.value = null;
await getData();
}
);
}); });
const emit = defineEmits(['onFetch']); async function getData() {
const arrayData = useArrayData($props.dataKey, {
async function fetch() { url: $props.url,
const params = {}; filter: $props.filter,
skip: 0,
if ($props.filter) params.filter = JSON.stringify($props.filter); });
const { data } = await arrayData.fetch({ append: false });
const { data } = await axios.get($props.url, { params });
entity.value = data;
emit('onFetch', data); emit('onFetch', data);
} }
const emit = defineEmits(['onFetch']);
watch($props, async () => {
entity.value = null;
await fetch();
});
</script> </script>
<template> <template>

View File

@ -29,6 +29,10 @@ export function useArrayData(key, userOptions) {
} }
}); });
if (key && userOptions) {
setOptions();
}
function setOptions() { function setOptions() {
const allowedOptions = [ const allowedOptions = [
'url', 'url',
@ -42,7 +46,7 @@ export function useArrayData(key, userOptions) {
]; ];
if (typeof userOptions === 'object') { if (typeof userOptions === 'object') {
for (const option in userOptions) { for (const option in userOptions) {
const isEmpty = userOptions[option] == null || userOptions[option] == ''; const isEmpty = userOptions[option] == null || userOptions[option] === '';
if (isEmpty || !allowedOptions.includes(option)) continue; if (isEmpty || !allowedOptions.includes(option)) continue;
if (Object.prototype.hasOwnProperty.call(store, option)) { if (Object.prototype.hasOwnProperty.call(store, option)) {
@ -97,6 +101,7 @@ export function useArrayData(key, userOptions) {
store.isLoading = false; store.isLoading = false;
canceller = null; canceller = null;
return response;
} }
function destroy() { function destroy() {
@ -147,10 +152,11 @@ export function useArrayData(key, userOptions) {
if (store.userParams && Object.keys(store.userParams).length !== 0) if (store.userParams && Object.keys(store.userParams).length !== 0)
query.params = JSON.stringify(store.userParams); query.params = JSON.stringify(store.userParams);
router.replace({ if (router)
path: route.path, router.replace({
query: query, path: route.path,
}); query: query,
});
} }
const totalRows = computed(() => (store.data && store.data.length) || 0); const totalRows = computed(() => (store.data && store.data.length) || 0);

View File

@ -45,3 +45,9 @@ body.body--dark {
.bg-vn-dark { .bg-vn-dark {
background-color: var(--vn-dark); background-color: var(--vn-dark);
} }
.vn-card {
background-color: var(--vn-gray);
color: var(--vn-text);
border-radius: 8px;
}

View File

@ -274,6 +274,7 @@ export default {
development: 'Development', development: 'Development',
log: 'Audit logs', log: 'Audit logs',
notes: 'Notes', notes: 'Notes',
action: 'Action',
}, },
list: { list: {
customer: 'Customer', customer: 'Customer',

View File

@ -273,6 +273,7 @@ export default {
photos: 'Fotos', photos: 'Fotos',
log: 'Registros de auditoría', log: 'Registros de auditoría',
notes: 'Notas', notes: 'Notas',
action: 'Acción',
}, },
list: { list: {
customer: 'Cliente', customer: 'Cliente',

View File

@ -0,0 +1,518 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { useStateStore } from 'src/stores/useStateStore';
import { toDate, toPercentage, toCurrency } from 'filters/index';
import { tMobile } from 'src/composables/tMobile';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { useArrayData } from 'composables/useArrayData';
const { t } = useI18n();
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const stateStore = computed(() => useStateStore());
const claim = ref(null);
const claimRef = ref();
const claimId = route.params.id;
const dialogDestination = ref(false);
const claimDestinationFk = ref(null);
const resolvedStateId = ref(null);
const claimActionsForm = ref();
const rows = ref([]);
const selectedRows = ref([]);
const destinationTypes = ref([]);
const totalClaimed = ref(null);
const DEFAULT_MAX_RESPONSABILITY = 5;
const DEFAULT_MIN_RESPONSABILITY = 1;
const arrayData = useArrayData('claimData');
const marker_labels = [
{ value: DEFAULT_MIN_RESPONSABILITY, label: t('claim.summary.company') },
{ value: DEFAULT_MAX_RESPONSABILITY, label: t('claim.summary.person') },
];
const columns = computed(() => [
{
name: 'Id',
label: t('Id item'),
field: (row) => row.itemFk,
},
{
name: 'ticket',
label: t('Ticket'),
field: (row) => row.ticketFk,
align: 'center',
},
{
name: 'destination',
label: t('Destination'),
field: (row) => row.claimDestinationFk,
align: 'left',
},
{
name: 'Landed',
label: t('Landed'),
field: (row) => toDate(row.landed),
},
{
name: 'quantity',
label: t('Quantity'),
field: (row) => row.quantity,
},
{
name: 'concept',
label: t('Description'),
field: (row) => row.concept,
align: 'left',
},
{
name: 'price',
label: t('Price'),
field: (row) => row.price,
format: (value) => value,
align: 'center',
},
{
name: 'discount',
label: t('Discount'),
field: (row) => row.discount,
format: (value) => toPercentage(value / 100),
align: 'left',
},
{
name: 'total',
label: t('Total'),
field: (row) => row.total,
format: (value) => value,
align: 'center',
},
{
name: 'delete',
},
]);
onMounted(() => {
getTotal();
});
function setData(data) {
rows.value = data;
getTotal();
}
function getTotal() {
if (rows.value.length) {
totalClaimed.value = rows.value.reduce((total, row) => total + row.total, 0);
}
}
async function updateDestinations(claimDestinationFk) {
await updateDestination(claimDestinationFk, selectedRows.value, { reload: true });
}
async function updateDestination(claimDestinationFk, row, options = {}) {
if (claimDestinationFk) {
await axios.post('Claims/updateClaimDestination', {
claimDestinationFk,
rows: Array.isArray(row) ? row : [row],
});
options.reload && claimActionsForm.value.reload();
}
}
async function regularizeClaim() {
const query = `Claims/${claimId}/regularizeClaim`;
await axios.post(query);
if (claim.value.responsibility >= Math.ceil(DEFAULT_MAX_RESPONSABILITY) / 2) {
await claimRef.value.fetch();
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmGreuges'),
message: t('confirmGreugesMessage'),
},
})
.onOk(async () => await onUpdateGreugeAccept());
} else {
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
await arrayData.fetch({ append: false });
}
async function updateGreuge(greuges) {
const { data } = await axios.post(`Greuges`, greuges);
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
return data;
}
async function onUpdateGreugeAccept() {
const greugeTypeFreightId = await getGreugeTypeId();
const freightPickUpPrice = await getGreugeConfig();
await updateGreuge({
clientFk: claim.value.clientFk,
description: `${t('ClaimGreugeDescription')} ${claimId}`.toUpperCase(),
amount: freightPickUpPrice,
greugeTypeFk: greugeTypeFreightId,
ticketFk: claim.value.ticketFk,
});
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
async function getGreugeTypeId() {
const params = { filter: { where: { code: 'freightPickUp' } } };
const query = `GreugeTypes/findOne`;
const { data } = await axios.get(query, { params });
return data.id;
}
async function getGreugeConfig() {
const query = `GreugeConfigs/findOne`;
const { data } = await axios.get(query);
return data.freightPickUpPrice;
}
async function save(data) {
const query = `Claims/${claimId}/updateClaimAction`;
await axios.patch(query, data);
}
async function importToNewRefundTicket() {
const query = `ClaimBeginnings/${claimId}/importToNewRefundTicket`;
await axios.post(query);
claimActionsForm.value.reload();
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
</script>
<template>
<FetchData
ref="claimRef"
:url="`Claims/${claimId}`"
@on-fetch="(data) => (claim = data)"
auto-load
/>
<FetchData
url="ClaimStates/findOne"
@on-fetch="(data) => (resolvedStateId = data.id)"
auto-load
:where="{ code: 'resolved' }"
/>
<FetchData
url="ClaimDestinations"
auto-load
@on-fetch="(data) => (destinationTypes = data)"
/>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer
v-model="stateStore.rightDrawer"
side="right"
:width="300"
show-if-above
v-if="claim"
>
<QCard class="totalClaim vn-card q-my-md q-pa-sm">
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
</QCard>
<QCard class="vn-card q-mb-md q-pa-sm">
<QItem class="justify-between">
<QItemLabel class="slider-container">
<p class="text-primary">
{{ t('claim.summary.actions') }}
</p>
<QSlider
class="responsibility { 'background-color:primary': quasar.platform.is.mobile }"
v-model="claim.responsibility"
:label-value="t('claim.summary.responsibility')"
@change="(value) => save({ responsibility: value })"
label-always
color="primary"
markers
:marker-labels="marker_labels"
:min="DEFAULT_MIN_RESPONSABILITY"
:max="DEFAULT_MAX_RESPONSABILITY"
/>
</QItemLabel>
</QItem>
</QCard>
<QItemLabel class="mana q-mb-md">
<QCheckbox
v-model="claim.isChargedToMana"
@update:model-value="(value) => save({ isChargedToMana: value })"
/>
<span>{{ t('mana') }}</span>
</QItemLabel>
</QDrawer>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport>
<CrudModel
v-if="claim"
data-key="ClaimEnds"
url="ClaimEnds/filter"
save-url="ClaimEnds/crud"
ref="claimActionsForm"
v-model:selected="selectedRows"
:filter="{ where: { claimFk: claimId } }"
:default-remove="true"
:default-save="false"
:default-reset="false"
@on-fetch="setData"
auto-load
>
<template #body>
<QTable
:columns="columns"
:rows="rows"
:dense="$q.screen.lt.md"
row-key="id"
selection="multiple"
v-model:selected="selectedRows"
:grid="$q.screen.lt.md"
>
<template #body-cell-ticket="{ value }">
<QTd align="center">
<span class="link">
{{ value }}
<TicketDescriptorProxy :id="value" />
</span>
</QTd>
</template>
<template #body-cell-destination="{ row }">
<QTd>
<VnSelectFilter
v-model="row.claimDestinationFk"
:options="destinationTypes"
option-label="description"
option-value="id"
:autofocus="true"
dense
input-debounce="0"
hide-selected
@update:model-value="(value) => updateDestination(value, row)"
/>
</QTd>
</template>
<template #body-cell-price="{ value }">
<QTd align="center">
{{ toCurrency(value) }}
</QTd>
</template>
<template #body-cell-total="{ value }">
<QTd align="center">
{{ toCurrency(value) }}
</QTd>
</template>
<!-- View for grid mode -->
<template #item="props">
<div class="q-mb-md col-12 grid-style-transition">
<QCard>
<QCardSection class="row justify-between">
<QCheckbox v-model="props.selected" />
<QBtn color="primary" icon="delete" flat round />
</QCardSection>
<QSeparator inset />
<QList dense>
<QItem v-for="column of props.cols" :key="column.name">
<QItemSection>
<QItemLabel caption>
{{ column.label }}
</QItemLabel>
</QItemSection>
<QItemSection side>
<QItemLabel v-if="column.name === 'destination'">
{{ column.value.description }}
</QItemLabel>
<QItemLabel v-else>
{{ column.value }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
<template #moreBeforeActions>
<QBtn
color="primary"
text-color="white"
:unelevated="true"
:label="tMobile('Regularize')"
:title="t('Regularize')"
icon="check"
@click="regularizeClaim"
:disable="claim.claimStateFk == resolvedStateId"
/>
<QBtn
color="primary"
text-color="white"
:unelevated="true"
:disable="!selectedRows.length"
:label="tMobile('Change destination')"
:title="t('Change destination')"
icon="swap_horiz"
@click="dialogDestination = !dialogDestination"
/>
<QBtn
color="primary"
text-color="white"
:unelevated="true"
:label="tMobile('Import claim')"
:title="t('Import claim')"
icon="Upload"
@click="importToNewRefundTicket"
:disable="claim.claimStateFk == resolvedStateId"
/>
</template>
</CrudModel>
<QDialog v-model="dialogDestination">
<QCard>
<QCardSection>
<QItem class="q-pa-sm">
<span class="q-dialog__title text-white">
{{ t('dialog title') }}
</span>
<QBtn icon="close" flat round dense v-close-popup />
</QItem>
</QCardSection>
<QItemSection>
<VnSelectFilter
class="q-pa-sm"
v-model="claimDestinationFk"
:options="destinationTypes"
option-label="description"
option-value="id"
:autofocus="true"
dense
input-debounce="0"
hide-selected
/>
</QItemSection>
<QCardActions class="justify-end q-mr-sm">
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
<QBtn
:disable="!claimDestinationFk"
:label="t('globals.save')"
color="primary"
v-close-popup
@click="updateDestinations(claimDestinationFk)"
/>
</QCardActions>
</QCard>
</QDialog>
<!-- <QDialog v-model="dialogGreuge">
<QCardSection>
<QItem class="q-pa-sm">
<span class="q-pa-sm q-dialog__title text-white">
{{ t('dialogGreuge title') }}
</span>
<QBtn class="q-pa-sm" icon="close" flat round dense v-close-popup />
</QItem>
<QCardActions class="justify-end q-mr-sm">
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup />
<QBtn
:label="t('globals.save')"
color="primary"
v-close-popup
@click="onUpdateGreugeAccept"
/>
</QCardActions>
</QCardSection>
</QDialog> -->
</template>
<style lang="scss" scoped>
.slider-container {
width: 50%;
}
@media (max-width: $breakpoint-xs) {
.slider-container {
width: 90%;
}
}
.q-table {
.q-item {
min-height: min-content;
height: 0;
}
}
.q-dialog {
.q-btn {
height: min-content;
}
}
.responsibility {
max-width: 100%;
margin-left: 40px;
}
.mana {
float: inline-start;
}
</style>
<i18n>
en:
mana: Is paid with mana
dialog title: Change destination to all selected rows
confirmGreuges: Do you want to insert complaints?
confirmGreugesMessage: Insert complaints into the client's record
es:
mana: Cargado al maná
Delivered: Descripción
Quantity: Cantidad
Claimed: Rec
Description: Descripción
Price: Precio
Discount: Dto.
Destination: Destino
Landed: F.entrega
Remove line: Eliminar línea
Total claimed: Total reclamado
Regularize: Regularizar
Change destination: Cambiar destino
Import claim: Importar reclamación
dialog title: Cambiar destino en todas las filas seleccionadas
Remove: Eliminar
dialogGreuge title: Insertar greuges en la ficha del cliente
ClaimGreugeDescription: Id reclamación
Id item: Id artículo
confirmGreuges: ¿Desea insertar greuges?
confirmGreugesMessage: Insertar greuges en la ficha del cliente
</i18n>

View File

@ -22,11 +22,6 @@ const $props = defineProps({
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
let salixUrl;
onMounted(async () => {
salixUrl = await getUrl(`claim/${entityId.value}`);
});
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
@ -42,18 +37,6 @@ onMounted(async () => {
<ClaimDescriptor /> <ClaimDescriptor />
<QSeparator /> <QSeparator />
<LeftMenu source="card" /> <LeftMenu source="card" />
<QSeparator />
<QList>
<QItem
active-class="text-primary"
clickable
v-ripple
:href="`${salixUrl}/action`"
>
<QItemSection avatar><QIcon name="vn:actions"></QIcon></QItemSection>
<QItemSection>{{ t('Action') }}</QItemSection>
</QItem>
</QList>
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>

View File

@ -62,13 +62,18 @@ const filter = {
], ],
}; };
const STATE_COLOR = {
pending: 'positive',
managed: 'warning',
resolved: 'negative',
};
function stateColor(code) { function stateColor(code) {
if (code === 'pending') return 'positive'; return STATE_COLOR[code];
if (code === 'managed') return 'warning';
if (code === 'resolved') return 'negative';
} }
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => { const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.client.name, entity.id); data.value = useCardDescription(entity.client.name, entity.id);
state.set('ClaimDescriptor', entity); state.set('ClaimDescriptor', entity);
}; };
@ -83,6 +88,7 @@ const setData = (entity) => {
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
@on-fetch="setData" @on-fetch="setData"
data-key="claimData"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<ClaimDescriptorMenu :claim="entity" /> <ClaimDescriptorMenu :claim="entity" />
@ -120,16 +126,16 @@ const setData = (entity) => {
<VnLv :label="t('claim.card.commercial')"> <VnLv :label="t('claim.card.commercial')">
<template #value> <template #value>
<span class="link"> <span class="link">
{{ entity.client.salesPersonUser.name }} {{ entity.client?.salesPersonUser?.name }}
<WorkerDescriptorProxy :id="entity.client.salesPersonFk" /> <WorkerDescriptorProxy :id="entity.client?.salesPersonFk" />
</span> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('claim.card.province')" :label="t('claim.card.province')"
:value="entity.ticket.address.province.name" :value="entity.ticket?.address?.province?.name"
/> />
<VnLv :label="t('claim.card.zone')" :value="entity.ticket.zone.name" /> <VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" />
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">
<QCardActions> <QCardActions>

View File

@ -7,6 +7,7 @@ import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import { getUrl } from 'composables/getUrl'; import { getUrl } from 'composables/getUrl';
import { tMobile } from 'composables/tMobile'; import { tMobile } from 'composables/tMobile';
import router from 'src/router';
const route = useRoute(); const route = useRoute();
@ -102,10 +103,6 @@ const columns = computed(() => [
tabIndex: 5, tabIndex: 5,
}, },
]); ]);
function goToAction() {
location.href = `${salixUrl}/action`;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -148,7 +145,7 @@ function goToAction() {
:data-required="{ claimFk: route.params.id }" :data-required="{ claimFk: route.params.id }"
v-model:selected="selected" v-model:selected="selected"
auto-load auto-load
@save-changes="goToAction" @save-changes="$router.push(`/claim/${route.params.id}/action`)"
:default-save="false" :default-save="false"
> >
<template #body="{ rows }"> <template #body="{ rows }">
@ -175,6 +172,7 @@ function goToAction() {
:option-label="col.optionLabel" :option-label="col.optionLabel"
:autofocus="col.tabIndex == 1" :autofocus="col.tabIndex == 1"
input-debounce="0" input-debounce="0"
hide-selected
> >
<template #option="scope" v-if="col.name == 'worker'"> <template #option="scope" v-if="col.name == 'worker'">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -213,6 +211,7 @@ function goToAction() {
dense dense
input-debounce="0" input-debounce="0"
:autofocus="col.tabIndex == 1" :autofocus="col.tabIndex == 1"
hide-selected
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -85,10 +85,15 @@ const detailsColumns = ref([
}, },
]); ]);
const STATE_COLOR = {
pending: 'positive',
managed: 'warning',
resolved: 'negative',
};
function stateColor(code) { function stateColor(code) {
if (code === 'pending') return 'green'; return STATE_COLOR[code];
if (code === 'managed') return 'orange';
if (code === 'resolved') return 'red';
} }
const developmentColumns = ref([ const developmentColumns = ref([

View File

@ -17,12 +17,14 @@ const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const STATE_COLOR = {
pending: 'positive',
managed: 'warning',
resolved: 'negative',
};
function stateColor(code) { function stateColor(code) {
if (code === 'pending') return 'green'; return STATE_COLOR[code];
if (code === 'managed') return 'orange';
if (code === 'resolved') return 'red';
} }
function navigate(id) { function navigate(id) {
router.push({ path: `/claim/${id}` }); router.push({ path: `/claim/${id}` });
} }

View File

@ -68,7 +68,7 @@ function viewSummary(id) {
</div> </div>
</Teleport> </Teleport>
</template> </template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256">
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<TicketFilter data-key="TicketList" /> <TicketFilter data-key="TicketList" />
</QScrollArea> </QScrollArea>

View File

@ -47,19 +47,22 @@ const filter = {
], ],
}; };
const sip = computed(() => worker.value.sip && worker.value.sip.extension); const sip = computed(() => worker.value?.sip && worker.value.sip.extension);
function getWorkerAvatar() { function getWorkerAvatar() {
const token = getToken(); const token = getToken();
return `/api/Images/user/160x160/${route.params.id}/download?access_token=${token}`; return `/api/Images/user/160x160/${route.params.id}/download?access_token=${token}`;
} }
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => const setData = (entity) => {
(data.value = useCardDescription(entity.user.nickname, entity.id)); if (!entity) return;
data.value = useCardDescription(entity.user.nickname, entity.id);
};
</script> </script>
<template> <template>
<CardDescriptor <CardDescriptor
module="Worker" module="Worker"
data-key="workerData"
:url="`Workers/${entityId}`" :url="`Workers/${entityId}`"
:filter="filter" :filter="filter"
:title="data.title" :title="data.title"
@ -90,8 +93,8 @@ const setData = (entity) =>
</QImg> </QImg>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('worker.card.name')" :value="entity.user.nickname" /> <VnLv :label="t('worker.card.name')" :value="entity.user?.nickname" />
<VnLv :label="t('worker.card.email')" :value="entity.user.email"> </VnLv> <VnLv :label="t('worker.card.email')" :value="entity.user?.email"> </VnLv>
<VnLv <VnLv
:label="t('worker.list.department')" :label="t('worker.list.department')"
:value="entity.department ? entity.department.department.name : null" :value="entity.department ? entity.department.department.name : null"

View File

@ -19,6 +19,7 @@ export default {
'ClaimLog', 'ClaimLog',
'ClaimNotes', 'ClaimNotes',
'ClaimDevelopment', 'ClaimDevelopment',
'ClaimAction',
], ],
}, },
children: [ children: [
@ -130,6 +131,15 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'), component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
}, },
{
name: 'ClaimAction',
path: 'action',
meta: {
title: 'action',
icon: 'vn:actions',
},
component: () => import('src/pages/Claim/Card/ClaimAction.vue'),
},
], ],
}, },
], ],

View File

@ -18,7 +18,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
skip: 0, skip: 0,
order: '', order: '',
data: ref(), data: ref(),
isLoading: false isLoading: false,
}; };
} }

View File

@ -0,0 +1,45 @@
/// <reference types="cypress" />
describe('ClaimAction', () => {
const claimId = 2;
const firstRow = 'tbody > :nth-child(1)';
const destinationRow = '.q-item__section > .q-field';
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/claim/${claimId}/action`);
});
it('should import claim', () => {
cy.get('[title="Import claim"]').click();
});
it('should change destination', () => {
const rowData = [true, null, null, 'Bueno'];
cy.fillRow(firstRow, rowData);
});
it('should change destination from other button', () => {
const rowData = [true];
cy.fillRow(firstRow, rowData);
cy.get('[title="Change destination"]').click();
cy.selectOption(destinationRow, 'Confeccion');
cy.get('.q-card > .q-card__actions > .q-btn--standard').click();
});
it('should regularize', () => {
cy.get('[title="Regularize"]').click();
cy.clickConfirm();
});
it('should remove the line', () => {
cy.fillRow(firstRow, [true]);
cy.removeCard();
cy.clickConfirm();
cy.reload();
cy.get(firstRow).should('not.exist');
});
});

View File

@ -88,7 +88,8 @@ Cypress.Commands.add('addCard', () => {
cy.get('.q-page-sticky > div > .q-btn').click(); cy.get('.q-page-sticky > div > .q-btn').click();
}); });
Cypress.Commands.add('clickConfirm', () => { Cypress.Commands.add('clickConfirm', () => {
cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); cy.waitForElement('.q-dialog__inner > .q-card');
cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click();
}); });
Cypress.Commands.add('notificationHas', (selector, text) => { Cypress.Commands.add('notificationHas', (selector, text) => {