Merge branch 'dev' into 8441-createVehicleInvoiceInSection
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
This commit is contained in:
commit
0dcbc49e35
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
@ -18,7 +18,7 @@ const arrayData = defineModel({
|
|||
function handler(event) {
|
||||
const clickedElement = event.target.closest('td');
|
||||
if (!clickedElement) return;
|
||||
|
||||
event.preventDefault();
|
||||
target.value = event.target;
|
||||
qmenuRef.value.show();
|
||||
colField.value = clickedElement.getAttribute('data-col-field');
|
||||
|
|
|
@ -110,7 +110,6 @@ const components = {
|
|||
component: markRaw(VnCheckbox),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
class: $props.showTitle ? 'q-py-sm' : 'q-px-md q-py-xs fit',
|
||||
'toggle-indeterminate': true,
|
||||
size: 'sm',
|
||||
},
|
||||
|
|
|
@ -222,10 +222,7 @@ onBeforeMount(() => {
|
|||
|
||||
onMounted(async () => {
|
||||
if ($props.isEditable) document.addEventListener('click', clickHandler);
|
||||
document.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
contextMenuRef.value.handler(event);
|
||||
});
|
||||
document.addEventListener('contextmenu', contextMenuRef.value.handler);
|
||||
mode.value =
|
||||
quasar.platform.is.mobile && !$props.disableOption?.card
|
||||
? CARD_MODE
|
||||
|
@ -386,16 +383,20 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
|
|||
}
|
||||
}
|
||||
|
||||
function isEditableColumn(column) {
|
||||
const isEditableCol = column?.isEditable ?? true;
|
||||
function isEditableColumn(column, row) {
|
||||
const isEditableCol =
|
||||
typeof column?.isEditable == 'function'
|
||||
? column?.isEditable(row)
|
||||
: (column?.isEditable ?? true);
|
||||
|
||||
const isVisible = column?.visible ?? true;
|
||||
const hasComponent = column?.component;
|
||||
|
||||
return $props.isEditable && isVisible && hasComponent && isEditableCol;
|
||||
}
|
||||
|
||||
function hasEditableFormat(column) {
|
||||
if (isEditableColumn(column)) return 'editable-text';
|
||||
function hasEditableFormat(column, row) {
|
||||
if (isEditableColumn(column, row)) return 'editable-text';
|
||||
}
|
||||
|
||||
const clickHandler = async (event) => {
|
||||
|
@ -409,7 +410,7 @@ const clickHandler = async (event) => {
|
|||
if (isDateElement || isTimeElement || isQSelectDropDown || isDialog) return;
|
||||
|
||||
if (clickedElement === null) {
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
destroyInput(editingRow.value, editingField.value);
|
||||
return;
|
||||
}
|
||||
const rowIndex = clickedElement.getAttribute('data-row-index');
|
||||
|
@ -419,20 +420,25 @@ const clickHandler = async (event) => {
|
|||
if (editingRow.value !== null && editingField.value !== null) {
|
||||
if (editingRow.value == rowIndex && editingField.value == colField) return;
|
||||
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
destroyInput(editingRow.value, editingField.value);
|
||||
}
|
||||
|
||||
if (isEditableColumn(column)) {
|
||||
await renderInput(Number(rowIndex), colField, clickedElement);
|
||||
if (
|
||||
isEditableColumn(
|
||||
column,
|
||||
CrudModelRef.value.formData[rowIndex ?? editingRow.value],
|
||||
)
|
||||
) {
|
||||
renderInput(Number(rowIndex), colField, clickedElement);
|
||||
}
|
||||
};
|
||||
|
||||
async function handleTabKey(event, rowIndex, colField) {
|
||||
function handleTabKey(event, rowIndex, colField) {
|
||||
if (editingRow.value == rowIndex && editingField.value == colField)
|
||||
await destroyInput(editingRow.value, editingField.value);
|
||||
destroyInput(editingRow.value, editingField.value);
|
||||
|
||||
const direction = event.shiftKey ? -1 : 1;
|
||||
const { nextRowIndex, nextColumnName } = await handleTabNavigation(
|
||||
const { nextRowIndex, nextColumnName } = handleTabNavigation(
|
||||
rowIndex,
|
||||
colField,
|
||||
direction,
|
||||
|
@ -441,10 +447,10 @@ async function handleTabKey(event, rowIndex, colField) {
|
|||
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
|
||||
|
||||
event.preventDefault();
|
||||
await renderInput(nextRowIndex, nextColumnName, null);
|
||||
renderInput(nextRowIndex, nextColumnName, null);
|
||||
}
|
||||
|
||||
async function renderInput(rowId, field, clickedElement) {
|
||||
function renderInput(rowId, field, clickedElement) {
|
||||
editingField.value = field;
|
||||
editingRow.value = rowId;
|
||||
|
||||
|
@ -482,19 +488,22 @@ async function renderInput(rowId, field, clickedElement) {
|
|||
} else row[column.name] = value;
|
||||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||
},
|
||||
keyup: async (event) => {
|
||||
if (event.key === 'Enter')
|
||||
await destroyInput(rowId, field, clickedElement);
|
||||
keyup: (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
destroyInput(rowId, field, clickedElement);
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
keydown: async (event) => {
|
||||
await column?.cellEvent?.['keydown']?.(event, row);
|
||||
keydown: (event) => {
|
||||
column?.cellEvent?.['keydown']?.(event, row);
|
||||
switch (event.key) {
|
||||
case 'Tab':
|
||||
await handleTabKey(event, rowId, field);
|
||||
handleTabKey(event, rowId, field);
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case 'Escape':
|
||||
await destroyInput(rowId, field, clickedElement);
|
||||
destroyInput(rowId, field, clickedElement);
|
||||
event.stopPropagation();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -527,25 +536,32 @@ async function updateSelectValue(value, column, row, oldValue) {
|
|||
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
|
||||
}
|
||||
|
||||
async function destroyInput(rowIndex, field, clickedElement) {
|
||||
function destroyInput(rowIndex, field, clickedElement) {
|
||||
if (!clickedElement)
|
||||
clickedElement = document.querySelector(
|
||||
`[data-row-index="${rowIndex}"][data-col-field="${field}"]`,
|
||||
);
|
||||
|
||||
if (clickedElement) {
|
||||
await nextTick();
|
||||
render(null, clickedElement);
|
||||
Array.from(clickedElement.childNodes).forEach((child) => {
|
||||
child.style.visibility = 'visible';
|
||||
child.style.position = '';
|
||||
const column = $props.columns.find((col) => col.name === field);
|
||||
if (typeof column?.beforeDestroy === 'function')
|
||||
column.beforeDestroy(CrudModelRef.value.formData[rowIndex]);
|
||||
|
||||
nextTick().then(() => {
|
||||
render(null, clickedElement);
|
||||
Array.from(clickedElement.childNodes).forEach((child) => {
|
||||
child.style.visibility = 'visible';
|
||||
child.style.position = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (editingRow.value !== rowIndex || editingField.value !== field) return;
|
||||
editingRow.value = null;
|
||||
editingField.value = null;
|
||||
}
|
||||
|
||||
async function handleTabNavigation(rowIndex, colName, direction) {
|
||||
function handleTabNavigation(rowIndex, colName, direction) {
|
||||
const columns = $props.columns;
|
||||
const totalColumns = columns.length;
|
||||
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
|
||||
|
@ -557,7 +573,13 @@ async function handleTabNavigation(rowIndex, colName, direction) {
|
|||
iterations++;
|
||||
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
|
||||
|
||||
if (isEditableColumn(columns[newColumnIndex])) break;
|
||||
if (
|
||||
isEditableColumn(
|
||||
columns[newColumnIndex],
|
||||
CrudModelRef.value.formData[rowIndex],
|
||||
)
|
||||
)
|
||||
break;
|
||||
} while (iterations < totalColumns);
|
||||
|
||||
if (iterations >= totalColumns + 1) return;
|
||||
|
@ -884,19 +906,19 @@ const handleHeaderSelection = (evt, data) => {
|
|||
: getToggleIcon(row[col?.name])
|
||||
"
|
||||
style="color: var(--vn-text-color)"
|
||||
:class="hasEditableFormat(col)"
|
||||
:class="hasEditableFormat(col, row)"
|
||||
size="14px"
|
||||
/>
|
||||
<QIcon
|
||||
v-else-if="col?.component === 'checkbox'"
|
||||
:name="getCheckboxIcon(row[col?.name])"
|
||||
style="color: var(--vn-text-color)"
|
||||
:class="hasEditableFormat(col)"
|
||||
:class="hasEditableFormat(col, row)"
|
||||
size="14px"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
:class="hasEditableFormat(col)"
|
||||
:class="hasEditableFormat(col, row)"
|
||||
:style="
|
||||
typeof col?.style == 'function'
|
||||
? col.style(row)
|
||||
|
@ -922,7 +944,11 @@ const handleHeaderSelection = (evt, data) => {
|
|||
v-for="(btn, index) of col.actions"
|
||||
v-show="btn.show ? btn.show(row) : true"
|
||||
:key="index"
|
||||
:title="btn.title"
|
||||
:title="
|
||||
typeof btn.title === 'function'
|
||||
? btn.title(row)
|
||||
: btn.title
|
||||
"
|
||||
:icon="btn.icon"
|
||||
class="q-pa-xs"
|
||||
flat
|
||||
|
@ -1231,7 +1257,7 @@ es:
|
|||
}
|
||||
|
||||
.bg-header {
|
||||
background-color: var(--vn-accent-color);
|
||||
background-color: var(--vn-section-color);
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,8 +36,6 @@ const validate = async () => {
|
|||
isLoading.value = true;
|
||||
await props.submitFn(newPassword, oldPassword);
|
||||
emit('onSubmit');
|
||||
} catch (e) {
|
||||
notify('errors.writeRequest', 'negative');
|
||||
} finally {
|
||||
changePassDialog.value.hide();
|
||||
isLoading.value = false;
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
<script setup>
|
||||
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
|
||||
import { date } from 'quasar';
|
||||
import VnDate from './VnDate.vue';
|
||||
import { useRequired } from 'src/composables/useRequired';
|
||||
|
||||
const $attrs = useAttrs();
|
||||
const { isRequired, requiredFieldRule } = useRequired($attrs);
|
||||
|
||||
const $props = defineProps({
|
||||
isOutlined: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPopupOpen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const model = defineModel({
|
||||
type: [String, Date, Array],
|
||||
default: null,
|
||||
});
|
||||
|
||||
const vnInputDateRef = ref(null);
|
||||
|
||||
const dateFormat = 'DD/MM/YYYY';
|
||||
const isPopupOpen = ref();
|
||||
const hover = ref();
|
||||
const mask = ref();
|
||||
|
||||
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
|
||||
|
||||
const formattedDate = computed({
|
||||
get() {
|
||||
if (!model.value) return model.value;
|
||||
if ($props.multiple) {
|
||||
return model.value
|
||||
.map((d) => date.formatDate(new Date(d), dateFormat))
|
||||
.join(', ');
|
||||
}
|
||||
return date.formatDate(new Date(model.value), dateFormat);
|
||||
},
|
||||
set(value) {
|
||||
if (value == model.value) return;
|
||||
if ($props.multiple) return; // No permitir edición manual en modo múltiple
|
||||
|
||||
let newDate;
|
||||
if (value) {
|
||||
// parse input
|
||||
if (value.includes('/') && value.length >= 10) {
|
||||
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
|
||||
value = date.formatDate(
|
||||
new Date(value).toISOString(),
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||
);
|
||||
}
|
||||
const [year, month, day] = value.split('-').map((e) => parseInt(e));
|
||||
newDate = new Date(year, month - 1, day);
|
||||
if (model.value && !$props.multiple) {
|
||||
const orgDate =
|
||||
model.value instanceof Date ? model.value : new Date(model.value);
|
||||
|
||||
newDate.setHours(
|
||||
orgDate.getHours(),
|
||||
orgDate.getMinutes(),
|
||||
orgDate.getSeconds(),
|
||||
orgDate.getMilliseconds(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isNaN(newDate)) model.value = newDate.toISOString();
|
||||
},
|
||||
});
|
||||
|
||||
const popupDate = computed(() => {
|
||||
if (!model.value) return model.value;
|
||||
if ($props.multiple) {
|
||||
return model.value.map((d) => date.formatDate(new Date(d), 'YYYY/MM/DD'));
|
||||
}
|
||||
return date.formatDate(new Date(model.value), 'YYYY/MM/DD');
|
||||
});
|
||||
onMounted(() => {
|
||||
// fix quasar bug
|
||||
mask.value = '##/##/####';
|
||||
if ($props.multiple && !model.value) {
|
||||
model.value = [];
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => model.value,
|
||||
(val) => (formattedDate.value = val),
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const styleAttrs = computed(() => {
|
||||
return $props.isOutlined
|
||||
? {
|
||||
dense: true,
|
||||
outlined: true,
|
||||
rounded: true,
|
||||
}
|
||||
: {};
|
||||
});
|
||||
|
||||
const manageDate = (dates) => {
|
||||
if ($props.multiple) {
|
||||
model.value = dates.map((d) => new Date(d).toISOString());
|
||||
} else {
|
||||
formattedDate.value = dates;
|
||||
}
|
||||
if ($props.isPopupOpen) isPopupOpen.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @mouseover="hover = true" @mouseleave="hover = false">
|
||||
<QInput
|
||||
ref="vnInputDateRef"
|
||||
v-model="formattedDate"
|
||||
class="vn-input-date"
|
||||
:mask="$props.multiple ? undefined : mask"
|
||||
placeholder="dd/mm/aaaa"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
:class="{ required: isRequired }"
|
||||
:rules="mixinRules"
|
||||
:clearable="false"
|
||||
@click="isPopupOpen = !isPopupOpen"
|
||||
@keydown="isPopupOpen = false"
|
||||
hide-bottom-space
|
||||
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="
|
||||
($attrs.clearable == undefined || $attrs.clearable) &&
|
||||
hover &&
|
||||
model &&
|
||||
!$attrs.disable
|
||||
"
|
||||
@click="
|
||||
vnInputDateRef.focus();
|
||||
model = null;
|
||||
isPopupOpen = false;
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<QMenu
|
||||
v-if="$q.screen.gt.xs"
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
v-model="isPopupOpen"
|
||||
anchor="bottom left"
|
||||
self="top start"
|
||||
:no-focus="true"
|
||||
:no-parent-event="true"
|
||||
>
|
||||
<VnDate
|
||||
v-model="popupDate"
|
||||
@update:model-value="manageDate"
|
||||
:multiple="multiple"
|
||||
/>
|
||||
</QMenu>
|
||||
<QDialog v-else v-model="isPopupOpen">
|
||||
<VnDate
|
||||
v-model="popupDate"
|
||||
@update:model-value="manageDate"
|
||||
:multiple="multiple"
|
||||
/>
|
||||
</QDialog>
|
||||
</QInput>
|
||||
</div>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Open date: Abrir fecha
|
||||
</i18n>
|
|
@ -162,7 +162,6 @@ async function fetch() {
|
|||
align-items: start;
|
||||
.label {
|
||||
color: var(--vn-label-color);
|
||||
width: 9em;
|
||||
overflow: hidden;
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
|
|
|
@ -236,10 +236,6 @@ const toModule = computed(() => {
|
|||
.label {
|
||||
color: var(--vn-label-color);
|
||||
font-size: 14px;
|
||||
|
||||
&:not(:has(a))::after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
&.ellipsis > .value {
|
||||
text-overflow: ellipsis;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import noImage from '/no-user.png';
|
||||
import noUser from '/no-user.png';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
|
||||
|
||||
const $props = defineProps({
|
||||
storage: {
|
||||
|
@ -43,7 +44,7 @@ const getUrl = (zoom = false) => {
|
|||
return `/api/${$props.storage}/${$props.id}/downloadFile?access_token=${token}`;
|
||||
return isEmployee
|
||||
? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||
: noImage;
|
||||
: noUser;
|
||||
};
|
||||
const reload = () => {
|
||||
timeStamp.value = `timestamp=${Date.now()}`;
|
||||
|
@ -60,6 +61,7 @@ defineExpose({
|
|||
v-bind="$attrs"
|
||||
@click.stop="show = $props.zoom"
|
||||
spinner-color="primary"
|
||||
:error-src="`/no_image${getDarkSuffix()}.png`"
|
||||
/>
|
||||
<QDialog v-if="$props.zoom" v-model="show">
|
||||
<QImg
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { Dark } from 'quasar';
|
||||
import { computed } from 'vue';
|
||||
import { getDarkSuffix } from 'src/composables/getDarkSuffix';
|
||||
|
||||
const $props = defineProps({
|
||||
logo: {
|
||||
|
@ -12,7 +12,7 @@ const $props = defineProps({
|
|||
const src = computed({
|
||||
get() {
|
||||
return new URL(
|
||||
`../../assets/${$props.logo}${Dark.isActive ? '_dark' : ''}.svg`,
|
||||
`../../assets/${$props.logo}${getDarkSuffix()}.svg`,
|
||||
import.meta.url,
|
||||
).href;
|
||||
},
|
||||
|
|
|
@ -42,12 +42,10 @@ const val = computed(() => $props.value);
|
|||
<div v-if="label || $slots.label" class="label">
|
||||
<slot name="label">
|
||||
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
|
||||
<span style="color: var(--vn-label-color)">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span style="color: var(--vn-label-color)"> {{ label }}: </span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="value" v-if="value || $slots.value">
|
||||
<div class="value">
|
||||
<slot name="value">
|
||||
<span :title="value" style="text-overflow: ellipsis">
|
||||
{{ dash ? dashIfEmpty(value) : value }}
|
||||
|
@ -75,21 +73,13 @@ const val = computed(() => $props.value);
|
|||
visibility: visible;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.label,
|
||||
.value {
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.copy {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.q-checkbox.disabled) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { toPercentage } from 'filters/index';
|
||||
import { toCurrency, toPercentage } from 'filters/index';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
|
@ -8,6 +8,10 @@ const props = defineProps({
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'percentage', // 'currency'
|
||||
},
|
||||
});
|
||||
|
||||
const valueClass = computed(() =>
|
||||
|
@ -21,7 +25,10 @@ const formattedValue = computed(() => props.value);
|
|||
<template>
|
||||
<span :class="valueClass">
|
||||
<QIcon :name="iconName" size="sm" class="value-icon" />
|
||||
{{ toPercentage(formattedValue) }}
|
||||
<span v-if="$props.format === 'percentage'">{{
|
||||
toPercentage(formattedValue)
|
||||
}}</span>
|
||||
<span v-if="$props.format === 'currency'">{{ toCurrency(formattedValue) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@ export function getColAlign(col) {
|
|||
case 'number':
|
||||
align = 'right';
|
||||
break;
|
||||
case 'time':
|
||||
case 'date':
|
||||
case 'checkbox':
|
||||
align = 'center';
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import { Dark } from 'quasar';
|
||||
export function getDarkSuffix() {
|
||||
return Dark.isActive ? '_dark' : '';
|
||||
}
|
|
@ -16,6 +16,8 @@ body.body--light {
|
|||
--vn-black-text-color: black;
|
||||
--vn-text-color-contrast: white;
|
||||
--vn-link-color: #1e90ff;
|
||||
--vn-input-underline-color: #bdbdbd;
|
||||
--vn-input-icons-color: #797979;
|
||||
|
||||
background-color: var(--vn-page-color);
|
||||
|
||||
|
@ -29,7 +31,7 @@ body.body--light {
|
|||
body.body--dark {
|
||||
--vn-header-color: #5d5d5d;
|
||||
--vn-page-color: #222;
|
||||
--vn-section-color: #3d3d3d;
|
||||
--vn-section-color: #3c3b3b;
|
||||
--vn-section-hover-color: #747474;
|
||||
--vn-text-color: white;
|
||||
--vn-label-color: #a8a8a8;
|
||||
|
@ -40,6 +42,8 @@ body.body--dark {
|
|||
--vn-black-text-color: black;
|
||||
--vn-text-color-contrast: black;
|
||||
--vn-link-color: #66bfff;
|
||||
--vn-input-underline-color: #545353;
|
||||
--vn-input-icons-color: #888787;
|
||||
|
||||
background-color: var(--vn-page-color);
|
||||
|
||||
|
@ -155,7 +159,6 @@ select:-webkit-autofill {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Estilo para el asterisco en campos requeridos */
|
||||
.q-field.required .q-field__label:after {
|
||||
content: ' *';
|
||||
}
|
||||
|
@ -290,6 +293,18 @@ input::-webkit-inner-spin-button {
|
|||
.expand {
|
||||
max-width: 400px;
|
||||
}
|
||||
th {
|
||||
border-bottom: 1px solid var(--vn-page-color) !important;
|
||||
}
|
||||
td {
|
||||
border-color: var(--vn-page-color);
|
||||
}
|
||||
div.q-field__append.q-field__marginal {
|
||||
color: var(--vn-input-icons-color) !important;
|
||||
}
|
||||
.q-field__control:before {
|
||||
border-color: var(--vn-input-underline-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-photo-btn {
|
||||
|
|
|
@ -513,26 +513,6 @@ entry:
|
|||
isRaid: Raid
|
||||
invoiceNumber: Invoice
|
||||
reference: Ref/Alb/Guide
|
||||
params:
|
||||
isExcludedFromAvailable: Excluir del inventario
|
||||
isOrdered: Pedida
|
||||
isConfirmed: Lista para etiquetar
|
||||
isReceived: Recibida
|
||||
isRaid: Redada
|
||||
landed: Fecha
|
||||
supplierFk: Proveedor
|
||||
invoiceNumber: Nº Factura
|
||||
reference: Ref/Alb/Guía
|
||||
agencyModeId: Agencia
|
||||
isBooked: Asentado
|
||||
companyFk: Empresa
|
||||
travelFk: Envio
|
||||
evaNotes: Notas
|
||||
warehouseOutFk: Origen
|
||||
warehouseInFk: Destino
|
||||
entryTypeDescription: Tipo entrada
|
||||
invoiceAmount: Importe
|
||||
dated: Fecha
|
||||
ticket:
|
||||
params:
|
||||
ticketFk: Ticket ID
|
||||
|
@ -898,6 +878,7 @@ components:
|
|||
minPrice: Min. Price
|
||||
itemFk: Item id
|
||||
dated: Date
|
||||
date: Date
|
||||
userPanel:
|
||||
copyToken: Token copied to clipboard
|
||||
settings: Settings
|
||||
|
|
|
@ -982,6 +982,7 @@ components:
|
|||
minPrice: Precio mínimo
|
||||
itemFk: Id item
|
||||
dated: Fecha
|
||||
date: Fecha
|
||||
userPanel:
|
||||
copyToken: Token copiado al portapapeles
|
||||
settings: Configuración
|
||||
|
|
|
@ -55,7 +55,6 @@ const filterBanks = {
|
|||
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||
include: { relation: 'accountingType' },
|
||||
order: 'id',
|
||||
limit: 30,
|
||||
};
|
||||
|
||||
const filterClientFindOne = {
|
||||
|
@ -200,7 +199,6 @@ async function getAmountPaid() {
|
|||
option-label="bank"
|
||||
:include="{ relation: 'accountingType' }"
|
||||
sort-by="id"
|
||||
:limit="0"
|
||||
@update:model-value="
|
||||
(value, options) => setPaymentType(data, value, options)
|
||||
"
|
||||
|
|
|
@ -8,6 +8,6 @@ import filter from './EntryFilter.js';
|
|||
data-key="Entry"
|
||||
url="Entries"
|
||||
:descriptor="EntryDescriptor"
|
||||
:filter="{ ...filter, where: { id: $route.params.id } }"
|
||||
:filter="filter"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -52,5 +52,22 @@ export default {
|
|||
fields: ['code', 'description'],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'dms',
|
||||
scope: {
|
||||
fields: [
|
||||
'dmsTypeFk',
|
||||
'reference',
|
||||
'hardCopyNumber',
|
||||
'workerFk',
|
||||
'description',
|
||||
'hasFile',
|
||||
'file',
|
||||
'created',
|
||||
'companyFk',
|
||||
'warehouseFk',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@ import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorP
|
|||
import InvoiceIntoBook from '../InvoiceInToBook.vue';
|
||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
|
||||
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
|
||||
import { getTotal } from 'src/composables/getTotal';
|
||||
|
||||
const props = defineProps({ id: { type: [Number, String], default: 0 } });
|
||||
const { t } = useI18n();
|
||||
|
@ -161,6 +163,22 @@ const intrastatColumns = ref([
|
|||
},
|
||||
]);
|
||||
|
||||
const vehicleColumns = ref([
|
||||
{
|
||||
name: 'numberPlate',
|
||||
label: 'globals.vehicle',
|
||||
field: (row) => row.vehicleInvoiceIn?.numberPlate,
|
||||
format: (value) => value,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
label: 'invoiceIn.list.amount',
|
||||
field: (row) => toCurrency(row.vehicleInvoiceIn?.amount),
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(async () => {
|
||||
invoiceInUrl.value = `${await getUrl('')}invoiceIn/${entityId.value}/`;
|
||||
});
|
||||
|
@ -218,6 +236,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
<VnTitle
|
||||
:url="getLink('basic-data')"
|
||||
:text="t('globals.pageTitles.basicData')"
|
||||
data-cy="basicDataTitleLink"
|
||||
/>
|
||||
<div class="vn-card-group">
|
||||
<div class="vn-card-content">
|
||||
|
@ -282,7 +301,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
:value="entity.expenseDeductible?.name"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.card.company')"
|
||||
:label="t('globals.company')"
|
||||
:value="entity.company?.code"
|
||||
/>
|
||||
<VnLv
|
||||
|
@ -319,8 +338,12 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
</div>
|
||||
</QCard>
|
||||
<!--Vat-->
|
||||
<QCard v-if="entity.invoiceInTax.length" class="vat">
|
||||
<VnTitle :url="getLink('vat')" :text="t('invoiceIn.card.vat')" />
|
||||
<QCard v-if="entity.invoiceInTax.length" class="col-extend">
|
||||
<VnTitle
|
||||
:url="getLink('vat')"
|
||||
:text="t('globals.pageTitles.vat')"
|
||||
data-cy="vatTitleLink"
|
||||
/>
|
||||
<QTable
|
||||
:columns="vatColumns"
|
||||
:rows="entity.invoiceInTax"
|
||||
|
@ -372,13 +395,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
currency,
|
||||
)
|
||||
}}</QTd>
|
||||
<QTd></QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
<!--Due Day-->
|
||||
<QCard v-if="entity.invoiceInDueDay.length" class="due-day">
|
||||
<VnTitle :url="getLink('due-day')" :text="t('invoiceIn.card.dueDay')" />
|
||||
<QCard v-if="entity.invoiceInDueDay.length" class="col-shrink">
|
||||
<VnTitle
|
||||
:url="getLink('due-day')"
|
||||
:text="t('globals.pageTitles.dueDay')"
|
||||
data-cy="dueDayTitleLink"
|
||||
/>
|
||||
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
|
||||
<template #header="dueDayProps">
|
||||
<QTr :props="dueDayProps" class="bg">
|
||||
|
@ -413,10 +441,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
</QTable>
|
||||
</QCard>
|
||||
<!--Intrastat-->
|
||||
<QCard v-if="entity.invoiceInIntrastat.length">
|
||||
<QCard v-if="entity.invoiceInIntrastat.length" class="col-extend">
|
||||
<VnTitle
|
||||
:url="getLink('intrastat')"
|
||||
:text="t('invoiceIn.card.intrastat')"
|
||||
:text="t('globals.pageTitles.intrastat')"
|
||||
data-cy="intrastatTitleLink"
|
||||
/>
|
||||
<QTable
|
||||
:columns="intrastatColumns"
|
||||
|
@ -450,6 +479,53 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
<!-- Vehicle -->
|
||||
<QCard v-if="entity?.vehicleInvoiceIn?.length" class="col-shrink">
|
||||
<VnTitle
|
||||
:url="getLink('vehicle')"
|
||||
:text="t('globals.vehicle')"
|
||||
data-cy="vehicleTitleLink"
|
||||
/>
|
||||
<QTable :columns="vehicleColumns" :rows="entity.vehicleInvoiceIn" flat>
|
||||
<template #header="vehicleProps">
|
||||
<QTr :props="vehicleProps" class="bg">
|
||||
<QTh
|
||||
v-for="col in vehicleProps.cols"
|
||||
:key="col.name"
|
||||
:props="vehicleProps"
|
||||
>
|
||||
{{ t(col.label) }}
|
||||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #body="vehicleProps">
|
||||
<QTr :props="vehicleProps">
|
||||
<QTd>
|
||||
<span class="link" data-cy="invoiceInSummary_vehicle">
|
||||
{{ vehicleProps.row.vehicle.numberPlate }}
|
||||
<VehicleDescriptorProxy
|
||||
:id="vehicleProps.row.vehicleFk"
|
||||
/> </span
|
||||
></QTd>
|
||||
<QTd align="left">{{
|
||||
toCurrency(vehicleProps.row.amount)
|
||||
}}</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #bottom-row>
|
||||
<QTr class="bg">
|
||||
<QTd></QTd>
|
||||
<QTd>
|
||||
{{
|
||||
toCurrency(
|
||||
getTotal(entity.vehicleInvoiceIn, 'amount'),
|
||||
)
|
||||
}}
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
</template>
|
||||
</CardSummary>
|
||||
</template>
|
||||
|
@ -463,15 +539,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
|
||||
@media (min-width: $breakpoint-md) {
|
||||
.summaryBody {
|
||||
.vat {
|
||||
.col-extend {
|
||||
flex: 65%;
|
||||
}
|
||||
|
||||
.due-day {
|
||||
.col-shrink {
|
||||
flex: 30%;
|
||||
}
|
||||
.vat,
|
||||
.due-day {
|
||||
.col-extend,
|
||||
.col-shrink {
|
||||
.q-table th {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import FetchData from 'src/components/FetchData.vue';
|
|||
import { getExchange } from 'src/composables/getExchange';
|
||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||
import VnSelectExpense from 'src/components/common/VnSelectExpense.vue';
|
||||
import axios from 'axios';
|
||||
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -51,7 +50,7 @@ const columns = computed(() => [
|
|||
create: true,
|
||||
width: 'max-content',
|
||||
cellEvent: {
|
||||
keydown: async (evt, row) => {
|
||||
keydown: (evt, row) => {
|
||||
if (evt.key !== 'Tab') return;
|
||||
const val = evt.target.value;
|
||||
if (!val || isNaN(val)) return;
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<script setup>
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||
import { toCurrency } from 'src/filters';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
import VehicleDescriptorProxy from 'src/pages/Route/Vehicle/Card/VehicleDescriptorProxy.vue';
|
||||
|
||||
const tableRef = ref();
|
||||
const { t } = useI18n();
|
||||
|
||||
const route = useRoute();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { notify } = useNotify();
|
||||
const dataKey = 'InvoiceInVehicleList';
|
||||
const filter = {
|
||||
include: [{ relation: 'vehicle', scope: { fields: ['id', 'numberPlate'] } }],
|
||||
};
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'vehicleFk',
|
||||
label: t('globals.vehicle'),
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'vehicles',
|
||||
fields: ['id', 'numberPlate'],
|
||||
optionLabel: 'numberPlate',
|
||||
optionFilterValue: 'numberPlate',
|
||||
find: {
|
||||
value: 'vehicleFk',
|
||||
label: 'vehiclePlateNumber',
|
||||
},
|
||||
},
|
||||
create: true,
|
||||
format: (row) => row.vehicle?.numberPlate,
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'amount',
|
||||
label: t('invoiceIn.list.amount'),
|
||||
component: 'number',
|
||||
create: true,
|
||||
format: (row) => toCurrency(row.amount),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('invoiceIn.unlinkVehicle'),
|
||||
icon: 'delete',
|
||||
action: (row) =>
|
||||
openConfirmationModal(
|
||||
t('invoiceIn.unlinkVehicle'),
|
||||
t('invoiceIn.unlinkVehicleConfirmation'),
|
||||
() => unassignVehicle(row.id),
|
||||
),
|
||||
isPrimary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
async function unassignVehicle(id) {
|
||||
try {
|
||||
await axios.delete(`VehicleInvoiceIns/${id}`);
|
||||
notify(t('invoiceIn.unlinkedVehicle'), 'positive');
|
||||
tableRef.value.reload();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
:data-key="dataKey"
|
||||
url="VehicleInvoiceIns"
|
||||
:user-filter="filter"
|
||||
:filter="{ where: { invoiceInFk: route.params.id } }"
|
||||
:columns="columns"
|
||||
:column-search="false"
|
||||
:right-search="false"
|
||||
:create="{
|
||||
urlCreate: 'VehicleInvoiceIns',
|
||||
title: t('invoiceIn.linkVehicleToInvoiceIn'),
|
||||
onDataSaved: () => tableRef.reload(),
|
||||
formInitialData: { invoiceInFk: route.params.id },
|
||||
}"
|
||||
auto-load
|
||||
>
|
||||
<template #column-vehicleFk="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.vehicle?.numberPlate }}
|
||||
<VehicleDescriptorProxy :id="row?.vehicleFk" />
|
||||
</span>
|
||||
</template>
|
||||
</VnTable>
|
||||
</template>
|
|
@ -34,14 +34,6 @@ invoiceIn:
|
|||
originalInvoice: Original invoice
|
||||
entry: Entry
|
||||
emailEmpty: The email can't be empty
|
||||
card:
|
||||
client: Client
|
||||
company: Company
|
||||
customerCard: Customer card
|
||||
ticketList: Ticket List
|
||||
vat: Vat
|
||||
dueDay: Due day
|
||||
intrastat: Intrastat
|
||||
summary:
|
||||
currency: Currency
|
||||
issued: Expedition date
|
||||
|
@ -71,3 +63,7 @@ invoiceIn:
|
|||
correctingFk: Rectificative
|
||||
issued: Issued
|
||||
noMatch: No match with the vat({totalTaxableBase})
|
||||
linkVehicleToInvoiceIn: Link vehicle to invoice
|
||||
unlinkedVehicle: Unlinked vehicle
|
||||
unlinkVehicle: Unlink vehicle
|
||||
unlinkVehicleConfirmation: This vehicle will be unlinked from this invoice! Continue anyway?
|
||||
|
|
|
@ -33,13 +33,6 @@ invoiceIn:
|
|||
originalInvoice: Factura origen
|
||||
entry: Entrada
|
||||
emailEmpty: El email no puede estar vacío
|
||||
card:
|
||||
client: Cliente
|
||||
company: Empresa
|
||||
customerCard: Ficha del cliente
|
||||
ticketList: Listado de tickets
|
||||
vat: Iva
|
||||
dueDay: Fecha de vencimiento
|
||||
summary:
|
||||
currency: Divisa
|
||||
docNumber: Número documento
|
||||
|
@ -52,7 +45,7 @@ invoiceIn:
|
|||
expense: Gasto
|
||||
taxableBase: Base imp.
|
||||
rate: Tasa
|
||||
sageTransaction: Sage transación
|
||||
sageTransaction: Sage transacción
|
||||
dueDay: Fecha
|
||||
bank: Caja
|
||||
foreignValue: Divisa
|
||||
|
@ -69,3 +62,7 @@ invoiceIn:
|
|||
correctingFk: Rectificativa
|
||||
issued: Fecha de emisión
|
||||
noMatch: No cuadra con el iva({totalTaxableBase})
|
||||
linkVehicleToInvoiceIn: Vincular vehículo a factura
|
||||
unlinkedVehicle: Vehículo desvinculado
|
||||
unlinkVehicle: Desvincular vehículo
|
||||
unlinkVehicleConfirmation: Este vehículo se desvinculará de esta factura! ¿Continuar de todas formas?
|
||||
|
|
|
@ -61,7 +61,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
:clear-store-on-unmount="false"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnSelect
|
||||
:label="t('item.basicData.type')"
|
||||
v-model="data.typeFk"
|
||||
|
@ -71,6 +71,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
hide-selected
|
||||
map-options
|
||||
required
|
||||
data-cy="itemBasicDataItemType"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -83,23 +84,30 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnInput :label="t('item.basicData.reference')" v-model="data.comment" />
|
||||
<VnInput
|
||||
:label="t('item.basicData.reference')"
|
||||
v-model="data.comment"
|
||||
data-cy="itemBasicDataReference"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('item.basicData.relevancy')"
|
||||
type="number"
|
||||
v-model="data.relevancy"
|
||||
data-cy="itemBasicDataRelevancy"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnRow class="q-py-sm">
|
||||
<VnInput
|
||||
:label="t('item.basicData.stems')"
|
||||
type="number"
|
||||
v-model="data.stems"
|
||||
data-cy="itemBasicDataStems"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('item.basicData.multiplier')"
|
||||
type="number"
|
||||
v-model="data.stemMultiplier"
|
||||
data-cy="itemBasicDataMultiplier"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
:label="t('item.basicData.generic')"
|
||||
|
@ -112,6 +120,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
map-options
|
||||
hide-selected
|
||||
action-icon="filter_alt"
|
||||
data-cy="itemBasicDataGeneric"
|
||||
>
|
||||
<template #form>
|
||||
<FilterItemForm
|
||||
|
@ -129,7 +138,12 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</template>
|
||||
</VnSelectDialog>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnCheckbox
|
||||
v-model="data.isCustomInspectionRequired"
|
||||
:label="t('item.basicData.isCustomInspectionRequired')"
|
||||
data-cy="itemBasicDataCustomInspection"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
:label="t('item.basicData.intrastat')"
|
||||
v-model="data.intrastatFk"
|
||||
|
@ -138,6 +152,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
option-label="description"
|
||||
map-options
|
||||
hide-selected
|
||||
data-cy="itemBasicDataIntrastat"
|
||||
>
|
||||
<template #form>
|
||||
<CreateIntrastatForm
|
||||
|
@ -165,78 +180,81 @@ const onIntrastatCreated = (response, formData) => {
|
|||
option-label="name"
|
||||
hide-selected
|
||||
map-options
|
||||
data-cy="itemBasicDataExpense"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnCheckbox
|
||||
v-model="data.hasKgPrice"
|
||||
:label="t('item.basicData.hasKgPrice')"
|
||||
data-cy="itemBasicDataHasKgPrice"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('item.basicData.weightByPiece')"
|
||||
v-model.number="data.weightByPiece"
|
||||
:min="0"
|
||||
type="number"
|
||||
data-cy="itemBasicDataWeightByPiece"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('item.basicData.boxUnits')"
|
||||
v-model.number="data.packingOut"
|
||||
:min="0"
|
||||
type="number"
|
||||
data-cy="itemBasicDataBoxUnits"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnCheckbox
|
||||
v-model="data.isActive"
|
||||
:label="t('item.basicData.isActive')"
|
||||
data-cy="itemBasicDataIsActive"
|
||||
/>
|
||||
|
||||
<VnCheckbox
|
||||
v-model="data.isFragile"
|
||||
:label="t('item.basicData.isFragile')"
|
||||
:info="t('item.basicData.isFragileTooltip')"
|
||||
data-cy="itemBasicDataIsFragile"
|
||||
/>
|
||||
<VnCheckbox
|
||||
v-model="data.isPhotoRequested"
|
||||
:label="t('item.basicData.isPhotoRequested')"
|
||||
:info="t('item.basicData.isPhotoRequestedTooltip')"
|
||||
data-cy="itemBasicDataIsPhotoRequested"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnInput
|
||||
:label="t('item.basicData.recycledPlastic')"
|
||||
v-model.number="data.recycledPlastic"
|
||||
:min="0"
|
||||
type="number"
|
||||
data-cy="itemBasicDataRecycledPlastic"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('item.basicData.nonRecycledPlastic')"
|
||||
v-model.number="data.nonRecycledPlastic"
|
||||
:min="0"
|
||||
type="number"
|
||||
data-cy="itemBasicDataNonRecycledPlastic"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<QCheckbox
|
||||
v-model="data.isActive"
|
||||
:label="t('item.basicData.isActive')"
|
||||
/>
|
||||
<QCheckbox
|
||||
v-model="data.hasKgPrice"
|
||||
:label="t('item.basicData.hasKgPrice')"
|
||||
/>
|
||||
<QCheckbox
|
||||
v-model="data.isCustomInspectionRequired"
|
||||
:label="t('item.basicData.isCustomInspectionRequired')"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnCheckbox
|
||||
v-model="data.isFragile"
|
||||
:label="t('item.basicData.isFragile')"
|
||||
:info="t('item.basicData.isFragileTooltip')"
|
||||
class="q-mr-sm"
|
||||
size="xs"
|
||||
/>
|
||||
<VnCheckbox
|
||||
v-model="data.isPhotoRequested"
|
||||
:label="t('item.basicData.isPhotoRequested')"
|
||||
:info="t('item.basicData.isPhotoRequestedTooltip')"
|
||||
class="q-mr-sm"
|
||||
size="xs"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnInput
|
||||
:label="t('item.basicData.description')"
|
||||
type="textarea"
|
||||
v-model="data.description"
|
||||
fill-input
|
||||
data-cy="itemBasicDataDescription"
|
||||
/>
|
||||
<VnInput
|
||||
v-show="data.isPhotoRequested"
|
||||
type="textarea"
|
||||
:label="t('globals.comment')"
|
||||
:label="t('item.basicData.photoMotivation')"
|
||||
v-model="data.photoMotivation"
|
||||
fill-input
|
||||
data-cy="itemBasicDataPhotoMotivation"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
|
|
|
@ -102,20 +102,21 @@ const columns = computed(() => [
|
|||
label: t('itemDiary.in'),
|
||||
field: 'invalue',
|
||||
name: 'in',
|
||||
align: 'left',
|
||||
align: 'right',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('itemDiary.out'),
|
||||
field: 'out',
|
||||
name: 'out',
|
||||
align: 'left',
|
||||
align: 'right',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('itemDiary.balance'),
|
||||
name: 'balance',
|
||||
align: 'left',
|
||||
align: 'right',
|
||||
class: 'q-px-sm',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -174,7 +175,11 @@ async function updateWarehouse(warehouseFk) {
|
|||
<template>
|
||||
<FetchData
|
||||
url="Warehouses"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
:filter="{
|
||||
fields: ['id', 'name'],
|
||||
order: 'name ASC',
|
||||
where: { isDestiny: true },
|
||||
}"
|
||||
auto-load
|
||||
@on-fetch="(data) => (warehousesOptions = data)"
|
||||
/>
|
||||
|
@ -217,7 +222,8 @@ async function updateWarehouse(warehouseFk) {
|
|||
<QTable
|
||||
:rows="itemBalances"
|
||||
:columns="columns"
|
||||
class="full-width q-mt-md"
|
||||
class="full-width q-mt-md q-px-md"
|
||||
style="background-color: var(--vn-section-color)"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body-cell-claim="{ row }">
|
||||
|
@ -294,14 +300,14 @@ async function updateWarehouse(warehouseFk) {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-in="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QTd @click.stop class="text-right">
|
||||
<span :class="{ 'is-in': row.invalue }">
|
||||
{{ dashIfEmpty(row.invalue) }}
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-balance="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QTd @click.stop class="text-right">
|
||||
<QBadge
|
||||
class="balance-negative"
|
||||
:color="
|
||||
|
|
|
@ -48,7 +48,7 @@ const columns = computed(() => [
|
|||
label: t('itemDiary.warehouse'),
|
||||
name: 'warehouse',
|
||||
field: 'warehouse',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.landed'),
|
||||
|
@ -60,7 +60,7 @@ const columns = computed(() => [
|
|||
label: t('lastEntries.entry'),
|
||||
name: 'entry',
|
||||
field: 'stateName',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
|
@ -75,14 +75,14 @@ const columns = computed(() => [
|
|||
label: t('lastEntries.printedStickers'),
|
||||
name: 'printedStickers',
|
||||
field: 'printedStickers',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.label'),
|
||||
name: 'stickers',
|
||||
field: 'stickers',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
style: (row) => highlightedRow(row),
|
||||
},
|
||||
|
@ -90,39 +90,39 @@ const columns = computed(() => [
|
|||
label: 'Packing',
|
||||
name: 'packing',
|
||||
field: 'packing',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.grouping'),
|
||||
name: 'grouping',
|
||||
field: 'grouping',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: t('itemBasicData.stems'),
|
||||
name: 'stems',
|
||||
field: 'stems',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
style: (row) => highlightedRow(row),
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.quantity'),
|
||||
name: 'quantity',
|
||||
field: 'quantity',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
style: (row) => highlightedRow(row),
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.cost'),
|
||||
name: 'cost',
|
||||
field: 'cost',
|
||||
align: 'center',
|
||||
field: 'right',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: 'Kg',
|
||||
name: 'weight',
|
||||
field: 'weight',
|
||||
align: 'center',
|
||||
align: 'right',
|
||||
style: (row) => highlightedRow(row),
|
||||
},
|
||||
{
|
||||
|
@ -136,7 +136,7 @@ const columns = computed(() => [
|
|||
label: t('lastEntries.supplier'),
|
||||
name: 'supplier',
|
||||
field: 'supplier',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -269,7 +269,7 @@ function highlightedRow(row) {
|
|||
</template>
|
||||
<template #body-cell-entry="{ row }">
|
||||
<QTd @click.stop :style="highlightedRow(row)">
|
||||
<div class="full-width flex justify-center">
|
||||
<div class="full-width text-right">
|
||||
<EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense />
|
||||
<span class="link">{{ row.entryFk }}</span>
|
||||
</div>
|
||||
|
@ -282,16 +282,16 @@ function highlightedRow(row) {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-printedStickers="{ row }">
|
||||
<QTd @click.stop class="text-center" :style="highlightedRow(row)">
|
||||
<QTd @click.stop class="text-right" :style="highlightedRow(row)">
|
||||
<span style="color: var(--vn-label-color)">
|
||||
{{ row.printedStickers }}</span
|
||||
>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-packing="{ row }">
|
||||
<QTd @click.stop :style="highlightedRow(row)">
|
||||
<QTd @click.stop :style="highlightedRow(row)" class="text-right">
|
||||
<QBadge
|
||||
class="center-content"
|
||||
class="grouping-badge"
|
||||
:class="getBadgeClass(row.groupingMode, 'packing')"
|
||||
rounded
|
||||
>
|
||||
|
@ -301,9 +301,9 @@ function highlightedRow(row) {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-grouping="{ row }">
|
||||
<QTd @click.stop :style="highlightedRow(row)">
|
||||
<QTd @click.stop :style="highlightedRow(row)" class="text-right">
|
||||
<QBadge
|
||||
class="center-content"
|
||||
class="grouping-badge"
|
||||
:class="getBadgeClass(row.groupingMode, 'grouping')"
|
||||
rounded
|
||||
>
|
||||
|
@ -313,7 +313,7 @@ function highlightedRow(row) {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-cost="{ row }">
|
||||
<QTd @click.stop class="text-center" :style="highlightedRow(row)">
|
||||
<QTd @click.stop class="text-right" :style="highlightedRow(row)">
|
||||
<span>
|
||||
{{ toCurrency(row.cost, 'EUR', 3) }}
|
||||
<QTooltip>
|
||||
|
@ -357,10 +357,7 @@ function highlightedRow(row) {
|
|||
.q-badge--rounded {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.center-content {
|
||||
display: flex;
|
||||
max-width: max-content;
|
||||
margin: auto;
|
||||
.grouping-badge {
|
||||
padding: 0 11px;
|
||||
height: 28px;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ const columns = computed(() => [
|
|||
{
|
||||
label: t('shelvings.item'),
|
||||
name: 'itemFk',
|
||||
align: 'left',
|
||||
align: 'right',
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
|
@ -102,19 +102,20 @@ const columns = computed(() => [
|
|||
name: 'label',
|
||||
align: 'left',
|
||||
columnFilter: { inWhere: true },
|
||||
component: 'number',
|
||||
format: (row) => (row.stock / row.packing).toFixed(2),
|
||||
},
|
||||
{
|
||||
label: t('shelvings.packing'),
|
||||
name: 'packing',
|
||||
attrs: { inWhere: true },
|
||||
align: 'left',
|
||||
component: 'number',
|
||||
},
|
||||
{
|
||||
label: t('globals.visible'),
|
||||
name: 'stock',
|
||||
attrs: { inWhere: true },
|
||||
align: 'left',
|
||||
component: 'number',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -138,21 +139,12 @@ watchEffect(selectedRows);
|
|||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#st-data">
|
||||
<div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222">
|
||||
<QCardSection horizontal>
|
||||
<span class="text-weight-bold text-subtitle1 text-center full-width">
|
||||
{{ t('shelvings.total') }}
|
||||
</span>
|
||||
</QCardSection>
|
||||
<QCardSection class="column items-center" horizontal>
|
||||
<div>
|
||||
<span class="details-label"
|
||||
>{{ t('shelvings.totalLabels') }}
|
||||
</span>
|
||||
<span>: {{ totalLabels }}</span>
|
||||
</div></QCardSection
|
||||
>
|
||||
</div>
|
||||
<QCardSection class="column items-center" horizontal>
|
||||
<div>
|
||||
<span class="details-label">{{ t('shelvings.totalLabels') }} </span>
|
||||
<span>: {{ totalLabels }}</span>
|
||||
</div>
|
||||
</QCardSection>
|
||||
</Teleport>
|
||||
<Teleport to="#st-actions">
|
||||
<QBtn
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
|
@ -48,7 +49,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
|||
<ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" />
|
||||
</template>
|
||||
<template #body="{ entity: { item, tags, visible, available, botanical } }">
|
||||
<QCard class="vn-one photo">
|
||||
<QCard class="vn-one photo" v-if="$route.name != 'ItemSummary'">
|
||||
<ItemDescriptorImage
|
||||
:entity-id="entityId"
|
||||
:visible="visible"
|
||||
|
@ -56,84 +57,108 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
|||
:show-edit-button="false"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCard class="vn-two">
|
||||
<VnTitle
|
||||
:url="getUrl(entityId, 'basic-data')"
|
||||
:text="t('globals.summary.basicData')"
|
||||
/>
|
||||
<VnLv :label="t('globals.name')" :value="item.name" />
|
||||
<VnLv :label="t('item.summary.completeName')" :value="item.longName" />
|
||||
<VnLv :label="t('item.summary.family')" :value="item.itemType.name" />
|
||||
<VnLv :label="t('globals.size')" :value="item.size" />
|
||||
<VnLv :label="t('globals.origin')" :value="item.origin.name" />
|
||||
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
|
||||
<VnLv
|
||||
:label="t('item.summary.multiplier')"
|
||||
:value="item.stemMultiplier"
|
||||
/>
|
||||
<div class="vn-card-group">
|
||||
<div class="vn-card-content">
|
||||
<VnLv :label="t('globals.name')" :value="item.name" />
|
||||
<VnLv
|
||||
:label="t('item.summary.completeName')"
|
||||
:value="item.longName"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.family')"
|
||||
:value="item.itemType.name"
|
||||
/>
|
||||
<VnLv :label="t('globals.size')" :value="item.size" />
|
||||
<VnLv :label="t('globals.origin')" :value="item.origin.name" />
|
||||
<VnLv :label="t('item.summary.stems')" :value="item.stems" />
|
||||
<VnLv
|
||||
:label="t('item.summary.multiplier')"
|
||||
:value="item.stemMultiplier"
|
||||
/>
|
||||
|
||||
<VnLv :label="t('item.summary.buyer')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="item.itemType.worker.user.name"
|
||||
:worker-id="item.itemType.worker.id"
|
||||
<VnLv :label="t('item.summary.buyer')">
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="item.itemType.worker.user.name"
|
||||
:worker-id="item.itemType.worker.id"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :info="t('Este artículo necesita una foto')">
|
||||
<template #value>
|
||||
<QCheckbox
|
||||
:label="t('item.summary.doPhoto')"
|
||||
v-model="item.isPhotoRequested"
|
||||
:disable="true"
|
||||
size="xs"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('globals.description')">
|
||||
<template #value>
|
||||
<span
|
||||
style="
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
"
|
||||
v-text="dashIfEmpty(item.description)"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
</div>
|
||||
<div class="vn-card-content">
|
||||
<VnLv
|
||||
:label="t('item.summary.intrastatCode')"
|
||||
:value="item.intrastat.id"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :info="t('Este artículo necesita una foto')">
|
||||
<template #value>
|
||||
<QCheckbox
|
||||
:label="t('item.summary.doPhoto')"
|
||||
v-model="item.isPhotoRequested"
|
||||
:disable="true"
|
||||
<VnLv
|
||||
:label="t('globals.intrastat')"
|
||||
:value="item.intrastat.description"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<VnTitle
|
||||
:url="getUrl(entityId, 'basic-data')"
|
||||
:text="t('item.summary.otherData')"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.intrastatCode')"
|
||||
:value="item.intrastat.id"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('globals.intrastat')"
|
||||
:value="item.intrastat.description"
|
||||
/>
|
||||
<VnLv :label="t('item.summary.ref')" :value="item.comment" />
|
||||
<VnLv :label="t('item.summary.relevance')" :value="item.relevancy" />
|
||||
<VnLv :label="t('item.summary.weight')" :value="item.weightByPiece" />
|
||||
<VnLv :label="t('item.summary.units')" :value="item.packingOut" />
|
||||
<VnLv :label="t('item.summary.expense')" :value="item.expense.name" />
|
||||
<VnLv :label="t('item.summary.generic')" :value="item.genericFk" />
|
||||
<VnLv
|
||||
:label="t('item.summary.recycledPlastic')"
|
||||
:value="item.recycledPlastic"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.nonRecycledPlastic')"
|
||||
:value="item.nonRecycledPlastic"
|
||||
/>
|
||||
<VnLv :label="t('item.summary.ref')" :value="item.comment" />
|
||||
<VnLv
|
||||
:label="t('item.summary.relevance')"
|
||||
:value="item.relevancy"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.weight')"
|
||||
:value="item.weightByPiece"
|
||||
/>
|
||||
<VnLv :label="t('item.summary.units')" :value="item.packingOut" />
|
||||
<VnLv
|
||||
:label="t('item.summary.expense')"
|
||||
:value="item.expense.name"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.generic')"
|
||||
:value="item.genericFk"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.recycledPlastic')"
|
||||
:value="item.recycledPlastic"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.nonRecycledPlastic')"
|
||||
:value="item.nonRecycledPlastic"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<VnTitle :url="getUrl(entityId, 'tags')" :text="t('globals.tags')" />
|
||||
<VnLv
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:label="`${tag.priority} ${tag.tag.name}:`"
|
||||
:label="`${tag.priority} ${tag.tag.name}`"
|
||||
:value="tag.value"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one" v-if="item.description">
|
||||
<VnTitle
|
||||
:url="getUrl(entityId, 'basic-data')"
|
||||
:text="t('globals.description')"
|
||||
/>
|
||||
<p v-text="item.description" />
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<VnTitle :url="getUrl(entityId, 'tax')" :text="t('item.summary.tax')" />
|
||||
<VnLv
|
||||
|
|
|
@ -17,7 +17,7 @@ const itemTagsRef = ref();
|
|||
const tagOptions = ref([]);
|
||||
const valueOptionsMap = ref(new Map());
|
||||
const getSelectedTagValues = async (tag) => {
|
||||
if (!tag.tagFk && tag.tag.isFree) return;
|
||||
if (!tag.tagFk && tag.tag?.isFree) return;
|
||||
const filter = {
|
||||
fields: ['value'],
|
||||
order: 'value ASC',
|
||||
|
@ -25,6 +25,7 @@ const getSelectedTagValues = async (tag) => {
|
|||
};
|
||||
|
||||
const params = { filter: JSON.stringify(filter) };
|
||||
if (!tag.tagFk) return;
|
||||
const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, {
|
||||
params,
|
||||
});
|
||||
|
@ -82,7 +83,6 @@ const insertTag = (rows) => {
|
|||
value: undefined,
|
||||
name: undefined,
|
||||
},
|
||||
|
||||
}"
|
||||
:data-default="{
|
||||
tag: {
|
||||
|
@ -113,7 +113,7 @@ const insertTag = (rows) => {
|
|||
<VnRow
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="items-center"
|
||||
class="items-center q-py-sm"
|
||||
>
|
||||
<VnSelect
|
||||
:label="t('itemTags.tag')"
|
||||
|
@ -139,9 +139,7 @@ const insertTag = (rows) => {
|
|||
emit-value
|
||||
use-input
|
||||
class="col"
|
||||
:is-clearable="false"
|
||||
:required="false"
|
||||
:rules="validate('itemTag.tagFk')"
|
||||
:use-like="false"
|
||||
sort-by="value"
|
||||
/>
|
||||
|
@ -152,7 +150,6 @@ const insertTag = (rows) => {
|
|||
v-model="row.value"
|
||||
:label="t('itemTags.value')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
|
||||
:data-cy="`tag${row?.tag?.name}Value`"
|
||||
/>
|
||||
<VnInput
|
||||
|
@ -161,7 +158,6 @@ const insertTag = (rows) => {
|
|||
v-model="row.priority"
|
||||
:required="true"
|
||||
:rules="validate('itemTag.priority')"
|
||||
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
|
||||
/>
|
||||
<div class="row justify-center" style="flex: 0">
|
||||
<QIcon
|
||||
|
@ -188,11 +184,8 @@ const insertTag = (rows) => {
|
|||
v-shortcut="'+'"
|
||||
fab
|
||||
data-cy="createNewTag"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('itemTags.addTag') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
:title="t('globals.add')"
|
||||
/>
|
||||
</QPageSticky>
|
||||
</template>
|
||||
</CrudModel>
|
||||
|
|
|
@ -64,27 +64,29 @@ const submitTaxes = async (data) => {
|
|||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="q-px-lg q-py-md">
|
||||
<VnRow
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="row q-gutter-md q-mb-md"
|
||||
>
|
||||
<VnInput
|
||||
:label="t('tax.country')"
|
||||
v-model="row.country.name"
|
||||
disable
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('tax.class')"
|
||||
v-model="row.taxClassFk"
|
||||
:options="taxesOptions"
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
/>
|
||||
</VnRow>
|
||||
</QCard>
|
||||
<div style="display: flex; justify-content: center">
|
||||
<QCard class="q-px-lg q-py-md">
|
||||
<VnRow
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="row q-gutter-md q-mb-md"
|
||||
>
|
||||
<VnInput
|
||||
:label="t('tax.country')"
|
||||
v-model="row.country.name"
|
||||
disable
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('tax.class')"
|
||||
v-model="row.taxClassFk"
|
||||
:options="taxesOptions"
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
/>
|
||||
</VnRow>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup>
|
||||
import { ref, computed, onBeforeMount } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import { toDate } from 'src/filters';
|
||||
|
@ -18,16 +17,13 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import axios from 'axios';
|
||||
import VnSection from 'src/components/common/VnSection.vue';
|
||||
|
||||
const entityId = computed(() => route.params.id);
|
||||
const { openCloneDialog } = cloneItem();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const { t } = useI18n();
|
||||
const tableRef = ref();
|
||||
const route = useRoute();
|
||||
const dataKey = 'ItemList';
|
||||
const validPriorities = ref([]);
|
||||
const defaultTag = ref();
|
||||
const defaultPriority = ref();
|
||||
const defaultItem = ref(null);
|
||||
|
||||
const itemFilter = {
|
||||
include: [
|
||||
|
@ -59,15 +55,14 @@ const itemFilter = {
|
|||
};
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: '',
|
||||
name: 'image',
|
||||
align: 'left',
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('item.list.id'),
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
isId: true,
|
||||
chip: {
|
||||
condition: () => true,
|
||||
|
@ -75,36 +70,36 @@ const columns = computed(() => [
|
|||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('entry.summary.grouping'),
|
||||
name: 'grouping',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'number',
|
||||
inWhere: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('entry.summary.packing'),
|
||||
name: 'packing',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'number',
|
||||
inWhere: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
label: t('globals.description'),
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
name: 'search',
|
||||
},
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('item.list.stems'),
|
||||
name: 'stems',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'number',
|
||||
inWhere: true,
|
||||
|
@ -112,19 +107,20 @@ const columns = computed(() => [
|
|||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('globals.size'),
|
||||
name: 'size',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'number',
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: true,
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
label: t('item.list.typeName'),
|
||||
name: 'typeFk',
|
||||
align: 'left',
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'ItemTypes',
|
||||
|
@ -173,26 +169,17 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
label: t('globals.intrastat'),
|
||||
name: 'intrastat',
|
||||
name: 'intrastatFk',
|
||||
align: 'left',
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'Intrastats',
|
||||
optionValue: 'description',
|
||||
fields: ['id', 'description'],
|
||||
optionLabel: 'description',
|
||||
},
|
||||
columnFilter: {
|
||||
name: 'intrastat',
|
||||
attrs: {
|
||||
url: 'Intrastats',
|
||||
optionValue: 'description',
|
||||
optionLabel: 'description',
|
||||
},
|
||||
},
|
||||
columnField: {
|
||||
component: null,
|
||||
optionValue: 'id',
|
||||
},
|
||||
cardVisible: true,
|
||||
format: (row, dashIfEmpty) => dashIfEmpty(row.intrastat),
|
||||
},
|
||||
{
|
||||
label: t('item.list.origin'),
|
||||
|
@ -238,21 +225,15 @@ const columns = computed(() => [
|
|||
},
|
||||
},
|
||||
{
|
||||
label: t('item.list.weight'),
|
||||
label: t('item.list.weightByPiece'),
|
||||
toolTip: t('item.list.weightByPiece'),
|
||||
name: 'weightByPiece',
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('item.list.stemMultiplier'),
|
||||
name: 'stemMultiplier',
|
||||
align: 'left',
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
|
@ -301,7 +282,6 @@ const columns = computed(() => [
|
|||
actions: [
|
||||
{
|
||||
title: t('globals.clone'),
|
||||
|
||||
icon: 'vn:clone',
|
||||
action: openCloneDialog,
|
||||
isPrimary: true,
|
||||
|
@ -317,15 +297,10 @@ const columns = computed(() => [
|
|||
]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const { data } = await axios.get('ItemConfigs');
|
||||
defaultTag.value = data[0].defaultTag;
|
||||
defaultPriority.value = data[0].defaultPriority;
|
||||
data.forEach((priority) => {
|
||||
validPriorities.value = priority.validPriorities;
|
||||
});
|
||||
const { data } = await axios.get('ItemConfigs/findOne');
|
||||
defaultItem.value = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSection
|
||||
:data-key="dataKey"
|
||||
|
@ -338,27 +313,29 @@ onBeforeMount(async () => {
|
|||
}"
|
||||
>
|
||||
<template #advanced-menu>
|
||||
<ItemListFilter data-key="ItemList" />
|
||||
<ItemListFilter :data-key="dataKey" />
|
||||
</template>
|
||||
<template #body>
|
||||
<VnTable
|
||||
v-if="defaultTag"
|
||||
ref="tableRef"
|
||||
v-if="defaultItem"
|
||||
:data-key="dataKey"
|
||||
:columns="columns"
|
||||
:right-search="false"
|
||||
redirect="Item"
|
||||
ref="tableRef"
|
||||
search-url="ItemList"
|
||||
url="Items/filter"
|
||||
:filter="itemFilter"
|
||||
:create="{
|
||||
urlCreate: 'Items/new',
|
||||
title: t('item.list.newItem'),
|
||||
onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
|
||||
formInitialData: {
|
||||
editorFk: entityId,
|
||||
tag: defaultTag,
|
||||
priority: defaultPriority,
|
||||
tag: defaultItem?.defaultTag,
|
||||
priority: defaultItem?.defaultPriority,
|
||||
},
|
||||
}"
|
||||
:is-editable="false"
|
||||
:columns="columns"
|
||||
redirect="Item"
|
||||
:right-search="false"
|
||||
auto-load
|
||||
>
|
||||
<template #column-image="{ row }">
|
||||
<VnImg
|
||||
|
@ -374,10 +351,18 @@ onBeforeMount(async () => {
|
|||
<ItemDescriptorProxy :id="row.id" />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-description="{ row }">
|
||||
<span class="row column full-width justify-between items-start">
|
||||
{{ row?.name }}
|
||||
<span class="subName">
|
||||
{{ row?.subName?.toUpperCase() }}
|
||||
</span>
|
||||
</span>
|
||||
<FetchedTags :item="row" :columns="3" />
|
||||
</template>
|
||||
<template #column-typeName="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.typeName }}
|
||||
{{ row.typeFk }}
|
||||
<ItemTypeDescriptorProxy :id="row.typeFk" />
|
||||
</span>
|
||||
</template>
|
||||
|
@ -387,20 +372,11 @@ onBeforeMount(async () => {
|
|||
<WorkerDescriptorProxy :id="row.buyerFk" />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-description="{ row }">
|
||||
<div class="row column full-width justify-between items-start">
|
||||
{{ row?.name }}
|
||||
<div v-if="row?.subName" class="subName">
|
||||
{{ row?.subName.toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
<FetchedTags :item="row" :columns="3" />
|
||||
</template>
|
||||
<template #more-create-dialog="{ data }">
|
||||
<VnInput
|
||||
v-model="data.provisionalName"
|
||||
:label="t('globals.description')"
|
||||
:is-required="true"
|
||||
:label="t('Provisional name')"
|
||||
:required="true"
|
||||
/>
|
||||
<VnSelect
|
||||
url="Tags"
|
||||
|
@ -410,7 +386,7 @@ onBeforeMount(async () => {
|
|||
option-label="name"
|
||||
option-value="id"
|
||||
:is-required="true"
|
||||
:sort-by="['name ASC']"
|
||||
:order="['name ASC']"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -427,7 +403,7 @@ onBeforeMount(async () => {
|
|||
:options="validPriorities"
|
||||
v-model="data.priority"
|
||||
:label="t('item.create.priority')"
|
||||
:is-required="true"
|
||||
:required="true"
|
||||
/>
|
||||
<VnSelect
|
||||
url="ItemTypes"
|
||||
|
@ -436,7 +412,7 @@ onBeforeMount(async () => {
|
|||
:fields="['id', 'code', 'name']"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
:is-required="true"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -456,7 +432,7 @@ onBeforeMount(async () => {
|
|||
:fields="['id', 'description']"
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
:is-required="true"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -476,7 +452,7 @@ onBeforeMount(async () => {
|
|||
:fields="['id', 'code', 'name']"
|
||||
option-label="code"
|
||||
option-value="id"
|
||||
:is-required="true"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -503,7 +479,5 @@ onBeforeMount(async () => {
|
|||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
New item: Nuevo artículo
|
||||
Create Item: Crear artículo
|
||||
You can search by id: Puedes buscar por id
|
||||
Provisional name: Nombre provisional
|
||||
</i18n>
|
||||
|
|
|
@ -8,7 +8,6 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
|
||||
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import axios from 'axios';
|
||||
|
@ -85,26 +84,6 @@ const removeTag = (index, params, search) => {
|
|||
applyTags(params, search);
|
||||
};
|
||||
|
||||
const applyFieldFilters = (params) => {
|
||||
fieldFiltersValues.value.forEach((fieldFilter) => {
|
||||
if (
|
||||
fieldFilter.selectedField &&
|
||||
(fieldFilter.value !== null ||
|
||||
fieldFilter.value !== '' ||
|
||||
fieldFilter.value !== undefined)
|
||||
) {
|
||||
params[fieldFilter.name] = fieldFilter.value;
|
||||
}
|
||||
});
|
||||
arrayData.applyFilter({ params });
|
||||
};
|
||||
|
||||
const removeFieldFilter = (index, params, search) => {
|
||||
delete params[fieldFiltersValues.value[index].name];
|
||||
(fieldFiltersValues.value || []).splice(index, 1);
|
||||
applyFieldFilters(params, search);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
stateStore.rightDrawer = true;
|
||||
if (arrayData.store?.userParams?.categoryFk)
|
||||
|
@ -125,7 +104,6 @@ onMounted(async () => {
|
|||
});
|
||||
});
|
||||
|
||||
// Fill fieldFiltersValues with existent userParams
|
||||
if (arrayData.store?.userParams) {
|
||||
fieldFiltersValues.value = Object.entries(arrayData.store?.userParams)
|
||||
.filter(([key, value]) => value && _moreFields.includes(key))
|
||||
|
@ -249,6 +227,16 @@ onMounted(async () => {
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
:label="t('params.isFloramondo')"
|
||||
v-model="params.isFloramondo"
|
||||
toggle-indeterminate
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<!-- Tags filter -->
|
||||
<QItemLabel header>
|
||||
{{ t('params.tags') }}
|
||||
|
@ -315,74 +303,6 @@ onMounted(async () => {
|
|||
@click="removeTag(index, params, searchFn)"
|
||||
/>
|
||||
</QItem>
|
||||
<!-- Filter fields -->
|
||||
<QItemLabel header
|
||||
>{{ t('More fields') }}
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
class="fill-icon-on-hover q-ml-md"
|
||||
size="sm"
|
||||
color="primary"
|
||||
@click="fieldFiltersValues.push({})"
|
||||
/></QItemLabel>
|
||||
<QItem v-for="(fieldFilter, index) in fieldFiltersValues" :key="index">
|
||||
<QItemSection class="col">
|
||||
<VnSelect
|
||||
class="full-width"
|
||||
:label="t('params.tag')"
|
||||
:model-value="fieldFilter.selectedField"
|
||||
:options="moreFields"
|
||||
option-label="label"
|
||||
option-value="label"
|
||||
dense
|
||||
filled
|
||||
:emit-value="false"
|
||||
use-input
|
||||
:is-clearable="false"
|
||||
@update:model-value="
|
||||
($event) => {
|
||||
fieldFilter.name = $event.name;
|
||||
fieldFilter.value = null;
|
||||
fieldFilter.selectedField = $event;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection class="col">
|
||||
<VnCheckbox
|
||||
v-if="fieldFilter.selectedField?.type === 'boolean'"
|
||||
v-model="fieldFilter.value"
|
||||
:label="t('params.value')"
|
||||
@update:model-value="applyFieldFilters(params, searchFn)"
|
||||
/>
|
||||
<VnInput
|
||||
v-else
|
||||
v-model="fieldFilter.value"
|
||||
:label="t('params.value')"
|
||||
:disable="!fieldFilter.selectedField"
|
||||
filled
|
||||
@keydown.enter="applyFieldFilters(params, searchFn)"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection side
|
||||
><QIcon
|
||||
name="delete"
|
||||
class="fill-icon-on-hover q-ml-xs"
|
||||
size="sm"
|
||||
color="primary"
|
||||
@click="removeFieldFilter(index, params, searchFn)"
|
||||
/></QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
:label="t('params.isFloramondo')"
|
||||
v-model="params.isFloramondo"
|
||||
toggle-indeterminate
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
|
@ -410,6 +330,17 @@ en:
|
|||
Green: Green
|
||||
Handmade: Handmade
|
||||
Plant: Plant
|
||||
packing: Packing
|
||||
grouping: Grouping
|
||||
stems: Stems
|
||||
size: Size
|
||||
intrastatFk: Intrastat
|
||||
ori:
|
||||
id: Origin
|
||||
workerFk: Buyer
|
||||
weightByPiece: Weight/stem
|
||||
stemMultiplier: Stem multiplier
|
||||
landed: Landed date
|
||||
es:
|
||||
More fields: Más campos
|
||||
params:
|
||||
|
@ -433,4 +364,15 @@ es:
|
|||
Green: Verde
|
||||
Handmade: Hecho a mano
|
||||
Plant: Planta
|
||||
packing: Packing
|
||||
grouping: Grouping
|
||||
stems: Tallos
|
||||
size: Altura
|
||||
intrastatFk: Intrastat
|
||||
ori:
|
||||
id: Origen
|
||||
workerFk: Comprador
|
||||
weightByPiece: Peso/tallo
|
||||
stemMultiplier: Multiplicador de tallos
|
||||
landed: Fecha de entrega
|
||||
</i18n>
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toCurrency } from 'filters/index';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { toDate, dashIfEmpty } from 'src/filters';
|
||||
import axios from 'axios';
|
||||
import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
|
||||
import { toDate } from 'src/filters';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import ItemRequestFilter from './ItemRequestFilter.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import FormModelPopup from 'src/components/FormModelPopup.vue';
|
||||
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const stateStore = useStateStore();
|
||||
const denyFormRef = ref(null);
|
||||
const denyRequestId = ref(null);
|
||||
const denyRequestIndex = ref(null);
|
||||
const itemRequestsOptions = ref([]);
|
||||
const userParams = {
|
||||
state: 'pending',
|
||||
daysOnward: 7,
|
||||
};
|
||||
const tableRef = ref();
|
||||
|
@ -34,9 +31,13 @@ onMounted(async () => {
|
|||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'id',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: t('globals.ticketId'),
|
||||
name: 'ticketFk',
|
||||
align: 'left',
|
||||
isId: true,
|
||||
chip: {
|
||||
condition: () => true,
|
||||
|
@ -44,15 +45,16 @@ const columns = computed(() => [
|
|||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
label: t('globals.shipped'),
|
||||
name: 'shipped',
|
||||
align: 'left',
|
||||
component: 'date',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)),
|
||||
columnClass: 'shrink',
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
label: t('globals.description'),
|
||||
|
@ -78,6 +80,7 @@ const columns = computed(() => [
|
|||
component: null,
|
||||
},
|
||||
columnClass: 'shrink',
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -91,6 +94,7 @@ const columns = computed(() => [
|
|||
component: null,
|
||||
},
|
||||
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
label: t('item.buyRequest.requested'),
|
||||
|
@ -121,21 +125,52 @@ const columns = computed(() => [
|
|||
component: null,
|
||||
},
|
||||
columnClass: 'shrink',
|
||||
isEditable: false,
|
||||
},
|
||||
|
||||
{
|
||||
label: t('globals.item'),
|
||||
name: 'item',
|
||||
name: 'itemFk',
|
||||
align: 'left',
|
||||
component: 'input',
|
||||
columnClass: 'expand',
|
||||
isEditable: ({ isOk }) => isOk === null,
|
||||
},
|
||||
{
|
||||
label: t('item.buyRequest.achieved'),
|
||||
name: 'achieved',
|
||||
name: 'saleQuantity',
|
||||
align: 'left',
|
||||
component: 'input',
|
||||
columnClass: 'shrink',
|
||||
isEditable: ({ itemFk, isOk }) => {
|
||||
if (itemFk && isOk === null) return true;
|
||||
},
|
||||
beforeDestroy: (row) => {
|
||||
if (!row.saleQuantity) {
|
||||
return tableRef.value.reload();
|
||||
}
|
||||
try {
|
||||
axios
|
||||
.post(`TicketRequests/${row.id}/confirm`, {
|
||||
id: row.id,
|
||||
itemFk: parseInt(row.itemFk),
|
||||
quantity: parseInt(row.saleQuantity),
|
||||
})
|
||||
.then(() => {
|
||||
axios
|
||||
.get(`Items/findOne`, { where: { id: row.itemFk } })
|
||||
.then((response) => {
|
||||
row.itemDescription = response.data.name;
|
||||
row.state = 1;
|
||||
});
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
return tableRef.value.reload();
|
||||
});
|
||||
} catch (error) {
|
||||
notify(error.response.data.error.message, 'negative');
|
||||
return tableRef.value.reload();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('item.buyRequest.concept'),
|
||||
|
@ -144,11 +179,12 @@ const columns = computed(() => [
|
|||
sortable: true,
|
||||
component: 'input',
|
||||
columnClass: 'expand',
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
label: t('globals.state'),
|
||||
name: 'state',
|
||||
format: (row) => getState(row.isOk),
|
||||
format: ({ isOk }) => getState(isOk),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
|
@ -163,7 +199,23 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'right',
|
||||
label: '',
|
||||
name: 'denyOptions',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: (row) => row.response,
|
||||
icon: 'insert_drive_file',
|
||||
isPrimary: true,
|
||||
show: (row) => row?.response?.length,
|
||||
},
|
||||
{
|
||||
title: t('Discard'),
|
||||
icon: 'thumb_down',
|
||||
fill: true,
|
||||
isPrimary: true,
|
||||
show: ({ isOk }) => isOk === null,
|
||||
action: (row) => showDenyRequestForm(row.id),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -181,54 +233,17 @@ const getBadgeColor = (date) => {
|
|||
if (difference > 0) return 'alert';
|
||||
};
|
||||
|
||||
const changeQuantity = async (request) => {
|
||||
if (request.saleFk) {
|
||||
const params = {
|
||||
quantity: request.saleQuantity,
|
||||
};
|
||||
|
||||
await axios.patch(`Sales/${request.saleFk}`, params);
|
||||
}
|
||||
await confirmRequest(request);
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
};
|
||||
|
||||
const confirmRequest = async (request) => {
|
||||
if (!request.itemFk || !request.saleQuantity) return;
|
||||
const params = {
|
||||
itemFk: request.itemFk,
|
||||
quantity: request.saleQuantity,
|
||||
attenderFk: request.attenderFk,
|
||||
};
|
||||
|
||||
const { data } = await axios.post(`TicketRequests/${request.id}/confirm`, params);
|
||||
request.itemDescription = data.concept;
|
||||
request.isOk = true;
|
||||
};
|
||||
|
||||
const getState = (isOk) => {
|
||||
if (isOk === null) return t('Pending');
|
||||
else if (isOk) return t('Accepted');
|
||||
else return t('Denied');
|
||||
};
|
||||
|
||||
const showDenyRequestForm = (requestId, rowIndex) => {
|
||||
const showDenyRequestForm = (requestId) => {
|
||||
denyRequestId.value = requestId;
|
||||
denyRequestIndex.value = rowIndex;
|
||||
denyFormRef.value.show();
|
||||
};
|
||||
|
||||
const onDenyAccept = (_, responseData) => {
|
||||
itemRequestsOptions.value[denyRequestIndex.value].isOk = responseData.isOk;
|
||||
itemRequestsOptions.value[denyRequestIndex.value].attenderFk =
|
||||
responseData.attenderFk;
|
||||
itemRequestsOptions.value[denyRequestIndex.value].response = responseData.response;
|
||||
denyRequestId.value = null;
|
||||
denyRequestIndex.value = null;
|
||||
tableRef.value.reload();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
|
@ -240,12 +255,13 @@ const onDenyAccept = (_, responseData) => {
|
|||
data-key="itemRequest"
|
||||
url="ticketRequests/filter"
|
||||
order="shipped ASC, isOk ASC"
|
||||
:columns="columns"
|
||||
:user-params="userParams"
|
||||
:right-search="false"
|
||||
auto-load
|
||||
:is-editable="true"
|
||||
:columns="columns"
|
||||
:disable-option="{ card: true }"
|
||||
chip-locale="item.params"
|
||||
:right-search="false"
|
||||
:default-remove="false"
|
||||
auto-load
|
||||
>
|
||||
<template #column-ticketFk="{ row }">
|
||||
<span class="link">
|
||||
|
@ -254,16 +270,14 @@ const onDenyAccept = (_, responseData) => {
|
|||
</span>
|
||||
</template>
|
||||
<template #column-shipped="{ row }">
|
||||
<QTd>
|
||||
<QBadge
|
||||
:color="getBadgeColor(row.shipped)"
|
||||
text-color="black"
|
||||
class="q-pa-sm"
|
||||
style="font-size: 14px"
|
||||
>
|
||||
{{ toDate(row.shipped) }}
|
||||
</QBadge>
|
||||
</QTd>
|
||||
<QBadge
|
||||
:color="getBadgeColor(row.shipped)"
|
||||
text-color="black"
|
||||
class="q-pa-xs"
|
||||
style="font-size: 14px"
|
||||
>
|
||||
{{ toDate(row.shipped) }}
|
||||
</QBadge>
|
||||
</template>
|
||||
<template #column-attenderName="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
|
@ -284,74 +298,34 @@ const onDenyAccept = (_, responseData) => {
|
|||
<DepartmentDescriptorProxy :id="row.departmentFk" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #column-item="{ row }">
|
||||
<span>
|
||||
<VnInput v-model.number="row.itemFk" dense />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-achieved="{ row }">
|
||||
<span>
|
||||
<VnInput
|
||||
ref="achievedRef"
|
||||
type="number"
|
||||
v-model.number="row.saleQuantity"
|
||||
:disable="!row.itemFk || row.isOk != null"
|
||||
@blur="changeQuantity(row)"
|
||||
@keyup.enter="$refs.achievedRef.vnInputRef.blur()"
|
||||
dense
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<template #column-concept="{ row }">
|
||||
<span @click.stop disabled="row.isOk != null">
|
||||
{{ row.itemDescription }}
|
||||
<span :class="{ link: row.itemDescription }" @click.stop>
|
||||
{{ dashIfEmpty(row.itemDescription) }}
|
||||
<ItemDescriptorProxy v-if="row.itemFk" :id="row.itemFk" />
|
||||
</span>
|
||||
</template>
|
||||
<template #moreFilterPanel="{ params }">
|
||||
<VnInputNumber
|
||||
:label="t('params.scopeDays')"
|
||||
v-model.number="params.daysOnward"
|
||||
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
|
||||
@remove="handleScopeDays()"
|
||||
class="q-px-xs q-pr-lg"
|
||||
filled
|
||||
dense
|
||||
lazy-rules
|
||||
is-outlined
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #column-denyOptions="{ row, rowIndex }">
|
||||
<QIcon
|
||||
v-if="row.response?.length"
|
||||
name="insert_drive_file"
|
||||
color="primary"
|
||||
size="sm"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ row.response }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row.isOk == null"
|
||||
name="thumb_down"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="fill-icon"
|
||||
@click="showDenyRequestForm(row.id, rowIndex)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Discard') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnTable>
|
||||
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
|
||||
<ItemRequestDenyForm :request-id="denyRequestId" @on-data-saved="onDenyAccept" />
|
||||
<FormModelPopup
|
||||
:url-create="`TicketRequests/${denyRequestId}/deny`"
|
||||
:title="t('Specify the reasons to deny this request')"
|
||||
:form-initial-data="{ id: denyRequestId }"
|
||||
@on-data-saved="tableRef.reload()"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnInput
|
||||
ref="textAreaRef"
|
||||
type="textarea"
|
||||
v-model="data.observation"
|
||||
fill-input
|
||||
:required="true"
|
||||
auto-grow
|
||||
data-cy="discardTextArea"
|
||||
/>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Discard: Descartar
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
<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 'src/components/FormModelPopup.vue';
|
||||
|
||||
defineProps({
|
||||
requestId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onDataSaved']);
|
||||
const { t } = useI18n();
|
||||
const textAreaRef = ref(null);
|
||||
const bankEntityFormData = reactive({});
|
||||
|
||||
const onDataSaved = (formData, requestResponse) => {
|
||||
emit('onDataSaved', formData, requestResponse);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
textAreaRef.value.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormModelPopup
|
||||
:url-create="`TicketRequests/${$props.requestId}/deny`"
|
||||
:title="t('Specify the reasons to deny this request')"
|
||||
:form-initial-data="bankEntityFormData"
|
||||
@on-data-saved="onDataSaved"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnRow>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
ref="textAreaRef"
|
||||
type="textarea"
|
||||
v-model="data.observation"
|
||||
fill-input
|
||||
:required="true"
|
||||
autogrow
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Specify the reasons to deny this request: Especifica las razones para descartar la petición
|
||||
</i18n>
|
|
@ -189,7 +189,7 @@ onMounted(async () => {
|
|||
<QCheckbox
|
||||
:label="t('params.mine')"
|
||||
v-model="params.mine"
|
||||
:toggle-indeterminate="false"
|
||||
:toggle-indeterminate="null"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -212,6 +212,13 @@ en:
|
|||
state: State
|
||||
daysOnward: Days onward
|
||||
myTeam: My team
|
||||
shipped: Shipped
|
||||
description: Description
|
||||
departmentFk: Department
|
||||
quantity: Quantity
|
||||
price: Price
|
||||
item: Item
|
||||
concept: Concept
|
||||
dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time
|
||||
denied: Denied
|
||||
accepted: Accepted
|
||||
|
@ -230,6 +237,13 @@ es:
|
|||
state: Estado
|
||||
daysOnward: Días en adelante
|
||||
myTeam: Mi equipo
|
||||
shipped: Enviado
|
||||
description: Descripción
|
||||
departmentFk: Departamento
|
||||
quantity: Cantidad
|
||||
price: Precio
|
||||
item: Artículo
|
||||
concept: Concepto
|
||||
dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez
|
||||
denied: Denegada
|
||||
accepted: Aceptada
|
||||
|
|
|
@ -42,11 +42,11 @@ const itemPackingTypesOptions = ref([]);
|
|||
/>
|
||||
<FormModel :url-update="`ItemTypes/${route.params.id}`" model="ItemType" auto-load>
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnInput v-model="data.code" :label="t('itemType.shared.code')" />
|
||||
<VnInput v-model="data.name" :label="t('itemType.shared.name')" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnSelect
|
||||
url="Workers/search"
|
||||
v-model="data.workerFk"
|
||||
|
@ -58,11 +58,7 @@ const itemPackingTypesOptions = ref([]);
|
|||
hide-selected
|
||||
>
|
||||
<template #prepend>
|
||||
<VnAvatar
|
||||
:worker-id="data.workerFk"
|
||||
color="primary"
|
||||
:title="title"
|
||||
/>
|
||||
<VnAvatar :worker-id="data.workerFk" color="primary" />
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -85,7 +81,7 @@ const itemPackingTypesOptions = ref([]);
|
|||
hide-selected
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnSelect
|
||||
v-model="data.temperatureFk"
|
||||
:label="t('itemType.shared.temperature')"
|
||||
|
@ -96,7 +92,7 @@ const itemPackingTypesOptions = ref([]);
|
|||
/>
|
||||
<VnInput v-model="data.life" :label="t('itemType.summary.life')" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<VnSelect
|
||||
v-model="data.itemPackingTypeFk"
|
||||
:label="t('itemType.shared.itemPackingType')"
|
||||
|
@ -107,7 +103,7 @@ const itemPackingTypesOptions = ref([]);
|
|||
/>
|
||||
<VnInput v-model="data.maxRefs" :label="t('itemType.shared.maxRefs')" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnRow class="q-py-sm">
|
||||
<QCheckbox
|
||||
v-model="data.isFragile"
|
||||
:label="t('itemType.shared.fragile')"
|
||||
|
|
|
@ -8,7 +8,8 @@ import filter from './ItemTypeFilter.js';
|
|||
<VnCard
|
||||
data-key="ItemType"
|
||||
url="ItemTypes"
|
||||
:filter="filter"
|
||||
:filter
|
||||
:id-in-where="true"
|
||||
:descriptor="ItemTypeDescriptor"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -30,7 +30,6 @@ const entityId = computed(() => {
|
|||
:filter="filter"
|
||||
title="code"
|
||||
data-key="ItemType"
|
||||
:to-module="{ name: 'ItemTypeList' }"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="$t('itemType.shared.code')" :value="entity.code" />
|
||||
|
|
|
@ -17,3 +17,10 @@ itemType:
|
|||
isUnconventionalSize: Is unconventional size
|
||||
search: Search item type
|
||||
searchInfo: Search item type by id, name or code
|
||||
params:
|
||||
id: Id
|
||||
code: Code
|
||||
name: Name
|
||||
categoryFk: Category
|
||||
workerFk: Comprador
|
||||
temperatureFk: Temperature
|
||||
|
|
|
@ -17,3 +17,10 @@ itemType:
|
|||
isUnconventionalSize: Es de tamaño poco convencional
|
||||
search: Buscar familia
|
||||
searchInfo: Buscar familia por id, nombre o código
|
||||
params:
|
||||
id: Id
|
||||
code: Código
|
||||
name: Nombre
|
||||
categoryFk: Reino
|
||||
workerFk: Comprador
|
||||
temperatureFk: Temperatura
|
||||
|
|
|
@ -25,6 +25,10 @@ const exprBuilder = (param, value) => {
|
|||
return {
|
||||
code: { like: `%${value}%` },
|
||||
};
|
||||
case 'temperatureFk':
|
||||
return {
|
||||
temperatureFk: value,
|
||||
};
|
||||
case 'search':
|
||||
if (value) {
|
||||
if (!isNaN(value)) {
|
||||
|
@ -51,16 +55,19 @@ const exprBuilder = (param, value) => {
|
|||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
align: 'right',
|
||||
name: 'id',
|
||||
label: t('id'),
|
||||
label: 'Id',
|
||||
isId: true,
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'code',
|
||||
label: t('code'),
|
||||
label: t('itemType.shared.code'),
|
||||
isTitle: true,
|
||||
cardVisible: true,
|
||||
},
|
||||
|
@ -71,8 +78,7 @@ const columns = computed(() => [
|
|||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
label: t('worker'),
|
||||
label: t('itemType.shared.worker'),
|
||||
name: 'workerFk',
|
||||
component: 'select',
|
||||
attrs: {
|
||||
|
@ -80,7 +86,6 @@ const columns = computed(() => [
|
|||
optionLabel: 'nickname',
|
||||
optionValue: 'id',
|
||||
},
|
||||
format: (row) => row.worker?.user?.name,
|
||||
cardVisible: true,
|
||||
columnField: { component: null },
|
||||
columnFilter: {
|
||||
|
@ -95,6 +100,7 @@ const columns = computed(() => [
|
|||
},
|
||||
inWhere: true,
|
||||
},
|
||||
format: (row) => row.worker?.user?.name,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -104,19 +110,24 @@ const columns = computed(() => [
|
|||
attrs: {
|
||||
options: itemCategoriesOptions.value,
|
||||
},
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: false,
|
||||
visible: false,
|
||||
format: (row, dashIfEmpty) => dashIfEmpty(row.category?.name),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'Temperature',
|
||||
name: 'temperatureFk',
|
||||
label: t('Temperature'),
|
||||
component: 'select',
|
||||
attrs: {
|
||||
options: temperatureOptions.value,
|
||||
},
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: false,
|
||||
visible: false,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
@ -141,20 +152,28 @@ const columns = computed(() => [
|
|||
:array-data-props="{
|
||||
url: 'ItemTypes',
|
||||
order: 'name ASC',
|
||||
exprBuilder,
|
||||
exprBuilder: exprBuilder,
|
||||
userFilter: {
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['id', 'name'],
|
||||
include: [
|
||||
{
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['id', 'name'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'category',
|
||||
scope: {
|
||||
fields: ['id', 'name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}"
|
||||
>
|
||||
|
@ -169,7 +188,7 @@ const columns = computed(() => [
|
|||
formInitialData: {},
|
||||
}"
|
||||
:columns="columns"
|
||||
auto-load
|
||||
:right-search="false"
|
||||
redirect="item/item-type"
|
||||
>
|
||||
<template #column-workerFk="{ row }">
|
||||
|
@ -208,15 +227,17 @@ const columns = computed(() => [
|
|||
|
||||
<i18n>
|
||||
es:
|
||||
id: Id
|
||||
code: Código
|
||||
worker: Trabajador
|
||||
ItemCategory: Reino
|
||||
Temperature: Temperatura
|
||||
Create ItemTypes: Crear familia
|
||||
params:
|
||||
id: Id
|
||||
code: Código
|
||||
worker: Trabajador
|
||||
ItemCategory: Reino
|
||||
Temperature: Temperatura
|
||||
Create ItemTypes: Crear familia
|
||||
en:
|
||||
code: Code
|
||||
worker: Worker
|
||||
ItemCategory: ItemCategory
|
||||
Temperature: Temperature
|
||||
params:
|
||||
code: Code
|
||||
worker: Worker
|
||||
ItemCategory: ItemCategory
|
||||
Temperature: Temperature
|
||||
</i18n>
|
||||
|
|
|
@ -8,10 +8,11 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
|
|||
import axios from 'axios';
|
||||
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
const MATCH = 'match';
|
||||
const { notifyResults } = displayResults();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const { t } = useI18n();
|
||||
const $props = defineProps({
|
||||
|
@ -42,7 +43,7 @@ const ticketConfig = ref({});
|
|||
const proposalTableRef = ref(null);
|
||||
|
||||
const sale = computed(() => $props.sales[0]);
|
||||
const saleFk = computed(() => sale.value.saleFk);
|
||||
const saleFk = computed(() => sale.value?.saleFk);
|
||||
const filter = computed(() => ({
|
||||
where: $props.filter,
|
||||
|
||||
|
@ -56,8 +57,24 @@ const defaultColumnAttrs = {
|
|||
};
|
||||
const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
|
||||
|
||||
const conditionalValuePrice = (price) =>
|
||||
price > 1 + ticketConfig.value.lackAlertPrice / 100 ? 'match' : 'not-match';
|
||||
const priceStatusClass = (proposalPrice) => {
|
||||
const originalPrice = sale.value?.price;
|
||||
|
||||
if (
|
||||
!originalPrice ||
|
||||
!ticketConfig.value ||
|
||||
typeof ticketConfig.value.lackAlertPrice !== 'number'
|
||||
) {
|
||||
return 'price-ok';
|
||||
}
|
||||
|
||||
const priceIncreasePercentage =
|
||||
((proposalPrice - originalPrice) / originalPrice) * 100;
|
||||
|
||||
return priceIncreasePercentage > ticketConfig.value.lackAlertPrice
|
||||
? 'price-alert'
|
||||
: 'price-ok';
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
|
@ -97,7 +114,15 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('item.list.color'),
|
||||
label: t('item.list.producer'),
|
||||
name: 'subName',
|
||||
field: 'subName',
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('proposal.tag5'),
|
||||
name: 'tag5',
|
||||
field: 'value5',
|
||||
columnClass: 'expand',
|
||||
|
@ -105,7 +130,7 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('item.list.stems'),
|
||||
label: t('proposal.tag6'),
|
||||
name: 'tag6',
|
||||
field: 'value6',
|
||||
columnClass: 'expand',
|
||||
|
@ -113,12 +138,27 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('item.list.producer'),
|
||||
label: t('proposal.tag7'),
|
||||
name: 'tag7',
|
||||
field: 'value7',
|
||||
columnClass: 'expand',
|
||||
},
|
||||
|
||||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('proposal.tag8'),
|
||||
name: 'tag8',
|
||||
field: 'value8',
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
label: t('proposal.advanceable'),
|
||||
name: 'advanceable',
|
||||
field: 'advanceable',
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
...defaultColumnAttrs,
|
||||
label: t('proposal.price2'),
|
||||
|
@ -169,14 +209,14 @@ function extractMatchValues(obj) {
|
|||
.filter((key) => key.startsWith(MATCH))
|
||||
.map((key) => parseInt(key.replace(MATCH, ''), 10));
|
||||
}
|
||||
const gradientStyle = (value) => {
|
||||
const gradientStyleClass = (row) => {
|
||||
let color = 'white';
|
||||
const perc = parseFloat(value);
|
||||
const value = parseFloat(row);
|
||||
switch (true) {
|
||||
case perc >= 0 && perc < 33:
|
||||
case value >= 0 && value < 33:
|
||||
color = 'primary';
|
||||
break;
|
||||
case perc >= 33 && perc < 66:
|
||||
case value >= 33 && value < 66:
|
||||
color = 'warning';
|
||||
break;
|
||||
|
||||
|
@ -193,52 +233,63 @@ const statusConditionalValue = (row) => {
|
|||
};
|
||||
|
||||
const isSelectionAvailable = (itemProposal) => {
|
||||
const { price2 } = itemProposal;
|
||||
const { price2, available } = itemProposal;
|
||||
const salePrice = sale.value.price;
|
||||
const byPrice = (100 * price2) / salePrice > ticketConfig.value.lackAlertPrice;
|
||||
if (byPrice) {
|
||||
return byPrice;
|
||||
const { lackAlertPrice } = ticketConfig.value;
|
||||
const isPriceTooHigh = (100 * price2) / salePrice > lackAlertPrice;
|
||||
if (isPriceTooHigh) {
|
||||
return isPriceTooHigh;
|
||||
}
|
||||
const byQuantity =
|
||||
(100 * itemProposal.available) / Math.abs($props.itemLack.lack) <
|
||||
ticketConfig.value.lackAlertPrice;
|
||||
return byQuantity;
|
||||
const hasEnoughQuantity =
|
||||
(100 * available) / Math.abs($props.itemLack.lack) < lackAlertPrice;
|
||||
return hasEnoughQuantity;
|
||||
};
|
||||
|
||||
async function change({ itemFk: substitutionFk }) {
|
||||
try {
|
||||
const promises = $props.sales.map(({ saleFk, quantity }) => {
|
||||
const params = {
|
||||
saleFk,
|
||||
substitutionFk,
|
||||
quantity,
|
||||
};
|
||||
return axios.post('Sales/replaceItem', params);
|
||||
});
|
||||
const results = await Promise.allSettled(promises);
|
||||
|
||||
notifyResults(results, 'saleFk');
|
||||
emit('itemReplaced', {
|
||||
type: 'refresh',
|
||||
quantity: quantity.value,
|
||||
itemProposal: proposalSelected.value[0],
|
||||
});
|
||||
proposalSelected.value = [];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
async function change(itemSubstitution) {
|
||||
if (!isSelectionAvailable(itemSubstitution)) {
|
||||
notify(t('notAvailable'), 'warning');
|
||||
return;
|
||||
}
|
||||
const { itemFk: substitutionFk } = itemSubstitution;
|
||||
let body;
|
||||
const promises = $props.sales.map(({ saleFk, quantity, ticketFk }) => {
|
||||
body = {
|
||||
saleFk,
|
||||
substitutionFk,
|
||||
quantity,
|
||||
ticketFk,
|
||||
};
|
||||
return axios.post('Sales/replaceItem', body);
|
||||
});
|
||||
const results = await Promise.allSettled(promises);
|
||||
|
||||
notifyResults(results, 'ticketFk');
|
||||
emit('itemReplaced', {
|
||||
...body,
|
||||
type: 'refresh',
|
||||
itemProposal: proposalSelected.value[0],
|
||||
});
|
||||
proposalSelected.value = [];
|
||||
}
|
||||
|
||||
async function handleTicketConfig(data) {
|
||||
ticketConfig.value = data[0];
|
||||
}
|
||||
|
||||
function filterRows(data) {
|
||||
const filteredRows = data.sort(
|
||||
(a, b) => isSelectionAvailable(b) - isSelectionAvailable(a),
|
||||
);
|
||||
proposalTableRef.value.CrudModelRef.formData = filteredRows;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="TicketConfigs"
|
||||
:filter="{ fields: ['lackAlertPrice'] }"
|
||||
@on-fetch="handleTicketConfig"
|
||||
></FetchData>
|
||||
auto-load
|
||||
/>
|
||||
<QInnerLoading
|
||||
:showing="isLoading"
|
||||
:label="t && t('globals.pleaseWait')"
|
||||
|
@ -255,13 +306,22 @@ async function handleTicketConfig(data) {
|
|||
:user-filter="filter"
|
||||
:columns="columns"
|
||||
class="full-width q-mt-md"
|
||||
@on-fetch="filterRows"
|
||||
row-key="id"
|
||||
:row-click="change"
|
||||
:is-editable="false"
|
||||
:right-search="false"
|
||||
:without-header="true"
|
||||
:disable-option="{ card: true, table: true }"
|
||||
>
|
||||
<template #top-right>
|
||||
<QBtn
|
||||
flat
|
||||
class="q-mr-sm"
|
||||
color="primary"
|
||||
icon="refresh"
|
||||
@click="proposalTableRef.reload()"
|
||||
/>
|
||||
</template>
|
||||
<template #column-longName="{ row }">
|
||||
<QTd
|
||||
class="flex"
|
||||
|
@ -269,15 +329,17 @@ async function handleTicketConfig(data) {
|
|||
>
|
||||
<div
|
||||
class="middle full-width"
|
||||
:class="[`proposal-${gradientStyle(statusConditionalValue(row))}`]"
|
||||
:class="[
|
||||
`proposal-${gradientStyleClass(statusConditionalValue(row))}`,
|
||||
]"
|
||||
>
|
||||
<QTooltip> {{ statusConditionalValue(row) }}% </QTooltip>
|
||||
</div>
|
||||
<div style="flex: 2 0 100%; align-content: center">
|
||||
<div>
|
||||
<span class="link">{{ row.longName }}</span>
|
||||
<span class="link" @click.stop>
|
||||
{{ row.longName }}
|
||||
<ItemDescriptorProxy :id="row.id" />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
|
@ -290,6 +352,9 @@ async function handleTicketConfig(data) {
|
|||
<template #column-tag7="{ row }">
|
||||
<span :class="{ match: !row.match7 }">{{ row.value7 }}</span>
|
||||
</template>
|
||||
<template #column-tag8="{ row }">
|
||||
<span :class="{ match: !row.match8 }">{{ row.value8 }}</span>
|
||||
</template>
|
||||
<template #column-counter="{ row }">
|
||||
<span
|
||||
:class="{
|
||||
|
@ -304,8 +369,17 @@ async function handleTicketConfig(data) {
|
|||
</template>
|
||||
<template #column-price2="{ row }">
|
||||
<div class="flex column items-center content-center">
|
||||
<VnStockValueDisplay :value="(sales[0].price - row.price2) / 100" />
|
||||
<span :class="[conditionalValuePrice(row.price2)]">{{
|
||||
<!-- Use class binding for tooltip background -->
|
||||
<QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle">
|
||||
<div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div>
|
||||
<div>
|
||||
{{ $t('proposal.itemOldPrice') }}:
|
||||
{{ toCurrency(sales[0]?.price) }}
|
||||
</div>
|
||||
</QTooltip>
|
||||
<VnStockValueDisplay :format="'currency'" :value="-row.price2 / 100" />
|
||||
<!-- Use class binding for text color -->
|
||||
<span :class="[priceStatusClass(row.price2)]">{{
|
||||
toCurrency(row.price2)
|
||||
}}</span>
|
||||
</div>
|
||||
|
@ -319,12 +393,26 @@ async function handleTicketConfig(data) {
|
|||
margin-right: 2px;
|
||||
flex: 2 0 5px;
|
||||
}
|
||||
.price-alert {
|
||||
color: $negative;
|
||||
&.q-tooltip {
|
||||
background-color: $negative;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.price-ok {
|
||||
color: inherit;
|
||||
&.q-tooltip {
|
||||
background-color: $positive;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.match {
|
||||
color: $negative;
|
||||
}
|
||||
.not-match {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.proposal-warning {
|
||||
background-color: $warning;
|
||||
}
|
||||
|
@ -344,3 +432,9 @@ async function handleTicketConfig(data) {
|
|||
font-size: smaller;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
notAvailable: 'Not available for replacement'
|
||||
es:
|
||||
notAvailable: 'No disponible para reemplazo'
|
||||
</i18n>
|
||||
|
|
|
@ -23,33 +23,32 @@ const $props = defineProps({
|
|||
default: () => [],
|
||||
},
|
||||
});
|
||||
const { dialogRef } = useDialogPluginComponent();
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||
useDialogPluginComponent();
|
||||
const emit = defineEmits([
|
||||
'onDialogClosed',
|
||||
'onDialogOk',
|
||||
'itemReplaced',
|
||||
...useDialogPluginComponent.emits,
|
||||
]);
|
||||
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
|
||||
const itemReplaced = (data) => {
|
||||
onDialogOK(data);
|
||||
dialogRef.value.hide();
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<QDialog ref="dialogRef" transition-show="scale" transition-hide="scale">
|
||||
<QCard class="dialog-width">
|
||||
<QCardSection class="row items-center q-pb-none">
|
||||
<span class="text-h6 text-grey">{{ $t('itemProposal') }}</span>
|
||||
<span class="text-h6 text-grey" v-text="$t('itemProposal')" />
|
||||
<QSpace />
|
||||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<ItemProposal
|
||||
v-bind="$props"
|
||||
@item-replaced="
|
||||
(data) => {
|
||||
emit('itemReplaced', data);
|
||||
dialogRef.hide();
|
||||
}
|
||||
"
|
||||
></ItemProposal
|
||||
></QCardSection>
|
||||
<ItemProposal v-bind="$props" @item-replaced="itemReplaced"
|
||||
/></QCardSection>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
|
|
@ -99,9 +99,6 @@ item:
|
|||
concept: Concept
|
||||
denyOptions: Deny
|
||||
scopeDays: Scope days
|
||||
searchbar:
|
||||
label: Search item
|
||||
info: You can search by id
|
||||
descriptor:
|
||||
item: Item
|
||||
buyer: Buyer
|
||||
|
@ -158,6 +155,7 @@ item:
|
|||
isPhotoRequestedTooltip: This item does need a photo
|
||||
isCustomInspectionRequired: Needs physical inspection (PIF)
|
||||
description: Description
|
||||
photoMotivation: Comment for the photographer
|
||||
fixedPrice:
|
||||
itemFk: Item ID
|
||||
groupingPrice: Grouping price
|
||||
|
@ -218,7 +216,7 @@ item:
|
|||
genus: Genus
|
||||
specie: Specie
|
||||
search: 'Search item'
|
||||
searchInfo: 'You can search by id'
|
||||
searchInfo: 'You can search by id or barcode'
|
||||
regularizeStock: Regularize stock
|
||||
itemProposal: Items proposal
|
||||
proposal:
|
||||
|
@ -231,6 +229,11 @@ proposal:
|
|||
value6: value6
|
||||
value7: value7
|
||||
value8: value8
|
||||
tag5: Tag5
|
||||
tag6: Tag6
|
||||
tag7: Tag7
|
||||
tag8: Tag8
|
||||
advanceable: Advanceable
|
||||
available: Available
|
||||
minQuantity: minQuantity
|
||||
price2: Price
|
||||
|
|
|
@ -73,13 +73,6 @@ itemTags:
|
|||
addTag: Añadir etiqueta
|
||||
tag: Etiqueta
|
||||
value: Valor
|
||||
itemType:
|
||||
shared:
|
||||
code: Código
|
||||
name: Nombre
|
||||
worker: Trabajador
|
||||
category: Reino
|
||||
temperature: Temperatura
|
||||
searchbar:
|
||||
label: Buscar artículo
|
||||
info: Buscar por id de artículo
|
||||
|
@ -155,15 +148,16 @@ item:
|
|||
weightByPiece: Peso (gramos)/tallo
|
||||
boxUnits: Unidades/caja
|
||||
recycledPlastic: Plastico reciclado
|
||||
nonRecycledPlastic: Plático no reciclado
|
||||
nonRecycledPlastic: Plástico no reciclado
|
||||
isActive: Activo
|
||||
hasKgPrice: Precio en kg
|
||||
isFragile: Frágil
|
||||
isFragileTooltip: Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
|
||||
isPhotoRequested: Hacer foto
|
||||
isPhotoRequestedTooltip: Este artículo necesita una foto
|
||||
isCustomInspectionRequired: Necesita inspección física (PIF)
|
||||
isCustomInspectionRequired: Necesita insp. física (PIF)
|
||||
description: Descripción
|
||||
photoMotivation: Comentario para el fotógrafo
|
||||
fixedPrice:
|
||||
itemFk: ID Artículo
|
||||
groupingPrice: Precio grouping
|
||||
|
@ -212,6 +206,8 @@ item:
|
|||
minSalesQuantity: Cantidad mínima de venta
|
||||
genus: Genus
|
||||
specie: Specie
|
||||
search: 'Buscar artículo'
|
||||
searchInfo: 'Puedes buscar por id de artículo o código de barras'
|
||||
regularizeStock: Regularizar stock
|
||||
buyRequest:
|
||||
ticketId: 'ID Ticket'
|
||||
|
@ -237,11 +233,16 @@ proposal:
|
|||
value6: value6
|
||||
value7: value7
|
||||
value8: value8
|
||||
tag5: Tag5
|
||||
tag6: Tag6
|
||||
tag7: Tag7
|
||||
tag8: Tag8
|
||||
available: Disponible
|
||||
minQuantity: Min. cantidad
|
||||
price2: Precio
|
||||
located: Ubicado
|
||||
counter: Contador
|
||||
advanceable: Adelantable
|
||||
difference: Diferencial
|
||||
groupingPrice: Precio Grouping
|
||||
itemOldPrice: Precio itemOld
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { QIcon } from 'quasar';
|
||||
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
|
||||
import { dashIfEmpty, toCurrency, toDate, toDateHourMinSec, toHour } from 'src/filters';
|
||||
import { openBuscaman } from 'src/utils/buscaman';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
|
@ -83,14 +83,14 @@ const ticketColumns = ref([
|
|||
{
|
||||
name: 'delivered',
|
||||
label: t('route.delivered'),
|
||||
field: (row) => dashIfEmpty(toDate(row?.delivered)),
|
||||
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'forecast',
|
||||
label: t('route.forecast'),
|
||||
field: (row) => dashIfEmpty(toDate(row?.forecast)),
|
||||
name: 'estimated',
|
||||
label: t('route.estimated'),
|
||||
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
},
|
||||
|
@ -103,7 +103,7 @@ const ticketColumns = ref([
|
|||
},
|
||||
{
|
||||
name: 'volume',
|
||||
label: t('route.summary.m3'),
|
||||
label: 'm³',
|
||||
field: (row) => row?.volume,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { dashIfEmpty, toDateHourMinSec } from 'src/filters';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import axios from 'axios';
|
||||
|
@ -66,15 +66,15 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
name: 'delivered',
|
||||
label: t('route.ticket.delivered'),
|
||||
field: (row) => dashIfEmpty(row?.delivered),
|
||||
label: t('route.delivered'),
|
||||
field: (row) => dashIfEmpty(toDateHourMinSec(row?.delivered)),
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'estimated',
|
||||
label: t('route.ticket.estimated'),
|
||||
field: (row) => dashIfEmpty(row?.estimated),
|
||||
label: t('route.estimated'),
|
||||
field: (row) => dashIfEmpty(toDateHourMinSec(row?.estimated)),
|
||||
sortable: false,
|
||||
align: 'left',
|
||||
},
|
||||
|
@ -254,7 +254,9 @@ const openSmsDialog = async () => {
|
|||
<QDialog v-model="confirmationDialog">
|
||||
<QCard style="min-width: 350px">
|
||||
<QCardSection>
|
||||
<p class="text-h6 q-ma-none">{{ t('route.ticket.selectStartingDate') }}</p>
|
||||
<p class="text-h6 q-ma-none">
|
||||
{{ t('route.ticket.selectStartingDate') }}
|
||||
</p>
|
||||
</QCardSection>
|
||||
|
||||
<QCardSection class="q-pt-none">
|
||||
|
@ -265,7 +267,12 @@ const openSmsDialog = async () => {
|
|||
/>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn flat :label="t('globals.cancel')" v-close-popup class="text-primary" />
|
||||
<QBtn
|
||||
flat
|
||||
:label="t('globals.cancel')"
|
||||
v-close-popup
|
||||
class="text-primary"
|
||||
/>
|
||||
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
||||
{{ t('globals.clone') }}
|
||||
</QBtn>
|
||||
|
@ -302,11 +309,7 @@ const openSmsDialog = async () => {
|
|||
class="q-mr-sm"
|
||||
@click="setOrderedPriority"
|
||||
>
|
||||
<QTooltip
|
||||
>{{
|
||||
t('route.ticket.renumberAllTickets')
|
||||
}}
|
||||
</QTooltip>
|
||||
<QTooltip>{{ t('route.ticket.renumberAllTickets') }} </QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="sms"
|
||||
|
@ -353,7 +356,11 @@ const openSmsDialog = async () => {
|
|||
@click="setHighestPriority(row, rows)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('route.ticket.assignHighestPriority') }}
|
||||
{{
|
||||
t(
|
||||
'route.ticket.assignHighestPriority',
|
||||
)
|
||||
}}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<VnInput
|
||||
|
@ -368,7 +375,9 @@ const openSmsDialog = async () => {
|
|||
<QTd>
|
||||
<span class="link" @click="goToBuscaman(row)">
|
||||
{{ value }}
|
||||
<QTooltip>{{ t('route.ticket.openBuscaman') }}</QTooltip>
|
||||
<QTooltip>{{
|
||||
t('route.ticket.openBuscaman')
|
||||
}}</QTooltip>
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
|
|
|
@ -2,32 +2,32 @@ route:
|
|||
filter:
|
||||
Served: Served
|
||||
summary:
|
||||
date: Date
|
||||
agency: Agency
|
||||
vehicle: Vehicle
|
||||
driver: Driver
|
||||
cost: Cost
|
||||
started: Started time
|
||||
finished: Finished time
|
||||
kmStart: Km start
|
||||
kmEnd: Km end
|
||||
volume: Volume
|
||||
packages: Packages
|
||||
description: Description
|
||||
tickets: Tickets
|
||||
order: Order
|
||||
street: Street
|
||||
city: City
|
||||
pc: PC
|
||||
client: Client
|
||||
state: State
|
||||
m3: m³
|
||||
packaging: Packaging
|
||||
ticket: Ticket
|
||||
closed: Closed
|
||||
open: Open
|
||||
yes: Yes
|
||||
no: No
|
||||
date: Date
|
||||
agency: Agency
|
||||
vehicle: Vehicle
|
||||
driver: Driver
|
||||
cost: Cost
|
||||
started: Started time
|
||||
finished: Finished time
|
||||
kmStart: Km start
|
||||
kmEnd: Km end
|
||||
volume: Volume
|
||||
packages: Packages
|
||||
description: Description
|
||||
tickets: Tickets
|
||||
order: Order
|
||||
street: Street
|
||||
city: City
|
||||
pc: PC
|
||||
client: Client
|
||||
state: State
|
||||
m3: m³
|
||||
packaging: Packaging
|
||||
ticket: Ticket
|
||||
closed: Closed
|
||||
open: Open
|
||||
yes: Yes
|
||||
no: No
|
||||
extendedList:
|
||||
selectStartingDate: Select the starting date
|
||||
startingDate: Starting date
|
||||
|
@ -105,7 +105,7 @@ route:
|
|||
dated: Dated
|
||||
preview: Preview
|
||||
delivered: Delivered
|
||||
forecast: Forecast
|
||||
estimated: Estimated
|
||||
cmr:
|
||||
search: Search Cmr
|
||||
searchInfo: You can search Cmr by Id
|
||||
|
@ -131,8 +131,6 @@ route:
|
|||
PC: PC
|
||||
client: Client
|
||||
warehouse: Warehouse
|
||||
delivered: Delivered
|
||||
estimated: Estimated
|
||||
packages: Packages
|
||||
packaging: Packaging
|
||||
ticket: Ticket
|
||||
|
|
|
@ -2,30 +2,30 @@ route:
|
|||
filter:
|
||||
Served: Servida
|
||||
summary:
|
||||
date: Fecha
|
||||
agency: Agencia
|
||||
vehicle: Vehículo
|
||||
driver: Conductor
|
||||
cost: Costo
|
||||
started: Hora inicio
|
||||
finished: Hora fin
|
||||
kmStart: Km inicio
|
||||
kmEnd: Km fin
|
||||
volume: Volumen
|
||||
packages: Bultos
|
||||
description: Descripción
|
||||
tickets: Tickets
|
||||
order: Orden
|
||||
street: Dirección fiscal
|
||||
city: Población
|
||||
pc: CP
|
||||
client: Cliente
|
||||
state: Estado
|
||||
packaging: Encajado
|
||||
closed: Cerrada
|
||||
open: Abierta
|
||||
yes: Sí
|
||||
no: No
|
||||
date: Fecha
|
||||
agency: Agencia
|
||||
vehicle: Vehículo
|
||||
driver: Conductor
|
||||
cost: Costo
|
||||
started: Hora inicio
|
||||
finished: Hora fin
|
||||
kmStart: Km inicio
|
||||
kmEnd: Km fin
|
||||
volume: Volumen
|
||||
packages: Bultos
|
||||
description: Descripción
|
||||
tickets: Tickets
|
||||
order: Orden
|
||||
street: Dirección fiscal
|
||||
city: Población
|
||||
pc: CP
|
||||
client: Cliente
|
||||
state: Estado
|
||||
packaging: Encajado
|
||||
closed: Cerrada
|
||||
open: Abierta
|
||||
yes: Sí
|
||||
no: No
|
||||
extendedList:
|
||||
selectStartingDate: Seleccione la fecha de inicio
|
||||
statingDate: Fecha de inicio
|
||||
|
@ -104,7 +104,7 @@ route:
|
|||
dated: Fecha
|
||||
preview: Vista previa
|
||||
delivered: Entregado
|
||||
forecast: Pronóstico
|
||||
estimated: Pronóstico
|
||||
cmr:
|
||||
list:
|
||||
results: resultados
|
||||
|
@ -127,8 +127,6 @@ route:
|
|||
PC: CP
|
||||
client: Cliente
|
||||
warehouse: Almacén
|
||||
delivered: Entregado
|
||||
estimated: Pronóstico
|
||||
packages: Bultos
|
||||
packaging: Encajado
|
||||
ticket: Ticket
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import axios from 'axios';
|
||||
export default async function (data, date) {
|
||||
export default async function (data, landed) {
|
||||
const reducedData = data.reduce((acc, item) => {
|
||||
const existing = acc.find(({ ticketFk }) => ticketFk === item.id);
|
||||
if (existing) {
|
||||
existing.sales.push(item.saleFk);
|
||||
} else {
|
||||
acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], date });
|
||||
acc.push({ ticketFk: item.ticketFk, sales: [item.saleFk], landed });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
|
|
@ -17,7 +17,6 @@ import ItemProposalProxy from 'src/pages/Item/components/ItemProposalProxy.vue';
|
|||
import { useQuasar } from 'quasar';
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const editableStates = ref([]);
|
||||
const stateStore = useStateStore();
|
||||
const tableRef = ref();
|
||||
const changeItemDialogRef = ref(null);
|
||||
|
@ -70,14 +69,11 @@ const showItemProposal = () => {
|
|||
})
|
||||
.onOk(itemProposalEvt);
|
||||
};
|
||||
|
||||
const isButtonDisabled = computed(() => selectedRows.value.length !== 1);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="States/editableStates"
|
||||
@on-fetch="(data) => (editableStates = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
:url="`Items/${entityId}/getCard`"
|
||||
:fields="['longName']"
|
||||
|
@ -99,11 +95,7 @@ const showItemProposal = () => {
|
|||
>
|
||||
<template #top-right>
|
||||
<QBtnGroup push class="q-mr-lg" style="column-gap: 1px">
|
||||
<QBtn
|
||||
data-cy="transferLines"
|
||||
color="primary"
|
||||
:disable="!(selectedRows.length === 1)"
|
||||
>
|
||||
<QBtn data-cy="transferLines" color="primary" :disable="isButtonDisabled">
|
||||
<template #default>
|
||||
<QIcon name="vn:splitline" />
|
||||
<QIcon name="vn:ticket" />
|
||||
|
@ -124,7 +116,7 @@ const showItemProposal = () => {
|
|||
<QBtn
|
||||
color="primary"
|
||||
@click="showItemProposal"
|
||||
:disable="!(selectedRows.length === 1)"
|
||||
:disable="isButtonDisabled"
|
||||
data-cy="itemProposal"
|
||||
>
|
||||
<QIcon name="import_export" class="rotate-90" />
|
||||
|
@ -135,7 +127,7 @@ const showItemProposal = () => {
|
|||
<VnPopupProxy
|
||||
data-cy="changeItem"
|
||||
icon="sync"
|
||||
:disable="!(selectedRows.length === 1)"
|
||||
:disable="isButtonDisabled"
|
||||
:tooltip="t('negative.detail.modal.changeItem.title')"
|
||||
>
|
||||
<template #extraIcon> <QIcon name="vn:item" /> </template>
|
||||
|
@ -149,7 +141,7 @@ const showItemProposal = () => {
|
|||
<VnPopupProxy
|
||||
data-cy="changeState"
|
||||
icon="sync"
|
||||
:disable="!(selectedRows.length === 1)"
|
||||
:disable="isButtonDisabled"
|
||||
:tooltip="t('negative.detail.modal.changeState.title')"
|
||||
>
|
||||
<template #extraIcon> <QIcon name="vn:eye" /> </template>
|
||||
|
@ -163,7 +155,7 @@ const showItemProposal = () => {
|
|||
<VnPopupProxy
|
||||
data-cy="changeQuantity"
|
||||
icon="sync"
|
||||
:disable="!(selectedRows.length === 1)"
|
||||
:disable="isButtonDisabled"
|
||||
:tooltip="t('negative.detail.modal.changeQuantity.title')"
|
||||
@click="showChangeQuantityDialog = true"
|
||||
>
|
||||
|
|
|
@ -7,6 +7,8 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInputDateTime from 'src/components/common/VnInputDateTime.vue';
|
||||
import VnInputDates from 'src/components/common/VnInputDates.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
|
@ -73,8 +75,8 @@ const setUserParams = (params) => {
|
|||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:hidden-tags="['excludedDates']"
|
||||
@set-user-params="setUserParams"
|
||||
:unremovable-params="['warehouseFk']"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
|
@ -92,7 +94,7 @@ const setUserParams = (params) => {
|
|||
dense
|
||||
filled
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
() => {
|
||||
setUserParams(params);
|
||||
}
|
||||
"
|
||||
|
@ -127,8 +129,19 @@ const setUserParams = (params) => {
|
|||
dense
|
||||
filled
|
||||
/>
|
||||
</QItemSection> </QItem
|
||||
><QItem>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDates
|
||||
v-model="params.excludedDates"
|
||||
filled
|
||||
:label="t('negative.excludedDates')"
|
||||
>
|
||||
</VnInputDates>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection v-if="categoriesOptions">
|
||||
<VnSelect
|
||||
:label="t('negative.categoryFk')"
|
||||
|
|
|
@ -7,6 +7,7 @@ import { onBeforeMount } from 'vue';
|
|||
import { dashIfEmpty, toDate, toHour } from 'src/filters';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import TicketLackFilter from './TicketLackFilter.vue';
|
||||
|
@ -45,10 +46,10 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
columnClass: 'shrink',
|
||||
name: 'timed',
|
||||
name: 'minTimed',
|
||||
align: 'center',
|
||||
label: t('negative.timed'),
|
||||
format: ({ timed }) => toHour(timed),
|
||||
format: ({ minTimed }) => toHour(minTimed),
|
||||
sortable: true,
|
||||
cardVisible: true,
|
||||
columnFilter: {
|
||||
|
@ -64,9 +65,25 @@ const columns = computed(() => [
|
|||
columnFilter: {
|
||||
component: 'input',
|
||||
type: 'number',
|
||||
inWhere: false,
|
||||
columnClass: 'shrink',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'nextEntryFk',
|
||||
align: 'center',
|
||||
label: t('negative.nextEntryFk'),
|
||||
format: ({ nextEntryFk }) => nextEntryFk,
|
||||
sortable: false,
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
name: 'nextEntryLanded',
|
||||
align: 'center',
|
||||
label: t('negative.nextEntryLanded'),
|
||||
format: ({ nextEntryLanded }) => toDate(nextEntryLanded),
|
||||
sortable: false,
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
name: 'longName',
|
||||
align: 'left',
|
||||
|
@ -195,6 +212,12 @@ const setUserParams = (params) => {
|
|||
<span @click.stop>{{ row.itemFk }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #column-nextEntryFk="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.nextEntryFk }}
|
||||
<EntryDescriptorProxy :id="row.nextEntryFk" />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-longName="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.longName }}
|
||||
|
|
|
@ -35,6 +35,7 @@ const filterLack = ref({
|
|||
order: 'ts.alertLevelCode ASC',
|
||||
});
|
||||
|
||||
const editableStates = ref([]);
|
||||
const selectedRows = ref([]);
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
@ -135,9 +136,12 @@ const saveChange = async (field, { row }) => {
|
|||
try {
|
||||
switch (field) {
|
||||
case 'alertLevelCode':
|
||||
const { id: code } = editableStates.value.find(
|
||||
({ name }) => name === row.code,
|
||||
);
|
||||
await axios.post(`Tickets/state`, {
|
||||
ticketFk: row.ticketFk,
|
||||
code: row[field],
|
||||
code,
|
||||
});
|
||||
break;
|
||||
|
||||
|
@ -160,6 +164,11 @@ function onBuysFetched(data) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="States/editableStates"
|
||||
@on-fetch="(data) => (editableStates = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
ref="fetchItemLack"
|
||||
:url="`Tickets/itemLack`"
|
||||
|
@ -309,12 +318,12 @@ function onBuysFetched(data) {
|
|||
</template>
|
||||
<template #column-alertLevelCode="props">
|
||||
<VnSelect
|
||||
url="States/editableStates"
|
||||
:options="editableStates"
|
||||
auto-load
|
||||
hide-selected
|
||||
option-value="id"
|
||||
option-value="name"
|
||||
option-label="name"
|
||||
v-model="props.row.alertLevelCode"
|
||||
v-model="props.row.code"
|
||||
v-on="getInputEvents(props)"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -19,18 +19,18 @@ const $props = defineProps({
|
|||
const updateItem = async () => {
|
||||
try {
|
||||
showChangeItemDialog.value = true;
|
||||
const rowsToUpdate = $props.selectedRows.map(({ saleFk, quantity }) =>
|
||||
const rowsToUpdate = $props.selectedRows.map(({ saleFk, ticketFk, quantity }) =>
|
||||
axios.post(`Sales/replaceItem`, {
|
||||
saleFk,
|
||||
ticketFk,
|
||||
substitutionFk: newItem.value,
|
||||
quantity,
|
||||
}),
|
||||
);
|
||||
const result = await Promise.allSettled(rowsToUpdate);
|
||||
notifyResults(result, 'saleFk');
|
||||
notifyResults(result, 'ticketFk');
|
||||
emit('update-item', newItem.value);
|
||||
} catch (err) {
|
||||
console.error('Error updating item:', err);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
@ -41,6 +41,7 @@ const updateItem = async () => {
|
|||
<QCardSection class="row items-center justify-center column items-stretch">
|
||||
<span>{{ $t('negative.detail.modal.changeItem.title') }}</span>
|
||||
<VnSelect
|
||||
data-cy="New item_select"
|
||||
url="Items/WithName"
|
||||
:fields="['id', 'name']"
|
||||
:sort-by="['id DESC']"
|
||||
|
|
|
@ -19,9 +19,9 @@ const $props = defineProps({
|
|||
const updateState = async () => {
|
||||
try {
|
||||
showChangeStateDialog.value = true;
|
||||
const rowsToUpdate = $props.selectedRows.map(({ id }) =>
|
||||
const rowsToUpdate = $props.selectedRows.map(({ ticketFk }) =>
|
||||
axios.post(`Tickets/state`, {
|
||||
ticketFk: id,
|
||||
ticketFk,
|
||||
code: newState.value,
|
||||
}),
|
||||
);
|
||||
|
@ -49,8 +49,9 @@ const updateState = async () => {
|
|||
v-model="newState"
|
||||
:options="editableStates"
|
||||
option-label="name"
|
||||
option-value="code"
|
||||
option-value="id"
|
||||
autofocus
|
||||
data-cy="New state_select"
|
||||
/>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
|
|
|
@ -14,8 +14,6 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnRow from 'src/components/ui/VnRow.vue';
|
||||
import TicketFilter from './TicketFilter.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
|
||||
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
||||
|
@ -25,6 +23,7 @@ import TicketProblems from 'src/components/TicketProblems.vue';
|
|||
import VnSection from 'src/components/common/VnSection.vue';
|
||||
import { getAddresses } from 'src/pages/Customer/composables/getAddresses';
|
||||
import { getAgencies } from 'src/pages/Route/Agency/composables/getAgencies';
|
||||
import TicketNewPayment from './components/TicketNewPayment.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
@ -73,11 +72,6 @@ const initializeFromQuery = () => {
|
|||
|
||||
const selectedRows = ref([]);
|
||||
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
|
||||
const showForm = ref(false);
|
||||
const dialogData = ref();
|
||||
const companiesOptions = ref([]);
|
||||
const accountingOptions = ref([]);
|
||||
const amountToReturn = ref();
|
||||
const dataKey = 'TicketList';
|
||||
const formInitialData = ref({});
|
||||
|
||||
|
@ -381,87 +375,18 @@ function openBalanceDialog(ticket) {
|
|||
description.value.push(ticketData.id);
|
||||
}
|
||||
|
||||
const balanceCreateDialog = ref({
|
||||
const dialogData = ref({
|
||||
amountPaid: amountPaid.value,
|
||||
clientFk: clientFk.value,
|
||||
description: `Albaran: ${description.value.join(', ')}`,
|
||||
});
|
||||
dialogData.value = balanceCreateDialog;
|
||||
showForm.value = true;
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const { data: email } = await axios.get('Clients', {
|
||||
params: {
|
||||
filter: JSON.stringify({ where: { id: dialogData.value.value.clientFk } }),
|
||||
quasar.dialog({
|
||||
component: TicketNewPayment,
|
||||
componentProps: {
|
||||
clientId: clientFk.value,
|
||||
formData: dialogData.value,
|
||||
},
|
||||
});
|
||||
|
||||
const { data } = await axios.post(
|
||||
`Clients/${dialogData.value.value.clientFk}/createReceipt`,
|
||||
{
|
||||
payed: dialogData.value.payed,
|
||||
companyFk: dialogData.value.companyFk,
|
||||
bankFk: dialogData.value.bankFk,
|
||||
amountPaid: dialogData.value.value.amountPaid,
|
||||
description: dialogData.value.value.description,
|
||||
clientFk: dialogData.value.value.clientFk,
|
||||
email: email[0].email,
|
||||
},
|
||||
);
|
||||
|
||||
if (data) notify('globals.dataSaved', 'positive');
|
||||
showForm.value = false;
|
||||
}
|
||||
|
||||
const setAmountToReturn = (newAmountGiven) => {
|
||||
const amountPaid = dialogData.value.value.amountPaid;
|
||||
|
||||
amountToReturn.value = newAmountGiven - amountPaid;
|
||||
};
|
||||
|
||||
function setReference(data) {
|
||||
let newDescription = '';
|
||||
|
||||
switch (data) {
|
||||
case 1:
|
||||
newDescription = `${t(
|
||||
'ticketList.creditCard',
|
||||
)}, ${dialogData.value.value.description.replace(
|
||||
/^(Credit Card, |Cash, |Transfers, )/,
|
||||
'',
|
||||
)}`;
|
||||
break;
|
||||
case 2:
|
||||
newDescription = `${t(
|
||||
'ticketList.cash',
|
||||
)}, ${dialogData.value.value.description.replace(
|
||||
/^(Credit Card, |Cash, |Transfers, )/,
|
||||
'',
|
||||
)}`;
|
||||
break;
|
||||
case 3:
|
||||
newDescription = `${newDescription.replace(
|
||||
/^(Credit Card, |Cash, |Transfers, )/,
|
||||
'',
|
||||
)}`;
|
||||
break;
|
||||
case 4:
|
||||
newDescription = `${t(
|
||||
'ticketList.transfers',
|
||||
)}, ${dialogData.value.value.description.replace(
|
||||
/^(Credit Card, |Cash, |Transfers, )/,
|
||||
'',
|
||||
)}`;
|
||||
break;
|
||||
case 3317:
|
||||
newDescription = '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dialogData.value.value.description = newDescription;
|
||||
}
|
||||
|
||||
function exprBuilder(param, value) {
|
||||
|
@ -492,16 +417,6 @@ function exprBuilder(param, value) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="Companies"
|
||||
@on-fetch="(data) => (companiesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Accountings"
|
||||
@on-fetch="(data) => (accountingOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnSection
|
||||
:data-key="dataKey"
|
||||
:columns="columns"
|
||||
|
@ -742,99 +657,6 @@ function exprBuilder(param, value) {
|
|||
{{ t('ticketList.accountPayment') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
<QDialog ref="dialogRef" v-model="showForm">
|
||||
<QCard class="q-pa-md q-mb-md">
|
||||
<QForm @submit="onSubmit()" class="q-pa-sm">
|
||||
{{ t('ticketList.addPayment') }}
|
||||
<VnRow>
|
||||
<VnInputDate
|
||||
:label="t('ticketList.date')"
|
||||
v-model="dialogData.payed"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('ticketList.company')"
|
||||
v-model="dialogData.companyFk"
|
||||
:options="companiesOptions"
|
||||
option-label="code"
|
||||
hide-selected
|
||||
>
|
||||
</VnSelect>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('ticketList.bank')"
|
||||
v-model="dialogData.bankFk"
|
||||
:options="accountingOptions"
|
||||
option-label="bank"
|
||||
hide-selected
|
||||
@update:model-value="setReference"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('ticketList.amount')"
|
||||
v-model="dialogData.value.amountPaid"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow v-if="dialogData.bankFk === 2">
|
||||
<span>
|
||||
{{ t('ticketList.cash') }}
|
||||
</span>
|
||||
</VnRow>
|
||||
<VnRow v-if="dialogData.bankFk === 2">
|
||||
<VnInput
|
||||
:label="t('ticketList.deliveredAmount')"
|
||||
v-model="dialogData.value.amountGiven"
|
||||
@update:model-value="setAmountToReturn"
|
||||
type="number"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('ticketList.amountToReturn')"
|
||||
:model-value="amountToReturn"
|
||||
type="number"
|
||||
readonly
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow v-if="dialogData.bankFk === 3 || dialogData.bankFk === 3117">
|
||||
<VnInput
|
||||
:label="t('ticketList.compensation')"
|
||||
v-model="dialogData.value.compensation"
|
||||
type="text"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInput
|
||||
:label="t('ticketList.reference')"
|
||||
v-model="dialogData.value.description"
|
||||
type="text"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow v-if="dialogData.bankFk === 2">
|
||||
<QCheckbox
|
||||
:label="t('ticketList.viewReceipt')"
|
||||
v-model="dialogData.value.viewReceipt"
|
||||
:toggle-indeterminate="false"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('ticketList.sendEmail')"
|
||||
v-model="dialogData.value.senEmail"
|
||||
:toggle-indeterminate="false"
|
||||
/>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
color="primary"
|
||||
@click="onSubmit()"
|
||||
/>
|
||||
<QBtn
|
||||
flat
|
||||
:label="t('globals.close')"
|
||||
color="primary"
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</QForm>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
|
||||
<QBtn
|
||||
@click="sendDocuware(selectedRows)"
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
|
||||
import { usePrintService } from 'src/composables/usePrintService';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
import FormModelPopup from 'src/components/FormModelPopup.vue';
|
||||
import VnRow from 'src/components/ui/VnRow.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnAccountNumber from 'src/components/common/VnAccountNumber.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const { sendEmail, openReport } = usePrintService();
|
||||
const { dialogRef } = useDialogPluginComponent();
|
||||
|
||||
const $props = defineProps({
|
||||
formData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
clientId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
promise: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const closeButton = ref(null);
|
||||
const viewReceipt = ref();
|
||||
const shouldSendEmail = ref(false);
|
||||
const maxAmount = ref();
|
||||
const accountingType = ref({});
|
||||
const isCash = ref(false);
|
||||
const formModelRef = ref(false);
|
||||
const amountToReturn = ref();
|
||||
const filterBanks = {
|
||||
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||
include: { relation: 'accountingType' },
|
||||
order: 'id',
|
||||
};
|
||||
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const initialData = ref({
|
||||
...$props.formData,
|
||||
companyFk: user.value.companyFk,
|
||||
payed: Date.vnNew(),
|
||||
});
|
||||
|
||||
function setPaymentType(data, accounting) {
|
||||
data.bankFk = accounting.id;
|
||||
if (!accounting) return;
|
||||
accountingType.value = accounting.accountingType;
|
||||
data.description = [];
|
||||
data.payed = Date.vnNew();
|
||||
isCash.value = accountingType.value.code == 'cash';
|
||||
viewReceipt.value = isCash.value;
|
||||
if (accountingType.value.daysInFuture)
|
||||
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
|
||||
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
|
||||
if (accountingType.value.code == 'compensation') return (data.description = '');
|
||||
|
||||
let descriptions = [];
|
||||
if (accountingType.value.receiptDescription)
|
||||
descriptions.push(accountingType.value.receiptDescription);
|
||||
if (data.description > 0) descriptions.push(data.description);
|
||||
data.description = descriptions.join(', ');
|
||||
}
|
||||
|
||||
const calculateFromAmount = (event) => {
|
||||
initialData.value.amountToReturn = Number(
|
||||
(parseFloat(initialData.value.deliveredAmount) + parseFloat(event) * -1).toFixed(
|
||||
2,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const calculateFromDeliveredAmount = (event) => {
|
||||
amountToReturn.value = Number((event - initialData.value.amountPaid).toFixed(2));
|
||||
};
|
||||
|
||||
function onBeforeSave(data) {
|
||||
const exceededAmount = data.amountPaid > maxAmount.value;
|
||||
if (isCash.value && exceededAmount)
|
||||
return notify(t('Amount exceeded', { maxAmount: maxAmount.value }), 'negative');
|
||||
|
||||
if (isCash.value && shouldSendEmail.value && !data.email)
|
||||
return notify(t('There is no assigned email for this client'), 'negative');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function onDataSaved({ email, id }) {
|
||||
try {
|
||||
if (shouldSendEmail.value && isCash.value)
|
||||
await sendEmail(`Receipts/${id}/receipt-email`, {
|
||||
recipient: email,
|
||||
});
|
||||
|
||||
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank');
|
||||
} finally {
|
||||
if ($props.promise) $props.promise();
|
||||
if (closeButton.value) closeButton.value.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function getSupplierClientReferences(data) {
|
||||
if (!data) return (initialData.value.description = '');
|
||||
const params = { bankAccount: data.compensationAccount };
|
||||
const { data: reference } = await axios(`Clients/getClientOrSupplierReference`, {
|
||||
params,
|
||||
});
|
||||
if (reference.supplierId) {
|
||||
data.description = t('Supplier Compensation Reference', {
|
||||
supplierId: reference.supplierId,
|
||||
supplierName: reference.supplierName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
data.description = t('Client Compensation Reference', {
|
||||
clientId: reference.clientId,
|
||||
clientName: reference.clientName,
|
||||
});
|
||||
}
|
||||
|
||||
async function getAmountPaid() {
|
||||
const filter = {
|
||||
where: {
|
||||
clientFk: $props.clientId,
|
||||
companyFk: initialData.value.companyFk,
|
||||
},
|
||||
};
|
||||
|
||||
const { data } = await getClientRisk(filter);
|
||||
initialData.value.amountPaid = (data?.length && data[0].amount) || undefined;
|
||||
}
|
||||
|
||||
async function onSubmit(formData) {
|
||||
const clientFk = $props.clientId;
|
||||
const {
|
||||
data: [{ email }],
|
||||
} = await axios.get('Clients', {
|
||||
params: {
|
||||
filter: JSON.stringify({ where: { id: clientFk } }),
|
||||
},
|
||||
});
|
||||
|
||||
const { data } = await axios.post(`Clients/${clientFk}/createReceipt`, {
|
||||
payed: formData.payed,
|
||||
companyFk: formData.companyFk,
|
||||
bankFk: formData.bankFk,
|
||||
amountPaid: formData.amountPaid,
|
||||
description: formData.description,
|
||||
clientFk,
|
||||
email,
|
||||
});
|
||||
|
||||
if (data) notify('globals.dataSaved', 'positive');
|
||||
await onDataSaved(data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QDialog ref="dialogRef" persistent>
|
||||
<FormModelPopup
|
||||
ref="formModelRef"
|
||||
:form-initial-data="initialData"
|
||||
:save-fn="onSubmit"
|
||||
:prevent-submit="true"
|
||||
:mapper="onBeforeSave"
|
||||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<h5 class="q-mt-none">{{ t('New payment') }}</h5>
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
autofocus
|
||||
:label="t('Bank')"
|
||||
v-model="data.bankFk"
|
||||
url="Accountings"
|
||||
:filter="filterBanks"
|
||||
option-label="bank"
|
||||
:include="{ relation: 'accountingType' }"
|
||||
sort-by="id"
|
||||
@update:model-value="
|
||||
(value, options) => setPaymentType(data, value, options)
|
||||
"
|
||||
:emit-value="false"
|
||||
data-cy="paymentBank"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt.id }}: {{ scope.opt.bank }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnInputNumber
|
||||
:label="t('Amount')"
|
||||
:required="true"
|
||||
@update:model-value="calculateFromAmount($event)"
|
||||
clearable
|
||||
v-model.number="data.amountPaid"
|
||||
data-cy="paymentAmount"
|
||||
:positive="false"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInputDate
|
||||
:label="t('Date')"
|
||||
v-model="data.payed"
|
||||
:required="true"
|
||||
/>
|
||||
<VnSelect
|
||||
url="Companies"
|
||||
:label="t('Company')"
|
||||
:required="true"
|
||||
:rules="validate('entry.companyFk')"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
v-model="data.companyFk"
|
||||
@update:model-value="getAmountPaid()"
|
||||
/>
|
||||
</VnRow>
|
||||
<div v-if="accountingType.code == 'compensation'">
|
||||
<div class="text-h6">
|
||||
{{ t('Compensation') }}
|
||||
</div>
|
||||
<VnRow>
|
||||
<VnAccountNumber
|
||||
:label="t('Compensation account')"
|
||||
clearable
|
||||
v-model="data.compensationAccount"
|
||||
@blur="getSupplierClientReferences(data)"
|
||||
/>
|
||||
</VnRow>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Reference')"
|
||||
:required="true"
|
||||
clearable
|
||||
v-model="data.description"
|
||||
/>
|
||||
<div v-if="accountingType.code == 'cash'">
|
||||
<div class="text-h6">{{ t('Cash') }}</div>
|
||||
<VnRow>
|
||||
<VnInputNumber
|
||||
:label="t('Delivered amount')"
|
||||
@update:model-value="calculateFromDeliveredAmount($event)"
|
||||
clearable
|
||||
v-model="data.deliveredAmount"
|
||||
/>
|
||||
<VnInputNumber
|
||||
:label="t('Amount to return')"
|
||||
disable
|
||||
v-model="amountToReturn"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QCheckbox v-model="viewReceipt" :label="t('View recipt')" />
|
||||
<QCheckbox v-model="shouldSendEmail" :label="t('Send email')" />
|
||||
</VnRow>
|
||||
</div>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
|
||||
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
|
||||
es:
|
||||
New payment: Añadir pago
|
||||
Date: Fecha
|
||||
Company: Empresa
|
||||
Bank: Caja
|
||||
Amount: Importe
|
||||
Reference: Referencia
|
||||
Cash: Efectivo
|
||||
Delivered amount: Cantidad entregada
|
||||
Amount to return: Cantidad a devolver
|
||||
View recipt: Ver recibido
|
||||
Send email: Enviar correo
|
||||
Compensation: Compensación
|
||||
Compensation account: Cuenta para compensar
|
||||
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
|
||||
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
|
||||
There is no assigned email for this client: No hay correo asignado para este cliente
|
||||
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {maxAmount}
|
||||
</i18n>
|
|
@ -206,7 +206,6 @@ ticketList:
|
|||
toLines: Go to lines
|
||||
addressNickname: Address nickname
|
||||
ref: Reference
|
||||
hour: Hour
|
||||
rounding: Rounding
|
||||
noVerifiedData: No verified data
|
||||
warehouse: Warehouse
|
||||
|
@ -215,6 +214,8 @@ ticketList:
|
|||
clientFrozen: Client frozen
|
||||
componentLack: Component lack
|
||||
negative:
|
||||
nextEntryFk: Next entry
|
||||
nextEntryLanded: Next entry landed
|
||||
hour: Hour
|
||||
id: Id Article
|
||||
longName: Article
|
||||
|
@ -225,6 +226,7 @@ negative:
|
|||
value: Negative
|
||||
itemFk: Article
|
||||
producer: Producer
|
||||
excludedDates: Excluded dates
|
||||
warehouse: Warehouse
|
||||
warehouseFk: Warehouse
|
||||
category: Category
|
||||
|
|
|
@ -215,6 +215,8 @@ ticketList:
|
|||
addressNickname: Alias consignatario
|
||||
ref: Referencia
|
||||
negative:
|
||||
nextEntryLanded: F. Entrada
|
||||
nextEntryFk: Entrada
|
||||
hour: Hora
|
||||
id: Id Articulo
|
||||
longName: Artículo
|
||||
|
@ -225,7 +227,8 @@ negative:
|
|||
origen: Origen
|
||||
value: Negativo
|
||||
warehouseFk: Almacen
|
||||
producer: Producer
|
||||
producer: Productor
|
||||
excludedDates: Fechas excluidas
|
||||
category: Categoría
|
||||
categoryFk: Familia
|
||||
typeFk: Familia
|
||||
|
|
|
@ -18,6 +18,7 @@ const invoiceInCard = {
|
|||
'InvoiceInIntrastat',
|
||||
'InvoiceInCorrective',
|
||||
'InvoiceInLog',
|
||||
'InvoiceInVehicle',
|
||||
],
|
||||
},
|
||||
children: [
|
||||
|
@ -75,6 +76,15 @@ const invoiceInCard = {
|
|||
},
|
||||
component: () => import('src/pages/InvoiceIn/Card/InvoiceInCorrective.vue'),
|
||||
},
|
||||
{
|
||||
name: 'InvoiceInVehicle',
|
||||
path: 'vehicle',
|
||||
meta: {
|
||||
title: 'vehicle',
|
||||
icon: 'directions_car',
|
||||
},
|
||||
component: () => import('src/pages/InvoiceIn/Card/InvoiceInVehicle.vue'),
|
||||
},
|
||||
{
|
||||
name: 'InvoiceInLog',
|
||||
path: 'log',
|
||||
|
|
|
@ -20,8 +20,8 @@ const itemCard = {
|
|||
},
|
||||
children: [
|
||||
{
|
||||
name: 'ItemSummary',
|
||||
path: 'summary',
|
||||
name: 'ItemSummary',
|
||||
meta: {
|
||||
title: 'summary',
|
||||
icon: 'launch',
|
||||
|
@ -189,6 +189,7 @@ export default {
|
|||
title: 'list',
|
||||
icon: 'view_list',
|
||||
},
|
||||
component: () => import('src/pages/Item/ItemList.vue'),
|
||||
},
|
||||
itemCard,
|
||||
],
|
||||
|
@ -202,19 +203,6 @@ export default {
|
|||
},
|
||||
component: () => import('src/pages/Item/ItemRequest.vue'),
|
||||
},
|
||||
{
|
||||
path: 'waste-breakdown',
|
||||
name: 'WasteBreakdown',
|
||||
meta: {
|
||||
title: 'wasteBreakdown',
|
||||
icon: 'vn:claims',
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
next({ name: 'ItemList' });
|
||||
window.location.href =
|
||||
'https://grafana.verdnatura.es/d/TTNXQAxVk';
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'fixed-price',
|
||||
name: 'ItemFixedPrice',
|
||||
|
@ -228,6 +216,11 @@ export default {
|
|||
path: 'item-type',
|
||||
name: 'ItemTypeMain',
|
||||
redirect: { name: 'ItemTypeList' },
|
||||
meta: {
|
||||
title: 'itemType',
|
||||
icon: 'family_restroom',
|
||||
moduleName: 'itemType',
|
||||
},
|
||||
component: () => import('src/pages/Item/ItemTypeList.vue'),
|
||||
children: [
|
||||
{
|
||||
|
@ -235,8 +228,9 @@ export default {
|
|||
path: 'list',
|
||||
meta: {
|
||||
title: 'family',
|
||||
icon: 'contact_support',
|
||||
icon: 'family_restroom',
|
||||
},
|
||||
component: () => import('src/pages/Item/ItemTypeList.vue'),
|
||||
},
|
||||
itemTypeCard,
|
||||
],
|
||||
|
|
|
@ -119,44 +119,45 @@ const agencyCard = {
|
|||
],
|
||||
};
|
||||
|
||||
const roadmapCard = {
|
||||
path: ':id',
|
||||
name: 'RoadmapCard',
|
||||
component: () => import('src/pages/Route/Roadmap/RoadmapCard.vue'),
|
||||
redirect: { name: 'RoadmapSummary' },
|
||||
meta: {
|
||||
menu: ['RoadmapBasicData', 'RoadmapStops'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'RoadmapSummary',
|
||||
path: 'summary',
|
||||
meta: {
|
||||
title: 'summary',
|
||||
icon: 'open_in_new',
|
||||
},
|
||||
component: () => import('pages/Route/Roadmap/RoadmapSummary.vue'),
|
||||
},
|
||||
{
|
||||
name: 'RoadmapBasicData',
|
||||
path: 'basic-data',
|
||||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
},
|
||||
component: () => import('pages/Route/Roadmap/RoadmapBasicData.vue'),
|
||||
},
|
||||
{
|
||||
name: 'RoadmapStops',
|
||||
path: 'stops',
|
||||
meta: {
|
||||
title: 'stops',
|
||||
icon: 'vn:lines',
|
||||
},
|
||||
component: () => import('pages/Route/Roadmap/RoadmapStops.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
// Waiting for the roadmap to be implemented refs #8227
|
||||
// const roadmapCard = {
|
||||
// path: ':id',
|
||||
// name: 'RoadmapCard',
|
||||
// component: () => import('src/pages/Route/Roadmap/RoadmapCard.vue'),
|
||||
// redirect: { name: 'RoadmapSummary' },
|
||||
// meta: {
|
||||
// menu: ['RoadmapBasicData', 'RoadmapStops'],
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// name: 'RoadmapSummary',
|
||||
// path: 'summary',
|
||||
// meta: {
|
||||
// title: 'summary',
|
||||
// icon: 'open_in_new',
|
||||
// },
|
||||
// component: () => import('pages/Route/Roadmap/RoadmapSummary.vue'),
|
||||
// },
|
||||
// {
|
||||
// name: 'RoadmapBasicData',
|
||||
// path: 'basic-data',
|
||||
// meta: {
|
||||
// title: 'basicData',
|
||||
// icon: 'vn:settings',
|
||||
// },
|
||||
// component: () => import('pages/Route/Roadmap/RoadmapBasicData.vue'),
|
||||
// },
|
||||
// {
|
||||
// name: 'RoadmapStops',
|
||||
// path: 'stops',
|
||||
// meta: {
|
||||
// title: 'stops',
|
||||
// icon: 'vn:lines',
|
||||
// },
|
||||
// component: () => import('pages/Route/Roadmap/RoadmapStops.vue'),
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
const vehicleCard = {
|
||||
path: ':id',
|
||||
|
@ -241,7 +242,7 @@ export default {
|
|||
'RouteList',
|
||||
'RouteExtendedList',
|
||||
'RouteAutonomous',
|
||||
'RouteRoadmap',
|
||||
// 'RouteRoadmap', Waiting for the roadmap to be implemented refs #8227
|
||||
'CmrList',
|
||||
'AgencyList',
|
||||
'VehicleList',
|
||||
|
@ -301,28 +302,29 @@ export default {
|
|||
},
|
||||
component: () => import('src/pages/Route/RouteAutonomous.vue'),
|
||||
},
|
||||
{
|
||||
path: 'roadmap',
|
||||
name: 'RouteRoadmap',
|
||||
redirect: { name: 'RoadmapList' },
|
||||
component: () => import('src/pages/Route/RouteRoadmap.vue'),
|
||||
meta: {
|
||||
title: 'RouteRoadmap',
|
||||
icon: 'vn:troncales',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'RoadmapList',
|
||||
path: 'list',
|
||||
meta: {
|
||||
title: 'list',
|
||||
icon: 'view_list',
|
||||
},
|
||||
component: () => import('src/pages/Route/RouteRoadmap.vue'),
|
||||
},
|
||||
roadmapCard,
|
||||
],
|
||||
},
|
||||
// Waiting for the roadmap to be implemented refs #8227
|
||||
// {
|
||||
// path: 'roadmap',
|
||||
// name: 'RouteRoadmap',
|
||||
// redirect: { name: 'RoadmapList' },
|
||||
// component: () => import('src/pages/Route/RouteRoadmap.vue'),
|
||||
// meta: {
|
||||
// title: 'RouteRoadmap',
|
||||
// icon: 'vn:troncales',
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// name: 'RoadmapList',
|
||||
// path: 'list',
|
||||
// meta: {
|
||||
// title: 'list',
|
||||
// icon: 'view_list',
|
||||
// },
|
||||
// component: () => import('src/pages/Route/RouteRoadmap.vue'),
|
||||
// },
|
||||
// roadmapCard,
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: 'cmr',
|
||||
name: 'CmrList',
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('ClaimDevelopment', { testIsolation: true }, () => {
|
|||
cy.saveCard();
|
||||
});
|
||||
|
||||
it('should add and remove new line', () => {
|
||||
it.skip('should add and remove new line', () => {
|
||||
cy.addCard();
|
||||
|
||||
cy.waitForElement(thirdRow);
|
||||
|
|
|
@ -5,8 +5,8 @@ describe('EntryList', () => {
|
|||
cy.login('buyer');
|
||||
cy.visit(`/#/entry/list`);
|
||||
});
|
||||
|
||||
it('View popup summary', () => {
|
||||
// fix on task https://redmine.verdnatura.es/issues/8638
|
||||
it.skip('View popup summary', () => {
|
||||
cy.createEntry();
|
||||
cy.get('.q-notification__message').eq(0).should('have.text', 'Data created');
|
||||
cy.waitForElement('[data-cy="entry-buys"]');
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('InvoiceInBasicData', { testIsolation: true }, () => {
|
|||
cy.validateForm(mock, { attr: 'data-cy' });
|
||||
});
|
||||
|
||||
it('should edit, remove and create the dms data', () => {
|
||||
xit('should edit, remove and create the dms data', () => {
|
||||
const firtsInput = 'Ticket:65';
|
||||
const secondInput = "I don't know what posting here!";
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('InvoiceInDueDay', () => {
|
|||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||
});
|
||||
|
||||
it('should remove the first line', () => {
|
||||
xit('should remove the first line', () => {
|
||||
cy.removeRow(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('InvoiceInList', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should open the details', () => {
|
||||
xit('should open the details', () => {
|
||||
cy.get('[data-col-field="id"]').then(($cells) => {
|
||||
const exactMatch = [...$cells].find(
|
||||
(cell) => cell.textContent.trim() === invoiceId,
|
||||
|
|
|
@ -1,7 +1,100 @@
|
|||
describe('InvoiceInSummary', () => {
|
||||
const url = '/#/invoice-in/1/summary';
|
||||
|
||||
const selectors = {
|
||||
supplierLink: '[data-cy="invoiceInSummary_supplier"]',
|
||||
vehicleLink: '[data-cy="invoiceInSummary_vehicle"]',
|
||||
descriptorOpenSummaryBtn: '.q-menu .descriptor [data-cy="openSummaryBtn"]',
|
||||
descriptorGoToSummaryBtn: '.q-menu .descriptor [data-cy="goToSummaryBtn"]',
|
||||
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
|
||||
supplierDescriptorTitle: '[data-cy="vnDescriptor_description"]',
|
||||
vehicleDescriptorTitle: '[data-cy="vnDescriptor_title"]',
|
||||
};
|
||||
|
||||
const sectionSelectors = {
|
||||
basicDataIcon: 'InvoiceInBasicData-menu-item',
|
||||
vatIcon: 'InvoiceInVat-menu-item',
|
||||
dueDayIcon: 'InvoiceInDueDay-menu-item',
|
||||
intrastatIcon: 'InvoiceInIntrastat-menu-item',
|
||||
vehicleIcon: 'InvoiceInVehicle-menu-item',
|
||||
logIcon: 'InvoiceInLog-menu-item',
|
||||
};
|
||||
|
||||
const titlesLinks = {
|
||||
basicData: '[data-cy="basicDataTitleLink"].link',
|
||||
vat: '[data-cy="vatTitleLink"].link',
|
||||
dueDay: '[data-cy="dueDayTitleLink"].link',
|
||||
intrastat: '[data-cy="intrastatTitleLink"].link',
|
||||
vehicle: '[data-cy="vehicleTitleLink"].link',
|
||||
};
|
||||
|
||||
const basePath = 'invoice-in/1/';
|
||||
const sectionRegex = {
|
||||
basicData: new RegExp(`${basePath}basic-data`),
|
||||
vat: new RegExp(`${basePath}vat`),
|
||||
dueDay: new RegExp(`${basePath}due-day`),
|
||||
intrastat: new RegExp(`${basePath}intrastat`),
|
||||
vehicle: new RegExp(`${basePath}vehicle`),
|
||||
log: new RegExp(`${basePath}log`),
|
||||
};
|
||||
|
||||
const supplierSummaryUrlRegex = /supplier\/\d+\/summary/;
|
||||
const vehicleSummaryUrlRegex = /vehicle\/\d+\/summary/;
|
||||
beforeEach(() => {
|
||||
cy.login('administrative');
|
||||
cy.visit('/#/invoice-in/3/summary');
|
||||
cy.visit(url);
|
||||
});
|
||||
|
||||
it('Should redirect to the corresponding section when clicking on the icons in the left menu', () => {
|
||||
cy.dataCy(sectionSelectors.basicDataIcon).click();
|
||||
cy.location().should('match', sectionRegex.basicData);
|
||||
|
||||
cy.visit(url);
|
||||
cy.dataCy(sectionSelectors.vatIcon).click();
|
||||
cy.location().should('match', sectionRegex.vat);
|
||||
|
||||
cy.visit(url);
|
||||
cy.dataCy(sectionSelectors.dueDayIcon).click();
|
||||
cy.location().should('match', sectionRegex.dueDay);
|
||||
|
||||
cy.visit(url);
|
||||
cy.dataCy(sectionSelectors.intrastatIcon).click();
|
||||
cy.location().should('match', sectionRegex.intrastat);
|
||||
|
||||
cy.visit(url);
|
||||
cy.dataCy(sectionSelectors.vehicleIcon).click();
|
||||
cy.location().should('match', sectionRegex.vehicle);
|
||||
|
||||
cy.visit(url);
|
||||
cy.dataCy(sectionSelectors.logIcon).click();
|
||||
cy.location().should('match', sectionRegex.log);
|
||||
});
|
||||
|
||||
describe('Title links redirections', () => {
|
||||
it('Should redirect to invoiceIn basic-data when clicking on basic-data title link', () => {
|
||||
cy.get(titlesLinks.basicData).click();
|
||||
cy.location().should('match', sectionRegex.basicData);
|
||||
});
|
||||
|
||||
it('Should redirect to invoiceIn vat when clicking on vat title link', () => {
|
||||
cy.get(titlesLinks.vat).click();
|
||||
cy.location().should('match', sectionRegex.vat);
|
||||
});
|
||||
|
||||
it('Should redirect to invoiceIn due-day when clicking on due-day title link', () => {
|
||||
cy.get(titlesLinks.dueDay).click();
|
||||
cy.location().should('match', sectionRegex.dueDay);
|
||||
});
|
||||
|
||||
it('Should redirect to invoiceIn intrastat when clicking on intrastat title link', () => {
|
||||
cy.get(titlesLinks.intrastat).click();
|
||||
cy.location().should('match', sectionRegex.intrastat);
|
||||
});
|
||||
|
||||
it('Should redirect to invoiceIn vehicle when clicking on vehicle title link', () => {
|
||||
cy.get(titlesLinks.vehicle).click();
|
||||
cy.location().should('match', sectionRegex.vehicle);
|
||||
});
|
||||
});
|
||||
|
||||
it('should booking and unbooking the invoice properly', () => {
|
||||
|
@ -9,16 +102,53 @@ describe('InvoiceInSummary', () => {
|
|||
cy.dataCy('invoiceInSummary_book').click();
|
||||
cy.dataCy('VnConfirm_confirm').click();
|
||||
cy.validateCheckbox(checkbox);
|
||||
cy.selectDescriptorOption();
|
||||
cy.dataCy('VnConfirm_confirm').click();
|
||||
});
|
||||
|
||||
it('should open the supplier descriptor popup', () => {
|
||||
cy.intercept('GET', /Suppliers\/\d+/).as('getSupplier');
|
||||
cy.dataCy('invoiceInSummary_supplier').then(($el) => {
|
||||
const description = $el.text().trim();
|
||||
$el.click();
|
||||
cy.wait('@getSupplier').then(() =>
|
||||
cy.validateDescriptor({ description, popup: true }),
|
||||
);
|
||||
describe('Supplier pop-ups', () => {
|
||||
it('Should redirect to the supplier summary from the supplier descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.supplierLink,
|
||||
steps: [selectors.descriptorGoToSummaryBtn],
|
||||
expectedUrlRegex: supplierSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.supplierDescriptorTitle,
|
||||
});
|
||||
});
|
||||
|
||||
it('Should redirect to the supplier summary from summary pop-up from the supplier descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.supplierLink,
|
||||
steps: [
|
||||
selectors.descriptorOpenSummaryBtn,
|
||||
selectors.summaryGoToSummaryBtn,
|
||||
],
|
||||
expectedUrlRegex: supplierSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.supplierDescriptorTitle,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vehicle pop-ups', () => {
|
||||
it('Should redirect to the vehicle summary from the vehicle descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.vehicleLink,
|
||||
steps: [selectors.descriptorGoToSummaryBtn],
|
||||
expectedUrlRegex: vehicleSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.vehicleDescriptorTitle,
|
||||
});
|
||||
});
|
||||
|
||||
it('Should redirect to the vehicle summary from summary pop-up from the vehicle descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.vehicleLink,
|
||||
steps: [
|
||||
selectors.descriptorOpenSummaryBtn,
|
||||
selectors.summaryGoToSummaryBtn,
|
||||
],
|
||||
expectedUrlRegex: vehicleSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.vehicleDescriptorTitle,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
describe('InvoiceInVehicle', () => {
|
||||
const selectors = {
|
||||
tableActionUnlink: 'tableAction-0',
|
||||
firstVehicleLink:
|
||||
'tr:first-child > [data-col-field="vehicleFk"] > .no-padding > .link',
|
||||
descriptorOpenSummaryBtn: '.q-menu .descriptor [data-cy="openSummaryBtn"]',
|
||||
descriptorGoToSummaryBtn: '.q-menu .descriptor [data-cy="goToSummaryBtn"]',
|
||||
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
|
||||
descriptorTitle: '[data-cy="vnDescriptor_title"]',
|
||||
};
|
||||
const vehicleSummaryUrlRegex = /vehicle\/\d+\/summary/;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login('administrative');
|
||||
cy.visit(`/#/invoice-in/1/vehicle`);
|
||||
});
|
||||
|
||||
it('should link and unlink vehicle to the invoice', () => {
|
||||
const data = {
|
||||
Vehicle: { val: '2222-IMK', type: 'select' },
|
||||
Amount: { val: 125 },
|
||||
};
|
||||
cy.addBtnClick();
|
||||
cy.fillInForm(data);
|
||||
cy.dataCy('FormModelPopup_save').click();
|
||||
cy.checkNotification('Data created');
|
||||
cy.dataCy(selectors.tableActionUnlink).last().click();
|
||||
cy.clickConfirm();
|
||||
cy.checkNotification('Unlinked vehicle');
|
||||
});
|
||||
|
||||
describe('Vehicle pop-ups', () => {
|
||||
xit('Should redirect to the vehicle summary from the vehicle descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.firstVehicleLink,
|
||||
steps: [selectors.descriptorGoToSummaryBtn],
|
||||
expectedUrlRegex: vehicleSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.descriptorTitle,
|
||||
});
|
||||
});
|
||||
|
||||
xit('Should redirect to the vehicle summary from summary pop-up from the vehicle descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.firstVehicleLink,
|
||||
steps: [
|
||||
selectors.descriptorOpenSummaryBtn,
|
||||
selectors.summaryGoToSummaryBtn,
|
||||
],
|
||||
expectedUrlRegex: vehicleSummaryUrlRegex,
|
||||
expectedTextSelector: selectors.descriptorTitle,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
describe('Item list', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('buyer');
|
||||
cy.visit(`/#/item/1/basic-data`);
|
||||
});
|
||||
|
||||
const mock = {
|
||||
itemBasicDataItemType: { val: 'Container', type: 'select' },
|
||||
itemBasicDataReference: '1',
|
||||
itemBasicDataRelevancy: '1',
|
||||
itemBasicDataStems: '1',
|
||||
itemBasicDataMultiplier: '2',
|
||||
itemBasicDataGeneric: { val: 'Pallet', type: 'select' },
|
||||
};
|
||||
|
||||
it('should edit every field', () => {
|
||||
cy.fillInForm(mock, { attr: 'data-cy' });
|
||||
cy.saveCard();
|
||||
cy.validateForm(mock, { attr: 'data-cy' });
|
||||
});
|
||||
});
|
|
@ -6,19 +6,16 @@ describe('Item summary', { testIsolation: true }, () => {
|
|||
});
|
||||
|
||||
it('should clone the item', () => {
|
||||
cy.dataCy('descriptor-more-opts').click();
|
||||
cy.get('.q-menu > .q-list > :nth-child(2) > .q-item__section').click();
|
||||
cy.selectDescriptorOption(2);
|
||||
cy.dataCy('VnConfirm_confirm').click();
|
||||
cy.waitForElement('[data-cy="itemTags"]');
|
||||
cy.dataCy('itemTags').should('be.visible');
|
||||
});
|
||||
|
||||
it('should regularize stock', () => {
|
||||
cy.dataCy('descriptor-more-opts').click();
|
||||
cy.get('.q-menu > .q-list > :nth-child(1) > .q-item__section').click();
|
||||
cy.selectDescriptorOption();
|
||||
cy.dataCy('regularizeStockInput').type('10');
|
||||
cy.dataCy('Warehouse_select').type('Warehouse One{enter}');
|
||||
cy.dataCy('FormModelPopup_save').click();
|
||||
cy.checkNotification('Data created');
|
||||
});
|
||||
});
|
|
@ -1,22 +1,21 @@
|
|||
/// <reference types="cypress" />
|
||||
|
||||
describe('Item list', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.login('buyer');
|
||||
cy.visit(`/#/item/list`);
|
||||
cy.typeSearchbar('{enter}');
|
||||
});
|
||||
|
||||
it('should filter the items and redirect to the summary', () => {
|
||||
cy.dataCy('Category_select').type('Plant');
|
||||
cy.get('.q-menu .q-item').contains('Plant').click();
|
||||
cy.dataCy('Type_select').type('Anthurium');
|
||||
cy.get('.q-menu .q-item').contains('Anthurium').click();
|
||||
cy.get('.q-virtual-scroll__content > :nth-child(4) > :nth-child(4)').click();
|
||||
cy.selectOption('[data-cy="Category_select"]', 'Plant');
|
||||
cy.selectOption('[data-cy="Type_select"]', 'Anthurium');
|
||||
cy.get('td[data-row-index="0"][data-col-field="description"]')
|
||||
.should('exist')
|
||||
.click();
|
||||
cy.url().should('include', '/summary');
|
||||
});
|
||||
|
||||
it('should create an item', () => {
|
||||
const data = {
|
||||
'Provisional name': { val: `Test item` },
|
||||
Description: { val: `Test item` },
|
||||
Type: { val: `Crisantemo`, type: 'select' },
|
||||
Intrastat: { val: `Coral y materiales similares`, type: 'select' },
|
||||
|
@ -26,8 +25,6 @@ describe('Item list', () => {
|
|||
cy.fillInForm(data);
|
||||
cy.dataCy('FormModelPopup_save').click();
|
||||
cy.checkNotification('Data created');
|
||||
cy.get(
|
||||
':nth-child(2) > .q-drawer > .q-drawer__content > .q-scrollarea > .q-scrollarea__container > .q-scrollarea__content',
|
||||
).should('be.visible');
|
||||
cy.url().should('include', '/basic-data');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
describe('Item Request', () => {
|
||||
const rowIndex = 0;
|
||||
const itemFkField = `td[data-row-index="${rowIndex}"][data-col-field="itemFk"]`;
|
||||
const saleQuantityField = `td[data-row-index="${rowIndex}"][data-col-field="saleQuantity"]`;
|
||||
const ticketInputFilter = '[data-cy="Ticket id_input"]';
|
||||
|
||||
before(() => {
|
||||
cy.login('buyer');
|
||||
cy.visit(`/#/item/request`);
|
||||
});
|
||||
|
||||
it('should fill the id and quantity then check the concept was updated', () => {
|
||||
cy.waitForElement('tbody');
|
||||
cy.get(ticketInputFilter).should('exist').type('38{enter}');
|
||||
cy.get(itemFkField).should('exist').click();
|
||||
cy.get(`${itemFkField} input`).should('exist').type('4');
|
||||
cy.get(saleQuantityField).should('exist').click();
|
||||
cy.get(`${saleQuantityField} input`).should('exist').type('10{esc}');
|
||||
cy.checkNotification('Data saved');
|
||||
});
|
||||
|
||||
it('should now click on the second deny request icon then type the reason', () => {
|
||||
cy.selectOption('[data-cy="State_select"]', 'Pending');
|
||||
cy.get('button[title="Discard"]').eq(0).click();
|
||||
cy.dataCy('discardTextArea').should('exist').type('test{enter}');
|
||||
cy.checkNotification('Data saved');
|
||||
});
|
||||
});
|
|
@ -1,23 +1,21 @@
|
|||
/// <reference types="cypress" />
|
||||
describe('Item type', () => {
|
||||
describe('Item type', { testIsolation: true }, () => {
|
||||
const workerError = 'employeeNick';
|
||||
const worker = 'buyerNick';
|
||||
const category = 'Artificial';
|
||||
const type = 'Flower';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.login('buyer');
|
||||
cy.visit(`/#/item/item-type`);
|
||||
});
|
||||
|
||||
it('should throw an error if the code already exists', () => {
|
||||
cy.dataCy('vnTableCreateBtn').click();
|
||||
cy.dataCy('vnTableCreateBtn').should('exist').click();
|
||||
cy.dataCy('codeInput').type('ALS');
|
||||
cy.dataCy('nameInput').type('Alstroemeria');
|
||||
cy.dataCy('vnWorkerSelect').type(workerError);
|
||||
cy.get('.q-menu .q-item').contains(workerError).click();
|
||||
cy.dataCy('itemCategorySelect').type(category);
|
||||
cy.get('.q-menu .q-item').contains(category).click();
|
||||
cy.selectOption('[data-cy="vnWorkerSelect"]', workerError);
|
||||
cy.selectOption('[data-cy="itemCategorySelect"]', category);
|
||||
cy.dataCy('FormModelPopup_save').click();
|
||||
cy.checkNotification('An item type with the same code already exists');
|
||||
});
|
||||
|
@ -26,10 +24,8 @@ describe('Item type', () => {
|
|||
cy.dataCy('vnTableCreateBtn').click();
|
||||
cy.dataCy('codeInput').type('LIL');
|
||||
cy.dataCy('nameInput').type('Lilium');
|
||||
cy.dataCy('vnWorkerSelect').type(worker);
|
||||
cy.get('.q-menu .q-item').contains(worker).click();
|
||||
cy.dataCy('itemCategorySelect').type(type);
|
||||
cy.get('.q-menu .q-item').contains(type).click();
|
||||
cy.selectOption('[data-cy="vnWorkerSelect"]', worker);
|
||||
cy.selectOption('[data-cy="itemCategorySelect"]', type);
|
||||
cy.dataCy('FormModelPopup_save').click();
|
||||
cy.checkNotification('Data created');
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ describe('OrderCatalog', { testIsolation: true }, () => {
|
|||
).type('{enter}');
|
||||
cy.get(':nth-child(1) > [data-cy="catalogFilterCategory"]').click();
|
||||
cy.dataCy('catalogFilterValueDialogBtn').last().click();
|
||||
cy.selectOption("[data-cy='catalogFilterValueDialogTagSelect']", 'Tallos');
|
||||
cy.selectOption('[data-cy="catalogFilterValueDialogTagSelect"]', 'Tallos');
|
||||
cy.dataCy('catalogFilterValueDialogValueInput').find('input').focus();
|
||||
cy.dataCy('catalogFilterValueDialogValueInput').find('input').type('2');
|
||||
cy.dataCy('catalogFilterValueDialogValueInput').find('input').type('{enter}');
|
||||
|
|
|
@ -133,7 +133,7 @@ describe('Vehicle DMS', { testIsolation: true }, () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('Worker pop-ups', () => {
|
||||
describe.skip('Worker pop-ups', () => {
|
||||
it('Should redirect to worker summary from worker descriptor pop-up', () => {
|
||||
cy.checkRedirectionFromPopUp({
|
||||
selectorToClick: selectors.firstRowWorkerLink,
|
||||
|
|
|
@ -1,146 +1,161 @@
|
|||
/// <reference types="cypress" />
|
||||
describe.skip('Ticket Lack detail', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.intercept('GET', /\/api\/Tickets\/itemLack\/5.*$/, {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{
|
||||
saleFk: 33,
|
||||
code: 'OK',
|
||||
ticketFk: 142,
|
||||
nickname: 'Malibu Point',
|
||||
shipped: '2000-12-31T23:00:00.000Z',
|
||||
hour: 0,
|
||||
quantity: 50,
|
||||
agName: 'Super-Man delivery',
|
||||
alertLevel: 0,
|
||||
stateName: 'OK',
|
||||
stateId: 3,
|
||||
itemFk: 5,
|
||||
price: 1.79,
|
||||
alertLevelCode: 'FREE',
|
||||
zoneFk: 9,
|
||||
zoneName: 'Zone superMan',
|
||||
theoreticalhour: '2011-11-01T22:59:00.000Z',
|
||||
isRookie: 1,
|
||||
turno: 1,
|
||||
peticionCompra: 1,
|
||||
hasObservation: 1,
|
||||
hasToIgnore: 1,
|
||||
isBasket: 1,
|
||||
minTimed: 0,
|
||||
customerId: 1104,
|
||||
customerName: 'Tony Stark',
|
||||
observationTypeCode: 'administrative',
|
||||
},
|
||||
],
|
||||
}).as('getItemLack');
|
||||
|
||||
cy.visit('/#/ticket/negative/5', false);
|
||||
cy.wait('@getItemLack');
|
||||
const firstRow = 'tr.cursor-pointer > :nth-child(1)';
|
||||
const ticketId = 1000000;
|
||||
const clickNotificationAction = () => {
|
||||
const notification = '.q-notification';
|
||||
cy.waitForElement(notification);
|
||||
cy.get(notification).should('be.visible');
|
||||
cy.get('.q-notification__actions > .q-btn').click();
|
||||
cy.get('@open').should((openStub) => {
|
||||
expect(openStub).to.be.called;
|
||||
const firstArg = openStub.args[0][0];
|
||||
expect(firstArg).to.match(/\/ticket\/\d+\/sale/);
|
||||
expect(firstArg).to.include(`/ticket/${ticketId}/sale`);
|
||||
});
|
||||
describe('Table actions', () => {
|
||||
it('should display only one row in the lack list', () => {
|
||||
cy.location('href').should('contain', '#/ticket/negative/5');
|
||||
|
||||
cy.get('[data-cy="changeItem"]').should('be.disabled');
|
||||
cy.get('[data-cy="changeState"]').should('be.disabled');
|
||||
cy.get('[data-cy="changeQuantity"]').should('be.disabled');
|
||||
cy.get('[data-cy="itemProposal"]').should('be.disabled');
|
||||
cy.get('[data-cy="transferLines"]').should('be.disabled');
|
||||
cy.get('tr.cursor-pointer > :nth-child(1)').click();
|
||||
cy.get('[data-cy="changeItem"]').should('be.enabled');
|
||||
cy.get('[data-cy="changeState"]').should('be.enabled');
|
||||
cy.get('[data-cy="changeQuantity"]').should('be.enabled');
|
||||
cy.get('[data-cy="itemProposal"]').should('be.enabled');
|
||||
cy.get('[data-cy="transferLines"]').should('be.enabled');
|
||||
};
|
||||
describe('Ticket Lack detail', { testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1980, 1020);
|
||||
cy.login('developer');
|
||||
cy.intercept('GET', /\/api\/Tickets\/itemLack\/88.*$/).as('getItemLack');
|
||||
cy.visit('/#/ticket/negative/88');
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').as('open');
|
||||
});
|
||||
cy.wait('@getItemLack').then((interception) => {
|
||||
const { query } = interception.request;
|
||||
const filter = JSON.parse(query.filter);
|
||||
expect(filter).to.have.property('where');
|
||||
expect(filter.where).to.have.property('alertLevelCode', 'FREE');
|
||||
});
|
||||
});
|
||||
describe('Table detail', () => {
|
||||
it('should open descriptors', () => {
|
||||
cy.get('.q-table').should('be.visible');
|
||||
cy.colField('zoneName').click();
|
||||
cy.dataCy('ZoneDescriptor').should('be.visible');
|
||||
cy.get('.q-item > .q-item__label').should('have.text', ' #1');
|
||||
cy.colField('ticketFk').click();
|
||||
cy.dataCy('TicketDescriptor').should('be.visible');
|
||||
cy.get('.q-item > .q-item__label').should('have.text', ` #${ticketId}`);
|
||||
cy.colField('nickname').find('.link').click();
|
||||
cy.waitForElement('[data-cy="CustomerDescriptor"]');
|
||||
cy.dataCy('CustomerDescriptor').should('be.visible');
|
||||
cy.get('.q-item > .q-item__label').should('have.text', ' #1');
|
||||
});
|
||||
it('should display only one row in the lack list', () => {
|
||||
cy.dataCy('changeItem').should('be.disabled');
|
||||
cy.dataCy('changeState').should('be.disabled');
|
||||
cy.dataCy('changeQuantity').should('be.disabled');
|
||||
cy.dataCy('itemProposal').should('be.disabled');
|
||||
cy.dataCy('transferLines').should('be.disabled');
|
||||
cy.get('tr.cursor-pointer > :nth-child(1)').click();
|
||||
cy.dataCy('changeItem').should('be.enabled');
|
||||
cy.dataCy('changeState').should('be.enabled');
|
||||
cy.dataCy('changeQuantity').should('be.enabled');
|
||||
cy.dataCy('itemProposal').should('be.enabled');
|
||||
cy.dataCy('transferLines').should('be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Split', () => {
|
||||
beforeEach(() => {
|
||||
cy.get(firstRow).click();
|
||||
cy.dataCy('transferLines').click();
|
||||
});
|
||||
it('Split', () => {
|
||||
cy.dataCy('ticketTransferPopup').find('.flex > .q-btn').click();
|
||||
|
||||
cy.checkNotification(`Ticket ${ticketId}: No split`);
|
||||
});
|
||||
});
|
||||
describe('change quantity', () => {
|
||||
beforeEach(() => {
|
||||
cy.get(firstRow).click();
|
||||
|
||||
cy.dataCy('changeQuantity').click();
|
||||
});
|
||||
|
||||
it('by popup', () => {
|
||||
cy.dataCy('New quantity_input').type(10);
|
||||
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
|
||||
|
||||
clickNotificationAction();
|
||||
});
|
||||
});
|
||||
describe('Change state', () => {
|
||||
beforeEach(() => {
|
||||
cy.get(firstRow).click();
|
||||
cy.dataCy('changeState').click();
|
||||
});
|
||||
it('by popup', () => {
|
||||
cy.dataCy('New state_select').should('be.visible');
|
||||
cy.selectOption('[data-cy="New state_select"]', 'OK');
|
||||
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
|
||||
|
||||
clickNotificationAction();
|
||||
});
|
||||
});
|
||||
describe('Change Item', () => {
|
||||
beforeEach(() => {
|
||||
cy.get(firstRow).click();
|
||||
cy.dataCy('changeItem').click();
|
||||
});
|
||||
it('by popup', () => {
|
||||
cy.dataCy('New item_select').should('be.visible');
|
||||
cy.selectOption('[data-cy="New item_select"]', 'Palito rojo');
|
||||
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
|
||||
cy.checkNotification('Ticket 1000000: price retrieval failed');
|
||||
cy.dataCy('changeItem').click();
|
||||
cy.selectOption('[data-cy="New item_select"]', 'Ranged weapon longbow 200cm');
|
||||
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
|
||||
|
||||
clickNotificationAction();
|
||||
});
|
||||
after(() => {
|
||||
cy.visit(`/#/ticket/${ticketId}/sale`);
|
||||
const quantity = Math.floor(Math.random() * 100) + 1;
|
||||
const rowIndex = 1;
|
||||
|
||||
cy.dataCy('ticketSaleQuantityInput')
|
||||
.find('input')
|
||||
.eq(rowIndex)
|
||||
.clear()
|
||||
.type(`${quantity}{enter}`);
|
||||
|
||||
cy.dataCy('ticketSaleQuantityInput')
|
||||
.find('input')
|
||||
.eq(rowIndex)
|
||||
.should('have.value', `${quantity}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Item proposal', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('tr.cursor-pointer > :nth-child(1)').click();
|
||||
|
||||
cy.intercept('GET', /\/api\/Items\/getSimilar\?.*$/, {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{
|
||||
id: 1,
|
||||
longName: 'Ranged weapon longbow 50cm',
|
||||
subName: 'Stark Industries',
|
||||
tag5: 'Color',
|
||||
value5: 'Brown',
|
||||
match5: 0,
|
||||
match6: 0,
|
||||
match7: 0,
|
||||
match8: 1,
|
||||
tag6: 'Categoria',
|
||||
value6: '+1 precission',
|
||||
tag7: 'Tallos',
|
||||
value7: '1',
|
||||
tag8: null,
|
||||
value8: null,
|
||||
available: 20,
|
||||
calc_id: 6,
|
||||
counter: 0,
|
||||
minQuantity: 1,
|
||||
visible: null,
|
||||
price2: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
longName: 'Ranged weapon longbow 100cm',
|
||||
subName: 'Stark Industries',
|
||||
tag5: 'Color',
|
||||
value5: 'Brown',
|
||||
match5: 0,
|
||||
match6: 1,
|
||||
match7: 0,
|
||||
match8: 1,
|
||||
tag6: 'Categoria',
|
||||
value6: '+1 precission',
|
||||
tag7: 'Tallos',
|
||||
value7: '1',
|
||||
tag8: null,
|
||||
value8: null,
|
||||
available: 50,
|
||||
calc_id: 6,
|
||||
counter: 1,
|
||||
minQuantity: 5,
|
||||
visible: null,
|
||||
price2: 10,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
longName: 'Ranged weapon longbow 200cm',
|
||||
subName: 'Stark Industries',
|
||||
tag5: 'Color',
|
||||
value5: 'Brown',
|
||||
match5: 1,
|
||||
match6: 1,
|
||||
match7: 1,
|
||||
match8: 1,
|
||||
tag6: 'Categoria',
|
||||
value6: '+1 precission',
|
||||
tag7: 'Tallos',
|
||||
value7: '1',
|
||||
tag8: null,
|
||||
value8: null,
|
||||
available: 185,
|
||||
calc_id: 6,
|
||||
counter: 10,
|
||||
minQuantity: 10,
|
||||
visible: null,
|
||||
price2: 100,
|
||||
},
|
||||
],
|
||||
}).as('getItemGetSimilar');
|
||||
cy.get('[data-cy="itemProposal"]').click();
|
||||
cy.wait('@getItemGetSimilar');
|
||||
cy.get(firstRow).click();
|
||||
cy.dataCy('itemProposal').click();
|
||||
});
|
||||
describe.skip('Replace item if', () => {
|
||||
it('Quantity is less than available', () => {
|
||||
cy.get(':nth-child(1) > .text-right > .q-btn').click();
|
||||
describe('Replace item if', () => {
|
||||
xit('Quantity is less than available', () => {
|
||||
const index = 2;
|
||||
cy.colField('tag7', index).click();
|
||||
cy.checkNotification('Not available for replacement');
|
||||
});
|
||||
|
||||
xit('item proposal cells', () => {
|
||||
const index = 1;
|
||||
cy.colField('longName', index)
|
||||
.find('.no-padding > .q-td > .middle')
|
||||
.should('have.class', 'proposal-primary');
|
||||
cy.colField('tag5', index)
|
||||
.find('.no-padding > .match')
|
||||
.should('have.class', 'match');
|
||||
cy.colField('tag6', index)
|
||||
.find('.no-padding > .match')
|
||||
.should('have.class', 'match');
|
||||
cy.colField('tag7', index).click();
|
||||
|
||||
clickNotificationAction();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,34 +1,16 @@
|
|||
/// <reference types="cypress" />
|
||||
describe('Ticket Lack list', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.intercept('GET', /Tickets\/itemLack\?.*$/, {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{
|
||||
itemFk: 5,
|
||||
longName: 'Ranged weapon pistol 9mm',
|
||||
warehouseFk: 1,
|
||||
producer: null,
|
||||
size: 15,
|
||||
category: null,
|
||||
warehouse: 'Warehouse One',
|
||||
lack: -50,
|
||||
inkFk: 'SLV',
|
||||
timed: '2025-01-25T22:59:00.000Z',
|
||||
minTimed: '23:59',
|
||||
originFk: 'Holand',
|
||||
},
|
||||
],
|
||||
}).as('getLack');
|
||||
cy.viewport(1980, 1020);
|
||||
|
||||
cy.login('developer');
|
||||
cy.visit('/#/ticket/negative');
|
||||
});
|
||||
|
||||
describe('Table actions', () => {
|
||||
it('should display only one row in the lack list', () => {
|
||||
cy.wait('@getLack', { timeout: 10000 });
|
||||
|
||||
cy.get('[data-col-field="longName"]').first().click();
|
||||
cy.dataCy('ItemDescriptor').should('be.visible');
|
||||
cy.get('.q-virtual-scroll__content > :nth-child(1) > .sticky').click();
|
||||
cy.location('href').should('contain', '#/ticket/negative/5');
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ describe('TicketBasicData', () => {
|
|||
cy.visit('/#/ticket/31/basic-data');
|
||||
});
|
||||
|
||||
it('Should redirect to customer basic data', () => {
|
||||
it.skip('Should redirect to customer basic data', () => {
|
||||
cy.get('.q-page').should('be.visible');
|
||||
cy.get(':nth-child(2) > div > .text-primary').click();
|
||||
cy.dataCy('Address_select').click();
|
||||
|
@ -16,7 +16,7 @@ describe('TicketBasicData', () => {
|
|||
).click();
|
||||
cy.url().should('include', '/customer/1104/basic-data');
|
||||
});
|
||||
it.only('stepper', () => {
|
||||
it('stepper', () => {
|
||||
cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active');
|
||||
|
||||
cy.get('.q-stepper__nav > .q-btn--standard').click();
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('TicketSale', { testIsolation: true }, () => {
|
|||
cy.intercept('POST', /\/api\/Sales\/\d+\/updatePrice/).as('updatePrice');
|
||||
|
||||
cy.dataCy('saveManaBtn').click();
|
||||
handleVnConfirm();
|
||||
cy.handleVnConfirm();
|
||||
cy.wait('@updatePrice').its('response.statusCode').should('eq', 200);
|
||||
|
||||
cy.get('[data-col-field="price"]')
|
||||
|
@ -43,7 +43,7 @@ describe('TicketSale', { testIsolation: true }, () => {
|
|||
);
|
||||
|
||||
cy.dataCy('saveManaBtn').click();
|
||||
handleVnConfirm();
|
||||
cy.handleVnConfirm();
|
||||
cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204);
|
||||
|
||||
cy.get('[data-col-field="discount"]')
|
||||
|
@ -61,23 +61,19 @@ describe('TicketSale', { testIsolation: true }, () => {
|
|||
.find('[data-cy="undefined_input"]')
|
||||
.type(concept)
|
||||
.type('{enter}');
|
||||
handleVnConfirm();
|
||||
cy.handleVnConfirm();
|
||||
|
||||
cy.get('[data-col-field="item"]').should('contain.text', `${concept}`);
|
||||
});
|
||||
it('change quantity ', () => {
|
||||
xit('change quantity ', () => {
|
||||
const quantity = Math.floor(Math.random() * 100) + 1;
|
||||
cy.waitForElement(firstRow);
|
||||
cy.dataCy('ticketSaleQuantityInput').find('input').clear();
|
||||
cy.intercept('POST', '**/api').as('postRequest');
|
||||
|
||||
cy.dataCy('ticketSaleQuantityInput')
|
||||
.find('input')
|
||||
.type(quantity)
|
||||
.trigger('tab');
|
||||
cy.get('.q-page > :nth-child(6)').click();
|
||||
cy.dataCy('ticketSaleQuantityInput').find('input').type(`${quantity}{enter}`);
|
||||
|
||||
handleVnConfirm();
|
||||
cy.handleVnConfirm();
|
||||
|
||||
cy.get('[data-cy="ticketSaleQuantityInput"]')
|
||||
.find('input')
|
||||
|
@ -210,8 +206,3 @@ function selectFirstRow() {
|
|||
cy.waitForElement(firstRow);
|
||||
cy.get(firstRow).find('.q-checkbox__inner').click();
|
||||
}
|
||||
function handleVnConfirm() {
|
||||
cy.confirmVnConfirm();
|
||||
|
||||
cy.checkNotification('Data saved');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="cypress" />
|
||||
// https://redmine.verdnatura.es/issues/8848
|
||||
describe.skip('VnShortcuts', () => {
|
||||
describe('VnShortcuts', () => {
|
||||
const modules = {
|
||||
item: 'a',
|
||||
customer: 'c',
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
Cypress.Commands.add('handleVnConfirm', () => {
|
||||
cy.confirmVnConfirm();
|
||||
|
||||
cy.checkNotification('Data saved');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('confirmVnConfirm', () =>
|
||||
cy.dataCy('VnConfirm_confirm').should('exist').click(),
|
||||
);
|
||||
|
|
|
@ -18,3 +18,37 @@ Cypress.Commands.add('tableActions', (n = 0, child = 1) =>
|
|||
`:nth-child(${child}) > .q-table--col-auto-width > [data-cy="tableAction-${n}"] > .q-btn__content > .q-icon`,
|
||||
),
|
||||
);
|
||||
|
||||
Cypress.Commands.add('validateVnTableRows', (opts = {}) => {
|
||||
let { cols = [] } = opts;
|
||||
const { rows = [] } = opts;
|
||||
if (!Array.isArray(cols)) cols = [cols];
|
||||
const rowSelector = rows.length
|
||||
? rows.map((row) => `> :nth-child(${row})`).join(', ')
|
||||
: '> *';
|
||||
cy.get(`[data-cy="vnTable"] .q-virtual-scroll__content`).within(() => {
|
||||
cy.get(`${rowSelector}`).each(($el) => {
|
||||
for (const { name, type = 'string', val, operation = 'equal' } of cols) {
|
||||
cy.wrap($el)
|
||||
.find(`[data-cy="vnTableCell_${name}"]`)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
if (type === 'string')
|
||||
expect(text.trim().toLowerCase()).to[operation](
|
||||
val.toLowerCase(),
|
||||
);
|
||||
if (type === 'number') cy.checkNumber(text, val, operation);
|
||||
if (type === 'date') cy.checkDate(text, val, operation);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('colField', (name, index = null, key = 'data-col-field') => {
|
||||
if (index) {
|
||||
cy.get(`:nth-child(${index}) > [${key}="${name}"]`);
|
||||
} else {
|
||||
cy.get(`[${key}="${name}"]`);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue