8627-devToTest #1421

Merged
alexm merged 768 commits from 8627-devToTest into test 2025-02-18 12:37:37 +00:00
27 changed files with 1165 additions and 750 deletions
Showing only changes of commit f33c4d42bf - Show all commits

View File

@ -51,7 +51,5 @@ export default boot(({ app }) => {
await useCau(response, message); await useCau(response, message);
}; };
app.directive('shortcut', keyShortcut); app.provide('app', app);
app.mixin(qFormMixin);
app.mixin(mainShortcutMixin);
}); });

View File

@ -1,31 +0,0 @@
<script setup>
import { computed } from 'vue';
const props = defineProps({
colors: {
type: Array,
required: true,
validator: (value) => value.length <= 3,
},
});
</script>
<template>
<div class="color-div">
<div
v-for="(color, index) in colors"
:key="index"
:style="{
backgroundColor: color,
height: '10px',
}"
>
&nbsp;
</div>
</div>
</template>
<style scoped>
.color-div {
display: flex;
flex-direction: column;
}
</style>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { markRaw, computed } from 'vue'; import { markRaw, computed } from 'vue';
import { QIcon, QCheckbox } from 'quasar'; import { QIcon, QCheckbox, QToggle } from 'quasar';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
/* basic input */ /* basic input */
@ -48,6 +48,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: null, default: null,
}, },
eventHandlers: {
type: Object,
default: null,
},
}); });
const defaultSelect = { const defaultSelect = {
@ -141,6 +145,9 @@ const defaultComponents = {
userLink: { userLink: {
component: markRaw(VnUserLink), component: markRaw(VnUserLink),
}, },
toggle: {
component: markRaw(QToggle),
},
}; };
const value = computed(() => { const value = computed(() => {
@ -187,6 +194,7 @@ const components = computed(() => {
...(component.attrs || {}), ...(component.attrs || {}),
autofocus: $props.autofocus, autofocus: $props.autofocus,
}, },
event: { ...component?.event, ...$props?.eventHandlers },
}; };
return acc; return acc;
}, {}); }, {});
@ -200,7 +208,6 @@ const components = computed(() => {
:components="components" :components="components"
:value="{ row, model }" :value="{ row, model }"
v-model="model" v-model="model"
@blur="emit('blur')"
/> />
<VnComponent <VnComponent
v-if="col.component" v-if="col.component"
@ -208,7 +215,6 @@ const components = computed(() => {
:components="components" :components="components"
:value="{ row, model }" :value="{ row, model }"
v-model="model" v-model="model"
@blur="emit('blur')"
/> />
<span :title="value" v-else>{{ value }}</span> <span :title="value" v-else>{{ value }}</span>
<VnComponent <VnComponent
@ -217,7 +223,7 @@ const components = computed(() => {
:components="components" :components="components"
:value="{ row, model }" :value="{ row, model }"
v-model="model" v-model="model"
@blur="emit('blur')"
/> />
<slot name="append" />
</div> </div>
</template> </template>

View File

@ -58,7 +58,7 @@ const selectComponent = {
component: markRaw(VnSelect), component: markRaw(VnSelect),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: 'q-px-sm q-pb-xs q-pt-none fit', class: 'q-pt-none fit test',
dense: true, dense: true,
filled: !$props.showTitle, filled: !$props.showTitle,
}, },
@ -120,6 +120,7 @@ const components = {
}; };
async function addFilter(value, name) { async function addFilter(value, name) {
console.log('test');
value ??= undefined; value ??= undefined;
if (value && typeof value === 'object') value = model.value; if (value && typeof value === 'object') value = model.value;
value = value === '' ? undefined : value; value = value === '' ? undefined : value;
@ -168,3 +169,12 @@ const onTabPressed = async () => {
/> />
</div> </div>
</template> </template>
<style lang="scss">
/* label.test {
padding-bottom: 0px !important;
background-color: red;
} */
label.test > .q-field__inner > .q-field__control {
padding: inherit;
}
</style>

View File

@ -1,23 +1,36 @@
<script setup> <script setup>
import { ref, onBeforeMount, onMounted, computed, watch, useAttrs, nextTick } from 'vue'; import {
ref,
onBeforeMount,
onMounted,
computed,
watch,
h,
render,
inject,
useAttrs,
} from 'vue';
import { useArrayData } from 'src/composables/useArrayData';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useFilterParams } from 'src/composables/useFilterParams'; import { useFilterParams } from 'src/composables/useFilterParams';
import { dashIfEmpty } from 'src/filters';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import FormModelPopup from 'components/FormModelPopup.vue'; import FormModelPopup from 'components/FormModelPopup.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnColumn from 'components/VnTable/VnColumn.vue';
import VnFilter from 'components/VnTable/VnFilter.vue'; import VnFilter from 'components/VnTable/VnFilter.vue';
import VnTableChip from 'components/VnTable/VnChip.vue'; import VnTableChip from 'components/VnTable/VnChip.vue';
import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
import VnTableFilter from './VnTableFilter.vue'; import VnTableFilter from './VnTableFilter.vue';
import { dashIfEmpty } from 'src/filters';
const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
columns: { columns: {
type: Array, type: Array,
@ -115,6 +128,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
withFilters: {
type: Boolean,
default: true,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -136,7 +153,12 @@ const createForm = ref();
const tableRef = ref(); const tableRef = ref();
const params = ref(useFilterParams($attrs['data-key']).params); const params = ref(useFilterParams($attrs['data-key']).params);
const orders = ref(useFilterParams($attrs['data-key']).orders); const orders = ref(useFilterParams($attrs['data-key']).orders);
const app = inject('app');
const editingRow = ref(null);
const editingField = ref(null);
const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
const tableModes = [ const tableModes = [
{ {
icon: 'view_column', icon: 'view_column',
@ -156,7 +178,6 @@ onBeforeMount(() => {
const urlParams = route.query[$props.searchUrl]; const urlParams = route.query[$props.searchUrl];
hasParams.value = urlParams && Object.keys(urlParams).length !== 0; hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
}); });
onMounted(() => { onMounted(() => {
mode.value = mode.value =
quasar.platform.is.mobile && !$props.disableOption?.card quasar.platform.is.mobile && !$props.disableOption?.card
@ -185,9 +206,6 @@ watch(
{ immediate: true } { immediate: true }
); );
const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
function splitColumns(columns) { function splitColumns(columns) {
splittedColumns.value = { splittedColumns.value = {
columns: [], columns: [],
@ -306,99 +324,210 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
} }
} }
const editingRow = ref(null); function isEditableColumn(column) {
const editingField = ref(null); const isEditableCol = column?.isEditable ?? true;
const isVisible = column?.visible ?? true;
const hasComponent = column?.component;
const handleClick = (event) => { return $props.isEditable && isVisible && hasComponent && isEditableCol;
}
function hasEditableFormat(column) {
if (isEditableColumn(column)) return 'editable-text';
}
const handleClick = async (event) => {
const clickedElement = event.target.closest('td'); const clickedElement = event.target.closest('td');
if (!clickedElement) return; if (!clickedElement) return;
const rowIndex = clickedElement.getAttribute('data-row-index'); const rowIndex = clickedElement.getAttribute('data-row-index');
console.log('HandleRowIndex: ', rowIndex);
const colField = clickedElement.getAttribute('data-col-field'); const colField = clickedElement.getAttribute('data-col-field');
console.log('HandleColField: ', colField);
if (rowIndex !== null && colField) { if (rowIndex !== null && colField) {
startEditing(Number(rowIndex), colField); console.log('handleClick STARTEDEDITING');
} const column = $props.columns.find((col) => col.name === colField);
}; console.log('isEditableColumn(column): ', isEditableColumn(column));
const vnEditableCell = ref(null); if (!isEditableColumn(column)) return;
const startEditing = async (rowId, field) => { await startEditing(Number(rowIndex), colField, clickedElement);
const col = $props.columns.find((col) => col.name === field); if (column.component !== 'checkbox') console.log();
if (col?.isEditable === false) return;
editingRow.value = rowId;
editingField.value = field;
if (col.component === 'checkbox') {
await nextTick();
const inputElement = vnEditableCell.value?.$el?.querySelector('span > div');
inputElement.focus();
} }
}; };
const stopEditing = (rowIndex, field) => { async function startEditing(rowId, field, clickedElement) {
console.log('startEditing: ', field);
if (rowId === editingRow.value && field === editingField.value) return;
editingRow.value = rowId;
editingField.value = field;
const column = $props.columns.find((col) => col.name === field);
console.log('LaVerdaderacolumn: ', column);
const row = CrudModelRef.value.formData[rowId];
const oldValue = CrudModelRef.value.formData[rowId][column?.name];
console.log('changes: ', CrudModelRef.value.getChanges());
if (!clickedElement)
clickedElement = document.querySelector(
`[data-row-index="${rowId}"][data-col-field="${field}"]`
);
Array.from(clickedElement.childNodes).forEach((child) => {
child.style.visibility = 'hidden';
child.style.position = 'absolute';
});
console.log('row[column.name]: ', row[column.name]);
const node = h(VnColumn, {
row: row,
column: column,
modelValue: row[column.name],
componentProp: 'columnField',
autofocus: true,
focusOnMount: true,
eventHandlers: {
'update:modelValue': (value) => {
console.log('update:modelValue: ', value);
row[column.name] = value;
column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
},
onMouseDown: (event) => {
console.log('mouseDown: ', field);
if (column.component === 'checkbox') event.stopPropagation();
},
blur: () => {
/* const focusElement = document.activeElement;
const rowIndex = focusElement.getAttribute('data-row-index');
const colField = focusElement.getAttribute('data-col-field');
console.log('rowIndex: ', rowIndex);
console.log('colField: ', colField);
console.log('editingField.value: ', editingField.value);
console.log('editingRow.value: ', editingRow.value);
handleBlur(rowId, field, clickedElement);
column?.cellEvent?.blur?.(row); */
},
keyup: async (event) => {
console.log('keyup: ', field);
if (event.key === 'Enter') handleBlur(rowId, field, clickedElement);
},
keydown: async (event) => {
switch (event.key) {
case 'Tab':
console.log('TabTest: ', field);
await handleTabKey(event, rowId, field);
event.stopPropagation();
if (column.component === 'checkbox')
handleBlur(rowId, field, clickedElement);
break;
case 'Escape':
console.log('Escape: ', field);
stopEditing(rowId, field, clickedElement);
break;
default:
break;
}
},
click: (event) => {
/* event.stopPropagation();
console.log('click: ', field);
if (column.component === 'checkbox') {
const allowNull = column?.toggleIndeterminate ?? true;
const currentValue = row[column.name];
let newValue;
if (allowNull) {
if (currentValue === null) {
newValue = true;
} else if (currentValue) {
newValue = false;
} else {
newValue = null;
}
} else {
newValue = !currentValue;
}
row[column.name] = newValue;
column?.cellEvent?.['update:modelValue']?.(newValue, row);
}
column?.cellEvent?.['click']?.(event, row); */
},
},
});
node.appContext = app._context;
render(node, clickedElement);
if (column.component === 'checkbox') node.el?.querySelector('span > div').focus();
}
function stopEditing(rowIndex, field, clickedElement) {
console.log('stopEditing: ', field);
if (clickedElement) {
render(null, clickedElement);
Array.from(clickedElement.childNodes).forEach((child) => {
child.style.visibility = 'visible';
child.style.position = '';
});
}
if (editingRow.value !== rowIndex || editingField.value !== field) return; if (editingRow.value !== rowIndex || editingField.value !== field) return;
editingRow.value = null; editingRow.value = null;
editingField.value = null; editingField.value = null;
}; }
const handleTab = async (rowIndex, colName) => { function handleBlur(rowIndex, field, clickedElement) {
console.log('handleBlur: ', field);
stopEditing(rowIndex, field, clickedElement);
}
async function handleTabNavigation(rowIndex, colName, direction) {
const columns = $props.columns; const columns = $props.columns;
const totalColumns = columns.length;
let currentColumnIndex = columns.findIndex((col) => col.name === colName); let currentColumnIndex = columns.findIndex((col) => col.name === colName);
let newColumnIndex = currentColumnIndex + 1;
while (
columns[newColumnIndex]?.visible === false ||
columns[newColumnIndex]?.isEditable === false ||
!columns[newColumnIndex]?.component
) {
newColumnIndex++;
if (newColumnIndex >= columns.length) newColumnIndex = 0;
}
if (currentColumnIndex >= newColumnIndex) rowIndex++; let iterations = 0;
let newColumnIndex = currentColumnIndex;
await startEditing(rowIndex, columns[newColumnIndex].name); do {
}; iterations++;
newColumnIndex = (newColumnIndex + direction + totalColumns) % totalColumns;
const handleShiftTab = async (rowIndex, colName) => { if (isEditableColumn(columns[newColumnIndex])) break;
console.log('handleShiftTab: '); } while (iterations < totalColumns);
const columns = $props.columns;
const currentColumnIndex = columns.findIndex((col) => col.name === colName);
if (currentColumnIndex === -1) return; if (iterations >= totalColumns) {
console.warn('No editable columns found.');
let prevColumnIndex = currentColumnIndex - 1;
let prevRowIndex = rowIndex;
while (prevColumnIndex >= 0 && columns[prevColumnIndex]?.isEditable === false) {
prevColumnIndex--;
}
if (prevColumnIndex < 0) {
prevColumnIndex = columns.length - 1;
prevRowIndex -= 1;
while (prevRowIndex >= 0 && columns[prevColumnIndex]?.isEditable === false) {
prevColumnIndex--;
if (prevColumnIndex < 0) {
prevColumnIndex = columns.length - 1;
prevRowIndex--;
}
}
}
if (prevRowIndex < 0) {
stopEditing(rowIndex, colName);
return; return;
} }
await startEditing(prevRowIndex, columns[prevColumnIndex]?.name); if (direction === 1 && newColumnIndex <= currentColumnIndex) {
console.log('finishHandleShiftTab'); rowIndex++;
}; } else if (direction === -1 && newColumnIndex >= currentColumnIndex) {
rowIndex--;
}
console.log('next: ', columns[newColumnIndex].name, 'rowIndex: ', rowIndex);
return { nextRowIndex: rowIndex, nextColumnName: columns[newColumnIndex].name };
}
async function handleTabKey(event, rowIndex, colName) {
const direction = event.shiftKey ? -1 : 1;
const { nextRowIndex, nextColumnName } = await handleTabNavigation(
rowIndex,
colName,
direction
);
if (nextRowIndex < 0 || nextRowIndex >= arrayData.store.data.length) return;
event.preventDefault();
await startEditing(nextRowIndex, nextColumnName, null);
}
const handleTabKey = async (event, rowIndex, colName) => {
if (event.shiftKey) await handleShiftTab(rowIndex, colName);
else await handleTab(rowIndex, colName);
};
function getCheckboxIcon(value) { function getCheckboxIcon(value) {
switch (typeof value) { switch (typeof value) {
case 'boolean': case 'boolean':
@ -408,25 +537,35 @@ function getCheckboxIcon(value) {
? 'indeterminate_check_box' ? 'indeterminate_check_box'
: 'unknown_med'; : 'unknown_med';
case 'number': case 'number':
return value > 0 ? 'check' : 'close'; return value === 0 ? 'close' : 'check';
case 'object': case 'object':
return value === null ? 'help_outline' : 'unknown_med'; return value === null ? 'help_outline' : 'unknown_med';
case 'undefined': case 'undefined':
return 'help_outline'; return 'help_outline';
default: default:
return 'unknown_med'; return 'indeterminate_check_box';
} }
} }
function shouldDisplayReadonly(col, rowIndex) { /* function getCheckboxIcon(value) {
return ( switch (typeof value) {
!col?.component || case 'boolean':
editingRow.value !== rowIndex || return value ? 'check_box' : 'check_box_outline_blank';
editingField.value !== col?.name case 'string':
); return value.toLowerCase() === 'partial'
} ? 'indeterminate_check_box'
: 'unknown_med';
case 'number':
return value === 0 ? 'check_box_outline_blank' : 'check_box';
case 'object':
return value === null ? 'help_outline' : 'unknown_med';
case 'undefined':
return 'help_outline';
default:
return 'indeterminate_check_box';
}
} */
</script> </script>
<template> <template>
<QDrawer <QDrawer
v-if="$props.rightSearch" v-if="$props.rightSearch"
@ -477,7 +616,7 @@ function shouldDisplayReadonly(col, rowIndex) {
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)" @row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)" @update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
@click="handleClick" v-on="isEditable ? { click: handleClick } : {}"
> >
<template #top-left v-if="!$props.withoutHeader"> <template #top-left v-if="!$props.withoutHeader">
<slot name="top-left"> </slot> <slot name="top-left"> </slot>
@ -496,13 +635,6 @@ function shouldDisplayReadonly(col, rowIndex) {
dense dense
:options="tableModes.filter((mode) => !mode.disable)" :options="tableModes.filter((mode) => !mode.disable)"
/> />
<QBtn
v-if="showRightIcon"
icon="filter_alt"
class="bg-vn-section-color q-ml-sm"
dense
@click="stateStore.toggleRightDrawer()"
/>
</template> </template>
<template #header-cell="{ col }"> <template #header-cell="{ col }">
<QTh <QTh
@ -512,7 +644,9 @@ function shouldDisplayReadonly(col, rowIndex) {
> >
<div <div
class="no-padding" class="no-padding"
:style="$props.columnSearch ? 'height: 75px' : ''" :style="
withFilters && $props.columnSearch ? 'height: 75px' : ''
"
> >
<div class="text-center" style="height: 30px"> <div class="text-center" style="height: 30px">
<QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip> <QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip>
@ -525,13 +659,17 @@ function shouldDisplayReadonly(col, rowIndex) {
/> />
</div> </div>
<VnFilter <VnFilter
v-if="$props.columnSearch && col.columnSearch !== false" v-if="
$props.columnSearch &&
col.columnSearch !== false &&
withFilters
"
:column="col" :column="col"
:show-title="true" :show-title="true"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
v-model="params[columnName(col)]" v-model="params[columnName(col)]"
:search-url="searchUrl" :search-url="searchUrl"
class="full-width" class="full-width test"
/> />
</div> </div>
</QTh> </QTh>
@ -550,7 +688,6 @@ function shouldDisplayReadonly(col, rowIndex) {
</template> </template>
<template #body-cell="{ col, row, rowIndex }"> <template #body-cell="{ col, row, rowIndex }">
<QTd <QTd
auto-width
class="no-margin q-px-xs" class="no-margin q-px-xs"
v-if="col.visible ?? true" v-if="col.visible ?? true"
:style="{ :style="{
@ -566,7 +703,6 @@ function shouldDisplayReadonly(col, rowIndex) {
:data-col-field="col?.name" :data-col-field="col?.name"
> >
<div <div
v-if="shouldDisplayReadonly(col, rowIndex)"
class="no-padding no-margin" class="no-padding no-margin"
style=" style="
overflow: hidden; overflow: hidden;
@ -584,18 +720,12 @@ function shouldDisplayReadonly(col, rowIndex) {
v-if="col?.component === 'checkbox'" v-if="col?.component === 'checkbox'"
:name="getCheckboxIcon(row[col?.name])" :name="getCheckboxIcon(row[col?.name])"
style="color: var(--vn-text-color)" style="color: var(--vn-text-color)"
size="var(--font-size)" :class="hasEditableFormat(col)"
:class=" size="17px"
isEditable &&
(col?.component ? 'editable-text' : '')
"
/> />
<span <span
v-else v-else
:class=" :class="hasEditableFormat(col)"
isEditable &&
(col?.component ? 'editable-text' : '')
"
:style="col?.style ? col.style(row) : null" :style="col?.style ? col.style(row) : null"
style="bottom: 0" style="bottom: 0"
> >
@ -607,27 +737,6 @@ function shouldDisplayReadonly(col, rowIndex) {
</span> </span>
</slot> </slot>
</div> </div>
<div v-else>
<VnTableColumn
ref="vnEditableCell"
:column="col"
:row="row"
:is-editable="col.isEditable ?? isEditable"
v-model="row[col.name]"
component-prop="columnField"
class="cell-input q-px-xs"
@blur="stopEditing(rowIndex, col?.name)"
@keyup.enter="stopEditing(rowIndex, col?.name)"
@keydown.tab.prevent="
handleTabKey($event, rowIndex, col?.name)
"
@keydown.shift.tab.prevent="
handleShiftTab(rowIndex, col?.name)
"
@keydown.escape="stopEditing(rowIndex, col?.name)"
:autofocus="true"
/>
</div>
</QTd> </QTd>
</template> </template>
<template #body-cell-tableActions="{ col, row }"> <template #body-cell-tableActions="{ col, row }">
@ -751,7 +860,7 @@ function shouldDisplayReadonly(col, rowIndex) {
:row="row" :row="row"
:row-index="index" :row-index="index"
> >
<VnTableColumn <VnColumn
:column="col" :column="col"
:row="row" :row="row"
:is-editable="false" :is-editable="false"
@ -792,7 +901,8 @@ function shouldDisplayReadonly(col, rowIndex) {
</component> </component>
</template> </template>
<template #bottom-row="{ cols }" v-if="$props.footer"> <template #bottom-row="{ cols }" v-if="$props.footer">
<QTr v-if="rows.length" style="height: 30px"> <QTr v-if="rows.length" style="height: 45px">
<QTh v-if="table.selection" />
<QTh <QTh
v-for="col of cols.filter((cols) => cols.visible ?? true)" v-for="col of cols.filter((cols) => cols.visible ?? true)"
:key="col?.id" :key="col?.id"
@ -838,7 +948,7 @@ function shouldDisplayReadonly(col, rowIndex) {
:column-name="column.name" :column-name="column.name"
:label="column.label" :label="column.label"
> >
<VnTableColumn <VnColumn
:column="column" :column="column"
:row="{}" :row="{}"
default="input" default="input"

View File

@ -0,0 +1,32 @@
<script setup>
const $props = defineProps({
colors: {
type: String,
default: '{"value":[]}',
},
});
const colorArray = JSON.parse($props.colors)?.value;
const maxHeight = 30;
const colorHeight = maxHeight / colorArray?.length;
</script>
<template>
<div class="color-div" :style="{ height: `${maxHeight}px` }">
<div
v-for="(color, index) in colorArray"
:key="index"
:style="{
backgroundColor: `#${color}`,
height: `${colorHeight}px`,
}"
>
&nbsp;
</div>
</div>
</template>
<style scoped>
.color-div {
display: flex;
flex-direction: column;
}
</style>

View File

@ -45,6 +45,7 @@ function toValueAttrs(attrs) {
} }
</script> </script>
<template> <template>
<slot name="test" />
<span <span
v-for="toComponent of componentArray" v-for="toComponent of componentArray"
:key="toComponent.name" :key="toComponent.name"
@ -57,6 +58,7 @@ function toValueAttrs(attrs) {
v-on="mix(toComponent).event ?? {}" v-on="mix(toComponent).event ?? {}"
v-model="model" v-model="model"
@blur="emit('blur')" @blur="emit('blur')"
@mouse-down="() => console.log('mouse-down')"
/> />
</span> </span>
</template> </template>

View File

@ -70,10 +70,6 @@ const focus = () => {
vnInputRef.value.focus(); vnInputRef.value.focus();
}; };
defineExpose({
focus,
});
const mixinRules = [ const mixinRules = [
requiredFieldRule, requiredFieldRule,
...($attrs.rules ?? []), ...($attrs.rules ?? []),

View File

@ -6,7 +6,7 @@ import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder'; import dataByOrder from 'src/utils/dataByOrder';
import { QItemLabel } from 'quasar'; import { QItemLabel } from 'quasar';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove', 'blur']); const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const $attrs = useAttrs(); const $attrs = useAttrs();
const { t } = useI18n(); const { t } = useI18n();
@ -327,7 +327,6 @@ function handleKeyDown(event) {
:option-value="optionValue" :option-value="optionValue"
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
@filter="filterHandler" @filter="filterHandler"
@blur="() => emit('blur')"
:emit-value="nullishToTrue($attrs['emit-value'])" :emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])" :map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])" :use-input="nullishToTrue($attrs['use-input'])"

View File

@ -34,7 +34,6 @@ const isAllowedToCreate = computed(() => {
return role.hasAny($props.rolesAllowedToCreate); return role.hasAny($props.rolesAllowedToCreate);
}); });
</script> </script>
<template> <template>
<VnSelect <VnSelect
v-model="value" v-model="value"
@ -63,7 +62,6 @@ const isAllowedToCreate = computed(() => {
</template> </template>
</VnSelect> </VnSelect>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.default-icon { .default-icon {
cursor: pointer; cursor: pointer;

View File

@ -76,7 +76,6 @@ a {
text-decoration: underline; text-decoration: underline;
} }
// Removes chrome autofill background
input:-webkit-autofill, input:-webkit-autofill,
select:-webkit-autofill { select:-webkit-autofill {
color: var(--vn-text-color); color: var(--vn-text-color);

View File

@ -392,16 +392,26 @@ entry:
list: list:
newEntry: New entry newEntry: New entry
tableVisibleColumns: tableVisibleColumns:
created: Creation isExcludedFromAvailable: Exclude from inventory
supplierFk: Supplier
isBooked: Booked
isConfirmed: Confirmed
isOrdered: Ordered isOrdered: Ordered
isConfirmed: Ready to label
isReceived: Received
isRaid: Raid
landed: Date
supplierFk: Supplier
reference: Ref/Alb/Guide
invoiceNumber: Invoice
agencyModeId: Agency
isBooked: Booked
companyFk: Company companyFk: Company
travelFk: Travel evaNotes: Notes
isExcludedFromAvailable: Inventory warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeDescription: Entry type
invoiceAmount: Import invoiceAmount: Import
travelFk: Travel
summary: summary:
invoiceAmount: Amount
commission: Commission commission: Commission
currency: Currency currency: Currency
invoiceNumber: Invoice number invoiceNumber: Invoice number
@ -454,7 +464,10 @@ entry:
ektFk: Ekt ektFk: Ekt
packingOut: Package out packingOut: Package out
landing: Landing landing: Landing
isExcludedFromAvailable: Es inventory isExcludedFromAvailable: Exclude from inventory
isRaid: Raid
invoiceNumber: Invoice
reference: Ref/Alb/Guide
ticket: ticket:
card: card:
customerId: Customer ID customerId: Customer ID

View File

@ -392,16 +392,26 @@ entry:
list: list:
newEntry: Nueva entrada newEntry: Nueva entrada
tableVisibleColumns: tableVisibleColumns:
created: Creación isExcludedFromAvailable: Excluir del inventario
supplierFk: Proveedor
isBooked: Asentado
isConfirmed: Confirmado
isOrdered: Pedida 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 companyFk: Empresa
travelFk: Envio travelFk: Envio
isExcludedFromAvailable: Inventario evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeDescription: Tipo entrada
invoiceAmount: Importe invoiceAmount: Importe
summary: summary:
invoiceAmount: Importe
commission: Comisión commission: Comisión
currency: Moneda currency: Moneda
invoiceNumber: Núm. factura invoiceNumber: Núm. factura
@ -455,7 +465,10 @@ entry:
ektFk: Ekt ektFk: Ekt
packingOut: Embalaje envíos packingOut: Embalaje envíos
landing: Llegada landing: Llegada
isExcludedFromAvailable: Es inventario isExcludedFromAvailable: Excluir del inventario
isRaid: Redada
invoiceNumber: Nº Factura
reference: Ref/Alb/Guía
ticket: ticket:
card: card:
customerId: ID cliente customerId: ID cliente

View File

@ -100,17 +100,6 @@ const columns = computed(() => [
columnFilter: { columnFilter: {
component: 'number', component: 'number',
}, },
columnField: {
component: null,
after: {
component: markRaw(VnLinkPhone),
attrs: ({ model }) => {
return {
'phone-number': model,
};
},
},
},
}, },
{ {
align: 'left', align: 'left',

View File

@ -247,8 +247,14 @@ function handleLocation(data, location) {
:label="t('Longitude')" :label="t('Longitude')"
clearable clearable
v-model="data.longitude" v-model="data.longitude"
:decimal-places="6"
/>
<VnInputNumber
:label="t('Latitude')"
clearable
v-model="data.latitude"
:decimal-places="6"
/> />
<VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" />
</VnRow> </VnRow>
<h4 class="q-mb-xs">{{ t('Notes') }}</h4> <h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow <VnRow

View File

@ -1,68 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Checkbox Focus with Button</title>
<style>
body {
font-family: Arial, sans-serif;
}
.checkbox-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
input[type='checkbox'] {
width: 20px;
height: 20px;
}
label {
margin-left: 10px;
}
/* Estilos para el foco */
input[type='checkbox']:focus {
outline: 2px solid blue;
outline-offset: 2px;
}
</style>
</head>
<body>
<div class="checkbox-container">
<input type="checkbox" id="myCheckbox" />
<label for="myCheckbox">Acepto los términos y condiciones</label>
</div>
<!-- Botón para enfocar el checkbox -->
<button id="focusButton">Dar foco al checkbox</button>
<script>
const checkbox = document.getElementById('myCheckbox');
const focusButton = document.getElementById('focusButton');
// Manejador de eventos para cuando el checkbox recibe el foco
checkbox.addEventListener('focus', () => {
console.log('El checkbox tiene el foco');
});
// Manejador de eventos para cuando el checkbox pierde el foco
checkbox.addEventListener('blur', () => {
console.log('El checkbox perdió el foco');
});
// Manejador de eventos para cuando se cambia el estado del checkbox
checkbox.addEventListener('change', (event) => {
if (event.target.checked) {
console.log('El checkbox está marcado');
} else {
console.log('El checkbox no está marcado');
}
});
// Dar foco al checkbox cuando se presiona el botón
focusButton.addEventListener('click', () => {
checkbox.focus();
});
</script>
</body>
</html>

View File

@ -9,6 +9,7 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FilterTravelForm from 'src/components/FilterTravelForm.vue'; import FilterTravelForm from 'src/components/FilterTravelForm.vue';
@ -51,28 +52,6 @@ const onFilterTravelSelected = (formData, id) => {
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<VnSelect
:label="t('globals.supplier')"
v-model="data.supplierFk"
url="Suppliers"
option-value="id"
option-label="nickname"
:fields="['id', 'nickname']"
hide-selected
:required="true"
map-options
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}, {{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelectDialog <VnSelectDialog
:label="t('entry.basicData.travel')" :label="t('entry.basicData.travel')"
v-model="data.travelFk" v-model="data.travelFk"
@ -105,9 +84,36 @@ const onFilterTravelSelected = (formData, id) => {
</QItem> </QItem>
</template> </template>
</VnSelectDialog> </VnSelectDialog>
<VnSelect
:label="t('globals.supplier')"
v-model="data.supplierFk"
url="Suppliers"
option-value="id"
option-label="nickname"
:fields="['id', 'nickname']"
hide-selected
:required="true"
map-options
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}, {{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput v-model="data.reference" :label="t('globals.reference')" /> <VnInput v-model="data.reference" :label="t('globals.reference')" />
<VnInputNumber
v-model="data.invoiceAmount"
:label="t('entry.summary.invoiceAmount')"
:positive="false"
/>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput

View File

@ -2,21 +2,36 @@
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted, ref } from 'vue'; import { h, onMounted, ref } from 'vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import FetchData from 'src/components/FetchData.vue';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnColor from 'src/components/VnColor.vue'; import VnColor from 'src/components/common/VnColor.vue';
import { QCheckbox } from 'quasar';
const $props = defineProps({
id: {
type: Number,
default: null,
},
editableMode: {
type: Boolean,
default: true,
},
});
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const selectedRows = ref([]); const selectedRows = ref([]);
const entityId = ref($props.id ?? route.params.id);
console.log('entityId: ', entityId.value);
const footer = ref({});
const columns = [ const columns = [
{ {
name: 'buyFk', name: 'id',
isId: true, isId: true,
visible: false, visible: false,
isEditable: false, isEditable: false,
@ -26,6 +41,7 @@ const columns = [
label: 'Nv', label: 'Nv',
name: 'isIgnored', name: 'isIgnored',
component: 'checkbox', component: 'checkbox',
toggleIndeterminate: false,
width: '35px', width: '35px',
}, },
{ {
@ -33,6 +49,7 @@ const columns = [
label: 'Id', label: 'Id',
name: 'itemFk', name: 'itemFk',
component: 'input', component: 'input',
isEditable: false,
create: true, create: true,
width: '45px', width: '45px',
}, },
@ -52,7 +69,8 @@ const columns = [
}, },
{ {
align: 'center', align: 'center',
label: t('Size'), label: t('Siz.'),
toolTip: t('Size'),
name: 'size', name: 'size',
width: '35px', width: '35px',
isEditable: false, isEditable: false,
@ -63,8 +81,18 @@ const columns = [
{ {
align: 'center', align: 'center',
label: t('Sti.'), label: t('Sti.'),
toolTip: t('Printed Stickers/Stickers'),
name: 'stickers', name: 'stickers',
component: 'number', component: 'number',
attrs: {
positive: false,
},
cellEvent: {
'update:modelValue': (value, oldValue, row) => {
row['quantity'] = value * row['packing'];
row['amount'] = row['quantity'] * row['buyingValue'];
},
},
width: '35px', width: '35px',
}, },
{ {
@ -92,11 +120,49 @@ const columns = [
label: 'Pack', label: 'Pack',
name: 'packing', name: 'packing',
component: 'number', component: 'number',
cellEvent: {
'update:modelValue': (value, oldValue, row) => {
console.log('oldValue: ', oldValue);
const oldPacking = oldValue === 1 || oldValue === null ? 1 : oldValue;
row['weight'] = (row['weight'] * value) / oldPacking;
row['quantity'] = row['stickers'] * value;
row['amount'] = row['quantity'] * row['buyingValue'];
},
},
width: '35px', width: '35px',
style: (row) => { style: (row) => {
if (row.groupingMode === 'grouping') if (row.groupingMode === 'grouping')
return { color: 'var(--vn-label-color)' }; return { color: 'var(--vn-label-color)' };
}, },
/* append: {
name: 'groupingMode',
h: (row) =>
h(QCheckbox, {
'data-name': 'groupingMode',
modelValue: row['groupingMode'] === 'packing',
size: 'sm',
'onUpdate:modelValue': (value) => {
console.log('entra');
if (value) row['groupingMode'] = 'packing';
else row['groupingMode'] = 'grouping';
},
onClick: (event) => {
console.log('eventOnClick: ', event);
},
}),
}, */
},
{
align: 'center',
label: 'Group',
name: 'groupingMode',
component: 'toggle',
attrs: {
trueValue: 'grouping',
falseValue: 'packing',
indeterminateValue: null,
},
width: '35px',
}, },
{ {
align: 'center', align: 'center',
@ -113,17 +179,31 @@ const columns = [
label: t('Quantity'), label: t('Quantity'),
name: 'quantity', name: 'quantity',
component: 'number', component: 'number',
width: '50px', attrs: {
style: (row) => { positive: false,
if (row?.quantity !== row?.stickers * row?.packing)
return { color: 'var(--q-negative)' };
}, },
cellEvent: {
'update:modelValue': (value, oldValue, row) => {
row['amount'] = value * row['buyingValue'];
},
},
width: '50px',
style: getQuantityStyle,
}, },
{ {
align: 'center', align: 'center',
label: 'Cost.', label: t('Cost'),
toolTip: t('Buying value'),
name: 'buyingValue', name: 'buyingValue',
component: 'number', component: 'number',
attrs: {
positive: false,
},
cellEvent: {
'update:modelValue': (value, oldValue, row) => {
row['amount'] = row['quantity'] * value;
},
},
width: '50px', width: '50px',
}, },
{ {
@ -131,13 +211,17 @@ const columns = [
label: t('Amount'), label: t('Amount'),
name: 'amount', name: 'amount',
width: '50px', width: '50px',
style: () => { component: 'number',
return { color: 'var(--vn-label-color)' }; attrs: {
positive: false,
}, },
isEditable: false,
style: getAmountStyle,
}, },
{ {
align: 'center', align: 'center',
label: t('Package'), label: t('Pack.'),
toolTip: t('Package'),
name: 'price2', name: 'price2',
component: 'number', component: 'number',
width: '35px', width: '35px',
@ -147,13 +231,40 @@ const columns = [
label: t('Box'), label: t('Box'),
name: 'price3', name: 'price3',
component: 'number', component: 'number',
cellEvent: {
'update:modelValue': (value, row) => {
/*
Call db.execV("UPDATE vn.item SET " & _
"typeFk = # " & _
",producerFk = # " & _
",minPrice = # " & _
",box = # " & _
",hasMinPrice = # " & _
",comment = # " & _
"WHERE id = # " _
, Me.tipo_id _
, Me.producer_id _
, Me.PVP _
, Me.caja _
, Me.Min _
, Nz(Me.reference, 0) _
, Me.Id_Article _
)
Me.Tarifa2 = Me.Tarifa2 * (Me.Tarifa3 / Me.Tarifa3.OldValue)
Call actualizar_compra
Me.sincro = True
*/
},
},
width: '35px', width: '35px',
}, },
{ {
align: 'center', align: 'center',
label: 'Min.', label: 'Min.',
toolTip: t('Minimum price'),
name: 'minPrice', name: 'minPrice',
component: 'number', component: 'number',
isEditable: false,
width: '35px', width: '35px',
style: (row) => { style: (row) => {
if (row?.hasMinPrice) if (row?.hasMinPrice)
@ -163,21 +274,27 @@ const columns = [
{ {
align: 'center', align: 'center',
label: t('P.Sen'), label: t('P.Sen'),
toolTip: t('Packing sent'),
name: 'packingOut', name: 'packingOut',
component: 'number', component: 'number',
isEditable: false,
width: '40px', width: '40px',
}, },
{ {
align: 'center', align: 'center',
label: t('Com.'), label: t('Com.'),
toolTip: t('Comment'),
name: 'comment', name: 'comment',
component: 'input', component: 'input',
isEditable: false,
width: '55px', width: '55px',
}, },
{ {
align: 'center', align: 'center',
label: 'Prod.', label: 'Prod.',
toolTip: t('Producer'),
name: 'subName', name: 'subName',
isEditable: false,
width: '45px', width: '45px',
style: () => { style: () => {
return { color: 'var(--vn-label-color)' }; return { color: 'var(--vn-label-color)' };
@ -185,43 +302,148 @@ const columns = [
}, },
{ {
align: 'center', align: 'center',
label: 'Tags', label: t('Tags'),
name: 'tags', name: 'tags',
width: '120px', width: '125px',
columnSearch: false, columnSearch: false,
}, },
{ {
align: 'center', align: 'center',
label: 'Comp.', label: 'Comp.',
toolTip: t('Company'),
name: 'company_name', name: 'company_name',
component: 'input',
isEditable: false,
width: '35px', width: '35px',
}, },
]; ];
function getQuantityStyle(row) {
if (row?.quantity !== row?.stickers * row?.packing)
return { color: 'var(--q-negative)' };
}
function getAmountStyle(row) {
if (row?.isChecked) return { color: 'var(--q-positive)' };
return { color: 'var(--vn-label-color)' };
}
onMounted(() => { onMounted(() => {
console.log('viewMode: ', $props.editableMode);
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
}); });
</script> </script>
<template> <template>
<VnSubToolbar /> <QToggle
toggle-indeterminate
toggle-order="ft"
v-model="cyan"
label="'ft' order + toggle-indeterminate"
color="cyan"
/>
<Teleport to="#st-data" v-if="stateStore?.isSubToolbarShown() && editableMode">
<QBtnGroup push style="column-gap: 1px">
<QBtn icon="calculate" color="primary" flat @click="console.log('calculate')">
<QTooltip>{{ t('tableActions.openBucketCalculator') }}</QTooltip>
</QBtn>
<QBtnDropdown
icon="box_edit"
color="primary"
flat
tool-tip="test"
@click="console.log('request_quote')"
:title="t('tableActions.setSaleMode')"
>
<div>
<QList>
<QItem clickable v-close-popup @click="setSaleMode('packing')">
<QItemSection>
<QItemLabel>Packing</QItemLabel>
</QItemSection>
</QItem>
<QItem clickable v-close-popup @click="setSaleMode('packing')">
<QItemSection>
<QItemLabel>Grouping</QItemLabel>
</QItemSection>
</QItem>
<QItem label="Grouping" />
</QList>
</div>
</QBtnDropdown>
<QBtn
icon="invert_colors"
color="primary"
flat
@click="console.log('price_check')"
>
<QTooltip>{{ t('tableActions.openCalculator') }}</QTooltip>
</QBtn>
<QBtn
icon="exposure_neg_1"
color="primary"
flat
@click="console.log('request_quote')"
title="test"
>
<QTooltip>{{ t('tableActions.invertQuantitySign') }}</QTooltip>
</QBtn>
<QBtn
icon="price_check"
color="primary"
flat
@click="console.log('request_quote')"
>
<QTooltip>{{ t('tableActions.checkAmount') }}</QTooltip>
</QBtn>
<QBtn
icon="price_check"
color="primary"
flat
@click="console.log('request_quote')"
>
<QTooltip>{{ t('tableActions.setMinPrice') }}</QTooltip>
</QBtn>
</QBtnGroup>
</Teleport>
<FetchData
ref="footerFetchDataRef"
:url="`Entries/${entityId}/getBuyList`"
:params="{ groupBy: 'GROUP BY b.entryFk' }"
@on-fetch="
(data) => {
console.log('data: ', data);
footer = data[0];
}
"
auto-load
/>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="EntryBuys" data-key="EntryBuys"
:url="`Entries/${route.params.id}/getBuys`" :url="`Entries/${entityId}/getBuyList`"
save-url="Buys/crud"
:disable-option="{ card: true }" :disable-option="{ card: true }"
v-model:selected="selectedRows" v-model:selected="selectedRows"
:table="{ :table="
editableMode
? {
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
}" }
: {}
"
:is-editable="editableMode"
:without-header="!editableMode"
:with-filters="editableMode"
:right-search="false" :right-search="false"
:row-click="false" :row-click="false"
:columns="columns" :columns="columns"
class="buyList" class="buyList"
is-editable table-height="84vh"
auto-load auto-load
footer
> >
<template #column-hex> <template #column-hex="{ row }">
<VnColor :colors="['#ff0000', '#ffff00', '#ff0000']" style="height: 100%" /> <VnColor :colors="row?.hexJson" style="height: 100%" />
</template> </template>
<template #column-name="{ row }"> <template #column-name="{ row }">
<span class="link"> <span class="link">
@ -233,29 +455,57 @@ onMounted(() => {
<FetchedTags :item="row" :columns="3" /> <FetchedTags :item="row" :columns="3" />
</template> </template>
<template #column-stickers="{ row }"> <template #column-stickers="{ row }">
<span style="color: var(--vn-label-color)">{{ row.printedStickers }}</span> <span :class="editableMode ? 'editable-text' : ''">
<span style="color: var(--vn-label-color)">{{
row.printedStickers
}}</span>
<span>/{{ row.stickers }}</span> <span>/{{ row.stickers }}</span>
</span>
</template>
<template #column-footer-stickers>
<div>
<span style="color: var(--vn-label-color)">{{
footer?.printedStickers
}}</span>
<span>/{{ footer?.stickers }}</span>
</div>
</template>
<template #column-footer-weight>
{{ footer?.weight }}
</template>
<template #column-footer-quantity>
<span :style="getQuantityStyle(footer)">
{{ footer?.quantity }}
</span>
</template>
<template #column-footer-amount>
<span :style="getAmountStyle(footer)">
{{ footer?.amount }}
</span>
</template> </template>
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped>
.q-checkbox__inner--dark {
&__inner {
border-radius: 0% !important;
background-color: rosybrown;
}
}
</style>
<i18n> <i18n>
es: es:
Article: Artículo3 Article: Artículo
Size: Med. Siz.: Med.
Size: Medida
Sti.: Eti. Sti.: Eti.
Bucket: Cubo Bucket: Cubo
Quantity: Cantidad Quantity: Cantidad
Amount: Importe Amount: Importe
Pack.: Paq.
Package: Paquete Package: Paquete
Box: Caja Box: Caja
P.Sen: P.Env P.Sen: P.Env
Packing sent: Packing envíos
Com.: Ref. Com.: Ref.
Comment: Referencia
Minimum price: Precio mínimo
Printed Stickers/Stickers: Etiquetas impresas/Etiquetas
Cost: Cost.
Buying value: Coste
Producer: Productor
Company: Compañia
Tags: Etiquetas
</i18n> </i18n>

View File

@ -1,17 +1,17 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import filter from './EntryFilter.js'; import filter from './EntryFilter.js';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import axios from 'axios';
import { useRouter } from 'vue-router';
const { push } = useRouter();
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -55,9 +55,22 @@ const getEntryRedirectionFilter = (entry) => {
}); });
}; };
const showEntryReport = () => { function showEntryReport() {
openReport(`Entries/${route.params.id}/entry-order-pdf`); openReport(`Entries/${entityId.value}/entry-order-pdf`);
}; }
function recalculateRates() {
console.log('recalculateRates');
}
async function cloneEntry() {
console.log('cloneEntry');
await axios
.post(`Entries/${entityId.value}/cloneEntry`)
.then((response) => push(`/entry/${response.data[0].vNewEntryFk}`));
}
async function deleteEntry() {
console.log('deleteEntry');
await axios.post(`Entries/${entityId.value}/deleteEntry`).then(() => push(`/entry/`));
}
</script> </script>
<template> <template>
@ -73,14 +86,39 @@ const showEntryReport = () => {
<QItem v-ripple clickable @click="showEntryReport(entity)"> <QItem v-ripple clickable @click="showEntryReport(entity)">
<QItemSection>{{ t('Show entry report') }}</QItemSection> <QItemSection>{{ t('Show entry report') }}</QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable @click="recalculateRates(entity)">
<QItemSection>{{ t('Recalculate rates') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="cloneEntry(entity)">
<QItemSection>{{ t('Clone') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="deleteEntry(entity)">
<QItemSection>{{ t('Delete') }}</QItemSection>
</QItem>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" /> <VnLv :label="t('Travel')">
<VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" /> <template #value>
<VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" /> <span class="link" v-if="entity?.travelFk">
{{ entity.travel?.agency?.name }}
{{ entity.travel?.warehouseOut?.code }} &rarr;
{{ entity.travel?.warehouseIn?.code }}
<TravelDescriptorProxy :id="entity?.travelFk" />
</span>
</template>
</VnLv>
<VnLv <VnLv
:label="t('globals.warehouseOut')" :label="t('entry.summary.travelShipped')"
:value="entity.travel?.warehouseOut?.name" :value="toDate(entity.travel?.shipped)"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entity.travel?.landed)"
/>
<VnLv :label="t('entry.summary.currency')" :value="entity?.currency?.code" />
<VnLv
:label="t('entry.summary.invoiceAmount')"
:value="entity?.invoiceAmount"
/> />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
@ -107,6 +145,14 @@ const showEntryReport = () => {
}}</QTooltip }}</QTooltip
> >
</QIcon> </QIcon>
<QIcon
v-if="!entity?.travelFk"
name="vn:deletedTicket"
size="xs"
color="primary"
>
<QTooltip>{{ t('This entry is deleted') }}</QTooltip>
</QIcon>
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{ entity }"> <template #actions="{ entity }">
@ -153,6 +199,7 @@ const showEntryReport = () => {
</template> </template>
<i18n> <i18n>
es: es:
Travel: Envío
Supplier card: Ficha del proveedor Supplier card: Ficha del proveedor
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual All entries with current supplier: Todas las entradas con el proveedor actual
@ -162,4 +209,5 @@ es:
Virtual entry: Es una redada Virtual entry: Es una redada
shipped: Enviado shipped: Enviado
landed: Recibido landed: Recibido
This entry is deleted: Esta entrada está eliminada
</i18n> </i18n>

View File

@ -9,6 +9,7 @@ export default {
'shipped', 'shipped',
'agencyModeFk', 'agencyModeFk',
'warehouseOutFk', 'warehouseOutFk',
'warehouseInFk',
'daysInForward', 'daysInForward',
], ],
include: [ include: [
@ -21,13 +22,13 @@ export default {
{ {
relation: 'warehouseOut', relation: 'warehouseOut',
scope: { scope: {
fields: ['name'], fields: ['name', 'code'],
}, },
}, },
{ {
relation: 'warehouseIn', relation: 'warehouseIn',
scope: { scope: {
fields: ['name'], fields: ['name', 'code'],
}, },
}, },
], ],
@ -39,5 +40,11 @@ export default {
fields: ['id', 'nickname'], fields: ['id', 'nickname'],
}, },
}, },
{
relation: 'currency',
scope: {
fields: ['id', 'code'],
},
},
], ],
}; };

View File

@ -2,15 +2,15 @@
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import EntryBuys from './EntryBuys.vue';
import { toDate, toCurrency } from 'src/filters'; import VnTitle from 'src/components/common/VnTitle.vue';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnToSummary from 'src/components/ui/VnToSummary.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue';
const route = useRoute(); const route = useRoute();
@ -30,117 +30,6 @@ const entry = ref();
const entryBuys = ref([]); const entryBuys = ref([]);
const entryUrl = ref(); const entryUrl = ref();
onMounted(async () => {
entryUrl.value = (await getUrl('entry/')) + entityId.value;
});
const tableColumnComponents = {
quantity: {
component: () => 'span',
props: () => {},
},
stickers: {
component: () => 'span',
props: () => {},
event: () => {},
},
packagingFk: {
component: () => 'span',
props: () => {},
event: () => {},
},
weight: {
component: () => 'span',
props: () => {},
event: () => {},
},
packing: {
component: () => 'span',
props: () => {},
event: () => {},
},
grouping: {
component: () => 'span',
props: () => {},
event: () => {},
},
buyingValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
amount: {
component: () => 'span',
props: () => {},
event: () => {},
},
pvp: {
component: () => 'span',
props: () => {},
event: () => {},
},
};
const entriesTableColumns = computed(() => {
return [
{
label: t('globals.quantity'),
field: 'quantity',
name: 'quantity',
align: 'left',
},
{
label: t('entry.summary.stickers'),
field: 'stickers',
name: 'stickers',
align: 'left',
},
{
label: t('entry.summary.package'),
field: 'packagingFk',
name: 'packagingFk',
align: 'left',
},
{
label: t('globals.weight'),
field: 'weight',
name: 'weight',
align: 'left',
},
{
label: t('entry.summary.packing'),
field: 'packing',
name: 'packing',
align: 'left',
},
{
label: t('entry.summary.grouping'),
field: 'grouping',
name: 'grouping',
align: 'left',
},
{
label: t('entry.summary.buyingValue'),
field: 'buyingValue',
name: 'buyingValue',
align: 'left',
format: (value) => toCurrency(value),
},
{
label: t('entry.summary.import'),
name: 'amount',
align: 'left',
format: (_, row) => toCurrency(row.buyingValue * row.quantity),
},
{
label: t('entry.summary.pvp'),
name: 'pvp',
align: 'left',
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
},
];
});
async function setEntryData(data) { async function setEntryData(data) {
if (data) entry.value = data; if (data) entry.value = data;
await fetchEntryBuys(); await fetchEntryBuys();
@ -150,8 +39,11 @@ const fetchEntryBuys = async () => {
const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`); const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`);
if (data) entryBuys.value = data; if (data) entryBuys.value = data;
}; };
</script>
onMounted(async () => {
entryUrl.value = (await getUrl('entry/')) + entityId.value;
});
</script>
<template> <template>
<CardSummary <CardSummary
ref="summaryRef" ref="summaryRef"
@ -168,81 +60,35 @@ const fetchEntryBuys = async () => {
/> />
</template> </template>
<template #header> <template #header>
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> <span>{{ entry.id }} - {{ entry?.supplier?.nickname }}</span>
</template> </template>
<template #body> <template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<router-link <VnTitle
:to="{ name: 'EntryBasicData', params: { id: entityId } }" :url="`#/entry/{{ entityId }}/basicData`"
class="header header-link" :text="t('globals.summary.basicData')"
> />
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" />
</router-link>
<div class="card-group"> <div class="card-group">
<div class="card-content"> <div class="card-content">
<VnLv <VnLv
:label="t('entry.summary.commission')" :label="t('entry.summary.commission')"
:value="entry.commission" :value="entry?.commission"
/> />
<VnLv <VnLv
:label="t('entry.summary.currency')" :label="t('entry.summary.currency')"
:value="entry.currency?.name" :value="entry?.currency?.name"
/> />
<VnLv :label="t('globals.company')" :value="entry.company.code" /> <VnLv
<VnLv :label="t('globals.reference')" :value="entry.reference" /> :label="t('globals.company')"
:value="entry?.company?.code"
/>
<VnLv :label="t('globals.reference')" :value="entry?.reference" />
<VnLv <VnLv
:label="t('entry.summary.invoiceNumber')" :label="t('entry.summary.invoiceNumber')"
:value="entry.invoiceNumber" :value="entry?.invoiceNumber"
/> />
</div> </div>
<div class="card-content"> <div class="card-content">
<VnLv :label="t('entry.summary.travelReference')">
<template #value>
<span class="link">
{{ entry.travel.ref }}
<TravelDescriptorProxy :id="entry.travel.id" />
</span>
</template>
</VnLv>
<VnLv
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency?.name"
/>
<VnLv
:label="t('shipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv
:label="t('globals.warehouseOut')"
:value="entry.travel.warehouseOut?.name"
/>
<VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
<VnLv
:label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name"
/>
<QCheckbox
:label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered"
:disable="true"
/>
<QCheckbox
:label="t('entry.summary.travelReceived')"
v-model="entry.travel.isReceived"
:disable="true"
/>
</div>
</div>
</QCard>
<QCard class="vn-one">
<router-link
:to="{ name: 'TravelSummary', params: { id: entry.travel.id } }"
class="header header-link"
>
{{ t('Travel data') }}
<QIcon name="open_in_new" />
</router-link>
<QCheckbox <QCheckbox
:label="t('entry.summary.ordered')" :label="t('entry.summary.ordered')"
v-model="entry.isOrdered" v-model="entry.isOrdered"
@ -263,31 +109,88 @@ const fetchEntryBuys = async () => {
v-model="entry.isExcludedFromAvailable" v-model="entry.isExcludedFromAvailable"
:disable="true" :disable="true"
/> />
</div>
</div>
</QCard>
<QCard class="vn-one" v-if="entry?.travelFk">
<VnTitle
:url="`#/travel/{{ entry.travel.id }}/summary`"
:text="t('globals.summary.basicData')"
/>
<div class="card-group">
<div class="card-content">
<VnLv :label="t('entry.summary.travelReference')">
<template #value>
<span class="link">
{{ entry.travel.ref }}
<TravelDescriptorProxy :id="entry.travel.id" />
</span>
</template>
</VnLv>
<VnLv
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency?.name"
/>
<VnLv
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv
:label="t('globals.warehouseOut')"
:value="entry.travel.warehouseOut?.name"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
<VnLv
:label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name"
/>
</div>
<div class="card-content">
<QCheckbox
:label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered"
:disable="true"
/>
<QCheckbox
:label="t('entry.summary.travelReceived')"
v-model="entry.travel.isReceived"
:disable="true"
/>
</div>
</div>
</QCard>
<QCard class="vn-max">
<VnTitle
:url="`#/entry/{{ entityId }}/buys`"
:text="t('entry.summary.buys')"
/>
<EntryBuys v-if="entityId" :id="entityId" :editable-mode="false" />
</QCard> </QCard>
</template> </template>
</CardSummary> </CardSummary>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.separation-row {
background-color: var(--vn-section-color) !important;
}
.card-group { .card-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.card-content { .card-content {
margin-bottom: 16px; /* Para dar espacio entre las secciones */ display: flex;
flex-direction: column;
text-overflow: ellipsis;
} }
@media (min-width: 1010px) { @media (min-width: 1010px) {
.card-group { .card-group {
flex-direction: row; /* Coloca los contenidos en fila cuando el ancho es mayor a 600px */ flex-direction: row;
} }
.card-content { .card-content {
flex: 1; /* Haz que las secciones ocupen el mismo espacio */ flex: 1;
margin-right: 16px; /* Espaciado entre las dos primeras tarjetas */ margin-right: 16px;
} }
} }
</style> </style>
@ -295,4 +198,5 @@ const fetchEntryBuys = async () => {
<i18n> <i18n>
es: es:
Travel data: Datos envío Travel data: Datos envío
InvoiceIn data: Datos factura
</i18n> </i18n>

View File

@ -18,6 +18,7 @@ const props = defineProps({
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const companiesOptions = ref([]); const companiesOptions = ref([]);
const entryFilterPanel = ref();
</script> </script>
<template> <template>
@ -37,7 +38,7 @@ const companiesOptions = ref([]);
@on-fetch="(data) => (currenciesOptions = data)" @on-fetch="(data) => (currenciesOptions = data)"
auto-load auto-load
/> />
<VnFilterPanel :data-key="props.dataKey" :search-button="true"> <VnFilterPanel ref="entryFilterPanel" :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -47,70 +48,82 @@ const companiesOptions = ref([]);
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <QCheckbox
v-model="params.search" :label="t('params.isExcludedFromAvailable')"
:label="t('entryFilter.filter.search')" v-model="params.isExcludedFromAvailable"
is-outlined toggle-indeterminate
/> >
<QTooltip>
{{
t(
'entry.list.tableVisibleColumns.isExcludedFromAvailable'
)
}}
</QTooltip>
</QCheckbox>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.isOrdered')"
v-model="params.isOrdered"
toggle-indeterminate
>
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isOrdered') }}
</QTooltip>
</QCheckbox>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <QCheckbox
v-model="params.reference" :label="t('params.isReceived')"
:label="t('entryFilter.filter.reference')" v-model="params.isReceived"
is-outlined toggle-indeterminate
/> >
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isReceived') }}
</QTooltip>
</QCheckbox>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.isRaid')"
v-model="params.isRaid"
toggle-indeterminate
>
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isRaid') }}
</QTooltip>
</QCheckbox>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <QCheckbox
v-model="params.invoiceNumber" :label="t('entry.list.tableVisibleColumns.isConfirmed')"
:label="t('params.invoiceNumber')" v-model="params.isConfirmed"
is-outlined toggle-indeterminate
/> >
<QTooltip>
{{ t('entry.list.tableVisibleColumns.isConfirmed') }}
</QTooltip>
</QCheckbox>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInputDate
v-model="params.travelFk" :label="t('params.landed')"
:label="t('params.travelFk')" v-model="params.landed"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.companyFk')"
v-model="params.companyFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="companiesOptions" is-outlined
option-value="id"
option-label="code"
hide-selected
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelect <VnInput v-model="params.id" label="Id" is-outlined />
:label="t('params.currencyFk')"
v-model="params.currencyFk"
@update:model-value="searchFn()"
:options="currenciesOptions"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -143,56 +156,90 @@ const companiesOptions = ref([]);
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInput
:label="t('params.created')" v-model="params.invoiceNumber"
v-model="params.created" :label="t('params.invoiceNumber')"
@update:model-value="searchFn()"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInput
:label="t('params.from')" v-model="params.reference"
v-model="params.from" :label="t('entryFilter.filter.reference')"
@update:model-value="searchFn()"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnSelect
:label="t('params.to')" :label="t('params.agencyModeId')"
v-model="params.to" v-model="params.agencyModeId"
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="AgencyModes"
:fields="['id', 'name']"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.evaNotes"
:label="t('params.evaNotes')"
is-outlined is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <VnSelect
:label="t('params.isBooked')" :label="t('params.warehouseOutFk')"
v-model="params.isBooked" v-model="params.warehouseOutFk"
toggle-indeterminate @update:model-value="searchFn()"
/> url="Warehouses"
</QItemSection> :fields="['id', 'name']"
<QItemSection> hide-selected
<QCheckbox dense
:label="t('params.isConfirmed')" outlined
v-model="params.isConfirmed" rounded
toggle-indeterminate
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <VnSelect
:label="t('params.isOrdered')" :label="t('params.warehouseInFk')"
v-model="params.isOrdered" v-model="params.warehouseInFk"
toggle-indeterminate @update:model-value="searchFn()"
url="Warehouses"
:fields="['id', 'name']"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.entryTypeCode')"
v-model="params.entryTypeCode"
@update:model-value="searchFn()"
url="EntryTypes"
:fields="['code', 'description']"
option-value="code"
option-label="description"
hide-selected
dense
outlined
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -203,30 +250,38 @@ const companiesOptions = ref([]);
<i18n> <i18n>
en: en:
params: params:
isExcludedFromAvailable: Inventory
invoiceNumber: Invoice number
travelFk: Travel
companyFk: Company
currencyFk: Currency
supplierFk: Supplier
from: From
to: To
created: Created
isBooked: Booked
isConfirmed: Confirmed
isOrdered: Ordered isOrdered: Ordered
isReceived: Received
isConfirmed: Confirmed
isRaid: Raid
landed: Date
id: Id
supplierFk: Supplier
invoiceNumber: Invoice number
reference: Ref/Alb/Guide
agencyModeId: Agency mode
evaNotes: Notes
warehouseOutFk: Origin
warehouseInFk: Destiny
entryTypeCode: Entry type
hasToShowDeletedEntries: Show deleted entries
es: es:
params: params:
isExcludedFromAvailable: Inventario
invoiceNumber: Núm. factura
travelFk: Envío
companyFk: Empresa
currencyFk: Moneda
supplierFk: Proveedor
from: Desde
to: Hasta
created: Fecha creación
isBooked: Asentado
isConfirmed: Confirmado
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Confirmado
isReceived: Recibida
isRaid: Raid
landed: Fecha
id: Id
supplierFk: Proveedor
invoiceNumber: Núm. factura
reference: Ref/Alb/Guía
agencyModeId: Modo agencia
evaNotes: Notas
warehouseOutFk: Origen
warehouseInFk: Destino
entryTypeCode: Tipo de entrada
hasToShowDeletedEntries: Mostrar entradas eliminadas
</i18n> </i18n>

View File

@ -1,23 +1,24 @@
<script setup> <script setup>
import { onMounted, ref, computed } from 'vue'; import axios from 'axios';
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import { onBeforeMount } from 'vue';
import EntryFilter from './EntryFilter.vue'; import EntryFilter from './EntryFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import EntrySummary from './Card/EntrySummary.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const defaultEntry = ref({});
const state = useState();
const user = state.getUser();
const { viewSummary } = useSummaryDialog(); const entryQueryFilter = {
const entryFilter = {
include: [ include: [
{ {
relation: 'suppliers', relation: 'suppliers',
@ -42,11 +43,50 @@ const entryFilter = {
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'status', align: 'center',
columnFilter: false, label: 'Ex',
toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable',
component: 'checkbox',
width: '35px',
}, },
{ {
align: 'left', align: 'center',
label: 'Pe',
toolTip: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered',
component: 'checkbox',
width: '35px',
},
{
align: 'center',
label: 'Le',
toolTip: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed',
component: 'checkbox',
width: '35px',
},
{
align: 'center',
label: 'Re',
toolTip: t('entry.list.tableVisibleColumns.isReceived'),
name: 'isReceived',
component: 'checkbox',
width: '35px',
},
{
align: 'center',
label: t('entry.list.tableVisibleColumns.landed'),
name: 'landed',
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)),
width: '105px',
},
{
align: 'right',
label: t('globals.id'), label: t('globals.id'),
name: 'id', name: 'id',
isId: true, isId: true,
@ -54,30 +94,6 @@ const columns = computed(() => [
condition: () => true, condition: () => true,
}, },
}, },
{
align: 'left',
label: t('globals.reference'),
name: 'reference',
isTitle: true,
component: 'input',
columnField: {
component: null,
},
create: true,
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.created'),
name: 'created',
create: true,
cardVisible: true,
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)),
},
{ {
align: 'left', align: 'left',
label: t('entry.list.tableVisibleColumns.supplierFk'), label: t('entry.list.tableVisibleColumns.supplierFk'),
@ -89,117 +105,168 @@ const columns = computed(() => [
url: 'suppliers', url: 'suppliers',
fields: ['id', 'name'], fields: ['id', 'name'],
}, },
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName),
}, },
{ {
align: 'center', align: 'left',
label: t('entry.list.tableVisibleColumns.isBooked'), label: t('entry.list.tableVisibleColumns.invoiceNumber'),
name: 'isBooked', name: 'invoiceNumber',
cardVisible: true, component: 'input',
create: true,
component: 'checkbox',
},
{
align: 'center',
label: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'center',
label: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered',
cardVisible: true,
create: true,
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
label: t('entry.list.tableVisibleColumns.companyFk'), label: t('entry.list.tableVisibleColumns.reference'),
name: 'reference',
isTitle: true,
component: 'input',
columnField: {
component: null,
},
cardVisible: true,
},
{
align: 'left',
label: 'AWB',
name: 'awbCode',
component: 'input',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.agencyModeId'),
name: 'agencyModeId',
cardVisible: true,
component: 'select',
attrs: {
url: 'agencyModes',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.agencyModeName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.evaNotes'),
name: 'evaNotes',
component: 'input',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseOutFk'),
name: 'warehouseOutFk',
cardVisible: true,
component: 'select',
attrs: {
url: 'warehouses',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseOutName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseInFk'),
name: 'warehouseInFk',
cardVisible: true,
component: 'select',
attrs: {
url: 'warehouses',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseInName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.entryTypeDescription'),
name: 'entryTypeCode',
cardVisible: true,
columnFilter: {
component: 'select',
attrs: {
optionValue: 'code',
optionLabel: 'description',
url: 'entryTypes',
fields: ['code', 'description'],
},
},
format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription),
},
{
name: 'dated',
label: t('entry.list.tableVisibleColumns.dated'),
component: 'date',
cardVisible: false,
visible: false,
create: true,
},
{
name: 'companyFk', name: 'companyFk',
label: t('entry.list.tableVisibleColumns.companyFk'),
cardVisible: false,
visible: false,
create: true,
component: 'select', component: 'select',
attrs: { attrs: {
url: 'companies', optionValue: 'id',
fields: ['id', 'code'],
optionLabel: 'code', optionLabel: 'code',
optionValue: 'id', url: 'Companies',
}, },
columnField: {
component: null,
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode),
}, },
{ {
align: 'left',
label: t('entry.list.tableVisibleColumns.travelFk'),
name: 'travelFk', name: 'travelFk',
component: 'select', label: t('entry.list.tableVisibleColumns.travelFk'),
attrs: { cardVisible: false,
url: 'travels', visible: false,
fields: ['id', 'ref'],
optionLabel: 'ref',
optionValue: 'id',
},
columnField: {
component: null,
},
create: true, create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.invoiceAmount'),
name: 'invoiceAmount',
cardVisible: true,
},
{
align: 'center',
label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:inventory',
},
columnFilter: {
inWhere: true,
},
component: 'checkbox',
},
{
align: 'center',
label: t('entry.list.tableVisibleColumns.isRaid'),
name: 'isRaid',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:net',
},
columnFilter: {
inWhere: true,
},
component: 'checkbox',
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, EntrySummary),
isPrimary: true,
},
],
}, },
]); ]);
function getBadgeAttrs(row) {
const date = row.landed;
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let timeDiff = today - timeTicket;
if (timeDiff > 0) return { color: 'warning', 'text-color': 'black' };
switch (row.entryTypeCode) {
case 'regularization':
case 'life':
case 'internal':
case 'inventory':
if (!row.isOrdered || !row.isConfirmed)
return { color: 'negative', 'text-color': 'black' };
break;
case 'product':
case 'packaging':
case 'devaluation':
case 'payment':
case 'transport':
if (
row.invoiceAmount === null ||
(row.invoiceNumber === null && row.reference === null) ||
!row.isOrdered ||
!row.isConfirmed
)
return { color: 'negative', 'text-color': 'black' };
break;
default:
break;
}
if (timeDiff < 0) return { color: 'info', 'text-color': 'black' };
return { color: 'transparent' };
}
onBeforeMount(async () => {
defaultEntry.value = (await axios.get('EntryConfigs/findOne')).data;
});
</script> </script>
<template> <template>
<VnSearchbar <VnSearchbar
@ -214,40 +281,35 @@ const columns = computed(() => [
</template> </template>
</RightMenu> </RightMenu>
<VnTable <VnTable
v-if="defaultEntry.defaultSupplierFk"
ref="tableRef" ref="tableRef"
data-key="EntryList" data-key="EntryList"
url="Entries/filter" url="Entries/filter"
:filter="entryFilter" :filter="entryQueryFilter"
:create="{ :create="{
urlCreate: 'Entries', urlCreate: 'Entries',
title: t('Create entry'), title: t('Create entry'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {}, formInitialData: {
supplierFk: defaultEntry.defaultSupplierFk,
dated: Date.vnNew(),
companyFk: user?.companyFk,
},
}" }"
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
redirect="entry" redirect="entry"
:right-search="false" :right-search="false"
> >
<template #column-status="{ row }"> <template #column-landed="{ row }">
<div class="row q-gutter-xs"> <QBadge
<QIcon v-if="row?.travelFk"
v-if="!!row.isExcludedFromAvailable" v-bind="getBadgeAttrs(row)"
name="vn:inventory" class="q-pa-sm"
color="primary" style="font-size: 14px"
> >
<QTooltip>{{ {{ toDate(row.landed) }}
t('entry.list.tableVisibleColumns.isExcludedFromAvailable') </QBadge>
}}</QTooltip>
</QIcon>
<QIcon v-if="!!row.isRaid" name="vn:net" color="primary">
<QTooltip>
{{
t('globals.raid', { daysInForward: row.daysInForward })
}}</QTooltip
>
</QIcon>
</div>
</template> </template>
<template #column-supplierFk="{ row }"> <template #column-supplierFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
@ -255,12 +317,6 @@ const columns = computed(() => [
<SupplierDescriptorProxy :id="row.supplierFk" /> <SupplierDescriptorProxy :id="row.supplierFk" />
</span> </span>
</template> </template>
<template #column-travelFk="{ row }">
<span class="link" @click.stop>
{{ row.travelRef }}
<TravelDescriptorProxy :id="row.travelFk" />
</span>
</template>
</VnTable> </VnTable>
</template> </template>

View File

@ -36,6 +36,10 @@ const $props = defineProps({
type: Number, type: Number,
default: null, default: null,
}, },
proxyRender: {
type: Boolean,
default: false,
},
}); });
const { openCloneDialog } = cloneItem(); const { openCloneDialog } = cloneItem();
@ -171,7 +175,7 @@ const openRegularizeStockForm = () => {
</QCardActions> </QCardActions>
</template> </template>
<template #actions="{}"> <template #actions="{}">
<QCardActions class="row justify-center"> <QCardActions class="row justify-center" v-if="proxyRender">
<QBtn <QBtn
:to="{ :to="{
name: 'ItemDiary', name: 'ItemDiary',
@ -184,6 +188,16 @@ const openRegularizeStockForm = () => {
> >
<QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip> <QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
:to="{
name: 'ItemLastEntries',
}"
size="md"
icon="vn:regentry"
color="primary"
>
<QTooltip>{{ t('item.descriptor.itemLastEntries') }}</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </CardDescriptor>

View File

@ -4,7 +4,7 @@ import ItemSummary from './ItemSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
type: Number, type: [Number, String],
required: true, required: true,
}, },
dated: { dated: {
@ -30,6 +30,7 @@ const $props = defineProps({
:dated="dated" :dated="dated"
:sale-fk="saleFk" :sale-fk="saleFk"
:warehouse-fk="warehouseFk" :warehouse-fk="warehouseFk"
:proxy-render="true"
/> />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -117,6 +117,7 @@ item:
available: Available available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }' warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary itemDiary: Item diary
itemLastEntries: Last entries
producer: Producer producer: Producer
clone: clone:
title: All its properties will be copied title: All its properties will be copied

View File

@ -119,6 +119,7 @@ item:
available: Disponible available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }' warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta itemDiary: Registro de compra-venta
itemLastEntries: Últimas entradas
producer: Productor producer: Productor
clone: clone:
title: Todas sus propiedades serán copiadas title: Todas sus propiedades serán copiadas